diff options
author | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2019-02-13 16:23:34 +0100 |
---|---|---|
committer | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2019-02-14 10:37:21 +0000 |
commit | 38a9a29f4f9436cace7f0e7abf9c586057df8a4e (patch) | |
tree | c4e8c458dc595bc0ddb435708fa2229edfd00bd4 /chromium/net/third_party/quiche | |
parent | e684a3455bcc29a6e3e66a004e352dea4e1141e7 (diff) | |
download | qtwebengine-chromium-38a9a29f4f9436cace7f0e7abf9c586057df8a4e.tar.gz |
BASELINE: Update Chromium to 73.0.3683.37
Change-Id: I08c9af2948b645f671e5d933aca1f7a90ea372f2
Reviewed-by: Michael BrĂ¼ning <michael.bruning@qt.io>
Diffstat (limited to 'chromium/net/third_party/quiche')
264 files changed, 48237 insertions, 0 deletions
diff --git a/chromium/net/third_party/quiche/src/CONTRIBUTING.md b/chromium/net/third_party/quiche/src/CONTRIBUTING.md new file mode 100644 index 00000000000..3a93363a0f9 --- /dev/null +++ b/chromium/net/third_party/quiche/src/CONTRIBUTING.md @@ -0,0 +1,28 @@ +# How to Contribute + +We'd love to accept your patches and contributions to this project. There are +just a few small guidelines you need to follow. + +## Contributor License Agreement + +Contributions to this project must be accompanied by a Contributor License +Agreement. You (or your employer) retain the copyright to your contribution; +this simply gives us permission to use and redistribute your contributions as +part of the project. Head over to <https://cla.developers.google.com/> to see +your current agreements on file or to sign a new one. + +You generally only need to submit a CLA once, so if you've already submitted one +(even if it was for a different project), you probably don't need to do it +again. + +## Code reviews + +All submissions, including submissions by project members, require review. We +use Gerrit pull requests for this purpose. + +TODO: write up the contributing guidelines. + +## Community Guidelines + +This project follows [Google's Open Source Community +Guidelines](https://opensource.google.com/conduct/). diff --git a/chromium/net/third_party/quiche/src/LICENSE b/chromium/net/third_party/quiche/src/LICENSE new file mode 100644 index 00000000000..a32e00ce6be --- /dev/null +++ b/chromium/net/third_party/quiche/src/LICENSE @@ -0,0 +1,27 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/chromium/net/third_party/quiche/src/README.md b/chromium/net/third_party/quiche/src/README.md new file mode 100644 index 00000000000..4e2037083a1 --- /dev/null +++ b/chromium/net/third_party/quiche/src/README.md @@ -0,0 +1,9 @@ +# QUICHE + +QUICHE (QUIC, Http/2, Etc) is Google's implementation of QUIC and related +protocols. It powers Chromium as well as Google's QUIC servers and some other +projects. + +The code is currently in process of being moved from +https://cs.chromium.org/chromium/src/net/third_party/ into this repository. +Please excuse our appearance while we're under construction. diff --git a/chromium/net/third_party/quiche/src/http2/decoder/decode_buffer.cc b/chromium/net/third_party/quiche/src/http2/decoder/decode_buffer.cc new file mode 100644 index 00000000000..b24420a1b0e --- /dev/null +++ b/chromium/net/third_party/quiche/src/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/chromium/net/third_party/quiche/src/http2/decoder/decode_buffer.h b/chromium/net/third_party/quiche/src/http2/decoder/decode_buffer.h new file mode 100644 index 00000000000..3b213c2a861 --- /dev/null +++ b/chromium/net/third_party/quiche/src/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/chromium/net/third_party/quiche/src/http2/decoder/decode_buffer_test.cc b/chromium/net/third_party/quiche/src/http2/decoder/decode_buffer_test.cc new file mode 100644 index 00000000000..e8e3d3bf176 --- /dev/null +++ b/chromium/net/third_party/quiche/src/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/chromium/net/third_party/quiche/src/http2/decoder/decode_http2_structures.cc b/chromium/net/third_party/quiche/src/http2/decoder/decode_http2_structures.cc new file mode 100644 index 00000000000..71f6cce57bf --- /dev/null +++ b/chromium/net/third_party/quiche/src/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/chromium/net/third_party/quiche/src/http2/decoder/decode_http2_structures.h b/chromium/net/third_party/quiche/src/http2/decoder/decode_http2_structures.h new file mode 100644 index 00000000000..3f6a317231e --- /dev/null +++ b/chromium/net/third_party/quiche/src/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/chromium/net/third_party/quiche/src/http2/decoder/decode_http2_structures_test.cc b/chromium/net/third_party/quiche/src/http2/decoder/decode_http2_structures_test.cc new file mode 100644 index 00000000000..43bd7e8d040 --- /dev/null +++ b/chromium/net/third_party/quiche/src/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/chromium/net/third_party/quiche/src/http2/decoder/decode_status.cc b/chromium/net/third_party/quiche/src/http2/decoder/decode_status.cc new file mode 100644 index 00000000000..6a913f05e50 --- /dev/null +++ b/chromium/net/third_party/quiche/src/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/chromium/net/third_party/quiche/src/http2/decoder/decode_status.h b/chromium/net/third_party/quiche/src/http2/decoder/decode_status.h new file mode 100644 index 00000000000..fdecd224e38 --- /dev/null +++ b/chromium/net/third_party/quiche/src/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/chromium/net/third_party/quiche/src/http2/decoder/frame_decoder_state.cc b/chromium/net/third_party/quiche/src/http2/decoder/frame_decoder_state.cc new file mode 100644 index 00000000000..946a6ee8d31 --- /dev/null +++ b/chromium/net/third_party/quiche/src/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/chromium/net/third_party/quiche/src/http2/decoder/frame_decoder_state.h b/chromium/net/third_party/quiche/src/http2/decoder/frame_decoder_state.h new file mode 100644 index 00000000000..1581752f086 --- /dev/null +++ b/chromium/net/third_party/quiche/src/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/chromium/net/third_party/quiche/src/http2/decoder/frame_decoder_state_test_util.cc b/chromium/net/third_party/quiche/src/http2/decoder/frame_decoder_state_test_util.cc new file mode 100644 index 00000000000..7c13ef00ada --- /dev/null +++ b/chromium/net/third_party/quiche/src/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/chromium/net/third_party/quiche/src/http2/decoder/frame_decoder_state_test_util.h b/chromium/net/third_party/quiche/src/http2/decoder/frame_decoder_state_test_util.h new file mode 100644 index 00000000000..fc81a4ad90a --- /dev/null +++ b/chromium/net/third_party/quiche/src/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/chromium/net/third_party/quiche/src/http2/decoder/http2_frame_decoder.cc b/chromium/net/third_party/quiche/src/http2/decoder/http2_frame_decoder.cc new file mode 100644 index 00000000000..314dfbfa19d --- /dev/null +++ b/chromium/net/third_party/quiche/src/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/chromium/net/third_party/quiche/src/http2/decoder/http2_frame_decoder.h b/chromium/net/third_party/quiche/src/http2/decoder/http2_frame_decoder.h new file mode 100644 index 00000000000..6257bbeb825 --- /dev/null +++ b/chromium/net/third_party/quiche/src/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/chromium/net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener.cc b/chromium/net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener.cc new file mode 100644 index 00000000000..4cbce66d6f1 --- /dev/null +++ b/chromium/net/third_party/quiche/src/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/chromium/net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener.h b/chromium/net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener.h new file mode 100644 index 00000000000..b87512257a4 --- /dev/null +++ b/chromium/net/third_party/quiche/src/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/chromium/net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener_test_util.cc b/chromium/net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener_test_util.cc new file mode 100644 index 00000000000..bc8cf1d5f72 --- /dev/null +++ b/chromium/net/third_party/quiche/src/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/chromium/net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener_test_util.h b/chromium/net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener_test_util.h new file mode 100644 index 00000000000..d6e84efe414 --- /dev/null +++ b/chromium/net/third_party/quiche/src/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/chromium/net/third_party/quiche/src/http2/decoder/http2_frame_decoder_test.cc b/chromium/net/third_party/quiche/src/http2/decoder/http2_frame_decoder_test.cc new file mode 100644 index 00000000000..172bd62ef01 --- /dev/null +++ b/chromium/net/third_party/quiche/src/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/chromium/net/third_party/quiche/src/http2/decoder/http2_structure_decoder.cc b/chromium/net/third_party/quiche/src/http2/decoder/http2_structure_decoder.cc new file mode 100644 index 00000000000..e4f2c1af079 --- /dev/null +++ b/chromium/net/third_party/quiche/src/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/chromium/net/third_party/quiche/src/http2/decoder/http2_structure_decoder.h b/chromium/net/third_party/quiche/src/http2/decoder/http2_structure_decoder.h new file mode 100644 index 00000000000..46c88b85f9d --- /dev/null +++ b/chromium/net/third_party/quiche/src/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/chromium/net/third_party/quiche/src/http2/decoder/http2_structure_decoder_test.cc b/chromium/net/third_party/quiche/src/http2/decoder/http2_structure_decoder_test.cc new file mode 100644 index 00000000000..8d8da7bb577 --- /dev/null +++ b/chromium/net/third_party/quiche/src/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/chromium/net/third_party/quiche/src/http2/decoder/http2_structure_decoder_test_util.cc b/chromium/net/third_party/quiche/src/http2/decoder/http2_structure_decoder_test_util.cc new file mode 100644 index 00000000000..90805eab643 --- /dev/null +++ b/chromium/net/third_party/quiche/src/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/chromium/net/third_party/quiche/src/http2/decoder/http2_structure_decoder_test_util.h b/chromium/net/third_party/quiche/src/http2/decoder/http2_structure_decoder_test_util.h new file mode 100644 index 00000000000..4b533c78e28 --- /dev/null +++ b/chromium/net/third_party/quiche/src/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/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/altsvc_payload_decoder.cc b/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/altsvc_payload_decoder.cc new file mode 100644 index 00000000000..4e4d860afd5 --- /dev/null +++ b/chromium/net/third_party/quiche/src/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/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/altsvc_payload_decoder.h b/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/altsvc_payload_decoder.h new file mode 100644 index 00000000000..e3523f9e0d2 --- /dev/null +++ b/chromium/net/third_party/quiche/src/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/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/altsvc_payload_decoder_test.cc b/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/altsvc_payload_decoder_test.cc new file mode 100644 index 00000000000..bf928d3ae50 --- /dev/null +++ b/chromium/net/third_party/quiche/src/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/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/continuation_payload_decoder.cc b/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/continuation_payload_decoder.cc new file mode 100644 index 00000000000..aa9c8172f32 --- /dev/null +++ b/chromium/net/third_party/quiche/src/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/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/continuation_payload_decoder.h b/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/continuation_payload_decoder.h new file mode 100644 index 00000000000..63b16ae638b --- /dev/null +++ b/chromium/net/third_party/quiche/src/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/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/continuation_payload_decoder_test.cc b/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/continuation_payload_decoder_test.cc new file mode 100644 index 00000000000..63888d354a9 --- /dev/null +++ b/chromium/net/third_party/quiche/src/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/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/data_payload_decoder.cc b/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/data_payload_decoder.cc new file mode 100644 index 00000000000..b6b80414738 --- /dev/null +++ b/chromium/net/third_party/quiche/src/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/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/data_payload_decoder.h b/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/data_payload_decoder.h new file mode 100644 index 00000000000..1e083a93343 --- /dev/null +++ b/chromium/net/third_party/quiche/src/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/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/data_payload_decoder_test.cc b/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/data_payload_decoder_test.cc new file mode 100644 index 00000000000..e5253b93cd9 --- /dev/null +++ b/chromium/net/third_party/quiche/src/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/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/goaway_payload_decoder.cc b/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/goaway_payload_decoder.cc new file mode 100644 index 00000000000..cf7b673c5ee --- /dev/null +++ b/chromium/net/third_party/quiche/src/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/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/goaway_payload_decoder.h b/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/goaway_payload_decoder.h new file mode 100644 index 00000000000..7a50873d108 --- /dev/null +++ b/chromium/net/third_party/quiche/src/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/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/goaway_payload_decoder_test.cc b/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/goaway_payload_decoder_test.cc new file mode 100644 index 00000000000..1df52148687 --- /dev/null +++ b/chromium/net/third_party/quiche/src/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/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/headers_payload_decoder.cc b/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/headers_payload_decoder.cc new file mode 100644 index 00000000000..ecaf0fade11 --- /dev/null +++ b/chromium/net/third_party/quiche/src/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/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/headers_payload_decoder.h b/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/headers_payload_decoder.h new file mode 100644 index 00000000000..d3cdfe59c74 --- /dev/null +++ b/chromium/net/third_party/quiche/src/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/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/headers_payload_decoder_test.cc b/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/headers_payload_decoder_test.cc new file mode 100644 index 00000000000..a6fcb65bc30 --- /dev/null +++ b/chromium/net/third_party/quiche/src/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/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/payload_decoder_base_test_util.cc b/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/payload_decoder_base_test_util.cc new file mode 100644 index 00000000000..88e1b98afe2 --- /dev/null +++ b/chromium/net/third_party/quiche/src/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/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/payload_decoder_base_test_util.h b/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/payload_decoder_base_test_util.h new file mode 100644 index 00000000000..8297e70dab4 --- /dev/null +++ b/chromium/net/third_party/quiche/src/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/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/ping_payload_decoder.cc b/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/ping_payload_decoder.cc new file mode 100644 index 00000000000..8d98046d70a --- /dev/null +++ b/chromium/net/third_party/quiche/src/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/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/ping_payload_decoder.h b/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/ping_payload_decoder.h new file mode 100644 index 00000000000..84704fb77a7 --- /dev/null +++ b/chromium/net/third_party/quiche/src/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/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/ping_payload_decoder_test.cc b/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/ping_payload_decoder_test.cc new file mode 100644 index 00000000000..34833b2f50a --- /dev/null +++ b/chromium/net/third_party/quiche/src/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/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/priority_payload_decoder.cc b/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/priority_payload_decoder.cc new file mode 100644 index 00000000000..7be1c95073b --- /dev/null +++ b/chromium/net/third_party/quiche/src/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/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/priority_payload_decoder.h b/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/priority_payload_decoder.h new file mode 100644 index 00000000000..921eefeb2c7 --- /dev/null +++ b/chromium/net/third_party/quiche/src/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/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/priority_payload_decoder_test.cc b/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/priority_payload_decoder_test.cc new file mode 100644 index 00000000000..4e44ebaebae --- /dev/null +++ b/chromium/net/third_party/quiche/src/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/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/push_promise_payload_decoder.cc b/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/push_promise_payload_decoder.cc new file mode 100644 index 00000000000..cec1c072f90 --- /dev/null +++ b/chromium/net/third_party/quiche/src/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/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/push_promise_payload_decoder.h b/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/push_promise_payload_decoder.h new file mode 100644 index 00000000000..2db9cb359c4 --- /dev/null +++ b/chromium/net/third_party/quiche/src/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/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/push_promise_payload_decoder_test.cc b/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/push_promise_payload_decoder_test.cc new file mode 100644 index 00000000000..9d55a803043 --- /dev/null +++ b/chromium/net/third_party/quiche/src/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/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/rst_stream_payload_decoder.cc b/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/rst_stream_payload_decoder.cc new file mode 100644 index 00000000000..c39a16a8aa4 --- /dev/null +++ b/chromium/net/third_party/quiche/src/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/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/rst_stream_payload_decoder.h b/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/rst_stream_payload_decoder.h new file mode 100644 index 00000000000..947fc06d09e --- /dev/null +++ b/chromium/net/third_party/quiche/src/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/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/rst_stream_payload_decoder_test.cc b/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/rst_stream_payload_decoder_test.cc new file mode 100644 index 00000000000..08f66210518 --- /dev/null +++ b/chromium/net/third_party/quiche/src/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/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/settings_payload_decoder.cc b/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/settings_payload_decoder.cc new file mode 100644 index 00000000000..bf29c4d84fb --- /dev/null +++ b/chromium/net/third_party/quiche/src/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/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/settings_payload_decoder.h b/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/settings_payload_decoder.h new file mode 100644 index 00000000000..7e3c313bb16 --- /dev/null +++ b/chromium/net/third_party/quiche/src/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/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/settings_payload_decoder_test.cc b/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/settings_payload_decoder_test.cc new file mode 100644 index 00000000000..3429779faf7 --- /dev/null +++ b/chromium/net/third_party/quiche/src/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/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/unknown_payload_decoder.cc b/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/unknown_payload_decoder.cc new file mode 100644 index 00000000000..10eaf2438a5 --- /dev/null +++ b/chromium/net/third_party/quiche/src/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/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/unknown_payload_decoder.h b/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/unknown_payload_decoder.h new file mode 100644 index 00000000000..7bf4103dcd0 --- /dev/null +++ b/chromium/net/third_party/quiche/src/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/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/unknown_payload_decoder_test.cc b/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/unknown_payload_decoder_test.cc new file mode 100644 index 00000000000..7ba95f746b8 --- /dev/null +++ b/chromium/net/third_party/quiche/src/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/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/window_update_payload_decoder.cc b/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/window_update_payload_decoder.cc new file mode 100644 index 00000000000..c0bb028b005 --- /dev/null +++ b/chromium/net/third_party/quiche/src/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/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/window_update_payload_decoder.h b/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/window_update_payload_decoder.h new file mode 100644 index 00000000000..158c16502db --- /dev/null +++ b/chromium/net/third_party/quiche/src/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/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/window_update_payload_decoder_test.cc b/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/window_update_payload_decoder_test.cc new file mode 100644 index 00000000000..77a2b90a45c --- /dev/null +++ b/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/window_update_payload_decoder_test.cc @@ -0,0 +1,95 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/http2/decoder/payload_decoders/window_update_payload_decoder.h" + +#include <stddef.h> + +#include "base/logging.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener.h" +#include "net/third_party/quiche/src/http2/decoder/payload_decoders/payload_decoder_base_test_util.h" +#include "net/third_party/quiche/src/http2/http2_constants.h" +#include "net/third_party/quiche/src/http2/http2_structures_test_util.h" +#include "net/third_party/quiche/src/http2/test_tools/frame_parts.h" +#include "net/third_party/quiche/src/http2/test_tools/frame_parts_collector.h" +#include "net/third_party/quiche/src/http2/test_tools/http2_random.h" +#include "net/third_party/quiche/src/http2/tools/http2_frame_builder.h" +#include "net/third_party/quiche/src/http2/tools/random_decoder_test.h" + +namespace http2 { +namespace test { + +class WindowUpdatePayloadDecoderPeer { + public: + static constexpr Http2FrameType FrameType() { + return Http2FrameType::WINDOW_UPDATE; + } + + // Returns the mask of flags that affect the decoding of the payload (i.e. + // flags that that indicate the presence of certain fields or padding). + static constexpr uint8_t FlagsAffectingPayloadDecoding() { return 0; } +}; + +namespace { + +struct Listener : public FramePartsCollector { + void OnWindowUpdate(const Http2FrameHeader& header, + uint32_t window_size_increment) override { + VLOG(1) << "OnWindowUpdate: " << header + << "; window_size_increment=" << window_size_increment; + EXPECT_EQ(Http2FrameType::WINDOW_UPDATE, header.type); + StartAndEndFrame(header)->OnWindowUpdate(header, window_size_increment); + } + + void OnFrameSizeError(const Http2FrameHeader& header) override { + VLOG(1) << "OnFrameSizeError: " << header; + FrameError(header)->OnFrameSizeError(header); + } +}; + +class WindowUpdatePayloadDecoderTest + : public AbstractPayloadDecoderTest<WindowUpdatePayloadDecoder, + WindowUpdatePayloadDecoderPeer, + Listener> { + protected: + Http2WindowUpdateFields RandWindowUpdateFields() { + Http2WindowUpdateFields fields; + test::Randomize(&fields, RandomPtr()); + VLOG(3) << "RandWindowUpdateFields: " << fields; + return fields; + } +}; + +// Confirm we get an error if the payload is not the correct size to hold +// exactly one Http2WindowUpdateFields. +TEST_F(WindowUpdatePayloadDecoderTest, WrongSize) { + auto approve_size = [](size_t size) { + return size != Http2WindowUpdateFields::EncodedSize(); + }; + Http2FrameBuilder fb; + fb.Append(RandWindowUpdateFields()); + fb.Append(RandWindowUpdateFields()); + fb.Append(RandWindowUpdateFields()); + EXPECT_TRUE(VerifyDetectsFrameSizeError(0, fb.buffer(), approve_size)); +} + +TEST_F(WindowUpdatePayloadDecoderTest, VariousPayloads) { + for (int n = 0; n < 100; ++n) { + uint32_t stream_id = n == 0 ? 0 : RandStreamId(); + Http2WindowUpdateFields fields = RandWindowUpdateFields(); + Http2FrameBuilder fb; + fb.Append(fields); + Http2FrameHeader header(fb.size(), Http2FrameType::WINDOW_UPDATE, + RandFlags(), stream_id); + set_frame_header(header); + FrameParts expected(header); + expected.SetOptWindowUpdateIncrement(fields.window_size_increment); + EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(fb.buffer(), expected)); + } +} + +} // namespace +} // namespace test +} // namespace http2 diff --git a/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_block_collector.cc b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_block_collector.cc new file mode 100644 index 00000000000..5b2c6b7293a --- /dev/null +++ b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_block_collector.cc @@ -0,0 +1,151 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_block_collector.h" + +#include <algorithm> +#include <memory> + +#include "base/logging.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_test_helpers.h" + +using ::testing::AssertionResult; +using ::testing::AssertionSuccess; + +namespace http2 { +namespace test { + +HpackBlockCollector::HpackBlockCollector() = default; +HpackBlockCollector::HpackBlockCollector(const HpackBlockCollector& other) + : pending_entry_(other.pending_entry_), entries_(other.entries_) {} +HpackBlockCollector::~HpackBlockCollector() = default; + +void HpackBlockCollector::OnIndexedHeader(size_t index) { + pending_entry_.OnIndexedHeader(index); + PushPendingEntry(); +} +void HpackBlockCollector::OnDynamicTableSizeUpdate(size_t size) { + pending_entry_.OnDynamicTableSizeUpdate(size); + PushPendingEntry(); +} +void HpackBlockCollector::OnStartLiteralHeader(HpackEntryType header_type, + size_t maybe_name_index) { + pending_entry_.OnStartLiteralHeader(header_type, maybe_name_index); +} +void HpackBlockCollector::OnNameStart(bool huffman_encoded, size_t len) { + pending_entry_.OnNameStart(huffman_encoded, len); +} +void HpackBlockCollector::OnNameData(const char* data, size_t len) { + pending_entry_.OnNameData(data, len); +} +void HpackBlockCollector::OnNameEnd() { + pending_entry_.OnNameEnd(); +} +void HpackBlockCollector::OnValueStart(bool huffman_encoded, size_t len) { + pending_entry_.OnValueStart(huffman_encoded, len); +} +void HpackBlockCollector::OnValueData(const char* data, size_t len) { + pending_entry_.OnValueData(data, len); +} +void HpackBlockCollector::OnValueEnd() { + pending_entry_.OnValueEnd(); + PushPendingEntry(); +} + +void HpackBlockCollector::PushPendingEntry() { + EXPECT_TRUE(pending_entry_.IsComplete()); + DVLOG(2) << "PushPendingEntry: " << pending_entry_; + entries_.push_back(pending_entry_); + EXPECT_TRUE(entries_.back().IsComplete()); + pending_entry_.Clear(); +} +void HpackBlockCollector::Clear() { + pending_entry_.Clear(); + entries_.clear(); +} + +void HpackBlockCollector::ExpectIndexedHeader(size_t index) { + entries_.push_back( + HpackEntryCollector(HpackEntryType::kIndexedHeader, index)); +} +void HpackBlockCollector::ExpectDynamicTableSizeUpdate(size_t size) { + entries_.push_back( + HpackEntryCollector(HpackEntryType::kDynamicTableSizeUpdate, size)); +} +void HpackBlockCollector::ExpectNameIndexAndLiteralValue( + HpackEntryType type, + size_t index, + bool value_huffman, + const Http2String& value) { + entries_.push_back(HpackEntryCollector(type, index, value_huffman, value)); +} +void HpackBlockCollector::ExpectLiteralNameAndValue(HpackEntryType type, + bool name_huffman, + const Http2String& name, + bool value_huffman, + const Http2String& value) { + entries_.push_back( + HpackEntryCollector(type, name_huffman, name, value_huffman, value)); +} + +void HpackBlockCollector::ShuffleEntries(Http2Random* rng) { + std::shuffle(entries_.begin(), entries_.end(), *rng); +} + +void HpackBlockCollector::AppendToHpackBlockBuilder( + HpackBlockBuilder* hbb) const { + CHECK(IsNotPending()); + for (const auto& entry : entries_) { + entry.AppendToHpackBlockBuilder(hbb); + } +} + +AssertionResult HpackBlockCollector::ValidateSoleIndexedHeader( + size_t ndx) const { + VERIFY_TRUE(pending_entry_.IsClear()); + VERIFY_EQ(1u, entries_.size()); + VERIFY_TRUE(entries_.front().ValidateIndexedHeader(ndx)); + return AssertionSuccess(); +} +AssertionResult HpackBlockCollector::ValidateSoleLiteralValueHeader( + HpackEntryType expected_type, + size_t expected_index, + bool expected_value_huffman, + Http2StringPiece expected_value) const { + VERIFY_TRUE(pending_entry_.IsClear()); + VERIFY_EQ(1u, entries_.size()); + VERIFY_TRUE(entries_.front().ValidateLiteralValueHeader( + expected_type, expected_index, expected_value_huffman, expected_value)); + return AssertionSuccess(); +} +AssertionResult HpackBlockCollector::ValidateSoleLiteralNameValueHeader( + HpackEntryType expected_type, + bool expected_name_huffman, + Http2StringPiece expected_name, + bool expected_value_huffman, + Http2StringPiece expected_value) const { + VERIFY_TRUE(pending_entry_.IsClear()); + VERIFY_EQ(1u, entries_.size()); + VERIFY_TRUE(entries_.front().ValidateLiteralNameValueHeader( + expected_type, expected_name_huffman, expected_name, + expected_value_huffman, expected_value)); + return AssertionSuccess(); +} +AssertionResult HpackBlockCollector::ValidateSoleDynamicTableSizeUpdate( + size_t size) const { + VERIFY_TRUE(pending_entry_.IsClear()); + VERIFY_EQ(1u, entries_.size()); + VERIFY_TRUE(entries_.front().ValidateDynamicTableSizeUpdate(size)); + return AssertionSuccess(); +} + +AssertionResult HpackBlockCollector::VerifyEq( + const HpackBlockCollector& that) const { + VERIFY_EQ(pending_entry_, that.pending_entry_); + VERIFY_EQ(entries_, that.entries_); + return AssertionSuccess(); +} + +} // namespace test +} // namespace http2 diff --git a/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_block_collector.h b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_block_collector.h new file mode 100644 index 00000000000..6ad14057c24 --- /dev/null +++ b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_block_collector.h @@ -0,0 +1,129 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef QUICHE_HTTP2_HPACK_DECODER_HPACK_BLOCK_COLLECTOR_H_ +#define QUICHE_HTTP2_HPACK_DECODER_HPACK_BLOCK_COLLECTOR_H_ + +// HpackBlockCollector implements HpackEntryDecoderListener in order to record +// the calls using HpackEntryCollector instances (one per HPACK entry). This +// supports testing of HpackBlockDecoder, which decodes entire HPACK blocks. +// +// In addition to implementing the callback methods, HpackBlockCollector also +// supports comparing two HpackBlockCollector instances (i.e. an expected and +// an actual), or a sole HPACK entry against an expected value. + +#include <stddef.h> + +#include <vector> + +#include "testing/gtest/include/gtest/gtest.h" +#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_collector.h" +#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_decoder_listener.h" +#include "net/third_party/quiche/src/http2/hpack/http2_hpack_constants.h" +#include "net/third_party/quiche/src/http2/hpack/tools/hpack_block_builder.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string_piece.h" +#include "net/third_party/quiche/src/http2/test_tools/http2_random.h" + +namespace http2 { +namespace test { + +class HpackBlockCollector : public HpackEntryDecoderListener { + public: + HpackBlockCollector(); + HpackBlockCollector(const HpackBlockCollector& other); + ~HpackBlockCollector() override; + + // Implementations of HpackEntryDecoderListener, forwarding to pending_entry_, + // an HpackEntryCollector for the "in-progress" HPACK entry. OnIndexedHeader + // and OnDynamicTableSizeUpdate are pending only for that one call, while + // OnStartLiteralHeader is followed by many calls, ending with OnValueEnd. + // Once all the calls for one HPACK entry have been received, PushPendingEntry + // is used to append the pending_entry_ entry to the collected entries_. + void OnIndexedHeader(size_t index) override; + void OnDynamicTableSizeUpdate(size_t size) override; + void OnStartLiteralHeader(HpackEntryType header_type, + size_t maybe_name_index) override; + void OnNameStart(bool huffman_encoded, size_t len) override; + void OnNameData(const char* data, size_t len) override; + void OnNameEnd() override; + void OnValueStart(bool huffman_encoded, size_t len) override; + void OnValueData(const char* data, size_t len) override; + void OnValueEnd() override; + + // Methods for creating a set of expectations (i.e. HPACK entries to compare + // against those collected by another instance of HpackBlockCollector). + + // Add an HPACK entry for an indexed header. + void ExpectIndexedHeader(size_t index); + + // Add an HPACK entry for a dynamic table size update. + void ExpectDynamicTableSizeUpdate(size_t size); + + // Add an HPACK entry for a header entry with an index for the name, and a + // literal value. + void ExpectNameIndexAndLiteralValue(HpackEntryType type, + size_t index, + bool value_huffman, + const Http2String& value); + + // Add an HPACK entry for a header entry with a literal name and value. + void ExpectLiteralNameAndValue(HpackEntryType type, + bool name_huffman, + const Http2String& name, + bool value_huffman, + const Http2String& value); + + // Shuffle the entries, in support of generating an HPACK block of entries + // in some random order. + void ShuffleEntries(Http2Random* rng); + + // Serialize entries_ to the HpackBlockBuilder. + void AppendToHpackBlockBuilder(HpackBlockBuilder* hbb) const; + + // Return AssertionSuccess if there is just one entry, and it is an + // Indexed Header with the specified index. + ::testing::AssertionResult ValidateSoleIndexedHeader(size_t ndx) const; + + // Return AssertionSuccess if there is just one entry, and it is a + // Dynamic Table Size Update with the specified size. + ::testing::AssertionResult ValidateSoleDynamicTableSizeUpdate( + size_t size) const; + + // Return AssertionSuccess if there is just one entry, and it is a Header + // entry with an index for the name and a literal value. + ::testing::AssertionResult ValidateSoleLiteralValueHeader( + HpackEntryType expected_type, + size_t expected_index, + bool expected_value_huffman, + Http2StringPiece expected_value) const; + + // Return AssertionSuccess if there is just one entry, and it is a Header + // with a literal name and literal value. + ::testing::AssertionResult ValidateSoleLiteralNameValueHeader( + HpackEntryType expected_type, + bool expected_name_huffman, + Http2StringPiece expected_name, + bool expected_value_huffman, + Http2StringPiece expected_value) const; + + bool IsNotPending() const { return pending_entry_.IsClear(); } + bool IsClear() const { return IsNotPending() && entries_.empty(); } + void Clear(); + + ::testing::AssertionResult VerifyEq(const HpackBlockCollector& that) const; + + private: + // Push the value of pending_entry_ onto entries_, and clear pending_entry_. + // The pending_entry_ must be complete. + void PushPendingEntry(); + + HpackEntryCollector pending_entry_; + std::vector<HpackEntryCollector> entries_; +}; + +} // namespace test +} // namespace http2 + +#endif // QUICHE_HTTP2_HPACK_DECODER_HPACK_BLOCK_COLLECTOR_H_ diff --git a/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_block_decoder.cc b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_block_decoder.cc new file mode 100644 index 00000000000..c6882d292c4 --- /dev/null +++ b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_block_decoder.cc @@ -0,0 +1,62 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_block_decoder.h" + +#include <cstdint> + +#include "base/logging.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string_utils.h" + +namespace http2 { + +DecodeStatus HpackBlockDecoder::Decode(DecodeBuffer* db) { + if (!before_entry_) { + DVLOG(2) << "HpackBlockDecoder::Decode resume entry, db->Remaining=" + << db->Remaining(); + DecodeStatus status = entry_decoder_.Resume(db, listener_); + switch (status) { + case DecodeStatus::kDecodeDone: + before_entry_ = true; + break; + case DecodeStatus::kDecodeInProgress: + DCHECK_EQ(0u, db->Remaining()); + return DecodeStatus::kDecodeInProgress; + case DecodeStatus::kDecodeError: + return DecodeStatus::kDecodeError; + } + } + DCHECK(before_entry_); + while (db->HasData()) { + DVLOG(2) << "HpackBlockDecoder::Decode start entry, db->Remaining=" + << db->Remaining(); + DecodeStatus status = entry_decoder_.Start(db, listener_); + switch (status) { + case DecodeStatus::kDecodeDone: + continue; + case DecodeStatus::kDecodeInProgress: + DCHECK_EQ(0u, db->Remaining()); + before_entry_ = false; + return DecodeStatus::kDecodeInProgress; + case DecodeStatus::kDecodeError: + return DecodeStatus::kDecodeError; + } + DCHECK(false); + } + DCHECK(before_entry_); + return DecodeStatus::kDecodeDone; +} + +Http2String HpackBlockDecoder::DebugString() const { + return Http2StrCat("HpackBlockDecoder(", entry_decoder_.DebugString(), + ", listener@", + Http2Hex(reinterpret_cast<intptr_t>(listener_)), + (before_entry_ ? ", between entries)" : ", in an entry)")); +} + +std::ostream& operator<<(std::ostream& out, const HpackBlockDecoder& v) { + return out << v.DebugString(); +} + +} // namespace http2 diff --git a/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_block_decoder.h b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_block_decoder.h new file mode 100644 index 00000000000..e8b23e16dce --- /dev/null +++ b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_block_decoder.h @@ -0,0 +1,64 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef QUICHE_HTTP2_HPACK_DECODER_HPACK_BLOCK_DECODER_H_ +#define QUICHE_HTTP2_HPACK_DECODER_HPACK_BLOCK_DECODER_H_ + +// HpackBlockDecoder decodes an entire HPACK block (or the available portion +// thereof in the DecodeBuffer) into entries, but doesn't include HPACK static +// or dynamic table support, so table indices remain indices at this level. +// Reports the entries to an HpackEntryDecoderListener. + +#include "base/logging.h" +#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h" +#include "net/third_party/quiche/src/http2/decoder/decode_status.h" +#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_decoder.h" +#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_decoder_listener.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_export.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string.h" + +namespace http2 { + +class HTTP2_EXPORT_PRIVATE HpackBlockDecoder { + public: + explicit HpackBlockDecoder(HpackEntryDecoderListener* listener) + : listener_(listener) { + DCHECK_NE(listener_, nullptr); + } + ~HpackBlockDecoder() {} + + HpackBlockDecoder(const HpackBlockDecoder&) = delete; + HpackBlockDecoder& operator=(const HpackBlockDecoder&) = delete; + + // Prepares the decoder to start decoding a new HPACK block. Expected + // to be called from an implementation of Http2FrameDecoderListener's + // OnHeadersStart or OnPushPromiseStart methods. + void Reset() { + DVLOG(2) << "HpackBlockDecoder::Reset"; + before_entry_ = true; + } + + // Decode the fragment of the HPACK block contained in the decode buffer. + // Expected to be called from an implementation of Http2FrameDecoderListener's + // OnHpackFragment method. + DecodeStatus Decode(DecodeBuffer* db); + + // Is the decoding process between entries (i.e. would the next byte be the + // first byte of a new HPACK entry)? + bool before_entry() const { return before_entry_; } + + Http2String DebugString() const; + + private: + HpackEntryDecoder entry_decoder_; + HpackEntryDecoderListener* const listener_; + bool before_entry_ = true; +}; + +HTTP2_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& out, + const HpackBlockDecoder& v); + +} // namespace http2 + +#endif // QUICHE_HTTP2_HPACK_DECODER_HPACK_BLOCK_DECODER_H_ diff --git a/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_block_decoder_test.cc b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_block_decoder_test.cc new file mode 100644 index 00000000000..fce45b2a316 --- /dev/null +++ b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_block_decoder_test.cc @@ -0,0 +1,294 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_block_decoder.h" + +// Tests of HpackBlockDecoder. + +#include <cstdint> + +#include "testing/gtest/include/gtest/gtest.h" +#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h" +#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_block_collector.h" +#include "net/third_party/quiche/src/http2/hpack/http2_hpack_constants.h" +#include "net/third_party/quiche/src/http2/hpack/tools/hpack_block_builder.h" +#include "net/third_party/quiche/src/http2/hpack/tools/hpack_example.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string_piece.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_test_helpers.h" +#include "net/third_party/quiche/src/http2/test_tools/http2_random.h" +#include "net/third_party/quiche/src/http2/tools/random_decoder_test.h" + +using ::testing::AssertionSuccess; + +namespace http2 { +namespace test { +namespace { + +class HpackBlockDecoderTest : public RandomDecoderTest { + protected: + HpackBlockDecoderTest() : listener_(&collector_), decoder_(&listener_) { + stop_decode_on_done_ = false; + decoder_.Reset(); + // Make sure logging doesn't crash. Not examining the result. + std::ostringstream strm; + strm << decoder_; + } + + DecodeStatus StartDecoding(DecodeBuffer* db) override { + collector_.Clear(); + decoder_.Reset(); + return ResumeDecoding(db); + } + + DecodeStatus ResumeDecoding(DecodeBuffer* db) override { + DecodeStatus status = decoder_.Decode(db); + + // Make sure logging doesn't crash. Not examining the result. + std::ostringstream strm; + strm << decoder_; + + return status; + } + + AssertionResult DecodeAndValidateSeveralWays(DecodeBuffer* db, + const Validator& validator) { + bool return_non_zero_on_first = false; + return RandomDecoderTest::DecodeAndValidateSeveralWays( + db, return_non_zero_on_first, validator); + } + + AssertionResult DecodeAndValidateSeveralWays(const HpackBlockBuilder& hbb, + const Validator& validator) { + DecodeBuffer db(hbb.buffer()); + return DecodeAndValidateSeveralWays(&db, validator); + } + + AssertionResult DecodeHpackExampleAndValidateSeveralWays( + Http2StringPiece hpack_example, + Validator validator) { + Http2String input = HpackExampleToStringOrDie(hpack_example); + DecodeBuffer db(input); + return DecodeAndValidateSeveralWays(&db, validator); + } + + uint8_t Rand8() { return Random().Rand8(); } + + Http2String Rand8String() { return Random().RandString(Rand8()); } + + HpackBlockCollector collector_; + HpackEntryDecoderVLoggingListener listener_; + HpackBlockDecoder decoder_; +}; + +// http://httpwg.org/specs/rfc7541.html#rfc.section.C.2.1 +TEST_F(HpackBlockDecoderTest, SpecExample_C_2_1) { + auto do_check = [this]() { + VERIFY_AND_RETURN_SUCCESS(collector_.ValidateSoleLiteralNameValueHeader( + HpackEntryType::kIndexedLiteralHeader, false, "custom-key", false, + "custom-header")); + }; + const char hpack_example[] = R"( + 40 | == Literal indexed == + 0a | Literal name (len = 10) + 6375 7374 6f6d 2d6b 6579 | custom-key + 0d | Literal value (len = 13) + 6375 7374 6f6d 2d68 6561 6465 72 | custom-header + | -> custom-key: + | custom-header + )"; + EXPECT_TRUE(DecodeHpackExampleAndValidateSeveralWays( + hpack_example, ValidateDoneAndEmpty(do_check))); + EXPECT_TRUE(do_check()); +} + +// http://httpwg.org/specs/rfc7541.html#rfc.section.C.2.2 +TEST_F(HpackBlockDecoderTest, SpecExample_C_2_2) { + auto do_check = [this]() { + VERIFY_AND_RETURN_SUCCESS(collector_.ValidateSoleLiteralValueHeader( + HpackEntryType::kUnindexedLiteralHeader, 4, false, "/sample/path")); + }; + const char hpack_example[] = R"( + 04 | == Literal not indexed == + | Indexed name (idx = 4) + | :path + 0c | Literal value (len = 12) + 2f73 616d 706c 652f 7061 7468 | /sample/path + | -> :path: /sample/path + )"; + EXPECT_TRUE(DecodeHpackExampleAndValidateSeveralWays( + hpack_example, ValidateDoneAndEmpty(do_check))); + EXPECT_TRUE(do_check()); +} + +// http://httpwg.org/specs/rfc7541.html#rfc.section.C.2.3 +TEST_F(HpackBlockDecoderTest, SpecExample_C_2_3) { + auto do_check = [this]() { + VERIFY_AND_RETURN_SUCCESS(collector_.ValidateSoleLiteralNameValueHeader( + HpackEntryType::kNeverIndexedLiteralHeader, false, "password", false, + "secret")); + }; + const char hpack_example[] = R"( + 10 | == Literal never indexed == + 08 | Literal name (len = 8) + 7061 7373 776f 7264 | password + 06 | Literal value (len = 6) + 7365 6372 6574 | secret + | -> password: secret + )"; + EXPECT_TRUE(DecodeHpackExampleAndValidateSeveralWays( + hpack_example, ValidateDoneAndEmpty(do_check))); + EXPECT_TRUE(do_check()); +} + +// http://httpwg.org/specs/rfc7541.html#rfc.section.C.2.4 +TEST_F(HpackBlockDecoderTest, SpecExample_C_2_4) { + auto do_check = [this]() { + VERIFY_AND_RETURN_SUCCESS(collector_.ValidateSoleIndexedHeader(2)); + }; + const char hpack_example[] = R"( + 82 | == Indexed - Add == + | idx = 2 + | -> :method: GET + )"; + EXPECT_TRUE(DecodeHpackExampleAndValidateSeveralWays( + hpack_example, ValidateDoneAndEmpty(do_check))); + EXPECT_TRUE(do_check()); +} +// http://httpwg.org/specs/rfc7541.html#rfc.section.C.3.1 +TEST_F(HpackBlockDecoderTest, SpecExample_C_3_1) { + Http2String example = R"( + 82 | == Indexed - Add == + | idx = 2 + | -> :method: GET + 86 | == Indexed - Add == + | idx = 6 + | -> :scheme: http + 84 | == Indexed - Add == + | idx = 4 + | -> :path: / + 41 | == Literal indexed == + | Indexed name (idx = 1) + | :authority + 0f | Literal value (len = 15) + 7777 772e 6578 616d 706c 652e 636f 6d | www.example.com + | -> :authority: + | www.example.com + )"; + HpackBlockCollector expected; + expected.ExpectIndexedHeader(2); + expected.ExpectIndexedHeader(6); + expected.ExpectIndexedHeader(4); + expected.ExpectNameIndexAndLiteralValue(HpackEntryType::kIndexedLiteralHeader, + 1, false, "www.example.com"); + NoArgValidator do_check = [expected, this]() { + VERIFY_AND_RETURN_SUCCESS(collector_.VerifyEq(expected)); + }; + EXPECT_TRUE(DecodeHpackExampleAndValidateSeveralWays( + example, ValidateDoneAndEmpty(do_check))); + EXPECT_TRUE(do_check()); +} + +// http://httpwg.org/specs/rfc7541.html#rfc.section.C.5.1 +TEST_F(HpackBlockDecoderTest, SpecExample_C_5_1) { + Http2String example = R"( + 48 | == Literal indexed == + | Indexed name (idx = 8) + | :status + 03 | Literal value (len = 3) + 3330 32 | 302 + | -> :status: 302 + 58 | == Literal indexed == + | Indexed name (idx = 24) + | cache-control + 07 | Literal value (len = 7) + 7072 6976 6174 65 | private + | -> cache-control: private + 61 | == Literal indexed == + | Indexed name (idx = 33) + | date + 1d | Literal value (len = 29) + 4d6f 6e2c 2032 3120 4f63 7420 3230 3133 | Mon, 21 Oct 2013 + 2032 303a 3133 3a32 3120 474d 54 | 20:13:21 GMT + | -> date: Mon, 21 Oct 2013 + | 20:13:21 GMT + 6e | == Literal indexed == + | Indexed name (idx = 46) + | location + 17 | Literal value (len = 23) + 6874 7470 733a 2f2f 7777 772e 6578 616d | https://www.exam + 706c 652e 636f 6d | ple.com + | -> location: + | https://www.example.com + )"; + HpackBlockCollector expected; + expected.ExpectNameIndexAndLiteralValue(HpackEntryType::kIndexedLiteralHeader, + 8, false, "302"); + expected.ExpectNameIndexAndLiteralValue(HpackEntryType::kIndexedLiteralHeader, + 24, false, "private"); + expected.ExpectNameIndexAndLiteralValue(HpackEntryType::kIndexedLiteralHeader, + 33, false, + "Mon, 21 Oct 2013 20:13:21 GMT"); + expected.ExpectNameIndexAndLiteralValue(HpackEntryType::kIndexedLiteralHeader, + 46, false, "https://www.example.com"); + NoArgValidator do_check = [expected, this]() { + VERIFY_AND_RETURN_SUCCESS(collector_.VerifyEq(expected)); + }; + EXPECT_TRUE(DecodeHpackExampleAndValidateSeveralWays( + example, ValidateDoneAndEmpty(do_check))); + EXPECT_TRUE(do_check()); +} + +// Generate a bunch of HPACK block entries to expect, use those expectations +// to generate an HPACK block, then decode it and confirm it matches those +// expectations. Some of these are invalid (such as Indexed, with index=0), +// but well-formed, and the decoder doesn't check for validity, just +// well-formedness. That includes the validity of the strings not being checked, +// such as lower-case ascii for the names, and valid Huffman encodings. +TEST_F(HpackBlockDecoderTest, Computed) { + HpackBlockCollector expected; + expected.ExpectIndexedHeader(0); + expected.ExpectIndexedHeader(1); + expected.ExpectIndexedHeader(126); + expected.ExpectIndexedHeader(127); + expected.ExpectIndexedHeader(128); + expected.ExpectDynamicTableSizeUpdate(0); + expected.ExpectDynamicTableSizeUpdate(1); + expected.ExpectDynamicTableSizeUpdate(14); + expected.ExpectDynamicTableSizeUpdate(15); + expected.ExpectDynamicTableSizeUpdate(30); + expected.ExpectDynamicTableSizeUpdate(31); + expected.ExpectDynamicTableSizeUpdate(4095); + expected.ExpectDynamicTableSizeUpdate(4096); + expected.ExpectDynamicTableSizeUpdate(8192); + for (auto type : {HpackEntryType::kIndexedLiteralHeader, + HpackEntryType::kUnindexedLiteralHeader, + HpackEntryType::kNeverIndexedLiteralHeader}) { + for (bool value_huffman : {false, true}) { + // An entry with an index for the name. Ensure the name index + // is not zero by adding one to the Rand8() result. + expected.ExpectNameIndexAndLiteralValue(type, Rand8() + 1, value_huffman, + Rand8String()); + // And two entries with literal names, one plain, one huffman encoded. + expected.ExpectLiteralNameAndValue(type, false, Rand8String(), + value_huffman, Rand8String()); + expected.ExpectLiteralNameAndValue(type, true, Rand8String(), + value_huffman, Rand8String()); + } + } + // Shuffle the entries and serialize them to produce an HPACK block. + expected.ShuffleEntries(RandomPtr()); + HpackBlockBuilder hbb; + expected.AppendToHpackBlockBuilder(&hbb); + + NoArgValidator do_check = [expected, this]() { + VERIFY_AND_RETURN_SUCCESS(collector_.VerifyEq(expected)); + }; + EXPECT_TRUE( + DecodeAndValidateSeveralWays(hbb, ValidateDoneAndEmpty(do_check))); + EXPECT_TRUE(do_check()); +} + +} // namespace +} // namespace test +} // namespace http2 diff --git a/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder.cc b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder.cc new file mode 100644 index 00000000000..d6897cbbfa1 --- /dev/null +++ b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder.cc @@ -0,0 +1,122 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder.h" + +#include "base/logging.h" +#include "net/third_party/quiche/src/http2/decoder/decode_status.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_estimate_memory_usage.h" + +namespace http2 { + +HpackDecoder::HpackDecoder(HpackDecoderListener* listener, + size_t max_string_size) + : decoder_state_(listener), + entry_buffer_(&decoder_state_, max_string_size), + block_decoder_(&entry_buffer_), + error_detected_(false) {} + +HpackDecoder::~HpackDecoder() = default; + +void HpackDecoder::set_tables_debug_listener( + HpackDecoderTablesDebugListener* debug_listener) { + decoder_state_.set_tables_debug_listener(debug_listener); +} + +void HpackDecoder::set_max_string_size_bytes(size_t max_string_size_bytes) { + entry_buffer_.set_max_string_size_bytes(max_string_size_bytes); +} + +void HpackDecoder::ApplyHeaderTableSizeSetting(uint32_t max_header_table_size) { + decoder_state_.ApplyHeaderTableSizeSetting(max_header_table_size); +} + +bool HpackDecoder::StartDecodingBlock() { + DVLOG(3) << "HpackDecoder::StartDecodingBlock, error_detected=" + << (error_detected() ? "true" : "false"); + if (error_detected()) { + return false; + } + // TODO(jamessynge): Eliminate Reset(), which shouldn't be necessary + // if there are no errors, and shouldn't be necessary with errors if + // we never resume decoding after an error has been detected. + block_decoder_.Reset(); + decoder_state_.OnHeaderBlockStart(); + return true; +} + +bool HpackDecoder::DecodeFragment(DecodeBuffer* db) { + DVLOG(3) << "HpackDecoder::DecodeFragment, error_detected=" + << (error_detected() ? "true" : "false") + << ", size=" << db->Remaining(); + if (error_detected()) { + return false; + } + // Decode contents of db as an HPACK block fragment, forwards the decoded + // entries to entry_buffer_, which in turn forwards them to decode_state_, + // which finally forwards them to the HpackDecoderListener. + DecodeStatus status = block_decoder_.Decode(db); + if (status == DecodeStatus::kDecodeError) { + // This has probably already been reported, but just in case... + ReportError("HPACK block malformed."); + return false; + } else if (error_detected()) { + return false; + } + // Should be positioned between entries iff decoding is complete. + DCHECK_EQ(block_decoder_.before_entry(), status == DecodeStatus::kDecodeDone) + << status; + if (!block_decoder_.before_entry()) { + entry_buffer_.BufferStringsIfUnbuffered(); + } + return true; +} + +bool HpackDecoder::EndDecodingBlock() { + DVLOG(3) << "HpackDecoder::EndDecodingBlock, error_detected=" + << (error_detected() ? "true" : "false"); + if (error_detected()) { + return false; + } + if (!block_decoder_.before_entry()) { + // The HPACK block ended in the middle of an entry. + ReportError("HPACK block truncated."); + return false; + } + decoder_state_.OnHeaderBlockEnd(); + if (error_detected()) { + // HpackDecoderState will have reported the error. + return false; + } + return true; +} + +bool HpackDecoder::error_detected() { + if (!error_detected_) { + if (entry_buffer_.error_detected()) { + DVLOG(2) << "HpackDecoder::error_detected in entry_buffer_"; + error_detected_ = true; + } else if (decoder_state_.error_detected()) { + DVLOG(2) << "HpackDecoder::error_detected in decoder_state_"; + error_detected_ = true; + } + } + return error_detected_; +} + +size_t HpackDecoder::EstimateMemoryUsage() const { + return Http2EstimateMemoryUsage(entry_buffer_); +} + +void HpackDecoder::ReportError(Http2StringPiece error_message) { + DVLOG(3) << "HpackDecoder::ReportError is new=" + << (!error_detected_ ? "true" : "false") + << ", error_message: " << error_message; + if (!error_detected_) { + error_detected_ = true; + decoder_state_.listener()->OnHeaderErrorDetected(error_message); + } +} + +} // namespace http2 diff --git a/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder.h b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder.h new file mode 100644 index 00000000000..e173bc6af19 --- /dev/null +++ b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder.h @@ -0,0 +1,123 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef QUICHE_HTTP2_HPACK_DECODER_HPACK_DECODER_H_ +#define QUICHE_HTTP2_HPACK_DECODER_HPACK_DECODER_H_ + +// Decodes HPACK blocks, calls an HpackDecoderListener with the decoded header +// entries. Also notifies the listener of errors and of the boundaries of the +// HPACK blocks. + +// TODO(jamessynge): Add feature allowing an HpackEntryDecoderListener +// sub-class (and possibly others) to be passed in for counting events, +// so that deciding whether to count is not done by having lots of if +// statements, but instead by inserting an indirection only when needed. + +// TODO(jamessynge): Consider whether to return false from methods below +// when an error has been previously detected. It protects calling code +// from its failure to pay attention to previous errors, but should we +// spend time to do that? + +#include <stddef.h> + +#include <cstdint> + +#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h" +#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_block_decoder.h" +#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_listener.h" +#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_state.h" +#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_tables.h" +#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_whole_entry_buffer.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_export.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string_piece.h" + +namespace http2 { +namespace test { +class HpackDecoderPeer; +} // namespace test + +class HTTP2_EXPORT_PRIVATE HpackDecoder { + public: + HpackDecoder(HpackDecoderListener* listener, size_t max_string_size); + virtual ~HpackDecoder(); + + HpackDecoder(const HpackDecoder&) = delete; + HpackDecoder& operator=(const HpackDecoder&) = delete; + + // Set listener to be notified of insertions into the HPACK dynamic table, + // and uses of those entries. + void set_tables_debug_listener( + HpackDecoderTablesDebugListener* debug_listener); + + // max_string_size specifies the maximum size of an on-the-wire string (name + // or value, plain or Huffman encoded) that will be accepted. See sections + // 5.1 and 5.2 of RFC 7541. This is a defense against OOM attacks; HTTP/2 + // allows a decoder to enforce any limit of the size of the header lists + // that it is willing to decode, including less than the MAX_HEADER_LIST_SIZE + // setting, a setting that is initially unlimited. For example, we might + // choose to send a MAX_HEADER_LIST_SIZE of 64KB, and to use that same value + // as the upper bound for individual strings. + void set_max_string_size_bytes(size_t max_string_size_bytes); + + // ApplyHeaderTableSizeSetting notifies this object that this endpoint has + // received a SETTINGS ACK frame acknowledging an earlier SETTINGS frame from + // this endpoint specifying a new value for SETTINGS_HEADER_TABLE_SIZE (the + // maximum size of the dynamic table that this endpoint will use to decode + // HPACK blocks). + // Because a SETTINGS frame can contain SETTINGS_HEADER_TABLE_SIZE values, + // the caller must keep track of those multiple changes, and make + // corresponding calls to this method. In particular, a call must be made + // with the lowest value acknowledged by the peer, and a call must be made + // with the final value acknowledged, in that order; additional calls may + // be made if additional values were sent. These calls must be made between + // decoding the SETTINGS ACK, and before the next HPACK block is decoded. + void ApplyHeaderTableSizeSetting(uint32_t max_header_table_size); + + // Prepares the decoder for decoding a new HPACK block, and announces this to + // its listener. Returns true if OK to continue with decoding, false if an + // error has been detected, which for StartDecodingBlock means the error was + // detected while decoding a previous HPACK block. + bool StartDecodingBlock(); + + // Decodes a fragment (some or all of the remainder) of an HPACK block, + // reporting header entries (name & value pairs) that it completely decodes + // in the process to the listener. Returns true successfully decoded, false if + // an error has been detected, either during decoding of the fragment, or + // prior to this call. + bool DecodeFragment(DecodeBuffer* db); + + // Completes the process of decoding an HPACK block: if the HPACK block was + // properly terminated, announces the end of the header list to the listener + // and returns true; else returns false. + bool EndDecodingBlock(); + + // Was an error detected? + bool error_detected(); + + // Returns the estimate of dynamically allocated memory in bytes. + size_t EstimateMemoryUsage() const; + + private: + friend class test::HpackDecoderPeer; + + // Reports an error to the listener IF this is the first error detected. + void ReportError(Http2StringPiece error_message); + + // The decompressor state, as defined by HPACK (i.e. the static and dynamic + // tables). + HpackDecoderState decoder_state_; + + // Assembles the various parts of a header entry into whole entries. + HpackWholeEntryBuffer entry_buffer_; + + // The decoder of HPACK blocks into entry parts, passed to entry_buffer_. + HpackBlockDecoder block_decoder_; + + // Has an error been detected? + bool error_detected_; +}; + +} // namespace http2 + +#endif // QUICHE_HTTP2_HPACK_DECODER_HPACK_DECODER_H_ diff --git a/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_listener.cc b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_listener.cc new file mode 100644 index 00000000000..8afa8aad316 --- /dev/null +++ b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_listener.cc @@ -0,0 +1,30 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_listener.h" + +namespace http2 { + +HpackDecoderListener::HpackDecoderListener() = default; +HpackDecoderListener::~HpackDecoderListener() = default; + +HpackDecoderNoOpListener::HpackDecoderNoOpListener() = default; +HpackDecoderNoOpListener::~HpackDecoderNoOpListener() = default; + +void HpackDecoderNoOpListener::OnHeaderListStart() {} +void HpackDecoderNoOpListener::OnHeader(HpackEntryType entry_type, + const HpackString& name, + const HpackString& value) {} +void HpackDecoderNoOpListener::OnHeaderListEnd() {} +void HpackDecoderNoOpListener::OnHeaderErrorDetected( + Http2StringPiece error_message) {} + +// static +HpackDecoderNoOpListener* HpackDecoderNoOpListener::NoOpListener() { + static HpackDecoderNoOpListener* static_instance = + new HpackDecoderNoOpListener(); + return static_instance; +} + +} // namespace http2 diff --git a/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_listener.h b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_listener.h new file mode 100644 index 00000000000..fa685916625 --- /dev/null +++ b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_listener.h @@ -0,0 +1,66 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Defines HpackDecoderListener, the base class of listeners for HTTP header +// lists decoded from an HPACK block. + +#ifndef QUICHE_HTTP2_HPACK_DECODER_HPACK_DECODER_LISTENER_H_ +#define QUICHE_HTTP2_HPACK_DECODER_HPACK_DECODER_LISTENER_H_ + +#include "net/third_party/quiche/src/http2/hpack/hpack_string.h" +#include "net/third_party/quiche/src/http2/hpack/http2_hpack_constants.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_export.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string_piece.h" + +namespace http2 { + +class HTTP2_EXPORT_PRIVATE HpackDecoderListener { + public: + HpackDecoderListener(); + virtual ~HpackDecoderListener(); + + // OnHeaderListStart is called at the start of decoding an HPACK block into + // an HTTP/2 header list. Will only be called once per block, even if it + // extends into CONTINUATION frames. + virtual void OnHeaderListStart() = 0; + + // Called for each header name-value pair that is decoded, in the order they + // appear in the HPACK block. Multiple values for a given key will be emitted + // as multiple calls to OnHeader. + virtual void OnHeader(HpackEntryType entry_type, + const HpackString& name, + const HpackString& value) = 0; + + // OnHeaderListEnd is called after successfully decoding an HPACK block into + // an HTTP/2 header list. Will only be called once per block, even if it + // extends into CONTINUATION frames. + virtual void OnHeaderListEnd() = 0; + + // OnHeaderErrorDetected is called if an error is detected while decoding. + // error_message may be used in a GOAWAY frame as the Opaque Data. + virtual void OnHeaderErrorDetected(Http2StringPiece error_message) = 0; +}; + +// A no-op implementation of HpackDecoderListener, useful for ignoring +// callbacks once an error is detected. +class HTTP2_EXPORT_PRIVATE HpackDecoderNoOpListener + : public HpackDecoderListener { + public: + HpackDecoderNoOpListener(); + ~HpackDecoderNoOpListener() override; + + void OnHeaderListStart() override; + void OnHeader(HpackEntryType entry_type, + const HpackString& name, + const HpackString& value) override; + void OnHeaderListEnd() override; + void OnHeaderErrorDetected(Http2StringPiece error_message) override; + + // Returns a listener that ignores all the calls. + static HpackDecoderNoOpListener* NoOpListener(); +}; + +} // namespace http2 + +#endif // QUICHE_HTTP2_HPACK_DECODER_HPACK_DECODER_LISTENER_H_ diff --git a/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_state.cc b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_state.cc new file mode 100644 index 00000000000..2ddf421775d --- /dev/null +++ b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_state.cc @@ -0,0 +1,218 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_state.h" + +#include "base/logging.h" +#include "net/third_party/quiche/src/http2/hpack/hpack_string.h" +#include "net/third_party/quiche/src/http2/http2_constants.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_macros.h" + +namespace http2 { +namespace { + +HpackString ExtractHpackString(HpackDecoderStringBuffer* string_buffer) { + if (string_buffer->IsBuffered()) { + return HpackString(string_buffer->ReleaseString()); + } else { + auto result = HpackString(string_buffer->str()); + string_buffer->Reset(); + return result; + } +} + +} // namespace + +HpackDecoderState::HpackDecoderState(HpackDecoderListener* listener) + : listener_(HTTP2_DIE_IF_NULL(listener)), + final_header_table_size_(Http2SettingsInfo::DefaultHeaderTableSize()), + lowest_header_table_size_(final_header_table_size_), + require_dynamic_table_size_update_(false), + allow_dynamic_table_size_update_(true), + saw_dynamic_table_size_update_(false), + error_detected_(false) {} +HpackDecoderState::~HpackDecoderState() = default; + +void HpackDecoderState::set_tables_debug_listener( + HpackDecoderTablesDebugListener* debug_listener) { + decoder_tables_.set_debug_listener(debug_listener); +} + +void HpackDecoderState::ApplyHeaderTableSizeSetting( + uint32_t header_table_size) { + DVLOG(2) << "HpackDecoderState::ApplyHeaderTableSizeSetting(" + << header_table_size << ")"; + DCHECK_LE(lowest_header_table_size_, final_header_table_size_); + if (header_table_size < lowest_header_table_size_) { + lowest_header_table_size_ = header_table_size; + } + final_header_table_size_ = header_table_size; + DVLOG(2) << "low water mark: " << lowest_header_table_size_; + DVLOG(2) << "final limit: " << final_header_table_size_; +} + +// Called to notify this object that we're starting to decode an HPACK block +// (e.g. a HEADERS or PUSH_PROMISE frame's header has been decoded). +void HpackDecoderState::OnHeaderBlockStart() { + DVLOG(2) << "HpackDecoderState::OnHeaderBlockStart"; + // This instance can't be reused after an error has been detected, as we must + // assume that the encoder and decoder compression states are no longer + // synchronized. + DCHECK(!error_detected_); + DCHECK_LE(lowest_header_table_size_, final_header_table_size_); + allow_dynamic_table_size_update_ = true; + saw_dynamic_table_size_update_ = false; + // If the peer has acknowledged a HEADER_TABLE_SIZE smaller than that which + // its HPACK encoder has been using, then the next HPACK block it sends MUST + // start with a Dynamic Table Size Update entry that is at least as low as + // lowest_header_table_size_. That may be followed by another as great as + // final_header_table_size_, if those are different. + require_dynamic_table_size_update_ = + (lowest_header_table_size_ < + decoder_tables_.current_header_table_size() || + final_header_table_size_ < decoder_tables_.header_table_size_limit()); + DVLOG(2) << "HpackDecoderState::OnHeaderListStart " + << "require_dynamic_table_size_update_=" + << require_dynamic_table_size_update_; + listener_->OnHeaderListStart(); +} + +void HpackDecoderState::OnIndexedHeader(size_t index) { + DVLOG(2) << "HpackDecoderState::OnIndexedHeader: " << index; + if (error_detected_) { + return; + } + if (require_dynamic_table_size_update_) { + ReportError("Missing dynamic table size update."); + return; + } + allow_dynamic_table_size_update_ = false; + const HpackStringPair* entry = decoder_tables_.Lookup(index); + if (entry != nullptr) { + listener_->OnHeader(HpackEntryType::kIndexedHeader, entry->name, + entry->value); + } else { + ReportError("Invalid index."); + } +} + +void HpackDecoderState::OnNameIndexAndLiteralValue( + HpackEntryType entry_type, + size_t name_index, + HpackDecoderStringBuffer* value_buffer) { + DVLOG(2) << "HpackDecoderState::OnNameIndexAndLiteralValue " << entry_type + << ", " << name_index << ", " << value_buffer->str(); + if (error_detected_) { + return; + } + if (require_dynamic_table_size_update_) { + ReportError("Missing dynamic table size update."); + return; + } + allow_dynamic_table_size_update_ = false; + const HpackStringPair* entry = decoder_tables_.Lookup(name_index); + if (entry != nullptr) { + HpackString value(ExtractHpackString(value_buffer)); + listener_->OnHeader(entry_type, entry->name, value); + if (entry_type == HpackEntryType::kIndexedLiteralHeader) { + decoder_tables_.Insert(entry->name, value); + } + } else { + ReportError("Invalid name index."); + } +} + +void HpackDecoderState::OnLiteralNameAndValue( + HpackEntryType entry_type, + HpackDecoderStringBuffer* name_buffer, + HpackDecoderStringBuffer* value_buffer) { + DVLOG(2) << "HpackDecoderState::OnLiteralNameAndValue " << entry_type << ", " + << name_buffer->str() << ", " << value_buffer->str(); + if (error_detected_) { + return; + } + if (require_dynamic_table_size_update_) { + ReportError("Missing dynamic table size update."); + return; + } + allow_dynamic_table_size_update_ = false; + HpackString name(ExtractHpackString(name_buffer)); + HpackString value(ExtractHpackString(value_buffer)); + listener_->OnHeader(entry_type, name, value); + if (entry_type == HpackEntryType::kIndexedLiteralHeader) { + decoder_tables_.Insert(name, value); + } +} + +void HpackDecoderState::OnDynamicTableSizeUpdate(size_t size_limit) { + DVLOG(2) << "HpackDecoderState::OnDynamicTableSizeUpdate " << size_limit + << ", required=" + << (require_dynamic_table_size_update_ ? "true" : "false") + << ", allowed=" + << (allow_dynamic_table_size_update_ ? "true" : "false"); + if (error_detected_) { + return; + } + DCHECK_LE(lowest_header_table_size_, final_header_table_size_); + if (!allow_dynamic_table_size_update_) { + // At most two dynamic table size updates allowed at the start, and not + // after a header. + ReportError("Dynamic table size update not allowed."); + return; + } + if (require_dynamic_table_size_update_) { + // The new size must not be greater than the low water mark. + if (size_limit > lowest_header_table_size_) { + ReportError("Initial dynamic table size update is above low water mark."); + return; + } + require_dynamic_table_size_update_ = false; + } else if (size_limit > final_header_table_size_) { + // The new size must not be greater than the final max header table size + // that the peer acknowledged. + ReportError("Dynamic table size update is above acknowledged setting."); + return; + } + decoder_tables_.DynamicTableSizeUpdate(size_limit); + if (saw_dynamic_table_size_update_) { + allow_dynamic_table_size_update_ = false; + } else { + saw_dynamic_table_size_update_ = true; + } + // We no longer need to keep an eye out for a lower header table size. + lowest_header_table_size_ = final_header_table_size_; +} + +void HpackDecoderState::OnHpackDecodeError(Http2StringPiece error_message) { + DVLOG(2) << "HpackDecoderState::OnHpackDecodeError " << error_message; + if (!error_detected_) { + ReportError(error_message); + } +} + +void HpackDecoderState::OnHeaderBlockEnd() { + DVLOG(2) << "HpackDecoderState::OnHeaderBlockEnd"; + if (error_detected_) { + return; + } + if (require_dynamic_table_size_update_) { + // Apparently the HPACK block was empty, but we needed it to contain at + // least 1 dynamic table size update. + ReportError("Missing dynamic table size update."); + } else { + listener_->OnHeaderListEnd(); + } +} + +void HpackDecoderState::ReportError(Http2StringPiece error_message) { + DVLOG(2) << "HpackDecoderState::ReportError is new=" + << (!error_detected_ ? "true" : "false") + << ", error_message: " << error_message; + if (!error_detected_) { + listener_->OnHeaderErrorDetected(error_message); + error_detected_ = true; + } +} + +} // namespace http2 diff --git a/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_state.h b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_state.h new file mode 100644 index 00000000000..fd341793539 --- /dev/null +++ b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_state.h @@ -0,0 +1,129 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// HpackDecoderState maintains the HPACK decompressor state; i.e. updates the +// HPACK dynamic table according to RFC 7541 as the entries in an HPACK block +// are decoded, and reads from the static and dynamic tables in order to build +// complete header entries. Calls an HpackDecoderListener with the completely +// decoded headers (i.e. after resolving table indices into names or values), +// thus translating the decoded HPACK entries into HTTP/2 headers. + +#ifndef QUICHE_HTTP2_HPACK_DECODER_HPACK_DECODER_STATE_H_ +#define QUICHE_HTTP2_HPACK_DECODER_HPACK_DECODER_STATE_H_ + +#include <stddef.h> + +#include <cstdint> + +#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_listener.h" +#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_string_buffer.h" +#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_tables.h" +#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_whole_entry_listener.h" +#include "net/third_party/quiche/src/http2/hpack/http2_hpack_constants.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_export.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string_piece.h" + +namespace http2 { +namespace test { +class HpackDecoderStatePeer; +} // namespace test + +class HTTP2_EXPORT_PRIVATE HpackDecoderState : public HpackWholeEntryListener { + public: + explicit HpackDecoderState(HpackDecoderListener* listener); + ~HpackDecoderState() override; + + HpackDecoderState(const HpackDecoderState&) = delete; + HpackDecoderState& operator=(const HpackDecoderState&) = delete; + + // Set the listener to be notified when a whole entry has been decoded, + // including resolving name or name and value references. + // The listener may be changed at any time. + HpackDecoderListener* listener() const { return listener_; } + + // Set listener to be notified of insertions into the HPACK dynamic table, + // and uses of those entries. + void set_tables_debug_listener( + HpackDecoderTablesDebugListener* debug_listener); + + // ApplyHeaderTableSizeSetting notifies this object that this endpoint has + // received a SETTINGS ACK frame acknowledging an earlier SETTINGS frame from + // this endpoint specifying a new value for SETTINGS_HEADER_TABLE_SIZE (the + // maximum size of the dynamic table that this endpoint will use to decode + // HPACK blocks). + // Because a SETTINGS frame can contain SETTINGS_HEADER_TABLE_SIZE values, + // the caller must keep track of those multiple changes, and make + // corresponding calls to this method. In particular, a call must be made + // with the lowest value acknowledged by the peer, and a call must be made + // with the final value acknowledged, in that order; additional calls may + // be made if additional values were sent. These calls must be made between + // decoding the SETTINGS ACK, and before the next HPACK block is decoded. + void ApplyHeaderTableSizeSetting(uint32_t max_header_table_size); + + // OnHeaderBlockStart notifies this object that we're starting to decode the + // HPACK payload of a HEADERS or PUSH_PROMISE frame. + void OnHeaderBlockStart(); + + // Implement the HpackWholeEntryListener methods, each of which notifies this + // object when an entire entry has been decoded. + void OnIndexedHeader(size_t index) override; + void OnNameIndexAndLiteralValue( + HpackEntryType entry_type, + size_t name_index, + HpackDecoderStringBuffer* value_buffer) override; + void OnLiteralNameAndValue(HpackEntryType entry_type, + HpackDecoderStringBuffer* name_buffer, + HpackDecoderStringBuffer* value_buffer) override; + void OnDynamicTableSizeUpdate(size_t size) override; + void OnHpackDecodeError(Http2StringPiece error_message) override; + + // OnHeaderBlockEnd notifies this object that an entire HPACK block has been + // decoded, which might have extended into CONTINUATION blocks. + void OnHeaderBlockEnd(); + + // Was an error detected? After an error has been detected and reported, + // no further callbacks will be made to the listener. + bool error_detected() const { return error_detected_; } + + const HpackDecoderTables& decoder_tables_for_test() const { + return decoder_tables_; + } + + private: + friend class test::HpackDecoderStatePeer; + + // Reports an error to the listener IF this is the first error detected. + void ReportError(Http2StringPiece error_message); + + // The static and dynamic HPACK tables. + HpackDecoderTables decoder_tables_; + + // The listener to be notified of headers, the start and end of header + // lists, and of errors. + HpackDecoderListener* listener_; + + // The most recent HEADER_TABLE_SIZE setting acknowledged by the peer. + uint32_t final_header_table_size_; + + // The lowest HEADER_TABLE_SIZE setting acknowledged by the peer; valid until + // the next HPACK block is decoded. + // TODO(jamessynge): Test raising the HEADER_TABLE_SIZE. + uint32_t lowest_header_table_size_; + + // Must the next (first) HPACK entry be a dynamic table size update? + bool require_dynamic_table_size_update_; + + // May the next (first or second) HPACK entry be a dynamic table size update? + bool allow_dynamic_table_size_update_; + + // Have we already seen a dynamic table size update in this HPACK block? + bool saw_dynamic_table_size_update_; + + // Has an error already been detected and reported to the listener? + bool error_detected_; +}; + +} // namespace http2 + +#endif // QUICHE_HTTP2_HPACK_DECODER_HPACK_DECODER_STATE_H_ diff --git a/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_state_test.cc b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_state_test.cc new file mode 100644 index 00000000000..115827e1912 --- /dev/null +++ b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_state_test.cc @@ -0,0 +1,539 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_state.h" + +// Tests of HpackDecoderState. + +#include <utility> +#include <vector> + +#include "base/logging.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "net/third_party/quiche/src/http2/hpack/hpack_string.h" +#include "net/third_party/quiche/src/http2/hpack/http2_hpack_constants.h" +#include "net/third_party/quiche/src/http2/http2_constants.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string_piece.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_test_helpers.h" + +using ::testing::AssertionResult; +using ::testing::AssertionSuccess; +using ::testing::Eq; +using ::testing::HasSubstr; +using ::testing::Mock; +using ::testing::StrictMock; + +namespace http2 { +namespace test { +class HpackDecoderStatePeer { + public: + static HpackDecoderTables* GetDecoderTables(HpackDecoderState* state) { + return &state->decoder_tables_; + } +}; + +namespace { + +class MockHpackDecoderListener : public HpackDecoderListener { + public: + MOCK_METHOD0(OnHeaderListStart, void()); + MOCK_METHOD3(OnHeader, + void(HpackEntryType entry_type, + const HpackString& name, + const HpackString& value)); + MOCK_METHOD0(OnHeaderListEnd, void()); + MOCK_METHOD1(OnHeaderErrorDetected, void(Http2StringPiece error_message)); +}; + +enum StringBacking { STATIC, UNBUFFERED, BUFFERED }; + +class HpackDecoderStateTest : public ::testing::Test { + protected: + HpackDecoderStateTest() : decoder_state_(&listener_) {} + + HpackDecoderTables* GetDecoderTables() { + return HpackDecoderStatePeer::GetDecoderTables(&decoder_state_); + } + + const HpackStringPair* Lookup(size_t index) { + return GetDecoderTables()->Lookup(index); + } + + size_t current_header_table_size() { + return GetDecoderTables()->current_header_table_size(); + } + + size_t header_table_size_limit() { + return GetDecoderTables()->header_table_size_limit(); + } + + void set_header_table_size_limit(size_t size) { + GetDecoderTables()->DynamicTableSizeUpdate(size); + } + + void SetStringBuffer(const char* s, + StringBacking backing, + HpackDecoderStringBuffer* string_buffer) { + switch (backing) { + case STATIC: + string_buffer->Set(s, true); + break; + case UNBUFFERED: + string_buffer->Set(s, false); + break; + case BUFFERED: + string_buffer->Set(s, false); + string_buffer->BufferStringIfUnbuffered(); + break; + } + } + + void SetName(const char* s, StringBacking backing) { + SetStringBuffer(s, backing, &name_buffer_); + } + + void SetValue(const char* s, StringBacking backing) { + SetStringBuffer(s, backing, &value_buffer_); + } + + void SendStartAndVerifyCallback() { + EXPECT_CALL(listener_, OnHeaderListStart()); + decoder_state_.OnHeaderBlockStart(); + Mock::VerifyAndClearExpectations(&listener_); + } + + void SendSizeUpdate(size_t size) { + decoder_state_.OnDynamicTableSizeUpdate(size); + Mock::VerifyAndClearExpectations(&listener_); + } + + void SendIndexAndVerifyCallback(size_t index, + HpackEntryType expected_type, + const char* expected_name, + const char* expected_value) { + EXPECT_CALL(listener_, + OnHeader(expected_type, Eq(expected_name), Eq(expected_value))); + decoder_state_.OnIndexedHeader(index); + Mock::VerifyAndClearExpectations(&listener_); + } + + void SendValueAndVerifyCallback(size_t name_index, + HpackEntryType entry_type, + const char* name, + const char* value, + StringBacking value_backing) { + SetValue(value, value_backing); + EXPECT_CALL(listener_, OnHeader(entry_type, Eq(name), Eq(value))); + decoder_state_.OnNameIndexAndLiteralValue(entry_type, name_index, + &value_buffer_); + Mock::VerifyAndClearExpectations(&listener_); + } + + void SendNameAndValueAndVerifyCallback(HpackEntryType entry_type, + const char* name, + StringBacking name_backing, + const char* value, + StringBacking value_backing) { + SetName(name, name_backing); + SetValue(value, value_backing); + EXPECT_CALL(listener_, OnHeader(entry_type, Eq(name), Eq(value))); + decoder_state_.OnLiteralNameAndValue(entry_type, &name_buffer_, + &value_buffer_); + Mock::VerifyAndClearExpectations(&listener_); + } + + void SendEndAndVerifyCallback() { + EXPECT_CALL(listener_, OnHeaderListEnd()); + decoder_state_.OnHeaderBlockEnd(); + Mock::VerifyAndClearExpectations(&listener_); + } + + // dynamic_index is one-based, because that is the way RFC 7541 shows it. + AssertionResult VerifyEntry(size_t dynamic_index, + const char* name, + const char* value) { + const HpackStringPair* entry = + Lookup(dynamic_index + kFirstDynamicTableIndex - 1); + VERIFY_NE(entry, nullptr); + VERIFY_EQ(entry->name.ToStringPiece(), name); + VERIFY_EQ(entry->value.ToStringPiece(), value); + return AssertionSuccess(); + } + AssertionResult VerifyNoEntry(size_t dynamic_index) { + const HpackStringPair* entry = + Lookup(dynamic_index + kFirstDynamicTableIndex - 1); + VERIFY_EQ(entry, nullptr); + return AssertionSuccess(); + } + AssertionResult VerifyDynamicTableContents( + const std::vector<std::pair<const char*, const char*>>& entries) { + size_t index = 1; + for (const auto& entry : entries) { + VERIFY_SUCCESS(VerifyEntry(index, entry.first, entry.second)); + ++index; + } + VERIFY_SUCCESS(VerifyNoEntry(index)); + return AssertionSuccess(); + } + + StrictMock<MockHpackDecoderListener> listener_; + HpackDecoderState decoder_state_; + HpackDecoderStringBuffer name_buffer_, value_buffer_; +}; + +// Test based on RFC 7541, section C.3: Request Examples without Huffman Coding. +// This section shows several consecutive header lists, corresponding to HTTP +// requests, on the same connection. +TEST_F(HpackDecoderStateTest, C3_RequestExamples) { + // C.3.1 First Request + // + // Header list to encode: + // + // :method: GET + // :scheme: http + // :path: / + // :authority: www.example.com + + SendStartAndVerifyCallback(); + SendIndexAndVerifyCallback(2, HpackEntryType::kIndexedHeader, ":method", + "GET"); + SendIndexAndVerifyCallback(6, HpackEntryType::kIndexedHeader, ":scheme", + "http"); + SendIndexAndVerifyCallback(4, HpackEntryType::kIndexedHeader, ":path", "/"); + SendValueAndVerifyCallback(1, HpackEntryType::kIndexedLiteralHeader, + ":authority", "www.example.com", UNBUFFERED); + SendEndAndVerifyCallback(); + + // Dynamic Table (after decoding): + // + // [ 1] (s = 57) :authority: www.example.com + // Table size: 57 + + ASSERT_TRUE(VerifyDynamicTableContents({{":authority", "www.example.com"}})); + ASSERT_EQ(57u, current_header_table_size()); + + // C.3.2 Second Request + // + // Header list to encode: + // + // :method: GET + // :scheme: http + // :path: / + // :authority: www.example.com + // cache-control: no-cache + + SendStartAndVerifyCallback(); + SendIndexAndVerifyCallback(2, HpackEntryType::kIndexedHeader, ":method", + "GET"); + SendIndexAndVerifyCallback(6, HpackEntryType::kIndexedHeader, ":scheme", + "http"); + SendIndexAndVerifyCallback(4, HpackEntryType::kIndexedHeader, ":path", "/"); + SendIndexAndVerifyCallback(62, HpackEntryType::kIndexedHeader, ":authority", + "www.example.com"); + SendValueAndVerifyCallback(24, HpackEntryType::kIndexedLiteralHeader, + "cache-control", "no-cache", UNBUFFERED); + SendEndAndVerifyCallback(); + + // Dynamic Table (after decoding): + // + // [ 1] (s = 53) cache-control: no-cache + // [ 2] (s = 57) :authority: www.example.com + // Table size: 110 + + ASSERT_TRUE(VerifyDynamicTableContents( + {{"cache-control", "no-cache"}, {":authority", "www.example.com"}})); + ASSERT_EQ(110u, current_header_table_size()); + + // C.3.3 Third Request + // + // Header list to encode: + // + // :method: GET + // :scheme: https + // :path: /index.html + // :authority: www.example.com + // custom-key: custom-value + + SendStartAndVerifyCallback(); + SendIndexAndVerifyCallback(2, HpackEntryType::kIndexedHeader, ":method", + "GET"); + SendIndexAndVerifyCallback(7, HpackEntryType::kIndexedHeader, ":scheme", + "https"); + SendIndexAndVerifyCallback(5, HpackEntryType::kIndexedHeader, ":path", + "/index.html"); + SendIndexAndVerifyCallback(63, HpackEntryType::kIndexedHeader, ":authority", + "www.example.com"); + SendNameAndValueAndVerifyCallback(HpackEntryType::kIndexedLiteralHeader, + "custom-key", UNBUFFERED, "custom-value", + UNBUFFERED); + SendEndAndVerifyCallback(); + + // Dynamic Table (after decoding): + // + // [ 1] (s = 54) custom-key: custom-value + // [ 2] (s = 53) cache-control: no-cache + // [ 3] (s = 57) :authority: www.example.com + // Table size: 164 + + ASSERT_TRUE(VerifyDynamicTableContents({{"custom-key", "custom-value"}, + {"cache-control", "no-cache"}, + {":authority", "www.example.com"}})); + ASSERT_EQ(164u, current_header_table_size()); +} + +// Test based on RFC 7541, section C.5: Response Examples without Huffman +// Coding. This section shows several consecutive header lists, corresponding +// to HTTP responses, on the same connection. The HTTP/2 setting parameter +// SETTINGS_HEADER_TABLE_SIZE is set to the value of 256 octets, causing +// some evictions to occur. +TEST_F(HpackDecoderStateTest, C5_ResponseExamples) { + set_header_table_size_limit(256); + + // C.5.1 First Response + // + // Header list to encode: + // + // :status: 302 + // cache-control: private + // date: Mon, 21 Oct 2013 20:13:21 GMT + // location: https://www.example.com + + SendStartAndVerifyCallback(); + SendValueAndVerifyCallback(8, HpackEntryType::kIndexedLiteralHeader, + ":status", "302", BUFFERED); + SendValueAndVerifyCallback(24, HpackEntryType::kIndexedLiteralHeader, + "cache-control", "private", UNBUFFERED); + SendValueAndVerifyCallback(33, HpackEntryType::kIndexedLiteralHeader, "date", + "Mon, 21 Oct 2013 20:13:21 GMT", UNBUFFERED); + SendValueAndVerifyCallback(46, HpackEntryType::kIndexedLiteralHeader, + "location", "https://www.example.com", UNBUFFERED); + SendEndAndVerifyCallback(); + + // Dynamic Table (after decoding): + // + // [ 1] (s = 63) location: https://www.example.com + // [ 2] (s = 65) date: Mon, 21 Oct 2013 20:13:21 GMT + // [ 3] (s = 52) cache-control: private + // [ 4] (s = 42) :status: 302 + // Table size: 222 + + ASSERT_TRUE( + VerifyDynamicTableContents({{"location", "https://www.example.com"}, + {"date", "Mon, 21 Oct 2013 20:13:21 GMT"}, + {"cache-control", "private"}, + {":status", "302"}})); + ASSERT_EQ(222u, current_header_table_size()); + + // C.5.2 Second Response + // + // The (":status", "302") header field is evicted from the dynamic table to + // free space to allow adding the (":status", "307") header field. + // + // Header list to encode: + // + // :status: 307 + // cache-control: private + // date: Mon, 21 Oct 2013 20:13:21 GMT + // location: https://www.example.com + + SendStartAndVerifyCallback(); + SendValueAndVerifyCallback(8, HpackEntryType::kIndexedLiteralHeader, + ":status", "307", BUFFERED); + SendIndexAndVerifyCallback(65, HpackEntryType::kIndexedHeader, + "cache-control", "private"); + SendIndexAndVerifyCallback(64, HpackEntryType::kIndexedHeader, "date", + "Mon, 21 Oct 2013 20:13:21 GMT"); + SendIndexAndVerifyCallback(63, HpackEntryType::kIndexedHeader, "location", + "https://www.example.com"); + SendEndAndVerifyCallback(); + + // Dynamic Table (after decoding): + // + // [ 1] (s = 42) :status: 307 + // [ 2] (s = 63) location: https://www.example.com + // [ 3] (s = 65) date: Mon, 21 Oct 2013 20:13:21 GMT + // [ 4] (s = 52) cache-control: private + // Table size: 222 + + ASSERT_TRUE( + VerifyDynamicTableContents({{":status", "307"}, + {"location", "https://www.example.com"}, + {"date", "Mon, 21 Oct 2013 20:13:21 GMT"}, + {"cache-control", "private"}})); + ASSERT_EQ(222u, current_header_table_size()); + + // C.5.3 Third Response + // + // Several header fields are evicted from the dynamic table during the + // processing of this header list. + // + // Header list to encode: + // + // :status: 200 + // cache-control: private + // date: Mon, 21 Oct 2013 20:13:22 GMT + // location: https://www.example.com + // content-encoding: gzip + // set-cookie: foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1 + + SendStartAndVerifyCallback(); + SendIndexAndVerifyCallback(8, HpackEntryType::kIndexedHeader, ":status", + "200"); + SendIndexAndVerifyCallback(65, HpackEntryType::kIndexedHeader, + "cache-control", "private"); + SendValueAndVerifyCallback(33, HpackEntryType::kIndexedLiteralHeader, "date", + "Mon, 21 Oct 2013 20:13:22 GMT", BUFFERED); + SendIndexAndVerifyCallback(64, HpackEntryType::kIndexedHeader, "location", + "https://www.example.com"); + SendValueAndVerifyCallback(26, HpackEntryType::kIndexedLiteralHeader, + "content-encoding", "gzip", UNBUFFERED); + SendValueAndVerifyCallback( + 55, HpackEntryType::kIndexedLiteralHeader, "set-cookie", + "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1", BUFFERED); + SendEndAndVerifyCallback(); + + // Dynamic Table (after decoding): + // + // [ 1] (s = 98) set-cookie: foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; + // max-age=3600; version=1 + // [ 2] (s = 52) content-encoding: gzip + // [ 3] (s = 65) date: Mon, 21 Oct 2013 20:13:22 GMT + // Table size: 215 + + ASSERT_TRUE(VerifyDynamicTableContents( + {{"set-cookie", + "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1"}, + {"content-encoding", "gzip"}, + {"date", "Mon, 21 Oct 2013 20:13:22 GMT"}})); + ASSERT_EQ(215u, current_header_table_size()); +} + +// Confirm that the table size can be changed, but at most twice. +TEST_F(HpackDecoderStateTest, OptionalTableSizeChanges) { + SendStartAndVerifyCallback(); + EXPECT_EQ(Http2SettingsInfo::DefaultHeaderTableSize(), + header_table_size_limit()); + SendSizeUpdate(1024); + EXPECT_EQ(1024u, header_table_size_limit()); + SendSizeUpdate(0); + EXPECT_EQ(0u, header_table_size_limit()); + + // Three updates aren't allowed. + EXPECT_CALL(listener_, + OnHeaderErrorDetected(HasSubstr("size update not allowed"))); + SendSizeUpdate(0); +} + +// Confirm that required size updates are indeed required before headers. +TEST_F(HpackDecoderStateTest, RequiredTableSizeChangeBeforeHeader) { + decoder_state_.ApplyHeaderTableSizeSetting(1024); + decoder_state_.ApplyHeaderTableSizeSetting(2048); + + // First provide the required update, and an allowed second update. + SendStartAndVerifyCallback(); + EXPECT_EQ(Http2SettingsInfo::DefaultHeaderTableSize(), + header_table_size_limit()); + SendSizeUpdate(1024); + EXPECT_EQ(1024u, header_table_size_limit()); + SendSizeUpdate(1500); + EXPECT_EQ(1500u, header_table_size_limit()); + SendEndAndVerifyCallback(); + + // Another HPACK block, but this time missing the required size update. + decoder_state_.ApplyHeaderTableSizeSetting(1024); + SendStartAndVerifyCallback(); + EXPECT_CALL(listener_, OnHeaderErrorDetected( + HasSubstr("Missing dynamic table size update"))); + decoder_state_.OnIndexedHeader(1); + + // Further decoded entries are ignored. + decoder_state_.OnIndexedHeader(1); + decoder_state_.OnDynamicTableSizeUpdate(1); + SetValue("value", UNBUFFERED); + decoder_state_.OnNameIndexAndLiteralValue( + HpackEntryType::kIndexedLiteralHeader, 4, &value_buffer_); + SetName("name", UNBUFFERED); + decoder_state_.OnLiteralNameAndValue(HpackEntryType::kIndexedLiteralHeader, + &name_buffer_, &value_buffer_); + decoder_state_.OnHeaderBlockEnd(); + decoder_state_.OnHpackDecodeError("NOT FORWARDED"); +} + +// Confirm that required size updates are validated. +TEST_F(HpackDecoderStateTest, InvalidRequiredSizeUpdate) { + // Require a size update, but provide one that isn't small enough. + decoder_state_.ApplyHeaderTableSizeSetting(1024); + SendStartAndVerifyCallback(); + EXPECT_EQ(Http2SettingsInfo::DefaultHeaderTableSize(), + header_table_size_limit()); + EXPECT_CALL(listener_, + OnHeaderErrorDetected(HasSubstr("above low water mark"))); + SendSizeUpdate(2048); +} + +// Confirm that required size updates are indeed required before the end. +TEST_F(HpackDecoderStateTest, RequiredTableSizeChangeBeforeEnd) { + decoder_state_.ApplyHeaderTableSizeSetting(1024); + SendStartAndVerifyCallback(); + EXPECT_CALL(listener_, OnHeaderErrorDetected( + HasSubstr("Missing dynamic table size update"))); + decoder_state_.OnHeaderBlockEnd(); +} + +// Confirm that optional size updates are validated. +TEST_F(HpackDecoderStateTest, InvalidOptionalSizeUpdate) { + // Require a size update, but provide one that isn't small enough. + SendStartAndVerifyCallback(); + EXPECT_EQ(Http2SettingsInfo::DefaultHeaderTableSize(), + header_table_size_limit()); + EXPECT_CALL(listener_, + OnHeaderErrorDetected(HasSubstr("size update is above"))); + SendSizeUpdate(Http2SettingsInfo::DefaultHeaderTableSize() + 1); +} + +TEST_F(HpackDecoderStateTest, InvalidStaticIndex) { + SendStartAndVerifyCallback(); + EXPECT_CALL(listener_, OnHeaderErrorDetected(HasSubstr("Invalid index"))); + decoder_state_.OnIndexedHeader(0); +} + +TEST_F(HpackDecoderStateTest, InvalidDynamicIndex) { + SendStartAndVerifyCallback(); + EXPECT_CALL(listener_, OnHeaderErrorDetected(HasSubstr("Invalid index"))); + decoder_state_.OnIndexedHeader(kFirstDynamicTableIndex); +} + +TEST_F(HpackDecoderStateTest, InvalidNameIndex) { + SendStartAndVerifyCallback(); + EXPECT_CALL(listener_, + OnHeaderErrorDetected(HasSubstr("Invalid name index"))); + SetValue("value", UNBUFFERED); + decoder_state_.OnNameIndexAndLiteralValue( + HpackEntryType::kIndexedLiteralHeader, kFirstDynamicTableIndex, + &value_buffer_); +} + +TEST_F(HpackDecoderStateTest, ErrorsSuppressCallbacks) { + SendStartAndVerifyCallback(); + EXPECT_CALL(listener_, + OnHeaderErrorDetected(Http2StringPiece("Huffman decode error."))); + decoder_state_.OnHpackDecodeError("Huffman decode error."); + + // Further decoded entries are ignored. + decoder_state_.OnIndexedHeader(1); + decoder_state_.OnDynamicTableSizeUpdate(1); + SetValue("value", UNBUFFERED); + decoder_state_.OnNameIndexAndLiteralValue( + HpackEntryType::kIndexedLiteralHeader, 4, &value_buffer_); + SetName("name", UNBUFFERED); + decoder_state_.OnLiteralNameAndValue(HpackEntryType::kIndexedLiteralHeader, + &name_buffer_, &value_buffer_); + decoder_state_.OnHeaderBlockEnd(); + decoder_state_.OnHpackDecodeError("NOT FORWARDED"); +} + +} // namespace +} // namespace test +} // namespace http2 diff --git a/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_string_buffer.cc b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_string_buffer.cc new file mode 100644 index 00000000000..b20c37a8204 --- /dev/null +++ b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_string_buffer.cc @@ -0,0 +1,235 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_string_buffer.h" + +#include <utility> + +#include "base/logging.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_bug_tracker.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_estimate_memory_usage.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string.h" + +namespace http2 { + +std::ostream& operator<<(std::ostream& out, + const HpackDecoderStringBuffer::State v) { + switch (v) { + case HpackDecoderStringBuffer::State::RESET: + return out << "RESET"; + case HpackDecoderStringBuffer::State::COLLECTING: + return out << "COLLECTING"; + case HpackDecoderStringBuffer::State::COMPLETE: + return out << "COMPLETE"; + } + // Since the value doesn't come over the wire, only a programming bug should + // result in reaching this point. + int unknown = static_cast<int>(v); + HTTP2_BUG << "Invalid HpackDecoderStringBuffer::State: " << unknown; + return out << "HpackDecoderStringBuffer::State(" << unknown << ")"; +} + +std::ostream& operator<<(std::ostream& out, + const HpackDecoderStringBuffer::Backing v) { + switch (v) { + case HpackDecoderStringBuffer::Backing::RESET: + return out << "RESET"; + case HpackDecoderStringBuffer::Backing::UNBUFFERED: + return out << "UNBUFFERED"; + case HpackDecoderStringBuffer::Backing::BUFFERED: + return out << "BUFFERED"; + case HpackDecoderStringBuffer::Backing::STATIC: + return out << "STATIC"; + } + // Since the value doesn't come over the wire, only a programming bug should + // result in reaching this point. + auto v2 = static_cast<int>(v); + HTTP2_BUG << "Invalid HpackDecoderStringBuffer::Backing: " << v2; + return out << "HpackDecoderStringBuffer::Backing(" << v2 << ")"; +} + +HpackDecoderStringBuffer::HpackDecoderStringBuffer() + : remaining_len_(0), + is_huffman_encoded_(false), + state_(State::RESET), + backing_(Backing::RESET) {} +HpackDecoderStringBuffer::~HpackDecoderStringBuffer() = default; + +void HpackDecoderStringBuffer::Reset() { + DVLOG(3) << "HpackDecoderStringBuffer::Reset"; + state_ = State::RESET; +} + +void HpackDecoderStringBuffer::Set(Http2StringPiece value, bool is_static) { + DVLOG(2) << "HpackDecoderStringBuffer::Set"; + DCHECK_EQ(state_, State::RESET); + value_ = value; + state_ = State::COMPLETE; + backing_ = is_static ? Backing::STATIC : Backing::UNBUFFERED; + // TODO(jamessynge): Determine which of these two fields must be set. + remaining_len_ = 0; + is_huffman_encoded_ = false; +} + +void HpackDecoderStringBuffer::OnStart(bool huffman_encoded, size_t len) { + DVLOG(2) << "HpackDecoderStringBuffer::OnStart"; + DCHECK_EQ(state_, State::RESET); + + remaining_len_ = len; + is_huffman_encoded_ = huffman_encoded; + state_ = State::COLLECTING; + + if (huffman_encoded) { + // We don't set, clear or use value_ for buffered strings until OnEnd. + decoder_.Reset(); + buffer_.clear(); + backing_ = Backing::BUFFERED; + + // Reserve space in buffer_ for the uncompressed string, assuming the + // maximum expansion. The shortest Huffman codes in the RFC are 5 bits long, + // which then expand to 8 bits during decoding (i.e. each code is for one + // plain text octet, aka byte), so the maximum size is 60% longer than the + // encoded size. + len = len * 8 / 5; + if (buffer_.capacity() < len) { + buffer_.reserve(len); + } + } else { + // Assume for now that we won't need to use buffer_, so don't reserve space + // in it. + backing_ = Backing::RESET; + // OnData is not called for empty (zero length) strings, so make sure that + // value_ is cleared. + value_ = Http2StringPiece(); + } +} + +bool HpackDecoderStringBuffer::OnData(const char* data, size_t len) { + DVLOG(2) << "HpackDecoderStringBuffer::OnData state=" << state_ + << ", backing=" << backing_; + DCHECK_EQ(state_, State::COLLECTING); + DCHECK_LE(len, remaining_len_); + remaining_len_ -= len; + + if (is_huffman_encoded_) { + DCHECK_EQ(backing_, Backing::BUFFERED); + return decoder_.Decode(Http2StringPiece(data, len), &buffer_); + } + + if (backing_ == Backing::RESET) { + // This is the first call to OnData. If data contains the entire string, + // don't copy the string. If we later find that the HPACK entry is split + // across input buffers, then we'll copy the string into buffer_. + if (remaining_len_ == 0) { + value_ = Http2StringPiece(data, len); + backing_ = Backing::UNBUFFERED; + return true; + } + + // We need to buffer the string because it is split across input buffers. + // Reserve space in buffer_ for the entire string. + backing_ = Backing::BUFFERED; + buffer_.reserve(remaining_len_ + len); + buffer_.assign(data, len); + return true; + } + + // This is not the first call to OnData for this string, so it should be + // buffered. + DCHECK_EQ(backing_, Backing::BUFFERED); + + // Append to the current contents of the buffer. + buffer_.append(data, len); + return true; +} + +bool HpackDecoderStringBuffer::OnEnd() { + DVLOG(2) << "HpackDecoderStringBuffer::OnEnd"; + DCHECK_EQ(state_, State::COLLECTING); + DCHECK_EQ(0u, remaining_len_); + + if (is_huffman_encoded_) { + DCHECK_EQ(backing_, Backing::BUFFERED); + // Did the Huffman encoding of the string end properly? + if (!decoder_.InputProperlyTerminated()) { + return false; // No, it didn't. + } + value_ = buffer_; + } else if (backing_ == Backing::BUFFERED) { + value_ = buffer_; + } + state_ = State::COMPLETE; + return true; +} + +void HpackDecoderStringBuffer::BufferStringIfUnbuffered() { + DVLOG(3) << "HpackDecoderStringBuffer::BufferStringIfUnbuffered state=" + << state_ << ", backing=" << backing_; + if (state_ != State::RESET && backing_ == Backing::UNBUFFERED) { + DVLOG(2) << "HpackDecoderStringBuffer buffering Http2String of length " + << value_.size(); + buffer_.assign(value_.data(), value_.size()); + if (state_ == State::COMPLETE) { + value_ = buffer_; + } + backing_ = Backing::BUFFERED; + } +} + +bool HpackDecoderStringBuffer::IsBuffered() const { + DVLOG(3) << "HpackDecoderStringBuffer::IsBuffered"; + return state_ != State::RESET && backing_ == Backing::BUFFERED; +} + +size_t HpackDecoderStringBuffer::BufferedLength() const { + DVLOG(3) << "HpackDecoderStringBuffer::BufferedLength"; + return IsBuffered() ? buffer_.size() : 0; +} + +Http2StringPiece HpackDecoderStringBuffer::str() const { + DVLOG(3) << "HpackDecoderStringBuffer::str"; + DCHECK_EQ(state_, State::COMPLETE); + return value_; +} + +Http2String HpackDecoderStringBuffer::ReleaseString() { + DVLOG(3) << "HpackDecoderStringBuffer::ReleaseString"; + DCHECK_EQ(state_, State::COMPLETE); + DCHECK_EQ(backing_, Backing::BUFFERED); + if (state_ == State::COMPLETE) { + state_ = State::RESET; + if (backing_ == Backing::BUFFERED) { + return std::move(buffer_); + } else { + return Http2String(value_); + } + } + return ""; +} + +void HpackDecoderStringBuffer::OutputDebugStringTo(std::ostream& out) const { + out << "{state=" << state_; + if (state_ != State::RESET) { + out << ", backing=" << backing_; + out << ", remaining_len=" << remaining_len_; + out << ", is_huffman_encoded=" << is_huffman_encoded_; + if (backing_ == Backing::BUFFERED) { + out << ", buffer: " << buffer_; + } else { + out << ", value: " << value_; + } + } + out << "}"; +} + +size_t HpackDecoderStringBuffer::EstimateMemoryUsage() const { + return Http2EstimateMemoryUsage(buffer_); +} + +std::ostream& operator<<(std::ostream& out, const HpackDecoderStringBuffer& v) { + v.OutputDebugStringTo(out); + return out; +} + +} // namespace http2 diff --git a/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_string_buffer.h b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_string_buffer.h new file mode 100644 index 00000000000..8a810b2d9d3 --- /dev/null +++ b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_string_buffer.h @@ -0,0 +1,102 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef QUICHE_HTTP2_HPACK_DECODER_HPACK_DECODER_STRING_BUFFER_H_ +#define QUICHE_HTTP2_HPACK_DECODER_HPACK_DECODER_STRING_BUFFER_H_ + +// HpackDecoderStringBuffer helps an HPACK decoder to avoid copies of a string +// literal (name or value) except when necessary (e.g. when split across two +// or more HPACK block fragments). + +#include <stddef.h> + +#include <ostream> + +#include "net/third_party/quiche/src/http2/hpack/huffman/hpack_huffman_decoder.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_export.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string_piece.h" + +namespace http2 { + +class HTTP2_EXPORT_PRIVATE HpackDecoderStringBuffer { + public: + enum class State : uint8_t { RESET, COLLECTING, COMPLETE }; + enum class Backing : uint8_t { RESET, UNBUFFERED, BUFFERED, STATIC }; + + HpackDecoderStringBuffer(); + ~HpackDecoderStringBuffer(); + + HpackDecoderStringBuffer(const HpackDecoderStringBuffer&) = delete; + HpackDecoderStringBuffer& operator=(const HpackDecoderStringBuffer&) = delete; + + void Reset(); + void Set(Http2StringPiece value, bool is_static); + + // Note that for Huffman encoded strings the length of the string after + // decoding may be larger (expected), the same or even smaller; the latter + // are unlikely, but possible if the encoder makes odd choices. + void OnStart(bool huffman_encoded, size_t len); + bool OnData(const char* data, size_t len); + bool OnEnd(); + void BufferStringIfUnbuffered(); + bool IsBuffered() const; + size_t BufferedLength() const; + + // Accessors for the completely collected string (i.e. Set or OnEnd has just + // been called, and no reset of the state has occurred). + + // Returns a Http2StringPiece pointing to the backing store for the string, + // either the internal buffer or the original transport buffer (e.g. for a + // literal value that wasn't Huffman encoded, and that wasn't split across + // transport buffers). + Http2StringPiece str() const; + + // Returns the completely collected string by value, using std::move in an + // effort to avoid unnecessary copies. ReleaseString() must not be called + // unless the string has been buffered (to avoid forcing a potentially + // unnecessary copy). ReleaseString() also resets the instance so that it can + // be used to collect another string. + Http2String ReleaseString(); + + State state_for_testing() const { return state_; } + Backing backing_for_testing() const { return backing_; } + void OutputDebugStringTo(std::ostream& out) const; + + // Returns the estimate of dynamically allocated memory in bytes. + size_t EstimateMemoryUsage() const; + + private: + // Storage for the string being buffered, if buffering is necessary + // (e.g. if Huffman encoded, buffer_ is storage for the decoded string). + Http2String buffer_; + + // The Http2StringPiece to be returned by HpackDecoderStringBuffer::str(). If + // a string has been collected, but not buffered, value_ points to that + // string. + Http2StringPiece value_; + + // The decoder to use if the string is Huffman encoded. + HpackHuffmanDecoder decoder_; + + // Count of bytes not yet passed to OnData. + size_t remaining_len_; + + // Is the HPACK string Huffman encoded? + bool is_huffman_encoded_; + + // State of the string decoding process. + State state_; + + // Where is the string stored? + Backing backing_; +}; + +HTTP2_EXPORT_PRIVATE std::ostream& operator<<( + std::ostream& out, + const HpackDecoderStringBuffer& v); + +} // namespace http2 + +#endif // QUICHE_HTTP2_HPACK_DECODER_HPACK_DECODER_STRING_BUFFER_H_ diff --git a/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_string_buffer_test.cc b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_string_buffer_test.cc new file mode 100644 index 00000000000..009b894fbe6 --- /dev/null +++ b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_string_buffer_test.cc @@ -0,0 +1,251 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_string_buffer.h" + +// Tests of HpackDecoderStringBuffer. + +#include <initializer_list> + +#include "base/logging.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string_utils.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_test_helpers.h" + +using ::testing::AssertionResult; +using ::testing::AssertionSuccess; +using ::testing::HasSubstr; + +namespace http2 { +namespace test { +namespace { + +class HpackDecoderStringBufferTest : public ::testing::Test { + protected: + typedef HpackDecoderStringBuffer::State State; + typedef HpackDecoderStringBuffer::Backing Backing; + + State state() const { return buf_.state_for_testing(); } + Backing backing() const { return buf_.backing_for_testing(); } + + // We want to know that LOG(x) << buf_ will work in production should that + // be needed, so we test that it outputs the expected values. + AssertionResult VerifyLogHasSubstrs(std::initializer_list<Http2String> strs) { + VLOG(1) << buf_; + std::ostringstream ss; + buf_.OutputDebugStringTo(ss); + Http2String dbg_str(ss.str()); + for (const auto& expected : strs) { + VERIFY_THAT(dbg_str, HasSubstr(expected)); + } + return AssertionSuccess(); + } + + HpackDecoderStringBuffer buf_; +}; + +TEST_F(HpackDecoderStringBufferTest, SetStatic) { + Http2StringPiece data("static string"); + + EXPECT_EQ(state(), State::RESET); + EXPECT_TRUE(VerifyLogHasSubstrs({"state=RESET"})); + + buf_.Set(data, /*is_static*/ true); + LOG(INFO) << buf_; + EXPECT_EQ(state(), State::COMPLETE); + EXPECT_EQ(backing(), Backing::STATIC); + EXPECT_EQ(data, buf_.str()); + EXPECT_EQ(data.data(), buf_.str().data()); + EXPECT_TRUE(VerifyLogHasSubstrs( + {"state=COMPLETE", "backing=STATIC", "value: static string"})); + + // The string is static, so BufferStringIfUnbuffered won't change anything. + buf_.BufferStringIfUnbuffered(); + EXPECT_EQ(state(), State::COMPLETE); + EXPECT_EQ(backing(), Backing::STATIC); + EXPECT_EQ(data, buf_.str()); + EXPECT_EQ(data.data(), buf_.str().data()); + EXPECT_TRUE(VerifyLogHasSubstrs( + {"state=COMPLETE", "backing=STATIC", "value: static string"})); +} + +TEST_F(HpackDecoderStringBufferTest, PlainWhole) { + Http2StringPiece data("some text."); + + LOG(INFO) << buf_; + EXPECT_EQ(state(), State::RESET); + + buf_.OnStart(/*huffman_encoded*/ false, data.size()); + EXPECT_EQ(state(), State::COLLECTING); + EXPECT_EQ(backing(), Backing::RESET); + LOG(INFO) << buf_; + + EXPECT_TRUE(buf_.OnData(data.data(), data.size())); + EXPECT_EQ(state(), State::COLLECTING); + EXPECT_EQ(backing(), Backing::UNBUFFERED); + + EXPECT_TRUE(buf_.OnEnd()); + EXPECT_EQ(state(), State::COMPLETE); + EXPECT_EQ(backing(), Backing::UNBUFFERED); + EXPECT_EQ(0u, buf_.BufferedLength()); + EXPECT_TRUE(VerifyLogHasSubstrs( + {"state=COMPLETE", "backing=UNBUFFERED", "value: some text."})); + + // We expect that the string buffer points to the passed in Http2StringPiece's + // backing store. + EXPECT_EQ(data.data(), buf_.str().data()); + + // Now force it to buffer the string, after which it will still have the same + // string value, but the backing store will be different. + buf_.BufferStringIfUnbuffered(); + LOG(INFO) << buf_; + EXPECT_EQ(backing(), Backing::BUFFERED); + EXPECT_EQ(buf_.BufferedLength(), data.size()); + EXPECT_EQ(data, buf_.str()); + EXPECT_NE(data.data(), buf_.str().data()); + EXPECT_TRUE(VerifyLogHasSubstrs( + {"state=COMPLETE", "backing=BUFFERED", "buffer: some text."})); +} + +TEST_F(HpackDecoderStringBufferTest, PlainSplit) { + Http2StringPiece data("some text."); + Http2StringPiece part1 = data.substr(0, 1); + Http2StringPiece part2 = data.substr(1); + + EXPECT_EQ(state(), State::RESET); + buf_.OnStart(/*huffman_encoded*/ false, data.size()); + EXPECT_EQ(state(), State::COLLECTING); + EXPECT_EQ(backing(), Backing::RESET); + + // OnData with only a part of the data, not the whole, so buf_ will buffer + // the data. + EXPECT_TRUE(buf_.OnData(part1.data(), part1.size())); + EXPECT_EQ(state(), State::COLLECTING); + EXPECT_EQ(backing(), Backing::BUFFERED); + EXPECT_EQ(buf_.BufferedLength(), part1.size()); + LOG(INFO) << buf_; + + EXPECT_TRUE(buf_.OnData(part2.data(), part2.size())); + EXPECT_EQ(state(), State::COLLECTING); + EXPECT_EQ(backing(), Backing::BUFFERED); + EXPECT_EQ(buf_.BufferedLength(), data.size()); + + EXPECT_TRUE(buf_.OnEnd()); + EXPECT_EQ(state(), State::COMPLETE); + EXPECT_EQ(backing(), Backing::BUFFERED); + EXPECT_EQ(buf_.BufferedLength(), data.size()); + LOG(INFO) << buf_; + + Http2StringPiece buffered = buf_.str(); + EXPECT_EQ(data, buffered); + EXPECT_NE(data.data(), buffered.data()); + + // The string is already buffered, so BufferStringIfUnbuffered should not make + // any change. + buf_.BufferStringIfUnbuffered(); + EXPECT_EQ(backing(), Backing::BUFFERED); + EXPECT_EQ(buf_.BufferedLength(), data.size()); + EXPECT_EQ(buffered, buf_.str()); + EXPECT_EQ(buffered.data(), buf_.str().data()); +} + +TEST_F(HpackDecoderStringBufferTest, HuffmanWhole) { + Http2String encoded = Http2HexDecode("f1e3c2e5f23a6ba0ab90f4ff"); + Http2StringPiece decoded("www.example.com"); + + EXPECT_EQ(state(), State::RESET); + buf_.OnStart(/*huffman_encoded*/ true, encoded.size()); + EXPECT_EQ(state(), State::COLLECTING); + + EXPECT_TRUE(buf_.OnData(encoded.data(), encoded.size())); + EXPECT_EQ(state(), State::COLLECTING); + EXPECT_EQ(backing(), Backing::BUFFERED); + + EXPECT_TRUE(buf_.OnEnd()); + EXPECT_EQ(state(), State::COMPLETE); + EXPECT_EQ(backing(), Backing::BUFFERED); + EXPECT_EQ(buf_.BufferedLength(), decoded.size()); + EXPECT_EQ(decoded, buf_.str()); + EXPECT_TRUE(VerifyLogHasSubstrs( + {"{state=COMPLETE", "backing=BUFFERED", "buffer: www.example.com}"})); + + Http2String s = buf_.ReleaseString(); + EXPECT_EQ(s, decoded); + EXPECT_EQ(state(), State::RESET); +} + +TEST_F(HpackDecoderStringBufferTest, HuffmanSplit) { + Http2String encoded = Http2HexDecode("f1e3c2e5f23a6ba0ab90f4ff"); + Http2String part1 = encoded.substr(0, 5); + Http2String part2 = encoded.substr(5); + Http2StringPiece decoded("www.example.com"); + + EXPECT_EQ(state(), State::RESET); + buf_.OnStart(/*huffman_encoded*/ true, encoded.size()); + EXPECT_EQ(state(), State::COLLECTING); + EXPECT_EQ(backing(), Backing::BUFFERED); + EXPECT_EQ(0u, buf_.BufferedLength()); + LOG(INFO) << buf_; + + EXPECT_TRUE(buf_.OnData(part1.data(), part1.size())); + EXPECT_EQ(state(), State::COLLECTING); + EXPECT_EQ(backing(), Backing::BUFFERED); + EXPECT_GT(buf_.BufferedLength(), 0u); + EXPECT_LT(buf_.BufferedLength(), decoded.size()); + LOG(INFO) << buf_; + + EXPECT_TRUE(buf_.OnData(part2.data(), part2.size())); + EXPECT_EQ(state(), State::COLLECTING); + EXPECT_EQ(backing(), Backing::BUFFERED); + EXPECT_EQ(buf_.BufferedLength(), decoded.size()); + LOG(INFO) << buf_; + + EXPECT_TRUE(buf_.OnEnd()); + EXPECT_EQ(state(), State::COMPLETE); + EXPECT_EQ(backing(), Backing::BUFFERED); + EXPECT_EQ(buf_.BufferedLength(), decoded.size()); + EXPECT_EQ(decoded, buf_.str()); + LOG(INFO) << buf_; + + buf_.Reset(); + EXPECT_EQ(state(), State::RESET); + LOG(INFO) << buf_; +} + +TEST_F(HpackDecoderStringBufferTest, InvalidHuffmanOnData) { + // Explicitly encode the End-of-String symbol, a no-no. + Http2String encoded = Http2HexDecode("ffffffff"); + + buf_.OnStart(/*huffman_encoded*/ true, encoded.size()); + EXPECT_EQ(state(), State::COLLECTING); + + EXPECT_FALSE(buf_.OnData(encoded.data(), encoded.size())); + EXPECT_EQ(state(), State::COLLECTING); + EXPECT_EQ(backing(), Backing::BUFFERED); + + LOG(INFO) << buf_; +} + +TEST_F(HpackDecoderStringBufferTest, InvalidHuffmanOnEnd) { + // Last byte of string doesn't end with prefix of End-of-String symbol. + Http2String encoded = Http2HexDecode("00"); + + buf_.OnStart(/*huffman_encoded*/ true, encoded.size()); + EXPECT_EQ(state(), State::COLLECTING); + + EXPECT_TRUE(buf_.OnData(encoded.data(), encoded.size())); + EXPECT_EQ(state(), State::COLLECTING); + EXPECT_EQ(backing(), Backing::BUFFERED); + + EXPECT_FALSE(buf_.OnEnd()); + LOG(INFO) << buf_; +} + +// TODO(jamessynge): Add tests for ReleaseString(). + +} // namespace +} // namespace test +} // namespace http2 diff --git a/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_tables.cc b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_tables.cc new file mode 100644 index 00000000000..c88648ff597 --- /dev/null +++ b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_tables.cc @@ -0,0 +1,153 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_tables.h" + +#include "base/logging.h" +#include "net/third_party/quiche/src/http2/hpack/http2_hpack_constants.h" + +namespace http2 { +namespace { + +std::vector<HpackStringPair>* MakeStaticTable() { + auto* ptr = new std::vector<HpackStringPair>(); + ptr->reserve(kFirstDynamicTableIndex); + ptr->emplace_back("", ""); + +#define STATIC_TABLE_ENTRY(name, value, index) \ + DCHECK_EQ(ptr->size(), static_cast<size_t>(index)); \ + ptr->emplace_back(name, value) + +#include "net/third_party/quiche/src/http2/hpack/hpack_static_table_entries.inc" + +#undef STATIC_TABLE_ENTRY + + return ptr; +} + +const std::vector<HpackStringPair>* GetStaticTable() { + static const std::vector<HpackStringPair>* const g_static_table = + MakeStaticTable(); + return g_static_table; +} + +} // namespace + +HpackDecoderTablesDebugListener::HpackDecoderTablesDebugListener() = default; +HpackDecoderTablesDebugListener::~HpackDecoderTablesDebugListener() = default; + +HpackDecoderStaticTable::HpackDecoderStaticTable( + const std::vector<HpackStringPair>* table) + : table_(table) {} + +HpackDecoderStaticTable::HpackDecoderStaticTable() : table_(GetStaticTable()) {} + +const HpackStringPair* HpackDecoderStaticTable::Lookup(size_t index) const { + if (0 < index && index < kFirstDynamicTableIndex) { + return &((*table_)[index]); + } + return nullptr; +} + +HpackDecoderDynamicTable::HpackDecoderTableEntry::HpackDecoderTableEntry( + const HpackString& name, + const HpackString& value) + : HpackStringPair(name, value) {} + +HpackDecoderDynamicTable::HpackDecoderDynamicTable() + : insert_count_(kFirstDynamicTableIndex - 1), debug_listener_(nullptr) {} +HpackDecoderDynamicTable::~HpackDecoderDynamicTable() = default; + +void HpackDecoderDynamicTable::DynamicTableSizeUpdate(size_t size_limit) { + DVLOG(3) << "HpackDecoderDynamicTable::DynamicTableSizeUpdate " << size_limit; + EnsureSizeNoMoreThan(size_limit); + DCHECK_LE(current_size_, size_limit); + size_limit_ = size_limit; +} + +// TODO(jamessynge): Check somewhere before here that names received from the +// peer are valid (e.g. are lower-case, no whitespace, etc.). +bool HpackDecoderDynamicTable::Insert(const HpackString& name, + const HpackString& value) { + HpackDecoderTableEntry entry(name, value); + size_t entry_size = entry.size(); + DVLOG(2) << "InsertEntry of size=" << entry_size << "\n name: " << name + << "\n value: " << value; + if (entry_size > size_limit_) { + DVLOG(2) << "InsertEntry: entry larger than table, removing " + << table_.size() << " entries, of total size " << current_size_ + << " bytes."; + table_.clear(); + current_size_ = 0; + return false; // Not inserted because too large. + } + ++insert_count_; + if (debug_listener_ != nullptr) { + entry.time_added = debug_listener_->OnEntryInserted(entry, insert_count_); + DVLOG(2) << "OnEntryInserted returned time_added=" << entry.time_added + << " for insert_count_=" << insert_count_; + } + size_t insert_limit = size_limit_ - entry_size; + EnsureSizeNoMoreThan(insert_limit); + table_.push_front(entry); + current_size_ += entry_size; + DVLOG(2) << "InsertEntry: current_size_=" << current_size_; + DCHECK_GE(current_size_, entry_size); + DCHECK_LE(current_size_, size_limit_); + return true; +} + +const HpackStringPair* HpackDecoderDynamicTable::Lookup(size_t index) const { + if (index < table_.size()) { + const HpackDecoderTableEntry& entry = table_[index]; + if (debug_listener_ != nullptr) { + size_t insert_count_of_index = insert_count_ + table_.size() - index; + debug_listener_->OnUseEntry(entry, insert_count_of_index, + entry.time_added); + } + return &entry; + } + return nullptr; +} + +void HpackDecoderDynamicTable::EnsureSizeNoMoreThan(size_t limit) { + DVLOG(2) << "EnsureSizeNoMoreThan limit=" << limit + << ", current_size_=" << current_size_; + // Not the most efficient choice, but any easy way to start. + while (current_size_ > limit) { + RemoveLastEntry(); + } + DCHECK_LE(current_size_, limit); +} + +void HpackDecoderDynamicTable::RemoveLastEntry() { + DCHECK(!table_.empty()); + if (!table_.empty()) { + DVLOG(2) << "RemoveLastEntry current_size_=" << current_size_ + << ", last entry size=" << table_.back().size(); + DCHECK_GE(current_size_, table_.back().size()); + current_size_ -= table_.back().size(); + table_.pop_back(); + // Empty IFF current_size_ == 0. + DCHECK_EQ(table_.empty(), current_size_ == 0); + } +} + +HpackDecoderTables::HpackDecoderTables() = default; +HpackDecoderTables::~HpackDecoderTables() = default; + +void HpackDecoderTables::set_debug_listener( + HpackDecoderTablesDebugListener* debug_listener) { + dynamic_table_.set_debug_listener(debug_listener); +} + +const HpackStringPair* HpackDecoderTables::Lookup(size_t index) const { + if (index < kFirstDynamicTableIndex) { + return static_table_.Lookup(index); + } else { + return dynamic_table_.Lookup(index - kFirstDynamicTableIndex); + } +} + +} // namespace http2 diff --git a/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_tables.h b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_tables.h new file mode 100644 index 00000000000..6df145c9b7e --- /dev/null +++ b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_tables.h @@ -0,0 +1,197 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef QUICHE_HTTP2_HPACK_DECODER_HPACK_DECODER_TABLES_H_ +#define QUICHE_HTTP2_HPACK_DECODER_HPACK_DECODER_TABLES_H_ + +// Static and dynamic tables for the HPACK decoder. See: +// http://httpwg.org/specs/rfc7541.html#indexing.tables + +// Note that the Lookup methods return nullptr if the requested index was not +// found. This should be treated as a COMPRESSION error according to the HTTP/2 +// spec, which is a connection level protocol error (i.e. the connection must +// be terminated). See these sections in the two RFCs: +// http://httpwg.org/specs/rfc7541.html#indexed.header.representation +// http://httpwg.org/specs/rfc7541.html#index.address.space +// http://httpwg.org/specs/rfc7540.html#HeaderBlock + +#include <stddef.h> + +#include <cstdint> +#include <vector> + +#include "net/third_party/quiche/src/http2/hpack/hpack_string.h" +#include "net/third_party/quiche/src/http2/http2_constants.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_containers.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_export.h" + +namespace http2 { +namespace test { +class HpackDecoderTablesPeer; +} // namespace test + +// HpackDecoderTablesDebugListener supports a QUIC experiment, enabling +// the gathering of information about the time-line of use of HPACK +// dynamic table entries. +class HTTP2_EXPORT_PRIVATE HpackDecoderTablesDebugListener { + public: + HpackDecoderTablesDebugListener(); + virtual ~HpackDecoderTablesDebugListener(); + + HpackDecoderTablesDebugListener(const HpackDecoderTablesDebugListener&) = + delete; + HpackDecoderTablesDebugListener& operator=( + const HpackDecoderTablesDebugListener&) = delete; + + // The entry has been inserted into the dynamic table. insert_count starts at + // 62 because 61 is the last index in the static table; insert_count increases + // by 1 with each insert into the dynamic table; it is not incremented when + // when a entry is too large to fit into the dynamic table at all (which has + // the effect of emptying the dynamic table). + // Returns a value that can be used as time_added in OnUseEntry. + virtual int64_t OnEntryInserted(const HpackStringPair& entry, + size_t insert_count) = 0; + + // The entry has been used, either for the name or for the name and value. + // insert_count is the same as passed to OnEntryInserted when entry was + // inserted to the dynamic table, and time_added is the value that was + // returned by OnEntryInserted. + virtual void OnUseEntry(const HpackStringPair& entry, + size_t insert_count, + int64_t time_added) = 0; +}; + +// See http://httpwg.org/specs/rfc7541.html#static.table.definition for the +// contents, and http://httpwg.org/specs/rfc7541.html#index.address.space for +// info about accessing the static table. +class HTTP2_EXPORT_PRIVATE HpackDecoderStaticTable { + public: + explicit HpackDecoderStaticTable(const std::vector<HpackStringPair>* table); + // Uses a global table shared by all threads. + HpackDecoderStaticTable(); + + // If index is valid, returns a pointer to the entry, otherwise returns + // nullptr. + const HpackStringPair* Lookup(size_t index) const; + + private: + friend class test::HpackDecoderTablesPeer; + const std::vector<HpackStringPair>* const table_; +}; + +// HpackDecoderDynamicTable implements HPACK compression feature "indexed +// headers"; previously sent headers may be referenced later by their index +// in the dynamic table. See these sections of the RFC: +// http://httpwg.org/specs/rfc7541.html#dynamic.table +// http://httpwg.org/specs/rfc7541.html#dynamic.table.management +class HTTP2_EXPORT_PRIVATE HpackDecoderDynamicTable { + public: + HpackDecoderDynamicTable(); + ~HpackDecoderDynamicTable(); + + HpackDecoderDynamicTable(const HpackDecoderDynamicTable&) = delete; + HpackDecoderDynamicTable& operator=(const HpackDecoderDynamicTable&) = delete; + + // Set the listener to be notified of insertions into this table, and later + // uses of those entries. Added for evaluation of changes to QUIC's use + // of HPACK. + void set_debug_listener(HpackDecoderTablesDebugListener* debug_listener) { + debug_listener_ = debug_listener; + } + + // Sets a new size limit, received from the peer; performs evictions if + // necessary to ensure that the current size does not exceed the new limit. + // The caller needs to have validated that size_limit does not + // exceed the acknowledged value of SETTINGS_HEADER_TABLE_SIZE. + void DynamicTableSizeUpdate(size_t size_limit); + + // Returns true if inserted, false if too large (at which point the + // dynamic table will be empty.) + bool Insert(const HpackString& name, const HpackString& value); + + // If index is valid, returns a pointer to the entry, otherwise returns + // nullptr. + const HpackStringPair* Lookup(size_t index) const; + + size_t size_limit() const { return size_limit_; } + size_t current_size() const { return current_size_; } + + private: + friend class test::HpackDecoderTablesPeer; + struct HpackDecoderTableEntry : public HpackStringPair { + HpackDecoderTableEntry(const HpackString& name, const HpackString& value); + int64_t time_added; + }; + + // Drop older entries to ensure the size is not greater than limit. + void EnsureSizeNoMoreThan(size_t limit); + + // Removes the oldest dynamic table entry. + void RemoveLastEntry(); + + Http2Deque<HpackDecoderTableEntry> table_; + + // The last received DynamicTableSizeUpdate value, initialized to + // SETTINGS_HEADER_TABLE_SIZE. + size_t size_limit_ = Http2SettingsInfo::DefaultHeaderTableSize(); + + size_t current_size_ = 0; + + // insert_count_ and debug_listener_ are used by a QUIC experiment; remove + // when the experiment is done. + size_t insert_count_; + HpackDecoderTablesDebugListener* debug_listener_; +}; + +class HTTP2_EXPORT_PRIVATE HpackDecoderTables { + public: + HpackDecoderTables(); + ~HpackDecoderTables(); + + HpackDecoderTables(const HpackDecoderTables&) = delete; + HpackDecoderTables& operator=(const HpackDecoderTables&) = delete; + + // Set the listener to be notified of insertions into the dynamic table, and + // later uses of those entries. Added for evaluation of changes to QUIC's use + // of HPACK. + void set_debug_listener(HpackDecoderTablesDebugListener* debug_listener); + + // Sets a new size limit, received from the peer; performs evictions if + // necessary to ensure that the current size does not exceed the new limit. + // The caller needs to have validated that size_limit does not + // exceed the acknowledged value of SETTINGS_HEADER_TABLE_SIZE. + void DynamicTableSizeUpdate(size_t size_limit) { + dynamic_table_.DynamicTableSizeUpdate(size_limit); + } + + // Returns true if inserted, false if too large (at which point the + // dynamic table will be empty.) + // TODO(jamessynge): Add methods for moving the string(s) into the table, + // or for otherwise avoiding unnecessary copies. + bool Insert(const HpackString& name, const HpackString& value) { + return dynamic_table_.Insert(name, value); + } + + // If index is valid, returns a pointer to the entry, otherwise returns + // nullptr. + const HpackStringPair* Lookup(size_t index) const; + + // The size limit that the peer (the HPACK encoder) has told the decoder it is + // currently operating with. Defaults to SETTINGS_HEADER_TABLE_SIZE, 4096. + size_t header_table_size_limit() const { return dynamic_table_.size_limit(); } + + // Sum of the sizes of the dynamic table entries. + size_t current_header_table_size() const { + return dynamic_table_.current_size(); + } + + private: + friend class test::HpackDecoderTablesPeer; + HpackDecoderStaticTable static_table_; + HpackDecoderDynamicTable dynamic_table_; +}; + +} // namespace http2 + +#endif // QUICHE_HTTP2_HPACK_DECODER_HPACK_DECODER_TABLES_H_ diff --git a/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_tables_test.cc b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_tables_test.cc new file mode 100644 index 00000000000..a54fa5f053b --- /dev/null +++ b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_tables_test.cc @@ -0,0 +1,266 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_tables.h" + +#include <algorithm> +#include <tuple> +#include <vector> + +#include "base/logging.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "net/third_party/quiche/src/http2/hpack/http2_hpack_constants.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_test_helpers.h" +#include "net/third_party/quiche/src/http2/test_tools/http2_random.h" +#include "net/third_party/quiche/src/http2/tools/random_util.h" + +using ::testing::AssertionResult; +using ::testing::AssertionSuccess; + +namespace http2 { +namespace test { +class HpackDecoderTablesPeer { + public: + static size_t num_dynamic_entries(const HpackDecoderTables& tables) { + return tables.dynamic_table_.table_.size(); + } +}; + +namespace { +struct StaticEntry { + const char* name; + const char* value; + size_t index; +}; + +std::vector<StaticEntry> MakeSpecStaticEntries() { + std::vector<StaticEntry> static_entries; + +#define STATIC_TABLE_ENTRY(name, value, index) \ + DCHECK_EQ(static_entries.size() + 1, static_cast<size_t>(index)); \ + static_entries.push_back({name, value, index}); + +#include "net/third_party/quiche/src/http2/hpack/hpack_static_table_entries.inc" + +#undef STATIC_TABLE_ENTRY + + return static_entries; +} + +template <class C> +void ShuffleCollection(C* collection, Http2Random* r) { + std::shuffle(collection->begin(), collection->end(), *r); +} + +class HpackDecoderStaticTableTest : public ::testing::Test { + protected: + HpackDecoderStaticTableTest() = default; + + std::vector<StaticEntry> shuffled_static_entries() { + std::vector<StaticEntry> entries = MakeSpecStaticEntries(); + ShuffleCollection(&entries, &random_); + return entries; + } + + // This test is in a function so that it can be applied to both the static + // table and the combined static+dynamic tables. + AssertionResult VerifyStaticTableContents() { + for (const auto& expected : shuffled_static_entries()) { + const HpackStringPair* found = Lookup(expected.index); + VERIFY_NE(found, nullptr); + VERIFY_EQ(expected.name, found->name) << expected.index; + VERIFY_EQ(expected.value, found->value) << expected.index; + } + + // There should be no entry with index 0. + VERIFY_EQ(nullptr, Lookup(0)); + return AssertionSuccess(); + } + + virtual const HpackStringPair* Lookup(size_t index) { + return static_table_.Lookup(index); + } + + Http2Random* RandomPtr() { return &random_; } + + Http2Random random_; + + private: + HpackDecoderStaticTable static_table_; +}; + +TEST_F(HpackDecoderStaticTableTest, StaticTableContents) { + EXPECT_TRUE(VerifyStaticTableContents()); +} + +size_t Size(const Http2String& name, const Http2String& value) { + return name.size() + value.size() + 32; +} + +// To support tests with more than a few of hand crafted changes to the dynamic +// table, we have another, exceedingly simple, implementation of the HPACK +// dynamic table containing FakeHpackEntry instances. We can thus compare the +// contents of the actual table with those in fake_dynamic_table_. + +typedef std::tuple<Http2String, Http2String, size_t> FakeHpackEntry; +const Http2String& Name(const FakeHpackEntry& entry) { + return std::get<0>(entry); +} +const Http2String& Value(const FakeHpackEntry& entry) { + return std::get<1>(entry); +} +size_t Size(const FakeHpackEntry& entry) { + return std::get<2>(entry); +} + +class HpackDecoderTablesTest : public HpackDecoderStaticTableTest { + protected: + const HpackStringPair* Lookup(size_t index) override { + return tables_.Lookup(index); + } + + size_t dynamic_size_limit() const { + return tables_.header_table_size_limit(); + } + size_t current_dynamic_size() const { + return tables_.current_header_table_size(); + } + size_t num_dynamic_entries() const { + return HpackDecoderTablesPeer::num_dynamic_entries(tables_); + } + + // Insert the name and value into fake_dynamic_table_. + void FakeInsert(const Http2String& name, const Http2String& value) { + FakeHpackEntry entry(name, value, Size(name, value)); + fake_dynamic_table_.insert(fake_dynamic_table_.begin(), entry); + } + + // Add up the size of all entries in fake_dynamic_table_. + size_t FakeSize() { + size_t sz = 0; + for (const auto& entry : fake_dynamic_table_) { + sz += Size(entry); + } + return sz; + } + + // If the total size of the fake_dynamic_table_ is greater than limit, + // keep the first N entries such that those N entries have a size not + // greater than limit, and such that keeping entry N+1 would have a size + // greater than limit. Returns the count of removed bytes. + size_t FakeTrim(size_t limit) { + size_t original_size = FakeSize(); + size_t total_size = 0; + for (size_t ndx = 0; ndx < fake_dynamic_table_.size(); ++ndx) { + total_size += Size(fake_dynamic_table_[ndx]); + if (total_size > limit) { + // Need to get rid of ndx and all following entries. + fake_dynamic_table_.erase(fake_dynamic_table_.begin() + ndx, + fake_dynamic_table_.end()); + return original_size - FakeSize(); + } + } + return 0; + } + + // Verify that the contents of the actual dynamic table match those in + // fake_dynamic_table_. + AssertionResult VerifyDynamicTableContents() { + VERIFY_EQ(current_dynamic_size(), FakeSize()); + VERIFY_EQ(num_dynamic_entries(), fake_dynamic_table_.size()); + + for (size_t ndx = 0; ndx < fake_dynamic_table_.size(); ++ndx) { + const HpackStringPair* found = Lookup(ndx + kFirstDynamicTableIndex); + VERIFY_NE(found, nullptr); + + const auto& expected = fake_dynamic_table_[ndx]; + VERIFY_EQ(Name(expected), found->name); + VERIFY_EQ(Value(expected), found->value); + } + + // Make sure there are no more entries. + VERIFY_EQ(nullptr, + Lookup(fake_dynamic_table_.size() + kFirstDynamicTableIndex)); + return AssertionSuccess(); + } + + // Apply an update to the limit on the maximum size of the dynamic table. + AssertionResult DynamicTableSizeUpdate(size_t size_limit) { + VERIFY_EQ(current_dynamic_size(), FakeSize()); + if (size_limit < current_dynamic_size()) { + // Will need to trim the dynamic table's oldest entries. + tables_.DynamicTableSizeUpdate(size_limit); + FakeTrim(size_limit); + return VerifyDynamicTableContents(); + } + // Shouldn't change the size. + tables_.DynamicTableSizeUpdate(size_limit); + return VerifyDynamicTableContents(); + } + + // Insert an entry into the dynamic table, confirming that trimming of entries + // occurs if the total size is greater than the limit, and that older entries + // move up by 1 index. + AssertionResult Insert(const Http2String& name, const Http2String& value) { + size_t old_count = num_dynamic_entries(); + if (tables_.Insert(HpackString(name), HpackString(value))) { + VERIFY_GT(current_dynamic_size(), 0u); + VERIFY_GT(num_dynamic_entries(), 0u); + } else { + VERIFY_EQ(current_dynamic_size(), 0u); + VERIFY_EQ(num_dynamic_entries(), 0u); + } + FakeInsert(name, value); + VERIFY_EQ(old_count + 1, fake_dynamic_table_.size()); + FakeTrim(dynamic_size_limit()); + VERIFY_EQ(current_dynamic_size(), FakeSize()); + VERIFY_EQ(num_dynamic_entries(), fake_dynamic_table_.size()); + return VerifyDynamicTableContents(); + } + + private: + HpackDecoderTables tables_; + + std::vector<FakeHpackEntry> fake_dynamic_table_; +}; + +TEST_F(HpackDecoderTablesTest, StaticTableContents) { + EXPECT_TRUE(VerifyStaticTableContents()); +} + +// Generate a bunch of random header entries, insert them, and confirm they +// present, as required by the RFC, using VerifyDynamicTableContents above on +// each Insert. Also apply various resizings of the dynamic table. +TEST_F(HpackDecoderTablesTest, RandomDynamicTable) { + EXPECT_EQ(0u, current_dynamic_size()); + EXPECT_TRUE(VerifyStaticTableContents()); + EXPECT_TRUE(VerifyDynamicTableContents()); + + std::vector<size_t> table_sizes; + table_sizes.push_back(dynamic_size_limit()); + table_sizes.push_back(0); + table_sizes.push_back(dynamic_size_limit() / 2); + table_sizes.push_back(dynamic_size_limit()); + table_sizes.push_back(dynamic_size_limit() / 2); + table_sizes.push_back(0); + table_sizes.push_back(dynamic_size_limit()); + + for (size_t limit : table_sizes) { + ASSERT_TRUE(DynamicTableSizeUpdate(limit)); + for (int insert_count = 0; insert_count < 100; ++insert_count) { + Http2String name = + GenerateHttp2HeaderName(random_.UniformInRange(2, 40), RandomPtr()); + Http2String value = + GenerateWebSafeString(random_.UniformInRange(2, 600), RandomPtr()); + ASSERT_TRUE(Insert(name, value)); + } + EXPECT_TRUE(VerifyStaticTableContents()); + } +} + +} // namespace +} // namespace test +} // namespace http2 diff --git a/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_test.cc b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_test.cc new file mode 100644 index 00000000000..563e977a07f --- /dev/null +++ b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_test.cc @@ -0,0 +1,1219 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder.h" + +// Tests of HpackDecoder. + +#include <tuple> +#include <utility> +#include <vector> + +#include "base/logging.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h" +#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_listener.h" +#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_state.h" +#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_tables.h" +#include "net/third_party/quiche/src/http2/hpack/hpack_string.h" +#include "net/third_party/quiche/src/http2/hpack/http2_hpack_constants.h" +#include "net/third_party/quiche/src/http2/hpack/tools/hpack_block_builder.h" +#include "net/third_party/quiche/src/http2/hpack/tools/hpack_example.h" +#include "net/third_party/quiche/src/http2/http2_constants.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_test_helpers.h" +#include "net/third_party/quiche/src/http2/test_tools/http2_random.h" +#include "net/third_party/quiche/src/http2/tools/random_util.h" + +using ::testing::AssertionFailure; +using ::testing::AssertionResult; +using ::testing::AssertionSuccess; +using ::testing::ElementsAreArray; +using ::testing::HasSubstr; + +namespace http2 { +namespace test { +class HpackDecoderStatePeer { + public: + static HpackDecoderTables* GetDecoderTables(HpackDecoderState* state) { + return &state->decoder_tables_; + } + static void set_listener(HpackDecoderState* state, + HpackDecoderListener* listener) { + state->listener_ = listener; + } +}; +class HpackDecoderPeer { + public: + static HpackDecoderState* GetDecoderState(HpackDecoder* decoder) { + return &decoder->decoder_state_; + } + static HpackDecoderTables* GetDecoderTables(HpackDecoder* decoder) { + return HpackDecoderStatePeer::GetDecoderTables(GetDecoderState(decoder)); + } +}; + +namespace { + +typedef std::tuple<HpackEntryType, Http2String, Http2String> HpackHeaderEntry; +typedef std::vector<HpackHeaderEntry> HpackHeaderEntries; + +// TODO(jamessynge): Create a ...test_utils.h file with the mock listener +// and with VerifyDynamicTableContents. +class MockHpackDecoderListener : public HpackDecoderListener { + public: + MOCK_METHOD0(OnHeaderListStart, void()); + MOCK_METHOD3(OnHeader, + void(HpackEntryType entry_type, + const HpackString& name, + const HpackString& value)); + MOCK_METHOD0(OnHeaderListEnd, void()); + MOCK_METHOD1(OnHeaderErrorDetected, void(Http2StringPiece error_message)); +}; + +class HpackDecoderTest : public ::testing::TestWithParam<bool>, + public HpackDecoderListener { + protected: + // Note that we initialize the random number generator with the same seed + // for each individual test, therefore the order in which the tests are + // executed does not effect the sequence produced by the RNG within any + // one test. + HpackDecoderTest() : decoder_(this, 4096) { + fragment_the_hpack_block_ = GetParam(); + } + ~HpackDecoderTest() override = default; + + void OnHeaderListStart() override { + ASSERT_FALSE(saw_start_); + ASSERT_FALSE(saw_end_); + saw_start_ = true; + header_entries_.clear(); + } + + // Called for each header name-value pair that is decoded, in the order they + // appear in the HPACK block. Multiple values for a given key will be emitted + // as multiple calls to OnHeader. + void OnHeader(HpackEntryType entry_type, + const HpackString& name, + const HpackString& value) override { + ASSERT_TRUE(saw_start_); + ASSERT_FALSE(saw_end_); + // header_entries_.push_back({entry_type, name.ToString(), + // value.ToString()}); + header_entries_.emplace_back(entry_type, name.ToString(), value.ToString()); + } + + // OnHeaderBlockEnd is called after successfully decoding an HPACK block. Will + // only be called once per block, even if it extends into CONTINUATION frames. + // A callback method which notifies when the parser finishes handling a + // header block (i.e. the containing frame has the END_STREAM flag set). + // Also indicates the total number of bytes in this block. + void OnHeaderListEnd() override { + ASSERT_TRUE(saw_start_); + ASSERT_FALSE(saw_end_); + ASSERT_TRUE(error_messages_.empty()); + saw_end_ = true; + } + + // OnHeaderErrorDetected is called if an error is detected while decoding. + // error_message may be used in a GOAWAY frame as the Opaque Data. + void OnHeaderErrorDetected(Http2StringPiece error_message) override { + ASSERT_TRUE(saw_start_); + error_messages_.push_back(Http2String(error_message)); + // No further callbacks should be made at this point, so replace 'this' as + // the listener with mock_listener_, which is a strict mock, so will + // generate an error for any calls. + HpackDecoderStatePeer::set_listener( + HpackDecoderPeer::GetDecoderState(&decoder_), &mock_listener_); + } + + AssertionResult DecodeBlock(Http2StringPiece block) { + VLOG(1) << "HpackDecoderTest::DecodeBlock"; + + VERIFY_FALSE(decoder_.error_detected()); + VERIFY_TRUE(error_messages_.empty()); + VERIFY_FALSE(saw_start_); + VERIFY_FALSE(saw_end_); + header_entries_.clear(); + + VERIFY_FALSE(decoder_.error_detected()); + VERIFY_TRUE(decoder_.StartDecodingBlock()); + VERIFY_FALSE(decoder_.error_detected()); + + if (fragment_the_hpack_block_) { + // See note in ctor regarding RNG. + while (!block.empty()) { + size_t fragment_size = random_.RandomSizeSkewedLow(block.size()); + DecodeBuffer db(block.substr(0, fragment_size)); + VERIFY_TRUE(decoder_.DecodeFragment(&db)); + VERIFY_EQ(0u, db.Remaining()); + block.remove_prefix(fragment_size); + } + } else { + DecodeBuffer db(block); + VERIFY_TRUE(decoder_.DecodeFragment(&db)); + VERIFY_EQ(0u, db.Remaining()); + } + VERIFY_FALSE(decoder_.error_detected()); + + VERIFY_TRUE(decoder_.EndDecodingBlock()); + if (saw_end_) { + VERIFY_FALSE(decoder_.error_detected()); + VERIFY_TRUE(error_messages_.empty()); + } else { + VERIFY_TRUE(decoder_.error_detected()); + VERIFY_FALSE(error_messages_.empty()); + } + + saw_start_ = saw_end_ = false; + return AssertionSuccess(); + } + + const HpackDecoderTables& GetDecoderTables() { + return *HpackDecoderPeer::GetDecoderTables(&decoder_); + } + const HpackStringPair* Lookup(size_t index) { + return GetDecoderTables().Lookup(index); + } + size_t current_header_table_size() { + return GetDecoderTables().current_header_table_size(); + } + size_t header_table_size_limit() { + return GetDecoderTables().header_table_size_limit(); + } + void set_header_table_size_limit(size_t size) { + HpackDecoderPeer::GetDecoderTables(&decoder_)->DynamicTableSizeUpdate(size); + } + + // dynamic_index is one-based, because that is the way RFC 7541 shows it. + AssertionResult VerifyEntry(size_t dynamic_index, + const char* name, + const char* value) { + const HpackStringPair* entry = + Lookup(dynamic_index + kFirstDynamicTableIndex - 1); + VERIFY_NE(entry, nullptr); + VERIFY_EQ(entry->name.ToStringPiece(), name); + VERIFY_EQ(entry->value.ToStringPiece(), value); + return AssertionSuccess(); + } + AssertionResult VerifyNoEntry(size_t dynamic_index) { + const HpackStringPair* entry = + Lookup(dynamic_index + kFirstDynamicTableIndex - 1); + VERIFY_EQ(entry, nullptr); + return AssertionSuccess(); + } + AssertionResult VerifyDynamicTableContents( + const std::vector<std::pair<const char*, const char*>>& entries) { + size_t index = 1; + for (const auto& entry : entries) { + VERIFY_SUCCESS(VerifyEntry(index, entry.first, entry.second)); + ++index; + } + VERIFY_SUCCESS(VerifyNoEntry(index)); + return AssertionSuccess(); + } + + Http2Random random_; + HpackDecoder decoder_; + testing::StrictMock<MockHpackDecoderListener> mock_listener_; + HpackHeaderEntries header_entries_; + std::vector<Http2String> error_messages_; + bool fragment_the_hpack_block_; + bool saw_start_ = false; + bool saw_end_ = false; +}; +INSTANTIATE_TEST_CASE_P(AllWays, HpackDecoderTest, ::testing::Bool()); + +// Test based on RFC 7541, section C.3: Request Examples without Huffman Coding. +// This section shows several consecutive header lists, corresponding to HTTP +// requests, on the same connection. +// http://httpwg.org/specs/rfc7541.html#rfc.section.C.3 +TEST_P(HpackDecoderTest, C3_RequestExamples) { + // C.3.1 First Request + Http2String hpack_block = HpackExampleToStringOrDie(R"( + 82 | == Indexed - Add == + | idx = 2 + | -> :method: GET + 86 | == Indexed - Add == + | idx = 6 + | -> :scheme: http + 84 | == Indexed - Add == + | idx = 4 + | -> :path: / + 41 | == Literal indexed == + | Indexed name (idx = 1) + | :authority + 0f | Literal value (len = 15) + 7777 772e 6578 616d 706c 652e 636f 6d | www.example.com + | -> :authority: + | www.example.com + )"); + EXPECT_TRUE(DecodeBlock(hpack_block)); + ASSERT_THAT( + header_entries_, + ElementsAreArray({ + HpackHeaderEntry{HpackEntryType::kIndexedHeader, ":method", "GET"}, + HpackHeaderEntry{HpackEntryType::kIndexedHeader, ":scheme", "http"}, + HpackHeaderEntry{HpackEntryType::kIndexedHeader, ":path", "/"}, + HpackHeaderEntry{HpackEntryType::kIndexedLiteralHeader, ":authority", + "www.example.com"}, + })); + + // Dynamic Table (after decoding): + // + // [ 1] (s = 57) :authority: www.example.com + // Table size: 57 + ASSERT_TRUE(VerifyDynamicTableContents({{":authority", "www.example.com"}})); + ASSERT_EQ(57u, current_header_table_size()); + + // C.3.2 Second Request + hpack_block = HpackExampleToStringOrDie(R"( + 82 | == Indexed - Add == + | idx = 2 + | -> :method: GET + 86 | == Indexed - Add == + | idx = 6 + | -> :scheme: http + 84 | == Indexed - Add == + | idx = 4 + | -> :path: / + be | == Indexed - Add == + | idx = 62 + | -> :authority: + | www.example.com + 58 | == Literal indexed == + | Indexed name (idx = 24) + | cache-control + 08 | Literal value (len = 8) + 6e6f 2d63 6163 6865 | no-cache + | -> cache-control: no-cache + )"); + EXPECT_TRUE(DecodeBlock(hpack_block)); + ASSERT_THAT( + header_entries_, + ElementsAreArray({ + HpackHeaderEntry{HpackEntryType::kIndexedHeader, ":method", "GET"}, + HpackHeaderEntry{HpackEntryType::kIndexedHeader, ":scheme", "http"}, + HpackHeaderEntry{HpackEntryType::kIndexedHeader, ":path", "/"}, + HpackHeaderEntry{HpackEntryType::kIndexedHeader, ":authority", + "www.example.com"}, + HpackHeaderEntry{HpackEntryType::kIndexedLiteralHeader, + "cache-control", "no-cache"}, + })); + + // Dynamic Table (after decoding): + // + // [ 1] (s = 53) cache-control: no-cache + // [ 2] (s = 57) :authority: www.example.com + // Table size: 110 + ASSERT_TRUE(VerifyDynamicTableContents( + {{"cache-control", "no-cache"}, {":authority", "www.example.com"}})); + ASSERT_EQ(110u, current_header_table_size()); + + // C.3.2 Third Request + hpack_block = HpackExampleToStringOrDie(R"( + 82 | == Indexed - Add == + | idx = 2 + | -> :method: GET + 87 | == Indexed - Add == + | idx = 7 + | -> :scheme: https + 85 | == Indexed - Add == + | idx = 5 + | -> :path: /index.html + bf | == Indexed - Add == + | idx = 63 + | -> :authority: + | www.example.com + 40 | == Literal indexed == + 0a | Literal name (len = 10) + 6375 7374 6f6d 2d6b 6579 | custom-key + 0c | Literal value (len = 12) + 6375 7374 6f6d 2d76 616c 7565 | custom-value + | -> custom-key: + | custom-value + )"); + EXPECT_TRUE(DecodeBlock(hpack_block)); + ASSERT_THAT( + header_entries_, + ElementsAreArray({ + HpackHeaderEntry{HpackEntryType::kIndexedHeader, ":method", "GET"}, + HpackHeaderEntry{HpackEntryType::kIndexedHeader, ":scheme", "https"}, + HpackHeaderEntry{HpackEntryType::kIndexedHeader, ":path", + "/index.html"}, + HpackHeaderEntry{HpackEntryType::kIndexedHeader, ":authority", + "www.example.com"}, + HpackHeaderEntry{HpackEntryType::kIndexedLiteralHeader, "custom-key", + "custom-value"}, + })); + + // Dynamic Table (after decoding): + // + // [ 1] (s = 54) custom-key: custom-value + // [ 2] (s = 53) cache-control: no-cache + // [ 3] (s = 57) :authority: www.example.com + // Table size: 164 + ASSERT_TRUE(VerifyDynamicTableContents({{"custom-key", "custom-value"}, + {"cache-control", "no-cache"}, + {":authority", "www.example.com"}})); + ASSERT_EQ(164u, current_header_table_size()); +} + +// Test based on RFC 7541, section C.4 Request Examples with Huffman Coding. +// This section shows the same examples as the previous section but uses +// Huffman encoding for the literal values. +// http://httpwg.org/specs/rfc7541.html#rfc.section.C.4 +TEST_P(HpackDecoderTest, C4_RequestExamplesWithHuffmanEncoding) { + // C.4.1 First Request + Http2String hpack_block = HpackExampleToStringOrDie(R"( + 82 | == Indexed - Add == + | idx = 2 + | -> :method: GET + 86 | == Indexed - Add == + | idx = 6 + | -> :scheme: http + 84 | == Indexed - Add == + | idx = 4 + | -> :path: / + 41 | == Literal indexed == + | Indexed name (idx = 1) + | :authority + 8c | Literal value (len = 12) + | Huffman encoded: + f1e3 c2e5 f23a 6ba0 ab90 f4ff | .....:k..... + | Decoded: + | www.example.com + | -> :authority: + | www.example.com + )"); + EXPECT_TRUE(DecodeBlock(hpack_block)); + ASSERT_THAT( + header_entries_, + ElementsAreArray({ + HpackHeaderEntry{HpackEntryType::kIndexedHeader, ":method", "GET"}, + HpackHeaderEntry{HpackEntryType::kIndexedHeader, ":scheme", "http"}, + HpackHeaderEntry{HpackEntryType::kIndexedHeader, ":path", "/"}, + HpackHeaderEntry{HpackEntryType::kIndexedLiteralHeader, ":authority", + "www.example.com"}, + })); + + // Dynamic Table (after decoding): + // + // [ 1] (s = 57) :authority: www.example.com + // Table size: 57 + ASSERT_TRUE(VerifyDynamicTableContents({{":authority", "www.example.com"}})); + ASSERT_EQ(57u, current_header_table_size()); + + // C.4.2 Second Request + hpack_block = HpackExampleToStringOrDie(R"( + 82 | == Indexed - Add == + | idx = 2 + | -> :method: GET + 86 | == Indexed - Add == + | idx = 6 + | -> :scheme: http + 84 | == Indexed - Add == + | idx = 4 + | -> :path: / + be | == Indexed - Add == + | idx = 62 + | -> :authority: + | www.example.com + 58 | == Literal indexed == + | Indexed name (idx = 24) + | cache-control + 86 | Literal value (len = 6) + | Huffman encoded: + a8eb 1064 9cbf | ...d.. + | Decoded: + | no-cache + | -> cache-control: no-cache + )"); + EXPECT_TRUE(DecodeBlock(hpack_block)); + ASSERT_THAT( + header_entries_, + ElementsAreArray({ + HpackHeaderEntry{HpackEntryType::kIndexedHeader, ":method", "GET"}, + HpackHeaderEntry{HpackEntryType::kIndexedHeader, ":scheme", "http"}, + HpackHeaderEntry{HpackEntryType::kIndexedHeader, ":path", "/"}, + HpackHeaderEntry{HpackEntryType::kIndexedHeader, ":authority", + "www.example.com"}, + HpackHeaderEntry{HpackEntryType::kIndexedLiteralHeader, + "cache-control", "no-cache"}, + })); + + // Dynamic Table (after decoding): + // + // [ 1] (s = 53) cache-control: no-cache + // [ 2] (s = 57) :authority: www.example.com + // Table size: 110 + ASSERT_TRUE(VerifyDynamicTableContents( + {{"cache-control", "no-cache"}, {":authority", "www.example.com"}})); + ASSERT_EQ(110u, current_header_table_size()); + + // C.4.2 Third Request + hpack_block = HpackExampleToStringOrDie(R"( + 82 | == Indexed - Add == + | idx = 2 + | -> :method: GET + 87 | == Indexed - Add == + | idx = 7 + | -> :scheme: https + 85 | == Indexed - Add == + | idx = 5 + | -> :path: /index.html + bf | == Indexed - Add == + | idx = 63 + | -> :authority: + | www.example.com + 40 | == Literal indexed == + 88 | Literal name (len = 8) + | Huffman encoded: + 25a8 49e9 5ba9 7d7f | %.I.[.}. + | Decoded: + | custom-key + 89 | Literal value (len = 9) + | Huffman encoded: + 25a8 49e9 5bb8 e8b4 bf | %.I.[.... + | Decoded: + | custom-value + | -> custom-key: + | custom-value + )"); + EXPECT_TRUE(DecodeBlock(hpack_block)); + ASSERT_THAT( + header_entries_, + ElementsAreArray({ + HpackHeaderEntry{HpackEntryType::kIndexedHeader, ":method", "GET"}, + HpackHeaderEntry{HpackEntryType::kIndexedHeader, ":scheme", "https"}, + HpackHeaderEntry{HpackEntryType::kIndexedHeader, ":path", + "/index.html"}, + HpackHeaderEntry{HpackEntryType::kIndexedHeader, ":authority", + "www.example.com"}, + HpackHeaderEntry{HpackEntryType::kIndexedLiteralHeader, "custom-key", + "custom-value"}, + })); + + // Dynamic Table (after decoding): + // + // [ 1] (s = 54) custom-key: custom-value + // [ 2] (s = 53) cache-control: no-cache + // [ 3] (s = 57) :authority: www.example.com + // Table size: 164 + ASSERT_TRUE(VerifyDynamicTableContents({{"custom-key", "custom-value"}, + {"cache-control", "no-cache"}, + {":authority", "www.example.com"}})); + ASSERT_EQ(164u, current_header_table_size()); +} + +// Test based on RFC 7541, section C.5: Response Examples without Huffman +// Coding. This section shows several consecutive header lists, corresponding +// to HTTP responses, on the same connection. The HTTP/2 setting parameter +// SETTINGS_HEADER_TABLE_SIZE is set to the value of 256 octets, causing +// some evictions to occur. +// http://httpwg.org/specs/rfc7541.html#rfc.section.C.5 +TEST_P(HpackDecoderTest, C5_ResponseExamples) { + set_header_table_size_limit(256); + + // C.5.1 First Response + // + // Header list to encode: + // + // :status: 302 + // cache-control: private + // date: Mon, 21 Oct 2013 20:13:21 GMT + // location: https://www.example.com + + Http2String hpack_block = HpackExampleToStringOrDie(R"( + 48 | == Literal indexed == + | Indexed name (idx = 8) + | :status + 03 | Literal value (len = 3) + 3330 32 | 302 + | -> :status: 302 + 58 | == Literal indexed == + | Indexed name (idx = 24) + | cache-control + 07 | Literal value (len = 7) + 7072 6976 6174 65 | private + | -> cache-control: private + 61 | == Literal indexed == + | Indexed name (idx = 33) + | date + 1d | Literal value (len = 29) + 4d6f 6e2c 2032 3120 4f63 7420 3230 3133 | Mon, 21 Oct 2013 + 2032 303a 3133 3a32 3120 474d 54 | 20:13:21 GMT + | -> date: Mon, 21 Oct 2013 + | 20:13:21 GMT + 6e | == Literal indexed == + | Indexed name (idx = 46) + | location + 17 | Literal value (len = 23) + 6874 7470 733a 2f2f 7777 772e 6578 616d | https://www.exam + 706c 652e 636f 6d | ple.com + | -> location: + | https://www.example.com + )"); + EXPECT_TRUE(DecodeBlock(hpack_block)); + ASSERT_THAT(header_entries_, + ElementsAreArray({ + HpackHeaderEntry{HpackEntryType::kIndexedLiteralHeader, + ":status", "302"}, + HpackHeaderEntry{HpackEntryType::kIndexedLiteralHeader, + "cache-control", "private"}, + HpackHeaderEntry{HpackEntryType::kIndexedLiteralHeader, + "date", "Mon, 21 Oct 2013 20:13:21 GMT"}, + HpackHeaderEntry{HpackEntryType::kIndexedLiteralHeader, + "location", "https://www.example.com"}, + })); + + // Dynamic Table (after decoding): + // + // [ 1] (s = 63) location: https://www.example.com + // [ 2] (s = 65) date: Mon, 21 Oct 2013 20:13:21 GMT + // [ 3] (s = 52) cache-control: private + // [ 4] (s = 42) :status: 302 + // Table size: 222 + ASSERT_TRUE( + VerifyDynamicTableContents({{"location", "https://www.example.com"}, + {"date", "Mon, 21 Oct 2013 20:13:21 GMT"}, + {"cache-control", "private"}, + {":status", "302"}})); + ASSERT_EQ(222u, current_header_table_size()); + + // C.5.2 Second Response + // + // The (":status", "302") header field is evicted from the dynamic table to + // free space to allow adding the (":status", "307") header field. + // + // Header list to encode: + // + // :status: 307 + // cache-control: private + // date: Mon, 21 Oct 2013 20:13:21 GMT + // location: https://www.example.com + + hpack_block = HpackExampleToStringOrDie(R"( + 48 | == Literal indexed == + | Indexed name (idx = 8) + | :status + 03 | Literal value (len = 3) + 3330 37 | 307 + | - evict: :status: 302 + | -> :status: 307 + c1 | == Indexed - Add == + | idx = 65 + | -> cache-control: private + c0 | == Indexed - Add == + | idx = 64 + | -> date: Mon, 21 Oct 2013 + | 20:13:21 GMT + bf | == Indexed - Add == + | idx = 63 + | -> location: + | https://www.example.com + )"); + EXPECT_TRUE(DecodeBlock(hpack_block)); + ASSERT_THAT(header_entries_, + ElementsAreArray({ + HpackHeaderEntry{HpackEntryType::kIndexedLiteralHeader, + ":status", "307"}, + HpackHeaderEntry{HpackEntryType::kIndexedHeader, + "cache-control", "private"}, + HpackHeaderEntry{HpackEntryType::kIndexedHeader, "date", + "Mon, 21 Oct 2013 20:13:21 GMT"}, + HpackHeaderEntry{HpackEntryType::kIndexedHeader, "location", + "https://www.example.com"}, + })); + + // Dynamic Table (after decoding): + // + // [ 1] (s = 42) :status: 307 + // [ 2] (s = 63) location: https://www.example.com + // [ 3] (s = 65) date: Mon, 21 Oct 2013 20:13:21 GMT + // [ 4] (s = 52) cache-control: private + // Table size: 222 + + ASSERT_TRUE( + VerifyDynamicTableContents({{":status", "307"}, + {"location", "https://www.example.com"}, + {"date", "Mon, 21 Oct 2013 20:13:21 GMT"}, + {"cache-control", "private"}})); + ASSERT_EQ(222u, current_header_table_size()); + + // C.5.3 Third Response + // + // Several header fields are evicted from the dynamic table during the + // processing of this header list. + // + // Header list to encode: + // + // :status: 200 + // cache-control: private + // date: Mon, 21 Oct 2013 20:13:22 GMT + // location: https://www.example.com + // content-encoding: gzip + // set-cookie: foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1 + hpack_block = HpackExampleToStringOrDie(R"( + 88 | == Indexed - Add == + | idx = 8 + | -> :status: 200 + c1 | == Indexed - Add == + | idx = 65 + | -> cache-control: private + 61 | == Literal indexed == + | Indexed name (idx = 33) + | date + 1d | Literal value (len = 29) + 4d6f 6e2c 2032 3120 4f63 7420 3230 3133 | Mon, 21 Oct 2013 + 2032 303a 3133 3a32 3220 474d 54 | 20:13:22 GMT + | - evict: cache-control: + | private + | -> date: Mon, 21 Oct 2013 + | 20:13:22 GMT + c0 | == Indexed - Add == + | idx = 64 + | -> location: + | https://www.example.com + 5a | == Literal indexed == + | Indexed name (idx = 26) + | content-encoding + 04 | Literal value (len = 4) + 677a 6970 | gzip + | - evict: date: Mon, 21 Oct + | 2013 20:13:21 GMT + | -> content-encoding: gzip + 77 | == Literal indexed == + | Indexed name (idx = 55) + | set-cookie + 38 | Literal value (len = 56) + 666f 6f3d 4153 444a 4b48 514b 425a 584f | foo=ASDJKHQKBZXO + 5157 454f 5049 5541 5851 5745 4f49 553b | QWEOPIUAXQWEOIU; + 206d 6178 2d61 6765 3d33 3630 303b 2076 | max-age=3600; v + 6572 7369 6f6e 3d31 | ersion=1 + | - evict: location: + | https://www.example.com + | - evict: :status: 307 + | -> set-cookie: foo=ASDJKHQ + | KBZXOQWEOPIUAXQWEOIU; ma + | x-age=3600; version=1 + )"); + EXPECT_TRUE(DecodeBlock(hpack_block)); + ASSERT_THAT( + header_entries_, + ElementsAreArray({ + HpackHeaderEntry{HpackEntryType::kIndexedHeader, ":status", "200"}, + HpackHeaderEntry{HpackEntryType::kIndexedHeader, "cache-control", + "private"}, + HpackHeaderEntry{HpackEntryType::kIndexedLiteralHeader, "date", + "Mon, 21 Oct 2013 20:13:22 GMT"}, + HpackHeaderEntry{HpackEntryType::kIndexedHeader, "location", + "https://www.example.com"}, + HpackHeaderEntry{HpackEntryType::kIndexedLiteralHeader, + "content-encoding", "gzip"}, + HpackHeaderEntry{ + HpackEntryType::kIndexedLiteralHeader, "set-cookie", + "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1"}, + })); + + // Dynamic Table (after decoding): + // + // [ 1] (s = 98) set-cookie: foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; + // max-age=3600; version=1 + // [ 2] (s = 52) content-encoding: gzip + // [ 3] (s = 65) date: Mon, 21 Oct 2013 20:13:22 GMT + // Table size: 215 + ASSERT_TRUE(VerifyDynamicTableContents( + {{"set-cookie", + "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1"}, + {"content-encoding", "gzip"}, + {"date", "Mon, 21 Oct 2013 20:13:22 GMT"}})); + ASSERT_EQ(215u, current_header_table_size()); +} + +// Test based on RFC 7541, section C.6: Response Examples with Huffman Coding. +// This section shows the same examples as the previous section but uses Huffman +// encoding for the literal values. The HTTP/2 setting parameter +// SETTINGS_HEADER_TABLE_SIZE is set to the value of 256 octets, causing some +// evictions to occur. The eviction mechanism uses the length of the decoded +// literal values, so the same evictions occur as in the previous section. +// http://httpwg.org/specs/rfc7541.html#rfc.section.C.6 +TEST_P(HpackDecoderTest, C6_ResponseExamplesWithHuffmanEncoding) { + set_header_table_size_limit(256); + + // C.5.1 First Response + // + // Header list to encode: + // + // :status: 302 + // cache-control: private + // date: Mon, 21 Oct 2013 20:13:21 GMT + // location: https://www.example.com + Http2String hpack_block = HpackExampleToStringOrDie(R"( + 48 | == Literal indexed == + | Indexed name (idx = 8) + | :status + 03 | Literal value (len = 3) + 3330 32 | 302 + | -> :status: 302 + 58 | == Literal indexed == + | Indexed name (idx = 24) + | cache-control + 07 | Literal value (len = 7) + 7072 6976 6174 65 | private + | -> cache-control: private + 61 | == Literal indexed == + | Indexed name (idx = 33) + | date + 1d | Literal value (len = 29) + 4d6f 6e2c 2032 3120 4f63 7420 3230 3133 | Mon, 21 Oct 2013 + 2032 303a 3133 3a32 3120 474d 54 | 20:13:21 GMT + | -> date: Mon, 21 Oct 2013 + | 20:13:21 GMT + 6e | == Literal indexed == + | Indexed name (idx = 46) + | location + 17 | Literal value (len = 23) + 6874 7470 733a 2f2f 7777 772e 6578 616d | https://www.exam + 706c 652e 636f 6d | ple.com + | -> location: + | https://www.example.com + )"); + EXPECT_TRUE(DecodeBlock(hpack_block)); + ASSERT_THAT(header_entries_, + ElementsAreArray({ + HpackHeaderEntry{HpackEntryType::kIndexedLiteralHeader, + ":status", "302"}, + HpackHeaderEntry{HpackEntryType::kIndexedLiteralHeader, + "cache-control", "private"}, + HpackHeaderEntry{HpackEntryType::kIndexedLiteralHeader, + "date", "Mon, 21 Oct 2013 20:13:21 GMT"}, + HpackHeaderEntry{HpackEntryType::kIndexedLiteralHeader, + "location", "https://www.example.com"}, + })); + + // Dynamic Table (after decoding): + // + // [ 1] (s = 63) location: https://www.example.com + // [ 2] (s = 65) date: Mon, 21 Oct 2013 20:13:21 GMT + // [ 3] (s = 52) cache-control: private + // [ 4] (s = 42) :status: 302 + // Table size: 222 + ASSERT_TRUE( + VerifyDynamicTableContents({{"location", "https://www.example.com"}, + {"date", "Mon, 21 Oct 2013 20:13:21 GMT"}, + {"cache-control", "private"}, + {":status", "302"}})); + ASSERT_EQ(222u, current_header_table_size()); + + // C.5.2 Second Response + // + // The (":status", "302") header field is evicted from the dynamic table to + // free space to allow adding the (":status", "307") header field. + // + // Header list to encode: + // + // :status: 307 + // cache-control: private + // date: Mon, 21 Oct 2013 20:13:21 GMT + // location: https://www.example.com + hpack_block = HpackExampleToStringOrDie(R"( + 48 | == Literal indexed == + | Indexed name (idx = 8) + | :status + 03 | Literal value (len = 3) + 3330 37 | 307 + | - evict: :status: 302 + | -> :status: 307 + c1 | == Indexed - Add == + | idx = 65 + | -> cache-control: private + c0 | == Indexed - Add == + | idx = 64 + | -> date: Mon, 21 Oct 2013 + | 20:13:21 GMT + bf | == Indexed - Add == + | idx = 63 + | -> location: + | https://www.example.com + )"); + EXPECT_TRUE(DecodeBlock(hpack_block)); + ASSERT_THAT(header_entries_, + ElementsAreArray({ + HpackHeaderEntry{HpackEntryType::kIndexedLiteralHeader, + ":status", "307"}, + HpackHeaderEntry{HpackEntryType::kIndexedHeader, + "cache-control", "private"}, + HpackHeaderEntry{HpackEntryType::kIndexedHeader, "date", + "Mon, 21 Oct 2013 20:13:21 GMT"}, + HpackHeaderEntry{HpackEntryType::kIndexedHeader, "location", + "https://www.example.com"}, + })); + + // Dynamic Table (after decoding): + // + // [ 1] (s = 42) :status: 307 + // [ 2] (s = 63) location: https://www.example.com + // [ 3] (s = 65) date: Mon, 21 Oct 2013 20:13:21 GMT + // [ 4] (s = 52) cache-control: private + // Table size: 222 + ASSERT_TRUE( + VerifyDynamicTableContents({{":status", "307"}, + {"location", "https://www.example.com"}, + {"date", "Mon, 21 Oct 2013 20:13:21 GMT"}, + {"cache-control", "private"}})); + ASSERT_EQ(222u, current_header_table_size()); + + // C.5.3 Third Response + // + // Several header fields are evicted from the dynamic table during the + // processing of this header list. + // + // Header list to encode: + // + // :status: 200 + // cache-control: private + // date: Mon, 21 Oct 2013 20:13:22 GMT + // location: https://www.example.com + // content-encoding: gzip + // set-cookie: foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1 + hpack_block = HpackExampleToStringOrDie(R"( + 88 | == Indexed - Add == + | idx = 8 + | -> :status: 200 + c1 | == Indexed - Add == + | idx = 65 + | -> cache-control: private + 61 | == Literal indexed == + | Indexed name (idx = 33) + | date + 1d | Literal value (len = 29) + 4d6f 6e2c 2032 3120 4f63 7420 3230 3133 | Mon, 21 Oct 2013 + 2032 303a 3133 3a32 3220 474d 54 | 20:13:22 GMT + | - evict: cache-control: + | private + | -> date: Mon, 21 Oct 2013 + | 20:13:22 GMT + c0 | == Indexed - Add == + | idx = 64 + | -> location: + | https://www.example.com + 5a | == Literal indexed == + | Indexed name (idx = 26) + | content-encoding + 04 | Literal value (len = 4) + 677a 6970 | gzip + | - evict: date: Mon, 21 Oct + | 2013 20:13:21 GMT + | -> content-encoding: gzip + 77 | == Literal indexed == + | Indexed name (idx = 55) + | set-cookie + 38 | Literal value (len = 56) + 666f 6f3d 4153 444a 4b48 514b 425a 584f | foo=ASDJKHQKBZXO + 5157 454f 5049 5541 5851 5745 4f49 553b | QWEOPIUAXQWEOIU; + 206d 6178 2d61 6765 3d33 3630 303b 2076 | max-age=3600; v + 6572 7369 6f6e 3d31 | ersion=1 + | - evict: location: + | https://www.example.com + | - evict: :status: 307 + | -> set-cookie: foo=ASDJKHQ + | KBZXOQWEOPIUAXQWEOIU; ma + | x-age=3600; version=1 + )"); + EXPECT_TRUE(DecodeBlock(hpack_block)); + ASSERT_THAT( + header_entries_, + ElementsAreArray({ + HpackHeaderEntry{HpackEntryType::kIndexedHeader, ":status", "200"}, + HpackHeaderEntry{HpackEntryType::kIndexedHeader, "cache-control", + "private"}, + HpackHeaderEntry{HpackEntryType::kIndexedLiteralHeader, "date", + "Mon, 21 Oct 2013 20:13:22 GMT"}, + HpackHeaderEntry{HpackEntryType::kIndexedHeader, "location", + "https://www.example.com"}, + HpackHeaderEntry{HpackEntryType::kIndexedLiteralHeader, + "content-encoding", "gzip"}, + HpackHeaderEntry{ + HpackEntryType::kIndexedLiteralHeader, "set-cookie", + "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1"}, + })); + + // Dynamic Table (after decoding): + // + // [ 1] (s = 98) set-cookie: foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; + // max-age=3600; version=1 + // [ 2] (s = 52) content-encoding: gzip + // [ 3] (s = 65) date: Mon, 21 Oct 2013 20:13:22 GMT + // Table size: 215 + ASSERT_TRUE(VerifyDynamicTableContents( + {{"set-cookie", + "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1"}, + {"content-encoding", "gzip"}, + {"date", "Mon, 21 Oct 2013 20:13:22 GMT"}})); + ASSERT_EQ(215u, current_header_table_size()); +} + +// Confirm that the table size can be changed, but at most twice. +TEST_P(HpackDecoderTest, ProcessesOptionalTableSizeUpdates) { + EXPECT_EQ(Http2SettingsInfo::DefaultHeaderTableSize(), + header_table_size_limit()); + // One update allowed. + { + HpackBlockBuilder hbb; + hbb.AppendDynamicTableSizeUpdate(3000); + EXPECT_TRUE(DecodeBlock(hbb.buffer())); + EXPECT_EQ(3000u, header_table_size_limit()); + EXPECT_EQ(0u, current_header_table_size()); + EXPECT_TRUE(header_entries_.empty()); + } + // Two updates allowed. + { + HpackBlockBuilder hbb; + hbb.AppendDynamicTableSizeUpdate(2000); + hbb.AppendDynamicTableSizeUpdate(2500); + EXPECT_TRUE(DecodeBlock(hbb.buffer())); + EXPECT_EQ(2500u, header_table_size_limit()); + EXPECT_EQ(0u, current_header_table_size()); + EXPECT_TRUE(header_entries_.empty()); + } + // A third update in the same HPACK block is rejected, so the final + // size is 1000, not 500. + { + HpackBlockBuilder hbb; + hbb.AppendDynamicTableSizeUpdate(1500); + hbb.AppendDynamicTableSizeUpdate(1000); + hbb.AppendDynamicTableSizeUpdate(500); + EXPECT_FALSE(DecodeBlock(hbb.buffer())); + EXPECT_EQ(1u, error_messages_.size()); + EXPECT_THAT(error_messages_[0], HasSubstr("size update not allowed")); + EXPECT_EQ(1000u, header_table_size_limit()); + EXPECT_EQ(0u, current_header_table_size()); + EXPECT_TRUE(header_entries_.empty()); + } + // An error has been detected, so calls to HpackDecoder::DecodeFragment + // should return immediately. + DecodeBuffer db("\x80"); + EXPECT_FALSE(decoder_.DecodeFragment(&db)); + EXPECT_EQ(0u, db.Offset()); + EXPECT_EQ(1u, error_messages_.size()); +} + +// Confirm that the table size can be changed when required, but at most twice. +TEST_P(HpackDecoderTest, ProcessesRequiredTableSizeUpdate) { + // One update required, two allowed, one provided, followed by a header. + decoder_.ApplyHeaderTableSizeSetting(1024); + decoder_.ApplyHeaderTableSizeSetting(2048); + EXPECT_EQ(Http2SettingsInfo::DefaultHeaderTableSize(), + header_table_size_limit()); + { + HpackBlockBuilder hbb; + hbb.AppendDynamicTableSizeUpdate(1024); + hbb.AppendIndexedHeader(4); // :path: / + EXPECT_TRUE(DecodeBlock(hbb.buffer())); + EXPECT_THAT(header_entries_, + ElementsAreArray({HpackHeaderEntry{ + HpackEntryType::kIndexedHeader, ":path", "/"}})); + EXPECT_EQ(1024u, header_table_size_limit()); + EXPECT_EQ(0u, current_header_table_size()); + } + // One update required, two allowed, two provided, followed by a header. + decoder_.ApplyHeaderTableSizeSetting(1000); + decoder_.ApplyHeaderTableSizeSetting(1500); + { + HpackBlockBuilder hbb; + hbb.AppendDynamicTableSizeUpdate(500); + hbb.AppendDynamicTableSizeUpdate(1250); + hbb.AppendIndexedHeader(5); // :path: /index.html + EXPECT_TRUE(DecodeBlock(hbb.buffer())); + EXPECT_THAT(header_entries_, + ElementsAreArray({HpackHeaderEntry{ + HpackEntryType::kIndexedHeader, ":path", "/index.html"}})); + EXPECT_EQ(1250u, header_table_size_limit()); + EXPECT_EQ(0u, current_header_table_size()); + } + // One update required, two allowed, three provided, followed by a header. + // The third update is rejected, so the final size is 1000, not 500. + decoder_.ApplyHeaderTableSizeSetting(500); + decoder_.ApplyHeaderTableSizeSetting(1000); + { + HpackBlockBuilder hbb; + hbb.AppendDynamicTableSizeUpdate(200); + hbb.AppendDynamicTableSizeUpdate(700); + hbb.AppendDynamicTableSizeUpdate(900); + hbb.AppendIndexedHeader(5); // Not decoded. + EXPECT_FALSE(DecodeBlock(hbb.buffer())); + EXPECT_FALSE(saw_end_); + EXPECT_EQ(1u, error_messages_.size()); + EXPECT_THAT(error_messages_[0], HasSubstr("size update not allowed")); + EXPECT_EQ(700u, header_table_size_limit()); + EXPECT_EQ(0u, current_header_table_size()); + EXPECT_TRUE(header_entries_.empty()); + } + // Now that an error has been detected, StartDecodingBlock should return + // false. + EXPECT_FALSE(decoder_.StartDecodingBlock()); +} + +// Confirm that required size updates are validated. +TEST_P(HpackDecoderTest, InvalidRequiredSizeUpdate) { + // Require a size update, but provide one that isn't small enough (must be + // zero or one, in this case). + decoder_.ApplyHeaderTableSizeSetting(1); + decoder_.ApplyHeaderTableSizeSetting(1024); + HpackBlockBuilder hbb; + hbb.AppendDynamicTableSizeUpdate(2); + EXPECT_TRUE(decoder_.StartDecodingBlock()); + DecodeBuffer db(hbb.buffer()); + EXPECT_FALSE(decoder_.DecodeFragment(&db)); + EXPECT_FALSE(saw_end_); + EXPECT_EQ(1u, error_messages_.size()); + EXPECT_THAT(error_messages_[0], HasSubstr("above low water mark")); + EXPECT_EQ(Http2SettingsInfo::DefaultHeaderTableSize(), + header_table_size_limit()); +} + +// Confirm that required size updates are indeed required before the end. +TEST_P(HpackDecoderTest, RequiredTableSizeChangeBeforeEnd) { + decoder_.ApplyHeaderTableSizeSetting(1024); + EXPECT_FALSE(DecodeBlock("")); + EXPECT_EQ(1u, error_messages_.size()); + EXPECT_THAT(error_messages_[0], + HasSubstr("Missing dynamic table size update")); + EXPECT_FALSE(saw_end_); +} + +// Confirm that required size updates are indeed required before an +// indexed header. +TEST_P(HpackDecoderTest, RequiredTableSizeChangeBeforeIndexedHeader) { + decoder_.ApplyHeaderTableSizeSetting(1024); + HpackBlockBuilder hbb; + hbb.AppendIndexedHeader(1); + EXPECT_FALSE(DecodeBlock(hbb.buffer())); + EXPECT_EQ(1u, error_messages_.size()); + EXPECT_THAT(error_messages_[0], + HasSubstr("Missing dynamic table size update")); + EXPECT_FALSE(saw_end_); + EXPECT_TRUE(header_entries_.empty()); +} + +// Confirm that required size updates are indeed required before an indexed +// header name. +// TODO(jamessynge): Move some of these to hpack_decoder_state_test.cc. +TEST_P(HpackDecoderTest, RequiredTableSizeChangeBeforeIndexedHeaderName) { + decoder_.ApplyHeaderTableSizeSetting(1024); + HpackBlockBuilder hbb; + hbb.AppendNameIndexAndLiteralValue(HpackEntryType::kIndexedLiteralHeader, 2, + false, "PUT"); + EXPECT_FALSE(DecodeBlock(hbb.buffer())); + EXPECT_EQ(1u, error_messages_.size()); + EXPECT_THAT(error_messages_[0], + HasSubstr("Missing dynamic table size update")); + EXPECT_FALSE(saw_end_); + EXPECT_TRUE(header_entries_.empty()); +} + +// Confirm that required size updates are indeed required before a literal +// header name. +TEST_P(HpackDecoderTest, RequiredTableSizeChangeBeforeLiteralName) { + decoder_.ApplyHeaderTableSizeSetting(1024); + HpackBlockBuilder hbb; + hbb.AppendLiteralNameAndValue(HpackEntryType::kNeverIndexedLiteralHeader, + false, "name", false, "some data."); + EXPECT_FALSE(DecodeBlock(hbb.buffer())); + EXPECT_EQ(1u, error_messages_.size()); + EXPECT_THAT(error_messages_[0], + HasSubstr("Missing dynamic table size update")); + EXPECT_FALSE(saw_end_); + EXPECT_TRUE(header_entries_.empty()); +} + +// Confirm that an excessively long varint is detected, in this case an +// index of 127, but with lots of additional high-order 0 bits provided, +// too many to be allowed. +TEST_P(HpackDecoderTest, InvalidIndexedHeaderVarint) { + EXPECT_TRUE(decoder_.StartDecodingBlock()); + DecodeBuffer db("\xff\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x00"); + EXPECT_FALSE(decoder_.DecodeFragment(&db)); + EXPECT_TRUE(decoder_.error_detected()); + EXPECT_FALSE(saw_end_); + EXPECT_EQ(1u, error_messages_.size()); + EXPECT_THAT(error_messages_[0], HasSubstr("malformed")); + EXPECT_TRUE(header_entries_.empty()); + // Now that an error has been detected, EndDecodingBlock should not succeed. + EXPECT_FALSE(decoder_.EndDecodingBlock()); +} + +// Confirm that an invalid index into the tables is detected, in this case an +// index of 0. +TEST_P(HpackDecoderTest, InvalidIndex) { + EXPECT_TRUE(decoder_.StartDecodingBlock()); + DecodeBuffer db("\x80"); + EXPECT_FALSE(decoder_.DecodeFragment(&db)); + EXPECT_TRUE(decoder_.error_detected()); + EXPECT_FALSE(saw_end_); + EXPECT_EQ(1u, error_messages_.size()); + EXPECT_THAT(error_messages_[0], HasSubstr("Invalid index")); + EXPECT_TRUE(header_entries_.empty()); + // Now that an error has been detected, EndDecodingBlock should not succeed. + EXPECT_FALSE(decoder_.EndDecodingBlock()); +} + +// Confirm that EndDecodingBlock detects a truncated HPACK block. +TEST_P(HpackDecoderTest, TruncatedBlock) { + HpackBlockBuilder hbb; + hbb.AppendDynamicTableSizeUpdate(3000); + EXPECT_EQ(3u, hbb.size()); + hbb.AppendDynamicTableSizeUpdate(4000); + EXPECT_EQ(6u, hbb.size()); + // Decodes this block if the whole thing is provided. + EXPECT_TRUE(DecodeBlock(hbb.buffer())); + EXPECT_EQ(4000u, header_table_size_limit()); + // Multiple times even. + EXPECT_TRUE(DecodeBlock(hbb.buffer())); + EXPECT_EQ(4000u, header_table_size_limit()); + // But not if the block is truncated. + EXPECT_FALSE(DecodeBlock(hbb.buffer().substr(0, hbb.size() - 1))); + EXPECT_FALSE(saw_end_); + EXPECT_EQ(1u, error_messages_.size()); + EXPECT_THAT(error_messages_[0], HasSubstr("truncated")); + // The first update was decoded. + EXPECT_EQ(3000u, header_table_size_limit()); + EXPECT_EQ(0u, current_header_table_size()); + EXPECT_TRUE(header_entries_.empty()); +} + +// Confirm that an oversized string is detected, ending decoding. +TEST_P(HpackDecoderTest, OversizeStringDetected) { + HpackBlockBuilder hbb; + hbb.AppendLiteralNameAndValue(HpackEntryType::kNeverIndexedLiteralHeader, + false, "name", false, "some data."); + hbb.AppendLiteralNameAndValue(HpackEntryType::kUnindexedLiteralHeader, false, + "name2", false, "longer data"); + + // Normally able to decode this block. + EXPECT_TRUE(DecodeBlock(hbb.buffer())); + EXPECT_THAT(header_entries_, + ElementsAreArray( + {HpackHeaderEntry{HpackEntryType::kNeverIndexedLiteralHeader, + "name", "some data."}, + HpackHeaderEntry{HpackEntryType::kUnindexedLiteralHeader, + "name2", "longer data"}})); + + // But not if the maximum size of strings is less than the longest string. + decoder_.set_max_string_size_bytes(10); + EXPECT_FALSE(DecodeBlock(hbb.buffer())); + EXPECT_THAT( + header_entries_, + ElementsAreArray({HpackHeaderEntry{ + HpackEntryType::kNeverIndexedLiteralHeader, "name", "some data."}})); + EXPECT_FALSE(saw_end_); + EXPECT_EQ(1u, error_messages_.size()); + EXPECT_THAT(error_messages_[0], HasSubstr("too long")); +} + +} // namespace +} // namespace test +} // namespace http2 diff --git a/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_collector.cc b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_collector.cc new file mode 100644 index 00000000000..9d17465577f --- /dev/null +++ b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_collector.cc @@ -0,0 +1,301 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_collector.h" + +#include "base/logging.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_string_collector.h" +#include "net/third_party/quiche/src/http2/hpack/http2_hpack_constants.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string_utils.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_test_helpers.h" + +using ::testing::AssertionResult; + +namespace http2 { +namespace test { +namespace { + +const HpackEntryType kInvalidHeaderType = static_cast<HpackEntryType>(99); +const size_t kInvalidIndex = 99999999; + +} // namespace + +HpackEntryCollector::HpackEntryCollector() { + Clear(); +} + +HpackEntryCollector::HpackEntryCollector(const HpackEntryCollector& other) = + default; + +HpackEntryCollector::HpackEntryCollector(HpackEntryType type, + size_t index_or_size) + : header_type_(type), index_(index_or_size), started_(true), ended_(true) {} +HpackEntryCollector::HpackEntryCollector(HpackEntryType type, + size_t index, + bool value_huffman, + const Http2String& value) + : header_type_(type), + index_(index), + value_(value, value_huffman), + started_(true), + ended_(true) {} +HpackEntryCollector::HpackEntryCollector(HpackEntryType type, + bool name_huffman, + const Http2String& name, + bool value_huffman, + const Http2String& value) + : header_type_(type), + index_(0), + name_(name, name_huffman), + value_(value, value_huffman), + started_(true), + ended_(true) {} + +HpackEntryCollector::~HpackEntryCollector() = default; + +void HpackEntryCollector::OnIndexedHeader(size_t index) { + ASSERT_FALSE(started_); + ASSERT_TRUE(IsClear()) << ToString(); + Init(HpackEntryType::kIndexedHeader, index); + ended_ = true; +} +void HpackEntryCollector::OnStartLiteralHeader(HpackEntryType header_type, + size_t maybe_name_index) { + ASSERT_FALSE(started_); + ASSERT_TRUE(IsClear()) << ToString(); + Init(header_type, maybe_name_index); +} +void HpackEntryCollector::OnNameStart(bool huffman_encoded, size_t len) { + ASSERT_TRUE(started_); + ASSERT_FALSE(ended_); + ASSERT_FALSE(IsClear()); + ASSERT_TRUE(LiteralNameExpected()) << ToString(); + name_.OnStringStart(huffman_encoded, len); +} +void HpackEntryCollector::OnNameData(const char* data, size_t len) { + ASSERT_TRUE(started_); + ASSERT_FALSE(ended_); + ASSERT_TRUE(LiteralNameExpected()) << ToString(); + ASSERT_TRUE(name_.IsInProgress()); + name_.OnStringData(data, len); +} +void HpackEntryCollector::OnNameEnd() { + ASSERT_TRUE(started_); + ASSERT_FALSE(ended_); + ASSERT_TRUE(LiteralNameExpected()) << ToString(); + ASSERT_TRUE(name_.IsInProgress()); + name_.OnStringEnd(); +} +void HpackEntryCollector::OnValueStart(bool huffman_encoded, size_t len) { + ASSERT_TRUE(started_); + ASSERT_FALSE(ended_); + if (LiteralNameExpected()) { + ASSERT_TRUE(name_.HasEnded()); + } + ASSERT_TRUE(LiteralValueExpected()) << ToString(); + ASSERT_TRUE(value_.IsClear()) << value_.ToString(); + value_.OnStringStart(huffman_encoded, len); +} +void HpackEntryCollector::OnValueData(const char* data, size_t len) { + ASSERT_TRUE(started_); + ASSERT_FALSE(ended_); + ASSERT_TRUE(LiteralValueExpected()) << ToString(); + ASSERT_TRUE(value_.IsInProgress()); + value_.OnStringData(data, len); +} +void HpackEntryCollector::OnValueEnd() { + ASSERT_TRUE(started_); + ASSERT_FALSE(ended_); + ASSERT_TRUE(LiteralValueExpected()) << ToString(); + ASSERT_TRUE(value_.IsInProgress()); + value_.OnStringEnd(); + ended_ = true; +} +void HpackEntryCollector::OnDynamicTableSizeUpdate(size_t size) { + ASSERT_FALSE(started_); + ASSERT_TRUE(IsClear()) << ToString(); + Init(HpackEntryType::kDynamicTableSizeUpdate, size); + ended_ = true; +} + +void HpackEntryCollector::Clear() { + header_type_ = kInvalidHeaderType; + index_ = kInvalidIndex; + name_.Clear(); + value_.Clear(); + started_ = ended_ = false; +} +bool HpackEntryCollector::IsClear() const { + return header_type_ == kInvalidHeaderType && index_ == kInvalidIndex && + name_.IsClear() && value_.IsClear() && !started_ && !ended_; +} +bool HpackEntryCollector::IsComplete() const { + return started_ && ended_; +} +bool HpackEntryCollector::LiteralNameExpected() const { + switch (header_type_) { + case HpackEntryType::kIndexedLiteralHeader: + case HpackEntryType::kUnindexedLiteralHeader: + case HpackEntryType::kNeverIndexedLiteralHeader: + return index_ == 0; + default: + return false; + } +} +bool HpackEntryCollector::LiteralValueExpected() const { + switch (header_type_) { + case HpackEntryType::kIndexedLiteralHeader: + case HpackEntryType::kUnindexedLiteralHeader: + case HpackEntryType::kNeverIndexedLiteralHeader: + return true; + default: + return false; + } +} +AssertionResult HpackEntryCollector::ValidateIndexedHeader( + size_t expected_index) const { + VERIFY_TRUE(started_); + VERIFY_TRUE(ended_); + VERIFY_EQ(HpackEntryType::kIndexedHeader, header_type_); + VERIFY_EQ(expected_index, index_); + return ::testing::AssertionSuccess(); +} +AssertionResult HpackEntryCollector::ValidateLiteralValueHeader( + HpackEntryType expected_type, + size_t expected_index, + bool expected_value_huffman, + Http2StringPiece expected_value) const { + VERIFY_TRUE(started_); + VERIFY_TRUE(ended_); + VERIFY_EQ(expected_type, header_type_); + VERIFY_NE(0u, expected_index); + VERIFY_EQ(expected_index, index_); + VERIFY_TRUE(name_.IsClear()); + VERIFY_SUCCESS(value_.Collected(expected_value, expected_value_huffman)); + return ::testing::AssertionSuccess(); +} +AssertionResult HpackEntryCollector::ValidateLiteralNameValueHeader( + HpackEntryType expected_type, + bool expected_name_huffman, + Http2StringPiece expected_name, + bool expected_value_huffman, + Http2StringPiece expected_value) const { + VERIFY_TRUE(started_); + VERIFY_TRUE(ended_); + VERIFY_EQ(expected_type, header_type_); + VERIFY_EQ(0u, index_); + VERIFY_SUCCESS(name_.Collected(expected_name, expected_name_huffman)); + VERIFY_SUCCESS(value_.Collected(expected_value, expected_value_huffman)); + return ::testing::AssertionSuccess(); +} +AssertionResult HpackEntryCollector::ValidateDynamicTableSizeUpdate( + size_t size) const { + VERIFY_TRUE(started_); + VERIFY_TRUE(ended_); + VERIFY_EQ(HpackEntryType::kDynamicTableSizeUpdate, header_type_); + VERIFY_EQ(index_, size); + return ::testing::AssertionSuccess(); +} + +void HpackEntryCollector::AppendToHpackBlockBuilder( + HpackBlockBuilder* hbb) const { + ASSERT_TRUE(started_ && ended_) << *this; + switch (header_type_) { + case HpackEntryType::kIndexedHeader: + hbb->AppendIndexedHeader(index_); + return; + + case HpackEntryType::kDynamicTableSizeUpdate: + hbb->AppendDynamicTableSizeUpdate(index_); + return; + + case HpackEntryType::kIndexedLiteralHeader: + case HpackEntryType::kUnindexedLiteralHeader: + case HpackEntryType::kNeverIndexedLiteralHeader: + ASSERT_TRUE(value_.HasEnded()) << *this; + if (index_ != 0) { + CHECK(name_.IsClear()); + hbb->AppendNameIndexAndLiteralValue(header_type_, index_, + value_.huffman_encoded, value_.s); + } else { + CHECK(name_.HasEnded()) << *this; + hbb->AppendLiteralNameAndValue(header_type_, name_.huffman_encoded, + name_.s, value_.huffman_encoded, + value_.s); + } + return; + + default: + ADD_FAILURE() << *this; + } +} + +Http2String HpackEntryCollector::ToString() const { + Http2String result("Type="); + switch (header_type_) { + case HpackEntryType::kIndexedHeader: + result += "IndexedHeader"; + break; + case HpackEntryType::kDynamicTableSizeUpdate: + result += "DynamicTableSizeUpdate"; + break; + case HpackEntryType::kIndexedLiteralHeader: + result += "IndexedLiteralHeader"; + break; + case HpackEntryType::kUnindexedLiteralHeader: + result += "UnindexedLiteralHeader"; + break; + case HpackEntryType::kNeverIndexedLiteralHeader: + result += "NeverIndexedLiteralHeader"; + break; + default: + if (header_type_ == kInvalidHeaderType) { + result += "<unset>"; + } else { + Http2StrAppend(&result, header_type_); + } + } + if (index_ != 0) { + Http2StrAppend(&result, " Index=", index_); + } + if (!name_.IsClear()) { + Http2StrAppend(&result, " Name", name_.ToString()); + } + if (!value_.IsClear()) { + Http2StrAppend(&result, " Value", value_.ToString()); + } + if (!started_) { + EXPECT_FALSE(ended_); + Http2StrAppend(&result, " !started"); + } else if (!ended_) { + Http2StrAppend(&result, " !ended"); + } else { + Http2StrAppend(&result, " Complete"); + } + return result; +} + +void HpackEntryCollector::Init(HpackEntryType type, size_t maybe_index) { + ASSERT_TRUE(IsClear()) << ToString(); + header_type_ = type; + index_ = maybe_index; + started_ = true; +} + +bool operator==(const HpackEntryCollector& a, const HpackEntryCollector& b) { + return a.name() == b.name() && a.value() == b.value() && + a.index() == b.index() && a.header_type() == b.header_type() && + a.started() == b.started() && a.ended() == b.ended(); +} +bool operator!=(const HpackEntryCollector& a, const HpackEntryCollector& b) { + return !(a == b); +} + +std::ostream& operator<<(std::ostream& out, const HpackEntryCollector& v) { + return out << v.ToString(); +} + +} // namespace test +} // namespace http2 diff --git a/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_collector.h b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_collector.h new file mode 100644 index 00000000000..c2c5fe5830d --- /dev/null +++ b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_collector.h @@ -0,0 +1,155 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef QUICHE_HTTP2_HPACK_DECODER_HPACK_ENTRY_COLLECTOR_H_ +#define QUICHE_HTTP2_HPACK_DECODER_HPACK_ENTRY_COLLECTOR_H_ + +// HpackEntryCollector records calls to HpackEntryDecoderListener in support +// of tests of HpackEntryDecoder, or which use it. Can only record the callbacks +// for the decoding of a single entry; call Clear() between decoding successive +// entries or use a distinct HpackEntryCollector for each entry. + +#include <stddef.h> + +#include <iosfwd> + +#include "testing/gtest/include/gtest/gtest.h" +#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_decoder_listener.h" +#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_string_collector.h" +#include "net/third_party/quiche/src/http2/hpack/http2_hpack_constants.h" +#include "net/third_party/quiche/src/http2/hpack/tools/hpack_block_builder.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string_piece.h" + +namespace http2 { +namespace test { + +class HpackEntryCollector : public HpackEntryDecoderListener { + public: + HpackEntryCollector(); + HpackEntryCollector(const HpackEntryCollector& other); + + // These next three constructors are intended for use in tests that create + // an HpackEntryCollector "manually", and then compare it against another + // that is populated via calls to the HpackEntryDecoderListener methods. + HpackEntryCollector(HpackEntryType type, size_t index_or_size); + HpackEntryCollector(HpackEntryType type, + size_t index, + bool value_huffman, + const Http2String& value); + HpackEntryCollector(HpackEntryType type, + bool name_huffman, + const Http2String& name, + bool value_huffman, + const Http2String& value); + + ~HpackEntryCollector() override; + + // Methods defined by HpackEntryDecoderListener. + void OnIndexedHeader(size_t index) override; + void OnStartLiteralHeader(HpackEntryType header_type, + size_t maybe_name_index) override; + void OnNameStart(bool huffman_encoded, size_t len) override; + void OnNameData(const char* data, size_t len) override; + void OnNameEnd() override; + void OnValueStart(bool huffman_encoded, size_t len) override; + void OnValueData(const char* data, size_t len) override; + void OnValueEnd() override; + void OnDynamicTableSizeUpdate(size_t size) override; + + // Clears the fields of the collector so that it is ready to start collecting + // another HPACK block entry. + void Clear(); + + // Is the collector ready to start collecting another HPACK block entry. + bool IsClear() const; + + // Has a complete entry been collected? + bool IsComplete() const; + + // Based on the HpackEntryType, is a literal name expected? + bool LiteralNameExpected() const; + + // Based on the HpackEntryType, is a literal value expected? + bool LiteralValueExpected() const; + + // Returns success if collected an Indexed Header (i.e. OnIndexedHeader was + // called). + ::testing::AssertionResult ValidateIndexedHeader(size_t expected_index) const; + + // Returns success if collected a Header with an indexed name and literal + // value (i.e. OnStartLiteralHeader was called with a non-zero index for + // the name, which must match expected_index). + ::testing::AssertionResult ValidateLiteralValueHeader( + HpackEntryType expected_type, + size_t expected_index, + bool expected_value_huffman, + Http2StringPiece expected_value) const; + + // Returns success if collected a Header with an literal name and literal + // value. + ::testing::AssertionResult ValidateLiteralNameValueHeader( + HpackEntryType expected_type, + bool expected_name_huffman, + Http2StringPiece expected_name, + bool expected_value_huffman, + Http2StringPiece expected_value) const; + + // Returns success if collected a Dynamic Table Size Update, + // with the specified size. + ::testing::AssertionResult ValidateDynamicTableSizeUpdate( + size_t expected_size) const; + + void set_header_type(HpackEntryType v) { header_type_ = v; } + HpackEntryType header_type() const { return header_type_; } + + void set_index(size_t v) { index_ = v; } + size_t index() const { return index_; } + + void set_name(const HpackStringCollector& v) { name_ = v; } + const HpackStringCollector& name() const { return name_; } + + void set_value(const HpackStringCollector& v) { value_ = v; } + const HpackStringCollector& value() const { return value_; } + + void set_started(bool v) { started_ = v; } + bool started() const { return started_; } + + void set_ended(bool v) { ended_ = v; } + bool ended() const { return ended_; } + + void AppendToHpackBlockBuilder(HpackBlockBuilder* hbb) const; + + // Returns a debug string. + Http2String ToString() const; + + private: + void Init(HpackEntryType type, size_t maybe_index); + + HpackEntryType header_type_; + size_t index_; + + HpackStringCollector name_; + HpackStringCollector value_; + + // True if has received a call to an HpackEntryDecoderListener method + // indicating the start of decoding an HPACK entry; for example, + // OnIndexedHeader set it true, but OnNameStart does not change it. + bool started_ = false; + + // True if has received a call to an HpackEntryDecoderListener method + // indicating the end of decoding an HPACK entry; for example, + // OnIndexedHeader and OnValueEnd both set it true, but OnNameEnd does + // not change it. + bool ended_ = false; +}; + +bool operator==(const HpackEntryCollector& a, const HpackEntryCollector& b); +bool operator!=(const HpackEntryCollector& a, const HpackEntryCollector& b); +std::ostream& operator<<(std::ostream& out, const HpackEntryCollector& v); + +} // namespace test +} // namespace http2 + +#endif // QUICHE_HTTP2_HPACK_DECODER_HPACK_ENTRY_COLLECTOR_H_ diff --git a/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_decoder.cc b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_decoder.cc new file mode 100644 index 00000000000..96b9f066030 --- /dev/null +++ b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_decoder.cc @@ -0,0 +1,267 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_decoder.h" + +#include <stddef.h> + +#include <cstdint> + +#include "base/logging.h" +#include "base/macros.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_bug_tracker.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_macros.h" + +namespace http2 { +namespace { +// Converts calls from HpackStringDecoder when decoding a header name into the +// appropriate HpackEntryDecoderListener::OnName* calls. +class NameDecoderListener { + public: + explicit NameDecoderListener(HpackEntryDecoderListener* listener) + : listener_(listener) {} + bool OnStringStart(bool huffman_encoded, size_t len) { + listener_->OnNameStart(huffman_encoded, len); + return true; + } + void OnStringData(const char* data, size_t len) { + listener_->OnNameData(data, len); + } + void OnStringEnd() { listener_->OnNameEnd(); } + + private: + HpackEntryDecoderListener* listener_; +}; + +// Converts calls from HpackStringDecoder when decoding a header value into +// the appropriate HpackEntryDecoderListener::OnValue* calls. +class ValueDecoderListener { + public: + explicit ValueDecoderListener(HpackEntryDecoderListener* listener) + : listener_(listener) {} + bool OnStringStart(bool huffman_encoded, size_t len) { + listener_->OnValueStart(huffman_encoded, len); + return true; + } + void OnStringData(const char* data, size_t len) { + listener_->OnValueData(data, len); + } + void OnStringEnd() { listener_->OnValueEnd(); } + + private: + HpackEntryDecoderListener* listener_; +}; +} // namespace + +DecodeStatus HpackEntryDecoder::Start(DecodeBuffer* db, + HpackEntryDecoderListener* listener) { + DCHECK(db != nullptr); + DCHECK(listener != nullptr); + DCHECK(db->HasData()); + DecodeStatus status = entry_type_decoder_.Start(db); + switch (status) { + case DecodeStatus::kDecodeDone: + // The type of the entry and its varint fit into the current decode + // buffer. + if (entry_type_decoder_.entry_type() == HpackEntryType::kIndexedHeader) { + // The entry consists solely of the entry type and varint. + // This is by far the most common case in practice. + listener->OnIndexedHeader(entry_type_decoder_.varint()); + return DecodeStatus::kDecodeDone; + } + state_ = EntryDecoderState::kDecodedType; + return Resume(db, listener); + case DecodeStatus::kDecodeInProgress: + // Hit the end of the decode buffer before fully decoding + // the entry type and varint. + DCHECK_EQ(0u, db->Remaining()); + state_ = EntryDecoderState::kResumeDecodingType; + return status; + case DecodeStatus::kDecodeError: + // The varint must have been invalid (too long). + return status; + } + + HTTP2_BUG << "Unreachable"; + return DecodeStatus::kDecodeError; +} + +DecodeStatus HpackEntryDecoder::Resume(DecodeBuffer* db, + HpackEntryDecoderListener* listener) { + DCHECK(db != nullptr); + DCHECK(listener != nullptr); + + DecodeStatus status; + + do { + switch (state_) { + case EntryDecoderState::kResumeDecodingType: + // entry_type_decoder_ returned kDecodeInProgress when last called. + DVLOG(1) << "kResumeDecodingType: db->Remaining=" << db->Remaining(); + status = entry_type_decoder_.Resume(db); + if (status != DecodeStatus::kDecodeDone) { + return status; + } + state_ = EntryDecoderState::kDecodedType; + HTTP2_FALLTHROUGH; + + case EntryDecoderState::kDecodedType: + // entry_type_decoder_ returned kDecodeDone, now need to decide how + // to proceed. + DVLOG(1) << "kDecodedType: db->Remaining=" << db->Remaining(); + if (DispatchOnType(listener)) { + // All done. + return DecodeStatus::kDecodeDone; + } + continue; + + case EntryDecoderState::kStartDecodingName: + DVLOG(1) << "kStartDecodingName: db->Remaining=" << db->Remaining(); + { + NameDecoderListener ncb(listener); + status = string_decoder_.Start(db, &ncb); + } + if (status != DecodeStatus::kDecodeDone) { + // On the assumption that the status is kDecodeInProgress, set + // state_ accordingly; unnecessary if status is kDecodeError, but + // that will only happen if the varint encoding the name's length + // is too long. + state_ = EntryDecoderState::kResumeDecodingName; + return status; + } + state_ = EntryDecoderState::kStartDecodingValue; + HTTP2_FALLTHROUGH; + + case EntryDecoderState::kStartDecodingValue: + DVLOG(1) << "kStartDecodingValue: db->Remaining=" << db->Remaining(); + { + ValueDecoderListener vcb(listener); + status = string_decoder_.Start(db, &vcb); + } + if (status == DecodeStatus::kDecodeDone) { + // Done with decoding the literal value, so we've reached the + // end of the header entry. + return status; + } + // On the assumption that the status is kDecodeInProgress, set + // state_ accordingly; unnecessary if status is kDecodeError, but + // that will only happen if the varint encoding the value's length + // is too long. + state_ = EntryDecoderState::kResumeDecodingValue; + return status; + + case EntryDecoderState::kResumeDecodingName: + // The literal name was split across decode buffers. + DVLOG(1) << "kResumeDecodingName: db->Remaining=" << db->Remaining(); + { + NameDecoderListener ncb(listener); + status = string_decoder_.Resume(db, &ncb); + } + if (status != DecodeStatus::kDecodeDone) { + // On the assumption that the status is kDecodeInProgress, set + // state_ accordingly; unnecessary if status is kDecodeError, but + // that will only happen if the varint encoding the name's length + // is too long. + state_ = EntryDecoderState::kResumeDecodingName; + return status; + } + state_ = EntryDecoderState::kStartDecodingValue; + break; + + case EntryDecoderState::kResumeDecodingValue: + // The literal value was split across decode buffers. + DVLOG(1) << "kResumeDecodingValue: db->Remaining=" << db->Remaining(); + { + ValueDecoderListener vcb(listener); + status = string_decoder_.Resume(db, &vcb); + } + if (status == DecodeStatus::kDecodeDone) { + // Done with decoding the value, therefore the entry as a whole. + return status; + } + // On the assumption that the status is kDecodeInProgress, set + // state_ accordingly; unnecessary if status is kDecodeError, but + // that will only happen if the varint encoding the value's length + // is too long. + state_ = EntryDecoderState::kResumeDecodingValue; + return status; + } + } while (true); +} + +bool HpackEntryDecoder::DispatchOnType(HpackEntryDecoderListener* listener) { + const HpackEntryType entry_type = entry_type_decoder_.entry_type(); + const uint32_t varint = static_cast<uint32_t>(entry_type_decoder_.varint()); + switch (entry_type) { + case HpackEntryType::kIndexedHeader: + // The entry consists solely of the entry type and varint. See: + // http://httpwg.org/specs/rfc7541.html#indexed.header.representation + listener->OnIndexedHeader(varint); + return true; + + case HpackEntryType::kIndexedLiteralHeader: + case HpackEntryType::kUnindexedLiteralHeader: + case HpackEntryType::kNeverIndexedLiteralHeader: + // The entry has a literal value, and if the varint is zero also has a + // literal name preceding the value. See: + // http://httpwg.org/specs/rfc7541.html#literal.header.representation + listener->OnStartLiteralHeader(entry_type, varint); + if (varint == 0) { + state_ = EntryDecoderState::kStartDecodingName; + } else { + state_ = EntryDecoderState::kStartDecodingValue; + } + return false; + + case HpackEntryType::kDynamicTableSizeUpdate: + // The entry consists solely of the entry type and varint. FWIW, I've + // never seen this type of entry in production (primarily browser + // traffic) so if you're designing an HPACK successor someday, consider + // dropping it or giving it a much longer prefix. See: + // http://httpwg.org/specs/rfc7541.html#encoding.context.update + listener->OnDynamicTableSizeUpdate(varint); + return true; + } + + HTTP2_BUG << "Unreachable, entry_type=" << entry_type; + return true; +} + +void HpackEntryDecoder::OutputDebugString(std::ostream& out) const { + out << "HpackEntryDecoder(state=" << state_ << ", " << entry_type_decoder_ + << ", " << string_decoder_ << ")"; +} + +Http2String HpackEntryDecoder::DebugString() const { + std::stringstream s; + s << *this; + return s.str(); +} + +std::ostream& operator<<(std::ostream& out, const HpackEntryDecoder& v) { + v.OutputDebugString(out); + return out; +} + +std::ostream& operator<<(std::ostream& out, + HpackEntryDecoder::EntryDecoderState state) { + typedef HpackEntryDecoder::EntryDecoderState EntryDecoderState; + switch (state) { + case EntryDecoderState::kResumeDecodingType: + return out << "kResumeDecodingType"; + case EntryDecoderState::kDecodedType: + return out << "kDecodedType"; + case EntryDecoderState::kStartDecodingName: + return out << "kStartDecodingName"; + case EntryDecoderState::kResumeDecodingName: + return out << "kResumeDecodingName"; + case EntryDecoderState::kStartDecodingValue: + return out << "kStartDecodingValue"; + case EntryDecoderState::kResumeDecodingValue: + return out << "kResumeDecodingValue"; + } + return out << static_cast<int>(state); +} + +} // namespace http2 diff --git a/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_decoder.h b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_decoder.h new file mode 100644 index 00000000000..fe59d964841 --- /dev/null +++ b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_decoder.h @@ -0,0 +1,85 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef QUICHE_HTTP2_HPACK_DECODER_HPACK_ENTRY_DECODER_H_ +#define QUICHE_HTTP2_HPACK_DECODER_HPACK_ENTRY_DECODER_H_ + +// HpackEntryDecoder decodes a single HPACK entry (i.e. one header or one +// dynamic table size update), in a resumable fashion. The first call, Start(), +// must provide a non-empty decode buffer. Continue with calls to Resume() if +// Start, and any subsequent calls to Resume, returns kDecodeInProgress. + +#include "base/logging.h" +#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h" +#include "net/third_party/quiche/src/http2/decoder/decode_status.h" +#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_decoder_listener.h" +#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_type_decoder.h" +#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_string_decoder.h" +#include "net/third_party/quiche/src/http2/hpack/http2_hpack_constants.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_export.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string.h" + +namespace http2 { + +class HTTP2_EXPORT_PRIVATE HpackEntryDecoder { + public: + enum class EntryDecoderState { + // Have started decoding the type/varint, but didn't finish on the previous + // attempt. Next state is kResumeDecodingType or kDecodedType. + kResumeDecodingType, + + // Have just finished decoding the type/varint. Final state if the type is + // kIndexedHeader or kDynamicTableSizeUpdate. Otherwise, the next state is + // kStartDecodingName (if the varint is 0), else kStartDecodingValue. + kDecodedType, + + // Ready to start decoding the literal name of a header entry. Next state + // is kResumeDecodingName (if the name is split across decode buffers), + // else kStartDecodingValue. + kStartDecodingName, + + // Resume decoding the literal name of a header that is split across decode + // buffers. + kResumeDecodingName, + + // Ready to start decoding the literal value of a header entry. Final state + // if the value string is entirely in the decode buffer, else the next state + // is kResumeDecodingValue. + kStartDecodingValue, + + // Resume decoding the literal value of a header that is split across decode + // buffers. + kResumeDecodingValue, + }; + + // Only call when the decode buffer has data (i.e. HpackBlockDecoder must + // not call until there is data). + DecodeStatus Start(DecodeBuffer* db, HpackEntryDecoderListener* listener); + + // Only call Resume if the previous call (Start or Resume) returned + // kDecodeInProgress; Resume is also called from Start when it has succeeded + // in decoding the entry type and its varint. + DecodeStatus Resume(DecodeBuffer* db, HpackEntryDecoderListener* listener); + + Http2String DebugString() const; + void OutputDebugString(std::ostream& out) const; + + private: + // Implements handling state kDecodedType. + bool DispatchOnType(HpackEntryDecoderListener* listener); + + HpackEntryTypeDecoder entry_type_decoder_; + HpackStringDecoder string_decoder_; + EntryDecoderState state_ = EntryDecoderState(); +}; + +HTTP2_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& out, + const HpackEntryDecoder& v); +HTTP2_EXPORT_PRIVATE std::ostream& operator<<( + std::ostream& out, + HpackEntryDecoder::EntryDecoderState state); + +} // namespace http2 + +#endif // QUICHE_HTTP2_HPACK_DECODER_HPACK_ENTRY_DECODER_H_ diff --git a/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_decoder_listener.cc b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_decoder_listener.cc new file mode 100644 index 00000000000..b783b150da8 --- /dev/null +++ b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_decoder_listener.cc @@ -0,0 +1,81 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_decoder_listener.h" + +#include "base/logging.h" + +namespace http2 { + +void HpackEntryDecoderVLoggingListener::OnIndexedHeader(size_t index) { + VLOG(1) << "OnIndexedHeader, index=" << index; + if (wrapped_) { + wrapped_->OnIndexedHeader(index); + } +} + +void HpackEntryDecoderVLoggingListener::OnStartLiteralHeader( + HpackEntryType entry_type, + size_t maybe_name_index) { + VLOG(1) << "OnStartLiteralHeader: entry_type=" << entry_type + << ", maybe_name_index=" << maybe_name_index; + if (wrapped_) { + wrapped_->OnStartLiteralHeader(entry_type, maybe_name_index); + } +} + +void HpackEntryDecoderVLoggingListener::OnNameStart(bool huffman_encoded, + size_t len) { + VLOG(1) << "OnNameStart: H=" << huffman_encoded << ", len=" << len; + if (wrapped_) { + wrapped_->OnNameStart(huffman_encoded, len); + } +} + +void HpackEntryDecoderVLoggingListener::OnNameData(const char* data, + size_t len) { + VLOG(1) << "OnNameData: len=" << len; + if (wrapped_) { + wrapped_->OnNameData(data, len); + } +} + +void HpackEntryDecoderVLoggingListener::OnNameEnd() { + VLOG(1) << "OnNameEnd"; + if (wrapped_) { + wrapped_->OnNameEnd(); + } +} + +void HpackEntryDecoderVLoggingListener::OnValueStart(bool huffman_encoded, + size_t len) { + VLOG(1) << "OnValueStart: H=" << huffman_encoded << ", len=" << len; + if (wrapped_) { + wrapped_->OnValueStart(huffman_encoded, len); + } +} + +void HpackEntryDecoderVLoggingListener::OnValueData(const char* data, + size_t len) { + VLOG(1) << "OnValueData: len=" << len; + if (wrapped_) { + wrapped_->OnValueData(data, len); + } +} + +void HpackEntryDecoderVLoggingListener::OnValueEnd() { + VLOG(1) << "OnValueEnd"; + if (wrapped_) { + wrapped_->OnValueEnd(); + } +} + +void HpackEntryDecoderVLoggingListener::OnDynamicTableSizeUpdate(size_t size) { + VLOG(1) << "OnDynamicTableSizeUpdate: size=" << size; + if (wrapped_) { + wrapped_->OnDynamicTableSizeUpdate(size); + } +} + +} // namespace http2 diff --git a/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_decoder_listener.h b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_decoder_listener.h new file mode 100644 index 00000000000..fd11f59ef4a --- /dev/null +++ b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_decoder_listener.h @@ -0,0 +1,110 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef QUICHE_HTTP2_HPACK_DECODER_HPACK_ENTRY_DECODER_LISTENER_H_ +#define QUICHE_HTTP2_HPACK_DECODER_HPACK_ENTRY_DECODER_LISTENER_H_ + +// Defines HpackEntryDecoderListener, the base class of listeners that +// HpackEntryDecoder calls. Also defines HpackEntryDecoderVLoggingListener +// which logs before calling another HpackEntryDecoderListener implementation. + +#include <stddef.h> + +#include "net/third_party/quiche/src/http2/hpack/http2_hpack_constants.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_export.h" + +namespace http2 { + +class HTTP2_EXPORT_PRIVATE HpackEntryDecoderListener { + public: + virtual ~HpackEntryDecoderListener() {} + + // Called when an indexed header (i.e. one in the static or dynamic table) has + // been decoded from an HPACK block. index is supposed to be non-zero, but + // that has not been checked by the caller. + virtual void OnIndexedHeader(size_t index) = 0; + + // Called when the start of a header with a literal value, and maybe a literal + // name, has been decoded. maybe_name_index is zero if the header has a + // literal name, else it is a reference into the static or dynamic table, from + // which the name should be determined. When the name is literal, the next + // call will be to OnNameStart; else it will be to OnValueStart. entry_type + // indicates whether the peer has added the entry to its dynamic table, and + // whether a proxy is permitted to do so when forwarding the entry. + virtual void OnStartLiteralHeader(HpackEntryType entry_type, + size_t maybe_name_index) = 0; + + // Called when the encoding (Huffman compressed or plain text) and the encoded + // length of a literal name has been decoded. OnNameData will be called next, + // and repeatedly until the sum of lengths passed to OnNameData is len. + virtual void OnNameStart(bool huffman_encoded, size_t len) = 0; + + // Called when len bytes of an encoded header name have been decoded. + virtual void OnNameData(const char* data, size_t len) = 0; + + // Called after the entire name has been passed to OnNameData. + // OnValueStart will be called next. + virtual void OnNameEnd() = 0; + + // Called when the encoding (Huffman compressed or plain text) and the encoded + // length of a literal value has been decoded. OnValueData will be called + // next, and repeatedly until the sum of lengths passed to OnValueData is len. + virtual void OnValueStart(bool huffman_encoded, size_t len) = 0; + + // Called when len bytes of an encoded header value have been decoded. + virtual void OnValueData(const char* data, size_t len) = 0; + + // Called after the entire value has been passed to OnValueData, marking the + // end of a header entry with a literal value, and maybe a literal name. + virtual void OnValueEnd() = 0; + + // Called when an update to the size of the peer's dynamic table has been + // decoded. + virtual void OnDynamicTableSizeUpdate(size_t size) = 0; +}; + +class HTTP2_EXPORT_PRIVATE HpackEntryDecoderVLoggingListener + : public HpackEntryDecoderListener { + public: + HpackEntryDecoderVLoggingListener() : wrapped_(nullptr) {} + explicit HpackEntryDecoderVLoggingListener(HpackEntryDecoderListener* wrapped) + : wrapped_(wrapped) {} + ~HpackEntryDecoderVLoggingListener() override {} + + void OnIndexedHeader(size_t index) override; + void OnStartLiteralHeader(HpackEntryType entry_type, + size_t maybe_name_index) override; + void OnNameStart(bool huffman_encoded, size_t len) override; + void OnNameData(const char* data, size_t len) override; + void OnNameEnd() override; + void OnValueStart(bool huffman_encoded, size_t len) override; + void OnValueData(const char* data, size_t len) override; + void OnValueEnd() override; + void OnDynamicTableSizeUpdate(size_t size) override; + + private: + HpackEntryDecoderListener* const wrapped_; +}; + +// A no-op implementation of HpackEntryDecoderListener. +class HTTP2_EXPORT_PRIVATE HpackEntryDecoderNoOpListener + : public HpackEntryDecoderListener { + public: + ~HpackEntryDecoderNoOpListener() override {} + + void OnIndexedHeader(size_t index) override {} + void OnStartLiteralHeader(HpackEntryType entry_type, + size_t maybe_name_index) override {} + void OnNameStart(bool huffman_encoded, size_t len) override {} + void OnNameData(const char* data, size_t len) override {} + void OnNameEnd() override {} + void OnValueStart(bool huffman_encoded, size_t len) override {} + void OnValueData(const char* data, size_t len) override {} + void OnValueEnd() override {} + void OnDynamicTableSizeUpdate(size_t size) override {} +}; + +} // namespace http2 + +#endif // QUICHE_HTTP2_HPACK_DECODER_HPACK_ENTRY_DECODER_LISTENER_H_ diff --git a/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_decoder_test.cc b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_decoder_test.cc new file mode 100644 index 00000000000..7249bd558a4 --- /dev/null +++ b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_decoder_test.cc @@ -0,0 +1,211 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_decoder.h" + +// Tests of HpackEntryDecoder. + +#include <cstdint> + +#include "testing/gtest/include/gtest/gtest.h" +#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_collector.h" +#include "net/third_party/quiche/src/http2/hpack/tools/hpack_block_builder.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_test_helpers.h" +#include "net/third_party/quiche/src/http2/test_tools/http2_random.h" +#include "net/third_party/quiche/src/http2/tools/random_decoder_test.h" + +using ::testing::AssertionResult; + +namespace http2 { +namespace test { +namespace { + +class HpackEntryDecoderTest : public RandomDecoderTest { + protected: + HpackEntryDecoderTest() : listener_(&collector_) {} + + DecodeStatus StartDecoding(DecodeBuffer* b) override { + collector_.Clear(); + return decoder_.Start(b, &listener_); + } + + DecodeStatus ResumeDecoding(DecodeBuffer* b) override { + return decoder_.Resume(b, &listener_); + } + + AssertionResult DecodeAndValidateSeveralWays(DecodeBuffer* db, + const Validator& validator) { + // StartDecoding, above, requires the DecodeBuffer be non-empty so that it + // can call Start with the prefix byte. + bool return_non_zero_on_first = true; + return RandomDecoderTest::DecodeAndValidateSeveralWays( + db, return_non_zero_on_first, validator); + } + + AssertionResult DecodeAndValidateSeveralWays(const HpackBlockBuilder& hbb, + const Validator& validator) { + DecodeBuffer db(hbb.buffer()); + return DecodeAndValidateSeveralWays(&db, validator); + } + + HpackEntryDecoder decoder_; + HpackEntryCollector collector_; + HpackEntryDecoderVLoggingListener listener_; +}; + +TEST_F(HpackEntryDecoderTest, IndexedHeader_Literals) { + { + const char input[] = {'\x82'}; // == Index 2 == + DecodeBuffer b(input); + auto do_check = [this]() { + VERIFY_AND_RETURN_SUCCESS(collector_.ValidateIndexedHeader(2)); + }; + EXPECT_TRUE( + DecodeAndValidateSeveralWays(&b, ValidateDoneAndEmpty(do_check))); + EXPECT_TRUE(do_check()); + } + collector_.Clear(); + { + const char input[] = {'\xfe'}; // == Index 126 == + DecodeBuffer b(input); + auto do_check = [this]() { + VERIFY_AND_RETURN_SUCCESS(collector_.ValidateIndexedHeader(126)); + }; + EXPECT_TRUE( + DecodeAndValidateSeveralWays(&b, ValidateDoneAndEmpty(do_check))); + EXPECT_TRUE(do_check()); + } + collector_.Clear(); + { + const char input[] = {'\xff', '\x00'}; // == Index 127 == + DecodeBuffer b(input); + auto do_check = [this]() { + VERIFY_AND_RETURN_SUCCESS(collector_.ValidateIndexedHeader(127)); + }; + EXPECT_TRUE( + DecodeAndValidateSeveralWays(&b, ValidateDoneAndEmpty(do_check))); + EXPECT_TRUE(do_check()); + } +} + +TEST_F(HpackEntryDecoderTest, IndexedHeader_Various) { + // Indices chosen to hit encoding and table boundaries. + for (const uint32_t ndx : {1, 2, 61, 62, 63, 126, 127, 254, 255, 256}) { + HpackBlockBuilder hbb; + hbb.AppendIndexedHeader(ndx); + + auto do_check = [this, ndx]() { + VERIFY_AND_RETURN_SUCCESS(collector_.ValidateIndexedHeader(ndx)); + }; + EXPECT_TRUE( + DecodeAndValidateSeveralWays(hbb, ValidateDoneAndEmpty(do_check))); + EXPECT_TRUE(do_check()); + } +} + +TEST_F(HpackEntryDecoderTest, IndexedLiteralValue_Literal) { + const char input[] = + "\x7f" // == Literal indexed, name index 0x40 == + "\x01" // 2nd byte of name index (0x01 + 0x3f == 0x40) + "\x0d" // Value length (13) + "custom-header"; // Value + DecodeBuffer b(input, sizeof input - 1); + auto do_check = [this]() { + VERIFY_AND_RETURN_SUCCESS(collector_.ValidateLiteralValueHeader( + HpackEntryType::kIndexedLiteralHeader, 0x40, false, "custom-header")); + }; + EXPECT_TRUE(DecodeAndValidateSeveralWays(&b, ValidateDoneAndEmpty(do_check))); + EXPECT_TRUE(do_check()); +} + +TEST_F(HpackEntryDecoderTest, IndexedLiteralNameValue_Literal) { + const char input[] = + "\x40" // == Literal indexed == + "\x0a" // Name length (10) + "custom-key" // Name + "\x0d" // Value length (13) + "custom-header"; // Value + + DecodeBuffer b(input, sizeof input - 1); + auto do_check = [this]() { + VERIFY_AND_RETURN_SUCCESS(collector_.ValidateLiteralNameValueHeader( + HpackEntryType::kIndexedLiteralHeader, false, "custom-key", false, + "custom-header")); + }; + EXPECT_TRUE(DecodeAndValidateSeveralWays(&b, ValidateDoneAndEmpty(do_check))); + EXPECT_TRUE(do_check()); +} + +TEST_F(HpackEntryDecoderTest, DynamicTableSizeUpdate_Literal) { + // Size update, length 31. + const char input[] = "\x3f\x00"; + DecodeBuffer b(input, 2); + auto do_check = [this]() { + VERIFY_AND_RETURN_SUCCESS(collector_.ValidateDynamicTableSizeUpdate(31)); + }; + EXPECT_TRUE(DecodeAndValidateSeveralWays(&b, ValidateDoneAndEmpty(do_check))); + EXPECT_TRUE(do_check()); +} + +class HpackLiteralEntryDecoderTest + : public HpackEntryDecoderTest, + public ::testing::WithParamInterface<HpackEntryType> { + protected: + HpackLiteralEntryDecoderTest() : entry_type_(GetParam()) {} + + const HpackEntryType entry_type_; +}; + +INSTANTIATE_TEST_CASE_P( + AllLiteralTypes, + HpackLiteralEntryDecoderTest, + testing::Values(HpackEntryType::kIndexedLiteralHeader, + HpackEntryType::kUnindexedLiteralHeader, + HpackEntryType::kNeverIndexedLiteralHeader)); + +TEST_P(HpackLiteralEntryDecoderTest, RandNameIndexAndLiteralValue) { + for (int n = 0; n < 10; n++) { + const uint32_t ndx = 1 + Random().Rand8(); + const bool value_is_huffman_encoded = (n % 2) == 0; + const Http2String value = Random().RandString(Random().Rand8()); + HpackBlockBuilder hbb; + hbb.AppendNameIndexAndLiteralValue(entry_type_, ndx, + value_is_huffman_encoded, value); + auto do_check = [this, ndx, value_is_huffman_encoded, + value]() -> AssertionResult { + VERIFY_AND_RETURN_SUCCESS(collector_.ValidateLiteralValueHeader( + entry_type_, ndx, value_is_huffman_encoded, value)); + }; + EXPECT_TRUE( + DecodeAndValidateSeveralWays(hbb, ValidateDoneAndEmpty(do_check))); + EXPECT_TRUE(do_check()); + } +} + +TEST_P(HpackLiteralEntryDecoderTest, RandLiteralNameAndValue) { + for (int n = 0; n < 10; n++) { + const bool name_is_huffman_encoded = (n & 1) == 0; + const int name_len = 1 + Random().Rand8(); + const Http2String name = Random().RandString(name_len); + const bool value_is_huffman_encoded = (n & 2) == 0; + const int value_len = Random().Skewed(10); + const Http2String value = Random().RandString(value_len); + HpackBlockBuilder hbb; + hbb.AppendLiteralNameAndValue(entry_type_, name_is_huffman_encoded, name, + value_is_huffman_encoded, value); + auto do_check = [this, name_is_huffman_encoded, name, + value_is_huffman_encoded, value]() -> AssertionResult { + VERIFY_AND_RETURN_SUCCESS(collector_.ValidateLiteralNameValueHeader( + entry_type_, name_is_huffman_encoded, name, value_is_huffman_encoded, + value)); + }; + EXPECT_TRUE( + DecodeAndValidateSeveralWays(hbb, ValidateDoneAndEmpty(do_check))); + EXPECT_TRUE(do_check()); + } +} + +} // namespace +} // namespace test +} // namespace http2 diff --git a/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_type_decoder.cc b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_type_decoder.cc new file mode 100644 index 00000000000..121e3d0b02c --- /dev/null +++ b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_type_decoder.cc @@ -0,0 +1,358 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_type_decoder.h" + +#include "base/logging.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_bug_tracker.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string_utils.h" + +namespace http2 { + +Http2String HpackEntryTypeDecoder::DebugString() const { + return Http2StrCat( + "HpackEntryTypeDecoder(varint_decoder=", varint_decoder_.DebugString(), + ", entry_type=", entry_type_, ")"); +} + +std::ostream& operator<<(std::ostream& out, const HpackEntryTypeDecoder& v) { + return out << v.DebugString(); +} + +// This ridiculous looking function turned out to be the winner in benchmarking +// of several very different alternative implementations. It would be even +// faster (~7%) if inlined in the header file, but I'm not sure if that is +// worth doing... yet. +// TODO(jamessynge): Benchmark again at a higher level (e.g. at least at the +// full HTTP/2 decoder level, but preferably still higher) to determine if the +// alternatives that take less code/data space are preferable in that situation. +DecodeStatus HpackEntryTypeDecoder::Start(DecodeBuffer* db) { + DCHECK(db != nullptr); + DCHECK(db->HasData()); + + // The high four bits (nibble) of first byte of the entry determine the type + // of the entry, and may also be the initial bits of the varint that + // represents an index or table size. Note the use of the word 'initial' + // rather than 'high'; the HPACK encoding of varints is not in network + // order (i.e. not big-endian, the high-order byte isn't first), nor in + // little-endian order. See: + // http://httpwg.org/specs/rfc7541.html#integer.representation + uint8_t byte = db->DecodeUInt8(); + switch (byte) { + case 0b00000000: + case 0b00000001: + case 0b00000010: + case 0b00000011: + case 0b00000100: + case 0b00000101: + case 0b00000110: + case 0b00000111: + case 0b00001000: + case 0b00001001: + case 0b00001010: + case 0b00001011: + case 0b00001100: + case 0b00001101: + case 0b00001110: + // The low 4 bits of |byte| are the initial bits of the varint. + // One of those bits is 0, so the varint is only one byte long. + entry_type_ = HpackEntryType::kUnindexedLiteralHeader; + varint_decoder_.set_value(byte); + return DecodeStatus::kDecodeDone; + + case 0b00001111: + // The low 4 bits of |byte| are the initial bits of the varint. All 4 + // are 1, so the varint extends into another byte. + entry_type_ = HpackEntryType::kUnindexedLiteralHeader; + return varint_decoder_.StartExtended(4, db); + + case 0b00010000: + case 0b00010001: + case 0b00010010: + case 0b00010011: + case 0b00010100: + case 0b00010101: + case 0b00010110: + case 0b00010111: + case 0b00011000: + case 0b00011001: + case 0b00011010: + case 0b00011011: + case 0b00011100: + case 0b00011101: + case 0b00011110: + // The low 4 bits of |byte| are the initial bits of the varint. + // One of those bits is 0, so the varint is only one byte long. + entry_type_ = HpackEntryType::kNeverIndexedLiteralHeader; + varint_decoder_.set_value(byte & 0x0f); + return DecodeStatus::kDecodeDone; + + case 0b00011111: + // The low 4 bits of |byte| are the initial bits of the varint. + // All of those bits are 1, so the varint extends into another byte. + entry_type_ = HpackEntryType::kNeverIndexedLiteralHeader; + return varint_decoder_.StartExtended(4, db); + + case 0b00100000: + case 0b00100001: + case 0b00100010: + case 0b00100011: + case 0b00100100: + case 0b00100101: + case 0b00100110: + case 0b00100111: + case 0b00101000: + case 0b00101001: + case 0b00101010: + case 0b00101011: + case 0b00101100: + case 0b00101101: + case 0b00101110: + case 0b00101111: + case 0b00110000: + case 0b00110001: + case 0b00110010: + case 0b00110011: + case 0b00110100: + case 0b00110101: + case 0b00110110: + case 0b00110111: + case 0b00111000: + case 0b00111001: + case 0b00111010: + case 0b00111011: + case 0b00111100: + case 0b00111101: + case 0b00111110: + entry_type_ = HpackEntryType::kDynamicTableSizeUpdate; + // The low 5 bits of |byte| are the initial bits of the varint. + // One of those bits is 0, so the varint is only one byte long. + varint_decoder_.set_value(byte & 0x01f); + return DecodeStatus::kDecodeDone; + + case 0b00111111: + entry_type_ = HpackEntryType::kDynamicTableSizeUpdate; + // The low 5 bits of |byte| are the initial bits of the varint. + // All of those bits are 1, so the varint extends into another byte. + return varint_decoder_.StartExtended(5, db); + + case 0b01000000: + case 0b01000001: + case 0b01000010: + case 0b01000011: + case 0b01000100: + case 0b01000101: + case 0b01000110: + case 0b01000111: + case 0b01001000: + case 0b01001001: + case 0b01001010: + case 0b01001011: + case 0b01001100: + case 0b01001101: + case 0b01001110: + case 0b01001111: + case 0b01010000: + case 0b01010001: + case 0b01010010: + case 0b01010011: + case 0b01010100: + case 0b01010101: + case 0b01010110: + case 0b01010111: + case 0b01011000: + case 0b01011001: + case 0b01011010: + case 0b01011011: + case 0b01011100: + case 0b01011101: + case 0b01011110: + case 0b01011111: + case 0b01100000: + case 0b01100001: + case 0b01100010: + case 0b01100011: + case 0b01100100: + case 0b01100101: + case 0b01100110: + case 0b01100111: + case 0b01101000: + case 0b01101001: + case 0b01101010: + case 0b01101011: + case 0b01101100: + case 0b01101101: + case 0b01101110: + case 0b01101111: + case 0b01110000: + case 0b01110001: + case 0b01110010: + case 0b01110011: + case 0b01110100: + case 0b01110101: + case 0b01110110: + case 0b01110111: + case 0b01111000: + case 0b01111001: + case 0b01111010: + case 0b01111011: + case 0b01111100: + case 0b01111101: + case 0b01111110: + entry_type_ = HpackEntryType::kIndexedLiteralHeader; + // The low 6 bits of |byte| are the initial bits of the varint. + // One of those bits is 0, so the varint is only one byte long. + varint_decoder_.set_value(byte & 0x03f); + return DecodeStatus::kDecodeDone; + + case 0b01111111: + entry_type_ = HpackEntryType::kIndexedLiteralHeader; + // The low 6 bits of |byte| are the initial bits of the varint. + // All of those bits are 1, so the varint extends into another byte. + return varint_decoder_.StartExtended(6, db); + + case 0b10000000: + case 0b10000001: + case 0b10000010: + case 0b10000011: + case 0b10000100: + case 0b10000101: + case 0b10000110: + case 0b10000111: + case 0b10001000: + case 0b10001001: + case 0b10001010: + case 0b10001011: + case 0b10001100: + case 0b10001101: + case 0b10001110: + case 0b10001111: + case 0b10010000: + case 0b10010001: + case 0b10010010: + case 0b10010011: + case 0b10010100: + case 0b10010101: + case 0b10010110: + case 0b10010111: + case 0b10011000: + case 0b10011001: + case 0b10011010: + case 0b10011011: + case 0b10011100: + case 0b10011101: + case 0b10011110: + case 0b10011111: + case 0b10100000: + case 0b10100001: + case 0b10100010: + case 0b10100011: + case 0b10100100: + case 0b10100101: + case 0b10100110: + case 0b10100111: + case 0b10101000: + case 0b10101001: + case 0b10101010: + case 0b10101011: + case 0b10101100: + case 0b10101101: + case 0b10101110: + case 0b10101111: + case 0b10110000: + case 0b10110001: + case 0b10110010: + case 0b10110011: + case 0b10110100: + case 0b10110101: + case 0b10110110: + case 0b10110111: + case 0b10111000: + case 0b10111001: + case 0b10111010: + case 0b10111011: + case 0b10111100: + case 0b10111101: + case 0b10111110: + case 0b10111111: + case 0b11000000: + case 0b11000001: + case 0b11000010: + case 0b11000011: + case 0b11000100: + case 0b11000101: + case 0b11000110: + case 0b11000111: + case 0b11001000: + case 0b11001001: + case 0b11001010: + case 0b11001011: + case 0b11001100: + case 0b11001101: + case 0b11001110: + case 0b11001111: + case 0b11010000: + case 0b11010001: + case 0b11010010: + case 0b11010011: + case 0b11010100: + case 0b11010101: + case 0b11010110: + case 0b11010111: + case 0b11011000: + case 0b11011001: + case 0b11011010: + case 0b11011011: + case 0b11011100: + case 0b11011101: + case 0b11011110: + case 0b11011111: + case 0b11100000: + case 0b11100001: + case 0b11100010: + case 0b11100011: + case 0b11100100: + case 0b11100101: + case 0b11100110: + case 0b11100111: + case 0b11101000: + case 0b11101001: + case 0b11101010: + case 0b11101011: + case 0b11101100: + case 0b11101101: + case 0b11101110: + case 0b11101111: + case 0b11110000: + case 0b11110001: + case 0b11110010: + case 0b11110011: + case 0b11110100: + case 0b11110101: + case 0b11110110: + case 0b11110111: + case 0b11111000: + case 0b11111001: + case 0b11111010: + case 0b11111011: + case 0b11111100: + case 0b11111101: + case 0b11111110: + entry_type_ = HpackEntryType::kIndexedHeader; + // The low 7 bits of |byte| are the initial bits of the varint. + // One of those bits is 0, so the varint is only one byte long. + varint_decoder_.set_value(byte & 0x07f); + return DecodeStatus::kDecodeDone; + + case 0b11111111: + entry_type_ = HpackEntryType::kIndexedHeader; + // The low 7 bits of |byte| are the initial bits of the varint. + // All of those bits are 1, so the varint extends into another byte. + return varint_decoder_.StartExtended(7, db); + } + HTTP2_BUG << "Unreachable, byte=" << std::hex << static_cast<uint32_t>(byte); + return DecodeStatus::kDecodeError; +} + +} // namespace http2 diff --git a/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_type_decoder.h b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_type_decoder.h new file mode 100644 index 00000000000..79898becbef --- /dev/null +++ b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_type_decoder.h @@ -0,0 +1,57 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef QUICHE_HTTP2_HPACK_DECODER_HPACK_ENTRY_TYPE_DECODER_H_ +#define QUICHE_HTTP2_HPACK_DECODER_HPACK_ENTRY_TYPE_DECODER_H_ + +// Decodes the type of an HPACK entry, and the variable length integer whose +// prefix is in the low-order bits of the same byte, "below" the type bits. +// The integer represents an index into static or dynamic table, which may be +// zero, or is the new size limit of the dynamic table. + +#include <cstdint> + +#include "base/logging.h" +#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h" +#include "net/third_party/quiche/src/http2/decoder/decode_status.h" +#include "net/third_party/quiche/src/http2/hpack/http2_hpack_constants.h" +#include "net/third_party/quiche/src/http2/hpack/varint/hpack_varint_decoder.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_export.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string.h" + +namespace http2 { + +class HTTP2_EXPORT_PRIVATE HpackEntryTypeDecoder { + public: + // Only call when the decode buffer has data (i.e. HpackEntryDecoder must + // not call until there is data). + DecodeStatus Start(DecodeBuffer* db); + + // Only call Resume if the previous call (Start or Resume) returned + // DecodeStatus::kDecodeInProgress. + DecodeStatus Resume(DecodeBuffer* db) { return varint_decoder_.Resume(db); } + + // Returns the decoded entry type. Only call if the preceding call to Start + // or Resume returned kDecodeDone. + HpackEntryType entry_type() const { return entry_type_; } + + // Returns the decoded variable length integer. Only call if the + // preceding call to Start or Resume returned kDecodeDone. + uint64_t varint() const { return varint_decoder_.value(); } + + Http2String DebugString() const; + + private: + HpackVarintDecoder varint_decoder_; + + // This field is initialized just to keep ASAN happy about reading it + // from DebugString(). + HpackEntryType entry_type_ = HpackEntryType::kIndexedHeader; +}; + +HTTP2_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& out, + const HpackEntryTypeDecoder& v); + +} // namespace http2 +#endif // QUICHE_HTTP2_HPACK_DECODER_HPACK_ENTRY_TYPE_DECODER_H_ diff --git a/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_type_decoder_test.cc b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_type_decoder_test.cc new file mode 100644 index 00000000000..afff7cc629d --- /dev/null +++ b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_type_decoder_test.cc @@ -0,0 +1,87 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_type_decoder.h" + +#include <vector> + +#include "base/logging.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "net/third_party/quiche/src/http2/hpack/tools/hpack_block_builder.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_test_helpers.h" +#include "net/third_party/quiche/src/http2/tools/random_decoder_test.h" + +using ::testing::AssertionFailure; +using ::testing::AssertionResult; +using ::testing::AssertionSuccess; + +namespace http2 { +namespace test { +namespace { +const bool kReturnNonZeroOnFirst = true; + +class HpackEntryTypeDecoderTest : public RandomDecoderTest { + protected: + DecodeStatus StartDecoding(DecodeBuffer* b) override { + CHECK_LT(0u, b->Remaining()); + return decoder_.Start(b); + } + + DecodeStatus ResumeDecoding(DecodeBuffer* b) override { + return decoder_.Resume(b); + } + + HpackEntryTypeDecoder decoder_; +}; + +TEST_F(HpackEntryTypeDecoderTest, DynamicTableSizeUpdate) { + for (uint32_t size = 0; size < 1000 * 1000; size += 256) { + HpackBlockBuilder bb; + bb.AppendDynamicTableSizeUpdate(size); + DecodeBuffer db(bb.buffer()); + auto validator = [size, this]() -> AssertionResult { + VERIFY_EQ(HpackEntryType::kDynamicTableSizeUpdate, decoder_.entry_type()); + VERIFY_EQ(size, decoder_.varint()); + return AssertionSuccess(); + }; + EXPECT_TRUE(DecodeAndValidateSeveralWays(&db, kReturnNonZeroOnFirst, + ValidateDoneAndEmpty(validator))) + << "\nentry_type=kDynamicTableSizeUpdate, size=" << size; + // Run the validator again to make sure that DecodeAndValidateSeveralWays + // did the right thing. + EXPECT_TRUE(validator()); + } +} + +TEST_F(HpackEntryTypeDecoderTest, HeaderWithIndex) { + std::vector<HpackEntryType> entry_types = { + HpackEntryType::kIndexedHeader, + HpackEntryType::kIndexedLiteralHeader, + HpackEntryType::kUnindexedLiteralHeader, + HpackEntryType::kNeverIndexedLiteralHeader, + }; + for (const HpackEntryType entry_type : entry_types) { + const uint32_t first = entry_type == HpackEntryType::kIndexedHeader ? 1 : 0; + for (uint32_t index = first; index < 1000; ++index) { + HpackBlockBuilder bb; + bb.AppendEntryTypeAndVarint(entry_type, index); + DecodeBuffer db(bb.buffer()); + auto validator = [entry_type, index, this]() -> AssertionResult { + VERIFY_EQ(entry_type, decoder_.entry_type()); + VERIFY_EQ(index, decoder_.varint()); + return AssertionSuccess(); + }; + EXPECT_TRUE(DecodeAndValidateSeveralWays(&db, kReturnNonZeroOnFirst, + ValidateDoneAndEmpty(validator))) + << "\nentry_type=" << entry_type << ", index=" << index; + // Run the validator again to make sure that DecodeAndValidateSeveralWays + // did the right thing. + EXPECT_TRUE(validator()); + } + } +} + +} // namespace +} // namespace test +} // namespace http2 diff --git a/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_string_collector.cc b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_string_collector.cc new file mode 100644 index 00000000000..247ce9c2a6a --- /dev/null +++ b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_string_collector.cc @@ -0,0 +1,123 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_string_collector.h" + +#include <stddef.h> + +#include <iosfwd> +#include <ostream> + +#include "testing/gtest/include/gtest/gtest.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string_utils.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_test_helpers.h" + +namespace http2 { +namespace test { +namespace { + +std::ostream& operator<<(std::ostream& out, + HpackStringCollector::CollectorState v) { + switch (v) { + case HpackStringCollector::CollectorState::kGenesis: + return out << "kGenesis"; + case HpackStringCollector::CollectorState::kStarted: + return out << "kStarted"; + case HpackStringCollector::CollectorState::kEnded: + return out << "kEnded"; + } + return out << "UnknownCollectorState"; +} + +} // namespace + +HpackStringCollector::HpackStringCollector() { + Clear(); +} + +HpackStringCollector::HpackStringCollector(const Http2String& str, bool huffman) + : s(str), len(str.size()), huffman_encoded(huffman), state(kEnded) {} + +void HpackStringCollector::Clear() { + s = ""; + len = 0; + huffman_encoded = false; + state = kGenesis; +} + +bool HpackStringCollector::IsClear() const { + return s.empty() && len == 0 && huffman_encoded == false && state == kGenesis; +} + +bool HpackStringCollector::IsInProgress() const { + return state == kStarted; +} + +bool HpackStringCollector::HasEnded() const { + return state == kEnded; +} + +void HpackStringCollector::OnStringStart(bool huffman, size_t length) { + EXPECT_TRUE(IsClear()) << ToString(); + state = kStarted; + huffman_encoded = huffman; + len = length; +} + +void HpackStringCollector::OnStringData(const char* data, size_t length) { + Http2StringPiece sp(data, length); + EXPECT_TRUE(IsInProgress()) << ToString(); + EXPECT_LE(sp.size(), len) << ToString(); + Http2StrAppend(&s, sp); + EXPECT_LE(s.size(), len) << ToString(); +} + +void HpackStringCollector::OnStringEnd() { + EXPECT_TRUE(IsInProgress()) << ToString(); + EXPECT_EQ(s.size(), len) << ToString(); + state = kEnded; +} + +::testing::AssertionResult HpackStringCollector::Collected( + Http2StringPiece str, + bool is_huffman_encoded) const { + VERIFY_TRUE(HasEnded()); + VERIFY_EQ(str.size(), len); + VERIFY_EQ(is_huffman_encoded, huffman_encoded); + VERIFY_EQ(str, s); + return ::testing::AssertionSuccess(); +} + +Http2String HpackStringCollector::ToString() const { + std::stringstream ss; + ss << *this; + return ss.str(); +} + +bool operator==(const HpackStringCollector& a, const HpackStringCollector& b) { + return a.s == b.s && a.len == b.len && + a.huffman_encoded == b.huffman_encoded && a.state == b.state; +} + +bool operator!=(const HpackStringCollector& a, const HpackStringCollector& b) { + return !(a == b); +} + +std::ostream& operator<<(std::ostream& out, const HpackStringCollector& v) { + out << "HpackStringCollector(state=" << v.state; + if (v.state == HpackStringCollector::kGenesis) { + return out << ")"; + } + if (v.huffman_encoded) { + out << ", Huffman Encoded"; + } + out << ", Length=" << v.len; + if (!v.s.empty() && v.len != v.s.size()) { + out << " (" << v.s.size() << ")"; + } + return out << ", String=\"" << Http2HexEscape(v.s) << "\")"; +} + +} // namespace test +} // namespace http2 diff --git a/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_string_collector.h b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_string_collector.h new file mode 100644 index 00000000000..76be13b743c --- /dev/null +++ b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_string_collector.h @@ -0,0 +1,63 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef QUICHE_HTTP2_HPACK_DECODER_HPACK_STRING_COLLECTOR_H_ +#define QUICHE_HTTP2_HPACK_DECODER_HPACK_STRING_COLLECTOR_H_ + +// Supports tests of decoding HPACK strings. + +#include <stddef.h> + +#include <iosfwd> + +#include "testing/gtest/include/gtest/gtest.h" +#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_string_decoder_listener.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string_piece.h" + +namespace http2 { +namespace test { + +// Records the callbacks associated with a decoding a string; must +// call Clear() between decoding successive strings. +struct HpackStringCollector : public HpackStringDecoderListener { + enum CollectorState { + kGenesis, + kStarted, + kEnded, + }; + + HpackStringCollector(); + HpackStringCollector(const Http2String& str, bool huffman); + + void Clear(); + bool IsClear() const; + bool IsInProgress() const; + bool HasEnded() const; + + void OnStringStart(bool huffman, size_t length) override; + void OnStringData(const char* data, size_t length) override; + void OnStringEnd() override; + + ::testing::AssertionResult Collected(Http2StringPiece str, + bool is_huffman_encoded) const; + + Http2String ToString() const; + + Http2String s; + size_t len; + bool huffman_encoded; + CollectorState state; +}; + +bool operator==(const HpackStringCollector& a, const HpackStringCollector& b); + +bool operator!=(const HpackStringCollector& a, const HpackStringCollector& b); + +std::ostream& operator<<(std::ostream& out, const HpackStringCollector& v); + +} // namespace test +} // namespace http2 + +#endif // QUICHE_HTTP2_HPACK_DECODER_HPACK_STRING_COLLECTOR_H_ diff --git a/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_string_decoder.cc b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_string_decoder.cc new file mode 100644 index 00000000000..0b9eb596835 --- /dev/null +++ b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_string_decoder.cc @@ -0,0 +1,35 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_string_decoder.h" + +#include "net/third_party/quiche/src/http2/platform/api/http2_string_utils.h" + +namespace http2 { + +Http2String HpackStringDecoder::DebugString() const { + return Http2StrCat("HpackStringDecoder(state=", StateToString(state_), + ", length=", length_decoder_.DebugString(), + ", remaining=", remaining_, + ", huffman=", huffman_encoded_ ? "true)" : "false)"); +} + +// static +Http2String HpackStringDecoder::StateToString(StringDecoderState v) { + switch (v) { + case kStartDecodingLength: + return "kStartDecodingLength"; + case kDecodingString: + return "kDecodingString"; + case kResumeDecodingLength: + return "kResumeDecodingLength"; + } + return Http2StrCat("UNKNOWN_STATE(", static_cast<uint32_t>(v), ")"); +} + +std::ostream& operator<<(std::ostream& out, const HpackStringDecoder& v) { + return out << v.DebugString(); +} + +} // namespace http2 diff --git a/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_string_decoder.h b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_string_decoder.h new file mode 100644 index 00000000000..2128728e9d1 --- /dev/null +++ b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_string_decoder.h @@ -0,0 +1,209 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef QUICHE_HTTP2_HPACK_DECODER_HPACK_STRING_DECODER_H_ +#define QUICHE_HTTP2_HPACK_DECODER_HPACK_STRING_DECODER_H_ + +// HpackStringDecoder decodes strings encoded per the HPACK spec; this does +// not mean decompressing Huffman encoded strings, just identifying the length, +// encoding and contents for a listener. + +#include <stddef.h> + +#include <algorithm> +#include <cstdint> + +#include "base/logging.h" +#include "base/macros.h" +#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h" +#include "net/third_party/quiche/src/http2/decoder/decode_status.h" +#include "net/third_party/quiche/src/http2/hpack/varint/hpack_varint_decoder.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_export.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_macros.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string.h" + +namespace http2 { + +// Decodes a single string in an HPACK header entry. The high order bit of +// the first byte of the length is the H (Huffman) bit indicating whether +// the value is Huffman encoded, and the remainder of the byte is the first +// 7 bits of an HPACK varint. +// +// Call Start() to begin decoding; if it returns kDecodeInProgress, then call +// Resume() when more input is available, repeating until kDecodeInProgress is +// not returned. If kDecodeDone or kDecodeError is returned, then Resume() must +// not be called until Start() has been called to start decoding a new string. +class HTTP2_EXPORT_PRIVATE HpackStringDecoder { + public: + enum StringDecoderState { + kStartDecodingLength, + kDecodingString, + kResumeDecodingLength, + }; + + template <class Listener> + DecodeStatus Start(DecodeBuffer* db, Listener* cb) { + // Fast decode path is used if the string is under 127 bytes and the + // entire length of the string is in the decode buffer. More than 83% of + // string lengths are encoded in just one byte. + if (db->HasData() && (*db->cursor() & 0x7f) != 0x7f) { + // The string is short. + uint8_t h_and_prefix = db->DecodeUInt8(); + uint8_t length = h_and_prefix & 0x7f; + bool huffman_encoded = (h_and_prefix & 0x80) == 0x80; + cb->OnStringStart(huffman_encoded, length); + if (length <= db->Remaining()) { + // Yeah, we've got the whole thing in the decode buffer. + // Ideally this will be the common case. Note that we don't + // update any of the member variables in this path. + cb->OnStringData(db->cursor(), length); + db->AdvanceCursor(length); + cb->OnStringEnd(); + return DecodeStatus::kDecodeDone; + } + // Not all in the buffer. + huffman_encoded_ = huffman_encoded; + remaining_ = length; + // Call Resume to decode the string body, which is only partially + // in the decode buffer (or not at all). + state_ = kDecodingString; + return Resume(db, cb); + } + // Call Resume to decode the string length, which is either not in + // the decode buffer, or spans multiple bytes. + state_ = kStartDecodingLength; + return Resume(db, cb); + } + + template <class Listener> + DecodeStatus Resume(DecodeBuffer* db, Listener* cb) { + DecodeStatus status; + while (true) { + switch (state_) { + case kStartDecodingLength: + DVLOG(2) << "kStartDecodingLength: db->Remaining=" << db->Remaining(); + if (!StartDecodingLength(db, cb, &status)) { + // The length is split across decode buffers. + return status; + } + // We've finished decoding the length, which spanned one or more + // bytes. Approximately 17% of strings have a length that is greater + // than 126 bytes, and thus the length is encoded in more than one + // byte, and so doesn't get the benefit of the optimization in + // Start() for single byte lengths. But, we still expect that most + // of such strings will be contained entirely in a single decode + // buffer, and hence this fall through skips another trip through the + // switch above and more importantly skips setting the state_ variable + // again in those cases where we don't need it. + HTTP2_FALLTHROUGH; + + case kDecodingString: + DVLOG(2) << "kDecodingString: db->Remaining=" << db->Remaining() + << " remaining_=" << remaining_; + return DecodeString(db, cb); + + case kResumeDecodingLength: + DVLOG(2) << "kResumeDecodingLength: db->Remaining=" + << db->Remaining(); + if (!ResumeDecodingLength(db, cb, &status)) { + return status; + } + } + } + } + + Http2String DebugString() const; + + private: + static Http2String StateToString(StringDecoderState v); + + // Returns true if the length is fully decoded and the listener wants the + // decoding to continue, false otherwise; status is set to the status from + // the varint decoder. + // If the length is not fully decoded, case state_ is set appropriately + // for the next call to Resume. + template <class Listener> + bool StartDecodingLength(DecodeBuffer* db, + Listener* cb, + DecodeStatus* status) { + if (db->Empty()) { + *status = DecodeStatus::kDecodeInProgress; + state_ = kStartDecodingLength; + return false; + } + uint8_t h_and_prefix = db->DecodeUInt8(); + huffman_encoded_ = (h_and_prefix & 0x80) == 0x80; + *status = length_decoder_.Start(h_and_prefix, 7, db); + if (*status == DecodeStatus::kDecodeDone) { + OnStringStart(cb, status); + return true; + } + // Set the state to cover the DecodeStatus::kDecodeInProgress case. + // Won't be needed if the status is kDecodeError. + state_ = kResumeDecodingLength; + return false; + } + + // Returns true if the length is fully decoded and the listener wants the + // decoding to continue, false otherwise; status is set to the status from + // the varint decoder; state_ is updated when fully decoded. + // If the length is not fully decoded, case state_ is set appropriately + // for the next call to Resume. + template <class Listener> + bool ResumeDecodingLength(DecodeBuffer* db, + Listener* cb, + DecodeStatus* status) { + DCHECK_EQ(state_, kResumeDecodingLength); + *status = length_decoder_.Resume(db); + if (*status == DecodeStatus::kDecodeDone) { + state_ = kDecodingString; + OnStringStart(cb, status); + return true; + } + return false; + } + + // Returns true if the listener wants the decoding to continue, and + // false otherwise, in which case status set. + template <class Listener> + void OnStringStart(Listener* cb, DecodeStatus* status) { + // TODO(vasilvv): fail explicitly in case of truncation. + remaining_ = static_cast<size_t>(length_decoder_.value()); + // Make callback so consumer knows what is coming. + cb->OnStringStart(huffman_encoded_, remaining_); + } + + // Passes the available portion of the string to the listener, and signals + // the end of the string when it is reached. Returns kDecodeDone or + // kDecodeInProgress as appropriate. + template <class Listener> + DecodeStatus DecodeString(DecodeBuffer* db, Listener* cb) { + size_t len = std::min(remaining_, db->Remaining()); + if (len > 0) { + cb->OnStringData(db->cursor(), len); + db->AdvanceCursor(len); + remaining_ -= len; + } + if (remaining_ == 0) { + cb->OnStringEnd(); + return DecodeStatus::kDecodeDone; + } + state_ = kDecodingString; + return DecodeStatus::kDecodeInProgress; + } + + HpackVarintDecoder length_decoder_; + + // These fields are initialized just to keep ASAN happy about reading + // them from DebugString(). + size_t remaining_ = 0; + StringDecoderState state_ = kStartDecodingLength; + bool huffman_encoded_ = false; +}; + +HTTP2_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& out, + const HpackStringDecoder& v); + +} // namespace http2 +#endif // QUICHE_HTTP2_HPACK_DECODER_HPACK_STRING_DECODER_H_ diff --git a/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_string_decoder_listener.cc b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_string_decoder_listener.cc new file mode 100644 index 00000000000..e0fbc657ef9 --- /dev/null +++ b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_string_decoder_listener.cc @@ -0,0 +1,36 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_string_decoder_listener.h" + +#include "base/logging.h" + +namespace http2 { +namespace test { + +void HpackStringDecoderVLoggingListener::OnStringStart(bool huffman_encoded, + size_t len) { + VLOG(1) << "OnStringStart: H=" << huffman_encoded << ", len=" << len; + if (wrapped_) { + wrapped_->OnStringStart(huffman_encoded, len); + } +} + +void HpackStringDecoderVLoggingListener::OnStringData(const char* data, + size_t len) { + VLOG(1) << "OnStringData: len=" << len; + if (wrapped_) { + return wrapped_->OnStringData(data, len); + } +} + +void HpackStringDecoderVLoggingListener::OnStringEnd() { + VLOG(1) << "OnStringEnd"; + if (wrapped_) { + return wrapped_->OnStringEnd(); + } +} + +} // namespace test +} // namespace http2 diff --git a/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_string_decoder_listener.h b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_string_decoder_listener.h new file mode 100644 index 00000000000..35a04170f99 --- /dev/null +++ b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_string_decoder_listener.h @@ -0,0 +1,62 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef QUICHE_HTTP2_HPACK_DECODER_HPACK_STRING_DECODER_LISTENER_H_ +#define QUICHE_HTTP2_HPACK_DECODER_HPACK_STRING_DECODER_LISTENER_H_ + +// Defines HpackStringDecoderListener which defines the methods required by an +// HpackStringDecoder. Also defines HpackStringDecoderVLoggingListener which +// logs before calling another HpackStringDecoderListener implementation. +// For now these are only used by tests, so placed in the test namespace. + +#include <stddef.h> + +#include "net/third_party/quiche/src/http2/platform/api/http2_export.h" + +namespace http2 { +namespace test { + +// HpackStringDecoder methods require a listener that implements the methods +// below, but it is NOT necessary to extend this class because the methods +// are templates. +class HTTP2_EXPORT_PRIVATE HpackStringDecoderListener { + public: + virtual ~HpackStringDecoderListener() {} + + // Called at the start of decoding an HPACK string. The encoded length of the + // string is |len| bytes, which may be zero. The string is Huffman encoded + // if huffman_encoded is true, else it is plain text (i.e. the encoded length + // is then the plain text length). + virtual void OnStringStart(bool huffman_encoded, size_t len) = 0; + + // Called when some data is available, or once when the string length is zero + // (to simplify the decoder, it doesn't have a special case for len==0). + virtual void OnStringData(const char* data, size_t len) = 0; + + // Called after OnStringData has provided all of the encoded bytes of the + // string. + virtual void OnStringEnd() = 0; +}; + +class HTTP2_EXPORT_PRIVATE HpackStringDecoderVLoggingListener + : public HpackStringDecoderListener { + public: + HpackStringDecoderVLoggingListener() : wrapped_(nullptr) {} + explicit HpackStringDecoderVLoggingListener( + HpackStringDecoderListener* wrapped) + : wrapped_(wrapped) {} + ~HpackStringDecoderVLoggingListener() override {} + + void OnStringStart(bool huffman_encoded, size_t len) override; + void OnStringData(const char* data, size_t len) override; + void OnStringEnd() override; + + private: + HpackStringDecoderListener* const wrapped_; +}; + +} // namespace test +} // namespace http2 + +#endif // QUICHE_HTTP2_HPACK_DECODER_HPACK_STRING_DECODER_LISTENER_H_ diff --git a/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_string_decoder_test.cc b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_string_decoder_test.cc new file mode 100644 index 00000000000..349b649d211 --- /dev/null +++ b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_string_decoder_test.cc @@ -0,0 +1,155 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_string_decoder.h" + +// Tests of HpackStringDecoder. + +#include "testing/gtest/include/gtest/gtest.h" +#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_string_collector.h" +#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_string_decoder_listener.h" +#include "net/third_party/quiche/src/http2/hpack/tools/hpack_block_builder.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string_piece.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_test_helpers.h" +#include "net/third_party/quiche/src/http2/test_tools/http2_random.h" +#include "net/third_party/quiche/src/http2/tools/random_decoder_test.h" + +using ::testing::AssertionResult; + +namespace http2 { +namespace test { +namespace { + +const bool kMayReturnZeroOnFirst = false; +const bool kCompressed = true; +const bool kUncompressed = false; + +class HpackStringDecoderTest : public RandomDecoderTest { + protected: + HpackStringDecoderTest() : listener_(&collector_) {} + + DecodeStatus StartDecoding(DecodeBuffer* b) override { + ++start_decoding_calls_; + collector_.Clear(); + return decoder_.Start(b, &listener_); + } + + DecodeStatus ResumeDecoding(DecodeBuffer* b) override { + // Provides coverage of DebugString and StateToString. + // Not validating output. + VLOG(1) << decoder_.DebugString(); + VLOG(2) << collector_; + return decoder_.Resume(b, &listener_); + } + + AssertionResult Collected(Http2StringPiece s, bool huffman_encoded) { + VLOG(1) << collector_; + return collector_.Collected(s, huffman_encoded); + } + + // expected_str is a Http2String rather than a const Http2String& or + // Http2StringPiece so that the lambda makes a copy of the string, and thus + // the string to be passed to Collected outlives the call to MakeValidator. + Validator MakeValidator(const Http2String& expected_str, + bool expected_huffman) { + return + [expected_str, expected_huffman, this]( + const DecodeBuffer& input, DecodeStatus status) -> AssertionResult { + AssertionResult result = Collected(expected_str, expected_huffman); + if (result) { + VERIFY_EQ(collector_, + HpackStringCollector(expected_str, expected_huffman)); + } else { + VERIFY_NE(collector_, + HpackStringCollector(expected_str, expected_huffman)); + } + VLOG(2) << collector_.ToString(); + collector_.Clear(); + VLOG(2) << collector_; + return result; + }; + } + + HpackStringDecoder decoder_; + HpackStringCollector collector_; + HpackStringDecoderVLoggingListener listener_; + size_t start_decoding_calls_ = 0; +}; + +TEST_F(HpackStringDecoderTest, DecodeEmptyString) { + { + Validator validator = ValidateDoneAndEmpty(MakeValidator("", kCompressed)); + const char kData[] = {'\x80'}; + DecodeBuffer b(kData); + EXPECT_TRUE( + DecodeAndValidateSeveralWays(&b, kMayReturnZeroOnFirst, validator)); + } + { + // Make sure it stops after decoding the empty string. + Validator validator = + ValidateDoneAndOffset(1, MakeValidator("", kUncompressed)); + const char kData[] = {'\x00', '\xff'}; + DecodeBuffer b(kData); + EXPECT_EQ(2u, b.Remaining()); + EXPECT_TRUE( + DecodeAndValidateSeveralWays(&b, kMayReturnZeroOnFirst, validator)); + EXPECT_EQ(1u, b.Remaining()); + } +} + +TEST_F(HpackStringDecoderTest, DecodeShortString) { + { + // Make sure it stops after decoding the non-empty string. + Validator validator = + ValidateDoneAndOffset(11, MakeValidator("start end.", kCompressed)); + const char kData[] = "\x8astart end.Don't peek at this."; + DecodeBuffer b(kData); + EXPECT_TRUE( + DecodeAndValidateSeveralWays(&b, kMayReturnZeroOnFirst, validator)); + } + { + Validator validator = + ValidateDoneAndOffset(11, MakeValidator("start end.", kUncompressed)); + Http2StringPiece data("\x0astart end."); + DecodeBuffer b(data); + EXPECT_TRUE( + DecodeAndValidateSeveralWays(&b, kMayReturnZeroOnFirst, validator)); + } +} + +TEST_F(HpackStringDecoderTest, DecodeLongStrings) { + Http2String name = Random().RandString(1024); + Http2String value = Random().RandString(65536); + HpackBlockBuilder hbb; + + hbb.AppendString(false, name); + uint32_t offset_after_name = hbb.size(); + EXPECT_EQ(3 + name.size(), offset_after_name); + + hbb.AppendString(true, value); + uint32_t offset_after_value = hbb.size(); + EXPECT_EQ(3 + name.size() + 4 + value.size(), offset_after_value); + + DecodeBuffer b(hbb.buffer()); + + // Decode the name... + EXPECT_TRUE(DecodeAndValidateSeveralWays( + &b, kMayReturnZeroOnFirst, + ValidateDoneAndOffset(offset_after_name, + MakeValidator(name, kUncompressed)))); + EXPECT_EQ(offset_after_name, b.Offset()); + EXPECT_EQ(offset_after_value - offset_after_name, b.Remaining()); + + // Decode the value... + EXPECT_TRUE(DecodeAndValidateSeveralWays( + &b, kMayReturnZeroOnFirst, + ValidateDoneAndOffset(offset_after_value - offset_after_name, + MakeValidator(value, kCompressed)))); + EXPECT_EQ(offset_after_value, b.Offset()); + EXPECT_EQ(0u, b.Remaining()); +} + +} // namespace +} // namespace test +} // namespace http2 diff --git a/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_whole_entry_buffer.cc b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_whole_entry_buffer.cc new file mode 100644 index 00000000000..b3e29b176cc --- /dev/null +++ b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_whole_entry_buffer.cc @@ -0,0 +1,139 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_whole_entry_buffer.h" + +#include "base/logging.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_estimate_memory_usage.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_macros.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string_utils.h" + +namespace http2 { + +HpackWholeEntryBuffer::HpackWholeEntryBuffer(HpackWholeEntryListener* listener, + size_t max_string_size_bytes) + : max_string_size_bytes_(max_string_size_bytes) { + set_listener(listener); +} +HpackWholeEntryBuffer::~HpackWholeEntryBuffer() = default; + +void HpackWholeEntryBuffer::set_listener(HpackWholeEntryListener* listener) { + listener_ = HTTP2_DIE_IF_NULL(listener); +} + +void HpackWholeEntryBuffer::set_max_string_size_bytes( + size_t max_string_size_bytes) { + max_string_size_bytes_ = max_string_size_bytes; +} + +void HpackWholeEntryBuffer::BufferStringsIfUnbuffered() { + name_.BufferStringIfUnbuffered(); + value_.BufferStringIfUnbuffered(); +} + +size_t HpackWholeEntryBuffer::EstimateMemoryUsage() const { + return Http2EstimateMemoryUsage(name_) + Http2EstimateMemoryUsage(value_); +} + +void HpackWholeEntryBuffer::OnIndexedHeader(size_t index) { + DVLOG(2) << "HpackWholeEntryBuffer::OnIndexedHeader: index=" << index; + listener_->OnIndexedHeader(index); +} + +void HpackWholeEntryBuffer::OnStartLiteralHeader(HpackEntryType entry_type, + size_t maybe_name_index) { + DVLOG(2) << "HpackWholeEntryBuffer::OnStartLiteralHeader: entry_type=" + << entry_type << ", maybe_name_index=" << maybe_name_index; + entry_type_ = entry_type; + maybe_name_index_ = maybe_name_index; +} + +void HpackWholeEntryBuffer::OnNameStart(bool huffman_encoded, size_t len) { + DVLOG(2) << "HpackWholeEntryBuffer::OnNameStart: huffman_encoded=" + << (huffman_encoded ? "true" : "false") << ", len=" << len; + DCHECK_EQ(maybe_name_index_, 0u); + if (!error_detected_) { + if (len > max_string_size_bytes_) { + DVLOG(1) << "Name length (" << len << ") is longer than permitted (" + << max_string_size_bytes_ << ")"; + ReportError("HPACK entry name size is too long."); + return; + } + name_.OnStart(huffman_encoded, len); + } +} + +void HpackWholeEntryBuffer::OnNameData(const char* data, size_t len) { + DVLOG(2) << "HpackWholeEntryBuffer::OnNameData: len=" << len << " data:\n" + << Http2HexDump(Http2StringPiece(data, len)); + DCHECK_EQ(maybe_name_index_, 0u); + if (!error_detected_ && !name_.OnData(data, len)) { + ReportError("Error decoding HPACK entry name."); + } +} + +void HpackWholeEntryBuffer::OnNameEnd() { + DVLOG(2) << "HpackWholeEntryBuffer::OnNameEnd"; + DCHECK_EQ(maybe_name_index_, 0u); + if (!error_detected_ && !name_.OnEnd()) { + ReportError("Error decoding HPACK entry name."); + } +} + +void HpackWholeEntryBuffer::OnValueStart(bool huffman_encoded, size_t len) { + DVLOG(2) << "HpackWholeEntryBuffer::OnValueStart: huffman_encoded=" + << (huffman_encoded ? "true" : "false") << ", len=" << len; + if (!error_detected_) { + if (len > max_string_size_bytes_) { + DVLOG(1) << "Value length (" << len << ") is longer than permitted (" + << max_string_size_bytes_ << ")"; + ReportError("HPACK entry value size is too long."); + return; + } + value_.OnStart(huffman_encoded, len); + } +} + +void HpackWholeEntryBuffer::OnValueData(const char* data, size_t len) { + DVLOG(2) << "HpackWholeEntryBuffer::OnValueData: len=" << len << " data:\n" + << Http2HexDump(Http2StringPiece(data, len)); + if (!error_detected_ && !value_.OnData(data, len)) { + ReportError("Error decoding HPACK entry value."); + } +} + +void HpackWholeEntryBuffer::OnValueEnd() { + DVLOG(2) << "HpackWholeEntryBuffer::OnValueEnd"; + if (error_detected_) { + return; + } + if (!value_.OnEnd()) { + ReportError("Error decoding HPACK entry value."); + return; + } + if (maybe_name_index_ == 0) { + listener_->OnLiteralNameAndValue(entry_type_, &name_, &value_); + name_.Reset(); + } else { + listener_->OnNameIndexAndLiteralValue(entry_type_, maybe_name_index_, + &value_); + } + value_.Reset(); +} + +void HpackWholeEntryBuffer::OnDynamicTableSizeUpdate(size_t size) { + DVLOG(2) << "HpackWholeEntryBuffer::OnDynamicTableSizeUpdate: size=" << size; + listener_->OnDynamicTableSizeUpdate(size); +} + +void HpackWholeEntryBuffer::ReportError(Http2StringPiece error_message) { + if (!error_detected_) { + DVLOG(1) << "HpackWholeEntryBuffer::ReportError: " << error_message; + error_detected_ = true; + listener_->OnHpackDecodeError(error_message); + listener_ = HpackWholeEntryNoOpListener::NoOpListener(); + } +} + +} // namespace http2 diff --git a/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_whole_entry_buffer.h b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_whole_entry_buffer.h new file mode 100644 index 00000000000..61bf58350ed --- /dev/null +++ b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_whole_entry_buffer.h @@ -0,0 +1,104 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef QUICHE_HTTP2_HPACK_DECODER_HPACK_WHOLE_ENTRY_BUFFER_H_ +#define QUICHE_HTTP2_HPACK_DECODER_HPACK_WHOLE_ENTRY_BUFFER_H_ + +// HpackWholeEntryBuffer isolates a listener from the fact that an entry may +// be split across multiple input buffers, providing one callback per entry. +// HpackWholeEntryBuffer requires that the HpackEntryDecoderListener be made in +// the correct order, which is tested by hpack_entry_decoder_test.cc. + +#include <stddef.h> + +#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_string_buffer.h" +#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_decoder_listener.h" +#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_whole_entry_listener.h" +#include "net/third_party/quiche/src/http2/hpack/http2_hpack_constants.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_export.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string_piece.h" + +namespace http2 { + +// TODO(jamessynge): Consider renaming HpackEntryDecoderListener to +// HpackEntryPartsListener or HpackEntryFragmentsListener. +class HTTP2_EXPORT_PRIVATE HpackWholeEntryBuffer + : public HpackEntryDecoderListener { + public: + // max_string_size specifies the maximum size of an on-the-wire string (name + // or value, plain or Huffman encoded) that will be accepted. See sections + // 5.1 and 5.2 of RFC 7541. This is a defense against OOM attacks; HTTP/2 + // allows a decoder to enforce any limit of the size of the header lists + // that it is willing decode, including less than the MAX_HEADER_LIST_SIZE + // setting, a setting that is initially unlimited. For example, we might + // choose to send a MAX_HEADER_LIST_SIZE of 64KB, and to use that same value + // as the upper bound for individual strings. + HpackWholeEntryBuffer(HpackWholeEntryListener* listener, + size_t max_string_size); + ~HpackWholeEntryBuffer() override; + + HpackWholeEntryBuffer(const HpackWholeEntryBuffer&) = delete; + HpackWholeEntryBuffer& operator=(const HpackWholeEntryBuffer&) = delete; + + // Set the listener to be notified when a whole entry has been decoded. + // The listener may be changed at any time. + void set_listener(HpackWholeEntryListener* listener); + + // Set how much encoded data this decoder is willing to buffer. + // TODO(jamessynge): Come up with consistent semantics for this protection + // across the various decoders; e.g. should it be for a single string or + // a single header entry? + void set_max_string_size_bytes(size_t max_string_size_bytes); + + // Ensure that decoded strings pointed to by the HpackDecoderStringBuffer + // instances name_ and value_ are buffered, which allows any underlying + // transport buffer to be freed or reused without overwriting the decoded + // strings. This is needed only when an HPACK entry is split across transport + // buffers. See HpackDecoder::DecodeFragment. + void BufferStringsIfUnbuffered(); + + // Was an error detected? After an error has been detected and reported, + // no further callbacks will be made to the listener. + bool error_detected() const { return error_detected_; } + + // Returns the estimate of dynamically allocated memory in bytes. + size_t EstimateMemoryUsage() const; + + // Implement the HpackEntryDecoderListener methods. + + void OnIndexedHeader(size_t index) override; + void OnStartLiteralHeader(HpackEntryType entry_type, + size_t maybe_name_index) override; + void OnNameStart(bool huffman_encoded, size_t len) override; + void OnNameData(const char* data, size_t len) override; + void OnNameEnd() override; + void OnValueStart(bool huffman_encoded, size_t len) override; + void OnValueData(const char* data, size_t len) override; + void OnValueEnd() override; + void OnDynamicTableSizeUpdate(size_t size) override; + + private: + void ReportError(Http2StringPiece error_message); + + HpackWholeEntryListener* listener_; + HpackDecoderStringBuffer name_, value_; + + // max_string_size_bytes_ specifies the maximum allowed size of an on-the-wire + // string. Larger strings will be reported as errors to the listener; the + // endpoint should treat these as COMPRESSION errors, which are CONNECTION + // level errors. + size_t max_string_size_bytes_; + + // The name index (or zero) of the current header entry with a literal value. + size_t maybe_name_index_; + + // The type of the current header entry (with literals) that is being decoded. + HpackEntryType entry_type_; + + bool error_detected_ = false; +}; + +} // namespace http2 + +#endif // QUICHE_HTTP2_HPACK_DECODER_HPACK_WHOLE_ENTRY_BUFFER_H_ diff --git a/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_whole_entry_buffer_test.cc b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_whole_entry_buffer_test.cc new file mode 100644 index 00000000000..75b281c062d --- /dev/null +++ b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_whole_entry_buffer_test.cc @@ -0,0 +1,206 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_whole_entry_buffer.h" + +// Tests of HpackWholeEntryBuffer: does it buffer correctly, and does it +// detect Huffman decoding errors and oversize string errors? + +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +using ::testing::AllOf; +using ::testing::HasSubstr; +using ::testing::InSequence; +using ::testing::Property; +using ::testing::StrictMock; + +namespace http2 { +namespace test { +namespace { + +constexpr size_t kMaxStringSize = 20; + +class MockHpackWholeEntryListener : public HpackWholeEntryListener { + public: + ~MockHpackWholeEntryListener() override = default; + + MOCK_METHOD1(OnIndexedHeader, void(size_t index)); + MOCK_METHOD3(OnNameIndexAndLiteralValue, + void(HpackEntryType entry_type, + size_t name_index, + HpackDecoderStringBuffer* value_buffer)); + MOCK_METHOD3(OnLiteralNameAndValue, + void(HpackEntryType entry_type, + HpackDecoderStringBuffer* name_buffer, + HpackDecoderStringBuffer* value_buffer)); + MOCK_METHOD1(OnDynamicTableSizeUpdate, void(size_t size)); + MOCK_METHOD1(OnHpackDecodeError, void(Http2StringPiece error_message)); +}; + +class HpackWholeEntryBufferTest : public ::testing::Test { + protected: + HpackWholeEntryBufferTest() : entry_buffer_(&listener_, kMaxStringSize) {} + ~HpackWholeEntryBufferTest() override = default; + + StrictMock<MockHpackWholeEntryListener> listener_; + HpackWholeEntryBuffer entry_buffer_; +}; + +// OnIndexedHeader is an immediate pass through. +TEST_F(HpackWholeEntryBufferTest, OnIndexedHeader) { + { + InSequence seq; + EXPECT_CALL(listener_, OnIndexedHeader(17)); + entry_buffer_.OnIndexedHeader(17); + } + { + InSequence seq; + EXPECT_CALL(listener_, OnIndexedHeader(62)); + entry_buffer_.OnIndexedHeader(62); + } + { + InSequence seq; + EXPECT_CALL(listener_, OnIndexedHeader(62)); + entry_buffer_.OnIndexedHeader(62); + } + { + InSequence seq; + EXPECT_CALL(listener_, OnIndexedHeader(128)); + entry_buffer_.OnIndexedHeader(128); + } + StrictMock<MockHpackWholeEntryListener> listener2; + entry_buffer_.set_listener(&listener2); + { + InSequence seq; + EXPECT_CALL(listener2, OnIndexedHeader(100)); + entry_buffer_.OnIndexedHeader(100); + } +} + +// OnDynamicTableSizeUpdate is an immediate pass through. +TEST_F(HpackWholeEntryBufferTest, OnDynamicTableSizeUpdate) { + { + InSequence seq; + EXPECT_CALL(listener_, OnDynamicTableSizeUpdate(4096)); + entry_buffer_.OnDynamicTableSizeUpdate(4096); + } + { + InSequence seq; + EXPECT_CALL(listener_, OnDynamicTableSizeUpdate(0)); + entry_buffer_.OnDynamicTableSizeUpdate(0); + } + { + InSequence seq; + EXPECT_CALL(listener_, OnDynamicTableSizeUpdate(1024)); + entry_buffer_.OnDynamicTableSizeUpdate(1024); + } + { + InSequence seq; + EXPECT_CALL(listener_, OnDynamicTableSizeUpdate(1024)); + entry_buffer_.OnDynamicTableSizeUpdate(1024); + } + StrictMock<MockHpackWholeEntryListener> listener2; + entry_buffer_.set_listener(&listener2); + { + InSequence seq; + EXPECT_CALL(listener2, OnDynamicTableSizeUpdate(0)); + entry_buffer_.OnDynamicTableSizeUpdate(0); + } +} + +TEST_F(HpackWholeEntryBufferTest, OnNameIndexAndLiteralValue) { + entry_buffer_.OnStartLiteralHeader(HpackEntryType::kNeverIndexedLiteralHeader, + 123); + entry_buffer_.OnValueStart(false, 10); + entry_buffer_.OnValueData("some data.", 10); + + // Force the value to be buffered. + entry_buffer_.BufferStringsIfUnbuffered(); + + EXPECT_CALL( + listener_, + OnNameIndexAndLiteralValue( + HpackEntryType::kNeverIndexedLiteralHeader, 123, + AllOf(Property(&HpackDecoderStringBuffer::str, "some data."), + Property(&HpackDecoderStringBuffer::BufferedLength, 10)))); + + entry_buffer_.OnValueEnd(); +} + +TEST_F(HpackWholeEntryBufferTest, OnLiteralNameAndValue) { + entry_buffer_.OnStartLiteralHeader(HpackEntryType::kIndexedLiteralHeader, 0); + // Force the name to be buffered by delivering it in two pieces. + entry_buffer_.OnNameStart(false, 9); + entry_buffer_.OnNameData("some-", 5); + entry_buffer_.OnNameData("name", 4); + entry_buffer_.OnNameEnd(); + entry_buffer_.OnValueStart(false, 12); + entry_buffer_.OnValueData("Header Value", 12); + + EXPECT_CALL( + listener_, + OnLiteralNameAndValue( + HpackEntryType::kIndexedLiteralHeader, + AllOf(Property(&HpackDecoderStringBuffer::str, "some-name"), + Property(&HpackDecoderStringBuffer::BufferedLength, 9)), + AllOf(Property(&HpackDecoderStringBuffer::str, "Header Value"), + Property(&HpackDecoderStringBuffer::BufferedLength, 0)))); + + entry_buffer_.OnValueEnd(); +} + +// Verify that a name longer than the allowed size generates an error. +TEST_F(HpackWholeEntryBufferTest, NameTooLong) { + entry_buffer_.OnStartLiteralHeader(HpackEntryType::kIndexedLiteralHeader, 0); + EXPECT_CALL(listener_, OnHpackDecodeError(HasSubstr("HPACK entry name"))); + entry_buffer_.OnNameStart(false, kMaxStringSize + 1); +} + +// Verify that a name longer than the allowed size generates an error. +TEST_F(HpackWholeEntryBufferTest, ValueTooLong) { + entry_buffer_.OnStartLiteralHeader(HpackEntryType::kIndexedLiteralHeader, 1); + EXPECT_CALL(listener_, OnHpackDecodeError(HasSubstr("HPACK entry value"))); + entry_buffer_.OnValueStart(false, kMaxStringSize + 1); +} + +// Verify that a Huffman encoded name with an explicit EOS generates an error +// for an explicit EOS. +TEST_F(HpackWholeEntryBufferTest, NameHuffmanError) { + const char data[] = "\xff\xff\xff"; + entry_buffer_.OnStartLiteralHeader(HpackEntryType::kUnindexedLiteralHeader, + 0); + entry_buffer_.OnNameStart(true, 4); + entry_buffer_.OnNameData(data, 3); + + EXPECT_CALL(listener_, OnHpackDecodeError(HasSubstr("HPACK entry name"))); + + entry_buffer_.OnNameData(data, 1); + + // After an error is reported, the listener is not called again. + EXPECT_CALL(listener_, OnDynamicTableSizeUpdate(8096)).Times(0); + entry_buffer_.OnDynamicTableSizeUpdate(8096); +} + +// Verify that a Huffman encoded value that isn't properly terminated with +// a partial EOS symbol generates an error. +TEST_F(HpackWholeEntryBufferTest, ValueeHuffmanError) { + const char data[] = "\x00\x00\x00"; + entry_buffer_.OnStartLiteralHeader(HpackEntryType::kNeverIndexedLiteralHeader, + 61); + entry_buffer_.OnValueStart(true, 3); + entry_buffer_.OnValueData(data, 3); + + EXPECT_CALL(listener_, OnHpackDecodeError(HasSubstr("HPACK entry value"))); + + entry_buffer_.OnValueEnd(); + + // After an error is reported, the listener is not called again. + EXPECT_CALL(listener_, OnIndexedHeader(17)).Times(0); + entry_buffer_.OnIndexedHeader(17); +} + +} // namespace +} // namespace test +} // namespace http2 diff --git a/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_whole_entry_listener.cc b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_whole_entry_listener.cc new file mode 100644 index 00000000000..b92e64a7887 --- /dev/null +++ b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_whole_entry_listener.cc @@ -0,0 +1,33 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_whole_entry_listener.h" + +namespace http2 { + +HpackWholeEntryListener::~HpackWholeEntryListener() = default; + +HpackWholeEntryNoOpListener::~HpackWholeEntryNoOpListener() = default; + +void HpackWholeEntryNoOpListener::OnIndexedHeader(size_t index) {} +void HpackWholeEntryNoOpListener::OnNameIndexAndLiteralValue( + HpackEntryType entry_type, + size_t name_index, + HpackDecoderStringBuffer* value_buffer) {} +void HpackWholeEntryNoOpListener::OnLiteralNameAndValue( + HpackEntryType entry_type, + HpackDecoderStringBuffer* name_buffer, + HpackDecoderStringBuffer* value_buffer) {} +void HpackWholeEntryNoOpListener::OnDynamicTableSizeUpdate(size_t size) {} +void HpackWholeEntryNoOpListener::OnHpackDecodeError( + Http2StringPiece error_message) {} + +// static +HpackWholeEntryNoOpListener* HpackWholeEntryNoOpListener::NoOpListener() { + static HpackWholeEntryNoOpListener* static_instance = + new HpackWholeEntryNoOpListener(); + return static_instance; +} + +} // namespace http2 diff --git a/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_whole_entry_listener.h b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_whole_entry_listener.h new file mode 100644 index 00000000000..2e559cef4d7 --- /dev/null +++ b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_whole_entry_listener.h @@ -0,0 +1,80 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Defines HpackWholeEntryListener, the base class of listeners for decoded +// complete HPACK entries, as opposed to HpackEntryDecoderListener which +// receives multiple callbacks for some single entries. + +#ifndef QUICHE_HTTP2_HPACK_DECODER_HPACK_WHOLE_ENTRY_LISTENER_H_ +#define QUICHE_HTTP2_HPACK_DECODER_HPACK_WHOLE_ENTRY_LISTENER_H_ + +#include <stddef.h> + +#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_string_buffer.h" +#include "net/third_party/quiche/src/http2/hpack/http2_hpack_constants.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_export.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string_piece.h" + +namespace http2 { + +class HTTP2_EXPORT_PRIVATE HpackWholeEntryListener { + public: + virtual ~HpackWholeEntryListener(); + + // Called when an indexed header (i.e. one in the static or dynamic table) has + // been decoded from an HPACK block. index is supposed to be non-zero, but + // that has not been checked by the caller. + virtual void OnIndexedHeader(size_t index) = 0; + + // Called when a header entry with a name index and literal value has + // been fully decoded from an HPACK block. name_index is NOT zero. + // entry_type will be kIndexedLiteralHeader, kUnindexedLiteralHeader, or + // kNeverIndexedLiteralHeader. + virtual void OnNameIndexAndLiteralValue( + HpackEntryType entry_type, + size_t name_index, + HpackDecoderStringBuffer* value_buffer) = 0; + + // Called when a header entry with a literal name and literal value + // has been fully decoded from an HPACK block. entry_type will be + // kIndexedLiteralHeader, kUnindexedLiteralHeader, or + // kNeverIndexedLiteralHeader. + virtual void OnLiteralNameAndValue( + HpackEntryType entry_type, + HpackDecoderStringBuffer* name_buffer, + HpackDecoderStringBuffer* value_buffer) = 0; + + // Called when an update to the size of the peer's dynamic table has been + // decoded. + virtual void OnDynamicTableSizeUpdate(size_t size) = 0; + + // OnHpackDecodeError is called if an error is detected while decoding. + // error_message may be used in a GOAWAY frame as the Opaque Data. + virtual void OnHpackDecodeError(Http2StringPiece error_message) = 0; +}; + +// A no-op implementation of HpackWholeEntryDecoderListener, useful for ignoring +// callbacks once an error is detected. +class HpackWholeEntryNoOpListener : public HpackWholeEntryListener { + public: + ~HpackWholeEntryNoOpListener() override; + + void OnIndexedHeader(size_t index) override; + void OnNameIndexAndLiteralValue( + HpackEntryType entry_type, + size_t name_index, + HpackDecoderStringBuffer* value_buffer) override; + void OnLiteralNameAndValue(HpackEntryType entry_type, + HpackDecoderStringBuffer* name_buffer, + HpackDecoderStringBuffer* value_buffer) override; + void OnDynamicTableSizeUpdate(size_t size) override; + void OnHpackDecodeError(Http2StringPiece error_message) override; + + // Returns a listener that ignores all the calls. + static HpackWholeEntryNoOpListener* NoOpListener(); +}; + +} // namespace http2 + +#endif // QUICHE_HTTP2_HPACK_DECODER_HPACK_WHOLE_ENTRY_LISTENER_H_ diff --git a/chromium/net/third_party/quiche/src/http2/hpack/hpack_static_table_entries.inc b/chromium/net/third_party/quiche/src/http2/hpack/hpack_static_table_entries.inc new file mode 100644 index 00000000000..c6ae125f3b2 --- /dev/null +++ b/chromium/net/third_party/quiche/src/http2/hpack/hpack_static_table_entries.inc @@ -0,0 +1,65 @@ +// This file is designed to be included by C/C++ files which need the contents +// of the HPACK static table. It may be included more than once if necessary. +// See http://httpwg.org/specs/rfc7541.html#static.table.definition + +STATIC_TABLE_ENTRY(":authority", "", 1); +STATIC_TABLE_ENTRY(":method", "GET", 2); +STATIC_TABLE_ENTRY(":method", "POST", 3); +STATIC_TABLE_ENTRY(":path", "/", 4); +STATIC_TABLE_ENTRY(":path", "/index.html", 5); +STATIC_TABLE_ENTRY(":scheme", "http", 6); +STATIC_TABLE_ENTRY(":scheme", "https", 7); +STATIC_TABLE_ENTRY(":status", "200", 8); +STATIC_TABLE_ENTRY(":status", "204", 9); +STATIC_TABLE_ENTRY(":status", "206", 10); +STATIC_TABLE_ENTRY(":status", "304", 11); +STATIC_TABLE_ENTRY(":status", "400", 12); +STATIC_TABLE_ENTRY(":status", "404", 13); +STATIC_TABLE_ENTRY(":status", "500", 14); +STATIC_TABLE_ENTRY("accept-charset", "", 15); +STATIC_TABLE_ENTRY("accept-encoding", "gzip, deflate", 16); +STATIC_TABLE_ENTRY("accept-language", "", 17); +STATIC_TABLE_ENTRY("accept-ranges", "", 18); +STATIC_TABLE_ENTRY("accept", "", 19); +STATIC_TABLE_ENTRY("access-control-allow-origin", "", 20); +STATIC_TABLE_ENTRY("age", "", 21); +STATIC_TABLE_ENTRY("allow", "", 22); +STATIC_TABLE_ENTRY("authorization", "", 23); +STATIC_TABLE_ENTRY("cache-control", "", 24); +STATIC_TABLE_ENTRY("content-disposition", "", 25); +STATIC_TABLE_ENTRY("content-encoding", "", 26); +STATIC_TABLE_ENTRY("content-language", "", 27); +STATIC_TABLE_ENTRY("content-length", "", 28); +STATIC_TABLE_ENTRY("content-location", "", 29); +STATIC_TABLE_ENTRY("content-range", "", 30); +STATIC_TABLE_ENTRY("content-type", "", 31); +STATIC_TABLE_ENTRY("cookie", "", 32); +STATIC_TABLE_ENTRY("date", "", 33); +STATIC_TABLE_ENTRY("etag", "", 34); +STATIC_TABLE_ENTRY("expect", "", 35); +STATIC_TABLE_ENTRY("expires", "", 36); +STATIC_TABLE_ENTRY("from", "", 37); +STATIC_TABLE_ENTRY("host", "", 38); +STATIC_TABLE_ENTRY("if-match", "", 39); +STATIC_TABLE_ENTRY("if-modified-since", "", 40); +STATIC_TABLE_ENTRY("if-none-match", "", 41); +STATIC_TABLE_ENTRY("if-range", "", 42); +STATIC_TABLE_ENTRY("if-unmodified-since", "", 43); +STATIC_TABLE_ENTRY("last-modified", "", 44); +STATIC_TABLE_ENTRY("link", "", 45); +STATIC_TABLE_ENTRY("location", "", 46); +STATIC_TABLE_ENTRY("max-forwards", "", 47); +STATIC_TABLE_ENTRY("proxy-authenticate", "", 48); +STATIC_TABLE_ENTRY("proxy-authorization", "", 49); +STATIC_TABLE_ENTRY("range", "", 50); +STATIC_TABLE_ENTRY("referer", "", 51); +STATIC_TABLE_ENTRY("refresh", "", 52); +STATIC_TABLE_ENTRY("retry-after", "", 53); +STATIC_TABLE_ENTRY("server", "", 54); +STATIC_TABLE_ENTRY("set-cookie", "", 55); +STATIC_TABLE_ENTRY("strict-transport-security", "", 56); +STATIC_TABLE_ENTRY("transfer-encoding", "", 57); +STATIC_TABLE_ENTRY("user-agent", "", 58); +STATIC_TABLE_ENTRY("vary", "", 59); +STATIC_TABLE_ENTRY("via", "", 60); +STATIC_TABLE_ENTRY("www-authenticate", "", 61); diff --git a/chromium/net/third_party/quiche/src/http2/hpack/hpack_string.cc b/chromium/net/third_party/quiche/src/http2/hpack/hpack_string.cc new file mode 100644 index 00000000000..58bb821810e --- /dev/null +++ b/chromium/net/third_party/quiche/src/http2/hpack/hpack_string.cc @@ -0,0 +1,72 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/http2/hpack/hpack_string.h" + +#include <utility> + +#include "base/logging.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string_utils.h" + +namespace http2 { + +HpackString::HpackString(const char* data) : str_(data) {} +HpackString::HpackString(Http2StringPiece str) : str_(Http2String(str)) {} +HpackString::HpackString(Http2String str) : str_(std::move(str)) {} +HpackString::HpackString(const HpackString& other) = default; +HpackString::~HpackString() = default; + +Http2StringPiece HpackString::ToStringPiece() const { + return str_; +} + +bool HpackString::operator==(const HpackString& other) const { + return str_ == other.str_; +} +bool HpackString::operator==(Http2StringPiece str) const { + return str == str_; +} + +bool operator==(Http2StringPiece a, const HpackString& b) { + return b == a; +} +bool operator!=(Http2StringPiece a, const HpackString& b) { + return !(b == a); +} +bool operator!=(const HpackString& a, const HpackString& b) { + return !(a == b); +} +bool operator!=(const HpackString& a, Http2StringPiece b) { + return !(a == b); +} +std::ostream& operator<<(std::ostream& out, const HpackString& v) { + return out << v.ToString(); +} + +HpackStringPair::HpackStringPair(const HpackString& name, + const HpackString& value) + : name(name), value(value) { + DVLOG(3) << DebugString() << " ctor"; +} + +HpackStringPair::HpackStringPair(Http2StringPiece name, Http2StringPiece value) + : name(name), value(value) { + DVLOG(3) << DebugString() << " ctor"; +} + +HpackStringPair::~HpackStringPair() { + DVLOG(3) << DebugString() << " dtor"; +} + +Http2String HpackStringPair::DebugString() const { + return Http2StrCat("HpackStringPair(name=", name.ToString(), + ", value=", value.ToString(), ")"); +} + +std::ostream& operator<<(std::ostream& os, const HpackStringPair& p) { + os << p.DebugString(); + return os; +} + +} // namespace http2 diff --git a/chromium/net/third_party/quiche/src/http2/hpack/hpack_string.h b/chromium/net/third_party/quiche/src/http2/hpack/hpack_string.h new file mode 100644 index 00000000000..b1c499c95be --- /dev/null +++ b/chromium/net/third_party/quiche/src/http2/hpack/hpack_string.h @@ -0,0 +1,75 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef QUICHE_HTTP2_HPACK_HPACK_STRING_H_ +#define QUICHE_HTTP2_HPACK_HPACK_STRING_H_ + +// HpackString is currently a very simple container for a string, but allows us +// to relatively easily experiment with alternate string storage mechanisms for +// handling strings to be encoded with HPACK, or decoded from HPACK, such as +// a ref-counted string. + +#include <stddef.h> + +#include <iosfwd> + +#include "net/third_party/quiche/src/http2/platform/api/http2_export.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string_piece.h" + +namespace http2 { + +class HTTP2_EXPORT_PRIVATE HpackString { + public: + explicit HpackString(const char* data); + explicit HpackString(Http2StringPiece str); + explicit HpackString(Http2String str); + HpackString(const HpackString& other); + + // Not sure yet whether this move ctor is required/sensible. + HpackString(HpackString&& other) = default; + + ~HpackString(); + + size_t size() const { return str_.size(); } + const Http2String& ToString() const { return str_; } + Http2StringPiece ToStringPiece() const; + + bool operator==(const HpackString& other) const; + + bool operator==(Http2StringPiece str) const; + + private: + Http2String str_; +}; + +HTTP2_EXPORT_PRIVATE bool operator==(Http2StringPiece a, const HpackString& b); +HTTP2_EXPORT_PRIVATE bool operator!=(Http2StringPiece a, const HpackString& b); +HTTP2_EXPORT_PRIVATE bool operator!=(const HpackString& a, + const HpackString& b); +HTTP2_EXPORT_PRIVATE bool operator!=(const HpackString& a, Http2StringPiece b); +HTTP2_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& out, + const HpackString& v); + +struct HTTP2_EXPORT_PRIVATE HpackStringPair { + HpackStringPair(const HpackString& name, const HpackString& value); + HpackStringPair(Http2StringPiece name, Http2StringPiece value); + ~HpackStringPair(); + + // Returns the size of a header entry with this name and value, per the RFC: + // http://httpwg.org/specs/rfc7541.html#calculating.table.size + size_t size() const { return 32 + name.size() + value.size(); } + + Http2String DebugString() const; + + const HpackString name; + const HpackString value; +}; + +HTTP2_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& os, + const HpackStringPair& p); + +} // namespace http2 + +#endif // QUICHE_HTTP2_HPACK_HPACK_STRING_H_ diff --git a/chromium/net/third_party/quiche/src/http2/hpack/hpack_string_test.cc b/chromium/net/third_party/quiche/src/http2/hpack/hpack_string_test.cc new file mode 100644 index 00000000000..87f5975886d --- /dev/null +++ b/chromium/net/third_party/quiche/src/http2/hpack/hpack_string_test.cc @@ -0,0 +1,149 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/http2/hpack/hpack_string.h" + +// Tests of HpackString. + +#include <utility> + +#include "base/logging.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_test_helpers.h" + +using ::testing::AssertionFailure; +using ::testing::AssertionResult; +using ::testing::AssertionSuccess; + +namespace http2 { +namespace test { +namespace { + +const char kStr0[] = "s0: Some string to be copied into another string."; +const char kStr1[] = "S1 - some string to be copied into yet another string."; + +class HpackStringTest : public ::testing::Test { + protected: + AssertionResult VerifyNotEqual(HpackString* actual, + const Http2String& not_expected_str) { + const char* not_expected_ptr = not_expected_str.c_str(); + Http2StringPiece not_expected_sp(not_expected_str); + + VERIFY_NE(*actual, not_expected_ptr); + VERIFY_NE(*actual, not_expected_sp); + VERIFY_NE(*actual, not_expected_str); + VERIFY_NE(actual->ToStringPiece(), not_expected_sp); + + if (!(not_expected_ptr != *actual)) { + return AssertionFailure(); + } + if (!(not_expected_sp != *actual)) { + return AssertionFailure(); + } + if (!(not_expected_str != *actual)) { + return AssertionFailure(); + } + if (!(not_expected_sp != actual->ToStringPiece())) { + return AssertionFailure(); + } + + return AssertionSuccess(); + } + + AssertionResult VerifyEqual(HpackString* actual, + const Http2String& expected_str) { + VERIFY_EQ(actual->size(), expected_str.size()); + + const char* expected_ptr = expected_str.c_str(); + const Http2StringPiece expected_sp(expected_str); + + VERIFY_EQ(*actual, expected_ptr); + VERIFY_EQ(*actual, expected_sp); + VERIFY_EQ(*actual, expected_str); + VERIFY_EQ(actual->ToStringPiece(), expected_sp); + + if (!(expected_sp == *actual)) { + return AssertionFailure(); + } + if (!(expected_ptr == *actual)) { + return AssertionFailure(); + } + if (!(expected_str == *actual)) { + return AssertionFailure(); + } + if (!(expected_sp == actual->ToStringPiece())) { + return AssertionFailure(); + } + + return AssertionSuccess(); + } +}; + +TEST_F(HpackStringTest, CharArrayConstructor) { + HpackString hs0(kStr0); + EXPECT_TRUE(VerifyEqual(&hs0, kStr0)); + EXPECT_TRUE(VerifyNotEqual(&hs0, kStr1)); + + HpackString hs1(kStr1); + EXPECT_TRUE(VerifyEqual(&hs1, kStr1)); + EXPECT_TRUE(VerifyNotEqual(&hs1, kStr0)); +} + +TEST_F(HpackStringTest, StringPieceConstructor) { + Http2StringPiece sp0(kStr0); + HpackString hs0(sp0); + EXPECT_TRUE(VerifyEqual(&hs0, kStr0)); + EXPECT_TRUE(VerifyNotEqual(&hs0, kStr1)); + + Http2StringPiece sp1(kStr1); + HpackString hs1(sp1); + EXPECT_TRUE(VerifyEqual(&hs1, kStr1)); + EXPECT_TRUE(VerifyNotEqual(&hs1, kStr0)); +} + +TEST_F(HpackStringTest, MoveStringConstructor) { + Http2String str0(kStr0); + HpackString hs0(str0); + EXPECT_TRUE(VerifyEqual(&hs0, kStr0)); + EXPECT_TRUE(VerifyNotEqual(&hs0, kStr1)); + + Http2String str1(kStr1); + HpackString hs1(str1); + EXPECT_TRUE(VerifyEqual(&hs1, kStr1)); + EXPECT_TRUE(VerifyNotEqual(&hs1, kStr0)); +} + +TEST_F(HpackStringTest, CopyConstructor) { + Http2StringPiece sp0(kStr0); + HpackString hs0(sp0); + HpackString hs1(hs0); + EXPECT_EQ(hs0, hs1); + + EXPECT_TRUE(VerifyEqual(&hs0, kStr0)); + EXPECT_TRUE(VerifyEqual(&hs1, kStr0)); + + EXPECT_TRUE(VerifyNotEqual(&hs0, kStr1)); + EXPECT_TRUE(VerifyNotEqual(&hs1, kStr1)); +} + +TEST_F(HpackStringTest, MoveConstructor) { + Http2StringPiece sp0(kStr0); + HpackString hs0(sp0); + EXPECT_TRUE(VerifyEqual(&hs0, kStr0)); + EXPECT_TRUE(VerifyNotEqual(&hs0, "")); + + HpackString hs1(std::move(hs0)); + EXPECT_NE(hs0, hs1); + + EXPECT_TRUE(VerifyEqual(&hs1, kStr0)); + EXPECT_TRUE(VerifyEqual(&hs0, "")); + EXPECT_TRUE(VerifyNotEqual(&hs1, "")); + + LOG(INFO) << hs0; + LOG(INFO) << hs1; +} + +} // namespace +} // namespace test +} // namespace http2 diff --git a/chromium/net/third_party/quiche/src/http2/hpack/http2_hpack_constants.cc b/chromium/net/third_party/quiche/src/http2/hpack/http2_hpack_constants.cc new file mode 100644 index 00000000000..f258ab9bcbd --- /dev/null +++ b/chromium/net/third_party/quiche/src/http2/hpack/http2_hpack_constants.cc @@ -0,0 +1,31 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/http2/hpack/http2_hpack_constants.h" + +#include "net/third_party/quiche/src/http2/platform/api/http2_string_utils.h" + +namespace http2 { + +Http2String HpackEntryTypeToString(HpackEntryType v) { + switch (v) { + case HpackEntryType::kIndexedHeader: + return "kIndexedHeader"; + case HpackEntryType::kDynamicTableSizeUpdate: + return "kDynamicTableSizeUpdate"; + case HpackEntryType::kIndexedLiteralHeader: + return "kIndexedLiteralHeader"; + case HpackEntryType::kUnindexedLiteralHeader: + return "kUnindexedLiteralHeader"; + case HpackEntryType::kNeverIndexedLiteralHeader: + return "kNeverIndexedLiteralHeader"; + } + return Http2StrCat("UnknownHpackEntryType(", static_cast<int>(v), ")"); +} + +std::ostream& operator<<(std::ostream& out, HpackEntryType v) { + return out << HpackEntryTypeToString(v); +} + +} // namespace http2 diff --git a/chromium/net/third_party/quiche/src/http2/hpack/http2_hpack_constants.h b/chromium/net/third_party/quiche/src/http2/hpack/http2_hpack_constants.h new file mode 100644 index 00000000000..de0d685595d --- /dev/null +++ b/chromium/net/third_party/quiche/src/http2/hpack/http2_hpack_constants.h @@ -0,0 +1,63 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef QUICHE_HTTP2_HPACK_HTTP2_HPACK_CONSTANTS_H_ +#define QUICHE_HTTP2_HPACK_HTTP2_HPACK_CONSTANTS_H_ + +// Enum HpackEntryType identifies the 5 basic types of HPACK Block Entries. +// +// See the spec for details: +// https://http2.github.io/http2-spec/compression.html#rfc.section.6 + +#include <ostream> + +#include "net/third_party/quiche/src/http2/platform/api/http2_export.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string.h" + +namespace http2 { + +const size_t kFirstDynamicTableIndex = 62; + +enum class HpackEntryType { + // Entry is an index into the static or dynamic table. Decoding it has no + // effect on the dynamic table. + kIndexedHeader, + + // The entry contains a literal value. The name may be either a literal or a + // reference to an entry in the static or dynamic table. + // The entry is added to the dynamic table after decoding. + kIndexedLiteralHeader, + + // The entry contains a literal value. The name may be either a literal or a + // reference to an entry in the static or dynamic table. + // The entry is not added to the dynamic table after decoding, but a proxy + // may choose to insert the entry into its dynamic table when forwarding + // to another endpoint. + kUnindexedLiteralHeader, + + // The entry contains a literal value. The name may be either a literal or a + // reference to an entry in the static or dynamic table. + // The entry is not added to the dynamic table after decoding, and a proxy + // must NOT insert the entry into its dynamic table when forwarding to another + // endpoint. + kNeverIndexedLiteralHeader, + + // Entry conveys the size limit of the dynamic table of the encoder to + // the decoder. May be used to flush the table by sending a zero and then + // resetting the size back up to the maximum that the encoder will use + // (within the limits of SETTINGS_HEADER_TABLE_SIZE sent by the + // decoder to the encoder, with the default of 4096 assumed). + kDynamicTableSizeUpdate, +}; + +// Returns the name of the enum member. +HTTP2_EXPORT_PRIVATE Http2String HpackEntryTypeToString(HpackEntryType v); + +// Inserts the name of the enum member into |out|. +HTTP2_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& out, + HpackEntryType v); + +} // namespace http2 + +#endif // QUICHE_HTTP2_HPACK_HTTP2_HPACK_CONSTANTS_H_ diff --git a/chromium/net/third_party/quiche/src/http2/hpack/http2_hpack_constants_test.cc b/chromium/net/third_party/quiche/src/http2/hpack/http2_hpack_constants_test.cc new file mode 100644 index 00000000000..5b20b186efd --- /dev/null +++ b/chromium/net/third_party/quiche/src/http2/hpack/http2_hpack_constants_test.cc @@ -0,0 +1,72 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/http2/hpack/http2_hpack_constants.h" + +#include "base/logging.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_mock_log.h" + +namespace http2 { +namespace test { +namespace { + +TEST(HpackEntryTypeTest, HpackEntryTypeToString) { + EXPECT_EQ("kIndexedHeader", + HpackEntryTypeToString(HpackEntryType::kIndexedHeader)); + EXPECT_EQ("kDynamicTableSizeUpdate", + HpackEntryTypeToString(HpackEntryType::kDynamicTableSizeUpdate)); + EXPECT_EQ("kIndexedLiteralHeader", + HpackEntryTypeToString(HpackEntryType::kIndexedLiteralHeader)); + EXPECT_EQ("kUnindexedLiteralHeader", + HpackEntryTypeToString(HpackEntryType::kUnindexedLiteralHeader)); + EXPECT_EQ("kNeverIndexedLiteralHeader", + HpackEntryTypeToString(HpackEntryType::kNeverIndexedLiteralHeader)); + EXPECT_EQ("UnknownHpackEntryType(12321)", + HpackEntryTypeToString(static_cast<HpackEntryType>(12321))); +} + +TEST(HpackEntryTypeTest, OutputHpackEntryType) { + { + CREATE_HTTP2_MOCK_LOG(log); + log.StartCapturingLogs(); + EXPECT_HTTP2_LOG_CALL_CONTAINS(log, INFO, "kIndexedHeader"); + LOG(INFO) << HpackEntryType::kIndexedHeader; + } + { + CREATE_HTTP2_MOCK_LOG(log); + log.StartCapturingLogs(); + EXPECT_HTTP2_LOG_CALL_CONTAINS(log, INFO, "kDynamicTableSizeUpdate"); + LOG(INFO) << HpackEntryType::kDynamicTableSizeUpdate; + } + { + CREATE_HTTP2_MOCK_LOG(log); + log.StartCapturingLogs(); + EXPECT_HTTP2_LOG_CALL_CONTAINS(log, INFO, "kIndexedLiteralHeader"); + LOG(INFO) << HpackEntryType::kIndexedLiteralHeader; + } + { + CREATE_HTTP2_MOCK_LOG(log); + log.StartCapturingLogs(); + EXPECT_HTTP2_LOG_CALL_CONTAINS(log, INFO, "kUnindexedLiteralHeader"); + LOG(INFO) << HpackEntryType::kUnindexedLiteralHeader; + } + { + CREATE_HTTP2_MOCK_LOG(log); + log.StartCapturingLogs(); + EXPECT_HTTP2_LOG_CALL_CONTAINS(log, INFO, "kNeverIndexedLiteralHeader"); + LOG(INFO) << HpackEntryType::kNeverIndexedLiteralHeader; + } + { + CREATE_HTTP2_MOCK_LOG(log); + log.StartCapturingLogs(); + EXPECT_HTTP2_LOG_CALL_CONTAINS(log, INFO, "UnknownHpackEntryType(1234321)"); + LOG(INFO) << static_cast<HpackEntryType>(1234321); + } +} + +} // namespace +} // namespace test +} // namespace http2 diff --git a/chromium/net/third_party/quiche/src/http2/hpack/huffman/hpack_huffman_decoder.cc b/chromium/net/third_party/quiche/src/http2/hpack/huffman/hpack_huffman_decoder.cc new file mode 100644 index 00000000000..d1076ed3c07 --- /dev/null +++ b/chromium/net/third_party/quiche/src/http2/hpack/huffman/hpack_huffman_decoder.cc @@ -0,0 +1,487 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/http2/hpack/huffman/hpack_huffman_decoder.h" + +#include <bitset> +#include <limits> + +#include "base/logging.h" + +// Terminology: +// +// Symbol - a plain text (unencoded) character (uint8), or the End-of-String +// (EOS) symbol, 256. +// +// Code - the sequence of bits used to encode a symbol, varying in length from +// 5 bits for the most common symbols (e.g. '0', '1', and 'a'), to +// 30 bits for the least common (e.g. the EOS symbol). +// For those symbols whose codes have the same length, their code values +// are sorted such that the lower symbol value has a lower code value. +// +// Canonical - a symbol's cardinal value when sorted first by code length, and +// then by symbol value. For example, canonical 0 is for ASCII '0' +// (uint8 value 0x30), which is the first of the symbols whose code +// is 5 bits long, and the last canonical is EOS, which is the last +// of the symbols whose code is 30 bits long. + +// TODO(jamessynge): Remove use of binary literals, that is a C++ 14 feature. + +namespace http2 { +namespace { + +// HuffmanCode is used to store the codes associated with symbols (a pattern of +// from 5 to 30 bits). +typedef uint32_t HuffmanCode; + +// HuffmanCodeBitCount is used to store a count of bits in a code. +typedef uint16_t HuffmanCodeBitCount; + +// HuffmanCodeBitSet is used for producing a string version of a code because +// std::bitset logs nicely. +typedef std::bitset<32> HuffmanCodeBitSet; +typedef std::bitset<64> HuffmanAccumulatorBitSet; + +static constexpr HuffmanCodeBitCount kMinCodeBitCount = 5; +static constexpr HuffmanCodeBitCount kMaxCodeBitCount = 30; +static constexpr HuffmanCodeBitCount kHuffmanCodeBitCount = + std::numeric_limits<HuffmanCode>::digits; + +static_assert(std::numeric_limits<HuffmanCode>::digits >= kMaxCodeBitCount, + "HuffmanCode isn't big enough."); + +static_assert(std::numeric_limits<HuffmanAccumulator>::digits >= + kMaxCodeBitCount, + "HuffmanAccumulator isn't big enough."); + +static constexpr HuffmanAccumulatorBitCount kHuffmanAccumulatorBitCount = + std::numeric_limits<HuffmanAccumulator>::digits; +static constexpr HuffmanAccumulatorBitCount kExtraAccumulatorBitCount = + kHuffmanAccumulatorBitCount - kHuffmanCodeBitCount; + +// PrefixInfo holds info about a group of codes that are all of the same length. +struct PrefixInfo { + // Given the leading bits (32 in this case) of the encoded string, and that + // they start with a code of length |code_length|, return the corresponding + // canonical for that leading code. + uint32_t DecodeToCanonical(HuffmanCode bits) const { + // What is the position of the canonical symbol being decoded within + // the canonical symbols of |length|? + HuffmanCode ordinal_in_length = + ((bits - first_code) >> (kHuffmanCodeBitCount - code_length)); + + // Combined with |canonical| to produce the position of the canonical symbol + // being decoded within all of the canonical symbols. + return first_canonical + ordinal_in_length; + } + + const HuffmanCode first_code; // First code of this length, left justified in + // the field (i.e. the first bit of the code is + // the high-order bit). + const uint16_t code_length; // Length of the prefix code |base|. + const uint16_t first_canonical; // First canonical symbol of this length. +}; + +inline std::ostream& operator<<(std::ostream& out, const PrefixInfo& v) { + return out << "{first_code: " << HuffmanCodeBitSet(v.first_code) + << ", code_length: " << v.code_length + << ", first_canonical: " << v.first_canonical << "}"; +} + +// Given |value|, a sequence of the leading bits remaining to be decoded, +// figure out which group of canonicals (by code length) that value starts +// with. This function was generated. +PrefixInfo PrefixToInfo(HuffmanCode value) { + if (value < 0b10111000000000000000000000000000) { + if (value < 0b01010000000000000000000000000000) { + return {0b00000000000000000000000000000000, 5, 0}; + } else { + return {0b01010000000000000000000000000000, 6, 10}; + } + } else { + if (value < 0b11111110000000000000000000000000) { + if (value < 0b11111000000000000000000000000000) { + return {0b10111000000000000000000000000000, 7, 36}; + } else { + return {0b11111000000000000000000000000000, 8, 68}; + } + } else { + if (value < 0b11111111110000000000000000000000) { + if (value < 0b11111111101000000000000000000000) { + if (value < 0b11111111010000000000000000000000) { + return {0b11111110000000000000000000000000, 10, 74}; + } else { + return {0b11111111010000000000000000000000, 11, 79}; + } + } else { + return {0b11111111101000000000000000000000, 12, 82}; + } + } else { + if (value < 0b11111111111111100000000000000000) { + if (value < 0b11111111111110000000000000000000) { + if (value < 0b11111111111100000000000000000000) { + return {0b11111111110000000000000000000000, 13, 84}; + } else { + return {0b11111111111100000000000000000000, 14, 90}; + } + } else { + return {0b11111111111110000000000000000000, 15, 92}; + } + } else { + if (value < 0b11111111111111110100100000000000) { + if (value < 0b11111111111111101110000000000000) { + if (value < 0b11111111111111100110000000000000) { + return {0b11111111111111100000000000000000, 19, 95}; + } else { + return {0b11111111111111100110000000000000, 20, 98}; + } + } else { + return {0b11111111111111101110000000000000, 21, 106}; + } + } else { + if (value < 0b11111111111111111110101000000000) { + if (value < 0b11111111111111111011000000000000) { + return {0b11111111111111110100100000000000, 22, 119}; + } else { + return {0b11111111111111111011000000000000, 23, 145}; + } + } else { + if (value < 0b11111111111111111111101111000000) { + if (value < 0b11111111111111111111100000000000) { + if (value < 0b11111111111111111111011000000000) { + return {0b11111111111111111110101000000000, 24, 174}; + } else { + return {0b11111111111111111111011000000000, 25, 186}; + } + } else { + return {0b11111111111111111111100000000000, 26, 190}; + } + } else { + if (value < 0b11111111111111111111111111110000) { + if (value < 0b11111111111111111111111000100000) { + return {0b11111111111111111111101111000000, 27, 205}; + } else { + return {0b11111111111111111111111000100000, 28, 224}; + } + } else { + return {0b11111111111111111111111111110000, 30, 253}; + } + } + } + } + } + } + } + } +} + +// Mapping from canonical symbol (0 to 255) to actual symbol. +// clang-format off +constexpr unsigned char kCanonicalToSymbol[] = { + '0', '1', '2', 'a', 'c', 'e', 'i', 'o', + 's', 't', 0x20, '%', '-', '.', '/', '3', + '4', '5', '6', '7', '8', '9', '=', 'A', + '_', 'b', 'd', 'f', 'g', 'h', 'l', 'm', + 'n', 'p', 'r', 'u', ':', 'B', 'C', 'D', + 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', + 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', + 'U', 'V', 'W', 'Y', 'j', 'k', 'q', 'v', + 'w', 'x', 'y', 'z', '&', '*', ',', ';', + 'X', 'Z', '!', '\"', '(', ')', '?', '\'', + '+', '|', '#', '>', 0x00, '$', '@', '[', + ']', '~', '^', '}', '<', '`', '{', '\\', + 0xc3, 0xd0, 0x80, 0x82, 0x83, 0xa2, 0xb8, 0xc2, + 0xe0, 0xe2, 0x99, 0xa1, 0xa7, 0xac, 0xb0, 0xb1, + 0xb3, 0xd1, 0xd8, 0xd9, 0xe3, 0xe5, 0xe6, 0x81, + 0x84, 0x85, 0x86, 0x88, 0x92, 0x9a, 0x9c, 0xa0, + 0xa3, 0xa4, 0xa9, 0xaa, 0xad, 0xb2, 0xb5, 0xb9, + 0xba, 0xbb, 0xbd, 0xbe, 0xc4, 0xc6, 0xe4, 0xe8, + 0xe9, 0x01, 0x87, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, + 0x8f, 0x93, 0x95, 0x96, 0x97, 0x98, 0x9b, 0x9d, + 0x9e, 0xa5, 0xa6, 0xa8, 0xae, 0xaf, 0xb4, 0xb6, + 0xb7, 0xbc, 0xbf, 0xc5, 0xe7, 0xef, 0x09, 0x8e, + 0x90, 0x91, 0x94, 0x9f, 0xab, 0xce, 0xd7, 0xe1, + 0xec, 0xed, 0xc7, 0xcf, 0xea, 0xeb, 0xc0, 0xc1, + 0xc8, 0xc9, 0xca, 0xcd, 0xd2, 0xd5, 0xda, 0xdb, + 0xee, 0xf0, 0xf2, 0xf3, 0xff, 0xcb, 0xcc, 0xd3, + 0xd4, 0xd6, 0xdd, 0xde, 0xdf, 0xf1, 0xf4, 0xf5, + 0xf6, 0xf7, 0xf8, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, + 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x0b, + 0x0c, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, + 0x15, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, + 0x1e, 0x1f, 0x7f, 0xdc, 0xf9, 0x0a, 0x0d, 0x16, +}; +// clang-format on + +constexpr size_t kShortCodeTableSize = 124; +struct ShortCodeInfo { + uint8_t symbol; + uint8_t length; +} kShortCodeTable[kShortCodeTableSize] = { + {0x30, 5}, // Match: 0b0000000, Symbol: 0 + {0x30, 5}, // Match: 0b0000001, Symbol: 0 + {0x30, 5}, // Match: 0b0000010, Symbol: 0 + {0x30, 5}, // Match: 0b0000011, Symbol: 0 + {0x31, 5}, // Match: 0b0000100, Symbol: 1 + {0x31, 5}, // Match: 0b0000101, Symbol: 1 + {0x31, 5}, // Match: 0b0000110, Symbol: 1 + {0x31, 5}, // Match: 0b0000111, Symbol: 1 + {0x32, 5}, // Match: 0b0001000, Symbol: 2 + {0x32, 5}, // Match: 0b0001001, Symbol: 2 + {0x32, 5}, // Match: 0b0001010, Symbol: 2 + {0x32, 5}, // Match: 0b0001011, Symbol: 2 + {0x61, 5}, // Match: 0b0001100, Symbol: a + {0x61, 5}, // Match: 0b0001101, Symbol: a + {0x61, 5}, // Match: 0b0001110, Symbol: a + {0x61, 5}, // Match: 0b0001111, Symbol: a + {0x63, 5}, // Match: 0b0010000, Symbol: c + {0x63, 5}, // Match: 0b0010001, Symbol: c + {0x63, 5}, // Match: 0b0010010, Symbol: c + {0x63, 5}, // Match: 0b0010011, Symbol: c + {0x65, 5}, // Match: 0b0010100, Symbol: e + {0x65, 5}, // Match: 0b0010101, Symbol: e + {0x65, 5}, // Match: 0b0010110, Symbol: e + {0x65, 5}, // Match: 0b0010111, Symbol: e + {0x69, 5}, // Match: 0b0011000, Symbol: i + {0x69, 5}, // Match: 0b0011001, Symbol: i + {0x69, 5}, // Match: 0b0011010, Symbol: i + {0x69, 5}, // Match: 0b0011011, Symbol: i + {0x6f, 5}, // Match: 0b0011100, Symbol: o + {0x6f, 5}, // Match: 0b0011101, Symbol: o + {0x6f, 5}, // Match: 0b0011110, Symbol: o + {0x6f, 5}, // Match: 0b0011111, Symbol: o + {0x73, 5}, // Match: 0b0100000, Symbol: s + {0x73, 5}, // Match: 0b0100001, Symbol: s + {0x73, 5}, // Match: 0b0100010, Symbol: s + {0x73, 5}, // Match: 0b0100011, Symbol: s + {0x74, 5}, // Match: 0b0100100, Symbol: t + {0x74, 5}, // Match: 0b0100101, Symbol: t + {0x74, 5}, // Match: 0b0100110, Symbol: t + {0x74, 5}, // Match: 0b0100111, Symbol: t + {0x20, 6}, // Match: 0b0101000, Symbol: (space) + {0x20, 6}, // Match: 0b0101001, Symbol: (space) + {0x25, 6}, // Match: 0b0101010, Symbol: % + {0x25, 6}, // Match: 0b0101011, Symbol: % + {0x2d, 6}, // Match: 0b0101100, Symbol: - + {0x2d, 6}, // Match: 0b0101101, Symbol: - + {0x2e, 6}, // Match: 0b0101110, Symbol: . + {0x2e, 6}, // Match: 0b0101111, Symbol: . + {0x2f, 6}, // Match: 0b0110000, Symbol: / + {0x2f, 6}, // Match: 0b0110001, Symbol: / + {0x33, 6}, // Match: 0b0110010, Symbol: 3 + {0x33, 6}, // Match: 0b0110011, Symbol: 3 + {0x34, 6}, // Match: 0b0110100, Symbol: 4 + {0x34, 6}, // Match: 0b0110101, Symbol: 4 + {0x35, 6}, // Match: 0b0110110, Symbol: 5 + {0x35, 6}, // Match: 0b0110111, Symbol: 5 + {0x36, 6}, // Match: 0b0111000, Symbol: 6 + {0x36, 6}, // Match: 0b0111001, Symbol: 6 + {0x37, 6}, // Match: 0b0111010, Symbol: 7 + {0x37, 6}, // Match: 0b0111011, Symbol: 7 + {0x38, 6}, // Match: 0b0111100, Symbol: 8 + {0x38, 6}, // Match: 0b0111101, Symbol: 8 + {0x39, 6}, // Match: 0b0111110, Symbol: 9 + {0x39, 6}, // Match: 0b0111111, Symbol: 9 + {0x3d, 6}, // Match: 0b1000000, Symbol: = + {0x3d, 6}, // Match: 0b1000001, Symbol: = + {0x41, 6}, // Match: 0b1000010, Symbol: A + {0x41, 6}, // Match: 0b1000011, Symbol: A + {0x5f, 6}, // Match: 0b1000100, Symbol: _ + {0x5f, 6}, // Match: 0b1000101, Symbol: _ + {0x62, 6}, // Match: 0b1000110, Symbol: b + {0x62, 6}, // Match: 0b1000111, Symbol: b + {0x64, 6}, // Match: 0b1001000, Symbol: d + {0x64, 6}, // Match: 0b1001001, Symbol: d + {0x66, 6}, // Match: 0b1001010, Symbol: f + {0x66, 6}, // Match: 0b1001011, Symbol: f + {0x67, 6}, // Match: 0b1001100, Symbol: g + {0x67, 6}, // Match: 0b1001101, Symbol: g + {0x68, 6}, // Match: 0b1001110, Symbol: h + {0x68, 6}, // Match: 0b1001111, Symbol: h + {0x6c, 6}, // Match: 0b1010000, Symbol: l + {0x6c, 6}, // Match: 0b1010001, Symbol: l + {0x6d, 6}, // Match: 0b1010010, Symbol: m + {0x6d, 6}, // Match: 0b1010011, Symbol: m + {0x6e, 6}, // Match: 0b1010100, Symbol: n + {0x6e, 6}, // Match: 0b1010101, Symbol: n + {0x70, 6}, // Match: 0b1010110, Symbol: p + {0x70, 6}, // Match: 0b1010111, Symbol: p + {0x72, 6}, // Match: 0b1011000, Symbol: r + {0x72, 6}, // Match: 0b1011001, Symbol: r + {0x75, 6}, // Match: 0b1011010, Symbol: u + {0x75, 6}, // Match: 0b1011011, Symbol: u + {0x3a, 7}, // Match: 0b1011100, Symbol: : + {0x42, 7}, // Match: 0b1011101, Symbol: B + {0x43, 7}, // Match: 0b1011110, Symbol: C + {0x44, 7}, // Match: 0b1011111, Symbol: D + {0x45, 7}, // Match: 0b1100000, Symbol: E + {0x46, 7}, // Match: 0b1100001, Symbol: F + {0x47, 7}, // Match: 0b1100010, Symbol: G + {0x48, 7}, // Match: 0b1100011, Symbol: H + {0x49, 7}, // Match: 0b1100100, Symbol: I + {0x4a, 7}, // Match: 0b1100101, Symbol: J + {0x4b, 7}, // Match: 0b1100110, Symbol: K + {0x4c, 7}, // Match: 0b1100111, Symbol: L + {0x4d, 7}, // Match: 0b1101000, Symbol: M + {0x4e, 7}, // Match: 0b1101001, Symbol: N + {0x4f, 7}, // Match: 0b1101010, Symbol: O + {0x50, 7}, // Match: 0b1101011, Symbol: P + {0x51, 7}, // Match: 0b1101100, Symbol: Q + {0x52, 7}, // Match: 0b1101101, Symbol: R + {0x53, 7}, // Match: 0b1101110, Symbol: S + {0x54, 7}, // Match: 0b1101111, Symbol: T + {0x55, 7}, // Match: 0b1110000, Symbol: U + {0x56, 7}, // Match: 0b1110001, Symbol: V + {0x57, 7}, // Match: 0b1110010, Symbol: W + {0x59, 7}, // Match: 0b1110011, Symbol: Y + {0x6a, 7}, // Match: 0b1110100, Symbol: j + {0x6b, 7}, // Match: 0b1110101, Symbol: k + {0x71, 7}, // Match: 0b1110110, Symbol: q + {0x76, 7}, // Match: 0b1110111, Symbol: v + {0x77, 7}, // Match: 0b1111000, Symbol: w + {0x78, 7}, // Match: 0b1111001, Symbol: x + {0x79, 7}, // Match: 0b1111010, Symbol: y + {0x7a, 7}, // Match: 0b1111011, Symbol: z +}; + +} // namespace + +HuffmanBitBuffer::HuffmanBitBuffer() { + Reset(); +} + +void HuffmanBitBuffer::Reset() { + accumulator_ = 0; + count_ = 0; +} + +size_t HuffmanBitBuffer::AppendBytes(Http2StringPiece input) { + HuffmanAccumulatorBitCount free_cnt = free_count(); + size_t bytes_available = input.size(); + if (free_cnt < 8 || bytes_available == 0) { + return 0; + } + + // Top up |accumulator_| until there isn't room for a whole byte. + size_t bytes_used = 0; + auto* ptr = reinterpret_cast<const uint8_t*>(input.data()); + do { + auto b = static_cast<HuffmanAccumulator>(*ptr++); + free_cnt -= 8; + accumulator_ |= (b << free_cnt); + ++bytes_used; + } while (free_cnt >= 8 && bytes_used < bytes_available); + count_ += (bytes_used * 8); + return bytes_used; +} + +HuffmanAccumulatorBitCount HuffmanBitBuffer::free_count() const { + return kHuffmanAccumulatorBitCount - count_; +} + +void HuffmanBitBuffer::ConsumeBits(HuffmanAccumulatorBitCount code_length) { + DCHECK_LE(code_length, count_); + accumulator_ <<= code_length; + count_ -= code_length; +} + +bool HuffmanBitBuffer::InputProperlyTerminated() const { + auto cnt = count(); + if (cnt < 8) { + if (cnt == 0) { + return true; + } + HuffmanAccumulator expected = ~(~HuffmanAccumulator() >> cnt); + // We expect all the bits below the high order |cnt| bits of accumulator_ + // to be cleared as we perform left shift operations while decoding. + DCHECK_EQ(accumulator_ & ~expected, 0u) + << "\n expected: " << HuffmanAccumulatorBitSet(expected) << "\n " + << *this; + return accumulator_ == expected; + } + return false; +} + +Http2String HuffmanBitBuffer::DebugString() const { + std::stringstream ss; + ss << "{accumulator: " << HuffmanAccumulatorBitSet(accumulator_) + << "; count: " << count_ << "}"; + return ss.str(); +} + +HpackHuffmanDecoder::HpackHuffmanDecoder() = default; + +HpackHuffmanDecoder::~HpackHuffmanDecoder() = default; + +bool HpackHuffmanDecoder::Decode(Http2StringPiece input, Http2String* output) { + DVLOG(1) << "HpackHuffmanDecoder::Decode"; + + // Fill bit_buffer_ from input. + input.remove_prefix(bit_buffer_.AppendBytes(input)); + + while (true) { + DVLOG(3) << "Enter Decode Loop, bit_buffer_: " << bit_buffer_; + if (bit_buffer_.count() >= 7) { + // Get high 7 bits of the bit buffer, see if that contains a complete + // code of 5, 6 or 7 bits. + uint8_t short_code = + bit_buffer_.value() >> (kHuffmanAccumulatorBitCount - 7); + DCHECK_LT(short_code, 128); + if (short_code < kShortCodeTableSize) { + ShortCodeInfo info = kShortCodeTable[short_code]; + bit_buffer_.ConsumeBits(info.length); + output->push_back(static_cast<char>(info.symbol)); + continue; + } + // The code is more than 7 bits long. Use PrefixToInfo, etc. to decode + // longer codes. + } else { + // We may have (mostly) drained bit_buffer_. If we can top it up, try + // using the table decoder above. + size_t byte_count = bit_buffer_.AppendBytes(input); + if (byte_count > 0) { + input.remove_prefix(byte_count); + continue; + } + } + + HuffmanCode code_prefix = bit_buffer_.value() >> kExtraAccumulatorBitCount; + DVLOG(3) << "code_prefix: " << HuffmanCodeBitSet(code_prefix); + + PrefixInfo prefix_info = PrefixToInfo(code_prefix); + DVLOG(3) << "prefix_info: " << prefix_info; + DCHECK_LE(kMinCodeBitCount, prefix_info.code_length); + DCHECK_LE(prefix_info.code_length, kMaxCodeBitCount); + + if (prefix_info.code_length <= bit_buffer_.count()) { + // We have enough bits for one code. + uint32_t canonical = prefix_info.DecodeToCanonical(code_prefix); + if (canonical < 256) { + // Valid code. + char c = kCanonicalToSymbol[canonical]; + output->push_back(c); + bit_buffer_.ConsumeBits(prefix_info.code_length); + continue; + } + // Encoder is not supposed to explicity encode the EOS symbol. + DLOG(ERROR) << "EOS explicitly encoded!\n " << bit_buffer_ << "\n " + << prefix_info; + return false; + } + // bit_buffer_ doesn't have enough bits in it to decode the next symbol. + // Append to it as many bytes as are available AND fit. + size_t byte_count = bit_buffer_.AppendBytes(input); + if (byte_count == 0) { + DCHECK_EQ(input.size(), 0u); + return true; + } + input.remove_prefix(byte_count); + } +} + +Http2String HpackHuffmanDecoder::DebugString() const { + return bit_buffer_.DebugString(); +} + +} // namespace http2 diff --git a/chromium/net/third_party/quiche/src/http2/hpack/huffman/hpack_huffman_decoder.h b/chromium/net/third_party/quiche/src/http2/hpack/huffman/hpack_huffman_decoder.h new file mode 100644 index 00000000000..8e511d62ae9 --- /dev/null +++ b/chromium/net/third_party/quiche/src/http2/hpack/huffman/hpack_huffman_decoder.h @@ -0,0 +1,134 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef QUICHE_HTTP2_HPACK_HUFFMAN_HPACK_HUFFMAN_DECODER_H_ +#define QUICHE_HTTP2_HPACK_HUFFMAN_HPACK_HUFFMAN_DECODER_H_ + +// HpackHuffmanDecoder is an incremental decoder of strings that have been +// encoded using the Huffman table defined in the HPACK spec. +// By incremental, we mean that the HpackHuffmanDecoder::Decode method does +// not require the entire string to be provided, and can instead decode the +// string as fragments of it become available (e.g. as HPACK block fragments +// are received for decoding by HpackEntryDecoder). + +#include <stddef.h> + +#include <cstdint> +#include <iosfwd> + +#include "net/third_party/quiche/src/http2/platform/api/http2_export.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string_piece.h" + +namespace http2 { + +// HuffmanAccumulator is used to store bits during decoding, e.g. next N bits +// that have not yet been decoded, but have been extracted from the encoded +// string). An advantage of using a uint64 for the accumulator +// is that it has room for the bits of the longest code plus the bits of a full +// byte; that means that when adding more bits to the accumulator, it can always +// be done in whole bytes. For example, if we currently have 26 bits in the +// accumulator, and need more to decode the current symbol, we can add a whole +// byte to the accumulator, and not have to do juggling with adding 6 bits (to +// reach 30), and then keep track of the last two bits we've not been able to +// add to the accumulator. +typedef uint64_t HuffmanAccumulator; +typedef size_t HuffmanAccumulatorBitCount; + +// HuffmanBitBuffer stores the leading edge of bits to be decoded. The high +// order bit of accumulator_ is the next bit to be decoded. +class HTTP2_EXPORT_PRIVATE HuffmanBitBuffer { + public: + HuffmanBitBuffer(); + + // Prepare for decoding a new Huffman encoded string. + void Reset(); + + // Add as many whole bytes to the accumulator (accumulator_) as possible, + // returning the number of bytes added. + size_t AppendBytes(Http2StringPiece input); + + // Get the bits of the accumulator. + HuffmanAccumulator value() const { return accumulator_; } + + // Number of bits of the encoded string that are in the accumulator + // (accumulator_). + HuffmanAccumulatorBitCount count() const { return count_; } + + // Are there no bits in the accumulator? + bool IsEmpty() const { return count_ == 0; } + + // Number of additional bits that can be added to the accumulator. + HuffmanAccumulatorBitCount free_count() const; + + // Consume the leading |code_length| bits of the accumulator. + void ConsumeBits(HuffmanAccumulatorBitCount code_length); + + // Are the contents valid for the end of a Huffman encoded string? The RFC + // states that EOS (end-of-string) symbol must not be explicitly encoded in + // the bit stream, but any unused bits in the final byte must be set to the + // prefix of the EOS symbol, which is all 1 bits. So there can be at most 7 + // such bits. + // Returns true if the bit buffer is empty, or contains at most 7 bits, all + // of them 1. Otherwise returns false. + bool InputProperlyTerminated() const; + + Http2String DebugString() const; + + private: + HuffmanAccumulator accumulator_; + HuffmanAccumulatorBitCount count_; +}; + +inline std::ostream& operator<<(std::ostream& out, const HuffmanBitBuffer& v) { + return out << v.DebugString(); +} + +class HTTP2_EXPORT_PRIVATE HpackHuffmanDecoder { + public: + HpackHuffmanDecoder(); + ~HpackHuffmanDecoder(); + + // Prepare for decoding a new Huffman encoded string. + void Reset() { bit_buffer_.Reset(); } + + // Decode the portion of a HPACK Huffman encoded string that is in |input|, + // appending the decoded symbols into |*output|, stopping when more bits are + // needed to determine the next symbol, which/ means that the input has been + // drained, and also that the bit_buffer_ is empty or that the bits that are + // in it are not a whole symbol. + // If |input| is the start of a string, the caller must first call Reset. + // If |input| includes the end of the encoded string, the caller must call + // InputProperlyTerminated after Decode has returned true in order to + // determine if the encoded string was properly terminated. + // Returns false if something went wrong (e.g. the encoding contains the code + // EOS symbol). Otherwise returns true, in which case input has been fully + // decoded or buffered; in particular, if the low-order bit of the final byte + // of the input is not the last bit of an encoded symbol, then bit_buffer_ + // will contain the leading bits of the code for that symbol, but not the + // final bits of that code. + // Note that output should be empty, but that it is not cleared by Decode(). + bool Decode(Http2StringPiece input, Http2String* output); + + // Is what remains in the bit_buffer_ valid at the end of an encoded string? + // Call after passing the the final portion of a Huffman string to Decode, + // and getting true as the result. + bool InputProperlyTerminated() const { + return bit_buffer_.InputProperlyTerminated(); + } + + Http2String DebugString() const; + + private: + HuffmanBitBuffer bit_buffer_; +}; + +inline std::ostream& operator<<(std::ostream& out, + const HpackHuffmanDecoder& v) { + return out << v.DebugString(); +} + +} // namespace http2 + +#endif // QUICHE_HTTP2_HPACK_HUFFMAN_HPACK_HUFFMAN_DECODER_H_ diff --git a/chromium/net/third_party/quiche/src/http2/hpack/huffman/hpack_huffman_decoder_test.cc b/chromium/net/third_party/quiche/src/http2/hpack/huffman/hpack_huffman_decoder_test.cc new file mode 100644 index 00000000000..6c07ca5b55b --- /dev/null +++ b/chromium/net/third_party/quiche/src/http2/hpack/huffman/hpack_huffman_decoder_test.cc @@ -0,0 +1,245 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/http2/hpack/huffman/hpack_huffman_decoder.h" + +// Tests of HpackHuffmanDecoder and HuffmanBitBuffer. + +#include <iostream> + +#include "base/macros.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h" +#include "net/third_party/quiche/src/http2/decoder/decode_status.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_arraysize.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string_utils.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_test_helpers.h" +#include "net/third_party/quiche/src/http2/tools/random_decoder_test.h" + +using ::testing::AssertionResult; + +namespace http2 { +namespace test { +namespace { + +TEST(HuffmanBitBufferTest, Reset) { + HuffmanBitBuffer bb; + EXPECT_TRUE(bb.IsEmpty()); + EXPECT_TRUE(bb.InputProperlyTerminated()); + EXPECT_EQ(bb.count(), 0u); + EXPECT_EQ(bb.free_count(), 64u); + EXPECT_EQ(bb.value(), 0u); +} + +TEST(HuffmanBitBufferTest, AppendBytesAligned) { + Http2String s; + s.push_back('\x11'); + s.push_back('\x22'); + s.push_back('\x33'); + Http2StringPiece sp(s); + + HuffmanBitBuffer bb; + sp.remove_prefix(bb.AppendBytes(sp)); + EXPECT_TRUE(sp.empty()); + EXPECT_FALSE(bb.IsEmpty()) << bb; + EXPECT_FALSE(bb.InputProperlyTerminated()); + EXPECT_EQ(bb.count(), 24u) << bb; + EXPECT_EQ(bb.free_count(), 40u) << bb; + EXPECT_EQ(bb.value(), HuffmanAccumulator(0x112233) << 40) << bb; + + s.clear(); + s.push_back('\x44'); + sp = s; + + sp.remove_prefix(bb.AppendBytes(sp)); + EXPECT_TRUE(sp.empty()); + EXPECT_EQ(bb.count(), 32u) << bb; + EXPECT_EQ(bb.free_count(), 32u) << bb; + EXPECT_EQ(bb.value(), HuffmanAccumulator(0x11223344) << 32) << bb; + + s.clear(); + s.push_back('\x55'); + s.push_back('\x66'); + s.push_back('\x77'); + s.push_back('\x88'); + s.push_back('\x99'); + sp = s; + + sp.remove_prefix(bb.AppendBytes(sp)); + EXPECT_EQ(sp.size(), 1u); + EXPECT_EQ('\x99', sp[0]); + EXPECT_EQ(bb.count(), 64u) << bb; + EXPECT_EQ(bb.free_count(), 0u) << bb; + EXPECT_EQ(bb.value(), HuffmanAccumulator(0x1122334455667788LL)) << bb; + + sp.remove_prefix(bb.AppendBytes(sp)); + EXPECT_EQ(sp.size(), 1u); + EXPECT_EQ('\x99', sp[0]); + EXPECT_EQ(bb.count(), 64u) << bb; + EXPECT_EQ(bb.free_count(), 0u) << bb; + EXPECT_EQ(bb.value(), HuffmanAccumulator(0x1122334455667788LL)) << bb; +} + +TEST(HuffmanBitBufferTest, ConsumeBits) { + Http2String s; + s.push_back('\x11'); + s.push_back('\x22'); + s.push_back('\x33'); + Http2StringPiece sp(s); + + HuffmanBitBuffer bb; + sp.remove_prefix(bb.AppendBytes(sp)); + EXPECT_TRUE(sp.empty()); + + bb.ConsumeBits(1); + EXPECT_EQ(bb.count(), 23u) << bb; + EXPECT_EQ(bb.free_count(), 41u) << bb; + EXPECT_EQ(bb.value(), HuffmanAccumulator(0x112233) << 41) << bb; + + bb.ConsumeBits(20); + EXPECT_EQ(bb.count(), 3u) << bb; + EXPECT_EQ(bb.free_count(), 61u) << bb; + EXPECT_EQ(bb.value(), HuffmanAccumulator(0x3) << 61) << bb; +} + +TEST(HuffmanBitBufferTest, AppendBytesUnaligned) { + Http2String s; + s.push_back('\x11'); + s.push_back('\x22'); + s.push_back('\x33'); + s.push_back('\x44'); + s.push_back('\x55'); + s.push_back('\x66'); + s.push_back('\x77'); + s.push_back('\x88'); + s.push_back('\x99'); + s.push_back('\xaa'); + s.push_back('\xbb'); + s.push_back('\xcc'); + s.push_back('\xdd'); + Http2StringPiece sp(s); + + HuffmanBitBuffer bb; + sp.remove_prefix(bb.AppendBytes(sp)); + EXPECT_EQ(sp.size(), 5u); + EXPECT_FALSE(bb.InputProperlyTerminated()); + + bb.ConsumeBits(15); + EXPECT_EQ(bb.count(), 49u) << bb; + EXPECT_EQ(bb.free_count(), 15u) << bb; + + HuffmanAccumulator expected(0x1122334455667788); + expected <<= 15; + EXPECT_EQ(bb.value(), expected); + + sp.remove_prefix(bb.AppendBytes(sp)); + EXPECT_EQ(sp.size(), 4u); + EXPECT_EQ(bb.count(), 57u) << bb; + EXPECT_EQ(bb.free_count(), 7u) << bb; + + expected |= (HuffmanAccumulator(0x99) << 7); + EXPECT_EQ(bb.value(), expected) + << bb << std::hex << "\n actual: " << bb.value() + << "\n expected: " << expected; +} + +class HpackHuffmanDecoderTest : public RandomDecoderTest { + protected: + HpackHuffmanDecoderTest() { + // The decoder may return true, and its accumulator may be empty, at + // many boundaries while decoding, and yet the whole string hasn't + // been decoded. + stop_decode_on_done_ = false; + } + + DecodeStatus StartDecoding(DecodeBuffer* b) override { + input_bytes_seen_ = 0; + output_buffer_.clear(); + decoder_.Reset(); + return ResumeDecoding(b); + } + + DecodeStatus ResumeDecoding(DecodeBuffer* b) override { + input_bytes_seen_ += b->Remaining(); + Http2StringPiece sp(b->cursor(), b->Remaining()); + if (decoder_.Decode(sp, &output_buffer_)) { + b->AdvanceCursor(b->Remaining()); + // Successfully decoded (or buffered) the bytes in Http2StringPiece. + EXPECT_LE(input_bytes_seen_, input_bytes_expected_); + // Have we reached the end of the encoded string? + if (input_bytes_expected_ == input_bytes_seen_) { + if (decoder_.InputProperlyTerminated()) { + return DecodeStatus::kDecodeDone; + } else { + return DecodeStatus::kDecodeError; + } + } + return DecodeStatus::kDecodeInProgress; + } + return DecodeStatus::kDecodeError; + } + + HpackHuffmanDecoder decoder_; + Http2String output_buffer_; + size_t input_bytes_seen_; + size_t input_bytes_expected_; +}; + +TEST_F(HpackHuffmanDecoderTest, SpecRequestExamples) { + HpackHuffmanDecoder decoder; + Http2String test_table[] = { + Http2HexDecode("f1e3c2e5f23a6ba0ab90f4ff"), + "www.example.com", + Http2HexDecode("a8eb10649cbf"), + "no-cache", + Http2HexDecode("25a849e95ba97d7f"), + "custom-key", + Http2HexDecode("25a849e95bb8e8b4bf"), + "custom-value", + }; + for (size_t i = 0; i != HTTP2_ARRAYSIZE(test_table); i += 2) { + const Http2String& huffman_encoded(test_table[i]); + const Http2String& plain_string(test_table[i + 1]); + Http2String buffer; + decoder.Reset(); + EXPECT_TRUE(decoder.Decode(huffman_encoded, &buffer)) << decoder; + EXPECT_TRUE(decoder.InputProperlyTerminated()) << decoder; + EXPECT_EQ(buffer, plain_string); + } +} + +TEST_F(HpackHuffmanDecoderTest, SpecResponseExamples) { + HpackHuffmanDecoder decoder; + // clang-format off + Http2String test_table[] = { + Http2HexDecode("6402"), + "302", + Http2HexDecode("aec3771a4b"), + "private", + Http2HexDecode("d07abe941054d444a8200595040b8166" + "e082a62d1bff"), + "Mon, 21 Oct 2013 20:13:21 GMT", + Http2HexDecode("9d29ad171863c78f0b97c8e9ae82ae43" + "d3"), + "https://www.example.com", + Http2HexDecode("94e7821dd7f2e6c7b335dfdfcd5b3960" + "d5af27087f3672c1ab270fb5291f9587" + "316065c003ed4ee5b1063d5007"), + "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1", + }; + // clang-format on + for (size_t i = 0; i != HTTP2_ARRAYSIZE(test_table); i += 2) { + const Http2String& huffman_encoded(test_table[i]); + const Http2String& plain_string(test_table[i + 1]); + Http2String buffer; + decoder.Reset(); + EXPECT_TRUE(decoder.Decode(huffman_encoded, &buffer)) << decoder; + EXPECT_TRUE(decoder.InputProperlyTerminated()) << decoder; + EXPECT_EQ(buffer, plain_string); + } +} + +} // namespace +} // namespace test +} // namespace http2 diff --git a/chromium/net/third_party/quiche/src/http2/hpack/huffman/hpack_huffman_encoder.cc b/chromium/net/third_party/quiche/src/http2/hpack/huffman/hpack_huffman_encoder.cc new file mode 100644 index 00000000000..9f561eedf46 --- /dev/null +++ b/chromium/net/third_party/quiche/src/http2/hpack/huffman/hpack_huffman_encoder.cc @@ -0,0 +1,110 @@ +// Copyright (c) 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/http2/hpack/huffman/hpack_huffman_encoder.h" + +#include "base/logging.h" +#include "net/third_party/quiche/src/http2/hpack/huffman/huffman_spec_tables.h" + +// TODO(jamessynge): Remove use of binary literals, that is a C++ 14 feature. + +namespace http2 { + +size_t ExactHuffmanSize(Http2StringPiece plain) { + size_t bits = 0; + for (const uint8_t c : plain) { + bits += HuffmanSpecTables::kCodeLengths[c]; + } + return (bits + 7) / 8; +} + +size_t BoundedHuffmanSize(Http2StringPiece plain) { + // TODO(jamessynge): Determine whether we should set the min size for Huffman + // encoding much higher (i.e. if less than N, then the savings isn't worth + // the cost of encoding and decoding). Of course, we need to decide on a + // value function, which might be throughput on a full load test, or a + // microbenchmark of the time to encode and then decode a HEADERS frame, + // possibly with the cost of crypto included (i.e. crypto is going to have + // a fairly constant per-byte cost, so reducing the number of bytes in-transit + // reduces the number that must be encrypted and later decrypted). + if (plain.size() < 3) { + // Huffman encoded string can't be smaller than the plain size for very + // short strings. + return plain.size(); + } + // TODO(jamessynge): Measure whether this can be done more efficiently with + // nested loops (e.g. make exact measurement of 8 bytes, then check if min + // remaining is too long). + // Compute the number of bits in an encoding that is shorter than the plain + // string (i.e. the number of bits in a string 1 byte shorter than plain), + // and use this as the limit of the size of the encoding. + const size_t limit_bits = (plain.size() - 1) * 8; + // The shortest code length in the Huffman table of the HPACK spec has 5 bits + // (e.g. for 0, 1, a and e). + const size_t min_code_length = 5; + // We can therefore say that all plain text bytes whose code length we've not + // yet looked up will take at least 5 bits. + size_t min_bits_remaining = plain.size() * min_code_length; + size_t bits = 0; + for (const uint8_t c : plain) { + bits += HuffmanSpecTables::kCodeLengths[c]; + min_bits_remaining -= min_code_length; + // If our minimum estimate of the total number of bits won't yield an + // encoding shorter the plain text, let's bail. + const size_t minimum_bits_total = bits + min_bits_remaining; + if (minimum_bits_total > limit_bits) { + bits += min_bits_remaining; + break; + } + } + return (bits + 7) / 8; +} + +void HuffmanEncode(Http2StringPiece plain, Http2String* huffman) { + DCHECK(huffman != nullptr); + huffman->clear(); // Note that this doesn't release memory. + uint64_t bit_buffer = 0; // High-bit is next bit to output. Not clear if that + // is more performant than having the low-bit be the + // last to be output. + size_t bits_unused = 64; // Number of bits available for the next code. + for (uint8_t c : plain) { + size_t code_length = HuffmanSpecTables::kCodeLengths[c]; + if (bits_unused < code_length) { + // There isn't enough room in bit_buffer for the code of c. + // Flush until bits_unused > 56 (i.e. 64 - 8). + do { + char h = static_cast<char>(bit_buffer >> 56); + bit_buffer <<= 8; + bits_unused += 8; + // Perhaps would be more efficient if we populated an array of chars, + // so we don't have to call push_back each time. Reconsider if used + // for production. + huffman->push_back(h); + } while (bits_unused <= 56); + } + uint64_t code = HuffmanSpecTables::kRightCodes[c]; + size_t shift_by = bits_unused - code_length; + bit_buffer |= (code << shift_by); + bits_unused -= code_length; + } + // bit_buffer contains (64-bits_unused) bits that still need to be flushed. + // Output whole bytes until we don't have any whole bytes left. + size_t bits_used = 64 - bits_unused; + while (bits_used >= 8) { + char h = static_cast<char>(bit_buffer >> 56); + bit_buffer <<= 8; + bits_used -= 8; + huffman->push_back(h); + } + if (bits_used > 0) { + // We have less than a byte left to output. The spec calls for padding out + // the final byte with the leading bits of the EOS symbol (30 1-bits). + constexpr uint64_t leading_eos_bits = 0b11111111; + bit_buffer |= (leading_eos_bits << (56 - bits_used)); + char h = static_cast<char>(bit_buffer >> 56); + huffman->push_back(h); + } +} + +} // namespace http2 diff --git a/chromium/net/third_party/quiche/src/http2/hpack/huffman/hpack_huffman_encoder.h b/chromium/net/third_party/quiche/src/http2/hpack/huffman/hpack_huffman_encoder.h new file mode 100644 index 00000000000..bf6b1b23b34 --- /dev/null +++ b/chromium/net/third_party/quiche/src/http2/hpack/huffman/hpack_huffman_encoder.h @@ -0,0 +1,40 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef QUICHE_HTTP2_HPACK_HUFFMAN_HPACK_HUFFMAN_ENCODER_H_ +#define QUICHE_HTTP2_HPACK_HUFFMAN_HPACK_HUFFMAN_ENCODER_H_ + +// Functions supporting the encoding of strings using the HPACK-defined Huffman +// table. + +#include <cstddef> // For size_t + +#include "net/third_party/quiche/src/http2/platform/api/http2_export.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string_piece.h" + +namespace http2 { + +// Returns the size of the Huffman encoding of |plain|, which may be greater +// than plain.size(). Mostly present for testing. +HTTP2_EXPORT_PRIVATE size_t ExactHuffmanSize(Http2StringPiece plain); + +// Returns the size of the Huffman encoding of |plain|, unless it is greater +// than or equal to plain.size(), in which case a value greater than or equal to +// plain.size() is returned. The advantage of this over ExactHuffmanSize is that +// it doesn't read as much of the input string in the event that the string is +// not compressible by HuffmanEncode (i.e. when the encoding is longer than the +// original string, it stops reading the input string as soon as it knows that). +HTTP2_EXPORT_PRIVATE size_t BoundedHuffmanSize(Http2StringPiece plain); + +// Encode the plain text string |plain| with the Huffman encoding defined in +// the HPACK RFC, 7541. |*huffman| does not have to be empty, it is cleared at +// the beginning of this function. This allows reusing the same string object +// across multiple invocations. +HTTP2_EXPORT_PRIVATE void HuffmanEncode(Http2StringPiece plain, + Http2String* huffman); + +} // namespace http2 + +#endif // QUICHE_HTTP2_HPACK_HUFFMAN_HPACK_HUFFMAN_ENCODER_H_ diff --git a/chromium/net/third_party/quiche/src/http2/hpack/huffman/hpack_huffman_encoder_test.cc b/chromium/net/third_party/quiche/src/http2/hpack/huffman/hpack_huffman_encoder_test.cc new file mode 100644 index 00000000000..ccb6983bc66 --- /dev/null +++ b/chromium/net/third_party/quiche/src/http2/hpack/huffman/hpack_huffman_encoder_test.cc @@ -0,0 +1,97 @@ +// Copyright (c) 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/http2/hpack/huffman/hpack_huffman_encoder.h" + +#include "base/macros.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_arraysize.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string_utils.h" + +namespace http2 { +namespace { + +TEST(HuffmanEncoderTest, SpecRequestExamples) { + Http2String test_table[] = { + Http2HexDecode("f1e3c2e5f23a6ba0ab90f4ff"), + "www.example.com", + Http2HexDecode("a8eb10649cbf"), + "no-cache", + Http2HexDecode("25a849e95ba97d7f"), + "custom-key", + Http2HexDecode("25a849e95bb8e8b4bf"), + "custom-value", + }; + for (size_t i = 0; i != HTTP2_ARRAYSIZE(test_table); i += 2) { + const Http2String& huffman_encoded(test_table[i]); + const Http2String& plain_string(test_table[i + 1]); + EXPECT_EQ(ExactHuffmanSize(plain_string), huffman_encoded.size()); + EXPECT_EQ(BoundedHuffmanSize(plain_string), huffman_encoded.size()); + Http2String buffer; + buffer.reserve(); + HuffmanEncode(plain_string, &buffer); + EXPECT_EQ(buffer, huffman_encoded) << "Error encoding " << plain_string; + } +} + +TEST(HuffmanEncoderTest, SpecResponseExamples) { + // clang-format off + Http2String test_table[] = { + Http2HexDecode("6402"), + "302", + Http2HexDecode("aec3771a4b"), + "private", + Http2HexDecode("d07abe941054d444a8200595040b8166" + "e082a62d1bff"), + "Mon, 21 Oct 2013 20:13:21 GMT", + Http2HexDecode("9d29ad171863c78f0b97c8e9ae82ae43" + "d3"), + "https://www.example.com", + Http2HexDecode("94e7821dd7f2e6c7b335dfdfcd5b3960" + "d5af27087f3672c1ab270fb5291f9587" + "316065c003ed4ee5b1063d5007"), + "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1", + }; + // clang-format on + for (size_t i = 0; i != HTTP2_ARRAYSIZE(test_table); i += 2) { + const Http2String& huffman_encoded(test_table[i]); + const Http2String& plain_string(test_table[i + 1]); + EXPECT_EQ(ExactHuffmanSize(plain_string), huffman_encoded.size()); + EXPECT_EQ(BoundedHuffmanSize(plain_string), huffman_encoded.size()); + Http2String buffer; + buffer.reserve(huffman_encoded.size()); + const size_t capacity = buffer.capacity(); + HuffmanEncode(plain_string, &buffer); + EXPECT_EQ(buffer, huffman_encoded) << "Error encoding " << plain_string; + EXPECT_EQ(capacity, buffer.capacity()); + } +} + +TEST(HuffmanEncoderTest, EncodedSizeAgreesWithEncodeString) { + Http2String test_table[] = { + "", + "Mon, 21 Oct 2013 20:13:21 GMT", + "https://www.example.com", + "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1", + Http2String(1, '\0'), + Http2String("foo\0bar", 7), + Http2String(256, '\0'), + }; + // Modify last |test_table| entry to cover all codes. + for (size_t i = 0; i != 256; ++i) { + test_table[HTTP2_ARRAYSIZE(test_table) - 1][i] = static_cast<char>(i); + } + + for (size_t i = 0; i != HTTP2_ARRAYSIZE(test_table); ++i) { + const Http2String& plain_string = test_table[i]; + Http2String huffman_encoded; + HuffmanEncode(plain_string, &huffman_encoded); + EXPECT_EQ(huffman_encoded.size(), ExactHuffmanSize(plain_string)); + EXPECT_LE(BoundedHuffmanSize(plain_string), plain_string.size()); + EXPECT_LE(BoundedHuffmanSize(plain_string), ExactHuffmanSize(plain_string)); + } +} + +} // namespace +} // namespace http2 diff --git a/chromium/net/third_party/quiche/src/http2/hpack/huffman/hpack_huffman_transcoder_test.cc b/chromium/net/third_party/quiche/src/http2/hpack/huffman/hpack_huffman_transcoder_test.cc new file mode 100644 index 00000000000..cfd73b515ad --- /dev/null +++ b/chromium/net/third_party/quiche/src/http2/hpack/huffman/hpack_huffman_transcoder_test.cc @@ -0,0 +1,184 @@ +// Copyright (c) 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// A test of roundtrips through the encoder and decoder. + +#include <stddef.h> + +#include "testing/gtest/include/gtest/gtest.h" +#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h" +#include "net/third_party/quiche/src/http2/decoder/decode_status.h" +#include "net/third_party/quiche/src/http2/hpack/huffman/hpack_huffman_decoder.h" +#include "net/third_party/quiche/src/http2/hpack/huffman/hpack_huffman_encoder.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string_piece.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string_utils.h" +#include "net/third_party/quiche/src/http2/tools/random_decoder_test.h" + +using ::testing::AssertionResult; +using ::testing::AssertionSuccess; +using ::testing::tuple; + +namespace http2 { +namespace test { +namespace { + +Http2String GenAsciiNonControlSet() { + Http2String s; + const char space = ' '; // First character after the control characters: 0x20 + const char del = 127; // First character after the non-control characters. + for (char c = space; c < del; ++c) { + s.push_back(c); + } + return s; +} + +class HpackHuffmanTranscoderTest : public RandomDecoderTest { + protected: + HpackHuffmanTranscoderTest() + : ascii_non_control_set_(GenAsciiNonControlSet()) { + // The decoder may return true, and its accumulator may be empty, at + // many boundaries while decoding, and yet the whole string hasn't + // been decoded. + stop_decode_on_done_ = false; + } + + DecodeStatus StartDecoding(DecodeBuffer* b) override { + input_bytes_seen_ = 0; + output_buffer_.clear(); + decoder_.Reset(); + return ResumeDecoding(b); + } + + DecodeStatus ResumeDecoding(DecodeBuffer* b) override { + input_bytes_seen_ += b->Remaining(); + Http2StringPiece sp(b->cursor(), b->Remaining()); + if (decoder_.Decode(sp, &output_buffer_)) { + b->AdvanceCursor(b->Remaining()); + // Successfully decoded (or buffered) the bytes in Http2StringPiece. + EXPECT_LE(input_bytes_seen_, input_bytes_expected_); + // Have we reached the end of the encoded string? + if (input_bytes_expected_ == input_bytes_seen_) { + if (decoder_.InputProperlyTerminated()) { + return DecodeStatus::kDecodeDone; + } else { + return DecodeStatus::kDecodeError; + } + } + return DecodeStatus::kDecodeInProgress; + } + return DecodeStatus::kDecodeError; + } + + AssertionResult TranscodeAndValidateSeveralWays( + Http2StringPiece plain, + Http2StringPiece expected_huffman) { + Http2String encoded; + HuffmanEncode(plain, &encoded); + if (expected_huffman.size() > 0 || plain.empty()) { + VERIFY_EQ(encoded, expected_huffman); + } + input_bytes_expected_ = encoded.size(); + auto validator = [plain, this]() -> AssertionResult { + VERIFY_EQ(output_buffer_.size(), plain.size()); + VERIFY_EQ(output_buffer_, plain); + return AssertionSuccess(); + }; + DecodeBuffer db(encoded); + bool return_non_zero_on_first = false; + return DecodeAndValidateSeveralWays(&db, return_non_zero_on_first, + ValidateDoneAndEmpty(validator)); + } + + AssertionResult TranscodeAndValidateSeveralWays(Http2StringPiece plain) { + return TranscodeAndValidateSeveralWays(plain, ""); + } + + Http2String RandomAsciiNonControlString(int length) { + return Random().RandStringWithAlphabet(length, ascii_non_control_set_); + } + + Http2String RandomBytes(int length) { return Random().RandString(length); } + + const Http2String ascii_non_control_set_; + HpackHuffmanDecoder decoder_; + Http2String output_buffer_; + size_t input_bytes_seen_; + size_t input_bytes_expected_; +}; + +TEST_F(HpackHuffmanTranscoderTest, RoundTripRandomAsciiNonControlString) { + for (size_t length = 0; length != 20; length++) { + const Http2String s = RandomAsciiNonControlString(length); + ASSERT_TRUE(TranscodeAndValidateSeveralWays(s)) + << "Unable to decode:\n\n" + << Http2HexDump(s) << "\n\noutput_buffer_:\n" + << Http2HexDump(output_buffer_); + } +} + +TEST_F(HpackHuffmanTranscoderTest, RoundTripRandomBytes) { + for (size_t length = 0; length != 20; length++) { + const Http2String s = RandomBytes(length); + ASSERT_TRUE(TranscodeAndValidateSeveralWays(s)) + << "Unable to decode:\n\n" + << Http2HexDump(s) << "\n\noutput_buffer_:\n" + << Http2HexDump(output_buffer_); + } +} + +// Two parameters: decoder choice, and the character to round-trip. +class HpackHuffmanTranscoderAdjacentCharTest + : public HpackHuffmanTranscoderTest, + public ::testing::WithParamInterface<int> { + protected: + HpackHuffmanTranscoderAdjacentCharTest() + : c_(static_cast<char>(GetParam())) {} + + const char c_; +}; + +INSTANTIATE_TEST_CASE_P(HpackHuffmanTranscoderAdjacentCharTest, + HpackHuffmanTranscoderAdjacentCharTest, + ::testing::Range(0, 256)); + +// Test c_ adjacent to every other character, both before and after. +TEST_P(HpackHuffmanTranscoderAdjacentCharTest, RoundTripAdjacentChar) { + Http2String s; + for (int a = 0; a < 256; ++a) { + s.push_back(static_cast<char>(a)); + s.push_back(c_); + s.push_back(static_cast<char>(a)); + } + ASSERT_TRUE(TranscodeAndValidateSeveralWays(s)); +} + +// Two parameters: character to repeat, number of repeats. +class HpackHuffmanTranscoderRepeatedCharTest + : public HpackHuffmanTranscoderTest, + public ::testing::WithParamInterface<tuple<int, int>> { + protected: + HpackHuffmanTranscoderRepeatedCharTest() + : c_(static_cast<char>(::testing::get<0>(GetParam()))), + length_(::testing::get<1>(GetParam())) {} + Http2String MakeString() { return Http2String(length_, c_); } + + private: + const char c_; + const size_t length_; +}; + +INSTANTIATE_TEST_CASE_P( + HpackHuffmanTranscoderRepeatedCharTest, + HpackHuffmanTranscoderRepeatedCharTest, + ::testing::Combine(::testing::Range(0, 256), + ::testing::Values(1, 2, 3, 4, 8, 16, 32))); + +TEST_P(HpackHuffmanTranscoderRepeatedCharTest, RoundTripRepeatedChar) { + ASSERT_TRUE(TranscodeAndValidateSeveralWays(MakeString())); +} + +} // namespace +} // namespace test +} // namespace http2 diff --git a/chromium/net/third_party/quiche/src/http2/hpack/huffman/huffman_spec_tables.cc b/chromium/net/third_party/quiche/src/http2/hpack/huffman/huffman_spec_tables.cc new file mode 100644 index 00000000000..27707b98120 --- /dev/null +++ b/chromium/net/third_party/quiche/src/http2/hpack/huffman/huffman_spec_tables.cc @@ -0,0 +1,578 @@ +// Copyright (c) 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/http2/hpack/huffman/huffman_spec_tables.h" + +namespace http2 { + +// clang-format off +// static +const uint8_t HuffmanSpecTables::kCodeLengths[] = { + 13, 23, 28, 28, 28, 28, 28, 28, // 0 - 7 + 28, 24, 30, 28, 28, 30, 28, 28, // 8 - 15 + 28, 28, 28, 28, 28, 28, 30, 28, // 16 - 23 + 28, 28, 28, 28, 28, 28, 28, 28, // 24 - 31 + 6, 10, 10, 12, 13, 6, 8, 11, // 32 - 39 + 10, 10, 8, 11, 8, 6, 6, 6, // 40 - 47 + 5, 5, 5, 6, 6, 6, 6, 6, // 48 - 55 + 6, 6, 7, 8, 15, 6, 12, 10, // 56 - 63 + 13, 6, 7, 7, 7, 7, 7, 7, // 64 - 71 + 7, 7, 7, 7, 7, 7, 7, 7, // 72 - 79 + 7, 7, 7, 7, 7, 7, 7, 7, // 80 - 87 + 8, 7, 8, 13, 19, 13, 14, 6, // 88 - 95 + 15, 5, 6, 5, 6, 5, 6, 6, // 96 - 103 + 6, 5, 7, 7, 6, 6, 6, 5, // 104 - 111 + 6, 7, 6, 5, 5, 6, 7, 7, // 112 - 119 + 7, 7, 7, 15, 11, 14, 13, 28, // 120 - 127 + 20, 22, 20, 20, 22, 22, 22, 23, // 128 - 135 + 22, 23, 23, 23, 23, 23, 24, 23, // 136 - 143 + 24, 24, 22, 23, 24, 23, 23, 23, // 144 - 151 + 23, 21, 22, 23, 22, 23, 23, 24, // 152 - 159 + 22, 21, 20, 22, 22, 23, 23, 21, // 160 - 167 + 23, 22, 22, 24, 21, 22, 23, 23, // 168 - 175 + 21, 21, 22, 21, 23, 22, 23, 23, // 176 - 183 + 20, 22, 22, 22, 23, 22, 22, 23, // 184 - 191 + 26, 26, 20, 19, 22, 23, 22, 25, // 192 - 199 + 26, 26, 26, 27, 27, 26, 24, 25, // 200 - 207 + 19, 21, 26, 27, 27, 26, 27, 24, // 208 - 215 + 21, 21, 26, 26, 28, 27, 27, 27, // 216 - 223 + 20, 24, 20, 21, 22, 21, 21, 23, // 224 - 231 + 22, 22, 25, 25, 24, 24, 26, 23, // 232 - 239 + 26, 27, 26, 26, 27, 27, 27, 27, // 240 - 247 + 27, 28, 27, 27, 27, 27, 27, 26, // 248 - 255 + 30, // 256 +}; + +// TODO(jamessynge): Remove use of binary literals, that is a C++ 14 feature. + +// Uncomment these codes if needed for generating Huffman output, as opposed +// to decoding Huffman input. +/* +// The encoding of each symbol, left justified (as printed), which means that +// the first bit of the encoding is the high-order bit of the uint32. +// static +const uint32_t HuffmanSpecTables::kLeftCodes[] = { + 0b11111111110000000000000000000000, // 0x00 + 0b11111111111111111011000000000000, // 0x01 + 0b11111111111111111111111000100000, // 0x02 + 0b11111111111111111111111000110000, // 0x03 + 0b11111111111111111111111001000000, // 0x04 + 0b11111111111111111111111001010000, // 0x05 + 0b11111111111111111111111001100000, // 0x06 + 0b11111111111111111111111001110000, // 0x07 + 0b11111111111111111111111010000000, // 0x08 + 0b11111111111111111110101000000000, // 0x09 + 0b11111111111111111111111111110000, // 0x0a + 0b11111111111111111111111010010000, // 0x0b + 0b11111111111111111111111010100000, // 0x0c + 0b11111111111111111111111111110100, // 0x0d + 0b11111111111111111111111010110000, // 0x0e + 0b11111111111111111111111011000000, // 0x0f + 0b11111111111111111111111011010000, // 0x10 + 0b11111111111111111111111011100000, // 0x11 + 0b11111111111111111111111011110000, // 0x12 + 0b11111111111111111111111100000000, // 0x13 + 0b11111111111111111111111100010000, // 0x14 + 0b11111111111111111111111100100000, // 0x15 + 0b11111111111111111111111111111000, // 0x16 + 0b11111111111111111111111100110000, // 0x17 + 0b11111111111111111111111101000000, // 0x18 + 0b11111111111111111111111101010000, // 0x19 + 0b11111111111111111111111101100000, // 0x1a + 0b11111111111111111111111101110000, // 0x1b + 0b11111111111111111111111110000000, // 0x1c + 0b11111111111111111111111110010000, // 0x1d + 0b11111111111111111111111110100000, // 0x1e + 0b11111111111111111111111110110000, // 0x1f + 0b01010000000000000000000000000000, // 0x20 + 0b11111110000000000000000000000000, // '!' + 0b11111110010000000000000000000000, // '\"' + 0b11111111101000000000000000000000, // '#' + 0b11111111110010000000000000000000, // '$' + 0b01010100000000000000000000000000, // '%' + 0b11111000000000000000000000000000, // '&' + 0b11111111010000000000000000000000, // '\'' + 0b11111110100000000000000000000000, // '(' + 0b11111110110000000000000000000000, // ')' + 0b11111001000000000000000000000000, // '*' + 0b11111111011000000000000000000000, // '+' + 0b11111010000000000000000000000000, // ',' + 0b01011000000000000000000000000000, // '-' + 0b01011100000000000000000000000000, // '.' + 0b01100000000000000000000000000000, // '/' + 0b00000000000000000000000000000000, // '0' + 0b00001000000000000000000000000000, // '1' + 0b00010000000000000000000000000000, // '2' + 0b01100100000000000000000000000000, // '3' + 0b01101000000000000000000000000000, // '4' + 0b01101100000000000000000000000000, // '5' + 0b01110000000000000000000000000000, // '6' + 0b01110100000000000000000000000000, // '7' + 0b01111000000000000000000000000000, // '8' + 0b01111100000000000000000000000000, // '9' + 0b10111000000000000000000000000000, // ':' + 0b11111011000000000000000000000000, // ';' + 0b11111111111110000000000000000000, // '<' + 0b10000000000000000000000000000000, // '=' + 0b11111111101100000000000000000000, // '>' + 0b11111111000000000000000000000000, // '?' + 0b11111111110100000000000000000000, // '@' + 0b10000100000000000000000000000000, // 'A' + 0b10111010000000000000000000000000, // 'B' + 0b10111100000000000000000000000000, // 'C' + 0b10111110000000000000000000000000, // 'D' + 0b11000000000000000000000000000000, // 'E' + 0b11000010000000000000000000000000, // 'F' + 0b11000100000000000000000000000000, // 'G' + 0b11000110000000000000000000000000, // 'H' + 0b11001000000000000000000000000000, // 'I' + 0b11001010000000000000000000000000, // 'J' + 0b11001100000000000000000000000000, // 'K' + 0b11001110000000000000000000000000, // 'L' + 0b11010000000000000000000000000000, // 'M' + 0b11010010000000000000000000000000, // 'N' + 0b11010100000000000000000000000000, // 'O' + 0b11010110000000000000000000000000, // 'P' + 0b11011000000000000000000000000000, // 'Q' + 0b11011010000000000000000000000000, // 'R' + 0b11011100000000000000000000000000, // 'S' + 0b11011110000000000000000000000000, // 'T' + 0b11100000000000000000000000000000, // 'U' + 0b11100010000000000000000000000000, // 'V' + 0b11100100000000000000000000000000, // 'W' + 0b11111100000000000000000000000000, // 'X' + 0b11100110000000000000000000000000, // 'Y' + 0b11111101000000000000000000000000, // 'Z' + 0b11111111110110000000000000000000, // '[' + 0b11111111111111100000000000000000, // '\\' + 0b11111111111000000000000000000000, // ']' + 0b11111111111100000000000000000000, // '^' + 0b10001000000000000000000000000000, // '_' + 0b11111111111110100000000000000000, // '`' + 0b00011000000000000000000000000000, // 'a' + 0b10001100000000000000000000000000, // 'b' + 0b00100000000000000000000000000000, // 'c' + 0b10010000000000000000000000000000, // 'd' + 0b00101000000000000000000000000000, // 'e' + 0b10010100000000000000000000000000, // 'f' + 0b10011000000000000000000000000000, // 'g' + 0b10011100000000000000000000000000, // 'h' + 0b00110000000000000000000000000000, // 'i' + 0b11101000000000000000000000000000, // 'j' + 0b11101010000000000000000000000000, // 'k' + 0b10100000000000000000000000000000, // 'l' + 0b10100100000000000000000000000000, // 'm' + 0b10101000000000000000000000000000, // 'n' + 0b00111000000000000000000000000000, // 'o' + 0b10101100000000000000000000000000, // 'p' + 0b11101100000000000000000000000000, // 'q' + 0b10110000000000000000000000000000, // 'r' + 0b01000000000000000000000000000000, // 's' + 0b01001000000000000000000000000000, // 't' + 0b10110100000000000000000000000000, // 'u' + 0b11101110000000000000000000000000, // 'v' + 0b11110000000000000000000000000000, // 'w' + 0b11110010000000000000000000000000, // 'x' + 0b11110100000000000000000000000000, // 'y' + 0b11110110000000000000000000000000, // 'z' + 0b11111111111111000000000000000000, // '{' + 0b11111111100000000000000000000000, // '|' + 0b11111111111101000000000000000000, // '}' + 0b11111111111010000000000000000000, // '~' + 0b11111111111111111111111111000000, // 0x7f + 0b11111111111111100110000000000000, // 0x80 + 0b11111111111111110100100000000000, // 0x81 + 0b11111111111111100111000000000000, // 0x82 + 0b11111111111111101000000000000000, // 0x83 + 0b11111111111111110100110000000000, // 0x84 + 0b11111111111111110101000000000000, // 0x85 + 0b11111111111111110101010000000000, // 0x86 + 0b11111111111111111011001000000000, // 0x87 + 0b11111111111111110101100000000000, // 0x88 + 0b11111111111111111011010000000000, // 0x89 + 0b11111111111111111011011000000000, // 0x8a + 0b11111111111111111011100000000000, // 0x8b + 0b11111111111111111011101000000000, // 0x8c + 0b11111111111111111011110000000000, // 0x8d + 0b11111111111111111110101100000000, // 0x8e + 0b11111111111111111011111000000000, // 0x8f + 0b11111111111111111110110000000000, // 0x90 + 0b11111111111111111110110100000000, // 0x91 + 0b11111111111111110101110000000000, // 0x92 + 0b11111111111111111100000000000000, // 0x93 + 0b11111111111111111110111000000000, // 0x94 + 0b11111111111111111100001000000000, // 0x95 + 0b11111111111111111100010000000000, // 0x96 + 0b11111111111111111100011000000000, // 0x97 + 0b11111111111111111100100000000000, // 0x98 + 0b11111111111111101110000000000000, // 0x99 + 0b11111111111111110110000000000000, // 0x9a + 0b11111111111111111100101000000000, // 0x9b + 0b11111111111111110110010000000000, // 0x9c + 0b11111111111111111100110000000000, // 0x9d + 0b11111111111111111100111000000000, // 0x9e + 0b11111111111111111110111100000000, // 0x9f + 0b11111111111111110110100000000000, // 0xa0 + 0b11111111111111101110100000000000, // 0xa1 + 0b11111111111111101001000000000000, // 0xa2 + 0b11111111111111110110110000000000, // 0xa3 + 0b11111111111111110111000000000000, // 0xa4 + 0b11111111111111111101000000000000, // 0xa5 + 0b11111111111111111101001000000000, // 0xa6 + 0b11111111111111101111000000000000, // 0xa7 + 0b11111111111111111101010000000000, // 0xa8 + 0b11111111111111110111010000000000, // 0xa9 + 0b11111111111111110111100000000000, // 0xaa + 0b11111111111111111111000000000000, // 0xab + 0b11111111111111101111100000000000, // 0xac + 0b11111111111111110111110000000000, // 0xad + 0b11111111111111111101011000000000, // 0xae + 0b11111111111111111101100000000000, // 0xaf + 0b11111111111111110000000000000000, // 0xb0 + 0b11111111111111110000100000000000, // 0xb1 + 0b11111111111111111000000000000000, // 0xb2 + 0b11111111111111110001000000000000, // 0xb3 + 0b11111111111111111101101000000000, // 0xb4 + 0b11111111111111111000010000000000, // 0xb5 + 0b11111111111111111101110000000000, // 0xb6 + 0b11111111111111111101111000000000, // 0xb7 + 0b11111111111111101010000000000000, // 0xb8 + 0b11111111111111111000100000000000, // 0xb9 + 0b11111111111111111000110000000000, // 0xba + 0b11111111111111111001000000000000, // 0xbb + 0b11111111111111111110000000000000, // 0xbc + 0b11111111111111111001010000000000, // 0xbd + 0b11111111111111111001100000000000, // 0xbe + 0b11111111111111111110001000000000, // 0xbf + 0b11111111111111111111100000000000, // 0xc0 + 0b11111111111111111111100001000000, // 0xc1 + 0b11111111111111101011000000000000, // 0xc2 + 0b11111111111111100010000000000000, // 0xc3 + 0b11111111111111111001110000000000, // 0xc4 + 0b11111111111111111110010000000000, // 0xc5 + 0b11111111111111111010000000000000, // 0xc6 + 0b11111111111111111111011000000000, // 0xc7 + 0b11111111111111111111100010000000, // 0xc8 + 0b11111111111111111111100011000000, // 0xc9 + 0b11111111111111111111100100000000, // 0xca + 0b11111111111111111111101111000000, // 0xcb + 0b11111111111111111111101111100000, // 0xcc + 0b11111111111111111111100101000000, // 0xcd + 0b11111111111111111111000100000000, // 0xce + 0b11111111111111111111011010000000, // 0xcf + 0b11111111111111100100000000000000, // 0xd0 + 0b11111111111111110001100000000000, // 0xd1 + 0b11111111111111111111100110000000, // 0xd2 + 0b11111111111111111111110000000000, // 0xd3 + 0b11111111111111111111110000100000, // 0xd4 + 0b11111111111111111111100111000000, // 0xd5 + 0b11111111111111111111110001000000, // 0xd6 + 0b11111111111111111111001000000000, // 0xd7 + 0b11111111111111110010000000000000, // 0xd8 + 0b11111111111111110010100000000000, // 0xd9 + 0b11111111111111111111101000000000, // 0xda + 0b11111111111111111111101001000000, // 0xdb + 0b11111111111111111111111111010000, // 0xdc + 0b11111111111111111111110001100000, // 0xdd + 0b11111111111111111111110010000000, // 0xde + 0b11111111111111111111110010100000, // 0xdf + 0b11111111111111101100000000000000, // 0xe0 + 0b11111111111111111111001100000000, // 0xe1 + 0b11111111111111101101000000000000, // 0xe2 + 0b11111111111111110011000000000000, // 0xe3 + 0b11111111111111111010010000000000, // 0xe4 + 0b11111111111111110011100000000000, // 0xe5 + 0b11111111111111110100000000000000, // 0xe6 + 0b11111111111111111110011000000000, // 0xe7 + 0b11111111111111111010100000000000, // 0xe8 + 0b11111111111111111010110000000000, // 0xe9 + 0b11111111111111111111011100000000, // 0xea + 0b11111111111111111111011110000000, // 0xeb + 0b11111111111111111111010000000000, // 0xec + 0b11111111111111111111010100000000, // 0xed + 0b11111111111111111111101010000000, // 0xee + 0b11111111111111111110100000000000, // 0xef + 0b11111111111111111111101011000000, // 0xf0 + 0b11111111111111111111110011000000, // 0xf1 + 0b11111111111111111111101100000000, // 0xf2 + 0b11111111111111111111101101000000, // 0xf3 + 0b11111111111111111111110011100000, // 0xf4 + 0b11111111111111111111110100000000, // 0xf5 + 0b11111111111111111111110100100000, // 0xf6 + 0b11111111111111111111110101000000, // 0xf7 + 0b11111111111111111111110101100000, // 0xf8 + 0b11111111111111111111111111100000, // 0xf9 + 0b11111111111111111111110110000000, // 0xfa + 0b11111111111111111111110110100000, // 0xfb + 0b11111111111111111111110111000000, // 0xfc + 0b11111111111111111111110111100000, // 0xfd + 0b11111111111111111111111000000000, // 0xfe + 0b11111111111111111111101110000000, // 0xff + 0b11111111111111111111111111111100, // 0x100 +}; +*/ + +// static +const uint32_t HuffmanSpecTables::kRightCodes[] = { + 0b00000000000000000001111111111000, // 0x00 + 0b00000000011111111111111111011000, // 0x01 + 0b00001111111111111111111111100010, // 0x02 + 0b00001111111111111111111111100011, // 0x03 + 0b00001111111111111111111111100100, // 0x04 + 0b00001111111111111111111111100101, // 0x05 + 0b00001111111111111111111111100110, // 0x06 + 0b00001111111111111111111111100111, // 0x07 + 0b00001111111111111111111111101000, // 0x08 + 0b00000000111111111111111111101010, // 0x09 + 0b00111111111111111111111111111100, // 0x0a + 0b00001111111111111111111111101001, // 0x0b + 0b00001111111111111111111111101010, // 0x0c + 0b00111111111111111111111111111101, // 0x0d + 0b00001111111111111111111111101011, // 0x0e + 0b00001111111111111111111111101100, // 0x0f + 0b00001111111111111111111111101101, // 0x10 + 0b00001111111111111111111111101110, // 0x11 + 0b00001111111111111111111111101111, // 0x12 + 0b00001111111111111111111111110000, // 0x13 + 0b00001111111111111111111111110001, // 0x14 + 0b00001111111111111111111111110010, // 0x15 + 0b00111111111111111111111111111110, // 0x16 + 0b00001111111111111111111111110011, // 0x17 + 0b00001111111111111111111111110100, // 0x18 + 0b00001111111111111111111111110101, // 0x19 + 0b00001111111111111111111111110110, // 0x1a + 0b00001111111111111111111111110111, // 0x1b + 0b00001111111111111111111111111000, // 0x1c + 0b00001111111111111111111111111001, // 0x1d + 0b00001111111111111111111111111010, // 0x1e + 0b00001111111111111111111111111011, // 0x1f + 0b00000000000000000000000000010100, // 0x20 + 0b00000000000000000000001111111000, // '!' + 0b00000000000000000000001111111001, // '\"' + 0b00000000000000000000111111111010, // '#' + 0b00000000000000000001111111111001, // '$' + 0b00000000000000000000000000010101, // '%' + 0b00000000000000000000000011111000, // '&' + 0b00000000000000000000011111111010, // '\'' + 0b00000000000000000000001111111010, // '(' + 0b00000000000000000000001111111011, // ')' + 0b00000000000000000000000011111001, // '*' + 0b00000000000000000000011111111011, // '+' + 0b00000000000000000000000011111010, // ',' + 0b00000000000000000000000000010110, // '-' + 0b00000000000000000000000000010111, // '.' + 0b00000000000000000000000000011000, // '/' + 0b00000000000000000000000000000000, // '0' + 0b00000000000000000000000000000001, // '1' + 0b00000000000000000000000000000010, // '2' + 0b00000000000000000000000000011001, // '3' + 0b00000000000000000000000000011010, // '4' + 0b00000000000000000000000000011011, // '5' + 0b00000000000000000000000000011100, // '6' + 0b00000000000000000000000000011101, // '7' + 0b00000000000000000000000000011110, // '8' + 0b00000000000000000000000000011111, // '9' + 0b00000000000000000000000001011100, // ':' + 0b00000000000000000000000011111011, // ';' + 0b00000000000000000111111111111100, // '<' + 0b00000000000000000000000000100000, // '=' + 0b00000000000000000000111111111011, // '>' + 0b00000000000000000000001111111100, // '?' + 0b00000000000000000001111111111010, // '@' + 0b00000000000000000000000000100001, // 'A' + 0b00000000000000000000000001011101, // 'B' + 0b00000000000000000000000001011110, // 'C' + 0b00000000000000000000000001011111, // 'D' + 0b00000000000000000000000001100000, // 'E' + 0b00000000000000000000000001100001, // 'F' + 0b00000000000000000000000001100010, // 'G' + 0b00000000000000000000000001100011, // 'H' + 0b00000000000000000000000001100100, // 'I' + 0b00000000000000000000000001100101, // 'J' + 0b00000000000000000000000001100110, // 'K' + 0b00000000000000000000000001100111, // 'L' + 0b00000000000000000000000001101000, // 'M' + 0b00000000000000000000000001101001, // 'N' + 0b00000000000000000000000001101010, // 'O' + 0b00000000000000000000000001101011, // 'P' + 0b00000000000000000000000001101100, // 'Q' + 0b00000000000000000000000001101101, // 'R' + 0b00000000000000000000000001101110, // 'S' + 0b00000000000000000000000001101111, // 'T' + 0b00000000000000000000000001110000, // 'U' + 0b00000000000000000000000001110001, // 'V' + 0b00000000000000000000000001110010, // 'W' + 0b00000000000000000000000011111100, // 'X' + 0b00000000000000000000000001110011, // 'Y' + 0b00000000000000000000000011111101, // 'Z' + 0b00000000000000000001111111111011, // '[' + 0b00000000000001111111111111110000, // '\\' + 0b00000000000000000001111111111100, // ']' + 0b00000000000000000011111111111100, // '^' + 0b00000000000000000000000000100010, // '_' + 0b00000000000000000111111111111101, // '`' + 0b00000000000000000000000000000011, // 'a' + 0b00000000000000000000000000100011, // 'b' + 0b00000000000000000000000000000100, // 'c' + 0b00000000000000000000000000100100, // 'd' + 0b00000000000000000000000000000101, // 'e' + 0b00000000000000000000000000100101, // 'f' + 0b00000000000000000000000000100110, // 'g' + 0b00000000000000000000000000100111, // 'h' + 0b00000000000000000000000000000110, // 'i' + 0b00000000000000000000000001110100, // 'j' + 0b00000000000000000000000001110101, // 'k' + 0b00000000000000000000000000101000, // 'l' + 0b00000000000000000000000000101001, // 'm' + 0b00000000000000000000000000101010, // 'n' + 0b00000000000000000000000000000111, // 'o' + 0b00000000000000000000000000101011, // 'p' + 0b00000000000000000000000001110110, // 'q' + 0b00000000000000000000000000101100, // 'r' + 0b00000000000000000000000000001000, // 's' + 0b00000000000000000000000000001001, // 't' + 0b00000000000000000000000000101101, // 'u' + 0b00000000000000000000000001110111, // 'v' + 0b00000000000000000000000001111000, // 'w' + 0b00000000000000000000000001111001, // 'x' + 0b00000000000000000000000001111010, // 'y' + 0b00000000000000000000000001111011, // 'z' + 0b00000000000000000111111111111110, // '{' + 0b00000000000000000000011111111100, // '|' + 0b00000000000000000011111111111101, // '}' + 0b00000000000000000001111111111101, // '~' + 0b00001111111111111111111111111100, // 0x7f + 0b00000000000011111111111111100110, // 0x80 + 0b00000000001111111111111111010010, // 0x81 + 0b00000000000011111111111111100111, // 0x82 + 0b00000000000011111111111111101000, // 0x83 + 0b00000000001111111111111111010011, // 0x84 + 0b00000000001111111111111111010100, // 0x85 + 0b00000000001111111111111111010101, // 0x86 + 0b00000000011111111111111111011001, // 0x87 + 0b00000000001111111111111111010110, // 0x88 + 0b00000000011111111111111111011010, // 0x89 + 0b00000000011111111111111111011011, // 0x8a + 0b00000000011111111111111111011100, // 0x8b + 0b00000000011111111111111111011101, // 0x8c + 0b00000000011111111111111111011110, // 0x8d + 0b00000000111111111111111111101011, // 0x8e + 0b00000000011111111111111111011111, // 0x8f + 0b00000000111111111111111111101100, // 0x90 + 0b00000000111111111111111111101101, // 0x91 + 0b00000000001111111111111111010111, // 0x92 + 0b00000000011111111111111111100000, // 0x93 + 0b00000000111111111111111111101110, // 0x94 + 0b00000000011111111111111111100001, // 0x95 + 0b00000000011111111111111111100010, // 0x96 + 0b00000000011111111111111111100011, // 0x97 + 0b00000000011111111111111111100100, // 0x98 + 0b00000000000111111111111111011100, // 0x99 + 0b00000000001111111111111111011000, // 0x9a + 0b00000000011111111111111111100101, // 0x9b + 0b00000000001111111111111111011001, // 0x9c + 0b00000000011111111111111111100110, // 0x9d + 0b00000000011111111111111111100111, // 0x9e + 0b00000000111111111111111111101111, // 0x9f + 0b00000000001111111111111111011010, // 0xa0 + 0b00000000000111111111111111011101, // 0xa1 + 0b00000000000011111111111111101001, // 0xa2 + 0b00000000001111111111111111011011, // 0xa3 + 0b00000000001111111111111111011100, // 0xa4 + 0b00000000011111111111111111101000, // 0xa5 + 0b00000000011111111111111111101001, // 0xa6 + 0b00000000000111111111111111011110, // 0xa7 + 0b00000000011111111111111111101010, // 0xa8 + 0b00000000001111111111111111011101, // 0xa9 + 0b00000000001111111111111111011110, // 0xaa + 0b00000000111111111111111111110000, // 0xab + 0b00000000000111111111111111011111, // 0xac + 0b00000000001111111111111111011111, // 0xad + 0b00000000011111111111111111101011, // 0xae + 0b00000000011111111111111111101100, // 0xaf + 0b00000000000111111111111111100000, // 0xb0 + 0b00000000000111111111111111100001, // 0xb1 + 0b00000000001111111111111111100000, // 0xb2 + 0b00000000000111111111111111100010, // 0xb3 + 0b00000000011111111111111111101101, // 0xb4 + 0b00000000001111111111111111100001, // 0xb5 + 0b00000000011111111111111111101110, // 0xb6 + 0b00000000011111111111111111101111, // 0xb7 + 0b00000000000011111111111111101010, // 0xb8 + 0b00000000001111111111111111100010, // 0xb9 + 0b00000000001111111111111111100011, // 0xba + 0b00000000001111111111111111100100, // 0xbb + 0b00000000011111111111111111110000, // 0xbc + 0b00000000001111111111111111100101, // 0xbd + 0b00000000001111111111111111100110, // 0xbe + 0b00000000011111111111111111110001, // 0xbf + 0b00000011111111111111111111100000, // 0xc0 + 0b00000011111111111111111111100001, // 0xc1 + 0b00000000000011111111111111101011, // 0xc2 + 0b00000000000001111111111111110001, // 0xc3 + 0b00000000001111111111111111100111, // 0xc4 + 0b00000000011111111111111111110010, // 0xc5 + 0b00000000001111111111111111101000, // 0xc6 + 0b00000001111111111111111111101100, // 0xc7 + 0b00000011111111111111111111100010, // 0xc8 + 0b00000011111111111111111111100011, // 0xc9 + 0b00000011111111111111111111100100, // 0xca + 0b00000111111111111111111111011110, // 0xcb + 0b00000111111111111111111111011111, // 0xcc + 0b00000011111111111111111111100101, // 0xcd + 0b00000000111111111111111111110001, // 0xce + 0b00000001111111111111111111101101, // 0xcf + 0b00000000000001111111111111110010, // 0xd0 + 0b00000000000111111111111111100011, // 0xd1 + 0b00000011111111111111111111100110, // 0xd2 + 0b00000111111111111111111111100000, // 0xd3 + 0b00000111111111111111111111100001, // 0xd4 + 0b00000011111111111111111111100111, // 0xd5 + 0b00000111111111111111111111100010, // 0xd6 + 0b00000000111111111111111111110010, // 0xd7 + 0b00000000000111111111111111100100, // 0xd8 + 0b00000000000111111111111111100101, // 0xd9 + 0b00000011111111111111111111101000, // 0xda + 0b00000011111111111111111111101001, // 0xdb + 0b00001111111111111111111111111101, // 0xdc + 0b00000111111111111111111111100011, // 0xdd + 0b00000111111111111111111111100100, // 0xde + 0b00000111111111111111111111100101, // 0xdf + 0b00000000000011111111111111101100, // 0xe0 + 0b00000000111111111111111111110011, // 0xe1 + 0b00000000000011111111111111101101, // 0xe2 + 0b00000000000111111111111111100110, // 0xe3 + 0b00000000001111111111111111101001, // 0xe4 + 0b00000000000111111111111111100111, // 0xe5 + 0b00000000000111111111111111101000, // 0xe6 + 0b00000000011111111111111111110011, // 0xe7 + 0b00000000001111111111111111101010, // 0xe8 + 0b00000000001111111111111111101011, // 0xe9 + 0b00000001111111111111111111101110, // 0xea + 0b00000001111111111111111111101111, // 0xeb + 0b00000000111111111111111111110100, // 0xec + 0b00000000111111111111111111110101, // 0xed + 0b00000011111111111111111111101010, // 0xee + 0b00000000011111111111111111110100, // 0xef + 0b00000011111111111111111111101011, // 0xf0 + 0b00000111111111111111111111100110, // 0xf1 + 0b00000011111111111111111111101100, // 0xf2 + 0b00000011111111111111111111101101, // 0xf3 + 0b00000111111111111111111111100111, // 0xf4 + 0b00000111111111111111111111101000, // 0xf5 + 0b00000111111111111111111111101001, // 0xf6 + 0b00000111111111111111111111101010, // 0xf7 + 0b00000111111111111111111111101011, // 0xf8 + 0b00001111111111111111111111111110, // 0xf9 + 0b00000111111111111111111111101100, // 0xfa + 0b00000111111111111111111111101101, // 0xfb + 0b00000111111111111111111111101110, // 0xfc + 0b00000111111111111111111111101111, // 0xfd + 0b00000111111111111111111111110000, // 0xfe + 0b00000011111111111111111111101110, // 0xff + 0b00111111111111111111111111111111, // 0x100 +}; +// clang-format off + +} // namespace http2 diff --git a/chromium/net/third_party/quiche/src/http2/hpack/huffman/huffman_spec_tables.h b/chromium/net/third_party/quiche/src/http2/hpack/huffman/huffman_spec_tables.h new file mode 100644 index 00000000000..6cd8726d7b4 --- /dev/null +++ b/chromium/net/third_party/quiche/src/http2/hpack/huffman/huffman_spec_tables.h @@ -0,0 +1,25 @@ +// Copyright (c) 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef QUICHE_HTTP2_HPACK_HUFFMAN_HUFFMAN_SPEC_TABLES_H_ +#define QUICHE_HTTP2_HPACK_HUFFMAN_HUFFMAN_SPEC_TABLES_H_ + +// Tables describing the Huffman encoding of bytes as specified by RFC7541. + +#include <cstdint> + +namespace http2 { + +struct HuffmanSpecTables { + // Number of bits in the encoding of each symbol (byte). + static const uint8_t kCodeLengths[257]; + + // The encoding of each symbol, right justified (as printed), which means that + // the last bit of the encoding is the low-order bit of the uint32. + static const uint32_t kRightCodes[257]; +}; + +} // namespace http2 + +#endif // QUICHE_HTTP2_HPACK_HUFFMAN_HUFFMAN_SPEC_TABLES_H_ diff --git a/chromium/net/third_party/quiche/src/http2/hpack/tools/hpack_block_builder.cc b/chromium/net/third_party/quiche/src/http2/hpack/tools/hpack_block_builder.cc new file mode 100644 index 00000000000..5a79f78b20c --- /dev/null +++ b/chromium/net/third_party/quiche/src/http2/hpack/tools/hpack_block_builder.cc @@ -0,0 +1,80 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/http2/hpack/tools/hpack_block_builder.h" + +#include "testing/gtest/include/gtest/gtest.h" +#include "net/third_party/quiche/src/http2/hpack/varint/hpack_varint_encoder.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_bug_tracker.h" + +namespace http2 { +namespace test { + +void HpackBlockBuilder::AppendHighBitsAndVarint(uint8_t high_bits, + uint8_t prefix_length, + uint64_t varint) { + EXPECT_LE(3, prefix_length); + EXPECT_LE(prefix_length, 8); + + HpackVarintEncoder varint_encoder; + + unsigned char c = + varint_encoder.StartEncoding(high_bits, prefix_length, varint); + buffer_.push_back(c); + + if (!varint_encoder.IsEncodingInProgress()) { + return; + } + + // After the prefix, at most 63 bits can remain to be encoded. + // Each octet holds 7 bits, so at most 9 octets are necessary. + // TODO(bnc): Move this into a constant in HpackVarintEncoder. + varint_encoder.ResumeEncoding(/* max_encoded_bytes = */ 10, &buffer_); + DCHECK(!varint_encoder.IsEncodingInProgress()); +} + +void HpackBlockBuilder::AppendEntryTypeAndVarint(HpackEntryType entry_type, + uint64_t varint) { + uint8_t high_bits; + uint8_t prefix_length; // Bits of the varint prefix in the first byte. + switch (entry_type) { + case HpackEntryType::kIndexedHeader: + high_bits = 0x80; + prefix_length = 7; + break; + case HpackEntryType::kDynamicTableSizeUpdate: + high_bits = 0x20; + prefix_length = 5; + break; + case HpackEntryType::kIndexedLiteralHeader: + high_bits = 0x40; + prefix_length = 6; + break; + case HpackEntryType::kUnindexedLiteralHeader: + high_bits = 0x00; + prefix_length = 4; + break; + case HpackEntryType::kNeverIndexedLiteralHeader: + high_bits = 0x10; + prefix_length = 4; + break; + default: + HTTP2_BUG << "Unreached, entry_type=" << entry_type; + high_bits = 0; + prefix_length = 0; + break; + } + AppendHighBitsAndVarint(high_bits, prefix_length, varint); +} + +void HpackBlockBuilder::AppendString(bool is_huffman_encoded, + Http2StringPiece str) { + uint8_t high_bits = is_huffman_encoded ? 0x80 : 0; + uint8_t prefix_length = 7; + AppendHighBitsAndVarint(high_bits, prefix_length, str.size()); + buffer_.append(str.data(), str.size()); +} + +} // namespace test +} // namespace http2 diff --git a/chromium/net/third_party/quiche/src/http2/hpack/tools/hpack_block_builder.h b/chromium/net/third_party/quiche/src/http2/hpack/tools/hpack_block_builder.h new file mode 100644 index 00000000000..560953cc341 --- /dev/null +++ b/chromium/net/third_party/quiche/src/http2/hpack/tools/hpack_block_builder.h @@ -0,0 +1,96 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef QUICHE_HTTP2_HPACK_TOOLS_HPACK_BLOCK_BUILDER_H_ +#define QUICHE_HTTP2_HPACK_TOOLS_HPACK_BLOCK_BUILDER_H_ + +// HpackBlockBuilder builds wire-format HPACK blocks (or fragments thereof) +// from components. + +// Supports very large varints to enable tests to create HPACK blocks with +// values that the decoder should reject. For now, this is only intended for +// use in tests, and thus has EXPECT* in the code. If desired to use it in an +// encoder, it will need optimization work, especially w.r.t memory mgmt, and +// the EXPECT* will need to be removed or replaced with DCHECKs. And of course +// the support for very large varints will not be needed in production code. + +#include <stddef.h> + +#include <cstdint> + +#include "testing/gtest/include/gtest/gtest.h" +#include "net/third_party/quiche/src/http2/hpack/http2_hpack_constants.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string_piece.h" + +namespace http2 { +namespace test { + +class HpackBlockBuilder { + public: + explicit HpackBlockBuilder(Http2StringPiece initial_contents) + : buffer_(initial_contents.data(), initial_contents.size()) {} + HpackBlockBuilder() {} + ~HpackBlockBuilder() {} + + size_t size() const { return buffer_.size(); } + const Http2String& buffer() const { return buffer_; } + + //---------------------------------------------------------------------------- + // Methods for appending a valid HPACK entry. + + void AppendIndexedHeader(uint64_t index) { + AppendEntryTypeAndVarint(HpackEntryType::kIndexedHeader, index); + } + + void AppendDynamicTableSizeUpdate(uint64_t size) { + AppendEntryTypeAndVarint(HpackEntryType::kDynamicTableSizeUpdate, size); + } + + void AppendNameIndexAndLiteralValue(HpackEntryType entry_type, + uint64_t name_index, + bool value_is_huffman_encoded, + Http2StringPiece value) { + // name_index==0 would indicate that the entry includes a literal name. + // Call AppendLiteralNameAndValue in that case. + EXPECT_NE(0u, name_index); + AppendEntryTypeAndVarint(entry_type, name_index); + AppendString(value_is_huffman_encoded, value); + } + + void AppendLiteralNameAndValue(HpackEntryType entry_type, + bool name_is_huffman_encoded, + Http2StringPiece name, + bool value_is_huffman_encoded, + Http2StringPiece value) { + AppendEntryTypeAndVarint(entry_type, 0); + AppendString(name_is_huffman_encoded, name); + AppendString(value_is_huffman_encoded, value); + } + + //---------------------------------------------------------------------------- + // Primitive methods that are not guaranteed to write a valid HPACK entry. + + // Appends a varint, with the specified high_bits above the prefix of the + // varint. + void AppendHighBitsAndVarint(uint8_t high_bits, + uint8_t prefix_length, + uint64_t varint); + + // Append the start of an HPACK entry for the specified type, with the + // specified varint. + void AppendEntryTypeAndVarint(HpackEntryType entry_type, uint64_t varint); + + // Append a header string (i.e. a header name or value) in HPACK format. + // Does NOT perform Huffman encoding. + void AppendString(bool is_huffman_encoded, Http2StringPiece str); + + private: + Http2String buffer_; +}; + +} // namespace test +} // namespace http2 + +#endif // QUICHE_HTTP2_HPACK_TOOLS_HPACK_BLOCK_BUILDER_H_ diff --git a/chromium/net/third_party/quiche/src/http2/hpack/tools/hpack_block_builder_test.cc b/chromium/net/third_party/quiche/src/http2/hpack/tools/hpack_block_builder_test.cc new file mode 100644 index 00000000000..a7b80647979 --- /dev/null +++ b/chromium/net/third_party/quiche/src/http2/hpack/tools/hpack_block_builder_test.cc @@ -0,0 +1,169 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/http2/hpack/tools/hpack_block_builder.h" + +#include "testing/gtest/include/gtest/gtest.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string_utils.h" + +namespace http2 { +namespace test { +namespace { +const bool kUncompressed = false; +const bool kCompressed = true; + +// TODO(jamessynge): Once static table code is checked in, switch to using +// constants from there. +const uint32_t kStaticTableMethodGET = 2; +const uint32_t kStaticTablePathSlash = 4; +const uint32_t kStaticTableSchemeHttp = 6; + +// Tests of encoding per the RFC. See: +// http://httpwg.org/specs/rfc7541.html#header.field.representation.examples +// The expected values have been copied from the RFC. +TEST(HpackBlockBuilderTest, ExamplesFromSpecC2) { + { + HpackBlockBuilder b; + b.AppendLiteralNameAndValue(HpackEntryType::kIndexedLiteralHeader, + kUncompressed, "custom-key", kUncompressed, + "custom-header"); + EXPECT_EQ(26u, b.size()); + + const char kExpected[] = + "\x40" // == Literal indexed == + "\x0a" // Name length (10) + "custom-key" // Name + "\x0d" // Value length (13) + "custom-header"; // Value + EXPECT_EQ(kExpected, b.buffer()); + } + { + HpackBlockBuilder b; + b.AppendNameIndexAndLiteralValue(HpackEntryType::kUnindexedLiteralHeader, 4, + kUncompressed, "/sample/path"); + EXPECT_EQ(14u, b.size()); + + const char kExpected[] = + "\x04" // == Literal unindexed, name index 0x04 == + "\x0c" // Value length (12) + "/sample/path"; // Value + EXPECT_EQ(kExpected, b.buffer()); + } + { + HpackBlockBuilder b; + b.AppendLiteralNameAndValue(HpackEntryType::kNeverIndexedLiteralHeader, + kUncompressed, "password", kUncompressed, + "secret"); + EXPECT_EQ(17u, b.size()); + + const char kExpected[] = + "\x10" // == Literal never indexed == + "\x08" // Name length (8) + "password" // Name + "\x06" // Value length (6) + "secret"; // Value + EXPECT_EQ(kExpected, b.buffer()); + } + { + HpackBlockBuilder b; + b.AppendIndexedHeader(2); + EXPECT_EQ(1u, b.size()); + + const char kExpected[] = "\x82"; // == Indexed (2) == + EXPECT_EQ(kExpected, b.buffer()); + } +} + +// Tests of encoding per the RFC. See: +// http://httpwg.org/specs/rfc7541.html#request.examples.without.huffman.coding +TEST(HpackBlockBuilderTest, ExamplesFromSpecC3) { + { + // Header block to encode: + // :method: GET + // :scheme: http + // :path: / + // :authority: www.example.com + HpackBlockBuilder b; + b.AppendIndexedHeader(2); // :method: GET + b.AppendIndexedHeader(6); // :scheme: http + b.AppendIndexedHeader(4); // :path: / + b.AppendNameIndexAndLiteralValue(HpackEntryType::kIndexedLiteralHeader, 1, + kUncompressed, "www.example.com"); + EXPECT_EQ(20u, b.size()); + + // Hex dump of encoded data (copied from RFC): + // 0x0000: 8286 8441 0f77 7777 2e65 7861 6d70 6c65 ...A.www.example + // 0x0010: 2e63 6f6d .com + + const Http2String expected = + Http2HexDecode("828684410f7777772e6578616d706c652e636f6d"); + EXPECT_EQ(expected, b.buffer()); + } +} + +// Tests of encoding per the RFC. See: +// http://httpwg.org/specs/rfc7541.html#request.examples.with.huffman.coding +TEST(HpackBlockBuilderTest, ExamplesFromSpecC4) { + { + // Header block to encode: + // :method: GET + // :scheme: http + // :path: / + // :authority: www.example.com (Huffman encoded) + HpackBlockBuilder b; + b.AppendIndexedHeader(kStaticTableMethodGET); + b.AppendIndexedHeader(kStaticTableSchemeHttp); + b.AppendIndexedHeader(kStaticTablePathSlash); + const char kHuffmanWwwExampleCom[] = {'\xf1', '\xe3', '\xc2', '\xe5', + '\xf2', '\x3a', '\x6b', '\xa0', + '\xab', '\x90', '\xf4', '\xff'}; + b.AppendNameIndexAndLiteralValue( + HpackEntryType::kIndexedLiteralHeader, 1, kCompressed, + Http2StringPiece(kHuffmanWwwExampleCom, sizeof kHuffmanWwwExampleCom)); + EXPECT_EQ(17u, b.size()); + + // Hex dump of encoded data (copied from RFC): + // 0x0000: 8286 8441 8cf1 e3c2 e5f2 3a6b a0ab 90f4 ...A......:k.... + // 0x0010: ff . + + const Http2String expected = + Http2HexDecode("828684418cf1e3c2e5f23a6ba0ab90f4ff"); + EXPECT_EQ(expected, b.buffer()); + } +} + +TEST(HpackBlockBuilderTest, DynamicTableSizeUpdate) { + { + HpackBlockBuilder b; + b.AppendDynamicTableSizeUpdate(0); + EXPECT_EQ(1u, b.size()); + + const char kData[] = {'\x20'}; + Http2StringPiece expected(kData, sizeof kData); + EXPECT_EQ(expected, b.buffer()); + } + { + HpackBlockBuilder b; + b.AppendDynamicTableSizeUpdate(4096); // The default size. + EXPECT_EQ(3u, b.size()); + + const char kData[] = {'\x3f', '\xe1', '\x1f'}; + Http2StringPiece expected(kData, sizeof kData); + EXPECT_EQ(expected, b.buffer()); + } + { + HpackBlockBuilder b; + b.AppendDynamicTableSizeUpdate(1000000000000); // A very large value. + EXPECT_EQ(7u, b.size()); + + const char kData[] = {'\x3f', '\xe1', '\x9f', '\x94', + '\xa5', '\x8d', '\x1d'}; + Http2StringPiece expected(kData, sizeof kData); + EXPECT_EQ(expected, b.buffer()); + } +} + +} // namespace +} // namespace test +} // namespace http2 diff --git a/chromium/net/third_party/quiche/src/http2/hpack/tools/hpack_example.cc b/chromium/net/third_party/quiche/src/http2/hpack/tools/hpack_example.cc new file mode 100644 index 00000000000..d20eb354260 --- /dev/null +++ b/chromium/net/third_party/quiche/src/http2/hpack/tools/hpack_example.cc @@ -0,0 +1,58 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/http2/hpack/tools/hpack_example.h" + +#include <ctype.h> + +#include "base/logging.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_bug_tracker.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string_utils.h" + +namespace http2 { +namespace test { +namespace { + +void HpackExampleToStringOrDie(Http2StringPiece example, Http2String* output) { + while (!example.empty()) { + const char c0 = example[0]; + if (isxdigit(c0)) { + CHECK_GT(example.size(), 1u) << "Truncated hex byte?"; + const char c1 = example[1]; + CHECK(isxdigit(c1)) << "Found half a byte?"; + *output += Http2HexDecode(example.substr(0, 2)); + example.remove_prefix(2); + continue; + } + if (isspace(c0)) { + example.remove_prefix(1); + continue; + } + if (!example.empty() && example[0] == '|') { + // Start of a comment. Skip to end of line or of input. + auto pos = example.find('\n'); + if (pos == Http2StringPiece::npos) { + // End of input. + break; + } + example.remove_prefix(pos + 1); + continue; + } + HTTP2_BUG << "Can't parse byte " << static_cast<int>(c0) + << Http2StrCat(" (0x", Http2Hex(c0), ")") + << "\nExample: " << example; + } + CHECK_LT(0u, output->size()) << "Example is empty."; +} + +} // namespace + +Http2String HpackExampleToStringOrDie(Http2StringPiece example) { + Http2String output; + HpackExampleToStringOrDie(example, &output); + return output; +} + +} // namespace test +} // namespace http2 diff --git a/chromium/net/third_party/quiche/src/http2/hpack/tools/hpack_example.h b/chromium/net/third_party/quiche/src/http2/hpack/tools/hpack_example.h new file mode 100644 index 00000000000..ddb22a32534 --- /dev/null +++ b/chromium/net/third_party/quiche/src/http2/hpack/tools/hpack_example.h @@ -0,0 +1,31 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef QUICHE_HTTP2_HPACK_TOOLS_HPACK_EXAMPLE_H_ +#define QUICHE_HTTP2_HPACK_TOOLS_HPACK_EXAMPLE_H_ + +#include "net/third_party/quiche/src/http2/platform/api/http2_string.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string_piece.h" + +// Parses HPACK examples in the format seen in the HPACK specification, +// RFC 7541. For example: +// +// 10 | == Literal never indexed == +// 08 | Literal name (len = 8) +// 7061 7373 776f 7264 | password +// 06 | Literal value (len = 6) +// 7365 6372 6574 | secret +// | -> password: secret +// +// (excluding the leading "//"). + +namespace http2 { +namespace test { + +Http2String HpackExampleToStringOrDie(Http2StringPiece example); + +} // namespace test +} // namespace http2 + +#endif // QUICHE_HTTP2_HPACK_TOOLS_HPACK_EXAMPLE_H_ diff --git a/chromium/net/third_party/quiche/src/http2/hpack/varint/hpack_varint_decoder.cc b/chromium/net/third_party/quiche/src/http2/hpack/varint/hpack_varint_decoder.cc new file mode 100644 index 00000000000..dd64025e506 --- /dev/null +++ b/chromium/net/third_party/quiche/src/http2/hpack/varint/hpack_varint_decoder.cc @@ -0,0 +1,172 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/http2/hpack/varint/hpack_varint_decoder.h" + +#include "net/third_party/quiche/src/http2/platform/api/http2_flag_utils.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string_utils.h" + +namespace http2 { + +DecodeStatus HpackVarintDecoder::Start(uint8_t prefix_value, + uint8_t prefix_length, + DecodeBuffer* db) { + DCHECK_LE(3u, prefix_length); + DCHECK_LE(prefix_length, 8u); + + // |prefix_mask| defines the sequence of low-order bits of the first byte + // that encode the prefix of the value. It is also the marker in those bits + // of the first byte indicating that at least one extension byte is needed. + const uint8_t prefix_mask = (1 << prefix_length) - 1; + + // Ignore the bits that aren't a part of the prefix of the varint. + value_ = prefix_value & prefix_mask; + + if (value_ < prefix_mask) { + MarkDone(); + return DecodeStatus::kDecodeDone; + } + + offset_ = 0; + return Resume(db); +} + +DecodeStatus HpackVarintDecoder::StartExtended(uint8_t prefix_length, + DecodeBuffer* db) { + DCHECK_LE(3u, prefix_length); + DCHECK_LE(prefix_length, 8u); + + value_ = (1 << prefix_length) - 1; + offset_ = 0; + return Resume(db); +} + +DecodeStatus HpackVarintDecoder::Resume(DecodeBuffer* db) { + if (decode_64_bits_) { + HTTP2_RELOADABLE_FLAG_COUNT(http2_varint_decode_64_bits); + // There can be at most 10 continuation bytes. Offset is zero for the + // first one and increases by 7 for each subsequent one. + const uint8_t kMaxOffset = 63; + CheckNotDone(); + + // Process most extension bytes without the need for overflow checking. + while (offset_ < kMaxOffset) { + if (db->Empty()) { + return DecodeStatus::kDecodeInProgress; + } + + uint8_t byte = db->DecodeUInt8(); + uint64_t summand = byte & 0x7f; + + // Shifting a 7 bit value to the left by at most 56 places can never + // overflow on uint64_t. + DCHECK_LE(offset_, 56); + DCHECK_LE(summand, std::numeric_limits<uint64_t>::max() >> offset_); + + summand <<= offset_; + + // At this point, + // |value_| is at most (2^prefix_length - 1) + (2^49 - 1), and + // |summand| is at most 255 << 56 (which is smaller than 2^63), + // so adding them can never overflow on uint64_t. + DCHECK_LE(value_, std::numeric_limits<uint64_t>::max() - summand); + + value_ += summand; + + // Decoding ends if continuation flag is not set. + if ((byte & 0x80) == 0) { + MarkDone(); + return DecodeStatus::kDecodeDone; + } + + offset_ += 7; + } + + if (db->Empty()) { + return DecodeStatus::kDecodeInProgress; + } + + DCHECK_EQ(kMaxOffset, offset_); + + uint8_t byte = db->DecodeUInt8(); + // No more extension bytes are allowed after this. + if ((byte & 0x80) == 0) { + uint64_t summand = byte & 0x7f; + // Check for overflow in left shift. + if (summand <= std::numeric_limits<uint64_t>::max() >> offset_) { + summand <<= offset_; + // Check for overflow in addition. + if (value_ <= std::numeric_limits<uint64_t>::max() - summand) { + value_ += summand; + MarkDone(); + return DecodeStatus::kDecodeDone; + } + } + } + + // Signal error if value is too large or there are too many extension bytes. + DLOG(WARNING) << "Variable length int encoding is too large or too long. " + << DebugString(); + MarkDone(); + return DecodeStatus::kDecodeError; + } + + // Old code path. TODO(bnc): remove. + DCHECK(!decode_64_bits_); + const uint8_t kMaxOffset = 28; + CheckNotDone(); + do { + if (db->Empty()) { + return DecodeStatus::kDecodeInProgress; + } + uint8_t byte = db->DecodeUInt8(); + if (offset_ == kMaxOffset && byte != 0) + break; + DCHECK(offset_ <= kMaxOffset - 7 || byte == 0); + // Shifting a 7 bit value to the left by at most 21 places can never + // overflow on uint32_t. Shifting 0 to the left cannot overflow either. + value_ += (byte & 0x7f) << offset_; + if ((byte & 0x80) == 0) { + MarkDone(); + return DecodeStatus::kDecodeDone; + } + offset_ += 7; + } while (offset_ <= kMaxOffset); + DLOG(WARNING) << "Variable length int encoding is too large or too long. " + << DebugString(); + MarkDone(); + return DecodeStatus::kDecodeError; +} + +uint64_t HpackVarintDecoder::value() const { + CheckDone(); + return value_; +} + +void HpackVarintDecoder::set_value(uint64_t v) { + MarkDone(); + value_ = v; +} + +Http2String HpackVarintDecoder::DebugString() const { + return Http2StrCat("HpackVarintDecoder(value=", value_, ", offset=", offset_, + ")"); +} + +DecodeStatus HpackVarintDecoder::StartForTest(uint8_t prefix_value, + uint8_t prefix_length, + DecodeBuffer* db) { + return Start(prefix_value, prefix_length, db); +} + +DecodeStatus HpackVarintDecoder::StartExtendedForTest(uint8_t prefix_length, + DecodeBuffer* db) { + return StartExtended(prefix_length, db); +} + +DecodeStatus HpackVarintDecoder::ResumeForTest(DecodeBuffer* db) { + return Resume(db); +} + +} // namespace http2 diff --git a/chromium/net/third_party/quiche/src/http2/hpack/varint/hpack_varint_decoder.h b/chromium/net/third_party/quiche/src/http2/hpack/varint/hpack_varint_decoder.h new file mode 100644 index 00000000000..c793500eaf6 --- /dev/null +++ b/chromium/net/third_party/quiche/src/http2/hpack/varint/hpack_varint_decoder.h @@ -0,0 +1,144 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// HpackVarintDecoder decodes HPACK variable length unsigned integers. In HPACK, +// these integers are used to identify static or dynamic table index entries, to +// specify string lengths, and to update the size limit of the dynamic table. +// In QPACK, in addition to these uses, these integers also identify streams. +// +// The caller will need to validate that the decoded value is in an acceptable +// range. +// +// For details of the encoding, see: +// http://httpwg.org/specs/rfc7541.html#integer.representation +// +// If GetHttp2ReloadableFlag(http2_varint_decode_64_bits) is true, then this +// decoder supports decoding any integer that can be represented on uint64_t, +// thereby exceeding the requirements for QPACK: "QPACK implementations MUST be +// able to decode integers up to 62 bits long." See +// https://quicwg.org/base-drafts/draft-ietf-quic-qpack.html#rfc.section.5.1.1 +// +// If GetHttp2ReloadableFlag(http2_varint_decode_64_bits) is false, then this +// decoder supports decoding integers up to 2^28 + 2^prefix_length - 2. +// +// This decoder supports at most 10 extension bytes (bytes following the prefix, +// also called continuation bytes) if +// GetHttp2ReloadableFlag(http2_varint_decode_64_bits) is true, and at most 5 +// extension bytes if GetHttp2ReloadableFlag(http2_varint_decode_64_bits) is +// false. An encoder is allowed to zero pad the encoded integer on the left, +// thereby increasing the number of extension bytes. If an encoder uses so much +// padding that the number of extension bytes exceeds the limit, then this +// decoder signals an error. + +#ifndef QUICHE_HTTP2_HPACK_VARINT_HPACK_VARINT_DECODER_H_ +#define QUICHE_HTTP2_HPACK_VARINT_HPACK_VARINT_DECODER_H_ + +#include <cstdint> +#include <limits> + +#include "base/logging.h" +#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h" +#include "net/third_party/quiche/src/http2/decoder/decode_status.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_export.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_flags.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string.h" + +namespace http2 { + +// Sentinel value for |HpackVarintDecoder::offset_| to signify that decoding is +// completed. Only used in debug builds. +#ifndef NDEBUG +const uint8_t kHpackVarintDecoderOffsetDone = + std::numeric_limits<uint8_t>::max(); +#endif + +// Decodes an HPACK variable length unsigned integer, in a resumable fashion +// so it can handle running out of input in the DecodeBuffer. Call Start or +// StartExtended the first time (when decoding the byte that contains the +// prefix), then call Resume later if it is necessary to resume. When done, +// call value() to retrieve the decoded value. +// +// No constructor or destructor. Holds no resources, so destruction isn't +// needed. Start and StartExtended handles the initialization of member +// variables. This is necessary in order for HpackVarintDecoder to be part +// of a union. +class HTTP2_EXPORT_PRIVATE HpackVarintDecoder { + public: + // |prefix_value| is the first byte of the encoded varint. + // |prefix_length| is number of bits in the first byte that are used for + // encoding the integer. |db| is the rest of the buffer, that is, not + // including the first byte. + DecodeStatus Start(uint8_t prefix_value, + uint8_t prefix_length, + DecodeBuffer* db); + + // The caller has already determined that the encoding requires multiple + // bytes, i.e. that the 3 to 8 low-order bits (the number determined by + // |prefix_length|) of the first byte are are all 1. |db| is the rest of the + // buffer, that is, not including the first byte. + DecodeStatus StartExtended(uint8_t prefix_length, DecodeBuffer* db); + + // Resume decoding a variable length integer after an earlier + // call to Start or StartExtended returned kDecodeInProgress. + DecodeStatus Resume(DecodeBuffer* db); + + uint64_t value() const; + + // This supports optimizations for the case of a varint with zero extension + // bytes, where the handling of the prefix is done by the caller. + void set_value(uint64_t v); + + // All the public methods below are for supporting assertions and tests. + + Http2String DebugString() const; + + // For benchmarking, these methods ensure the decoder + // is NOT inlined into the caller. + DecodeStatus StartForTest(uint8_t prefix_value, + uint8_t prefix_length, + DecodeBuffer* db); + DecodeStatus StartExtendedForTest(uint8_t prefix_length, DecodeBuffer* db); + DecodeStatus ResumeForTest(DecodeBuffer* db); + + private: + // Protection in case Resume is called when it shouldn't be. + void MarkDone() { +#ifndef NDEBUG + offset_ = kHpackVarintDecoderOffsetDone; +#endif + } + void CheckNotDone() const { +#ifndef NDEBUG + DCHECK_NE(kHpackVarintDecoderOffsetDone, offset_); +#endif + } + void CheckDone() const { +#ifndef NDEBUG + DCHECK_EQ(kHpackVarintDecoderOffsetDone, offset_); +#endif + } + + // If true, decode integers up to 2^64 - 1, and accept at most 10 extension + // bytes (some of which might be padding). + // If false, decode integers up to 2^28 + 2^prefix_length - 2, and accept at + // most 5 extension bytes (some of which might be padding). + bool decode_64_bits_ = GetHttp2ReloadableFlag(http2_varint_decode_64_bits); + + // These fields are initialized just to keep ASAN happy about reading + // them from DebugString(). + + // The encoded integer is being accumulated in |value_|. When decoding is + // complete, |value_| holds the result. + uint64_t value_ = 0; + + // Each extension byte encodes in its lowest 7 bits a segment of the integer. + // |offset_| is the number of places this segment has to be shifted to the + // left for decoding. It is zero for the first extension byte, and increases + // by 7 for each subsequent extension byte. + uint8_t offset_ = 0; +}; + +} // namespace http2 + +#endif // QUICHE_HTTP2_HPACK_VARINT_HPACK_VARINT_DECODER_H_ diff --git a/chromium/net/third_party/quiche/src/http2/hpack/varint/hpack_varint_decoder_test.cc b/chromium/net/third_party/quiche/src/http2/hpack/varint/hpack_varint_decoder_test.cc new file mode 100644 index 00000000000..e36ad07949d --- /dev/null +++ b/chromium/net/third_party/quiche/src/http2/hpack/varint/hpack_varint_decoder_test.cc @@ -0,0 +1,486 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/http2/hpack/varint/hpack_varint_decoder.h" + +// Test HpackVarintDecoder against hardcoded data. + +#include <stddef.h> + +#include "base/logging.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_arraysize.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string_piece.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string_utils.h" +#include "net/third_party/quiche/src/http2/tools/random_decoder_test.h" + +using ::testing::AssertionFailure; +using ::testing::AssertionSuccess; + +namespace http2 { +namespace test { +namespace { + +// Save previous value of flag and restore on destruction. +class FlagSaver { + public: + FlagSaver() = delete; + explicit FlagSaver(bool decode_64_bits) + : saved_value_(GetHttp2ReloadableFlag(http2_varint_decode_64_bits)) { + SetHttp2ReloadableFlag(http2_varint_decode_64_bits, decode_64_bits); + } + ~FlagSaver() { + SetHttp2ReloadableFlag(http2_varint_decode_64_bits, saved_value_); + } + + private: + const bool saved_value_; +}; + +class HpackVarintDecoderTest + : public RandomDecoderTest, + public ::testing::WithParamInterface< + ::testing::tuple<bool, uint8_t, const char*>> { + protected: + HpackVarintDecoderTest() + : decode_64_bits_(::testing::get<0>(GetParam())), + high_bits_(::testing::get<1>(GetParam())), + suffix_(Http2HexDecode(::testing::get<2>(GetParam()))), + flag_saver_(decode_64_bits_), + prefix_length_(0) {} + + void DecodeExpectSuccess(Http2StringPiece data, + uint32_t prefix_length, + uint64_t expected_value) { + Validator validator = [expected_value, this]( + const DecodeBuffer& db, + DecodeStatus status) -> AssertionResult { + VERIFY_EQ(expected_value, decoder_.value()) + << "Value doesn't match expected: " << decoder_.value() + << " != " << expected_value; + return AssertionSuccess(); + }; + + // First validate that decoding is done and that we've advanced the cursor + // the expected amount. + validator = ValidateDoneAndOffset(/* offset = */ data.size(), validator); + + EXPECT_TRUE(Decode(data, prefix_length, validator)); + + EXPECT_EQ(expected_value, decoder_.value()); + } + + void DecodeExpectError(Http2StringPiece data, uint32_t prefix_length) { + Validator validator = [](const DecodeBuffer& db, + DecodeStatus status) -> AssertionResult { + VERIFY_EQ(DecodeStatus::kDecodeError, status); + return AssertionSuccess(); + }; + + EXPECT_TRUE(Decode(data, prefix_length, validator)); + } + + bool decode_64_bits() const { return decode_64_bits_; } + + private: + AssertionResult Decode(Http2StringPiece data, + uint32_t prefix_length, + const Validator validator) { + prefix_length_ = prefix_length; + + // Copy |data| so that it can be modified. + Http2String data_copy(data); + + // Bits of the first byte not part of the prefix should be ignored. + uint8_t high_bits_mask = 0b11111111 << prefix_length_; + data_copy[0] |= (high_bits_mask & high_bits_); + + // Extra bytes appended to the input should be ignored. + data_copy.append(suffix_); + + DecodeBuffer b(data_copy); + + // StartDecoding, above, requires the DecodeBuffer be non-empty so that it + // can call Start with the prefix byte. + bool return_non_zero_on_first = true; + + return DecodeAndValidateSeveralWays(&b, return_non_zero_on_first, + validator); + } + + DecodeStatus StartDecoding(DecodeBuffer* b) override { + CHECK_LT(0u, b->Remaining()); + uint8_t prefix = b->DecodeUInt8(); + return decoder_.Start(prefix, prefix_length_, b); + } + + DecodeStatus ResumeDecoding(DecodeBuffer* b) override { + return decoder_.Resume(b); + } + + // Test new or old behavior. + const bool decode_64_bits_; + // Bits of the first byte not part of the prefix. + const uint8_t high_bits_; + // Extra bytes appended to the input. + const Http2String suffix_; + + // |flag_saver_| must preceed |decoder_| so that the flag is already set when + // |decoder_| is constructed. + FlagSaver flag_saver_; + HpackVarintDecoder decoder_; + uint8_t prefix_length_; +}; + +INSTANTIATE_TEST_CASE_P( + HpackVarintDecoderTest, + HpackVarintDecoderTest, + ::testing::Combine( + // Test both the new version (supporting 64 bit integers) and the old + // one (only supporting up to 2^28 + 2^prefix_length - 2. + ::testing::Bool(), + // Bits of the first byte not part of the prefix should be ignored. + ::testing::Values(0b00000000, 0b11111111, 0b10101010), + // Extra bytes appended to the input should be ignored. + ::testing::Values("", "00", "666f6f"))); + +// Test data used when decode_64_bits() == true. +struct { + const char* data; + uint32_t prefix_length; + uint64_t expected_value; +} kSuccessTestData[] = { + // Zero value with different prefix lengths. + {"00", 3, 0}, + {"00", 4, 0}, + {"00", 5, 0}, + {"00", 6, 0}, + {"00", 7, 0}, + {"00", 8, 0}, + // Small values that fit in the prefix. + {"06", 3, 6}, + {"0d", 4, 13}, + {"10", 5, 16}, + {"29", 6, 41}, + {"56", 7, 86}, + {"bf", 8, 191}, + // Values of 2^n-1, which have an all-zero extension byte. + {"0700", 3, 7}, + {"0f00", 4, 15}, + {"1f00", 5, 31}, + {"3f00", 6, 63}, + {"7f00", 7, 127}, + {"ff00", 8, 255}, + // Values of 2^n-1, plus one extra byte of padding. + {"078000", 3, 7}, + {"0f8000", 4, 15}, + {"1f8000", 5, 31}, + {"3f8000", 6, 63}, + {"7f8000", 7, 127}, + {"ff8000", 8, 255}, + // Values requiring one extension byte. + {"0760", 3, 103}, + {"0f2a", 4, 57}, + {"1f7f", 5, 158}, + {"3f02", 6, 65}, + {"7f49", 7, 200}, + {"ff6f", 8, 366}, + // Values requiring one extension byte, plus one byte of padding. + {"07e000", 3, 103}, + {"0faa00", 4, 57}, + {"1fff00", 5, 158}, + {"3f8200", 6, 65}, + {"7fc900", 7, 200}, + {"ffef00", 8, 366}, + // Values requiring one extension byte, plus two bytes of padding. + {"07e08000", 3, 103}, + {"0faa8000", 4, 57}, + {"1fff8000", 5, 158}, + {"3f828000", 6, 65}, + {"7fc98000", 7, 200}, + {"ffef8000", 8, 366}, + // Values requiring one extension byte, plus the maximum amount of padding. + {"07e0808080808080808000", 3, 103}, + {"0faa808080808080808000", 4, 57}, + {"1fff808080808080808000", 5, 158}, + {"3f82808080808080808000", 6, 65}, + {"7fc9808080808080808000", 7, 200}, + {"ffef808080808080808000", 8, 366}, + // Values requiring two extension bytes. + {"07b260", 3, 12345}, + {"0f8a2a", 4, 5401}, + {"1fa87f", 5, 16327}, + {"3fd002", 6, 399}, + {"7fff49", 7, 9598}, + {"ffe32f", 8, 6370}, + // Values requiring two extension bytes, plus one byte of padding. + {"07b2e000", 3, 12345}, + {"0f8aaa00", 4, 5401}, + {"1fa8ff00", 5, 16327}, + {"3fd08200", 6, 399}, + {"7fffc900", 7, 9598}, + {"ffe3af00", 8, 6370}, + // Values requiring two extension bytes, plus the maximum amount of padding. + {"07b2e080808080808000", 3, 12345}, + {"0f8aaa80808080808000", 4, 5401}, + {"1fa8ff80808080808000", 5, 16327}, + {"3fd08280808080808000", 6, 399}, + {"7fffc980808080808000", 7, 9598}, + {"ffe3af80808080808000", 8, 6370}, + // Values requiring three extension bytes. + {"078ab260", 3, 1579281}, + {"0fc18a2a", 4, 689488}, + {"1fada87f", 5, 2085964}, + {"3fa0d002", 6, 43103}, + {"7ffeff49", 7, 1212541}, + {"ff93de23", 8, 585746}, + // Values requiring three extension bytes, plus one byte of padding. + {"078ab2e000", 3, 1579281}, + {"0fc18aaa00", 4, 689488}, + {"1fada8ff00", 5, 2085964}, + {"3fa0d08200", 6, 43103}, + {"7ffeffc900", 7, 1212541}, + {"ff93dea300", 8, 585746}, + // Values requiring four extension bytes. + {"079f8ab260", 3, 202147110}, + {"0fa2c18a2a", 4, 88252593}, + {"1fd0ada87f", 5, 266999535}, + {"3ff9a0d002", 6, 5509304}, + {"7f9efeff49", 7, 155189149}, + {"ffaa82f404", 8, 10289705}, + // Values requiring four extension bytes, plus one byte of padding. + {"079f8ab2e000", 3, 202147110}, + {"0fa2c18aaa00", 4, 88252593}, + {"1fd0ada8ff00", 5, 266999535}, + {"3ff9a0d08200", 6, 5509304}, + {"7f9efeffc900", 7, 155189149}, + {"ffaa82f48400", 8, 10289705}, + // Values requiring six extension bytes. + {"0783aa9f8ab260", 3, 3311978140938}, + {"0ff0b0a2c18a2a", 4, 1445930244223}, + {"1fda84d0ada87f", 5, 4374519874169}, + {"3fb5fbf9a0d002", 6, 90263420404}, + {"7fcff19efeff49", 7, 2542616951118}, + {"ff9fa486bbc327", 8, 1358138807070}, + // Values requiring eight extension bytes. + {"07f19883aa9f8ab260", 3, 54263449861016696}, + {"0f84fdf0b0a2c18a2a", 4, 23690121121119891}, + {"1fa0dfda84d0ada87f", 5, 71672133617889215}, + {"3f9ff0b5fbf9a0d002", 6, 1478875878881374}, + {"7ffbc1cff19efeff49", 7, 41658236125045114}, + {"ff91b6fb85af99c342", 8, 37450237664484368}, + // Values requiring ten extension bytes. + {"0794f1f19883aa9f8ab201", 3, 12832019021693745307u}, + {"0fa08f84fdf0b0a2c18a01", 4, 9980690937382242223u}, + {"1fbfdda0dfda84d0ada801", 5, 12131360551794650846u}, + {"3f9dc79ff0b5fbf9a0d001", 6, 15006530362736632796u}, + {"7f8790fbc1cff19efeff01", 7, 18445754019193211014u}, + {"fffba8c5b8d3fe9f8c8401", 8, 9518498503615141242u}, + // Maximum value: 2^64-1. + {"07f8ffffffffffffffff01", 3, 18446744073709551615u}, + {"0ff0ffffffffffffffff01", 4, 18446744073709551615u}, + {"1fe0ffffffffffffffff01", 5, 18446744073709551615u}, + {"3fc0ffffffffffffffff01", 6, 18446744073709551615u}, + {"7f80ffffffffffffffff01", 7, 18446744073709551615u}, + {"ff80feffffffffffffff01", 8, 18446744073709551615u}, + // Examples from RFC7541 C.1. + {"0a", 5, 10}, + {"1f9a0a", 5, 1337}, +}; + +// Test data used when decode_64_bits() == false. +struct { + const char* data; + uint32_t prefix_length; + uint64_t expected_value; +} kSuccessTestDataOld[] = { + // Zero value with different prefix lengths. + {"00", 3, 0}, + {"00", 4, 0}, + {"00", 5, 0}, + {"00", 6, 0}, + {"00", 7, 0}, + // Small values that fit in the prefix. + {"06", 3, 6}, + {"0d", 4, 13}, + {"10", 5, 16}, + {"29", 6, 41}, + {"56", 7, 86}, + // Values of 2^n-1, which have an all-zero extension byte. + {"0700", 3, 7}, + {"0f00", 4, 15}, + {"1f00", 5, 31}, + {"3f00", 6, 63}, + {"7f00", 7, 127}, + // Values of 2^n-1, plus one extra byte of padding. + {"078000", 3, 7}, + {"0f8000", 4, 15}, + {"1f8000", 5, 31}, + {"3f8000", 6, 63}, + {"7f8000", 7, 127}, + // Values requiring one extension byte. + {"0760", 3, 103}, + {"0f2a", 4, 57}, + {"1f7f", 5, 158}, + {"3f02", 6, 65}, + {"7f49", 7, 200}, + // Values requiring one extension byte, plus one byte of padding. + {"07e000", 3, 103}, + {"0faa00", 4, 57}, + {"1fff00", 5, 158}, + {"3f8200", 6, 65}, + {"7fc900", 7, 200}, + // Values requiring one extension byte, plus two bytes of padding. + {"07e08000", 3, 103}, + {"0faa8000", 4, 57}, + {"1fff8000", 5, 158}, + {"3f828000", 6, 65}, + {"7fc98000", 7, 200}, + // Values requiring one extension byte, plus the maximum amount of padding. + {"07e080808000", 3, 103}, + {"0faa80808000", 4, 57}, + {"1fff80808000", 5, 158}, + {"3f8280808000", 6, 65}, + {"7fc980808000", 7, 200}, + // Values requiring two extension bytes. + {"07b260", 3, 12345}, + {"0f8a2a", 4, 5401}, + {"1fa87f", 5, 16327}, + {"3fd002", 6, 399}, + {"7fff49", 7, 9598}, + // Values requiring two extension bytes, plus one byte of padding. + {"07b2e000", 3, 12345}, + {"0f8aaa00", 4, 5401}, + {"1fa8ff00", 5, 16327}, + {"3fd08200", 6, 399}, + {"7fffc900", 7, 9598}, + // Values requiring two extension bytes, plus the maximum amount of padding. + {"07b2e0808000", 3, 12345}, + {"0f8aaa808000", 4, 5401}, + {"1fa8ff808000", 5, 16327}, + {"3fd082808000", 6, 399}, + {"7fffc9808000", 7, 9598}, + // Values requiring three extension bytes. + {"078ab260", 3, 1579281}, + {"0fc18a2a", 4, 689488}, + {"1fada87f", 5, 2085964}, + {"3fa0d002", 6, 43103}, + {"7ffeff49", 7, 1212541}, + // Values requiring three extension bytes, plus one byte of padding. + {"078ab2e000", 3, 1579281}, + {"0fc18aaa00", 4, 689488}, + {"1fada8ff00", 5, 2085964}, + {"3fa0d08200", 6, 43103}, + {"7ffeffc900", 7, 1212541}, + // Values requiring four extension bytes. + {"079f8ab260", 3, 202147110}, + {"0fa2c18a2a", 4, 88252593}, + {"1fd0ada87f", 5, 266999535}, + {"3ff9a0d002", 6, 5509304}, + {"7f9efeff49", 7, 155189149}, + // Values requiring four extension bytes, plus one byte of padding. + {"079f8ab2e000", 3, 202147110}, + {"0fa2c18aaa00", 4, 88252593}, + {"1fd0ada8ff00", 5, 266999535}, + {"3ff9a0d08200", 6, 5509304}, + {"7f9efeffc900", 7, 155189149}, + // Examples from RFC7541 C.1. + {"0a", 5, 10}, + {"1f9a0a", 5, 1337}, +}; + +TEST_P(HpackVarintDecoderTest, Success) { + if (decode_64_bits()) { + for (size_t i = 0; i < HTTP2_ARRAYSIZE(kSuccessTestData); ++i) { + DecodeExpectSuccess(Http2HexDecode(kSuccessTestData[i].data), + kSuccessTestData[i].prefix_length, + kSuccessTestData[i].expected_value); + } + } else { + for (size_t i = 0; i < HTTP2_ARRAYSIZE(kSuccessTestDataOld); ++i) { + DecodeExpectSuccess(Http2HexDecode(kSuccessTestDataOld[i].data), + kSuccessTestDataOld[i].prefix_length, + kSuccessTestDataOld[i].expected_value); + } + } +} + +// Test data used when decode_64_bits() == true. +struct { + const char* data; + uint32_t prefix_length; +} kErrorTestData[] = { + // Too many extension bytes, all 0s (except for extension bit in each byte). + {"0780808080808080808080", 3}, + {"0f80808080808080808080", 4}, + {"1f80808080808080808080", 5}, + {"3f80808080808080808080", 6}, + {"7f80808080808080808080", 7}, + {"ff80808080808080808080", 8}, + // Too many extension bytes, all 1s. + {"07ffffffffffffffffffff", 3}, + {"0fffffffffffffffffffff", 4}, + {"1fffffffffffffffffffff", 5}, + {"3fffffffffffffffffffff", 6}, + {"7fffffffffffffffffffff", 7}, + {"ffffffffffffffffffffff", 8}, + // Value of 2^64, one higher than maximum of 2^64-1. + {"07f9ffffffffffffffff01", 3}, + {"0ff1ffffffffffffffff01", 4}, + {"1fe1ffffffffffffffff01", 5}, + {"3fc1ffffffffffffffff01", 6}, + {"7f81ffffffffffffffff01", 7}, + {"ff81feffffffffffffff01", 8}, + // Maximum value: 2^64-1, with one byte of padding. + {"07f8ffffffffffffffff8100", 3}, + {"0ff0ffffffffffffffff8100", 4}, + {"1fe0ffffffffffffffff8100", 5}, + {"3fc0ffffffffffffffff8100", 6}, + {"7f80ffffffffffffffff8100", 7}, + {"ff80feffffffffffffff8100", 8}}; + +// Test data used when decode_64_bits() == false. +// In this mode, HpackVarintDecoder allows at most five extension bytes, +// and fifth extension byte must be zero. +struct { + const char* data; + uint32_t prefix_length; +} kErrorTestDataOld[] = { + // Maximum number of extension bytes but last byte is non-zero. + {"078080808001", 3}, + {"0f8080808001", 4}, + {"1f8080808001", 5}, + {"3f8080808001", 6}, + {"7f8080808001", 7}, + // Too many extension bytes, all 0s (except for extension bit in each byte). + {"078080808080", 3}, + {"0f8080808080", 4}, + {"1f8080808080", 5}, + {"3f8080808080", 6}, + {"7f8080808080", 7}, + // Too many extension bytes, all 1s. + {"07ffffffffff", 3}, + {"0fffffffffff", 4}, + {"1fffffffffff", 5}, + {"3fffffffffff", 6}, + {"7fffffffffff", 7}, +}; + +TEST_P(HpackVarintDecoderTest, Error) { + if (decode_64_bits()) { + for (size_t i = 0; i < HTTP2_ARRAYSIZE(kErrorTestData); ++i) { + DecodeExpectError(Http2HexDecode(kErrorTestData[i].data), + kErrorTestData[i].prefix_length); + } + } else { + for (size_t i = 0; i < HTTP2_ARRAYSIZE(kErrorTestDataOld); ++i) { + DecodeExpectError(Http2HexDecode(kErrorTestDataOld[i].data), + kErrorTestDataOld[i].prefix_length); + } + } +} + +} // namespace +} // namespace test +} // namespace http2 diff --git a/chromium/net/third_party/quiche/src/http2/hpack/varint/hpack_varint_encoder.cc b/chromium/net/third_party/quiche/src/http2/hpack/varint/hpack_varint_encoder.cc new file mode 100644 index 00000000000..d3685d988cd --- /dev/null +++ b/chromium/net/third_party/quiche/src/http2/hpack/varint/hpack_varint_encoder.cc @@ -0,0 +1,65 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/http2/hpack/varint/hpack_varint_encoder.h" + +#include "base/logging.h" + +namespace http2 { + +HpackVarintEncoder::HpackVarintEncoder() + : varint_(0), encoding_in_progress_(false) {} + +unsigned char HpackVarintEncoder::StartEncoding(uint8_t high_bits, + uint8_t prefix_length, + uint64_t varint) { + DCHECK(!encoding_in_progress_); + DCHECK_EQ(0u, varint_); + DCHECK_LE(1u, prefix_length); + DCHECK_LE(prefix_length, 8u); + + // prefix_mask defines the sequence of low-order bits of the first byte + // that encode the prefix of the value. It is also the marker in those bits + // of the first byte indicating that at least one extension byte is needed. + const uint8_t prefix_mask = (1 << prefix_length) - 1; + DCHECK_EQ(0, high_bits & prefix_mask); + + if (varint < prefix_mask) { + // The integer fits into the prefix in its entirety. + return high_bits | static_cast<unsigned char>(varint); + } + + // We need extension bytes. + varint_ = varint - prefix_mask; + encoding_in_progress_ = true; + return high_bits | prefix_mask; +} + +size_t HpackVarintEncoder::ResumeEncoding(size_t max_encoded_bytes, + Http2String* output) { + DCHECK(encoding_in_progress_); + DCHECK_NE(0u, max_encoded_bytes); + + size_t encoded_bytes = 0; + while (encoded_bytes < max_encoded_bytes) { + ++encoded_bytes; + if (varint_ < 128) { + // Encode final seven bits, with continuation bit set to zero. + output->push_back(varint_); + varint_ = 0; + encoding_in_progress_ = false; + break; + } + // Encode the next seven bits, with continuation bit set to one. + output->push_back(0b10000000 | (varint_ % 128)); + varint_ >>= 7; + } + return encoded_bytes; +} + +bool HpackVarintEncoder::IsEncodingInProgress() const { + return encoding_in_progress_; +} + +} // namespace http2 diff --git a/chromium/net/third_party/quiche/src/http2/hpack/varint/hpack_varint_encoder.h b/chromium/net/third_party/quiche/src/http2/hpack/varint/hpack_varint_encoder.h new file mode 100644 index 00000000000..68f7474817f --- /dev/null +++ b/chromium/net/third_party/quiche/src/http2/hpack/varint/hpack_varint_encoder.h @@ -0,0 +1,51 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef QUICHE_HTTP2_HPACK_VARINT_HPACK_VARINT_ENCODER_H_ +#define QUICHE_HTTP2_HPACK_VARINT_HPACK_VARINT_ENCODER_H_ + +#include <cstdint> + +#include "net/third_party/quiche/src/http2/platform/api/http2_export.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string.h" + +namespace http2 { + +// HPACK integer encoder class implementing variable length integer +// representation defined in RFC7541, Section 5.1: +// https://httpwg.org/specs/rfc7541.html#integer.representation +class HTTP2_EXPORT_PRIVATE HpackVarintEncoder { + public: + HpackVarintEncoder(); + + // Start encoding an integer. Return the first encoded byte (composed of + // optional high bits and 1 to 8 bit long prefix). It is possible that this + // completes the encoding. Must not be called when previously started + // encoding is still in progress. + unsigned char StartEncoding(uint8_t high_bits, + uint8_t prefix_length, + uint64_t varint); + + // Continue encoding the integer |varint| passed in to StartEncoding(). + // Append the next at most |max_encoded_bytes| encoded octets to |output|. + // Returns the number of encoded octets. Must not be called unless a + // previously started encoding is still in progress. + size_t ResumeEncoding(size_t max_encoded_bytes, Http2String* output); + + // Returns true if encoding an integer has started and is not completed yet. + bool IsEncodingInProgress() const; + + private: + // The original integer shifted to the right by the number of bits already + // encoded. The lower bits shifted away have already been encoded, and + // |varint_| has the higher bits that remain to be encoded. + uint64_t varint_; + + // True when encoding an integer has started and is not completed yet. + bool encoding_in_progress_; +}; + +} // namespace http2 + +#endif // QUICHE_HTTP2_HPACK_VARINT_HPACK_VARINT_ENCODER_H_ diff --git a/chromium/net/third_party/quiche/src/http2/hpack/varint/hpack_varint_encoder_test.cc b/chromium/net/third_party/quiche/src/http2/hpack/varint/hpack_varint_encoder_test.cc new file mode 100644 index 00000000000..a6043a0dd2c --- /dev/null +++ b/chromium/net/third_party/quiche/src/http2/hpack/varint/hpack_varint_encoder_test.cc @@ -0,0 +1,178 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/http2/hpack/varint/hpack_varint_encoder.h" + +#include "net/third_party/quiche/src/http2/platform/api/http2_arraysize.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string_utils.h" + +#include "testing/gtest/include/gtest/gtest.h" + +namespace http2 { +namespace test { +namespace { + +// Freshly constructed encoder is not in the process of encoding. +TEST(HpackVarintEncoderTest, Done) { + HpackVarintEncoder varint_encoder; + EXPECT_FALSE(varint_encoder.IsEncodingInProgress()); +} + +struct { + uint8_t high_bits; + uint8_t prefix_length; + uint64_t value; + uint8_t expected_encoding; +} kShortTestData[] = {{0b10110010, 1, 0, 0b10110010}, + {0b10101100, 2, 2, 0b10101110}, + {0b10100000, 3, 6, 0b10100110}, + {0b10110000, 4, 13, 0b10111101}, + {0b10100000, 5, 8, 0b10101000}, + {0b11000000, 6, 48, 0b11110000}, + {0b10000000, 7, 99, 0b11100011}, + // Example from RFC7541 C.1. + {0b00000000, 5, 10, 0b00001010}}; + +// Encode integers that fit in the prefix. +TEST(HpackVarintEncoderTest, Short) { + HpackVarintEncoder varint_encoder; + + for (size_t i = 0; i < HTTP2_ARRAYSIZE(kShortTestData); ++i) { + EXPECT_EQ(kShortTestData[i].expected_encoding, + varint_encoder.StartEncoding(kShortTestData[i].high_bits, + kShortTestData[i].prefix_length, + kShortTestData[i].value)); + EXPECT_FALSE(varint_encoder.IsEncodingInProgress()); + } +} + +struct { + uint8_t high_bits; + uint8_t prefix_length; + uint64_t value; + const char* expected_encoding; +} kLongTestData[] = { + // One extension byte. + {0b10011000, 3, 103, "9f60"}, + {0b10010000, 4, 57, "9f2a"}, + {0b11000000, 5, 158, "df7f"}, + {0b01000000, 6, 65, "7f02"}, + {0b00000000, 7, 200, "7f49"}, + // Two extension bytes. + {0b10011000, 3, 12345, "9fb260"}, + {0b10010000, 4, 5401, "9f8a2a"}, + {0b11000000, 5, 16327, "dfa87f"}, + {0b01000000, 6, 399, "7fd002"}, + {0b00000000, 7, 9598, "7fff49"}, + // Three extension bytes. + {0b10011000, 3, 1579281, "9f8ab260"}, + {0b10010000, 4, 689488, "9fc18a2a"}, + {0b11000000, 5, 2085964, "dfada87f"}, + {0b01000000, 6, 43103, "7fa0d002"}, + {0b00000000, 7, 1212541, "7ffeff49"}, + // Four extension bytes. + {0b10011000, 3, 202147110, "9f9f8ab260"}, + {0b10010000, 4, 88252593, "9fa2c18a2a"}, + {0b11000000, 5, 266999535, "dfd0ada87f"}, + {0b01000000, 6, 5509304, "7ff9a0d002"}, + {0b00000000, 7, 155189149, "7f9efeff49"}, + // Six extension bytes. + {0b10011000, 3, 3311978140938, "9f83aa9f8ab260"}, + {0b10010000, 4, 1445930244223, "9ff0b0a2c18a2a"}, + {0b11000000, 5, 4374519874169, "dfda84d0ada87f"}, + {0b01000000, 6, 90263420404, "7fb5fbf9a0d002"}, + {0b00000000, 7, 2542616951118, "7fcff19efeff49"}, + // Eight extension bytes. + {0b10011000, 3, 54263449861016696, "9ff19883aa9f8ab260"}, + {0b10010000, 4, 23690121121119891, "9f84fdf0b0a2c18a2a"}, + {0b11000000, 5, 71672133617889215, "dfa0dfda84d0ada87f"}, + {0b01000000, 6, 1478875878881374, "7f9ff0b5fbf9a0d002"}, + {0b00000000, 7, 41658236125045114, "7ffbc1cff19efeff49"}, + // Ten extension bytes. + {0b10011000, 3, 12832019021693745307u, "9f94f1f19883aa9f8ab201"}, + {0b10010000, 4, 9980690937382242223u, "9fa08f84fdf0b0a2c18a01"}, + {0b11000000, 5, 12131360551794650846u, "dfbfdda0dfda84d0ada801"}, + {0b01000000, 6, 15006530362736632796u, "7f9dc79ff0b5fbf9a0d001"}, + {0b00000000, 7, 18445754019193211014u, "7f8790fbc1cff19efeff01"}, + // Maximum value: 2^64-1. + {0b10011000, 3, 18446744073709551615u, "9ff8ffffffffffffffff01"}, + {0b10010000, 4, 18446744073709551615u, "9ff0ffffffffffffffff01"}, + {0b11000000, 5, 18446744073709551615u, "dfe0ffffffffffffffff01"}, + {0b01000000, 6, 18446744073709551615u, "7fc0ffffffffffffffff01"}, + {0b00000000, 7, 18446744073709551615u, "7f80ffffffffffffffff01"}, + // Example from RFC7541 C.1. + {0b00000000, 5, 1337, "1f9a0a"}, +}; + +// Encode integers that do not fit in the prefix. +TEST(HpackVarintEncoderTest, Long) { + HpackVarintEncoder varint_encoder; + + // Test encoding byte by byte, also test encoding in + // a single ResumeEncoding() call. + for (bool byte_by_byte : {true, false}) { + for (size_t i = 0; i < HTTP2_ARRAYSIZE(kLongTestData); ++i) { + Http2String expected_encoding = + Http2HexDecode(kLongTestData[i].expected_encoding); + ASSERT_FALSE(expected_encoding.empty()); + + EXPECT_EQ(static_cast<unsigned char>(expected_encoding[0]), + varint_encoder.StartEncoding(kLongTestData[i].high_bits, + kLongTestData[i].prefix_length, + kLongTestData[i].value)); + EXPECT_TRUE(varint_encoder.IsEncodingInProgress()); + + Http2String output; + if (byte_by_byte) { + while (varint_encoder.IsEncodingInProgress()) { + EXPECT_EQ(1u, varint_encoder.ResumeEncoding(1, &output)); + } + } else { + // TODO(bnc): Factor out maximum number of extension bytes into a + // constant in HpackVarintEncoder. + EXPECT_EQ(expected_encoding.size() - 1, + varint_encoder.ResumeEncoding(10, &output)); + EXPECT_FALSE(varint_encoder.IsEncodingInProgress()); + } + EXPECT_EQ(expected_encoding.size() - 1, output.size()); + EXPECT_EQ(expected_encoding.substr(1), output); + } + } +} + +struct { + uint8_t high_bits; + uint8_t prefix_length; + uint64_t value; + uint8_t expected_encoding_first_byte; +} kLastByteIsZeroTestData[] = { + {0b10110010, 1, 1, 0b10110011}, {0b10101100, 2, 3, 0b10101111}, + {0b10101000, 3, 7, 0b10101111}, {0b10110000, 4, 15, 0b10111111}, + {0b10100000, 5, 31, 0b10111111}, {0b11000000, 6, 63, 0b11111111}, + {0b10000000, 7, 127, 0b11111111}, {0b00000000, 8, 255, 0b11111111}}; + +// Make sure that the encoder outputs the last byte even when it is zero. This +// happens exactly when encoding the value 2^prefix_length - 1. +TEST(HpackVarintEncoderTest, LastByteIsZero) { + HpackVarintEncoder varint_encoder; + + for (size_t i = 0; i < HTTP2_ARRAYSIZE(kLastByteIsZeroTestData); ++i) { + EXPECT_EQ( + kLastByteIsZeroTestData[i].expected_encoding_first_byte, + varint_encoder.StartEncoding(kLastByteIsZeroTestData[i].high_bits, + kLastByteIsZeroTestData[i].prefix_length, + kLastByteIsZeroTestData[i].value)); + EXPECT_TRUE(varint_encoder.IsEncodingInProgress()); + + Http2String output; + EXPECT_EQ(1u, varint_encoder.ResumeEncoding(1, &output)); + ASSERT_EQ(1u, output.size()); + EXPECT_EQ(0b00000000, output[0]); + EXPECT_FALSE(varint_encoder.IsEncodingInProgress()); + } +} + +} // namespace +} // namespace test +} // namespace http2 diff --git a/chromium/net/third_party/quiche/src/http2/hpack/varint/hpack_varint_round_trip_test.cc b/chromium/net/third_party/quiche/src/http2/hpack/varint/hpack_varint_round_trip_test.cc new file mode 100644 index 00000000000..3299d5e2261 --- /dev/null +++ b/chromium/net/third_party/quiche/src/http2/hpack/varint/hpack_varint_round_trip_test.cc @@ -0,0 +1,513 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/http2/hpack/varint/hpack_varint_decoder.h" + +// Test HpackVarintDecoder against data encoded via HpackBlockBuilder, +// which uses HpackVarintEncoder under the hood. + +#include <stddef.h> + +#include <iterator> +#include <set> +#include <vector> + +#include "base/logging.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "net/third_party/quiche/src/http2/hpack/tools/hpack_block_builder.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string_piece.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string_utils.h" +#include "net/third_party/quiche/src/http2/tools/random_decoder_test.h" + +using ::testing::AssertionFailure; +using ::testing::AssertionSuccess; +using ::testing::Bool; +using ::testing::WithParamInterface; + +namespace http2 { +namespace test { +namespace { + +// Save previous value of flag and restore on destruction. +class FlagSaver { + public: + FlagSaver() = delete; + explicit FlagSaver(bool decode_64_bits) + : saved_value_(GetHttp2ReloadableFlag(http2_varint_decode_64_bits)) { + SetHttp2ReloadableFlag(http2_varint_decode_64_bits, decode_64_bits); + } + ~FlagSaver() { + SetHttp2ReloadableFlag(http2_varint_decode_64_bits, saved_value_); + } + + private: + const bool saved_value_; +}; + +// Returns the highest value with the specified number of extension bytes +// and the specified prefix length (bits). +uint64_t HiValueOfExtensionBytes(uint32_t extension_bytes, + uint32_t prefix_length) { + return (1 << prefix_length) - 2 + + (extension_bytes == 0 ? 0 : (1LLU << (extension_bytes * 7))); +} + +class HpackVarintRoundTripTest : public RandomDecoderTest, + public WithParamInterface<bool> { + protected: + HpackVarintRoundTripTest() + : decode_64_bits_(GetParam()), + flag_saver_(decode_64_bits_), + prefix_length_(0) {} + + DecodeStatus StartDecoding(DecodeBuffer* b) override { + CHECK_LT(0u, b->Remaining()); + uint8_t prefix = b->DecodeUInt8(); + return decoder_.Start(prefix, prefix_length_, b); + } + + DecodeStatus ResumeDecoding(DecodeBuffer* b) override { + return decoder_.Resume(b); + } + + void DecodeSeveralWays(uint64_t expected_value, uint32_t expected_offset) { + // The validator is called after each of the several times that the input + // DecodeBuffer is decoded, each with a different segmentation of the input. + // Validate that decoder_.value() matches the expected value. + Validator validator = [expected_value, this]( + const DecodeBuffer& db, + DecodeStatus status) -> AssertionResult { + if (decoder_.value() != expected_value) { + return AssertionFailure() + << "Value doesn't match expected: " << decoder_.value() + << " != " << expected_value; + } + return AssertionSuccess(); + }; + + // First validate that decoding is done and that we've advanced the cursor + // the expected amount. + validator = ValidateDoneAndOffset(expected_offset, validator); + + // StartDecoding, above, requires the DecodeBuffer be non-empty so that it + // can call Start with the prefix byte. + bool return_non_zero_on_first = true; + + DecodeBuffer b(buffer_); + EXPECT_TRUE( + DecodeAndValidateSeveralWays(&b, return_non_zero_on_first, validator)); + + EXPECT_EQ(expected_value, decoder_.value()); + EXPECT_EQ(expected_offset, b.Offset()); + } + + void EncodeNoRandom(uint64_t value, uint8_t prefix_length) { + DCHECK_LE(3, prefix_length); + DCHECK_LE(prefix_length, 8); + prefix_length_ = prefix_length; + + HpackBlockBuilder bb; + bb.AppendHighBitsAndVarint(0, prefix_length_, value); + buffer_ = bb.buffer(); + ASSERT_LT(0u, buffer_.size()); + + const uint8_t prefix_mask = (1 << prefix_length_) - 1; + ASSERT_EQ(static_cast<uint8_t>(buffer_[0]), + static_cast<uint8_t>(buffer_[0]) & prefix_mask); + } + + void Encode(uint64_t value, uint8_t prefix_length) { + EncodeNoRandom(value, prefix_length); + // Add some random bits to the prefix (the first byte) above the mask. + uint8_t prefix = buffer_[0]; + buffer_[0] = prefix | (Random().Rand8() << prefix_length); + const uint8_t prefix_mask = (1 << prefix_length_) - 1; + ASSERT_EQ(prefix, buffer_[0] & prefix_mask); + } + + // This is really a test of HpackBlockBuilder, making sure that the input to + // HpackVarintDecoder is as expected, which also acts as confirmation that + // my thinking about the encodings being used by the tests, i.e. cover the + // range desired. + void ValidateEncoding(uint64_t value, + uint64_t minimum, + uint64_t maximum, + size_t expected_bytes) { + ASSERT_EQ(expected_bytes, buffer_.size()); + if (expected_bytes > 1) { + const uint8_t prefix_mask = (1 << prefix_length_) - 1; + EXPECT_EQ(prefix_mask, buffer_[0] & prefix_mask); + size_t last = expected_bytes - 1; + for (size_t ndx = 1; ndx < last; ++ndx) { + // Before the last extension byte, we expect the high-bit set. + uint8_t byte = buffer_[ndx]; + if (value == minimum) { + EXPECT_EQ(0x80, byte) << "ndx=" << ndx; + } else if (value == maximum) { + if (expected_bytes < 11) { + EXPECT_EQ(0xff, byte) << "ndx=" << ndx; + } + } else { + EXPECT_EQ(0x80, byte & 0x80) << "ndx=" << ndx; + } + } + // The last extension byte should not have the high-bit set. + uint8_t byte = buffer_[last]; + if (value == minimum) { + if (expected_bytes == 2) { + EXPECT_EQ(0x00, byte); + } else { + EXPECT_EQ(0x01, byte); + } + } else if (value == maximum) { + if (expected_bytes < 11) { + EXPECT_EQ(0x7f, byte); + } + } else { + EXPECT_EQ(0x00, byte & 0x80); + } + } else { + const uint8_t prefix_mask = (1 << prefix_length_) - 1; + EXPECT_EQ(value, static_cast<uint32_t>(buffer_[0] & prefix_mask)); + EXPECT_LT(value, prefix_mask); + } + } + + void EncodeAndDecodeValues(const std::set<uint64_t>& values, + uint8_t prefix_length, + size_t expected_bytes) { + CHECK(!values.empty()); + const uint64_t minimum = *values.begin(); + const uint64_t maximum = *values.rbegin(); + for (const uint64_t value : values) { + Encode(value, prefix_length); // Sets buffer_. + + Http2String msg = Http2StrCat("value=", value, " (0x", Http2Hex(value), + "), prefix_length=", prefix_length, + ", expected_bytes=", expected_bytes, "\n", + Http2HexDump(buffer_)); + + if (value == minimum) { + LOG(INFO) << "Checking minimum; " << msg; + } else if (value == maximum) { + LOG(INFO) << "Checking maximum; " << msg; + } + + SCOPED_TRACE(msg); + ValidateEncoding(value, minimum, maximum, expected_bytes); + DecodeSeveralWays(value, expected_bytes); + + // Append some random data to the end of buffer_ and repeat. That random + // data should be ignored. + buffer_.append(Random().RandString(1 + Random().Uniform(10))); + DecodeSeveralWays(value, expected_bytes); + + // If possible, add extension bytes that don't change the value. + if (1 < expected_bytes) { + buffer_.resize(expected_bytes); + for (uint8_t total_bytes = expected_bytes + 1; total_bytes <= 6; + ++total_bytes) { + // Mark the current last byte as not being the last one. + EXPECT_EQ(0x00, 0x80 & buffer_.back()); + buffer_.back() |= 0x80; + buffer_.push_back('\0'); + DecodeSeveralWays(value, total_bytes); + } + } + } + } + + // Encode values (all or some of it) in [start, start+range). Check + // that |start| is the smallest value and |start+range-1| is the largest value + // corresponding to |expected_bytes|, except if |expected_bytes| is maximal. + void EncodeAndDecodeValuesInRange(uint64_t start, + uint64_t range, + uint8_t prefix_length, + size_t expected_bytes) { + const uint8_t prefix_mask = (1 << prefix_length) - 1; + const uint64_t beyond = start + range; + + LOG(INFO) << "############################################################"; + LOG(INFO) << "prefix_length=" << static_cast<int>(prefix_length); + LOG(INFO) << "prefix_mask=" << std::hex << static_cast<int>(prefix_mask); + LOG(INFO) << "start=" << start << " (" << std::hex << start << ")"; + LOG(INFO) << "range=" << range << " (" << std::hex << range << ")"; + LOG(INFO) << "beyond=" << beyond << " (" << std::hex << beyond << ")"; + LOG(INFO) << "expected_bytes=" << expected_bytes; + + if (expected_bytes < 11) { + // Confirm the claim that beyond requires more bytes. + Encode(beyond, prefix_length); + EXPECT_EQ(expected_bytes + 1, buffer_.size()) << Http2HexDump(buffer_); + } + + std::set<uint64_t> values; + if (range < 200) { + // Select all values in the range. + for (uint64_t offset = 0; offset < range; ++offset) { + values.insert(start + offset); + } + } else { + // Select some values in this range, including the minimum and maximum + // values that require exactly |expected_bytes| extension bytes. + values.insert({start, start + 1, beyond - 2, beyond - 1}); + while (values.size() < 100) { + values.insert(Random().UniformInRange(start, beyond - 1)); + } + } + + EncodeAndDecodeValues(values, prefix_length, expected_bytes); + } + + // |flag_saver_| must preceed |decoder_| so that the flag is already set when + // |decoder_| is constructed. + const bool decode_64_bits_; + FlagSaver flag_saver_; + HpackVarintDecoder decoder_; + Http2String buffer_; + uint8_t prefix_length_; +}; + +INSTANTIATE_TEST_CASE_P(HpackVarintRoundTripTest, + HpackVarintRoundTripTest, + Bool()); + +// To help me and future debuggers of varint encodings, this LOGs out the +// transition points where a new extension byte is added. +TEST_P(HpackVarintRoundTripTest, Encode) { + for (int prefix_length = 3; prefix_length <= 8; ++prefix_length) { + const uint64_t a = HiValueOfExtensionBytes(0, prefix_length); + const uint64_t b = HiValueOfExtensionBytes(1, prefix_length); + const uint64_t c = HiValueOfExtensionBytes(2, prefix_length); + const uint64_t d = HiValueOfExtensionBytes(3, prefix_length); + const uint64_t e = HiValueOfExtensionBytes(4, prefix_length); + const uint64_t f = HiValueOfExtensionBytes(5, prefix_length); + const uint64_t g = HiValueOfExtensionBytes(6, prefix_length); + const uint64_t h = HiValueOfExtensionBytes(7, prefix_length); + const uint64_t i = HiValueOfExtensionBytes(8, prefix_length); + const uint64_t j = HiValueOfExtensionBytes(9, prefix_length); + + LOG(INFO) << "############################################################"; + LOG(INFO) << "prefix_length=" << prefix_length << " a=" << a + << " b=" << b << " c=" << c << " d=" << d << " e=" << e + << " f=" << f << " g=" << g << " h=" << h << " i=" << i + << " j=" << j; + + std::vector<uint64_t> values = { + 0, 1, // Force line break. + a - 1, a, a + 1, a + 2, a + 3, // Force line break. + b - 1, b, b + 1, b + 2, b + 3, // Force line break. + c - 1, c, c + 1, c + 2, c + 3, // Force line break. + d - 1, d, d + 1, d + 2, d + 3, // Force line break. + e - 1, e, e + 1, e + 2, e + 3 // Force line break. + }; + if (decode_64_bits_) { + for (auto value : { + f - 1, f, f + 1, f + 2, f + 3, // Force line break. + g - 1, g, g + 1, g + 2, g + 3, // Force line break. + h - 1, h, h + 1, h + 2, h + 3, // Force line break. + i - 1, i, i + 1, i + 2, i + 3, // Force line break. + j - 1, j, j + 1, j + 2, j + 3, // Force line break. + }) { + values.push_back(value); + } + } + + for (uint64_t value : values) { + EncodeNoRandom(value, prefix_length); + Http2String dump = Http2HexDump(buffer_); + LOG(INFO) << Http2StringPrintf("%10llu %0#18x ", value, value) + << Http2HexDump(buffer_).substr(7); + } + } +} + +TEST_P(HpackVarintRoundTripTest, FromSpec1337) { + DecodeBuffer b(Http2StringPiece("\x1f\x9a\x0a")); + uint32_t prefix_length = 5; + uint8_t p = b.DecodeUInt8(); + EXPECT_EQ(1u, b.Offset()); + EXPECT_EQ(DecodeStatus::kDecodeDone, decoder_.Start(p, prefix_length, &b)); + EXPECT_EQ(3u, b.Offset()); + EXPECT_EQ(1337u, decoder_.value()); + + EncodeNoRandom(1337, prefix_length); + EXPECT_EQ(3u, buffer_.size()); + EXPECT_EQ('\x1f', buffer_[0]); + EXPECT_EQ('\x9a', buffer_[1]); + EXPECT_EQ('\x0a', buffer_[2]); +} + +// Test all the values that fit into the prefix (one less than the mask). +TEST_P(HpackVarintRoundTripTest, ValidatePrefixOnly) { + for (int prefix_length = 3; prefix_length <= 8; ++prefix_length) { + const uint8_t prefix_mask = (1 << prefix_length) - 1; + EncodeAndDecodeValuesInRange(0, prefix_mask, prefix_length, 1); + } +} + +// Test all values that require exactly 1 extension byte. +TEST_P(HpackVarintRoundTripTest, ValidateOneExtensionByte) { + for (int prefix_length = 3; prefix_length <= 8; ++prefix_length) { + const uint64_t start = HiValueOfExtensionBytes(0, prefix_length) + 1; + EncodeAndDecodeValuesInRange(start, 128, prefix_length, 2); + } +} + +// Test *some* values that require exactly 2 extension bytes. +TEST_P(HpackVarintRoundTripTest, ValidateTwoExtensionBytes) { + for (int prefix_length = 3; prefix_length <= 8; ++prefix_length) { + const uint64_t start = HiValueOfExtensionBytes(1, prefix_length) + 1; + const uint64_t range = 127 << 7; + + EncodeAndDecodeValuesInRange(start, range, prefix_length, 3); + } +} + +// Test *some* values that require 3 extension bytes. +TEST_P(HpackVarintRoundTripTest, ValidateThreeExtensionBytes) { + for (int prefix_length = 3; prefix_length <= 8; ++prefix_length) { + const uint64_t start = HiValueOfExtensionBytes(2, prefix_length) + 1; + const uint64_t range = 127 << 14; + + EncodeAndDecodeValuesInRange(start, range, prefix_length, 4); + } +} + +// Test *some* values that require 4 extension bytes. +TEST_P(HpackVarintRoundTripTest, ValidateFourExtensionBytes) { + for (int prefix_length = 3; prefix_length <= 8; ++prefix_length) { + const uint64_t start = HiValueOfExtensionBytes(3, prefix_length) + 1; + const uint64_t range = 127 << 21; + + EncodeAndDecodeValuesInRange(start, range, prefix_length, 5); + } +} + +// Test *some* values that require 5 extension bytes. +TEST_P(HpackVarintRoundTripTest, ValidateFiveExtensionBytes) { + if (!decode_64_bits_) { + return; + } + + for (int prefix_length = 3; prefix_length <= 8; ++prefix_length) { + const uint64_t start = HiValueOfExtensionBytes(4, prefix_length) + 1; + const uint64_t range = 127llu << 28; + + EncodeAndDecodeValuesInRange(start, range, prefix_length, 6); + } +} + +// Test *some* values that require 6 extension bytes. +TEST_P(HpackVarintRoundTripTest, ValidateSixExtensionBytes) { + if (!decode_64_bits_) { + return; + } + + for (int prefix_length = 3; prefix_length <= 8; ++prefix_length) { + const uint64_t start = HiValueOfExtensionBytes(5, prefix_length) + 1; + const uint64_t range = 127llu << 35; + + EncodeAndDecodeValuesInRange(start, range, prefix_length, 7); + } +} + +// Test *some* values that require 7 extension bytes. +TEST_P(HpackVarintRoundTripTest, ValidateSevenExtensionBytes) { + if (!decode_64_bits_) { + return; + } + + for (int prefix_length = 3; prefix_length <= 8; ++prefix_length) { + const uint64_t start = HiValueOfExtensionBytes(6, prefix_length) + 1; + const uint64_t range = 127llu << 42; + + EncodeAndDecodeValuesInRange(start, range, prefix_length, 8); + } +} + +// Test *some* values that require 8 extension bytes. +TEST_P(HpackVarintRoundTripTest, ValidateEightExtensionBytes) { + if (!decode_64_bits_) { + return; + } + + for (int prefix_length = 3; prefix_length <= 8; ++prefix_length) { + const uint64_t start = HiValueOfExtensionBytes(7, prefix_length) + 1; + const uint64_t range = 127llu << 49; + + EncodeAndDecodeValuesInRange(start, range, prefix_length, 9); + } +} + +// Test *some* values that require 9 extension bytes. +TEST_P(HpackVarintRoundTripTest, ValidateNineExtensionBytes) { + if (!decode_64_bits_) { + return; + } + + for (int prefix_length = 3; prefix_length <= 8; ++prefix_length) { + const uint64_t start = HiValueOfExtensionBytes(8, prefix_length) + 1; + const uint64_t range = 127llu << 56; + + EncodeAndDecodeValuesInRange(start, range, prefix_length, 10); + } +} + +// Test *some* values that require 10 extension bytes. +TEST_P(HpackVarintRoundTripTest, ValidateTenExtensionBytes) { + if (!decode_64_bits_) { + return; + } + + for (int prefix_length = 3; prefix_length <= 8; ++prefix_length) { + const uint64_t start = HiValueOfExtensionBytes(9, prefix_length) + 1; + const uint64_t range = std::numeric_limits<uint64_t>::max() - start; + + EncodeAndDecodeValuesInRange(start, range, prefix_length, 11); + } +} + +// Test the value one larger than the largest that can be decoded. +TEST_P(HpackVarintRoundTripTest, ValueTooLarge) { + // New implementation can decode any integer that HpackVarintEncoder can + // encode. + if (decode_64_bits_) { + return; + } + + for (prefix_length_ = 3; prefix_length_ <= 8; ++prefix_length_) { + const uint64_t too_large = (1 << 28) + (1 << prefix_length_) - 1; + const uint32_t expected_offset = 6; + HpackBlockBuilder bb; + bb.AppendHighBitsAndVarint(0, prefix_length_, too_large); + buffer_ = bb.buffer(); + + // The validator is called after each of the several times that the input + // DecodeBuffer is decoded, each with a different segmentation of the input. + // Validate that decoder_.value() matches the expected value. + bool validated = false; + Validator validator = [&validated, expected_offset]( + const DecodeBuffer& db, + DecodeStatus status) -> AssertionResult { + validated = true; + VERIFY_EQ(DecodeStatus::kDecodeError, status); + VERIFY_EQ(expected_offset, db.Offset()); + return AssertionSuccess(); + }; + + // StartDecoding, above, requires the DecodeBuffer be non-empty so that it + // can call Start with the prefix byte. + bool return_non_zero_on_first = true; + DecodeBuffer b(buffer_); + EXPECT_TRUE( + DecodeAndValidateSeveralWays(&b, return_non_zero_on_first, validator)); + EXPECT_EQ(expected_offset, b.Offset()); + EXPECT_TRUE(validated); + } +} + +} // namespace +} // namespace test +} // namespace http2 diff --git a/chromium/net/third_party/quiche/src/http2/http2_constants.cc b/chromium/net/third_party/quiche/src/http2/http2_constants.cc new file mode 100644 index 00000000000..d4d59a555c7 --- /dev/null +++ b/chromium/net/third_party/quiche/src/http2/http2_constants.cc @@ -0,0 +1,150 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/http2/http2_constants.h" + +#include "base/logging.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string_piece.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string_utils.h" + +namespace http2 { + +Http2String Http2FrameTypeToString(Http2FrameType v) { + switch (v) { + case Http2FrameType::DATA: + return "DATA"; + case Http2FrameType::HEADERS: + return "HEADERS"; + case Http2FrameType::PRIORITY: + return "PRIORITY"; + case Http2FrameType::RST_STREAM: + return "RST_STREAM"; + case Http2FrameType::SETTINGS: + return "SETTINGS"; + case Http2FrameType::PUSH_PROMISE: + return "PUSH_PROMISE"; + case Http2FrameType::PING: + return "PING"; + case Http2FrameType::GOAWAY: + return "GOAWAY"; + case Http2FrameType::WINDOW_UPDATE: + return "WINDOW_UPDATE"; + case Http2FrameType::CONTINUATION: + return "CONTINUATION"; + case Http2FrameType::ALTSVC: + return "ALTSVC"; + } + return Http2StrCat("UnknownFrameType(", static_cast<int>(v), ")"); +} + +Http2String Http2FrameTypeToString(uint8_t v) { + return Http2FrameTypeToString(static_cast<Http2FrameType>(v)); +} + +Http2String Http2FrameFlagsToString(Http2FrameType type, uint8_t flags) { + Http2String s; + // Closure to append flag name |v| to the Http2String |s|, + // and to clear |bit| from |flags|. + auto append_and_clear = [&s, &flags](Http2StringPiece v, uint8_t bit) { + if (!s.empty()) { + s.push_back('|'); + } + Http2StrAppend(&s, v); + flags ^= bit; + }; + if (flags & 0x01) { + if (type == Http2FrameType::DATA || type == Http2FrameType::HEADERS) { + append_and_clear("END_STREAM", Http2FrameFlag::END_STREAM); + } else if (type == Http2FrameType::SETTINGS || + type == Http2FrameType::PING) { + append_and_clear("ACK", Http2FrameFlag::ACK); + } + } + if (flags & 0x04) { + if (type == Http2FrameType::HEADERS || + type == Http2FrameType::PUSH_PROMISE || + type == Http2FrameType::CONTINUATION) { + append_and_clear("END_HEADERS", Http2FrameFlag::END_HEADERS); + } + } + if (flags & 0x08) { + if (type == Http2FrameType::DATA || type == Http2FrameType::HEADERS || + type == Http2FrameType::PUSH_PROMISE) { + append_and_clear("PADDED", Http2FrameFlag::PADDED); + } + } + if (flags & 0x20) { + if (type == Http2FrameType::HEADERS) { + append_and_clear("PRIORITY", Http2FrameFlag::PRIORITY); + } + } + if (flags != 0) { + append_and_clear(Http2StringPrintf("0x%02x", flags), flags); + } + DCHECK_EQ(0, flags); + return s; +} +Http2String Http2FrameFlagsToString(uint8_t type, uint8_t flags) { + return Http2FrameFlagsToString(static_cast<Http2FrameType>(type), flags); +} + +Http2String Http2ErrorCodeToString(uint32_t v) { + switch (v) { + case 0x0: + return "NO_ERROR"; + case 0x1: + return "PROTOCOL_ERROR"; + case 0x2: + return "INTERNAL_ERROR"; + case 0x3: + return "FLOW_CONTROL_ERROR"; + case 0x4: + return "SETTINGS_TIMEOUT"; + case 0x5: + return "STREAM_CLOSED"; + case 0x6: + return "FRAME_SIZE_ERROR"; + case 0x7: + return "REFUSED_STREAM"; + case 0x8: + return "CANCEL"; + case 0x9: + return "COMPRESSION_ERROR"; + case 0xa: + return "CONNECT_ERROR"; + case 0xb: + return "ENHANCE_YOUR_CALM"; + case 0xc: + return "INADEQUATE_SECURITY"; + case 0xd: + return "HTTP_1_1_REQUIRED"; + } + return Http2StrCat("UnknownErrorCode(0x", Http2Hex(v), ")"); +} +Http2String Http2ErrorCodeToString(Http2ErrorCode v) { + return Http2ErrorCodeToString(static_cast<uint32_t>(v)); +} + +Http2String Http2SettingsParameterToString(uint32_t v) { + switch (v) { + case 0x1: + return "HEADER_TABLE_SIZE"; + case 0x2: + return "ENABLE_PUSH"; + case 0x3: + return "MAX_CONCURRENT_STREAMS"; + case 0x4: + return "INITIAL_WINDOW_SIZE"; + case 0x5: + return "MAX_FRAME_SIZE"; + case 0x6: + return "MAX_HEADER_LIST_SIZE"; + } + return Http2StrCat("UnknownSettingsParameter(0x", Http2Hex(v), ")"); +} +Http2String Http2SettingsParameterToString(Http2SettingsParameter v) { + return Http2SettingsParameterToString(static_cast<uint32_t>(v)); +} + +} // namespace http2 diff --git a/chromium/net/third_party/quiche/src/http2/http2_constants.h b/chromium/net/third_party/quiche/src/http2/http2_constants.h new file mode 100644 index 00000000000..93920069c5a --- /dev/null +++ b/chromium/net/third_party/quiche/src/http2/http2_constants.h @@ -0,0 +1,261 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef QUICHE_HTTP2_HTTP2_CONSTANTS_H_ +#define QUICHE_HTTP2_HTTP2_CONSTANTS_H_ + +// Constants from the HTTP/2 spec, RFC 7540, and associated helper functions. + +#include <cstdint> +#include <iosfwd> +#include <ostream> + +#include "net/third_party/quiche/src/http2/platform/api/http2_export.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string.h" + +namespace http2 { + +// TODO(jamessynge): create http2_simple_types for types similar to +// SpdyStreamId, but not for structures like Http2FrameHeader. Then will be +// able to move these stream id functions there. +constexpr uint32_t UInt31Mask() { + return 0x7fffffff; +} +constexpr uint32_t StreamIdMask() { + return UInt31Mask(); +} + +// The value used to identify types of frames. Upper case to match the RFC. +// The comments indicate which flags are valid for that frame type. +// ALTSVC is defined in http://httpwg.org/http-extensions/alt-svc.html +// (not yet final standard as of March 2016, but close). +enum class Http2FrameType : uint8_t { + DATA = 0, // END_STREAM | PADDED + HEADERS = 1, // END_STREAM | END_HEADERS | PADDED | PRIORITY + PRIORITY = 2, // + RST_STREAM = 3, // + SETTINGS = 4, // ACK + PUSH_PROMISE = 5, // END_HEADERS | PADDED + PING = 6, // ACK + GOAWAY = 7, // + WINDOW_UPDATE = 8, // + CONTINUATION = 9, // END_HEADERS + ALTSVC = 10, // +}; + +// Is the frame type known/supported? +inline bool IsSupportedHttp2FrameType(uint32_t v) { + return v <= static_cast<uint32_t>(Http2FrameType::ALTSVC); +} +inline bool IsSupportedHttp2FrameType(Http2FrameType v) { + return IsSupportedHttp2FrameType(static_cast<uint32_t>(v)); +} + +// The return type is 'Http2String' so that they can generate a unique string +// for each unsupported value. Since these are just used for debugging/error +// messages, that isn't a cost to we need to worry about. The same applies to +// the functions later in this file. +HTTP2_EXPORT_PRIVATE Http2String Http2FrameTypeToString(Http2FrameType v); +HTTP2_EXPORT_PRIVATE Http2String Http2FrameTypeToString(uint8_t v); +HTTP2_EXPORT_PRIVATE inline std::ostream& operator<<(std::ostream& out, + Http2FrameType v) { + return out << Http2FrameTypeToString(v); +} + +// Flags that appear in supported frame types. These are treated as bit masks. +// The comments indicate for which frame types the flag is valid. +enum Http2FrameFlag { + END_STREAM = 0x01, // DATA, HEADERS + ACK = 0x01, // SETTINGS, PING + END_HEADERS = 0x04, // HEADERS, PUSH_PROMISE, CONTINUATION + PADDED = 0x08, // DATA, HEADERS, PUSH_PROMISE + PRIORITY = 0x20, // HEADERS +}; + +// Formats zero or more flags for the specified type of frame. Returns an +// empty string if flags==0. +HTTP2_EXPORT_PRIVATE Http2String Http2FrameFlagsToString(Http2FrameType type, + uint8_t flags); +HTTP2_EXPORT_PRIVATE Http2String Http2FrameFlagsToString(uint8_t type, + uint8_t flags); + +// Error codes for GOAWAY and RST_STREAM frames. +enum class Http2ErrorCode : uint32_t { + // The associated condition is not a result of an error. For example, a GOAWAY + // might include this code to indicate graceful shutdown of a connection. + HTTP2_NO_ERROR = 0x0, + + // The endpoint detected an unspecific protocol error. This error is for use + // when a more specific error code is not available. + PROTOCOL_ERROR = 0x1, + + // The endpoint encountered an unexpected internal error. + INTERNAL_ERROR = 0x2, + + // The endpoint detected that its peer violated the flow-control protocol. + FLOW_CONTROL_ERROR = 0x3, + + // The endpoint sent a SETTINGS frame but did not receive a response in a + // timely manner. See Section 6.5.3 ("Settings Synchronization"). + SETTINGS_TIMEOUT = 0x4, + + // The endpoint received a frame after a stream was half-closed. + STREAM_CLOSED = 0x5, + + // The endpoint received a frame with an invalid size. + FRAME_SIZE_ERROR = 0x6, + + // The endpoint refused the stream prior to performing any application + // processing (see Section 8.1.4 for details). + REFUSED_STREAM = 0x7, + + // Used by the endpoint to indicate that the stream is no longer needed. + CANCEL = 0x8, + + // The endpoint is unable to maintain the header compression context + // for the connection. + COMPRESSION_ERROR = 0x9, + + // The connection established in response to a CONNECT request (Section 8.3) + // was reset or abnormally closed. + CONNECT_ERROR = 0xa, + + // The endpoint detected that its peer is exhibiting a behavior that might + // be generating excessive load. + ENHANCE_YOUR_CALM = 0xb, + + // The underlying transport has properties that do not meet minimum + // security requirements (see Section 9.2). + INADEQUATE_SECURITY = 0xc, + + // The endpoint requires that HTTP/1.1 be used instead of HTTP/2. + HTTP_1_1_REQUIRED = 0xd, +}; + +// Is the error code supported? (So far that means it is in RFC 7540.) +inline bool IsSupportedHttp2ErrorCode(uint32_t v) { + return v <= static_cast<uint32_t>(Http2ErrorCode::HTTP_1_1_REQUIRED); +} +inline bool IsSupportedHttp2ErrorCode(Http2ErrorCode v) { + return IsSupportedHttp2ErrorCode(static_cast<uint32_t>(v)); +} + +// Format the specified error code. +HTTP2_EXPORT_PRIVATE Http2String Http2ErrorCodeToString(uint32_t v); +HTTP2_EXPORT_PRIVATE Http2String Http2ErrorCodeToString(Http2ErrorCode v); +HTTP2_EXPORT_PRIVATE inline std::ostream& operator<<(std::ostream& out, + Http2ErrorCode v) { + return out << Http2ErrorCodeToString(v); +} + +// Supported parameters in SETTINGS frames; so far just those in RFC 7540. +enum class Http2SettingsParameter : uint16_t { + // Allows the sender to inform the remote endpoint of the maximum size of the + // header compression table used to decode header blocks, in octets. The + // encoder can select any size equal to or less than this value by using + // signaling specific to the header compression format inside a header block + // (see [COMPRESSION]). The initial value is 4,096 octets. + HEADER_TABLE_SIZE = 0x1, + + // This setting can be used to disable server push (Section 8.2). An endpoint + // MUST NOT send a PUSH_PROMISE frame if it receives this parameter set to a + // value of 0. An endpoint that has both set this parameter to 0 and had it + // acknowledged MUST treat the receipt of a PUSH_PROMISE frame as a connection + // error (Section 5.4.1) of type PROTOCOL_ERROR. + // + // The initial value is 1, which indicates that server push is permitted. Any + // value other than 0 or 1 MUST be treated as a connection error (Section + // 5.4.1) of type PROTOCOL_ERROR. + ENABLE_PUSH = 0x2, + + // Indicates the maximum number of concurrent streams that the sender will + // allow. This limit is directional: it applies to the number of streams that + // the sender permits the receiver to create. Initially, there is no limit to + // this value. It is recommended that this value be no smaller than 100, so as + // to not unnecessarily limit parallelism. + // + // A value of 0 for MAX_CONCURRENT_STREAMS SHOULD NOT be treated as + // special by endpoints. A zero value does prevent the creation of new + // streams; however, this can also happen for any limit that is exhausted with + // active streams. Servers SHOULD only set a zero value for short durations; + // if a server does not wish to accept requests, closing the connection is + // more appropriate. + MAX_CONCURRENT_STREAMS = 0x3, + + // Indicates the sender's initial window size (in octets) for stream-level + // flow control. The initial value is 2^16-1 (65,535) octets. + // + // This setting affects the window size of all streams (see Section 6.9.2). + // + // Values above the maximum flow-control window size of 2^31-1 MUST be treated + // as a connection error (Section 5.4.1) of type FLOW_CONTROL_ERROR. + INITIAL_WINDOW_SIZE = 0x4, + + // Indicates the size of the largest frame payload that the sender is willing + // to receive, in octets. + // + // The initial value is 2^14 (16,384) octets. The value advertised by an + // endpoint MUST be between this initial value and the maximum allowed frame + // size (2^24-1 or 16,777,215 octets), inclusive. Values outside this range + // MUST be treated as a connection error (Section 5.4.1) of type + // PROTOCOL_ERROR. + MAX_FRAME_SIZE = 0x5, + + // This advisory setting informs a peer of the maximum size of header list + // that the sender is prepared to accept, in octets. The value is based on the + // uncompressed size of header fields, including the length of the name and + // value in octets plus an overhead of 32 octets for each header field. + // + // For any given request, a lower limit than what is advertised MAY be + // enforced. The initial value of this setting is unlimited. + MAX_HEADER_LIST_SIZE = 0x6, +}; + +// Is the settings parameter supported (so far that means it is in RFC 7540)? +inline bool IsSupportedHttp2SettingsParameter(uint32_t v) { + return 0 < v && v <= static_cast<uint32_t>( + Http2SettingsParameter::MAX_HEADER_LIST_SIZE); +} +inline bool IsSupportedHttp2SettingsParameter(Http2SettingsParameter v) { + return IsSupportedHttp2SettingsParameter(static_cast<uint32_t>(v)); +} + +// Format the specified settings parameter. +HTTP2_EXPORT_PRIVATE Http2String Http2SettingsParameterToString(uint32_t v); +HTTP2_EXPORT_PRIVATE Http2String +Http2SettingsParameterToString(Http2SettingsParameter v); +inline std::ostream& operator<<(std::ostream& out, Http2SettingsParameter v) { + return out << Http2SettingsParameterToString(v); +} + +// Information about the initial, minimum and maximum value of settings (not +// applicable to all settings parameters). +class Http2SettingsInfo { + public: + // Default value for HEADER_TABLE_SIZE. + static constexpr uint32_t DefaultHeaderTableSize() { return 4096; } + + // Default value for ENABLE_PUSH. + static constexpr bool DefaultEnablePush() { return true; } + + // Default value for INITIAL_WINDOW_SIZE. + static constexpr uint32_t DefaultInitialWindowSize() { return 65535; } + + // Maximum value for INITIAL_WINDOW_SIZE, and for the connection flow control + // window, and for each stream flow control window. + static constexpr uint32_t MaximumWindowSize() { return UInt31Mask(); } + + // Default value for MAX_FRAME_SIZE. + static constexpr uint32_t DefaultMaxFrameSize() { return 16384; } + + // Minimum value for MAX_FRAME_SIZE. + static constexpr uint32_t MinimumMaxFrameSize() { return 16384; } + + // Maximum value for MAX_FRAME_SIZE. + static constexpr uint32_t MaximumMaxFrameSize() { return (1 << 24) - 1; } +}; + +} // namespace http2 + +#endif // QUICHE_HTTP2_HTTP2_CONSTANTS_H_ diff --git a/chromium/net/third_party/quiche/src/http2/http2_constants_test.cc b/chromium/net/third_party/quiche/src/http2/http2_constants_test.cc new file mode 100644 index 00000000000..223082adafe --- /dev/null +++ b/chromium/net/third_party/quiche/src/http2/http2_constants_test.cc @@ -0,0 +1,271 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/http2/http2_constants.h" + +#include "testing/gtest/include/gtest/gtest.h" + +namespace http2 { +namespace test { +namespace { + +class Http2ConstantsTest : public testing::Test {}; + +TEST(Http2ConstantsTest, Http2FrameType) { + EXPECT_EQ(Http2FrameType::DATA, static_cast<Http2FrameType>(0)); + EXPECT_EQ(Http2FrameType::HEADERS, static_cast<Http2FrameType>(1)); + EXPECT_EQ(Http2FrameType::PRIORITY, static_cast<Http2FrameType>(2)); + EXPECT_EQ(Http2FrameType::RST_STREAM, static_cast<Http2FrameType>(3)); + EXPECT_EQ(Http2FrameType::SETTINGS, static_cast<Http2FrameType>(4)); + EXPECT_EQ(Http2FrameType::PUSH_PROMISE, static_cast<Http2FrameType>(5)); + EXPECT_EQ(Http2FrameType::PING, static_cast<Http2FrameType>(6)); + EXPECT_EQ(Http2FrameType::GOAWAY, static_cast<Http2FrameType>(7)); + EXPECT_EQ(Http2FrameType::WINDOW_UPDATE, static_cast<Http2FrameType>(8)); + EXPECT_EQ(Http2FrameType::CONTINUATION, static_cast<Http2FrameType>(9)); + EXPECT_EQ(Http2FrameType::ALTSVC, static_cast<Http2FrameType>(10)); +} + +TEST(Http2ConstantsTest, Http2FrameTypeToString) { + EXPECT_EQ("DATA", Http2FrameTypeToString(Http2FrameType::DATA)); + EXPECT_EQ("HEADERS", Http2FrameTypeToString(Http2FrameType::HEADERS)); + EXPECT_EQ("PRIORITY", Http2FrameTypeToString(Http2FrameType::PRIORITY)); + EXPECT_EQ("RST_STREAM", Http2FrameTypeToString(Http2FrameType::RST_STREAM)); + EXPECT_EQ("SETTINGS", Http2FrameTypeToString(Http2FrameType::SETTINGS)); + EXPECT_EQ("PUSH_PROMISE", + Http2FrameTypeToString(Http2FrameType::PUSH_PROMISE)); + EXPECT_EQ("PING", Http2FrameTypeToString(Http2FrameType::PING)); + EXPECT_EQ("GOAWAY", Http2FrameTypeToString(Http2FrameType::GOAWAY)); + EXPECT_EQ("WINDOW_UPDATE", + Http2FrameTypeToString(Http2FrameType::WINDOW_UPDATE)); + EXPECT_EQ("CONTINUATION", + Http2FrameTypeToString(Http2FrameType::CONTINUATION)); + EXPECT_EQ("ALTSVC", Http2FrameTypeToString(Http2FrameType::ALTSVC)); + + EXPECT_EQ("DATA", Http2FrameTypeToString(0)); + EXPECT_EQ("HEADERS", Http2FrameTypeToString(1)); + EXPECT_EQ("PRIORITY", Http2FrameTypeToString(2)); + EXPECT_EQ("RST_STREAM", Http2FrameTypeToString(3)); + EXPECT_EQ("SETTINGS", Http2FrameTypeToString(4)); + EXPECT_EQ("PUSH_PROMISE", Http2FrameTypeToString(5)); + EXPECT_EQ("PING", Http2FrameTypeToString(6)); + EXPECT_EQ("GOAWAY", Http2FrameTypeToString(7)); + EXPECT_EQ("WINDOW_UPDATE", Http2FrameTypeToString(8)); + EXPECT_EQ("CONTINUATION", Http2FrameTypeToString(9)); + EXPECT_EQ("ALTSVC", Http2FrameTypeToString(10)); + + EXPECT_EQ("UnknownFrameType(99)", Http2FrameTypeToString(99)); +} + +TEST(Http2ConstantsTest, Http2FrameFlag) { + EXPECT_EQ(Http2FrameFlag::END_STREAM, static_cast<Http2FrameFlag>(0x01)); + EXPECT_EQ(Http2FrameFlag::ACK, static_cast<Http2FrameFlag>(0x01)); + EXPECT_EQ(Http2FrameFlag::END_HEADERS, static_cast<Http2FrameFlag>(0x04)); + EXPECT_EQ(Http2FrameFlag::PADDED, static_cast<Http2FrameFlag>(0x08)); + EXPECT_EQ(Http2FrameFlag::PRIORITY, static_cast<Http2FrameFlag>(0x20)); + + EXPECT_EQ(Http2FrameFlag::END_STREAM, 0x01); + EXPECT_EQ(Http2FrameFlag::ACK, 0x01); + EXPECT_EQ(Http2FrameFlag::END_HEADERS, 0x04); + EXPECT_EQ(Http2FrameFlag::PADDED, 0x08); + EXPECT_EQ(Http2FrameFlag::PRIORITY, 0x20); +} + +TEST(Http2ConstantsTest, Http2FrameFlagsToString) { + // Single flags... + + // 0b00000001 + EXPECT_EQ("END_STREAM", Http2FrameFlagsToString(Http2FrameType::DATA, + Http2FrameFlag::END_STREAM)); + EXPECT_EQ("END_STREAM", + Http2FrameFlagsToString(Http2FrameType::HEADERS, 0x01)); + EXPECT_EQ("ACK", Http2FrameFlagsToString(Http2FrameType::SETTINGS, + Http2FrameFlag::ACK)); + EXPECT_EQ("ACK", Http2FrameFlagsToString(Http2FrameType::PING, 0x01)); + + // 0b00000010 + EXPECT_EQ("0x02", Http2FrameFlagsToString(0xff, 0x02)); + + // 0b00000100 + EXPECT_EQ("END_HEADERS", + Http2FrameFlagsToString(Http2FrameType::HEADERS, + Http2FrameFlag::END_HEADERS)); + EXPECT_EQ("END_HEADERS", + Http2FrameFlagsToString(Http2FrameType::PUSH_PROMISE, 0x04)); + EXPECT_EQ("END_HEADERS", Http2FrameFlagsToString(0x09, 0x04)); + EXPECT_EQ("0x04", Http2FrameFlagsToString(0xff, 0x04)); + + // 0b00001000 + EXPECT_EQ("PADDED", Http2FrameFlagsToString(Http2FrameType::DATA, + Http2FrameFlag::PADDED)); + EXPECT_EQ("PADDED", Http2FrameFlagsToString(Http2FrameType::HEADERS, 0x08)); + EXPECT_EQ("PADDED", Http2FrameFlagsToString(0x05, 0x08)); + EXPECT_EQ("0x08", Http2FrameFlagsToString(0xff, Http2FrameFlag::PADDED)); + + // 0b00010000 + EXPECT_EQ("0x10", Http2FrameFlagsToString(Http2FrameType::SETTINGS, 0x10)); + + // 0b00100000 + EXPECT_EQ("PRIORITY", Http2FrameFlagsToString(Http2FrameType::HEADERS, 0x20)); + EXPECT_EQ("0x20", + Http2FrameFlagsToString(Http2FrameType::PUSH_PROMISE, 0x20)); + + // 0b01000000 + EXPECT_EQ("0x40", Http2FrameFlagsToString(0xff, 0x40)); + + // 0b10000000 + EXPECT_EQ("0x80", Http2FrameFlagsToString(0xff, 0x80)); + + // Combined flags... + + EXPECT_EQ("END_STREAM|PADDED|0xf6", + Http2FrameFlagsToString(Http2FrameType::DATA, 0xff)); + EXPECT_EQ("END_STREAM|END_HEADERS|PADDED|PRIORITY|0xd2", + Http2FrameFlagsToString(Http2FrameType::HEADERS, 0xff)); + EXPECT_EQ("0xff", Http2FrameFlagsToString(Http2FrameType::PRIORITY, 0xff)); + EXPECT_EQ("0xff", Http2FrameFlagsToString(Http2FrameType::RST_STREAM, 0xff)); + EXPECT_EQ("ACK|0xfe", + Http2FrameFlagsToString(Http2FrameType::SETTINGS, 0xff)); + EXPECT_EQ("END_HEADERS|PADDED|0xf3", + Http2FrameFlagsToString(Http2FrameType::PUSH_PROMISE, 0xff)); + EXPECT_EQ("ACK|0xfe", Http2FrameFlagsToString(Http2FrameType::PING, 0xff)); + EXPECT_EQ("0xff", Http2FrameFlagsToString(Http2FrameType::GOAWAY, 0xff)); + EXPECT_EQ("0xff", + Http2FrameFlagsToString(Http2FrameType::WINDOW_UPDATE, 0xff)); + EXPECT_EQ("END_HEADERS|0xfb", + Http2FrameFlagsToString(Http2FrameType::CONTINUATION, 0xff)); + EXPECT_EQ("0xff", Http2FrameFlagsToString(Http2FrameType::ALTSVC, 0xff)); + EXPECT_EQ("0xff", Http2FrameFlagsToString(0xff, 0xff)); +} + +TEST(Http2ConstantsTest, Http2ErrorCode) { + EXPECT_EQ(Http2ErrorCode::HTTP2_NO_ERROR, static_cast<Http2ErrorCode>(0x0)); + EXPECT_EQ(Http2ErrorCode::PROTOCOL_ERROR, static_cast<Http2ErrorCode>(0x1)); + EXPECT_EQ(Http2ErrorCode::INTERNAL_ERROR, static_cast<Http2ErrorCode>(0x2)); + EXPECT_EQ(Http2ErrorCode::FLOW_CONTROL_ERROR, + static_cast<Http2ErrorCode>(0x3)); + EXPECT_EQ(Http2ErrorCode::SETTINGS_TIMEOUT, static_cast<Http2ErrorCode>(0x4)); + EXPECT_EQ(Http2ErrorCode::STREAM_CLOSED, static_cast<Http2ErrorCode>(0x5)); + EXPECT_EQ(Http2ErrorCode::FRAME_SIZE_ERROR, static_cast<Http2ErrorCode>(0x6)); + EXPECT_EQ(Http2ErrorCode::REFUSED_STREAM, static_cast<Http2ErrorCode>(0x7)); + EXPECT_EQ(Http2ErrorCode::CANCEL, static_cast<Http2ErrorCode>(0x8)); + EXPECT_EQ(Http2ErrorCode::COMPRESSION_ERROR, + static_cast<Http2ErrorCode>(0x9)); + EXPECT_EQ(Http2ErrorCode::CONNECT_ERROR, static_cast<Http2ErrorCode>(0xa)); + EXPECT_EQ(Http2ErrorCode::ENHANCE_YOUR_CALM, + static_cast<Http2ErrorCode>(0xb)); + EXPECT_EQ(Http2ErrorCode::INADEQUATE_SECURITY, + static_cast<Http2ErrorCode>(0xc)); + EXPECT_EQ(Http2ErrorCode::HTTP_1_1_REQUIRED, + static_cast<Http2ErrorCode>(0xd)); +} + +TEST(Http2ConstantsTest, Http2ErrorCodeToString) { + EXPECT_EQ("NO_ERROR", Http2ErrorCodeToString(Http2ErrorCode::HTTP2_NO_ERROR)); + EXPECT_EQ("NO_ERROR", Http2ErrorCodeToString(0x0)); + EXPECT_EQ("PROTOCOL_ERROR", + Http2ErrorCodeToString(Http2ErrorCode::PROTOCOL_ERROR)); + EXPECT_EQ("PROTOCOL_ERROR", Http2ErrorCodeToString(0x1)); + EXPECT_EQ("INTERNAL_ERROR", + Http2ErrorCodeToString(Http2ErrorCode::INTERNAL_ERROR)); + EXPECT_EQ("INTERNAL_ERROR", Http2ErrorCodeToString(0x2)); + EXPECT_EQ("FLOW_CONTROL_ERROR", + Http2ErrorCodeToString(Http2ErrorCode::FLOW_CONTROL_ERROR)); + EXPECT_EQ("FLOW_CONTROL_ERROR", Http2ErrorCodeToString(0x3)); + EXPECT_EQ("SETTINGS_TIMEOUT", + Http2ErrorCodeToString(Http2ErrorCode::SETTINGS_TIMEOUT)); + EXPECT_EQ("SETTINGS_TIMEOUT", Http2ErrorCodeToString(0x4)); + EXPECT_EQ("STREAM_CLOSED", + Http2ErrorCodeToString(Http2ErrorCode::STREAM_CLOSED)); + EXPECT_EQ("STREAM_CLOSED", Http2ErrorCodeToString(0x5)); + EXPECT_EQ("FRAME_SIZE_ERROR", + Http2ErrorCodeToString(Http2ErrorCode::FRAME_SIZE_ERROR)); + EXPECT_EQ("FRAME_SIZE_ERROR", Http2ErrorCodeToString(0x6)); + EXPECT_EQ("REFUSED_STREAM", + Http2ErrorCodeToString(Http2ErrorCode::REFUSED_STREAM)); + EXPECT_EQ("REFUSED_STREAM", Http2ErrorCodeToString(0x7)); + EXPECT_EQ("CANCEL", Http2ErrorCodeToString(Http2ErrorCode::CANCEL)); + EXPECT_EQ("CANCEL", Http2ErrorCodeToString(0x8)); + EXPECT_EQ("COMPRESSION_ERROR", + Http2ErrorCodeToString(Http2ErrorCode::COMPRESSION_ERROR)); + EXPECT_EQ("COMPRESSION_ERROR", Http2ErrorCodeToString(0x9)); + EXPECT_EQ("CONNECT_ERROR", + Http2ErrorCodeToString(Http2ErrorCode::CONNECT_ERROR)); + EXPECT_EQ("CONNECT_ERROR", Http2ErrorCodeToString(0xa)); + EXPECT_EQ("ENHANCE_YOUR_CALM", + Http2ErrorCodeToString(Http2ErrorCode::ENHANCE_YOUR_CALM)); + EXPECT_EQ("ENHANCE_YOUR_CALM", Http2ErrorCodeToString(0xb)); + EXPECT_EQ("INADEQUATE_SECURITY", + Http2ErrorCodeToString(Http2ErrorCode::INADEQUATE_SECURITY)); + EXPECT_EQ("INADEQUATE_SECURITY", Http2ErrorCodeToString(0xc)); + EXPECT_EQ("HTTP_1_1_REQUIRED", + Http2ErrorCodeToString(Http2ErrorCode::HTTP_1_1_REQUIRED)); + EXPECT_EQ("HTTP_1_1_REQUIRED", Http2ErrorCodeToString(0xd)); + + EXPECT_EQ("UnknownErrorCode(0x123)", Http2ErrorCodeToString(0x123)); +} + +TEST(Http2ConstantsTest, Http2SettingsParameter) { + EXPECT_EQ(Http2SettingsParameter::HEADER_TABLE_SIZE, + static_cast<Http2SettingsParameter>(0x1)); + EXPECT_EQ(Http2SettingsParameter::ENABLE_PUSH, + static_cast<Http2SettingsParameter>(0x2)); + EXPECT_EQ(Http2SettingsParameter::MAX_CONCURRENT_STREAMS, + static_cast<Http2SettingsParameter>(0x3)); + EXPECT_EQ(Http2SettingsParameter::INITIAL_WINDOW_SIZE, + static_cast<Http2SettingsParameter>(0x4)); + EXPECT_EQ(Http2SettingsParameter::MAX_FRAME_SIZE, + static_cast<Http2SettingsParameter>(0x5)); + EXPECT_EQ(Http2SettingsParameter::MAX_HEADER_LIST_SIZE, + static_cast<Http2SettingsParameter>(0x6)); + + EXPECT_TRUE(IsSupportedHttp2SettingsParameter( + Http2SettingsParameter::HEADER_TABLE_SIZE)); + EXPECT_TRUE( + IsSupportedHttp2SettingsParameter(Http2SettingsParameter::ENABLE_PUSH)); + EXPECT_TRUE(IsSupportedHttp2SettingsParameter( + Http2SettingsParameter::MAX_CONCURRENT_STREAMS)); + EXPECT_TRUE(IsSupportedHttp2SettingsParameter( + Http2SettingsParameter::INITIAL_WINDOW_SIZE)); + EXPECT_TRUE(IsSupportedHttp2SettingsParameter( + Http2SettingsParameter::MAX_FRAME_SIZE)); + EXPECT_TRUE(IsSupportedHttp2SettingsParameter( + Http2SettingsParameter::MAX_HEADER_LIST_SIZE)); + + EXPECT_FALSE(IsSupportedHttp2SettingsParameter( + static_cast<Http2SettingsParameter>(0))); + EXPECT_FALSE(IsSupportedHttp2SettingsParameter( + static_cast<Http2SettingsParameter>(7))); +} + +TEST(Http2ConstantsTest, Http2SettingsParameterToString) { + EXPECT_EQ("HEADER_TABLE_SIZE", + Http2SettingsParameterToString( + Http2SettingsParameter::HEADER_TABLE_SIZE)); + EXPECT_EQ("HEADER_TABLE_SIZE", Http2SettingsParameterToString(0x1)); + EXPECT_EQ("ENABLE_PUSH", Http2SettingsParameterToString( + Http2SettingsParameter::ENABLE_PUSH)); + EXPECT_EQ("ENABLE_PUSH", Http2SettingsParameterToString(0x2)); + EXPECT_EQ("MAX_CONCURRENT_STREAMS", + Http2SettingsParameterToString( + Http2SettingsParameter::MAX_CONCURRENT_STREAMS)); + EXPECT_EQ("MAX_CONCURRENT_STREAMS", Http2SettingsParameterToString(0x3)); + EXPECT_EQ("INITIAL_WINDOW_SIZE", + Http2SettingsParameterToString( + Http2SettingsParameter::INITIAL_WINDOW_SIZE)); + EXPECT_EQ("INITIAL_WINDOW_SIZE", Http2SettingsParameterToString(0x4)); + EXPECT_EQ("MAX_FRAME_SIZE", Http2SettingsParameterToString( + Http2SettingsParameter::MAX_FRAME_SIZE)); + EXPECT_EQ("MAX_FRAME_SIZE", Http2SettingsParameterToString(0x5)); + EXPECT_EQ("MAX_HEADER_LIST_SIZE", + Http2SettingsParameterToString( + Http2SettingsParameter::MAX_HEADER_LIST_SIZE)); + EXPECT_EQ("MAX_HEADER_LIST_SIZE", Http2SettingsParameterToString(0x6)); + + EXPECT_EQ("UnknownSettingsParameter(0x123)", + Http2SettingsParameterToString(0x123)); +} + +} // namespace +} // namespace test +} // namespace http2 diff --git a/chromium/net/third_party/quiche/src/http2/http2_constants_test_util.cc b/chromium/net/third_party/quiche/src/http2/http2_constants_test_util.cc new file mode 100644 index 00000000000..e729890b5b0 --- /dev/null +++ b/chromium/net/third_party/quiche/src/http2/http2_constants_test_util.cc @@ -0,0 +1,84 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/http2/http2_constants_test_util.h" + +namespace http2 { +namespace test { + +std::vector<Http2ErrorCode> AllHttp2ErrorCodes() { + // clang-format off + return { + Http2ErrorCode::HTTP2_NO_ERROR, + Http2ErrorCode::PROTOCOL_ERROR, + Http2ErrorCode::INTERNAL_ERROR, + Http2ErrorCode::FLOW_CONTROL_ERROR, + Http2ErrorCode::SETTINGS_TIMEOUT, + Http2ErrorCode::STREAM_CLOSED, + Http2ErrorCode::FRAME_SIZE_ERROR, + Http2ErrorCode::REFUSED_STREAM, + Http2ErrorCode::CANCEL, + Http2ErrorCode::COMPRESSION_ERROR, + Http2ErrorCode::CONNECT_ERROR, + Http2ErrorCode::ENHANCE_YOUR_CALM, + Http2ErrorCode::INADEQUATE_SECURITY, + Http2ErrorCode::HTTP_1_1_REQUIRED, + }; + // clang-format on +} + +std::vector<Http2SettingsParameter> AllHttp2SettingsParameters() { + // clang-format off + return { + Http2SettingsParameter::HEADER_TABLE_SIZE, + Http2SettingsParameter::ENABLE_PUSH, + Http2SettingsParameter::MAX_CONCURRENT_STREAMS, + Http2SettingsParameter::INITIAL_WINDOW_SIZE, + Http2SettingsParameter::MAX_FRAME_SIZE, + Http2SettingsParameter::MAX_HEADER_LIST_SIZE, + }; + // clang-format on +} + +// Returns a mask of flags supported for the specified frame type. Returns +// zero for unknown frame types. +uint8_t KnownFlagsMaskForFrameType(Http2FrameType type) { + switch (type) { + case Http2FrameType::DATA: + return Http2FrameFlag::END_STREAM | Http2FrameFlag::PADDED; + case Http2FrameType::HEADERS: + return Http2FrameFlag::END_STREAM | Http2FrameFlag::END_HEADERS | + Http2FrameFlag::PADDED | Http2FrameFlag::PRIORITY; + case Http2FrameType::PRIORITY: + return 0x00; + case Http2FrameType::RST_STREAM: + return 0x00; + case Http2FrameType::SETTINGS: + return Http2FrameFlag::ACK; + case Http2FrameType::PUSH_PROMISE: + return Http2FrameFlag::END_HEADERS | Http2FrameFlag::PADDED; + case Http2FrameType::PING: + return Http2FrameFlag::ACK; + case Http2FrameType::GOAWAY: + return 0x00; + case Http2FrameType::WINDOW_UPDATE: + return 0x00; + case Http2FrameType::CONTINUATION: + return Http2FrameFlag::END_HEADERS; + case Http2FrameType::ALTSVC: + return 0x00; + default: + return 0x00; + } +} + +uint8_t InvalidFlagMaskForFrameType(Http2FrameType type) { + if (IsSupportedHttp2FrameType(type)) { + return ~KnownFlagsMaskForFrameType(type); + } + return 0x00; +} + +} // namespace test +} // namespace http2 diff --git a/chromium/net/third_party/quiche/src/http2/http2_constants_test_util.h b/chromium/net/third_party/quiche/src/http2/http2_constants_test_util.h new file mode 100644 index 00000000000..6ddc1cd4adf --- /dev/null +++ b/chromium/net/third_party/quiche/src/http2/http2_constants_test_util.h @@ -0,0 +1,34 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef QUICHE_HTTP2_HTTP2_CONSTANTS_TEST_UTIL_H_ +#define QUICHE_HTTP2_HTTP2_CONSTANTS_TEST_UTIL_H_ + +#include <cstdint> +#include <vector> + +#include "net/third_party/quiche/src/http2/http2_constants.h" + +namespace http2 { +namespace test { + +// Returns a vector of all supported RST_STREAM and GOAWAY error codes. +std::vector<Http2ErrorCode> AllHttp2ErrorCodes(); + +// Returns a vector of all supported parameters in SETTINGS frames. +std::vector<Http2SettingsParameter> AllHttp2SettingsParameters(); + +// Returns a mask of flags supported for the specified frame type. Returns +// zero for unknown frame types. +uint8_t KnownFlagsMaskForFrameType(Http2FrameType type); + +// Returns a mask of flag bits known to be invalid for the frame type. +// For unknown frame types, the mask is zero; i.e., we don't know that any +// are invalid. +uint8_t InvalidFlagMaskForFrameType(Http2FrameType type); + +} // namespace test +} // namespace http2 + +#endif // QUICHE_HTTP2_HTTP2_CONSTANTS_TEST_UTIL_H_ diff --git a/chromium/net/third_party/quiche/src/http2/http2_structures.cc b/chromium/net/third_party/quiche/src/http2/http2_structures.cc new file mode 100644 index 00000000000..7dbaf30aedc --- /dev/null +++ b/chromium/net/third_party/quiche/src/http2/http2_structures.cc @@ -0,0 +1,132 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/http2/http2_structures.h" + +#include <cstring> // For std::memcmp +#include <sstream> + +#include "net/third_party/quiche/src/http2/platform/api/http2_string_utils.h" + +namespace http2 { + +// Http2FrameHeader: + +bool Http2FrameHeader::IsProbableHttpResponse() const { + return (payload_length == 0x485454 && // "HTT" + static_cast<char>(type) == 'P' && // "P" + flags == '/'); // "/" +} + +Http2String Http2FrameHeader::ToString() const { + return Http2StrCat("length=", payload_length, + ", type=", Http2FrameTypeToString(type), + ", flags=", FlagsToString(), ", stream=", stream_id); +} + +Http2String Http2FrameHeader::FlagsToString() const { + return Http2FrameFlagsToString(type, flags); +} + +bool operator==(const Http2FrameHeader& a, const Http2FrameHeader& b) { + return a.payload_length == b.payload_length && a.stream_id == b.stream_id && + a.type == b.type && a.flags == b.flags; +} + +std::ostream& operator<<(std::ostream& out, const Http2FrameHeader& v) { + return out << v.ToString(); +} + +// Http2PriorityFields: + +bool operator==(const Http2PriorityFields& a, const Http2PriorityFields& b) { + return a.stream_dependency == b.stream_dependency && a.weight == b.weight; +} + +Http2String Http2PriorityFields::ToString() const { + std::stringstream ss; + ss << "E=" << (is_exclusive ? "true" : "false") + << ", stream=" << stream_dependency + << ", weight=" << static_cast<uint32_t>(weight); + return ss.str(); +} + +std::ostream& operator<<(std::ostream& out, const Http2PriorityFields& v) { + return out << v.ToString(); +} + +// Http2RstStreamFields: + +bool operator==(const Http2RstStreamFields& a, const Http2RstStreamFields& b) { + return a.error_code == b.error_code; +} + +std::ostream& operator<<(std::ostream& out, const Http2RstStreamFields& v) { + return out << "error_code=" << v.error_code; +} + +// Http2SettingFields: + +bool operator==(const Http2SettingFields& a, const Http2SettingFields& b) { + return a.parameter == b.parameter && a.value == b.value; +} +std::ostream& operator<<(std::ostream& out, const Http2SettingFields& v) { + return out << "parameter=" << v.parameter << ", value=" << v.value; +} + +// Http2PushPromiseFields: + +bool operator==(const Http2PushPromiseFields& a, + const Http2PushPromiseFields& b) { + return a.promised_stream_id == b.promised_stream_id; +} + +std::ostream& operator<<(std::ostream& out, const Http2PushPromiseFields& v) { + return out << "promised_stream_id=" << v.promised_stream_id; +} + +// Http2PingFields: + +bool operator==(const Http2PingFields& a, const Http2PingFields& b) { + static_assert((sizeof a.opaque_bytes) == Http2PingFields::EncodedSize(), + "Why not the same size?"); + return 0 == + std::memcmp(a.opaque_bytes, b.opaque_bytes, sizeof a.opaque_bytes); +} + +std::ostream& operator<<(std::ostream& out, const Http2PingFields& v) { + return out << "opaque_bytes=0x" + << Http2HexEncode(v.opaque_bytes, sizeof v.opaque_bytes); +} + +// Http2GoAwayFields: + +bool operator==(const Http2GoAwayFields& a, const Http2GoAwayFields& b) { + return a.last_stream_id == b.last_stream_id && a.error_code == b.error_code; +} +std::ostream& operator<<(std::ostream& out, const Http2GoAwayFields& v) { + return out << "last_stream_id=" << v.last_stream_id + << ", error_code=" << v.error_code; +} + +// Http2WindowUpdateFields: + +bool operator==(const Http2WindowUpdateFields& a, + const Http2WindowUpdateFields& b) { + return a.window_size_increment == b.window_size_increment; +} +std::ostream& operator<<(std::ostream& out, const Http2WindowUpdateFields& v) { + return out << "window_size_increment=" << v.window_size_increment; +} + +// Http2AltSvcFields: + +bool operator==(const Http2AltSvcFields& a, const Http2AltSvcFields& b) { + return a.origin_length == b.origin_length; +} +std::ostream& operator<<(std::ostream& out, const Http2AltSvcFields& v) { + return out << "origin_length=" << v.origin_length; +} + +} // namespace http2 diff --git a/chromium/net/third_party/quiche/src/http2/http2_structures.h b/chromium/net/third_party/quiche/src/http2/http2_structures.h new file mode 100644 index 00000000000..01c011e1f9c --- /dev/null +++ b/chromium/net/third_party/quiche/src/http2/http2_structures.h @@ -0,0 +1,325 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef QUICHE_HTTP2_HTTP2_STRUCTURES_H_ +#define QUICHE_HTTP2_HTTP2_STRUCTURES_H_ + +// Defines structs for various fixed sized structures in HTTP/2. +// +// Those structs with multiple fields have constructors that take arguments in +// the same order as their encoding (which may be different from their order +// in the struct). For single field structs, use aggregate initialization if +// desired, e.g.: +// +// Http2RstStreamFields var{Http2ErrorCode::ENHANCE_YOUR_CALM}; +// or: +// SomeFunc(Http2RstStreamFields{Http2ErrorCode::ENHANCE_YOUR_CALM}); +// +// Each struct includes a static method EncodedSize which returns the number +// of bytes of the encoding. +// +// With the exception of Http2FrameHeader, all the types are named +// Http2<X>Fields, where X is the title-case form of the frame which always +// includes the fields; the "always" is to cover the case of the PRIORITY frame; +// its fields optionally appear in the HEADERS frame, but the struct is called +// Http2PriorityFields. + +#include <stddef.h> + +#include <cstdint> +#include <ostream> + +#include "base/logging.h" +#include "net/third_party/quiche/src/http2/http2_constants.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_export.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string.h" + +namespace http2 { + +struct HTTP2_EXPORT_PRIVATE Http2FrameHeader { + Http2FrameHeader() {} + Http2FrameHeader(uint32_t payload_length, + Http2FrameType type, + uint8_t flags, + uint32_t stream_id) + : payload_length(payload_length), + stream_id(stream_id), + type(type), + flags(static_cast<Http2FrameFlag>(flags)) { + DCHECK_LT(payload_length, static_cast<uint32_t>(1 << 24)) + << "Payload Length is only a 24 bit field\n" + << ToString(); + } + + static constexpr size_t EncodedSize() { return 9; } + + // Keep the current value of those flags that are in + // valid_flags, and clear all the others. + void RetainFlags(uint8_t valid_flags) { + flags = static_cast<Http2FrameFlag>(flags & valid_flags); + } + + // Returns true if any of the flags in flag_mask are set, + // otherwise false. + bool HasAnyFlags(uint8_t flag_mask) const { return 0 != (flags & flag_mask); } + + // Is the END_STREAM flag set? + bool IsEndStream() const { + DCHECK(type == Http2FrameType::DATA || type == Http2FrameType::HEADERS) + << ToString(); + return (flags & Http2FrameFlag::END_STREAM) != 0; + } + + // Is the ACK flag set? + bool IsAck() const { + DCHECK(type == Http2FrameType::SETTINGS || type == Http2FrameType::PING) + << ToString(); + return (flags & Http2FrameFlag::ACK) != 0; + } + + // Is the END_HEADERS flag set? + bool IsEndHeaders() const { + DCHECK(type == Http2FrameType::HEADERS || + type == Http2FrameType::PUSH_PROMISE || + type == Http2FrameType::CONTINUATION) + << ToString(); + return (flags & Http2FrameFlag::END_HEADERS) != 0; + } + + // Is the PADDED flag set? + bool IsPadded() const { + DCHECK(type == Http2FrameType::DATA || type == Http2FrameType::HEADERS || + type == Http2FrameType::PUSH_PROMISE) + << ToString(); + return (flags & Http2FrameFlag::PADDED) != 0; + } + + // Is the PRIORITY flag set? + bool HasPriority() const { + DCHECK_EQ(type, Http2FrameType::HEADERS) << ToString(); + return (flags & Http2FrameFlag::PRIORITY) != 0; + } + + // Does the encoding of this header start with "HTTP/", indicating that it + // might be from a non-HTTP/2 server. + bool IsProbableHttpResponse() const; + + // Produce strings useful for debugging/logging messages. + Http2String ToString() const; + Http2String FlagsToString() const; + + // 24 bit length of the payload after the header, including any padding. + // First field in encoding. + uint32_t payload_length; // 24 bits + + // 31 bit stream id, with high bit (32nd bit) reserved (must be zero), + // and is cleared during decoding. + // Fourth field in encoding. + uint32_t stream_id; + + // Type of the frame. + // Second field in encoding. + Http2FrameType type; + + // Flag bits, with interpretations that depend upon the frame type. + // Flag bits not used by the frame type are cleared. + // Third field in encoding. + Http2FrameFlag flags; +}; + +HTTP2_EXPORT_PRIVATE bool operator==(const Http2FrameHeader& a, + const Http2FrameHeader& b); +HTTP2_EXPORT_PRIVATE inline bool operator!=(const Http2FrameHeader& a, + const Http2FrameHeader& b) { + return !(a == b); +} +HTTP2_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& out, + const Http2FrameHeader& v); + +// Http2PriorityFields: + +struct HTTP2_EXPORT_PRIVATE Http2PriorityFields { + Http2PriorityFields() {} + Http2PriorityFields(uint32_t stream_dependency, + uint32_t weight, + bool is_exclusive) + : stream_dependency(stream_dependency), + weight(weight), + is_exclusive(is_exclusive) { + // Can't have the high-bit set in the stream id because we need to use + // that for the EXCLUSIVE flag bit. + DCHECK_EQ(stream_dependency, stream_dependency & StreamIdMask()) + << "Stream Dependency is only a 31-bit field.\n" + << ToString(); + DCHECK_LE(1u, weight) << "Weight is too small."; + DCHECK_LE(weight, 256u) << "Weight is too large."; + } + static constexpr size_t EncodedSize() { return 5; } + + // Produce strings useful for debugging/logging messages. + Http2String ToString() const; + + // A 31-bit stream identifier for the stream that this stream depends on. + uint32_t stream_dependency; + + // Weight (1 to 256) is encoded as a byte in the range 0 to 255, so we + // add one when decoding, and store it in a field larger than a byte. + uint32_t weight; + + // A single-bit flag indicating that the stream dependency is exclusive; + // extracted from high bit of stream dependency field during decoding. + bool is_exclusive; +}; + +HTTP2_EXPORT_PRIVATE bool operator==(const Http2PriorityFields& a, + const Http2PriorityFields& b); +HTTP2_EXPORT_PRIVATE inline bool operator!=(const Http2PriorityFields& a, + const Http2PriorityFields& b) { + return !(a == b); +} +HTTP2_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& out, + const Http2PriorityFields& v); + +// Http2RstStreamFields: + +struct Http2RstStreamFields { + static constexpr size_t EncodedSize() { return 4; } + bool IsSupportedErrorCode() const { + return IsSupportedHttp2ErrorCode(error_code); + } + + Http2ErrorCode error_code; +}; + +HTTP2_EXPORT_PRIVATE bool operator==(const Http2RstStreamFields& a, + const Http2RstStreamFields& b); +HTTP2_EXPORT_PRIVATE inline bool operator!=(const Http2RstStreamFields& a, + const Http2RstStreamFields& b) { + return !(a == b); +} +HTTP2_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& out, + const Http2RstStreamFields& v); + +// Http2SettingFields: + +struct Http2SettingFields { + Http2SettingFields() {} + Http2SettingFields(Http2SettingsParameter parameter, uint32_t value) + : parameter(parameter), value(value) {} + static constexpr size_t EncodedSize() { return 6; } + bool IsSupportedParameter() const { + return IsSupportedHttp2SettingsParameter(parameter); + } + + Http2SettingsParameter parameter; + uint32_t value; +}; + +HTTP2_EXPORT_PRIVATE bool operator==(const Http2SettingFields& a, + const Http2SettingFields& b); +HTTP2_EXPORT_PRIVATE inline bool operator!=(const Http2SettingFields& a, + const Http2SettingFields& b) { + return !(a == b); +} +HTTP2_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& out, + const Http2SettingFields& v); + +// Http2PushPromiseFields: + +struct Http2PushPromiseFields { + static constexpr size_t EncodedSize() { return 4; } + + uint32_t promised_stream_id; +}; + +HTTP2_EXPORT_PRIVATE bool operator==(const Http2PushPromiseFields& a, + const Http2PushPromiseFields& b); +HTTP2_EXPORT_PRIVATE inline bool operator!=(const Http2PushPromiseFields& a, + const Http2PushPromiseFields& b) { + return !(a == b); +} +HTTP2_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& out, + const Http2PushPromiseFields& v); + +// Http2PingFields: + +struct Http2PingFields { + static constexpr size_t EncodedSize() { return 8; } + + uint8_t opaque_bytes[8]; +}; + +HTTP2_EXPORT_PRIVATE bool operator==(const Http2PingFields& a, + const Http2PingFields& b); +HTTP2_EXPORT_PRIVATE inline bool operator!=(const Http2PingFields& a, + const Http2PingFields& b) { + return !(a == b); +} +HTTP2_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& out, + const Http2PingFields& v); + +// Http2GoAwayFields: + +struct Http2GoAwayFields { + Http2GoAwayFields() {} + Http2GoAwayFields(uint32_t last_stream_id, Http2ErrorCode error_code) + : last_stream_id(last_stream_id), error_code(error_code) {} + static constexpr size_t EncodedSize() { return 8; } + bool IsSupportedErrorCode() const { + return IsSupportedHttp2ErrorCode(error_code); + } + + uint32_t last_stream_id; + Http2ErrorCode error_code; +}; + +HTTP2_EXPORT_PRIVATE bool operator==(const Http2GoAwayFields& a, + const Http2GoAwayFields& b); +HTTP2_EXPORT_PRIVATE inline bool operator!=(const Http2GoAwayFields& a, + const Http2GoAwayFields& b) { + return !(a == b); +} +HTTP2_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& out, + const Http2GoAwayFields& v); + +// Http2WindowUpdateFields: + +struct Http2WindowUpdateFields { + static constexpr size_t EncodedSize() { return 4; } + + // 31-bit, unsigned increase in the window size (only positive values are + // allowed). The high-bit is reserved for the future. + uint32_t window_size_increment; +}; + +HTTP2_EXPORT_PRIVATE bool operator==(const Http2WindowUpdateFields& a, + const Http2WindowUpdateFields& b); +HTTP2_EXPORT_PRIVATE inline bool operator!=(const Http2WindowUpdateFields& a, + const Http2WindowUpdateFields& b) { + return !(a == b); +} +HTTP2_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& out, + const Http2WindowUpdateFields& v); + +// Http2AltSvcFields: + +struct Http2AltSvcFields { + static constexpr size_t EncodedSize() { return 2; } + + // This is the one fixed size portion of the ALTSVC payload. + uint16_t origin_length; +}; + +HTTP2_EXPORT_PRIVATE bool operator==(const Http2AltSvcFields& a, + const Http2AltSvcFields& b); +HTTP2_EXPORT_PRIVATE inline bool operator!=(const Http2AltSvcFields& a, + const Http2AltSvcFields& b) { + return !(a == b); +} +HTTP2_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& out, + const Http2AltSvcFields& v); + +} // namespace http2 + +#endif // QUICHE_HTTP2_HTTP2_STRUCTURES_H_ diff --git a/chromium/net/third_party/quiche/src/http2/http2_structures_test.cc b/chromium/net/third_party/quiche/src/http2/http2_structures_test.cc new file mode 100644 index 00000000000..30ebb3660ea --- /dev/null +++ b/chromium/net/third_party/quiche/src/http2/http2_structures_test.cc @@ -0,0 +1,537 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/http2/http2_structures.h" + +// Tests are focused on Http2FrameHeader because it has by far the most +// methods of any of the structures. +// Note that EXPECT.*DEATH tests are slow (a fork is probably involved). + +// And in case you're wondering, yes, these are ridiculously thorough tests, +// but believe it or not, I've found stupid bugs this way. + +#include <memory> +#include <ostream> +#include <sstream> +#include <tuple> +#include <type_traits> +#include <vector> + +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "net/third_party/quiche/src/http2/http2_structures_test_util.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string_utils.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_test_helpers.h" +#include "net/third_party/quiche/src/http2/test_tools/http2_random.h" + +using ::testing::AssertionResult; +using ::testing::AssertionSuccess; +using ::testing::Combine; +using ::testing::HasSubstr; +using ::testing::MatchesRegex; +using ::testing::Not; +using ::testing::Values; +using ::testing::ValuesIn; + +namespace http2 { +namespace test { +namespace { + +template <typename E> +E IncrementEnum(E e) { + using I = typename std::underlying_type<E>::type; + return static_cast<E>(1 + static_cast<I>(e)); +} + +template <class T> +AssertionResult VerifyRandomCalls() { + T t1; + Http2Random seq1; + Randomize(&t1, &seq1); + + T t2; + Http2Random seq2(seq1.Key()); + Randomize(&t2, &seq2); + + // The two Randomize calls should have made the same number of calls into + // the Http2Random implementations. + VERIFY_EQ(seq1.Rand64(), seq2.Rand64()); + + // And because Http2Random implementation is returning the same sequence, and + // Randomize should have been consistent in applying those results, the two + // Ts should have the same value. + VERIFY_EQ(t1, t2); + + Randomize(&t2, &seq2); + VERIFY_NE(t1, t2); + + Randomize(&t1, &seq1); + VERIFY_EQ(t1, t2); + + VERIFY_EQ(seq1.Rand64(), seq2.Rand64()); + + return AssertionSuccess(); +} + +#if GTEST_HAS_DEATH_TEST && !defined(NDEBUG) +std::vector<Http2FrameType> ValidFrameTypes() { + std::vector<Http2FrameType> valid_types{Http2FrameType::DATA}; + while (valid_types.back() != Http2FrameType::ALTSVC) { + valid_types.push_back(IncrementEnum(valid_types.back())); + } + return valid_types; +} +#endif // GTEST_HAS_DEATH_TEST && !defined(NDEBUG) + +TEST(Http2FrameHeaderTest, Constructor) { + Http2Random random; + uint8_t frame_type = 0; + do { + // Only the payload length is DCHECK'd in the constructor, so we need to + // make sure it is a "uint24". + uint32_t payload_length = random.Rand32() & 0xffffff; + Http2FrameType type = static_cast<Http2FrameType>(frame_type); + uint8_t flags = random.Rand8(); + uint32_t stream_id = random.Rand32(); + + Http2FrameHeader v(payload_length, type, flags, stream_id); + + EXPECT_EQ(payload_length, v.payload_length); + EXPECT_EQ(type, v.type); + EXPECT_EQ(flags, v.flags); + EXPECT_EQ(stream_id, v.stream_id); + } while (frame_type++ == 255); + +#if GTEST_HAS_DEATH_TEST && !defined(NDEBUG) + EXPECT_DEBUG_DEATH(Http2FrameHeader(0x01000000, Http2FrameType::DATA, 0, 1), + "payload_length"); +#endif // GTEST_HAS_DEATH_TEST && !defined(NDEBUG) +} + +TEST(Http2FrameHeaderTest, Eq) { + Http2Random random; + uint32_t payload_length = random.Rand32() & 0xffffff; + Http2FrameType type = static_cast<Http2FrameType>(random.Rand8()); + + uint8_t flags = random.Rand8(); + uint32_t stream_id = random.Rand32(); + + Http2FrameHeader v(payload_length, type, flags, stream_id); + + EXPECT_EQ(payload_length, v.payload_length); + EXPECT_EQ(type, v.type); + EXPECT_EQ(flags, v.flags); + EXPECT_EQ(stream_id, v.stream_id); + + Http2FrameHeader u(0, type, ~flags, stream_id); + + EXPECT_NE(u, v); + EXPECT_NE(v, u); + EXPECT_FALSE(u == v); + EXPECT_FALSE(v == u); + EXPECT_TRUE(u != v); + EXPECT_TRUE(v != u); + + u = v; + + EXPECT_EQ(u, v); + EXPECT_EQ(v, u); + EXPECT_TRUE(u == v); + EXPECT_TRUE(v == u); + EXPECT_FALSE(u != v); + EXPECT_FALSE(v != u); + + EXPECT_TRUE(VerifyRandomCalls<Http2FrameHeader>()); +} + +#if GTEST_HAS_DEATH_TEST && !defined(NDEBUG) +// The tests of the valid frame types include EXPECT_DEBUG_DEATH, which is +// quite slow, so using value parameterized tests in order to allow sharding. +class Http2FrameHeaderTypeAndFlagTest + : public ::testing::TestWithParam< + std::tuple<Http2FrameType, Http2FrameFlag>> { + protected: + Http2FrameHeaderTypeAndFlagTest() + : type_(std::get<0>(GetParam())), flags_(std::get<1>(GetParam())) { + LOG(INFO) << "Frame type: " << type_; + LOG(INFO) << "Frame flags: " << Http2FrameFlagsToString(type_, flags_); + } + + const Http2FrameType type_; + const Http2FrameFlag flags_; +}; + +class IsEndStreamTest : public Http2FrameHeaderTypeAndFlagTest {}; +INSTANTIATE_TEST_CASE_P(IsEndStream, + IsEndStreamTest, + Combine(ValuesIn(ValidFrameTypes()), + Values(~Http2FrameFlag::END_STREAM, 0xff))); +TEST_P(IsEndStreamTest, IsEndStream) { + const bool is_set = + (flags_ & Http2FrameFlag::END_STREAM) == Http2FrameFlag::END_STREAM; + Http2String flags_string; + Http2FrameHeader v(0, type_, flags_, 0); + switch (type_) { + case Http2FrameType::DATA: + case Http2FrameType::HEADERS: + EXPECT_EQ(is_set, v.IsEndStream()) << v; + flags_string = v.FlagsToString(); + if (is_set) { + EXPECT_THAT(flags_string, MatchesRegex(".*\\|?END_STREAM\\|.*")); + } else { + EXPECT_THAT(flags_string, Not(HasSubstr("END_STREAM"))); + } + v.RetainFlags(Http2FrameFlag::END_STREAM); + EXPECT_EQ(is_set, v.IsEndStream()) << v; + { + std::stringstream s; + s << v; + EXPECT_EQ(v.ToString(), s.str()); + if (is_set) { + EXPECT_THAT(s.str(), HasSubstr("flags=END_STREAM,")); + } else { + EXPECT_THAT(s.str(), HasSubstr("flags=,")); + } + } + break; + default: + EXPECT_DEBUG_DEATH(v.IsEndStream(), "DATA.*HEADERS") << v; + } +} + +class IsACKTest : public Http2FrameHeaderTypeAndFlagTest {}; +INSTANTIATE_TEST_CASE_P(IsAck, + IsACKTest, + Combine(ValuesIn(ValidFrameTypes()), + Values(~Http2FrameFlag::ACK, 0xff))); +TEST_P(IsACKTest, IsAck) { + const bool is_set = (flags_ & Http2FrameFlag::ACK) == Http2FrameFlag::ACK; + Http2String flags_string; + Http2FrameHeader v(0, type_, flags_, 0); + switch (type_) { + case Http2FrameType::SETTINGS: + case Http2FrameType::PING: + EXPECT_EQ(is_set, v.IsAck()) << v; + flags_string = v.FlagsToString(); + if (is_set) { + EXPECT_THAT(flags_string, MatchesRegex(".*\\|?ACK\\|.*")); + } else { + EXPECT_THAT(flags_string, Not(HasSubstr("ACK"))); + } + v.RetainFlags(Http2FrameFlag::ACK); + EXPECT_EQ(is_set, v.IsAck()) << v; + { + std::stringstream s; + s << v; + EXPECT_EQ(v.ToString(), s.str()); + if (is_set) { + EXPECT_THAT(s.str(), HasSubstr("flags=ACK,")); + } else { + EXPECT_THAT(s.str(), HasSubstr("flags=,")); + } + } + break; + default: + EXPECT_DEBUG_DEATH(v.IsAck(), "SETTINGS.*PING") << v; + } +} + +class IsEndHeadersTest : public Http2FrameHeaderTypeAndFlagTest {}; +INSTANTIATE_TEST_CASE_P(IsEndHeaders, + IsEndHeadersTest, + Combine(ValuesIn(ValidFrameTypes()), + Values(~Http2FrameFlag::END_HEADERS, 0xff))); +TEST_P(IsEndHeadersTest, IsEndHeaders) { + const bool is_set = + (flags_ & Http2FrameFlag::END_HEADERS) == Http2FrameFlag::END_HEADERS; + Http2String flags_string; + Http2FrameHeader v(0, type_, flags_, 0); + switch (type_) { + case Http2FrameType::HEADERS: + case Http2FrameType::PUSH_PROMISE: + case Http2FrameType::CONTINUATION: + EXPECT_EQ(is_set, v.IsEndHeaders()) << v; + flags_string = v.FlagsToString(); + if (is_set) { + EXPECT_THAT(flags_string, MatchesRegex(".*\\|?END_HEADERS\\|.*")); + } else { + EXPECT_THAT(flags_string, Not(HasSubstr("END_HEADERS"))); + } + v.RetainFlags(Http2FrameFlag::END_HEADERS); + EXPECT_EQ(is_set, v.IsEndHeaders()) << v; + { + std::stringstream s; + s << v; + EXPECT_EQ(v.ToString(), s.str()); + if (is_set) { + EXPECT_THAT(s.str(), HasSubstr("flags=END_HEADERS,")); + } else { + EXPECT_THAT(s.str(), HasSubstr("flags=,")); + } + } + break; + default: + EXPECT_DEBUG_DEATH(v.IsEndHeaders(), + "HEADERS.*PUSH_PROMISE.*CONTINUATION") + << v; + } +} + +class IsPaddedTest : public Http2FrameHeaderTypeAndFlagTest {}; +INSTANTIATE_TEST_CASE_P(IsPadded, + IsPaddedTest, + Combine(ValuesIn(ValidFrameTypes()), + Values(~Http2FrameFlag::PADDED, 0xff))); +TEST_P(IsPaddedTest, IsPadded) { + const bool is_set = + (flags_ & Http2FrameFlag::PADDED) == Http2FrameFlag::PADDED; + Http2String flags_string; + Http2FrameHeader v(0, type_, flags_, 0); + switch (type_) { + case Http2FrameType::DATA: + case Http2FrameType::HEADERS: + case Http2FrameType::PUSH_PROMISE: + EXPECT_EQ(is_set, v.IsPadded()) << v; + flags_string = v.FlagsToString(); + if (is_set) { + EXPECT_THAT(flags_string, MatchesRegex(".*\\|?PADDED\\|.*")); + } else { + EXPECT_THAT(flags_string, Not(HasSubstr("PADDED"))); + } + v.RetainFlags(Http2FrameFlag::PADDED); + EXPECT_EQ(is_set, v.IsPadded()) << v; + { + std::stringstream s; + s << v; + EXPECT_EQ(v.ToString(), s.str()); + if (is_set) { + EXPECT_THAT(s.str(), HasSubstr("flags=PADDED,")); + } else { + EXPECT_THAT(s.str(), HasSubstr("flags=,")); + } + } + break; + default: + EXPECT_DEBUG_DEATH(v.IsPadded(), "DATA.*HEADERS.*PUSH_PROMISE") << v; + } +} + +class HasPriorityTest : public Http2FrameHeaderTypeAndFlagTest {}; +INSTANTIATE_TEST_CASE_P(HasPriority, + HasPriorityTest, + Combine(ValuesIn(ValidFrameTypes()), + Values(~Http2FrameFlag::PRIORITY, 0xff))); +TEST_P(HasPriorityTest, HasPriority) { + const bool is_set = + (flags_ & Http2FrameFlag::PRIORITY) == Http2FrameFlag::PRIORITY; + Http2String flags_string; + Http2FrameHeader v(0, type_, flags_, 0); + switch (type_) { + case Http2FrameType::HEADERS: + EXPECT_EQ(is_set, v.HasPriority()) << v; + flags_string = v.FlagsToString(); + if (is_set) { + EXPECT_THAT(flags_string, MatchesRegex(".*\\|?PRIORITY\\|.*")); + } else { + EXPECT_THAT(flags_string, Not(HasSubstr("PRIORITY"))); + } + v.RetainFlags(Http2FrameFlag::PRIORITY); + EXPECT_EQ(is_set, v.HasPriority()) << v; + { + std::stringstream s; + s << v; + EXPECT_EQ(v.ToString(), s.str()); + if (is_set) { + EXPECT_THAT(s.str(), HasSubstr("flags=PRIORITY,")); + } else { + EXPECT_THAT(s.str(), HasSubstr("flags=,")); + } + } + break; + default: + EXPECT_DEBUG_DEATH(v.HasPriority(), "HEADERS") << v; + } +} + +TEST(Http2PriorityFieldsTest, Constructor) { + Http2Random random; + uint32_t stream_dependency = random.Rand32() & StreamIdMask(); + uint32_t weight = 1 + random.Rand8(); + bool is_exclusive = random.OneIn(2); + + Http2PriorityFields v(stream_dependency, weight, is_exclusive); + + EXPECT_EQ(stream_dependency, v.stream_dependency); + EXPECT_EQ(weight, v.weight); + EXPECT_EQ(is_exclusive, v.is_exclusive); + + // The high-bit must not be set on the stream id. + EXPECT_DEBUG_DEATH( + Http2PriorityFields(stream_dependency | 0x80000000, weight, is_exclusive), + "31-bit"); + + // The weight must be in the range 1-256. + EXPECT_DEBUG_DEATH(Http2PriorityFields(stream_dependency, 0, is_exclusive), + "too small"); + EXPECT_DEBUG_DEATH( + Http2PriorityFields(stream_dependency, weight + 256, is_exclusive), + "too large"); + + EXPECT_TRUE(VerifyRandomCalls<Http2PriorityFields>()); +} +#endif // GTEST_HAS_DEATH_TEST && !defined(NDEBUG) + +TEST(Http2RstStreamFieldsTest, IsSupported) { + Http2RstStreamFields v{Http2ErrorCode::HTTP2_NO_ERROR}; + EXPECT_TRUE(v.IsSupportedErrorCode()) << v; + + Http2RstStreamFields u{static_cast<Http2ErrorCode>(~0)}; + EXPECT_FALSE(u.IsSupportedErrorCode()) << v; + + EXPECT_TRUE(VerifyRandomCalls<Http2RstStreamFields>()); +} + +TEST(Http2SettingFieldsTest, Misc) { + Http2Random random; + Http2SettingsParameter parameter = + static_cast<Http2SettingsParameter>(random.Rand16()); + uint32_t value = random.Rand32(); + + Http2SettingFields v(parameter, value); + + EXPECT_EQ(v, v); + EXPECT_EQ(parameter, v.parameter); + EXPECT_EQ(value, v.value); + + if (static_cast<uint16_t>(parameter) < 7) { + EXPECT_TRUE(v.IsSupportedParameter()) << v; + } else { + EXPECT_FALSE(v.IsSupportedParameter()) << v; + } + + Http2SettingFields u(parameter, ~value); + EXPECT_NE(v, u); + EXPECT_EQ(v.parameter, u.parameter); + EXPECT_NE(v.value, u.value); + + Http2SettingFields w(IncrementEnum(parameter), value); + EXPECT_NE(v, w); + EXPECT_NE(v.parameter, w.parameter); + EXPECT_EQ(v.value, w.value); + + Http2SettingFields x(Http2SettingsParameter::MAX_FRAME_SIZE, 123); + std::stringstream s; + s << x; + EXPECT_EQ("parameter=MAX_FRAME_SIZE, value=123", s.str()); + + EXPECT_TRUE(VerifyRandomCalls<Http2SettingFields>()); +} + +TEST(Http2PushPromiseTest, Misc) { + Http2Random random; + uint32_t promised_stream_id = random.Rand32() & StreamIdMask(); + + Http2PushPromiseFields v{promised_stream_id}; + EXPECT_EQ(promised_stream_id, v.promised_stream_id); + EXPECT_EQ(v, v); + + std::stringstream s; + s << v; + EXPECT_EQ(Http2StrCat("promised_stream_id=", promised_stream_id), s.str()); + + // High-bit is reserved, but not used, so we can set it. + promised_stream_id |= 0x80000000; + Http2PushPromiseFields w{promised_stream_id}; + EXPECT_EQ(w, w); + EXPECT_NE(v, w); + + v.promised_stream_id = promised_stream_id; + EXPECT_EQ(v, w); + + EXPECT_TRUE(VerifyRandomCalls<Http2PushPromiseFields>()); +} + +TEST(Http2PingFieldsTest, Misc) { + Http2PingFields v{{'8', ' ', 'b', 'y', 't', 'e', 's', '\0'}}; + std::stringstream s; + s << v; + EXPECT_EQ("opaque_bytes=0x3820627974657300", s.str()); + + EXPECT_TRUE(VerifyRandomCalls<Http2PingFields>()); +} + +TEST(Http2GoAwayFieldsTest, Misc) { + Http2Random random; + uint32_t last_stream_id = random.Rand32() & StreamIdMask(); + Http2ErrorCode error_code = static_cast<Http2ErrorCode>(random.Rand32()); + + Http2GoAwayFields v(last_stream_id, error_code); + EXPECT_EQ(v, v); + EXPECT_EQ(last_stream_id, v.last_stream_id); + EXPECT_EQ(error_code, v.error_code); + + if (static_cast<uint32_t>(error_code) < 14) { + EXPECT_TRUE(v.IsSupportedErrorCode()) << v; + } else { + EXPECT_FALSE(v.IsSupportedErrorCode()) << v; + } + + Http2GoAwayFields u(~last_stream_id, error_code); + EXPECT_NE(v, u); + EXPECT_NE(v.last_stream_id, u.last_stream_id); + EXPECT_EQ(v.error_code, u.error_code); + + EXPECT_TRUE(VerifyRandomCalls<Http2GoAwayFields>()); +} + +TEST(Http2WindowUpdateTest, Misc) { + Http2Random random; + uint32_t window_size_increment = random.Rand32() & UInt31Mask(); + + Http2WindowUpdateFields v{window_size_increment}; + EXPECT_EQ(window_size_increment, v.window_size_increment); + EXPECT_EQ(v, v); + + std::stringstream s; + s << v; + EXPECT_EQ(Http2StrCat("window_size_increment=", window_size_increment), + s.str()); + + // High-bit is reserved, but not used, so we can set it. + window_size_increment |= 0x80000000; + Http2WindowUpdateFields w{window_size_increment}; + EXPECT_EQ(w, w); + EXPECT_NE(v, w); + + v.window_size_increment = window_size_increment; + EXPECT_EQ(v, w); + + EXPECT_TRUE(VerifyRandomCalls<Http2WindowUpdateFields>()); +} + +TEST(Http2AltSvcTest, Misc) { + Http2Random random; + uint16_t origin_length = random.Rand16(); + + Http2AltSvcFields v{origin_length}; + EXPECT_EQ(origin_length, v.origin_length); + EXPECT_EQ(v, v); + + std::stringstream s; + s << v; + EXPECT_EQ(Http2StrCat("origin_length=", origin_length), s.str()); + + Http2AltSvcFields w{++origin_length}; + EXPECT_EQ(w, w); + EXPECT_NE(v, w); + + v.origin_length = w.origin_length; + EXPECT_EQ(v, w); + + EXPECT_TRUE(VerifyRandomCalls<Http2AltSvcFields>()); +} + +} // namespace +} // namespace test +} // namespace http2 diff --git a/chromium/net/third_party/quiche/src/http2/http2_structures_test_util.cc b/chromium/net/third_party/quiche/src/http2/http2_structures_test_util.cc new file mode 100644 index 00000000000..4a1a74d8ee9 --- /dev/null +++ b/chromium/net/third_party/quiche/src/http2/http2_structures_test_util.cc @@ -0,0 +1,109 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/http2/http2_structures_test_util.h" + +#include <cstdint> + +#include "net/third_party/quiche/src/http2/http2_constants.h" +#include "net/third_party/quiche/src/http2/http2_constants_test_util.h" +#include "net/third_party/quiche/src/http2/http2_structures.h" +#include "net/third_party/quiche/src/http2/test_tools/http2_random.h" + +namespace http2 { +namespace test { + +void Randomize(Http2FrameHeader* p, Http2Random* rng) { + p->payload_length = rng->Rand32() & 0xffffff; + p->type = static_cast<Http2FrameType>(rng->Rand8()); + p->flags = static_cast<Http2FrameFlag>(rng->Rand8()); + p->stream_id = rng->Rand32() & StreamIdMask(); +} +void Randomize(Http2PriorityFields* p, Http2Random* rng) { + p->stream_dependency = rng->Rand32() & StreamIdMask(); + p->weight = rng->Rand8() + 1; + p->is_exclusive = rng->OneIn(2); +} +void Randomize(Http2RstStreamFields* p, Http2Random* rng) { + p->error_code = static_cast<Http2ErrorCode>(rng->Rand32()); +} +void Randomize(Http2SettingFields* p, Http2Random* rng) { + p->parameter = static_cast<Http2SettingsParameter>(rng->Rand16()); + p->value = rng->Rand32(); +} +void Randomize(Http2PushPromiseFields* p, Http2Random* rng) { + p->promised_stream_id = rng->Rand32() & StreamIdMask(); +} +void Randomize(Http2PingFields* p, Http2Random* rng) { + for (int ndx = 0; ndx < 8; ++ndx) { + p->opaque_bytes[ndx] = rng->Rand8(); + } +} +void Randomize(Http2GoAwayFields* p, Http2Random* rng) { + p->last_stream_id = rng->Rand32() & StreamIdMask(); + p->error_code = static_cast<Http2ErrorCode>(rng->Rand32()); +} +void Randomize(Http2WindowUpdateFields* p, Http2Random* rng) { + p->window_size_increment = rng->Rand32() & 0x7fffffff; +} +void Randomize(Http2AltSvcFields* p, Http2Random* rng) { + p->origin_length = rng->Rand16(); +} + +void ScrubFlagsOfHeader(Http2FrameHeader* header) { + uint8_t invalid_mask = InvalidFlagMaskForFrameType(header->type); + uint8_t keep_mask = ~invalid_mask; + header->RetainFlags(keep_mask); +} + +bool FrameIsPadded(const Http2FrameHeader& header) { + switch (header.type) { + case Http2FrameType::DATA: + case Http2FrameType::HEADERS: + case Http2FrameType::PUSH_PROMISE: + return header.IsPadded(); + default: + return false; + } +} + +bool FrameHasPriority(const Http2FrameHeader& header) { + switch (header.type) { + case Http2FrameType::HEADERS: + return header.HasPriority(); + case Http2FrameType::PRIORITY: + return true; + default: + return false; + } +} + +bool FrameCanHavePayload(const Http2FrameHeader& header) { + switch (header.type) { + case Http2FrameType::DATA: + case Http2FrameType::HEADERS: + case Http2FrameType::PUSH_PROMISE: + case Http2FrameType::CONTINUATION: + case Http2FrameType::PING: + case Http2FrameType::GOAWAY: + case Http2FrameType::ALTSVC: + return true; + default: + return false; + } +} + +bool FrameCanHaveHpackPayload(const Http2FrameHeader& header) { + switch (header.type) { + case Http2FrameType::HEADERS: + case Http2FrameType::PUSH_PROMISE: + case Http2FrameType::CONTINUATION: + return true; + default: + return false; + } +} + +} // namespace test +} // namespace http2 diff --git a/chromium/net/third_party/quiche/src/http2/http2_structures_test_util.h b/chromium/net/third_party/quiche/src/http2/http2_structures_test_util.h new file mode 100644 index 00000000000..86fbf3f20af --- /dev/null +++ b/chromium/net/third_party/quiche/src/http2/http2_structures_test_util.h @@ -0,0 +1,59 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef QUICHE_HTTP2_HTTP2_STRUCTURES_TEST_UTIL_H_ +#define QUICHE_HTTP2_HTTP2_STRUCTURES_TEST_UTIL_H_ + +#include "testing/gtest/include/gtest/gtest.h" +#include "net/third_party/quiche/src/http2/http2_structures.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string.h" +#include "net/third_party/quiche/src/http2/test_tools/http2_random.h" +#include "net/third_party/quiche/src/http2/tools/http2_frame_builder.h" + +namespace http2 { +namespace test { + +template <class S> +Http2String SerializeStructure(const S& s) { + Http2FrameBuilder fb; + fb.Append(s); + EXPECT_EQ(S::EncodedSize(), fb.size()); + return fb.buffer(); +} + +// Randomize the members of out, in a manner that yields encodeable contents +// (e.g. a "uint24" field has only the low 24 bits set). +void Randomize(Http2FrameHeader* out, Http2Random* rng); +void Randomize(Http2PriorityFields* out, Http2Random* rng); +void Randomize(Http2RstStreamFields* out, Http2Random* rng); +void Randomize(Http2SettingFields* out, Http2Random* rng); +void Randomize(Http2PushPromiseFields* out, Http2Random* rng); +void Randomize(Http2PingFields* out, Http2Random* rng); +void Randomize(Http2GoAwayFields* out, Http2Random* rng); +void Randomize(Http2WindowUpdateFields* out, Http2Random* rng); +void Randomize(Http2AltSvcFields* out, Http2Random* rng); + +// Clear bits of header->flags that are known to be invalid for the +// type. For unknown frame types, no change is made. +void ScrubFlagsOfHeader(Http2FrameHeader* header); + +// Is the frame with this header padded? Only true for known/supported frame +// types. +bool FrameIsPadded(const Http2FrameHeader& header); + +// Does the frame with this header have Http2PriorityFields? +bool FrameHasPriority(const Http2FrameHeader& header); + +// Does the frame with this header have a variable length payload (including +// empty) payload (e.g. DATA or HEADERS)? Really a test of the frame type. +bool FrameCanHavePayload(const Http2FrameHeader& header); + +// Does the frame with this header have a variable length HPACK payload +// (including empty) payload (e.g. HEADERS)? Really a test of the frame type. +bool FrameCanHaveHpackPayload(const Http2FrameHeader& header); + +} // namespace test +} // namespace http2 + +#endif // QUICHE_HTTP2_HTTP2_STRUCTURES_TEST_UTIL_H_ diff --git a/chromium/net/third_party/quiche/src/http2/platform/api/http2_arraysize.h b/chromium/net/third_party/quiche/src/http2/platform/api/http2_arraysize.h new file mode 100644 index 00000000000..417e53b4c78 --- /dev/null +++ b/chromium/net/third_party/quiche/src/http2/platform/api/http2_arraysize.h @@ -0,0 +1,12 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef QUICHE_HTTP2_PLATFORM_API_HTTP2_ARRAYSIZE_H_ +#define QUICHE_HTTP2_PLATFORM_API_HTTP2_ARRAYSIZE_H_ + +#include "net/http2/platform/impl/http2_arraysize_impl.h" + +#define HTTP2_ARRAYSIZE(x) HTTP2_ARRAYSIZE_IMPL(x) + +#endif // QUICHE_HTTP2_PLATFORM_API_HTTP2_ARRAYSIZE_H_ diff --git a/chromium/net/third_party/quiche/src/http2/platform/api/http2_bug_tracker.h b/chromium/net/third_party/quiche/src/http2/platform/api/http2_bug_tracker.h new file mode 100644 index 00000000000..93ad67717f1 --- /dev/null +++ b/chromium/net/third_party/quiche/src/http2/platform/api/http2_bug_tracker.h @@ -0,0 +1,15 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef QUICHE_HTTP2_PLATFORM_API_HTTP2_BUG_TRACKER_H_ +#define QUICHE_HTTP2_PLATFORM_API_HTTP2_BUG_TRACKER_H_ + +#include "net/http2/platform/impl/http2_bug_tracker_impl.h" + +#define HTTP2_BUG HTTP2_BUG_IMPL +#define HTTP2_BUG_IF HTTP2_BUG_IF_IMPL +#define FLAGS_http2_always_log_bugs_for_tests \ + FLAGS_http2_always_log_bugs_for_tests_IMPL + +#endif // QUICHE_HTTP2_PLATFORM_API_HTTP2_BUG_TRACKER_H_ diff --git a/chromium/net/third_party/quiche/src/http2/platform/api/http2_containers.h b/chromium/net/third_party/quiche/src/http2/platform/api/http2_containers.h new file mode 100644 index 00000000000..e748e7bcd91 --- /dev/null +++ b/chromium/net/third_party/quiche/src/http2/platform/api/http2_containers.h @@ -0,0 +1,17 @@ +#ifndef QUICHE_HTTP2_PLATFORM_API_HTTP2_CONTAINERS_H_ +#define QUICHE_HTTP2_PLATFORM_API_HTTP2_CONTAINERS_H_ + +#include "net/http2/platform/impl/http2_containers_impl.h" + +namespace http2 { + +// Represents a double-ended queue which may be backed by a list or a flat +// circular buffer. +// +// DOES NOT GUARANTEE POINTER OR ITERATOR STABILITY! +template <typename T> +using Http2Deque = Http2DequeImpl<T>; + +} // namespace http2 + +#endif // QUICHE_HTTP2_PLATFORM_API_HTTP2_CONTAINERS_H_ diff --git a/chromium/net/third_party/quiche/src/http2/platform/api/http2_estimate_memory_usage.h b/chromium/net/third_party/quiche/src/http2/platform/api/http2_estimate_memory_usage.h new file mode 100644 index 00000000000..fd405faa460 --- /dev/null +++ b/chromium/net/third_party/quiche/src/http2/platform/api/http2_estimate_memory_usage.h @@ -0,0 +1,21 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef QUICHE_HTTP2_PLATFORM_API_HTTP2_ESTIMATE_MEMORY_USAGE_H_ +#define QUICHE_HTTP2_PLATFORM_API_HTTP2_ESTIMATE_MEMORY_USAGE_H_ + +#include <cstddef> + +#include "net/http2/platform/impl/http2_estimate_memory_usage_impl.h" + +namespace http2 { + +template <class T> +size_t Http2EstimateMemoryUsage(const T& object) { + return Http2EstimateMemoryUsageImpl(object); +} + +} // namespace http2 + +#endif // QUICHE_HTTP2_PLATFORM_API_HTTP2_ESTIMATE_MEMORY_USAGE_H_ diff --git a/chromium/net/third_party/quiche/src/http2/platform/api/http2_export.h b/chromium/net/third_party/quiche/src/http2/platform/api/http2_export.h new file mode 100644 index 00000000000..e262f744782 --- /dev/null +++ b/chromium/net/third_party/quiche/src/http2/platform/api/http2_export.h @@ -0,0 +1,10 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef QUICHE_HTTP2_PLATFORM_API_HTTP2_EXPORT_H_ +#define QUICHE_HTTP2_PLATFORM_API_HTTP2_EXPORT_H_ + +#include "net/http2/platform/impl/http2_export_impl.h" + +#endif // QUICHE_HTTP2_PLATFORM_API_HTTP2_EXPORT_H_ diff --git a/chromium/net/third_party/quiche/src/http2/platform/api/http2_flag_utils.h b/chromium/net/third_party/quiche/src/http2/platform/api/http2_flag_utils.h new file mode 100644 index 00000000000..4303c437d0a --- /dev/null +++ b/chromium/net/third_party/quiche/src/http2/platform/api/http2_flag_utils.h @@ -0,0 +1,12 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef QUICHE_HTTP2_PLATFORM_API_HTTP2_FLAG_UTILS_H_ +#define QUICHE_HTTP2_PLATFORM_API_HTTP2_FLAG_UTILS_H_ + +#include "net/http2/platform/impl/http2_flag_utils_impl.h" + +#define HTTP2_RELOADABLE_FLAG_COUNT HTTP2_RELOADABLE_FLAG_COUNT_IMPL + +#endif // QUICHE_HTTP2_PLATFORM_API_HTTP2_FLAG_UTILS_H_ diff --git a/chromium/net/third_party/quiche/src/http2/platform/api/http2_flags.h b/chromium/net/third_party/quiche/src/http2/platform/api/http2_flags.h new file mode 100644 index 00000000000..08f95da687f --- /dev/null +++ b/chromium/net/third_party/quiche/src/http2/platform/api/http2_flags.h @@ -0,0 +1,14 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef QUICHE_HTTP2_PLATFORM_API_HTTP2_FLAGS_H_ +#define QUICHE_HTTP2_PLATFORM_API_HTTP2_FLAGS_H_ + +#include "net/http2/platform/impl/http2_flags_impl.h" + +#define GetHttp2ReloadableFlag(flag) GetHttp2ReloadableFlagImpl(flag) +#define SetHttp2ReloadableFlag(flag, value) \ + SetHttp2ReloadableFlagImpl(flag, value) + +#endif // QUICHE_HTTP2_PLATFORM_API_HTTP2_FLAGS_H_ diff --git a/chromium/net/third_party/quiche/src/http2/platform/api/http2_macros.h b/chromium/net/third_party/quiche/src/http2/platform/api/http2_macros.h new file mode 100644 index 00000000000..0be5e89b4ec --- /dev/null +++ b/chromium/net/third_party/quiche/src/http2/platform/api/http2_macros.h @@ -0,0 +1,10 @@ +#ifndef QUICHE_HTTP2_PLATFORM_API_HTTP2_MACROS_H_ +#define QUICHE_HTTP2_PLATFORM_API_HTTP2_MACROS_H_ + +#include "net/http2/platform/impl/http2_macros_impl.h" + +#define HTTP2_FALLTHROUGH HTTP2_FALLTHROUGH_IMPL +#define HTTP2_UNREACHABLE() HTTP2_UNREACHABLE_IMPL() +#define HTTP2_DIE_IF_NULL(ptr) HTTP2_DIE_IF_NULL_IMPL(ptr) + +#endif // QUICHE_HTTP2_PLATFORM_API_HTTP2_MACROS_H_ diff --git a/chromium/net/third_party/quiche/src/http2/platform/api/http2_mock_log.h b/chromium/net/third_party/quiche/src/http2/platform/api/http2_mock_log.h new file mode 100644 index 00000000000..3d16837d518 --- /dev/null +++ b/chromium/net/third_party/quiche/src/http2/platform/api/http2_mock_log.h @@ -0,0 +1,18 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef QUICHE_HTTP2_PLATFORM_API_HTTP2_MOCK_LOG_H_ +#define QUICHE_HTTP2_PLATFORM_API_HTTP2_MOCK_LOG_H_ + +#include "net/http2/platform/impl/http2_mock_log_impl.h" + +using Http2MockLog = Http2MockLogImpl; +#define CREATE_HTTP2_MOCK_LOG(log) CREATE_HTTP2_MOCK_LOG_IMPL(log) + +#define EXPECT_HTTP2_LOG_CALL(log) EXPECT_HTTP2_LOG_CALL_IMPL(log) + +#define EXPECT_HTTP2_LOG_CALL_CONTAINS(log, level, content) \ + EXPECT_HTTP2_LOG_CALL_CONTAINS_IMPL(log, level, content) + +#endif // QUICHE_HTTP2_PLATFORM_API_HTTP2_MOCK_LOG_H_ diff --git a/chromium/net/third_party/quiche/src/http2/platform/api/http2_optional.h b/chromium/net/third_party/quiche/src/http2/platform/api/http2_optional.h new file mode 100644 index 00000000000..64ce5f5f478 --- /dev/null +++ b/chromium/net/third_party/quiche/src/http2/platform/api/http2_optional.h @@ -0,0 +1,19 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef QUICHE_HTTP2_PLATFORM_API_HTTP2_OPTIONAL_H_ +#define QUICHE_HTTP2_PLATFORM_API_HTTP2_OPTIONAL_H_ + +#include <utility> + +#include "net/http2/platform/impl/http2_optional_impl.h" + +namespace http2 { + +template <typename T> +using Http2Optional = Http2OptionalImpl<T>; + +} // namespace http2 + +#endif // QUICHE_HTTP2_PLATFORM_API_HTTP2_OPTIONAL_H_ diff --git a/chromium/net/third_party/quiche/src/http2/platform/api/http2_ptr_util.h b/chromium/net/third_party/quiche/src/http2/platform/api/http2_ptr_util.h new file mode 100644 index 00000000000..2530e7ccf58 --- /dev/null +++ b/chromium/net/third_party/quiche/src/http2/platform/api/http2_ptr_util.h @@ -0,0 +1,22 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef QUICHE_HTTP2_PLATFORM_API_HTTP2_PTR_UTIL_H_ +#define QUICHE_HTTP2_PLATFORM_API_HTTP2_PTR_UTIL_H_ + +#include <memory> +#include <utility> + +#include "net/http2/platform/impl/http2_ptr_util_impl.h" + +namespace http2 { + +template <typename T, typename... Args> +std::unique_ptr<T> Http2MakeUnique(Args&&... args) { + return Http2MakeUniqueImpl<T>(std::forward<Args>(args)...); +} + +} // namespace http2 + +#endif // QUICHE_HTTP2_PLATFORM_API_HTTP2_PTR_UTIL_H_ diff --git a/chromium/net/third_party/quiche/src/http2/platform/api/http2_reconstruct_object.h b/chromium/net/third_party/quiche/src/http2/platform/api/http2_reconstruct_object.h new file mode 100644 index 00000000000..ddcb312157e --- /dev/null +++ b/chromium/net/third_party/quiche/src/http2/platform/api/http2_reconstruct_object.h @@ -0,0 +1,34 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef QUICHE_HTTP2_PLATFORM_API_HTTP2_RECONSTRUCT_OBJECT_H_ +#define QUICHE_HTTP2_PLATFORM_API_HTTP2_RECONSTRUCT_OBJECT_H_ + +#include <utility> + +#include "net/http2/platform/impl/http2_reconstruct_object_impl.h" + +namespace http2 { +namespace test { + +class Http2Random; + +// Reconstruct an object so that it is initialized as when it was first +// constructed. Runs the destructor to handle objects that might own resources, +// and runs the constructor with the provided arguments, if any. +template <class T, class... Args> +void Http2ReconstructObject(T* ptr, Http2Random* rng, Args&&... args) { + Http2ReconstructObjectImpl(ptr, rng, std::forward<Args>(args)...); +} + +// This version applies default-initialization to the object. +template <class T> +void Http2DefaultReconstructObject(T* ptr, Http2Random* rng) { + Http2DefaultReconstructObjectImpl(ptr, rng); +} + +} // namespace test +} // namespace http2 + +#endif // QUICHE_HTTP2_PLATFORM_API_HTTP2_RECONSTRUCT_OBJECT_H_ diff --git a/chromium/net/third_party/quiche/src/http2/platform/api/http2_string.h b/chromium/net/third_party/quiche/src/http2/platform/api/http2_string.h new file mode 100644 index 00000000000..25f9e593cc1 --- /dev/null +++ b/chromium/net/third_party/quiche/src/http2/platform/api/http2_string.h @@ -0,0 +1,16 @@ +// Copyright (c) 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef QUICHE_HTTP2_PLATFORM_API_HTTP2_STRING_H_ +#define QUICHE_HTTP2_PLATFORM_API_HTTP2_STRING_H_ + +#include "net/http2/platform/impl/http2_string_impl.h" + +namespace http2 { + +using Http2String = Http2StringImpl; + +} // namespace http2 + +#endif // QUICHE_HTTP2_PLATFORM_API_HTTP2_STRING_H_ diff --git a/chromium/net/third_party/quiche/src/http2/platform/api/http2_string_piece.h b/chromium/net/third_party/quiche/src/http2/platform/api/http2_string_piece.h new file mode 100644 index 00000000000..92fb3ef393e --- /dev/null +++ b/chromium/net/third_party/quiche/src/http2/platform/api/http2_string_piece.h @@ -0,0 +1,16 @@ +// Copyright (c) 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef QUICHE_HTTP2_PLATFORM_API_HTTP2_STRING_PIECE_H_ +#define QUICHE_HTTP2_PLATFORM_API_HTTP2_STRING_PIECE_H_ + +#include "net/http2/platform/impl/http2_string_piece_impl.h" + +namespace http2 { + +using Http2StringPiece = Http2StringPieceImpl; + +} // namespace http2 + +#endif // QUICHE_HTTP2_PLATFORM_API_HTTP2_STRING_PIECE_H_ diff --git a/chromium/net/third_party/quiche/src/http2/platform/api/http2_string_utils.h b/chromium/net/third_party/quiche/src/http2/platform/api/http2_string_utils.h new file mode 100644 index 00000000000..ba4056051ee --- /dev/null +++ b/chromium/net/third_party/quiche/src/http2/platform/api/http2_string_utils.h @@ -0,0 +1,56 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef QUICHE_HTTP2_PLATFORM_API_HTTP2_STRING_UTILS_H_ +#define QUICHE_HTTP2_PLATFORM_API_HTTP2_STRING_UTILS_H_ + +#include <type_traits> +#include <utility> + +#include "net/third_party/quiche/src/http2/platform/api/http2_string.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string_piece.h" +#include "net/http2/platform/impl/http2_string_utils_impl.h" + +namespace http2 { + +template <typename... Args> +inline Http2String Http2StrCat(const Args&... args) { + return Http2StrCatImpl(std::forward<const Args&>(args)...); +} + +template <typename... Args> +inline void Http2StrAppend(Http2String* output, const Args&... args) { + Http2StrAppendImpl(output, std::forward<const Args&>(args)...); +} + +template <typename... Args> +inline Http2String Http2StringPrintf(const Args&... args) { + return Http2StringPrintfImpl(std::forward<const Args&>(args)...); +} + +inline Http2String Http2HexEncode(const void* bytes, size_t size) { + return Http2HexEncodeImpl(bytes, size); +} + +inline Http2String Http2HexDecode(Http2StringPiece data) { + return Http2HexDecodeImpl(data); +} + +inline Http2String Http2HexDump(Http2StringPiece data) { + return Http2HexDumpImpl(data); +} + +inline Http2String Http2HexEscape(Http2StringPiece data) { + return Http2HexEscapeImpl(data); +} + +template <typename Number> +inline Http2String Http2Hex(Number number) { + static_assert(std::is_integral<Number>::value, "Number has to be an int"); + return Http2HexImpl(number); +} + +} // namespace http2 + +#endif // QUICHE_HTTP2_PLATFORM_API_HTTP2_STRING_UTILS_H_ diff --git a/chromium/net/third_party/quiche/src/http2/platform/api/http2_string_utils_test.cc b/chromium/net/third_party/quiche/src/http2/platform/api/http2_string_utils_test.cc new file mode 100644 index 00000000000..254f9d7d460 --- /dev/null +++ b/chromium/net/third_party/quiche/src/http2/platform/api/http2_string_utils_test.cc @@ -0,0 +1,178 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/http2/platform/api/http2_string_utils.h" + +#include <cstdint> + +#include "testing/gtest/include/gtest/gtest.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string_piece.h" + +namespace http2 { +namespace test { +namespace { + +TEST(Http2StringUtilsTest, Http2StrCat) { + // No arguments. + EXPECT_EQ("", Http2StrCat()); + + // Single string-like argument. + const char kFoo[] = "foo"; + const Http2String string_foo(kFoo); + const Http2StringPiece stringpiece_foo(string_foo); + EXPECT_EQ("foo", Http2StrCat(kFoo)); + EXPECT_EQ("foo", Http2StrCat(string_foo)); + EXPECT_EQ("foo", Http2StrCat(stringpiece_foo)); + + // Two string-like arguments. + const char kBar[] = "bar"; + const Http2StringPiece stringpiece_bar(kBar); + const Http2String string_bar(kBar); + EXPECT_EQ("foobar", Http2StrCat(kFoo, kBar)); + EXPECT_EQ("foobar", Http2StrCat(kFoo, string_bar)); + EXPECT_EQ("foobar", Http2StrCat(kFoo, stringpiece_bar)); + EXPECT_EQ("foobar", Http2StrCat(string_foo, kBar)); + EXPECT_EQ("foobar", Http2StrCat(string_foo, string_bar)); + EXPECT_EQ("foobar", Http2StrCat(string_foo, stringpiece_bar)); + EXPECT_EQ("foobar", Http2StrCat(stringpiece_foo, kBar)); + EXPECT_EQ("foobar", Http2StrCat(stringpiece_foo, string_bar)); + EXPECT_EQ("foobar", Http2StrCat(stringpiece_foo, stringpiece_bar)); + + // Many-many arguments. + EXPECT_EQ( + "foobarbazquxquuxquuzcorgegraultgarplywaldofredplughxyzzythud", + Http2StrCat("foo", "bar", "baz", "qux", "quux", "quuz", "corge", "grault", + "garply", "waldo", "fred", "plugh", "xyzzy", "thud")); + + // Numerical arguments. + const int16_t i = 1; + const uint64_t u = 8; + const double d = 3.1415; + + EXPECT_EQ("1 8", Http2StrCat(i, " ", u)); + EXPECT_EQ("3.14151181", Http2StrCat(d, i, i, u, i)); + EXPECT_EQ("i: 1, u: 8, d: 3.1415", + Http2StrCat("i: ", i, ", u: ", u, ", d: ", d)); + + // Boolean arguments. + const bool t = true; + const bool f = false; + + EXPECT_EQ("1", Http2StrCat(t)); + EXPECT_EQ("0", Http2StrCat(f)); + EXPECT_EQ("0110", Http2StrCat(f, t, t, f)); + + // Mixed string-like, numerical, and Boolean arguments. + EXPECT_EQ("foo1foo081bar3.14151", + Http2StrCat(kFoo, i, string_foo, f, u, t, stringpiece_bar, d, t)); + EXPECT_EQ("3.141511bar18bar13.14150", + Http2StrCat(d, t, t, string_bar, i, u, kBar, t, d, f)); +} + +TEST(Http2StringUtilsTest, Http2StrAppend) { + // No arguments on empty string. + Http2String output; + Http2StrAppend(&output); + EXPECT_TRUE(output.empty()); + + // Single string-like argument. + const char kFoo[] = "foo"; + const Http2String string_foo(kFoo); + const Http2StringPiece stringpiece_foo(string_foo); + Http2StrAppend(&output, kFoo); + EXPECT_EQ("foo", output); + Http2StrAppend(&output, string_foo); + EXPECT_EQ("foofoo", output); + Http2StrAppend(&output, stringpiece_foo); + EXPECT_EQ("foofoofoo", output); + + // No arguments on non-empty string. + Http2StrAppend(&output); + EXPECT_EQ("foofoofoo", output); + + output.clear(); + + // Two string-like arguments. + const char kBar[] = "bar"; + const Http2StringPiece stringpiece_bar(kBar); + const Http2String string_bar(kBar); + Http2StrAppend(&output, kFoo, kBar); + EXPECT_EQ("foobar", output); + Http2StrAppend(&output, kFoo, string_bar); + EXPECT_EQ("foobarfoobar", output); + Http2StrAppend(&output, kFoo, stringpiece_bar); + EXPECT_EQ("foobarfoobarfoobar", output); + Http2StrAppend(&output, string_foo, kBar); + EXPECT_EQ("foobarfoobarfoobarfoobar", output); + + output.clear(); + + Http2StrAppend(&output, string_foo, string_bar); + EXPECT_EQ("foobar", output); + Http2StrAppend(&output, string_foo, stringpiece_bar); + EXPECT_EQ("foobarfoobar", output); + Http2StrAppend(&output, stringpiece_foo, kBar); + EXPECT_EQ("foobarfoobarfoobar", output); + Http2StrAppend(&output, stringpiece_foo, string_bar); + EXPECT_EQ("foobarfoobarfoobarfoobar", output); + + output.clear(); + + Http2StrAppend(&output, stringpiece_foo, stringpiece_bar); + EXPECT_EQ("foobar", output); + + // Many-many arguments. + Http2StrAppend(&output, "foo", "bar", "baz", "qux", "quux", "quuz", "corge", + "grault", "garply", "waldo", "fred", "plugh", "xyzzy", "thud"); + EXPECT_EQ( + "foobarfoobarbazquxquuxquuzcorgegraultgarplywaldofredplughxyzzythud", + output); + + output.clear(); + + // Numerical arguments. + const int16_t i = 1; + const uint64_t u = 8; + const double d = 3.1415; + + Http2StrAppend(&output, i, " ", u); + EXPECT_EQ("1 8", output); + Http2StrAppend(&output, d, i, i, u, i); + EXPECT_EQ("1 83.14151181", output); + Http2StrAppend(&output, "i: ", i, ", u: ", u, ", d: ", d); + EXPECT_EQ("1 83.14151181i: 1, u: 8, d: 3.1415", output); + + output.clear(); + + // Boolean arguments. + const bool t = true; + const bool f = false; + + Http2StrAppend(&output, t); + EXPECT_EQ("1", output); + Http2StrAppend(&output, f); + EXPECT_EQ("10", output); + Http2StrAppend(&output, f, t, t, f); + EXPECT_EQ("100110", output); + + output.clear(); + + // Mixed string-like, numerical, and Boolean arguments. + Http2StrAppend(&output, kFoo, i, string_foo, f, u, t, stringpiece_bar, d, t); + EXPECT_EQ("foo1foo081bar3.14151", output); + Http2StrAppend(&output, d, t, t, string_bar, i, u, kBar, t, d, f); + EXPECT_EQ("foo1foo081bar3.141513.141511bar18bar13.14150", output); +} + +TEST(Http2StringUtilsTest, Http2StringPrintf) { + EXPECT_EQ("", Http2StringPrintf("%s", "")); + EXPECT_EQ("foobar", Http2StringPrintf("%sbar", "foo")); + EXPECT_EQ("foobar", Http2StringPrintf("%s%s", "foo", "bar")); + EXPECT_EQ("foo: 1, bar: 2.0", + Http2StringPrintf("foo: %d, bar: %.1f", 1, 2.0)); +} + +} // namespace +} // namespace test +} // namespace http2 diff --git a/chromium/net/third_party/quiche/src/http2/platform/api/http2_test_helpers.h b/chromium/net/third_party/quiche/src/http2/platform/api/http2_test_helpers.h new file mode 100644 index 00000000000..a54f4ab1b7e --- /dev/null +++ b/chromium/net/third_party/quiche/src/http2/platform/api/http2_test_helpers.h @@ -0,0 +1,16 @@ +#ifndef QUICHE_HTTP2_PLATFORM_API_HTTP2_TEST_HELPERS_H_ +#define QUICHE_HTTP2_PLATFORM_API_HTTP2_TEST_HELPERS_H_ + +// Provides VERIFY_* macros, similar to EXPECT_* and ASSERT_*, but they return +// an AssertionResult if the condition is not satisfied. +#include "net/http2/platform/impl/http2_test_helpers_impl.h" + +#include "testing/gtest/include/gtest/gtest.h" // For AssertionSuccess + +#define VERIFY_AND_RETURN_SUCCESS(expression) \ + { \ + VERIFY_SUCCESS(expression); \ + return ::testing::AssertionSuccess(); \ + } + +#endif // QUICHE_HTTP2_PLATFORM_API_HTTP2_TEST_HELPERS_H_ diff --git a/chromium/net/third_party/quiche/src/http2/test_tools/frame_parts.cc b/chromium/net/third_party/quiche/src/http2/test_tools/frame_parts.cc new file mode 100644 index 00000000000..3d0453e9294 --- /dev/null +++ b/chromium/net/third_party/quiche/src/http2/test_tools/frame_parts.cc @@ -0,0 +1,526 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/http2/test_tools/frame_parts.h" + +#include <type_traits> + +#include "base/logging.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "net/third_party/quiche/src/http2/http2_structures_test_util.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string_utils.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_test_helpers.h" + +using ::testing::AssertionFailure; +using ::testing::AssertionResult; +using ::testing::AssertionSuccess; +using ::testing::ContainerEq; + +namespace http2 { +namespace test { +namespace { + +static_assert(std::is_base_of<Http2FrameDecoderListener, FrameParts>::value && + !std::is_abstract<FrameParts>::value, + "FrameParts needs to implement all of the methods of " + "Http2FrameDecoderListener"); + +// Compare two optional variables of the same type. +// TODO(jamessynge): Maybe create a ::testing::Matcher for this. +template <class T> +AssertionResult VerifyOptionalEq(const T& opt_a, const T& opt_b) { + if (opt_a) { + if (opt_b) { + VERIFY_EQ(opt_a.value(), opt_b.value()); + } else { + return AssertionFailure() + << "opt_b is not set; opt_a.value()=" << opt_a.value(); + } + } else if (opt_b) { + return AssertionFailure() + << "opt_a is not set; opt_b.value()=" << opt_b.value(); + } + return AssertionSuccess(); +} + +} // namespace + +FrameParts::FrameParts(const Http2FrameHeader& header) : frame_header_(header) { + VLOG(1) << "FrameParts, header: " << frame_header_; +} + +FrameParts::FrameParts(const Http2FrameHeader& header, Http2StringPiece payload) + : FrameParts(header) { + VLOG(1) << "FrameParts with payload.size() = " << payload.size(); + this->payload_.append(payload.data(), payload.size()); + opt_payload_length_ = payload.size(); +} +FrameParts::FrameParts(const Http2FrameHeader& header, + Http2StringPiece payload, + size_t total_pad_length) + : FrameParts(header, payload) { + VLOG(1) << "FrameParts with total_pad_length=" << total_pad_length; + SetTotalPadLength(total_pad_length); +} + +FrameParts::FrameParts(const FrameParts& header) = default; + +FrameParts::~FrameParts() = default; + +AssertionResult FrameParts::VerifyEquals(const FrameParts& that) const { +#define COMMON_MESSAGE "\n this: " << *this << "\n that: " << that + + VERIFY_EQ(frame_header_, that.frame_header_) << COMMON_MESSAGE; + VERIFY_EQ(payload_, that.payload_) << COMMON_MESSAGE; + VERIFY_EQ(padding_, that.padding_) << COMMON_MESSAGE; + VERIFY_EQ(altsvc_origin_, that.altsvc_origin_) << COMMON_MESSAGE; + VERIFY_EQ(altsvc_value_, that.altsvc_value_) << COMMON_MESSAGE; + VERIFY_THAT(settings_, ContainerEq(that.settings_)) << COMMON_MESSAGE; + +#define VERIFY_OPTIONAL_FIELD(field_name) \ + VERIFY_SUCCESS(VerifyOptionalEq(field_name, that.field_name)) + + VERIFY_OPTIONAL_FIELD(opt_altsvc_origin_length_) << COMMON_MESSAGE; + VERIFY_OPTIONAL_FIELD(opt_altsvc_value_length_) << COMMON_MESSAGE; + VERIFY_OPTIONAL_FIELD(opt_goaway_) << COMMON_MESSAGE; + VERIFY_OPTIONAL_FIELD(opt_missing_length_) << COMMON_MESSAGE; + VERIFY_OPTIONAL_FIELD(opt_pad_length_) << COMMON_MESSAGE; + VERIFY_OPTIONAL_FIELD(opt_ping_) << COMMON_MESSAGE; + VERIFY_OPTIONAL_FIELD(opt_priority_) << COMMON_MESSAGE; + VERIFY_OPTIONAL_FIELD(opt_push_promise_) << COMMON_MESSAGE; + VERIFY_OPTIONAL_FIELD(opt_rst_stream_error_code_) << COMMON_MESSAGE; + VERIFY_OPTIONAL_FIELD(opt_window_update_increment_) << COMMON_MESSAGE; + +#undef VERIFY_OPTIONAL_FIELD + + return AssertionSuccess(); +} + +void FrameParts::SetTotalPadLength(size_t total_pad_length) { + opt_pad_length_.reset(); + padding_.clear(); + if (total_pad_length > 0) { + ASSERT_LE(total_pad_length, 256u); + ASSERT_TRUE(frame_header_.IsPadded()); + opt_pad_length_ = total_pad_length - 1; + char zero = 0; + padding_.append(opt_pad_length_.value(), zero); + } + + if (opt_pad_length_) { + VLOG(1) << "SetTotalPadLength: pad_length=" << opt_pad_length_.value(); + } else { + VLOG(1) << "SetTotalPadLength: has no pad length"; + } +} + +void FrameParts::SetAltSvcExpected(Http2StringPiece origin, + Http2StringPiece value) { + altsvc_origin_.append(origin.data(), origin.size()); + altsvc_value_.append(value.data(), value.size()); + opt_altsvc_origin_length_ = origin.size(); + opt_altsvc_value_length_ = value.size(); +} + +bool FrameParts::OnFrameHeader(const Http2FrameHeader& header) { + ADD_FAILURE() << "OnFrameHeader: " << *this; + return true; +} + +void FrameParts::OnDataStart(const Http2FrameHeader& header) { + VLOG(1) << "OnDataStart: " << header; + ASSERT_TRUE(StartFrameOfType(header, Http2FrameType::DATA)) << *this; + opt_payload_length_ = header.payload_length; +} + +void FrameParts::OnDataPayload(const char* data, size_t len) { + VLOG(1) << "OnDataPayload: len=" << len + << "; frame_header_: " << frame_header_; + ASSERT_TRUE(InFrameOfType(Http2FrameType::DATA)) << *this; + ASSERT_TRUE(AppendString(Http2StringPiece(data, len), &payload_, + &opt_payload_length_)); +} + +void FrameParts::OnDataEnd() { + VLOG(1) << "OnDataEnd; frame_header_: " << frame_header_; + ASSERT_TRUE(EndFrameOfType(Http2FrameType::DATA)) << *this; +} + +void FrameParts::OnHeadersStart(const Http2FrameHeader& header) { + VLOG(1) << "OnHeadersStart: " << header; + ASSERT_TRUE(StartFrameOfType(header, Http2FrameType::HEADERS)) << *this; + opt_payload_length_ = header.payload_length; +} + +void FrameParts::OnHeadersPriority(const Http2PriorityFields& priority) { + VLOG(1) << "OnHeadersPriority: priority: " << priority + << "; frame_header_: " << frame_header_; + ASSERT_TRUE(InFrameOfType(Http2FrameType::HEADERS)) << *this; + ASSERT_FALSE(opt_priority_); + opt_priority_ = priority; + ASSERT_TRUE(opt_payload_length_); + opt_payload_length_ = + opt_payload_length_.value() - Http2PriorityFields::EncodedSize(); +} + +void FrameParts::OnHpackFragment(const char* data, size_t len) { + VLOG(1) << "OnHpackFragment: len=" << len + << "; frame_header_: " << frame_header_; + ASSERT_TRUE(got_start_callback_); + ASSERT_FALSE(got_end_callback_); + ASSERT_TRUE(FrameCanHaveHpackPayload(frame_header_)) << *this; + ASSERT_TRUE(AppendString(Http2StringPiece(data, len), &payload_, + &opt_payload_length_)); +} + +void FrameParts::OnHeadersEnd() { + VLOG(1) << "OnHeadersEnd; frame_header_: " << frame_header_; + ASSERT_TRUE(EndFrameOfType(Http2FrameType::HEADERS)) << *this; +} + +void FrameParts::OnPriorityFrame(const Http2FrameHeader& header, + const Http2PriorityFields& priority) { + VLOG(1) << "OnPriorityFrame: " << header << "; priority: " << priority; + ASSERT_TRUE(StartFrameOfType(header, Http2FrameType::PRIORITY)) << *this; + ASSERT_FALSE(opt_priority_); + opt_priority_ = priority; + ASSERT_TRUE(EndFrameOfType(Http2FrameType::PRIORITY)) << *this; +} + +void FrameParts::OnContinuationStart(const Http2FrameHeader& header) { + VLOG(1) << "OnContinuationStart: " << header; + ASSERT_TRUE(StartFrameOfType(header, Http2FrameType::CONTINUATION)) << *this; + opt_payload_length_ = header.payload_length; +} + +void FrameParts::OnContinuationEnd() { + VLOG(1) << "OnContinuationEnd; frame_header_: " << frame_header_; + ASSERT_TRUE(EndFrameOfType(Http2FrameType::CONTINUATION)) << *this; +} + +void FrameParts::OnPadLength(size_t trailing_length) { + VLOG(1) << "OnPadLength: trailing_length=" << trailing_length; + ASSERT_TRUE(InPaddedFrame()) << *this; + ASSERT_FALSE(opt_pad_length_); + ASSERT_TRUE(opt_payload_length_); + size_t total_padding_length = trailing_length + 1; + ASSERT_GE(opt_payload_length_.value(), total_padding_length); + opt_payload_length_ = opt_payload_length_.value() - total_padding_length; + opt_pad_length_ = trailing_length; +} + +void FrameParts::OnPadding(const char* pad, size_t skipped_length) { + VLOG(1) << "OnPadding: skipped_length=" << skipped_length; + ASSERT_TRUE(InPaddedFrame()) << *this; + ASSERT_TRUE(opt_pad_length_); + ASSERT_TRUE(AppendString(Http2StringPiece(pad, skipped_length), &padding_, + &opt_pad_length_)); +} + +void FrameParts::OnRstStream(const Http2FrameHeader& header, + Http2ErrorCode error_code) { + VLOG(1) << "OnRstStream: " << header << "; code=" << error_code; + ASSERT_TRUE(StartFrameOfType(header, Http2FrameType::RST_STREAM)) << *this; + ASSERT_FALSE(opt_rst_stream_error_code_); + opt_rst_stream_error_code_ = error_code; + ASSERT_TRUE(EndFrameOfType(Http2FrameType::RST_STREAM)) << *this; +} + +void FrameParts::OnSettingsStart(const Http2FrameHeader& header) { + VLOG(1) << "OnSettingsStart: " << header; + ASSERT_TRUE(StartFrameOfType(header, Http2FrameType::SETTINGS)) << *this; + ASSERT_EQ(0u, settings_.size()); + ASSERT_FALSE(header.IsAck()) << header; +} + +void FrameParts::OnSetting(const Http2SettingFields& setting_fields) { + VLOG(1) << "OnSetting: " << setting_fields; + ASSERT_TRUE(InFrameOfType(Http2FrameType::SETTINGS)) << *this; + settings_.push_back(setting_fields); +} + +void FrameParts::OnSettingsEnd() { + VLOG(1) << "OnSettingsEnd; frame_header_: " << frame_header_; + ASSERT_TRUE(EndFrameOfType(Http2FrameType::SETTINGS)) << *this; +} + +void FrameParts::OnSettingsAck(const Http2FrameHeader& header) { + VLOG(1) << "OnSettingsAck: " << header; + ASSERT_TRUE(StartFrameOfType(header, Http2FrameType::SETTINGS)) << *this; + ASSERT_EQ(0u, settings_.size()); + ASSERT_TRUE(header.IsAck()); + ASSERT_TRUE(EndFrameOfType(Http2FrameType::SETTINGS)) << *this; +} + +void FrameParts::OnPushPromiseStart(const Http2FrameHeader& header, + const Http2PushPromiseFields& promise, + size_t total_padding_length) { + VLOG(1) << "OnPushPromiseStart header: " << header << "; promise: " << promise + << "; total_padding_length: " << total_padding_length; + ASSERT_TRUE(StartFrameOfType(header, Http2FrameType::PUSH_PROMISE)) << *this; + ASSERT_GE(header.payload_length, Http2PushPromiseFields::EncodedSize()); + opt_payload_length_ = + header.payload_length - Http2PushPromiseFields::EncodedSize(); + ASSERT_FALSE(opt_push_promise_); + opt_push_promise_ = promise; + if (total_padding_length > 0) { + ASSERT_GE(opt_payload_length_.value(), total_padding_length); + OnPadLength(total_padding_length - 1); + } else { + ASSERT_FALSE(header.IsPadded()); + } +} + +void FrameParts::OnPushPromiseEnd() { + VLOG(1) << "OnPushPromiseEnd; frame_header_: " << frame_header_; + ASSERT_TRUE(EndFrameOfType(Http2FrameType::PUSH_PROMISE)) << *this; +} + +void FrameParts::OnPing(const Http2FrameHeader& header, + const Http2PingFields& ping) { + VLOG(1) << "OnPing header: " << header << " ping: " << ping; + ASSERT_TRUE(StartFrameOfType(header, Http2FrameType::PING)) << *this; + ASSERT_FALSE(header.IsAck()); + ASSERT_FALSE(opt_ping_); + opt_ping_ = ping; + ASSERT_TRUE(EndFrameOfType(Http2FrameType::PING)) << *this; +} + +void FrameParts::OnPingAck(const Http2FrameHeader& header, + const Http2PingFields& ping) { + VLOG(1) << "OnPingAck header: " << header << " ping: " << ping; + ASSERT_TRUE(StartFrameOfType(header, Http2FrameType::PING)) << *this; + ASSERT_TRUE(header.IsAck()); + ASSERT_FALSE(opt_ping_); + opt_ping_ = ping; + ASSERT_TRUE(EndFrameOfType(Http2FrameType::PING)) << *this; +} + +void FrameParts::OnGoAwayStart(const Http2FrameHeader& header, + const Http2GoAwayFields& goaway) { + VLOG(1) << "OnGoAwayStart: " << goaway; + ASSERT_TRUE(StartFrameOfType(header, Http2FrameType::GOAWAY)) << *this; + ASSERT_FALSE(opt_goaway_); + opt_goaway_ = goaway; + opt_payload_length_ = + header.payload_length - Http2GoAwayFields::EncodedSize(); +} + +void FrameParts::OnGoAwayOpaqueData(const char* data, size_t len) { + VLOG(1) << "OnGoAwayOpaqueData: len=" << len; + ASSERT_TRUE(InFrameOfType(Http2FrameType::GOAWAY)) << *this; + ASSERT_TRUE(AppendString(Http2StringPiece(data, len), &payload_, + &opt_payload_length_)); +} + +void FrameParts::OnGoAwayEnd() { + VLOG(1) << "OnGoAwayEnd; frame_header_: " << frame_header_; + ASSERT_TRUE(EndFrameOfType(Http2FrameType::GOAWAY)) << *this; +} + +void FrameParts::OnWindowUpdate(const Http2FrameHeader& header, + uint32_t increment) { + VLOG(1) << "OnWindowUpdate header: " << header + << " increment=" << increment; + ASSERT_TRUE(StartFrameOfType(header, Http2FrameType::WINDOW_UPDATE)) << *this; + ASSERT_FALSE(opt_window_update_increment_); + opt_window_update_increment_ = increment; + ASSERT_TRUE(EndFrameOfType(Http2FrameType::WINDOW_UPDATE)) << *this; +} + +void FrameParts::OnAltSvcStart(const Http2FrameHeader& header, + size_t origin_length, + size_t value_length) { + VLOG(1) << "OnAltSvcStart: " << header + << " origin_length: " << origin_length + << " value_length: " << value_length; + ASSERT_TRUE(StartFrameOfType(header, Http2FrameType::ALTSVC)) << *this; + ASSERT_FALSE(opt_altsvc_origin_length_); + opt_altsvc_origin_length_ = origin_length; + ASSERT_FALSE(opt_altsvc_value_length_); + opt_altsvc_value_length_ = value_length; +} + +void FrameParts::OnAltSvcOriginData(const char* data, size_t len) { + VLOG(1) << "OnAltSvcOriginData: len=" << len; + ASSERT_TRUE(InFrameOfType(Http2FrameType::ALTSVC)) << *this; + ASSERT_TRUE(AppendString(Http2StringPiece(data, len), &altsvc_origin_, + &opt_altsvc_origin_length_)); +} + +void FrameParts::OnAltSvcValueData(const char* data, size_t len) { + VLOG(1) << "OnAltSvcValueData: len=" << len; + ASSERT_TRUE(InFrameOfType(Http2FrameType::ALTSVC)) << *this; + ASSERT_TRUE(AppendString(Http2StringPiece(data, len), &altsvc_value_, + &opt_altsvc_value_length_)); +} + +void FrameParts::OnAltSvcEnd() { + VLOG(1) << "OnAltSvcEnd; frame_header_: " << frame_header_; + ASSERT_TRUE(EndFrameOfType(Http2FrameType::ALTSVC)) << *this; +} + +void FrameParts::OnUnknownStart(const Http2FrameHeader& header) { + VLOG(1) << "OnUnknownStart: " << header; + ASSERT_FALSE(IsSupportedHttp2FrameType(header.type)) << header; + ASSERT_FALSE(got_start_callback_); + ASSERT_EQ(frame_header_, header); + got_start_callback_ = true; + opt_payload_length_ = header.payload_length; +} + +void FrameParts::OnUnknownPayload(const char* data, size_t len) { + VLOG(1) << "OnUnknownPayload: len=" << len; + ASSERT_FALSE(IsSupportedHttp2FrameType(frame_header_.type)) << *this; + ASSERT_TRUE(got_start_callback_); + ASSERT_FALSE(got_end_callback_); + ASSERT_TRUE(AppendString(Http2StringPiece(data, len), &payload_, + &opt_payload_length_)); +} + +void FrameParts::OnUnknownEnd() { + VLOG(1) << "OnUnknownEnd; frame_header_: " << frame_header_; + ASSERT_FALSE(IsSupportedHttp2FrameType(frame_header_.type)) << *this; + ASSERT_TRUE(got_start_callback_); + ASSERT_FALSE(got_end_callback_); + got_end_callback_ = true; +} + +void FrameParts::OnPaddingTooLong(const Http2FrameHeader& header, + size_t missing_length) { + VLOG(1) << "OnPaddingTooLong: " << header + << "; missing_length: " << missing_length; + ASSERT_EQ(frame_header_, header); + ASSERT_FALSE(got_end_callback_); + ASSERT_TRUE(FrameIsPadded(header)); + ASSERT_FALSE(opt_pad_length_); + ASSERT_FALSE(opt_missing_length_); + opt_missing_length_ = missing_length; + got_start_callback_ = true; + got_end_callback_ = true; +} + +void FrameParts::OnFrameSizeError(const Http2FrameHeader& header) { + VLOG(1) << "OnFrameSizeError: " << header; + ASSERT_EQ(frame_header_, header); + ASSERT_FALSE(got_end_callback_); + ASSERT_FALSE(has_frame_size_error_); + has_frame_size_error_ = true; + got_end_callback_ = true; +} + +void FrameParts::OutputTo(std::ostream& out) const { + out << "FrameParts{\n frame_header_: " << frame_header_ << "\n"; + if (!payload_.empty()) { + out << " payload_=\"" << Http2HexEscape(payload_) << "\"\n"; + } + if (!padding_.empty()) { + out << " padding_=\"" << Http2HexEscape(padding_) << "\"\n"; + } + if (!altsvc_origin_.empty()) { + out << " altsvc_origin_=\"" << Http2HexEscape(altsvc_origin_) << "\"\n"; + } + if (!altsvc_value_.empty()) { + out << " altsvc_value_=\"" << Http2HexEscape(altsvc_value_) << "\"\n"; + } + if (opt_priority_) { + out << " priority=" << opt_priority_.value() << "\n"; + } + if (opt_rst_stream_error_code_) { + out << " rst_stream=" << opt_rst_stream_error_code_.value() << "\n"; + } + if (opt_push_promise_) { + out << " push_promise=" << opt_push_promise_.value() << "\n"; + } + if (opt_ping_) { + out << " ping=" << opt_ping_.value() << "\n"; + } + if (opt_goaway_) { + out << " goaway=" << opt_goaway_.value() << "\n"; + } + if (opt_window_update_increment_) { + out << " window_update=" << opt_window_update_increment_.value() << "\n"; + } + if (opt_payload_length_) { + out << " payload_length=" << opt_payload_length_.value() << "\n"; + } + if (opt_pad_length_) { + out << " pad_length=" << opt_pad_length_.value() << "\n"; + } + if (opt_missing_length_) { + out << " missing_length=" << opt_missing_length_.value() << "\n"; + } + if (opt_altsvc_origin_length_) { + out << " origin_length=" << opt_altsvc_origin_length_.value() << "\n"; + } + if (opt_altsvc_value_length_) { + out << " value_length=" << opt_altsvc_value_length_.value() << "\n"; + } + if (has_frame_size_error_) { + out << " has_frame_size_error\n"; + } + if (got_start_callback_) { + out << " got_start_callback\n"; + } + if (got_end_callback_) { + out << " got_end_callback\n"; + } + for (size_t ndx = 0; ndx < settings_.size(); ++ndx) { + out << " setting[" << ndx << "]=" << settings_[ndx]; + } + out << "}"; +} + +AssertionResult FrameParts::StartFrameOfType( + const Http2FrameHeader& header, + Http2FrameType expected_frame_type) { + VERIFY_EQ(header.type, expected_frame_type); + VERIFY_FALSE(got_start_callback_); + VERIFY_FALSE(got_end_callback_); + VERIFY_EQ(frame_header_, header); + got_start_callback_ = true; + return AssertionSuccess(); +} + +AssertionResult FrameParts::InFrameOfType(Http2FrameType expected_frame_type) { + VERIFY_TRUE(got_start_callback_); + VERIFY_FALSE(got_end_callback_); + VERIFY_EQ(frame_header_.type, expected_frame_type); + return AssertionSuccess(); +} + +AssertionResult FrameParts::EndFrameOfType(Http2FrameType expected_frame_type) { + VERIFY_SUCCESS(InFrameOfType(expected_frame_type)); + got_end_callback_ = true; + return AssertionSuccess(); +} + +AssertionResult FrameParts::InPaddedFrame() { + VERIFY_TRUE(got_start_callback_); + VERIFY_FALSE(got_end_callback_); + VERIFY_TRUE(FrameIsPadded(frame_header_)); + return AssertionSuccess(); +} + +AssertionResult FrameParts::AppendString(Http2StringPiece source, + Http2String* target, + Http2Optional<size_t>* opt_length) { + target->append(source.data(), source.size()); + if (opt_length != nullptr) { + VERIFY_TRUE(*opt_length) << "Length is not set yet\n" << *this; + VERIFY_LE(target->size(), opt_length->value()) + << "String too large; source.size() = " << source.size() << "\n" + << *this; + } + return ::testing::AssertionSuccess(); +} + +std::ostream& operator<<(std::ostream& out, const FrameParts& v) { + v.OutputTo(out); + return out; +} + +} // namespace test +} // namespace http2 diff --git a/chromium/net/third_party/quiche/src/http2/test_tools/frame_parts.h b/chromium/net/third_party/quiche/src/http2/test_tools/frame_parts.h new file mode 100644 index 00000000000..b531174fa1d --- /dev/null +++ b/chromium/net/third_party/quiche/src/http2/test_tools/frame_parts.h @@ -0,0 +1,249 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef QUICHE_HTTP2_TEST_TOOLS_FRAME_PARTS_H_ +#define QUICHE_HTTP2_TEST_TOOLS_FRAME_PARTS_H_ + +// FrameParts implements Http2FrameDecoderListener, recording the callbacks +// during the decoding of a single frame. It is also used for comparing the +// info that a test expects to be recorded during the decoding of a frame +// with the actual recorded value (i.e. by providing a comparator). + +#include <stddef.h> + +#include <cstdint> +#include <vector> + +#include "base/logging.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener.h" +#include "net/third_party/quiche/src/http2/http2_constants.h" +#include "net/third_party/quiche/src/http2/http2_structures.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_optional.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string_piece.h" + +namespace http2 { +namespace test { + +class FrameParts : public Http2FrameDecoderListener { + public: + // The first callback for every type of frame includes the frame header; this + // is the only constructor used during decoding of a frame. + explicit FrameParts(const Http2FrameHeader& header); + + // For use in tests where the expected frame has a variable size payload. + FrameParts(const Http2FrameHeader& header, Http2StringPiece payload); + + // For use in tests where the expected frame has a variable size payload + // and may be padded. + FrameParts(const Http2FrameHeader& header, + Http2StringPiece payload, + size_t total_pad_length); + + // Copy constructor. + FrameParts(const FrameParts& header); + + ~FrameParts() override; + + // Returns AssertionSuccess() if they're equal, else AssertionFailure() + // with info about the difference. + ::testing::AssertionResult VerifyEquals(const FrameParts& other) const; + + // Format this FrameParts object. + void OutputTo(std::ostream& out) const; + + // Set the total padding length (0 to 256). + void SetTotalPadLength(size_t total_pad_length); + + // Set the origin and value expected in an ALTSVC frame. + void SetAltSvcExpected(Http2StringPiece origin, Http2StringPiece value); + + // Http2FrameDecoderListener methods: + bool OnFrameHeader(const Http2FrameHeader& header) override; + void OnDataStart(const Http2FrameHeader& header) override; + void OnDataPayload(const char* data, size_t len) override; + void OnDataEnd() override; + void OnHeadersStart(const Http2FrameHeader& header) override; + void OnHeadersPriority(const Http2PriorityFields& priority) override; + void OnHpackFragment(const char* data, size_t len) override; + void OnHeadersEnd() override; + void OnPriorityFrame(const Http2FrameHeader& header, + const Http2PriorityFields& priority) override; + void OnContinuationStart(const Http2FrameHeader& header) override; + void OnContinuationEnd() override; + void OnPadLength(size_t trailing_length) override; + void OnPadding(const char* pad, size_t skipped_length) override; + void OnRstStream(const Http2FrameHeader& header, + Http2ErrorCode error_code) override; + void OnSettingsStart(const Http2FrameHeader& header) override; + void OnSetting(const Http2SettingFields& setting_fields) override; + void OnSettingsEnd() override; + void OnSettingsAck(const Http2FrameHeader& header) override; + void OnPushPromiseStart(const Http2FrameHeader& header, + const Http2PushPromiseFields& promise, + size_t total_padding_length) override; + void OnPushPromiseEnd() override; + void OnPing(const Http2FrameHeader& header, + const Http2PingFields& ping) override; + void OnPingAck(const Http2FrameHeader& header, + const Http2PingFields& ping) override; + void OnGoAwayStart(const Http2FrameHeader& header, + const Http2GoAwayFields& goaway) override; + void OnGoAwayOpaqueData(const char* data, size_t len) override; + void OnGoAwayEnd() override; + void OnWindowUpdate(const Http2FrameHeader& header, + uint32_t increment) override; + void OnAltSvcStart(const Http2FrameHeader& header, + size_t origin_length, + size_t value_length) override; + void OnAltSvcOriginData(const char* data, size_t len) override; + void OnAltSvcValueData(const char* data, size_t len) override; + void OnAltSvcEnd() override; + void OnUnknownStart(const Http2FrameHeader& header) override; + void OnUnknownPayload(const char* data, size_t len) override; + void OnUnknownEnd() override; + void OnPaddingTooLong(const Http2FrameHeader& header, + size_t missing_length) override; + void OnFrameSizeError(const Http2FrameHeader& header) override; + + void AppendSetting(const Http2SettingFields& setting_fields) { + settings_.push_back(setting_fields); + } + + const Http2FrameHeader& GetFrameHeader() const { return frame_header_; } + + Http2Optional<Http2PriorityFields> GetOptPriority() const { + return opt_priority_; + } + Http2Optional<Http2ErrorCode> GetOptRstStreamErrorCode() const { + return opt_rst_stream_error_code_; + } + Http2Optional<Http2PushPromiseFields> GetOptPushPromise() const { + return opt_push_promise_; + } + Http2Optional<Http2PingFields> GetOptPing() const { return opt_ping_; } + Http2Optional<Http2GoAwayFields> GetOptGoaway() const { return opt_goaway_; } + Http2Optional<size_t> GetOptPadLength() const { return opt_pad_length_; } + Http2Optional<size_t> GetOptPayloadLength() const { + return opt_payload_length_; + } + Http2Optional<size_t> GetOptMissingLength() const { + return opt_missing_length_; + } + Http2Optional<size_t> GetOptAltsvcOriginLength() const { + return opt_altsvc_origin_length_; + } + Http2Optional<size_t> GetOptAltsvcValueLength() const { + return opt_altsvc_value_length_; + } + Http2Optional<size_t> GetOptWindowUpdateIncrement() const { + return opt_window_update_increment_; + } + bool GetHasFrameSizeError() const { return has_frame_size_error_; } + + void SetOptPriority(Http2Optional<Http2PriorityFields> opt_priority) { + opt_priority_ = opt_priority; + } + void SetOptRstStreamErrorCode( + Http2Optional<Http2ErrorCode> opt_rst_stream_error_code) { + opt_rst_stream_error_code_ = opt_rst_stream_error_code; + } + void SetOptPushPromise( + Http2Optional<Http2PushPromiseFields> opt_push_promise) { + opt_push_promise_ = opt_push_promise; + } + void SetOptPing(Http2Optional<Http2PingFields> opt_ping) { + opt_ping_ = opt_ping; + } + void SetOptGoaway(Http2Optional<Http2GoAwayFields> opt_goaway) { + opt_goaway_ = opt_goaway; + } + void SetOptPadLength(Http2Optional<size_t> opt_pad_length) { + opt_pad_length_ = opt_pad_length; + } + void SetOptPayloadLength(Http2Optional<size_t> opt_payload_length) { + opt_payload_length_ = opt_payload_length; + } + void SetOptMissingLength(Http2Optional<size_t> opt_missing_length) { + opt_missing_length_ = opt_missing_length; + } + void SetOptAltsvcOriginLength( + Http2Optional<size_t> opt_altsvc_origin_length) { + opt_altsvc_origin_length_ = opt_altsvc_origin_length; + } + void SetOptAltsvcValueLength(Http2Optional<size_t> opt_altsvc_value_length) { + opt_altsvc_value_length_ = opt_altsvc_value_length; + } + void SetOptWindowUpdateIncrement( + Http2Optional<size_t> opt_window_update_increment) { + opt_window_update_increment_ = opt_window_update_increment; + } + + void SetHasFrameSizeError(bool has_frame_size_error) { + has_frame_size_error_ = has_frame_size_error; + } + + private: + // ASSERT during an On* method that we're handling a frame of type + // expected_frame_type, and have not already received other On* methods + // (i.e. got_start_callback is false). + ::testing::AssertionResult StartFrameOfType( + const Http2FrameHeader& header, + Http2FrameType expected_frame_type); + + // ASSERT that StartFrameOfType has already been called with + // expected_frame_type (i.e. got_start_callback has been called), and that + // EndFrameOfType has not yet been called (i.e. got_end_callback is false). + ::testing::AssertionResult InFrameOfType(Http2FrameType expected_frame_type); + + // ASSERT that we're InFrameOfType, and then sets got_end_callback=true. + ::testing::AssertionResult EndFrameOfType(Http2FrameType expected_frame_type); + + // ASSERT that we're in the middle of processing a frame that is padded. + ::testing::AssertionResult InPaddedFrame(); + + // Append source to target. If opt_length is not nullptr, then verifies that + // the optional has a value (i.e. that the necessary On*Start method has been + // called), and that target is not longer than opt_length->value(). + ::testing::AssertionResult AppendString(Http2StringPiece source, + Http2String* target, + Http2Optional<size_t>* opt_length); + + const Http2FrameHeader frame_header_; + + Http2String payload_; + Http2String padding_; + Http2String altsvc_origin_; + Http2String altsvc_value_; + + Http2Optional<Http2PriorityFields> opt_priority_; + Http2Optional<Http2ErrorCode> opt_rst_stream_error_code_; + Http2Optional<Http2PushPromiseFields> opt_push_promise_; + Http2Optional<Http2PingFields> opt_ping_; + Http2Optional<Http2GoAwayFields> opt_goaway_; + + Http2Optional<size_t> opt_pad_length_; + Http2Optional<size_t> opt_payload_length_; + Http2Optional<size_t> opt_missing_length_; + Http2Optional<size_t> opt_altsvc_origin_length_; + Http2Optional<size_t> opt_altsvc_value_length_; + + Http2Optional<size_t> opt_window_update_increment_; + + bool has_frame_size_error_ = false; + + std::vector<Http2SettingFields> settings_; + + // These booleans are not checked by CompareCollectedFrames. + bool got_start_callback_ = false; + bool got_end_callback_ = false; +}; + +std::ostream& operator<<(std::ostream& out, const FrameParts& v); + +} // namespace test +} // namespace http2 + +#endif // QUICHE_HTTP2_TEST_TOOLS_FRAME_PARTS_H_ diff --git a/chromium/net/third_party/quiche/src/http2/test_tools/frame_parts_collector.cc b/chromium/net/third_party/quiche/src/http2/test_tools/frame_parts_collector.cc new file mode 100644 index 00000000000..be4d986259f --- /dev/null +++ b/chromium/net/third_party/quiche/src/http2/test_tools/frame_parts_collector.cc @@ -0,0 +1,113 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/http2/test_tools/frame_parts_collector.h" + +#include <utility> + +#include "base/logging.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "net/third_party/quiche/src/http2/http2_structures_test_util.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_ptr_util.h" + +namespace http2 { +namespace test { + +FramePartsCollector::FramePartsCollector() = default; +FramePartsCollector::~FramePartsCollector() = default; + +void FramePartsCollector::Reset() { + current_frame_.reset(); + collected_frames_.clear(); + expected_header_set_ = false; +} + +const FrameParts* FramePartsCollector::frame(size_t n) const { + if (n < size()) { + return collected_frames_.at(n).get(); + } + CHECK(n == size()); + return current_frame(); +} + +void FramePartsCollector::ExpectFrameHeader(const Http2FrameHeader& header) { + EXPECT_FALSE(IsInProgress()); + EXPECT_FALSE(expected_header_set_) + << "expected_header_: " << expected_header_; + expected_header_ = header; + expected_header_set_ = true; + // OnFrameHeader is called before the flags are scrubbed, but the other + // methods are called after, so scrub the invalid flags from expected_header_. + ScrubFlagsOfHeader(&expected_header_); +} + +void FramePartsCollector::TestExpectedHeader(const Http2FrameHeader& header) { + if (expected_header_set_) { + EXPECT_EQ(header, expected_header_); + expected_header_set_ = false; + } +} + +Http2FrameDecoderListener* FramePartsCollector::StartFrame( + const Http2FrameHeader& header) { + TestExpectedHeader(header); + EXPECT_FALSE(IsInProgress()); + if (current_frame_ == nullptr) { + current_frame_ = Http2MakeUnique<FrameParts>(header); + } + return current_frame(); +} + +Http2FrameDecoderListener* FramePartsCollector::StartAndEndFrame( + const Http2FrameHeader& header) { + TestExpectedHeader(header); + EXPECT_FALSE(IsInProgress()); + if (current_frame_ == nullptr) { + current_frame_ = Http2MakeUnique<FrameParts>(header); + } + Http2FrameDecoderListener* result = current_frame(); + collected_frames_.push_back(std::move(current_frame_)); + return result; +} + +Http2FrameDecoderListener* FramePartsCollector::CurrentFrame() { + EXPECT_TRUE(IsInProgress()); + if (current_frame_ == nullptr) { + return &failing_listener_; + } + return current_frame(); +} + +Http2FrameDecoderListener* FramePartsCollector::EndFrame() { + EXPECT_TRUE(IsInProgress()); + if (current_frame_ == nullptr) { + return &failing_listener_; + } + Http2FrameDecoderListener* result = current_frame(); + collected_frames_.push_back(std::move(current_frame_)); + return result; +} + +Http2FrameDecoderListener* FramePartsCollector::FrameError( + const Http2FrameHeader& header) { + TestExpectedHeader(header); + if (current_frame_ == nullptr) { + // The decoder may detect an error before making any calls to the listener + // regarding the frame, in which case current_frame_==nullptr and we need + // to create a FrameParts instance. + current_frame_ = Http2MakeUnique<FrameParts>(header); + } else { + // Similarly, the decoder may have made calls to the listener regarding the + // frame before detecting the error; for example, the DATA payload decoder + // calls OnDataStart before it can detect padding errors, hence before it + // can call OnPaddingTooLong. + EXPECT_EQ(header, current_frame_->GetFrameHeader()); + } + Http2FrameDecoderListener* result = current_frame(); + collected_frames_.push_back(std::move(current_frame_)); + return result; +} + +} // namespace test +} // namespace http2 diff --git a/chromium/net/third_party/quiche/src/http2/test_tools/frame_parts_collector.h b/chromium/net/third_party/quiche/src/http2/test_tools/frame_parts_collector.h new file mode 100644 index 00000000000..a35740a9f25 --- /dev/null +++ b/chromium/net/third_party/quiche/src/http2/test_tools/frame_parts_collector.h @@ -0,0 +1,111 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef QUICHE_HTTP2_TEST_TOOLS_FRAME_PARTS_COLLECTOR_H_ +#define QUICHE_HTTP2_TEST_TOOLS_FRAME_PARTS_COLLECTOR_H_ + +// FramePartsCollector is a base class for Http2FrameDecoderListener +// implementations that create one FrameParts instance for each decoded frame. + +#include <stddef.h> + +#include <memory> +#include <vector> + +#include "net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener.h" +#include "net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener_test_util.h" +#include "net/third_party/quiche/src/http2/http2_structures.h" +#include "net/third_party/quiche/src/http2/test_tools/frame_parts.h" + +namespace http2 { +namespace test { + +class FramePartsCollector : public FailingHttp2FrameDecoderListener { + public: + FramePartsCollector(); + ~FramePartsCollector() override; + + // Toss out the collected data. + void Reset(); + + // Returns true if has started recording the info for a frame and has not yet + // finished doing so. + bool IsInProgress() const { return current_frame_ != nullptr; } + + // Returns the FrameParts instance into which we're currently recording + // callback info if IsInProgress, else nullptr. + const FrameParts* current_frame() const { return current_frame_.get(); } + + // Returns the number of completely collected FrameParts instances. + size_t size() const { return collected_frames_.size(); } + + // Returns the n'th frame, where 0 is the oldest of the collected frames, + // and n==size() is the frame currently being collected, if there is one. + // Returns nullptr if the requested index is not valid. + const FrameParts* frame(size_t n) const; + + protected: + // In support of OnFrameHeader, set the header that we expect to be used in + // the next call. + // TODO(jamessynge): Remove ExpectFrameHeader et al. once done with supporting + // SpdyFramer's exact states. + void ExpectFrameHeader(const Http2FrameHeader& header); + + // For use in implementing On*Start methods of Http2FrameDecoderListener, + // returns a FrameParts instance, which will be newly created if + // IsInProgress==false (which the caller should ensure), else will be the + // current_frame(); never returns nullptr. + // If called when IsInProgress==true, a test failure will be recorded. + Http2FrameDecoderListener* StartFrame(const Http2FrameHeader& header); + + // For use in implementing On* callbacks, such as OnPingAck, that are the only + // call expected for the frame being decoded; not for On*Start methods. + // Returns a FrameParts instance, which will be newly created if + // IsInProgress==false (which the caller should ensure), else will be the + // current_frame(); never returns nullptr. + // If called when IsInProgress==true, a test failure will be recorded. + Http2FrameDecoderListener* StartAndEndFrame(const Http2FrameHeader& header); + + // If IsInProgress==true, returns the FrameParts into which the current + // frame is being recorded; else records a test failure and returns + // failing_listener_, which will record a test failure when any of its + // On* methods is called. + Http2FrameDecoderListener* CurrentFrame(); + + // For use in implementing On*End methods, pushes the current frame onto + // the vector of completed frames, and returns a pointer to it for recording + // the info in the final call. If IsInProgress==false, records a test failure + // and returns failing_listener_, which will record a test failure when any + // of its On* methods is called. + Http2FrameDecoderListener* EndFrame(); + + // For use in implementing OnPaddingTooLong and OnFrameSizeError, is + // equivalent to EndFrame() if IsInProgress==true, else equivalent to + // StartAndEndFrame(). + Http2FrameDecoderListener* FrameError(const Http2FrameHeader& header); + + private: + // Returns the mutable FrameParts instance into which we're currently + // recording callback info if IsInProgress, else nullptr. + FrameParts* current_frame() { return current_frame_.get(); } + + // If expected header is set, verify that it matches the header param. + // TODO(jamessynge): Remove TestExpectedHeader et al. once done + // with supporting SpdyFramer's exact states. + void TestExpectedHeader(const Http2FrameHeader& header); + + std::unique_ptr<FrameParts> current_frame_; + std::vector<std::unique_ptr<FrameParts>> collected_frames_; + FailingHttp2FrameDecoderListener failing_listener_; + + // TODO(jamessynge): Remove expected_header_ et al. once done with supporting + // SpdyFramer's exact states. + Http2FrameHeader expected_header_; + bool expected_header_set_ = false; +}; + +} // namespace test +} // namespace http2 + +#endif // QUICHE_HTTP2_TEST_TOOLS_FRAME_PARTS_COLLECTOR_H_ diff --git a/chromium/net/third_party/quiche/src/http2/test_tools/frame_parts_collector_listener.cc b/chromium/net/third_party/quiche/src/http2/test_tools/frame_parts_collector_listener.cc new file mode 100644 index 00000000000..8d9da78707b --- /dev/null +++ b/chromium/net/third_party/quiche/src/http2/test_tools/frame_parts_collector_listener.cc @@ -0,0 +1,230 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/http2/test_tools/frame_parts_collector_listener.h" + +#include "base/logging.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace http2 { +namespace test { + +bool FramePartsCollectorListener::OnFrameHeader( + const Http2FrameHeader& header) { + VLOG(1) << "OnFrameHeader: " << header; + ExpectFrameHeader(header); + return true; +} + +void FramePartsCollectorListener::OnDataStart(const Http2FrameHeader& header) { + VLOG(1) << "OnDataStart: " << header; + StartFrame(header)->OnDataStart(header); +} + +void FramePartsCollectorListener::OnDataPayload(const char* data, size_t len) { + VLOG(1) << "OnDataPayload: len=" << len; + CurrentFrame()->OnDataPayload(data, len); +} + +void FramePartsCollectorListener::OnDataEnd() { + VLOG(1) << "OnDataEnd"; + EndFrame()->OnDataEnd(); +} + +void FramePartsCollectorListener::OnHeadersStart( + const Http2FrameHeader& header) { + VLOG(1) << "OnHeadersStart: " << header; + StartFrame(header)->OnHeadersStart(header); +} + +void FramePartsCollectorListener::OnHeadersPriority( + const Http2PriorityFields& priority) { + VLOG(1) << "OnHeadersPriority: " << priority; + CurrentFrame()->OnHeadersPriority(priority); +} + +void FramePartsCollectorListener::OnHpackFragment(const char* data, + size_t len) { + VLOG(1) << "OnHpackFragment: len=" << len; + CurrentFrame()->OnHpackFragment(data, len); +} + +void FramePartsCollectorListener::OnHeadersEnd() { + VLOG(1) << "OnHeadersEnd"; + EndFrame()->OnHeadersEnd(); +} + +void FramePartsCollectorListener::OnPriorityFrame( + const Http2FrameHeader& header, + const Http2PriorityFields& priority_fields) { + VLOG(1) << "OnPriority: " << header << "; " << priority_fields; + StartAndEndFrame(header)->OnPriorityFrame(header, priority_fields); +} + +void FramePartsCollectorListener::OnContinuationStart( + const Http2FrameHeader& header) { + VLOG(1) << "OnContinuationStart: " << header; + StartFrame(header)->OnContinuationStart(header); +} + +void FramePartsCollectorListener::OnContinuationEnd() { + VLOG(1) << "OnContinuationEnd"; + EndFrame()->OnContinuationEnd(); +} + +void FramePartsCollectorListener::OnPadLength(size_t pad_length) { + VLOG(1) << "OnPadLength: " << pad_length; + CurrentFrame()->OnPadLength(pad_length); +} + +void FramePartsCollectorListener::OnPadding(const char* padding, + size_t skipped_length) { + VLOG(1) << "OnPadding: " << skipped_length; + CurrentFrame()->OnPadding(padding, skipped_length); +} + +void FramePartsCollectorListener::OnRstStream(const Http2FrameHeader& header, + Http2ErrorCode error_code) { + VLOG(1) << "OnRstStream: " << header << "; error_code=" << error_code; + StartAndEndFrame(header)->OnRstStream(header, error_code); +} + +void FramePartsCollectorListener::OnSettingsStart( + const Http2FrameHeader& header) { + VLOG(1) << "OnSettingsStart: " << header; + EXPECT_EQ(Http2FrameType::SETTINGS, header.type) << header; + EXPECT_EQ(Http2FrameFlag(), header.flags) << header; + StartFrame(header)->OnSettingsStart(header); +} + +void FramePartsCollectorListener::OnSetting( + const Http2SettingFields& setting_fields) { + VLOG(1) << "Http2SettingFields: setting_fields=" << setting_fields; + CurrentFrame()->OnSetting(setting_fields); +} + +void FramePartsCollectorListener::OnSettingsEnd() { + VLOG(1) << "OnSettingsEnd"; + EndFrame()->OnSettingsEnd(); +} + +void FramePartsCollectorListener::OnSettingsAck( + const Http2FrameHeader& header) { + VLOG(1) << "OnSettingsAck: " << header; + StartAndEndFrame(header)->OnSettingsAck(header); +} + +void FramePartsCollectorListener::OnPushPromiseStart( + const Http2FrameHeader& header, + const Http2PushPromiseFields& promise, + size_t total_padding_length) { + VLOG(1) << "OnPushPromiseStart header: " << header << " promise: " << promise + << " total_padding_length: " << total_padding_length; + EXPECT_EQ(Http2FrameType::PUSH_PROMISE, header.type); + StartFrame(header)->OnPushPromiseStart(header, promise, total_padding_length); +} + +void FramePartsCollectorListener::OnPushPromiseEnd() { + VLOG(1) << "OnPushPromiseEnd"; + EndFrame()->OnPushPromiseEnd(); +} + +void FramePartsCollectorListener::OnPing(const Http2FrameHeader& header, + const Http2PingFields& ping) { + VLOG(1) << "OnPing: " << header << "; " << ping; + StartAndEndFrame(header)->OnPing(header, ping); +} + +void FramePartsCollectorListener::OnPingAck(const Http2FrameHeader& header, + const Http2PingFields& ping) { + VLOG(1) << "OnPingAck: " << header << "; " << ping; + StartAndEndFrame(header)->OnPingAck(header, ping); +} + +void FramePartsCollectorListener::OnGoAwayStart( + const Http2FrameHeader& header, + const Http2GoAwayFields& goaway) { + VLOG(1) << "OnGoAwayStart header: " << header << "; goaway: " << goaway; + StartFrame(header)->OnGoAwayStart(header, goaway); +} + +void FramePartsCollectorListener::OnGoAwayOpaqueData(const char* data, + size_t len) { + VLOG(1) << "OnGoAwayOpaqueData: len=" << len; + CurrentFrame()->OnGoAwayOpaqueData(data, len); +} + +void FramePartsCollectorListener::OnGoAwayEnd() { + VLOG(1) << "OnGoAwayEnd"; + EndFrame()->OnGoAwayEnd(); +} + +void FramePartsCollectorListener::OnWindowUpdate( + const Http2FrameHeader& header, + uint32_t window_size_increment) { + VLOG(1) << "OnWindowUpdate: " << header + << "; window_size_increment=" << window_size_increment; + EXPECT_EQ(Http2FrameType::WINDOW_UPDATE, header.type); + StartAndEndFrame(header)->OnWindowUpdate(header, window_size_increment); +} + +void FramePartsCollectorListener::OnAltSvcStart(const Http2FrameHeader& header, + size_t origin_length, + size_t value_length) { + VLOG(1) << "OnAltSvcStart header: " << header + << "; origin_length=" << origin_length + << "; value_length=" << value_length; + StartFrame(header)->OnAltSvcStart(header, origin_length, value_length); +} + +void FramePartsCollectorListener::OnAltSvcOriginData(const char* data, + size_t len) { + VLOG(1) << "OnAltSvcOriginData: len=" << len; + CurrentFrame()->OnAltSvcOriginData(data, len); +} + +void FramePartsCollectorListener::OnAltSvcValueData(const char* data, + size_t len) { + VLOG(1) << "OnAltSvcValueData: len=" << len; + CurrentFrame()->OnAltSvcValueData(data, len); +} + +void FramePartsCollectorListener::OnAltSvcEnd() { + VLOG(1) << "OnAltSvcEnd"; + EndFrame()->OnAltSvcEnd(); +} + +void FramePartsCollectorListener::OnUnknownStart( + const Http2FrameHeader& header) { + VLOG(1) << "OnUnknownStart: " << header; + StartFrame(header)->OnUnknownStart(header); +} + +void FramePartsCollectorListener::OnUnknownPayload(const char* data, + size_t len) { + VLOG(1) << "OnUnknownPayload: len=" << len; + CurrentFrame()->OnUnknownPayload(data, len); +} + +void FramePartsCollectorListener::OnUnknownEnd() { + VLOG(1) << "OnUnknownEnd"; + EndFrame()->OnUnknownEnd(); +} + +void FramePartsCollectorListener::OnPaddingTooLong( + const Http2FrameHeader& header, + size_t missing_length) { + VLOG(1) << "OnPaddingTooLong: " << header + << " missing_length: " << missing_length; + EndFrame()->OnPaddingTooLong(header, missing_length); +} + +void FramePartsCollectorListener::OnFrameSizeError( + const Http2FrameHeader& header) { + VLOG(1) << "OnFrameSizeError: " << header; + FrameError(header)->OnFrameSizeError(header); +} + +} // namespace test +} // namespace http2 diff --git a/chromium/net/third_party/quiche/src/http2/test_tools/frame_parts_collector_listener.h b/chromium/net/third_party/quiche/src/http2/test_tools/frame_parts_collector_listener.h new file mode 100644 index 00000000000..07a82aaa806 --- /dev/null +++ b/chromium/net/third_party/quiche/src/http2/test_tools/frame_parts_collector_listener.h @@ -0,0 +1,85 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef QUICHE_HTTP2_TEST_TOOLS_FRAME_PARTS_COLLECTOR_LISTENER_H_ +#define QUICHE_HTTP2_TEST_TOOLS_FRAME_PARTS_COLLECTOR_LISTENER_H_ + +// FramePartsCollectorListener extends FramePartsCollector with an +// implementation of every method of Http2FrameDecoderListener; it is +// essentially the union of all the Listener classes in the tests of the +// payload decoders (i.e. in ./payload_decoders/*_test.cc files), with the +// addition of the OnFrameHeader method. +// FramePartsCollectorListener supports tests of Http2FrameDecoder. + +#include <stddef.h> + +#include <cstdint> + +#include "net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener.h" +#include "net/third_party/quiche/src/http2/http2_constants.h" +#include "net/third_party/quiche/src/http2/http2_structures.h" +#include "net/third_party/quiche/src/http2/test_tools/frame_parts_collector.h" + +namespace http2 { +namespace test { + +class FramePartsCollectorListener : public FramePartsCollector { + public: + FramePartsCollectorListener() {} + ~FramePartsCollectorListener() override {} + + // TODO(jamessynge): Remove OnFrameHeader once done with supporting + // SpdyFramer's exact states. + bool OnFrameHeader(const Http2FrameHeader& header) override; + void OnDataStart(const Http2FrameHeader& header) override; + void OnDataPayload(const char* data, size_t len) override; + void OnDataEnd() override; + void OnHeadersStart(const Http2FrameHeader& header) override; + void OnHeadersPriority(const Http2PriorityFields& priority) override; + void OnHpackFragment(const char* data, size_t len) override; + void OnHeadersEnd() override; + void OnPriorityFrame(const Http2FrameHeader& header, + const Http2PriorityFields& priority_fields) override; + void OnContinuationStart(const Http2FrameHeader& header) override; + void OnContinuationEnd() override; + void OnPadLength(size_t pad_length) override; + void OnPadding(const char* padding, size_t skipped_length) override; + void OnRstStream(const Http2FrameHeader& header, + Http2ErrorCode error_code) override; + void OnSettingsStart(const Http2FrameHeader& header) override; + void OnSetting(const Http2SettingFields& setting_fields) override; + void OnSettingsEnd() override; + void OnSettingsAck(const Http2FrameHeader& header) override; + void OnPushPromiseStart(const Http2FrameHeader& header, + const Http2PushPromiseFields& promise, + size_t total_padding_length) override; + void OnPushPromiseEnd() override; + void OnPing(const Http2FrameHeader& header, + const Http2PingFields& ping) override; + void OnPingAck(const Http2FrameHeader& header, + const Http2PingFields& ping) override; + void OnGoAwayStart(const Http2FrameHeader& header, + const Http2GoAwayFields& goaway) override; + void OnGoAwayOpaqueData(const char* data, size_t len) override; + void OnGoAwayEnd() override; + void OnWindowUpdate(const Http2FrameHeader& header, + uint32_t window_size_increment) override; + void OnAltSvcStart(const Http2FrameHeader& header, + size_t origin_length, + size_t value_length) override; + void OnAltSvcOriginData(const char* data, size_t len) override; + void OnAltSvcValueData(const char* data, size_t len) override; + void OnAltSvcEnd() override; + void OnUnknownStart(const Http2FrameHeader& header) override; + void OnUnknownPayload(const char* data, size_t len) override; + void OnUnknownEnd() override; + void OnPaddingTooLong(const Http2FrameHeader& header, + size_t missing_length) override; + void OnFrameSizeError(const Http2FrameHeader& header) override; +}; + +} // namespace test +} // namespace http2 + +#endif // QUICHE_HTTP2_TEST_TOOLS_FRAME_PARTS_COLLECTOR_LISTENER_H_ diff --git a/chromium/net/third_party/quiche/src/http2/test_tools/http2_random.cc b/chromium/net/third_party/quiche/src/http2/test_tools/http2_random.cc new file mode 100644 index 00000000000..fc577f467fe --- /dev/null +++ b/chromium/net/third_party/quiche/src/http2/test_tools/http2_random.cc @@ -0,0 +1,72 @@ +#include "net/third_party/quiche/src/http2/test_tools/http2_random.h" + +#include "base/logging.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string_utils.h" +#include "third_party/boringssl/src/include/openssl/chacha.h" +#include "third_party/boringssl/src/include/openssl/rand.h" + +static const uint8_t kZeroNonce[12] = {0}; + +namespace http2 { +namespace test { + +Http2Random::Http2Random() { + RAND_bytes(key_, sizeof(key_)); + + LOG(INFO) << "Initialized test RNG with the following key: " << Key(); +} + +Http2Random::Http2Random(Http2StringPiece key) { + Http2String decoded_key = Http2HexDecode(key); + CHECK_EQ(sizeof(key_), decoded_key.size()); + memcpy(key_, decoded_key.data(), sizeof(key_)); +} + +Http2String Http2Random::Key() const { + return Http2HexEncode(key_, sizeof(key_)); +} + +void Http2Random::FillRandom(void* buffer, size_t buffer_size) { + memset(buffer, 0, buffer_size); + uint8_t* buffer_u8 = reinterpret_cast<uint8_t*>(buffer); + CRYPTO_chacha_20(buffer_u8, buffer_u8, buffer_size, key_, kZeroNonce, + counter_++); +} + +Http2String Http2Random::RandString(int length) { + Http2String result; + result.resize(length); + FillRandom(&result[0], length); + return result; +} + +uint64_t Http2Random::Rand64() { + union { + uint64_t number; + uint8_t bytes[sizeof(uint64_t)]; + } result; + FillRandom(result.bytes, sizeof(result.bytes)); + return result.number; +} + +double Http2Random::RandDouble() { + union { + double f; + uint64_t i; + } value; + value.i = (1023ull << 52ull) | (Rand64() & 0xfffffffffffffu); + return value.f - 1.0; +} + +Http2String Http2Random::RandStringWithAlphabet(int length, + Http2StringPiece alphabet) { + Http2String result; + result.resize(length); + for (int i = 0; i < length; i++) { + result[i] = alphabet[Uniform(alphabet.size())]; + } + return result; +} + +} // namespace test +} // namespace http2 diff --git a/chromium/net/third_party/quiche/src/http2/test_tools/http2_random.h b/chromium/net/third_party/quiche/src/http2/test_tools/http2_random.h new file mode 100644 index 00000000000..def60f8665d --- /dev/null +++ b/chromium/net/third_party/quiche/src/http2/test_tools/http2_random.h @@ -0,0 +1,88 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef QUICHE_HTTP2_TEST_TOOLS_HTTP2_RANDOM_H_ +#define QUICHE_HTTP2_TEST_TOOLS_HTTP2_RANDOM_H_ + +#include <cmath> +#include <cstdint> +#include <limits> +#include <random> + +#include "net/third_party/quiche/src/http2/platform/api/http2_string.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string_piece.h" + +namespace http2 { +namespace test { + +// The random number generator used for unit tests. Since the algorithm is +// deterministic and fixed, this can be used to reproduce flakes in the unit +// tests caused by specific random values. +class Http2Random { + public: + Http2Random(); + + Http2Random(const Http2Random&) = delete; + Http2Random& operator=(const Http2Random&) = delete; + + // Reproducible random number generation: by using the same key, the same + // sequence of results is obtained. + explicit Http2Random(Http2StringPiece key); + Http2String Key() const; + + void FillRandom(void* buffer, size_t buffer_size); + Http2String RandString(int length); + + // Returns a random 64-bit value. + uint64_t Rand64(); + + // Return a uniformly distrubted random number in [0, n). + uint32_t Uniform(uint32_t n) { return Rand64() % n; } + // Return a uniformly distrubted random number in [lo, hi). + uint64_t UniformInRange(uint64_t lo, uint64_t hi) { + return lo + Rand64() % (hi - lo); + } + // Return an integer of logarithmically random scale. + uint32_t Skewed(uint32_t max_log) { + const uint32_t base = Rand32() % (max_log + 1); + const uint32_t mask = ((base < 32) ? (1u << base) : 0u) - 1u; + return Rand32() & mask; + } + // Return a random number in [0, max] range that skews low. + uint64_t RandomSizeSkewedLow(uint64_t max) { + return std::round(max * std::pow(RandDouble(), 2)); + } + + // Returns a random double between 0 and 1. + double RandDouble(); + float RandFloat() { return RandDouble(); } + + // Has 1/n chance of returning true. + bool OneIn(int n) { return Uniform(n) == 0; } + + uint8_t Rand8() { return Rand64(); } + uint16_t Rand16() { return Rand64(); } + uint32_t Rand32() { return Rand64(); } + + // Return a random string consisting of the characters from the specified + // alphabet. + Http2String RandStringWithAlphabet(int length, Http2StringPiece alphabet); + + // STL UniformRandomNumberGenerator implementation. + using result_type = uint64_t; + static constexpr result_type min() { return 0; } + static constexpr result_type max() { + return std::numeric_limits<result_type>::max(); + } + result_type operator()() { return Rand64(); } + + private: + uint8_t key_[32]; + uint32_t counter_ = 0; +}; + +} // namespace test +} // namespace http2 + +#endif // QUICHE_HTTP2_TEST_TOOLS_HTTP2_RANDOM_H_ diff --git a/chromium/net/third_party/quiche/src/http2/test_tools/http2_random_test.cc b/chromium/net/third_party/quiche/src/http2/test_tools/http2_random_test.cc new file mode 100644 index 00000000000..a1b74f64a7b --- /dev/null +++ b/chromium/net/third_party/quiche/src/http2/test_tools/http2_random_test.cc @@ -0,0 +1,94 @@ +#include "net/third_party/quiche/src/http2/test_tools/http2_random.h" + +#include <set> + +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace http2 { +namespace test { +namespace { + +TEST(Http2RandomTest, ProducesDifferentNumbers) { + Http2Random random; + uint64_t value1 = random.Rand64(); + uint64_t value2 = random.Rand64(); + uint64_t value3 = random.Rand64(); + + EXPECT_NE(value1, value2); + EXPECT_NE(value2, value3); + EXPECT_NE(value3, value1); +} + +TEST(Http2RandomTest, StartsWithDifferentKeys) { + Http2Random random1; + Http2Random random2; + + EXPECT_NE(random1.Key(), random2.Key()); + EXPECT_NE(random1.Rand64(), random2.Rand64()); + EXPECT_NE(random1.Rand64(), random2.Rand64()); + EXPECT_NE(random1.Rand64(), random2.Rand64()); +} + +TEST(Http2RandomTest, ReproducibleRandom) { + Http2Random random; + uint64_t value1 = random.Rand64(); + uint64_t value2 = random.Rand64(); + + Http2Random clone_random(random.Key()); + EXPECT_EQ(clone_random.Key(), random.Key()); + EXPECT_EQ(value1, clone_random.Rand64()); + EXPECT_EQ(value2, clone_random.Rand64()); +} + +TEST(Http2RandomTest, STLShuffle) { + Http2Random random; + const Http2String original = "abcdefghijklmonpqrsuvwxyz"; + + Http2String shuffled = original; + std::shuffle(shuffled.begin(), shuffled.end(), random); + EXPECT_NE(original, shuffled); +} + +TEST(Http2RandomTest, RandFloat) { + Http2Random random; + for (int i = 0; i < 10000; i++) { + float value = random.RandFloat(); + ASSERT_GE(value, 0.f); + ASSERT_LE(value, 1.f); + } +} + +TEST(Http2RandomTest, RandStringWithAlphabet) { + Http2Random random; + Http2String str = random.RandStringWithAlphabet(1000, "xyz"); + EXPECT_EQ(1000u, str.size()); + + std::set<char> characters(str.begin(), str.end()); + EXPECT_THAT(characters, testing::ElementsAre('x', 'y', 'z')); +} + +TEST(Http2RandomTest, SkewedLow) { + Http2Random random; + constexpr size_t kMax = 1234; + for (int i = 0; i < 10000; i++) { + size_t value = random.RandomSizeSkewedLow(kMax); + ASSERT_GE(value, 0u); + ASSERT_LE(value, kMax); + } +} + +// Checks that SkewedLow() generates full range. This is required, since in +// some unit tests would infinitely loop. +TEST(Http2RandomTest, SkewedLowFullRange) { + Http2Random random; + std::set<size_t> values; + for (int i = 0; i < 1000; i++) { + values.insert(random.RandomSizeSkewedLow(3)); + } + EXPECT_THAT(values, testing::ElementsAre(0, 1, 2, 3)); +} + +} // namespace +} // namespace test +} // namespace http2 diff --git a/chromium/net/third_party/quiche/src/http2/tools/http2_frame_builder.cc b/chromium/net/third_party/quiche/src/http2/tools/http2_frame_builder.cc new file mode 100644 index 00000000000..1dfcdeb0396 --- /dev/null +++ b/chromium/net/third_party/quiche/src/http2/tools/http2_frame_builder.cc @@ -0,0 +1,181 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/http2/tools/http2_frame_builder.h" + +#ifdef WIN32 +#include <winsock2.h> // for htonl() functions +#else +#include <arpa/inet.h> +#include <netinet/in.h> // for htonl, htons +#endif + +#include "testing/gtest/include/gtest/gtest.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string_utils.h" + +namespace http2 { +namespace test { + +Http2FrameBuilder::Http2FrameBuilder(Http2FrameType type, + uint8_t flags, + uint32_t stream_id) { + AppendUInt24(0); // Frame payload length, unknown so far. + Append(type); + AppendUInt8(flags); + AppendUInt31(stream_id); +} + +Http2FrameBuilder::Http2FrameBuilder(const Http2FrameHeader& v) { + Append(v); +} + +void Http2FrameBuilder::Append(Http2StringPiece s) { + Http2StrAppend(&buffer_, s); +} + +void Http2FrameBuilder::AppendBytes(const void* data, uint32_t num_bytes) { + Append(Http2StringPiece(static_cast<const char*>(data), num_bytes)); +} + +void Http2FrameBuilder::AppendZeroes(size_t num_zero_bytes) { + char zero = 0; + buffer_.append(num_zero_bytes, zero); +} + +void Http2FrameBuilder::AppendUInt8(uint8_t value) { + AppendBytes(&value, 1); +} + +void Http2FrameBuilder::AppendUInt16(uint16_t value) { + value = htons(value); + AppendBytes(&value, 2); +} + +void Http2FrameBuilder::AppendUInt24(uint32_t value) { + // Doesn't make sense to try to append a larger value, as that doesn't + // simulate something an encoder could do (i.e. the other 8 bits simply aren't + // there to be occupied). + EXPECT_EQ(value, value & 0xffffff); + value = htonl(value); + AppendBytes(reinterpret_cast<char*>(&value) + 1, 3); +} + +void Http2FrameBuilder::AppendUInt31(uint32_t value) { + // If you want to test the high-bit being set, call AppendUInt32 instead. + uint32_t tmp = value & StreamIdMask(); + EXPECT_EQ(value, value & StreamIdMask()) + << "High-bit of uint32_t should be clear."; + value = htonl(tmp); + AppendBytes(&value, 4); +} + +void Http2FrameBuilder::AppendUInt32(uint32_t value) { + value = htonl(value); + AppendBytes(&value, sizeof(value)); +} + +void Http2FrameBuilder::Append(Http2ErrorCode error_code) { + AppendUInt32(static_cast<uint32_t>(error_code)); +} + +void Http2FrameBuilder::Append(Http2FrameType type) { + AppendUInt8(static_cast<uint8_t>(type)); +} + +void Http2FrameBuilder::Append(Http2SettingsParameter parameter) { + AppendUInt16(static_cast<uint16_t>(parameter)); +} + +void Http2FrameBuilder::Append(const Http2FrameHeader& v) { + AppendUInt24(v.payload_length); + Append(v.type); + AppendUInt8(v.flags); + AppendUInt31(v.stream_id); +} + +void Http2FrameBuilder::Append(const Http2PriorityFields& v) { + // The EXCLUSIVE flag is the high-bit of the 32-bit stream dependency field. + uint32_t tmp = v.stream_dependency & StreamIdMask(); + EXPECT_EQ(tmp, v.stream_dependency); + if (v.is_exclusive) { + tmp |= 0x80000000; + } + AppendUInt32(tmp); + + // The PRIORITY frame's weight field is logically in the range [1, 256], + // but is encoded as a byte in the range [0, 255]. + ASSERT_LE(1u, v.weight); + ASSERT_LE(v.weight, 256u); + AppendUInt8(v.weight - 1); +} + +void Http2FrameBuilder::Append(const Http2RstStreamFields& v) { + Append(v.error_code); +} + +void Http2FrameBuilder::Append(const Http2SettingFields& v) { + Append(v.parameter); + AppendUInt32(v.value); +} + +void Http2FrameBuilder::Append(const Http2PushPromiseFields& v) { + AppendUInt31(v.promised_stream_id); +} + +void Http2FrameBuilder::Append(const Http2PingFields& v) { + AppendBytes(v.opaque_bytes, sizeof Http2PingFields::opaque_bytes); +} + +void Http2FrameBuilder::Append(const Http2GoAwayFields& v) { + AppendUInt31(v.last_stream_id); + Append(v.error_code); +} + +void Http2FrameBuilder::Append(const Http2WindowUpdateFields& v) { + EXPECT_NE(0u, v.window_size_increment) << "Increment must be non-zero."; + AppendUInt31(v.window_size_increment); +} + +void Http2FrameBuilder::Append(const Http2AltSvcFields& v) { + AppendUInt16(v.origin_length); +} + +// Methods for changing existing buffer contents. + +void Http2FrameBuilder::WriteAt(Http2StringPiece s, size_t offset) { + ASSERT_LE(offset, buffer_.size()); + size_t len = offset + s.size(); + if (len > buffer_.size()) { + buffer_.resize(len); + } + for (size_t ndx = 0; ndx < s.size(); ++ndx) { + buffer_[offset + ndx] = s[ndx]; + } +} + +void Http2FrameBuilder::WriteBytesAt(const void* data, + uint32_t num_bytes, + size_t offset) { + WriteAt(Http2StringPiece(static_cast<const char*>(data), num_bytes), offset); +} + +void Http2FrameBuilder::WriteUInt24At(uint32_t value, size_t offset) { + ASSERT_LT(value, static_cast<uint32_t>(1 << 24)); + value = htonl(value); + WriteBytesAt(reinterpret_cast<char*>(&value) + 1, sizeof(value) - 1, offset); +} + +void Http2FrameBuilder::SetPayloadLength(uint32_t payload_length) { + WriteUInt24At(payload_length, 0); +} + +size_t Http2FrameBuilder::SetPayloadLength() { + EXPECT_GE(size(), Http2FrameHeader::EncodedSize()); + uint32_t payload_length = size() - Http2FrameHeader::EncodedSize(); + SetPayloadLength(payload_length); + return payload_length; +} + +} // namespace test +} // namespace http2 diff --git a/chromium/net/third_party/quiche/src/http2/tools/http2_frame_builder.h b/chromium/net/third_party/quiche/src/http2/tools/http2_frame_builder.h new file mode 100644 index 00000000000..c36cb56e48a --- /dev/null +++ b/chromium/net/third_party/quiche/src/http2/tools/http2_frame_builder.h @@ -0,0 +1,101 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef QUICHE_HTTP2_TOOLS_HTTP2_FRAME_BUILDER_H_ +#define QUICHE_HTTP2_TOOLS_HTTP2_FRAME_BUILDER_H_ + +// Http2FrameBuilder builds wire-format HTTP/2 frames (or fragments thereof) +// from components. +// +// For now, this is only intended for use in tests, and thus has EXPECT* in the +// code. If desired to use it in an encoder, it will need optimization work, +// especially w.r.t memory mgmt, and the EXPECT* will need to be removed or +// replaced with DCHECKs. + +#include <stddef.h> // for size_t + +#include <cstdint> + +#include "net/third_party/quiche/src/http2/http2_constants.h" +#include "net/third_party/quiche/src/http2/http2_structures.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string_piece.h" + +namespace http2 { +namespace test { + +class Http2FrameBuilder { + public: + Http2FrameBuilder(Http2FrameType type, uint8_t flags, uint32_t stream_id); + explicit Http2FrameBuilder(const Http2FrameHeader& v); + Http2FrameBuilder() {} + ~Http2FrameBuilder() {} + + size_t size() const { return buffer_.size(); } + const Http2String& buffer() const { return buffer_; } + + //---------------------------------------------------------------------------- + // Methods for appending to the end of the buffer. + + // Append a sequence of bytes from various sources. + void Append(Http2StringPiece s); + void AppendBytes(const void* data, uint32_t num_bytes); + + // Append an array of type T[N] to the string. Intended for tests with arrays + // initialized from literals, such as: + // const char kData[] = {0x12, 0x23, ...}; + // builder.AppendBytes(kData); + template <typename T, size_t N> + void AppendBytes(T (&buf)[N]) { + AppendBytes(buf, N * sizeof(buf[0])); + } + + // Support for appending padding. Does not read or write the Pad Length field. + void AppendZeroes(size_t num_zero_bytes); + + // Append various sizes of unsigned integers. + void AppendUInt8(uint8_t value); + void AppendUInt16(uint16_t value); + void AppendUInt24(uint32_t value); + void AppendUInt31(uint32_t value); + void AppendUInt32(uint32_t value); + + // Append various enums. + void Append(Http2ErrorCode error_code); + void Append(Http2FrameType type); + void Append(Http2SettingsParameter parameter); + + // Append various structures. + void Append(const Http2FrameHeader& v); + void Append(const Http2PriorityFields& v); + void Append(const Http2RstStreamFields& v); + void Append(const Http2SettingFields& v); + void Append(const Http2PushPromiseFields& v); + void Append(const Http2PingFields& v); + void Append(const Http2GoAwayFields& v); + void Append(const Http2WindowUpdateFields& v); + void Append(const Http2AltSvcFields& v); + + // Methods for changing existing buffer contents (mostly focused on updating + // the payload length). + + void WriteAt(Http2StringPiece s, size_t offset); + void WriteBytesAt(const void* data, uint32_t num_bytes, size_t offset); + void WriteUInt24At(uint32_t value, size_t offset); + + // Set the payload length to the specified size. + void SetPayloadLength(uint32_t payload_length); + + // Sets the payload length to the size of the buffer minus the size of + // the frame header. + size_t SetPayloadLength(); + + private: + Http2String buffer_; +}; + +} // namespace test +} // namespace http2 + +#endif // QUICHE_HTTP2_TOOLS_HTTP2_FRAME_BUILDER_H_ diff --git a/chromium/net/third_party/quiche/src/http2/tools/random_decoder_test.cc b/chromium/net/third_party/quiche/src/http2/tools/random_decoder_test.cc new file mode 100644 index 00000000000..623487c6e0c --- /dev/null +++ b/chromium/net/third_party/quiche/src/http2/tools/random_decoder_test.cc @@ -0,0 +1,167 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/http2/tools/random_decoder_test.h" + +#include <stddef.h> + +#include <algorithm> +#include <memory> + +#include "base/logging.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h" +#include "net/third_party/quiche/src/http2/decoder/decode_status.h" +#include "net/third_party/quiche/src/http2/http2_constants.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_test_helpers.h" + +using ::testing::AssertionFailure; +using ::testing::AssertionResult; +using ::testing::AssertionSuccess; + +namespace http2 { +namespace test { + +RandomDecoderTest::RandomDecoderTest() = default; + +bool RandomDecoderTest::StopDecodeOnDone() { + return stop_decode_on_done_; +} + +DecodeStatus RandomDecoderTest::DecodeSegments(DecodeBuffer* original, + const SelectSize& select_size) { + DecodeStatus status = DecodeStatus::kDecodeInProgress; + bool first = true; + VLOG(2) << "DecodeSegments: input size=" << original->Remaining(); + while (first || original->HasData()) { + size_t remaining = original->Remaining(); + size_t size = + std::min(remaining, select_size(first, original->Offset(), remaining)); + DecodeBuffer db(original->cursor(), size); + VLOG(2) << "Decoding " << size << " bytes of " << remaining << " remaining"; + if (first) { + first = false; + status = StartDecoding(&db); + } else { + status = ResumeDecoding(&db); + } + // A decoder MUST consume some input (if any is available), else we could + // get stuck in infinite loops. + if (db.Offset() == 0 && db.HasData() && + status != DecodeStatus::kDecodeError) { + ADD_FAILURE() << "Decoder didn't make any progress; db.FullSize=" + << db.FullSize() + << " original.Offset=" << original->Offset(); + return DecodeStatus::kDecodeError; + } + original->AdvanceCursor(db.Offset()); + switch (status) { + case DecodeStatus::kDecodeDone: + if (original->Empty() || StopDecodeOnDone()) { + return DecodeStatus::kDecodeDone; + } + continue; + case DecodeStatus::kDecodeInProgress: + continue; + case DecodeStatus::kDecodeError: + return DecodeStatus::kDecodeError; + } + } + return status; +} + +// Decode |original| multiple times, with different segmentations, validating +// after each decode, returning on the first failure. +AssertionResult RandomDecoderTest::DecodeAndValidateSeveralWays( + DecodeBuffer* original, + bool return_non_zero_on_first, + const Validator& validator) { + const uint32_t original_remaining = original->Remaining(); + VLOG(1) << "DecodeAndValidateSeveralWays - Start, remaining = " + << original_remaining; + uint32_t first_consumed; + { + // Fast decode (no stopping unless decoder does so). + DecodeBuffer input(original->cursor(), original_remaining); + VLOG(2) << "DecodeSegmentsAndValidate with SelectRemaining"; + VERIFY_SUCCESS( + DecodeSegmentsAndValidate(&input, SelectRemaining(), validator)) + << "\nFailed with SelectRemaining; input.Offset=" << input.Offset() + << "; input.Remaining=" << input.Remaining(); + first_consumed = input.Offset(); + } + if (original_remaining <= 30) { + // Decode again, one byte at a time. + DecodeBuffer input(original->cursor(), original_remaining); + VLOG(2) << "DecodeSegmentsAndValidate with SelectOne"; + VERIFY_SUCCESS(DecodeSegmentsAndValidate(&input, SelectOne(), validator)) + << "\nFailed with SelectOne; input.Offset=" << input.Offset() + << "; input.Remaining=" << input.Remaining(); + VERIFY_EQ(first_consumed, input.Offset()) << "\nFailed with SelectOne"; + } + if (original_remaining <= 20) { + // Decode again, one or zero bytes at a time. + DecodeBuffer input(original->cursor(), original_remaining); + VLOG(2) << "DecodeSegmentsAndValidate with SelectZeroAndOne"; + VERIFY_SUCCESS(DecodeSegmentsAndValidate( + &input, SelectZeroAndOne(return_non_zero_on_first), validator)) + << "\nFailed with SelectZeroAndOne"; + VERIFY_EQ(first_consumed, input.Offset()) + << "\nFailed with SelectZeroAndOne; input.Offset=" << input.Offset() + << "; input.Remaining=" << input.Remaining(); + } + { + // Decode again, with randomly selected segment sizes. + DecodeBuffer input(original->cursor(), original_remaining); + VLOG(2) << "DecodeSegmentsAndValidate with SelectRandom"; + VERIFY_SUCCESS(DecodeSegmentsAndValidate( + &input, SelectRandom(return_non_zero_on_first), validator)) + << "\nFailed with SelectRandom; input.Offset=" << input.Offset() + << "; input.Remaining=" << input.Remaining(); + VERIFY_EQ(first_consumed, input.Offset()) << "\nFailed with SelectRandom"; + } + VERIFY_EQ(original_remaining, original->Remaining()); + original->AdvanceCursor(first_consumed); + VLOG(1) << "DecodeAndValidateSeveralWays - SUCCESS"; + return ::testing::AssertionSuccess(); +} + +// static +RandomDecoderTest::SelectSize RandomDecoderTest::SelectZeroAndOne( + bool return_non_zero_on_first) { + std::shared_ptr<bool> zero_next(new bool); + *zero_next = !return_non_zero_on_first; + return [zero_next](bool first, size_t offset, size_t remaining) -> size_t { + if (*zero_next) { + *zero_next = false; + return 0; + } else { + *zero_next = true; + return 1; + } + }; +} + +RandomDecoderTest::SelectSize RandomDecoderTest::SelectRandom( + bool return_non_zero_on_first) { + return [this, return_non_zero_on_first](bool first, size_t offset, + size_t remaining) -> size_t { + uint32_t r = random_.Rand32(); + if (first && return_non_zero_on_first) { + CHECK_LT(0u, remaining); + if (remaining == 1) { + return 1; + } + return 1 + (r % remaining); // size in range [1, remaining). + } + return r % (remaining + 1); // size in range [0, remaining]. + }; +} + +uint32_t RandomDecoderTest::RandStreamId() { + return random_.Rand32() & StreamIdMask(); +} + +} // namespace test +} // namespace http2 diff --git a/chromium/net/third_party/quiche/src/http2/tools/random_decoder_test.h b/chromium/net/third_party/quiche/src/http2/tools/random_decoder_test.h new file mode 100644 index 00000000000..36f319605f5 --- /dev/null +++ b/chromium/net/third_party/quiche/src/http2/tools/random_decoder_test.h @@ -0,0 +1,258 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef QUICHE_HTTP2_TOOLS_RANDOM_DECODER_TEST_H_ +#define QUICHE_HTTP2_TOOLS_RANDOM_DECODER_TEST_H_ + +// RandomDecoderTest is a base class for tests of decoding various kinds +// of HTTP/2 and HPACK encodings. + +// TODO(jamessynge): Move more methods into .cc file. + +#include <stddef.h> + +#include <cstdint> +#include <functional> +#include <memory> +#include <type_traits> + +#include "base/logging.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h" +#include "net/third_party/quiche/src/http2/decoder/decode_status.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_string_piece.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_test_helpers.h" +#include "net/third_party/quiche/src/http2/test_tools/http2_random.h" + +namespace http2 { +namespace test { + +// Some helpers. + +template <typename T, size_t N> +Http2StringPiece ToStringPiece(T (&data)[N]) { + return Http2StringPiece(reinterpret_cast<const char*>(data), N * sizeof(T)); +} + +// Overwrite the enum with some random value, probably not a valid value for +// the enum type, but which fits into its storage. +template <typename T, + typename E = typename std::enable_if<std::is_enum<T>::value>::type> +void CorruptEnum(T* out, Http2Random* rng) { + // Per cppreference.com, if the destination type of a static_cast is + // smaller than the source type (i.e. type of r and uint32 below), the + // resulting value is the smallest unsigned value equal to the source value + // modulo 2^n, where n is the number of bits used to represent the + // destination type unsigned U. + using underlying_type_T = typename std::underlying_type<T>::type; + using unsigned_underlying_type_T = + typename std::make_unsigned<underlying_type_T>::type; + auto r = static_cast<unsigned_underlying_type_T>(rng->Rand32()); + *out = static_cast<T>(r); +} + +// Base class for tests of the ability to decode a sequence of bytes with +// various boundaries between the DecodeBuffers provided to the decoder. +class RandomDecoderTest : public ::testing::Test { + public: + // SelectSize returns the size of the next DecodeBuffer to be passed to the + // decoder. Note that RandomDecoderTest allows that size to be zero, though + // some decoders can't deal with that on the first byte, hence the |first| + // parameter. + typedef std::function<size_t(bool first, size_t offset, size_t remaining)> + SelectSize; + + // Validator returns an AssertionResult so test can do: + // EXPECT_THAT(DecodeAndValidate(..., validator)); + typedef ::testing::AssertionResult AssertionResult; + typedef std::function<AssertionResult(const DecodeBuffer& input, + DecodeStatus status)> + Validator; + typedef std::function<AssertionResult()> NoArgValidator; + + RandomDecoderTest(); + + protected: + // TODO(jamessynge): Modify StartDecoding, etc. to (somehow) return + // AssertionResult so that the VERIFY_* methods exported from + // gunit_helpers.h can be widely used. + + // Start decoding; call allows sub-class to Reset the decoder, or deal with + // the first byte if that is done in a unique fashion. Might be called with + // a zero byte buffer. + virtual DecodeStatus StartDecoding(DecodeBuffer* db) = 0; + + // Resume decoding of the input after a prior call to StartDecoding, and + // possibly many calls to ResumeDecoding. + virtual DecodeStatus ResumeDecoding(DecodeBuffer* db) = 0; + + // Return true if a decode status of kDecodeDone indicates that + // decoding should stop. + virtual bool StopDecodeOnDone(); + + // Decode buffer |original| until we run out of input, or kDecodeDone is + // returned by the decoder AND StopDecodeOnDone() returns true. Segments + // (i.e. cuts up) the original DecodeBuffer into (potentially) smaller buffers + // by calling |select_size| to decide how large each buffer should be. + // We do this to test the ability to deal with arbitrary boundaries, as might + // happen in transport. + // Returns the final DecodeStatus. + DecodeStatus DecodeSegments(DecodeBuffer* original, + const SelectSize& select_size); + + // Decode buffer |original| until we run out of input, or kDecodeDone is + // returned by the decoder AND StopDecodeOnDone() returns true. Segments + // (i.e. cuts up) the original DecodeBuffer into (potentially) smaller buffers + // by calling |select_size| to decide how large each buffer should be. + // We do this to test the ability to deal with arbitrary boundaries, as might + // happen in transport. + // Invokes |validator| with the final decode status and the original decode + // buffer, with the cursor advanced as far as has been consumed by the decoder + // and returns validator's result. + ::testing::AssertionResult DecodeSegmentsAndValidate( + DecodeBuffer* original, + const SelectSize& select_size, + const Validator& validator) { + DecodeStatus status = DecodeSegments(original, select_size); + VERIFY_AND_RETURN_SUCCESS(validator(*original, status)); + } + + // Returns a SelectSize function for fast decoding, i.e. passing all that + // is available to the decoder. + static SelectSize SelectRemaining() { + return [](bool first, size_t offset, size_t remaining) -> size_t { + return remaining; + }; + } + + // Returns a SelectSize function for decoding a single byte at a time. + static SelectSize SelectOne() { + return + [](bool first, size_t offset, size_t remaining) -> size_t { return 1; }; + } + + // Returns a SelectSize function for decoding a single byte at a time, where + // zero byte buffers are also allowed. Alternates between zero and one. + static SelectSize SelectZeroAndOne(bool return_non_zero_on_first); + + // Returns a SelectSize function for decoding random sized segments. + SelectSize SelectRandom(bool return_non_zero_on_first); + + // Decode |original| multiple times, with different segmentations of the + // decode buffer, validating after each decode, and confirming that they + // each decode the same amount. Returns on the first failure, else returns + // success. + AssertionResult DecodeAndValidateSeveralWays(DecodeBuffer* original, + bool return_non_zero_on_first, + const Validator& validator); + + static Validator ToValidator(std::nullptr_t) { + return [](const DecodeBuffer& input, DecodeStatus status) { + return ::testing::AssertionSuccess(); + }; + } + + static Validator ToValidator(const Validator& validator) { + if (validator == nullptr) { + return ToValidator(nullptr); + } + return validator; + } + + static Validator ToValidator(const NoArgValidator& validator) { + if (validator == nullptr) { + return ToValidator(nullptr); + } + return [validator](const DecodeBuffer& input, DecodeStatus status) { + return validator(); + }; + } + + // Wraps a validator with another validator + // that first checks that the DecodeStatus is kDecodeDone and + // that the DecodeBuffer is empty. + // TODO(jamessynge): Replace this overload with the next, as using this method + // usually means that the wrapped function doesn't need to be passed the + // DecodeBuffer nor the DecodeStatus. + static Validator ValidateDoneAndEmpty(const Validator& wrapped) { + return [wrapped](const DecodeBuffer& input, + DecodeStatus status) -> AssertionResult { + VERIFY_EQ(status, DecodeStatus::kDecodeDone); + VERIFY_EQ(0u, input.Remaining()) << "\nOffset=" << input.Offset(); + if (wrapped) { + return wrapped(input, status); + } + return ::testing::AssertionSuccess(); + }; + } + static Validator ValidateDoneAndEmpty(NoArgValidator wrapped) { + return [wrapped](const DecodeBuffer& input, + DecodeStatus status) -> AssertionResult { + VERIFY_EQ(status, DecodeStatus::kDecodeDone); + VERIFY_EQ(0u, input.Remaining()) << "\nOffset=" << input.Offset(); + if (wrapped) { + return wrapped(); + } + return ::testing::AssertionSuccess(); + }; + } + static Validator ValidateDoneAndEmpty() { + NoArgValidator validator; + return ValidateDoneAndEmpty(validator); + } + + // Wraps a validator with another validator + // that first checks that the DecodeStatus is kDecodeDone and + // that the DecodeBuffer has the expected offset. + // TODO(jamessynge): Replace this overload with the next, as using this method + // usually means that the wrapped function doesn't need to be passed the + // DecodeBuffer nor the DecodeStatus. + static Validator ValidateDoneAndOffset(uint32_t offset, + const Validator& wrapped) { + return [wrapped, offset](const DecodeBuffer& input, + DecodeStatus status) -> AssertionResult { + VERIFY_EQ(status, DecodeStatus::kDecodeDone); + VERIFY_EQ(offset, input.Offset()) << "\nRemaining=" << input.Remaining(); + if (wrapped) { + return wrapped(input, status); + } + return ::testing::AssertionSuccess(); + }; + } + static Validator ValidateDoneAndOffset(uint32_t offset, + NoArgValidator wrapped) { + return [wrapped, offset](const DecodeBuffer& input, + DecodeStatus status) -> AssertionResult { + VERIFY_EQ(status, DecodeStatus::kDecodeDone); + VERIFY_EQ(offset, input.Offset()) << "\nRemaining=" << input.Remaining(); + if (wrapped) { + return wrapped(); + } + return ::testing::AssertionSuccess(); + }; + } + static Validator ValidateDoneAndOffset(uint32_t offset) { + NoArgValidator validator; + return ValidateDoneAndOffset(offset, validator); + } + + // Expose |random_| as Http2Random so callers don't have to care about which + // sub-class of Http2Random is used, nor can they rely on the specific + // sub-class that RandomDecoderTest uses. + Http2Random& Random() { return random_; } + Http2Random* RandomPtr() { return &random_; } + + uint32_t RandStreamId(); + + bool stop_decode_on_done_ = true; + + private: + Http2Random random_; +}; + +} // namespace test +} // namespace http2 + +#endif // QUICHE_HTTP2_TOOLS_RANDOM_DECODER_TEST_H_ diff --git a/chromium/net/third_party/quiche/src/http2/tools/random_util.cc b/chromium/net/third_party/quiche/src/http2/tools/random_util.cc new file mode 100644 index 00000000000..82c3edd4063 --- /dev/null +++ b/chromium/net/third_party/quiche/src/http2/tools/random_util.cc @@ -0,0 +1,39 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/http2/tools/random_util.h" + +#include <cmath> + +namespace http2 { +namespace test { + +// Here "word" means something that starts with a lower-case letter, and has +// zero or more additional characters that are numbers or lower-case letters. +Http2String GenerateHttp2HeaderName(size_t len, Http2Random* rng) { + Http2StringPiece alpha_lc = "abcdefghijklmnopqrstuvwxyz"; + // If the name is short, just make it one word. + if (len < 8) { + return rng->RandStringWithAlphabet(len, alpha_lc); + } + // If the name is longer, ensure it starts with a word, and after that may + // have any character in alphanumdash_lc. 4 is arbitrary, could be as low + // as 1. + Http2StringPiece alphanumdash_lc = "abcdefghijklmnopqrstuvwxyz0123456789-"; + return rng->RandStringWithAlphabet(4, alpha_lc) + + rng->RandStringWithAlphabet(len - 4, alphanumdash_lc); +} + +Http2String GenerateWebSafeString(size_t len, Http2Random* rng) { + static const char* kWebsafe64 = + "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_"; + return rng->RandStringWithAlphabet(len, kWebsafe64); +} + +Http2String GenerateWebSafeString(size_t lo, size_t hi, Http2Random* rng) { + return GenerateWebSafeString(rng->UniformInRange(lo, hi), rng); +} + +} // namespace test +} // namespace http2 diff --git a/chromium/net/third_party/quiche/src/http2/tools/random_util.h b/chromium/net/third_party/quiche/src/http2/tools/random_util.h new file mode 100644 index 00000000000..a2107b7c7c9 --- /dev/null +++ b/chromium/net/third_party/quiche/src/http2/tools/random_util.h @@ -0,0 +1,29 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef QUICHE_HTTP2_TOOLS_RANDOM_UTIL_H_ +#define QUICHE_HTTP2_TOOLS_RANDOM_UTIL_H_ + +#include <stddef.h> + +#include "net/third_party/quiche/src/http2/platform/api/http2_string.h" +#include "net/third_party/quiche/src/http2/test_tools/http2_random.h" + +namespace http2 { +namespace test { + +// Generate a string with the allowed character set for HTTP/2 / HPACK header +// names. +Http2String GenerateHttp2HeaderName(size_t len, Http2Random* rng); + +// Generate a string with the web-safe string character set of specified len. +Http2String GenerateWebSafeString(size_t len, Http2Random* rng); + +// Generate a string with the web-safe string character set of length [lo, hi). +Http2String GenerateWebSafeString(size_t lo, size_t hi, Http2Random* rng); + +} // namespace test +} // namespace http2 + +#endif // QUICHE_HTTP2_TOOLS_RANDOM_UTIL_H_ diff --git a/chromium/net/third_party/quiche/src/spdy/core/array_output_buffer.cc b/chromium/net/third_party/quiche/src/spdy/core/array_output_buffer.cc new file mode 100644 index 00000000000..ea82eb0006f --- /dev/null +++ b/chromium/net/third_party/quiche/src/spdy/core/array_output_buffer.cc @@ -0,0 +1,23 @@ +// 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/spdy/core/array_output_buffer.h" + +namespace spdy { + +void ArrayOutputBuffer::Next(char** data, int* size) { + *data = current_; + *size = capacity_ > 0 ? capacity_ : 0; +} + +void ArrayOutputBuffer::AdvanceWritePtr(int64_t count) { + current_ += count; + capacity_ -= count; +} + +uint64_t ArrayOutputBuffer::BytesFree() const { + return capacity_; +} + +} // namespace spdy diff --git a/chromium/net/third_party/quiche/src/spdy/core/array_output_buffer.h b/chromium/net/third_party/quiche/src/spdy/core/array_output_buffer.h new file mode 100644 index 00000000000..72303f18aad --- /dev/null +++ b/chromium/net/third_party/quiche/src/spdy/core/array_output_buffer.h @@ -0,0 +1,45 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef QUICHE_SPDY_CORE_ARRAY_OUTPUT_BUFFER_H_ +#define QUICHE_SPDY_CORE_ARRAY_OUTPUT_BUFFER_H_ + +#include <cstddef> +#include "net/third_party/quiche/src/spdy/core/zero_copy_output_buffer.h" + +namespace spdy { + +class ArrayOutputBuffer : public ZeroCopyOutputBuffer { + public: + // |buffer| is pointed to the output to write to, and |size| is the capacity + // of the output. + ArrayOutputBuffer(char* buffer, int64_t size) + : current_(buffer), begin_(buffer), capacity_(size) {} + ~ArrayOutputBuffer() override {} + + ArrayOutputBuffer(const ArrayOutputBuffer&) = delete; + ArrayOutputBuffer& operator=(const ArrayOutputBuffer&) = delete; + + void Next(char** data, int* size) override; + void AdvanceWritePtr(int64_t count) override; + uint64_t BytesFree() const override; + + size_t Size() const { return current_ - begin_; } + char* Begin() const { return begin_; } + + // Resets the buffer to its original state. + void Reset() { + capacity_ += Size(); + current_ = begin_; + } + + private: + char* current_ = nullptr; + char* begin_ = nullptr; + uint64_t capacity_ = 0; +}; + +} // namespace spdy + +#endif // QUICHE_SPDY_CORE_ARRAY_OUTPUT_BUFFER_H_ diff --git a/chromium/net/third_party/quiche/src/spdy/core/array_output_buffer_test.cc b/chromium/net/third_party/quiche/src/spdy/core/array_output_buffer_test.cc new file mode 100644 index 00000000000..369778d8537 --- /dev/null +++ b/chromium/net/third_party/quiche/src/spdy/core/array_output_buffer_test.cc @@ -0,0 +1,50 @@ +// 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/spdy/core/array_output_buffer.h" + +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace spdy { +namespace test { + +// This test verifies that ArrayOutputBuffer is initialized properly. +TEST(ArrayOutputBufferTest, InitializedFromArray) { + char array[100]; + ArrayOutputBuffer buffer(array, sizeof(array)); + EXPECT_EQ(sizeof(array), buffer.BytesFree()); + EXPECT_EQ(0u, buffer.Size()); + EXPECT_EQ(array, buffer.Begin()); +} + +// This test verifies that Reset() causes an ArrayOutputBuffer's capacity and +// size to be reset to the initial state. +TEST(ArrayOutputBufferTest, WriteAndReset) { + char array[100]; + ArrayOutputBuffer buffer(array, sizeof(array)); + + // Let's write some bytes. + char* dst; + int size; + buffer.Next(&dst, &size); + ASSERT_GT(size, 1); + ASSERT_NE(nullptr, dst); + const int64_t written = size / 2; + memset(dst, 'x', written); + buffer.AdvanceWritePtr(written); + + // The buffer should be partially used. + EXPECT_EQ(static_cast<uint64_t>(size) - written, buffer.BytesFree()); + EXPECT_EQ(static_cast<uint64_t>(written), buffer.Size()); + + buffer.Reset(); + + // After a reset, the buffer should regain its full capacity. + EXPECT_EQ(sizeof(array), buffer.BytesFree()); + EXPECT_EQ(0u, buffer.Size()); +} + +} // namespace test +} // namespace spdy diff --git a/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_constants.cc b/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_constants.cc new file mode 100644 index 00000000000..9c1498a9d2e --- /dev/null +++ b/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_constants.cc @@ -0,0 +1,386 @@ +// Copyright 2014 The Chromium 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/spdy/core/hpack/hpack_constants.h" + +#include <cstddef> +#include <memory> +#include <vector> + +#include "base/logging.h" +#include "net/third_party/quiche/src/spdy/core/hpack/hpack_huffman_table.h" +#include "net/third_party/quiche/src/spdy/core/hpack/hpack_static_table.h" +#include "net/third_party/quiche/src/spdy/platform/api/spdy_arraysize.h" + +namespace spdy { + +// Produced by applying the python program [1] with tables provided by [2] +// (inserted into the source of the python program) and copy-paste them into +// this file. +// +// [1] net/tools/build_hpack_constants.py in Chromium +// [2] http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-08 + +// HpackHuffmanSymbol entries are initialized as {code, length, id}. +// Codes are specified in the |length| most-significant bits of |code|. +const std::vector<HpackHuffmanSymbol>& HpackHuffmanCodeVector() { + static const auto* kHpackHuffmanCode = new std::vector<HpackHuffmanSymbol>{ + {0xffc00000ul, 13, 0}, // 11111111|11000 + {0xffffb000ul, 23, 1}, // 11111111|11111111|1011000 + {0xfffffe20ul, 28, 2}, // 11111111|11111111|11111110|0010 + {0xfffffe30ul, 28, 3}, // 11111111|11111111|11111110|0011 + {0xfffffe40ul, 28, 4}, // 11111111|11111111|11111110|0100 + {0xfffffe50ul, 28, 5}, // 11111111|11111111|11111110|0101 + {0xfffffe60ul, 28, 6}, // 11111111|11111111|11111110|0110 + {0xfffffe70ul, 28, 7}, // 11111111|11111111|11111110|0111 + {0xfffffe80ul, 28, 8}, // 11111111|11111111|11111110|1000 + {0xffffea00ul, 24, 9}, // 11111111|11111111|11101010 + {0xfffffff0ul, 30, 10}, // 11111111|11111111|11111111|111100 + {0xfffffe90ul, 28, 11}, // 11111111|11111111|11111110|1001 + {0xfffffea0ul, 28, 12}, // 11111111|11111111|11111110|1010 + {0xfffffff4ul, 30, 13}, // 11111111|11111111|11111111|111101 + {0xfffffeb0ul, 28, 14}, // 11111111|11111111|11111110|1011 + {0xfffffec0ul, 28, 15}, // 11111111|11111111|11111110|1100 + {0xfffffed0ul, 28, 16}, // 11111111|11111111|11111110|1101 + {0xfffffee0ul, 28, 17}, // 11111111|11111111|11111110|1110 + {0xfffffef0ul, 28, 18}, // 11111111|11111111|11111110|1111 + {0xffffff00ul, 28, 19}, // 11111111|11111111|11111111|0000 + {0xffffff10ul, 28, 20}, // 11111111|11111111|11111111|0001 + {0xffffff20ul, 28, 21}, // 11111111|11111111|11111111|0010 + {0xfffffff8ul, 30, 22}, // 11111111|11111111|11111111|111110 + {0xffffff30ul, 28, 23}, // 11111111|11111111|11111111|0011 + {0xffffff40ul, 28, 24}, // 11111111|11111111|11111111|0100 + {0xffffff50ul, 28, 25}, // 11111111|11111111|11111111|0101 + {0xffffff60ul, 28, 26}, // 11111111|11111111|11111111|0110 + {0xffffff70ul, 28, 27}, // 11111111|11111111|11111111|0111 + {0xffffff80ul, 28, 28}, // 11111111|11111111|11111111|1000 + {0xffffff90ul, 28, 29}, // 11111111|11111111|11111111|1001 + {0xffffffa0ul, 28, 30}, // 11111111|11111111|11111111|1010 + {0xffffffb0ul, 28, 31}, // 11111111|11111111|11111111|1011 + {0x50000000ul, 6, 32}, // ' ' 010100 + {0xfe000000ul, 10, 33}, // '!' 11111110|00 + {0xfe400000ul, 10, 34}, // '"' 11111110|01 + {0xffa00000ul, 12, 35}, // '#' 11111111|1010 + {0xffc80000ul, 13, 36}, // '$' 11111111|11001 + {0x54000000ul, 6, 37}, // '%' 010101 + {0xf8000000ul, 8, 38}, // '&' 11111000 + {0xff400000ul, 11, 39}, // ''' 11111111|010 + {0xfe800000ul, 10, 40}, // '(' 11111110|10 + {0xfec00000ul, 10, 41}, // ')' 11111110|11 + {0xf9000000ul, 8, 42}, // '*' 11111001 + {0xff600000ul, 11, 43}, // '+' 11111111|011 + {0xfa000000ul, 8, 44}, // ',' 11111010 + {0x58000000ul, 6, 45}, // '-' 010110 + {0x5c000000ul, 6, 46}, // '.' 010111 + {0x60000000ul, 6, 47}, // '/' 011000 + {0x00000000ul, 5, 48}, // '0' 00000 + {0x08000000ul, 5, 49}, // '1' 00001 + {0x10000000ul, 5, 50}, // '2' 00010 + {0x64000000ul, 6, 51}, // '3' 011001 + {0x68000000ul, 6, 52}, // '4' 011010 + {0x6c000000ul, 6, 53}, // '5' 011011 + {0x70000000ul, 6, 54}, // '6' 011100 + {0x74000000ul, 6, 55}, // '7' 011101 + {0x78000000ul, 6, 56}, // '8' 011110 + {0x7c000000ul, 6, 57}, // '9' 011111 + {0xb8000000ul, 7, 58}, // ':' 1011100 + {0xfb000000ul, 8, 59}, // ';' 11111011 + {0xfff80000ul, 15, 60}, // '<' 11111111|1111100 + {0x80000000ul, 6, 61}, // '=' 100000 + {0xffb00000ul, 12, 62}, // '>' 11111111|1011 + {0xff000000ul, 10, 63}, // '?' 11111111|00 + {0xffd00000ul, 13, 64}, // '@' 11111111|11010 + {0x84000000ul, 6, 65}, // 'A' 100001 + {0xba000000ul, 7, 66}, // 'B' 1011101 + {0xbc000000ul, 7, 67}, // 'C' 1011110 + {0xbe000000ul, 7, 68}, // 'D' 1011111 + {0xc0000000ul, 7, 69}, // 'E' 1100000 + {0xc2000000ul, 7, 70}, // 'F' 1100001 + {0xc4000000ul, 7, 71}, // 'G' 1100010 + {0xc6000000ul, 7, 72}, // 'H' 1100011 + {0xc8000000ul, 7, 73}, // 'I' 1100100 + {0xca000000ul, 7, 74}, // 'J' 1100101 + {0xcc000000ul, 7, 75}, // 'K' 1100110 + {0xce000000ul, 7, 76}, // 'L' 1100111 + {0xd0000000ul, 7, 77}, // 'M' 1101000 + {0xd2000000ul, 7, 78}, // 'N' 1101001 + {0xd4000000ul, 7, 79}, // 'O' 1101010 + {0xd6000000ul, 7, 80}, // 'P' 1101011 + {0xd8000000ul, 7, 81}, // 'Q' 1101100 + {0xda000000ul, 7, 82}, // 'R' 1101101 + {0xdc000000ul, 7, 83}, // 'S' 1101110 + {0xde000000ul, 7, 84}, // 'T' 1101111 + {0xe0000000ul, 7, 85}, // 'U' 1110000 + {0xe2000000ul, 7, 86}, // 'V' 1110001 + {0xe4000000ul, 7, 87}, // 'W' 1110010 + {0xfc000000ul, 8, 88}, // 'X' 11111100 + {0xe6000000ul, 7, 89}, // 'Y' 1110011 + {0xfd000000ul, 8, 90}, // 'Z' 11111101 + {0xffd80000ul, 13, 91}, // '[' 11111111|11011 + {0xfffe0000ul, 19, 92}, // '\' 11111111|11111110|000 + {0xffe00000ul, 13, 93}, // ']' 11111111|11100 + {0xfff00000ul, 14, 94}, // '^' 11111111|111100 + {0x88000000ul, 6, 95}, // '_' 100010 + {0xfffa0000ul, 15, 96}, // '`' 11111111|1111101 + {0x18000000ul, 5, 97}, // 'a' 00011 + {0x8c000000ul, 6, 98}, // 'b' 100011 + {0x20000000ul, 5, 99}, // 'c' 00100 + {0x90000000ul, 6, 100}, // 'd' 100100 + {0x28000000ul, 5, 101}, // 'e' 00101 + {0x94000000ul, 6, 102}, // 'f' 100101 + {0x98000000ul, 6, 103}, // 'g' 100110 + {0x9c000000ul, 6, 104}, // 'h' 100111 + {0x30000000ul, 5, 105}, // 'i' 00110 + {0xe8000000ul, 7, 106}, // 'j' 1110100 + {0xea000000ul, 7, 107}, // 'k' 1110101 + {0xa0000000ul, 6, 108}, // 'l' 101000 + {0xa4000000ul, 6, 109}, // 'm' 101001 + {0xa8000000ul, 6, 110}, // 'n' 101010 + {0x38000000ul, 5, 111}, // 'o' 00111 + {0xac000000ul, 6, 112}, // 'p' 101011 + {0xec000000ul, 7, 113}, // 'q' 1110110 + {0xb0000000ul, 6, 114}, // 'r' 101100 + {0x40000000ul, 5, 115}, // 's' 01000 + {0x48000000ul, 5, 116}, // 't' 01001 + {0xb4000000ul, 6, 117}, // 'u' 101101 + {0xee000000ul, 7, 118}, // 'v' 1110111 + {0xf0000000ul, 7, 119}, // 'w' 1111000 + {0xf2000000ul, 7, 120}, // 'x' 1111001 + {0xf4000000ul, 7, 121}, // 'y' 1111010 + {0xf6000000ul, 7, 122}, // 'z' 1111011 + {0xfffc0000ul, 15, 123}, // '{' 11111111|1111110 + {0xff800000ul, 11, 124}, // '|' 11111111|100 + {0xfff40000ul, 14, 125}, // '}' 11111111|111101 + {0xffe80000ul, 13, 126}, // '~' 11111111|11101 + {0xffffffc0ul, 28, 127}, // 11111111|11111111|11111111|1100 + {0xfffe6000ul, 20, 128}, // 11111111|11111110|0110 + {0xffff4800ul, 22, 129}, // 11111111|11111111|010010 + {0xfffe7000ul, 20, 130}, // 11111111|11111110|0111 + {0xfffe8000ul, 20, 131}, // 11111111|11111110|1000 + {0xffff4c00ul, 22, 132}, // 11111111|11111111|010011 + {0xffff5000ul, 22, 133}, // 11111111|11111111|010100 + {0xffff5400ul, 22, 134}, // 11111111|11111111|010101 + {0xffffb200ul, 23, 135}, // 11111111|11111111|1011001 + {0xffff5800ul, 22, 136}, // 11111111|11111111|010110 + {0xffffb400ul, 23, 137}, // 11111111|11111111|1011010 + {0xffffb600ul, 23, 138}, // 11111111|11111111|1011011 + {0xffffb800ul, 23, 139}, // 11111111|11111111|1011100 + {0xffffba00ul, 23, 140}, // 11111111|11111111|1011101 + {0xffffbc00ul, 23, 141}, // 11111111|11111111|1011110 + {0xffffeb00ul, 24, 142}, // 11111111|11111111|11101011 + {0xffffbe00ul, 23, 143}, // 11111111|11111111|1011111 + {0xffffec00ul, 24, 144}, // 11111111|11111111|11101100 + {0xffffed00ul, 24, 145}, // 11111111|11111111|11101101 + {0xffff5c00ul, 22, 146}, // 11111111|11111111|010111 + {0xffffc000ul, 23, 147}, // 11111111|11111111|1100000 + {0xffffee00ul, 24, 148}, // 11111111|11111111|11101110 + {0xffffc200ul, 23, 149}, // 11111111|11111111|1100001 + {0xffffc400ul, 23, 150}, // 11111111|11111111|1100010 + {0xffffc600ul, 23, 151}, // 11111111|11111111|1100011 + {0xffffc800ul, 23, 152}, // 11111111|11111111|1100100 + {0xfffee000ul, 21, 153}, // 11111111|11111110|11100 + {0xffff6000ul, 22, 154}, // 11111111|11111111|011000 + {0xffffca00ul, 23, 155}, // 11111111|11111111|1100101 + {0xffff6400ul, 22, 156}, // 11111111|11111111|011001 + {0xffffcc00ul, 23, 157}, // 11111111|11111111|1100110 + {0xffffce00ul, 23, 158}, // 11111111|11111111|1100111 + {0xffffef00ul, 24, 159}, // 11111111|11111111|11101111 + {0xffff6800ul, 22, 160}, // 11111111|11111111|011010 + {0xfffee800ul, 21, 161}, // 11111111|11111110|11101 + {0xfffe9000ul, 20, 162}, // 11111111|11111110|1001 + {0xffff6c00ul, 22, 163}, // 11111111|11111111|011011 + {0xffff7000ul, 22, 164}, // 11111111|11111111|011100 + {0xffffd000ul, 23, 165}, // 11111111|11111111|1101000 + {0xffffd200ul, 23, 166}, // 11111111|11111111|1101001 + {0xfffef000ul, 21, 167}, // 11111111|11111110|11110 + {0xffffd400ul, 23, 168}, // 11111111|11111111|1101010 + {0xffff7400ul, 22, 169}, // 11111111|11111111|011101 + {0xffff7800ul, 22, 170}, // 11111111|11111111|011110 + {0xfffff000ul, 24, 171}, // 11111111|11111111|11110000 + {0xfffef800ul, 21, 172}, // 11111111|11111110|11111 + {0xffff7c00ul, 22, 173}, // 11111111|11111111|011111 + {0xffffd600ul, 23, 174}, // 11111111|11111111|1101011 + {0xffffd800ul, 23, 175}, // 11111111|11111111|1101100 + {0xffff0000ul, 21, 176}, // 11111111|11111111|00000 + {0xffff0800ul, 21, 177}, // 11111111|11111111|00001 + {0xffff8000ul, 22, 178}, // 11111111|11111111|100000 + {0xffff1000ul, 21, 179}, // 11111111|11111111|00010 + {0xffffda00ul, 23, 180}, // 11111111|11111111|1101101 + {0xffff8400ul, 22, 181}, // 11111111|11111111|100001 + {0xffffdc00ul, 23, 182}, // 11111111|11111111|1101110 + {0xffffde00ul, 23, 183}, // 11111111|11111111|1101111 + {0xfffea000ul, 20, 184}, // 11111111|11111110|1010 + {0xffff8800ul, 22, 185}, // 11111111|11111111|100010 + {0xffff8c00ul, 22, 186}, // 11111111|11111111|100011 + {0xffff9000ul, 22, 187}, // 11111111|11111111|100100 + {0xffffe000ul, 23, 188}, // 11111111|11111111|1110000 + {0xffff9400ul, 22, 189}, // 11111111|11111111|100101 + {0xffff9800ul, 22, 190}, // 11111111|11111111|100110 + {0xffffe200ul, 23, 191}, // 11111111|11111111|1110001 + {0xfffff800ul, 26, 192}, // 11111111|11111111|11111000|00 + {0xfffff840ul, 26, 193}, // 11111111|11111111|11111000|01 + {0xfffeb000ul, 20, 194}, // 11111111|11111110|1011 + {0xfffe2000ul, 19, 195}, // 11111111|11111110|001 + {0xffff9c00ul, 22, 196}, // 11111111|11111111|100111 + {0xffffe400ul, 23, 197}, // 11111111|11111111|1110010 + {0xffffa000ul, 22, 198}, // 11111111|11111111|101000 + {0xfffff600ul, 25, 199}, // 11111111|11111111|11110110|0 + {0xfffff880ul, 26, 200}, // 11111111|11111111|11111000|10 + {0xfffff8c0ul, 26, 201}, // 11111111|11111111|11111000|11 + {0xfffff900ul, 26, 202}, // 11111111|11111111|11111001|00 + {0xfffffbc0ul, 27, 203}, // 11111111|11111111|11111011|110 + {0xfffffbe0ul, 27, 204}, // 11111111|11111111|11111011|111 + {0xfffff940ul, 26, 205}, // 11111111|11111111|11111001|01 + {0xfffff100ul, 24, 206}, // 11111111|11111111|11110001 + {0xfffff680ul, 25, 207}, // 11111111|11111111|11110110|1 + {0xfffe4000ul, 19, 208}, // 11111111|11111110|010 + {0xffff1800ul, 21, 209}, // 11111111|11111111|00011 + {0xfffff980ul, 26, 210}, // 11111111|11111111|11111001|10 + {0xfffffc00ul, 27, 211}, // 11111111|11111111|11111100|000 + {0xfffffc20ul, 27, 212}, // 11111111|11111111|11111100|001 + {0xfffff9c0ul, 26, 213}, // 11111111|11111111|11111001|11 + {0xfffffc40ul, 27, 214}, // 11111111|11111111|11111100|010 + {0xfffff200ul, 24, 215}, // 11111111|11111111|11110010 + {0xffff2000ul, 21, 216}, // 11111111|11111111|00100 + {0xffff2800ul, 21, 217}, // 11111111|11111111|00101 + {0xfffffa00ul, 26, 218}, // 11111111|11111111|11111010|00 + {0xfffffa40ul, 26, 219}, // 11111111|11111111|11111010|01 + {0xffffffd0ul, 28, 220}, // 11111111|11111111|11111111|1101 + {0xfffffc60ul, 27, 221}, // 11111111|11111111|11111100|011 + {0xfffffc80ul, 27, 222}, // 11111111|11111111|11111100|100 + {0xfffffca0ul, 27, 223}, // 11111111|11111111|11111100|101 + {0xfffec000ul, 20, 224}, // 11111111|11111110|1100 + {0xfffff300ul, 24, 225}, // 11111111|11111111|11110011 + {0xfffed000ul, 20, 226}, // 11111111|11111110|1101 + {0xffff3000ul, 21, 227}, // 11111111|11111111|00110 + {0xffffa400ul, 22, 228}, // 11111111|11111111|101001 + {0xffff3800ul, 21, 229}, // 11111111|11111111|00111 + {0xffff4000ul, 21, 230}, // 11111111|11111111|01000 + {0xffffe600ul, 23, 231}, // 11111111|11111111|1110011 + {0xffffa800ul, 22, 232}, // 11111111|11111111|101010 + {0xffffac00ul, 22, 233}, // 11111111|11111111|101011 + {0xfffff700ul, 25, 234}, // 11111111|11111111|11110111|0 + {0xfffff780ul, 25, 235}, // 11111111|11111111|11110111|1 + {0xfffff400ul, 24, 236}, // 11111111|11111111|11110100 + {0xfffff500ul, 24, 237}, // 11111111|11111111|11110101 + {0xfffffa80ul, 26, 238}, // 11111111|11111111|11111010|10 + {0xffffe800ul, 23, 239}, // 11111111|11111111|1110100 + {0xfffffac0ul, 26, 240}, // 11111111|11111111|11111010|11 + {0xfffffcc0ul, 27, 241}, // 11111111|11111111|11111100|110 + {0xfffffb00ul, 26, 242}, // 11111111|11111111|11111011|00 + {0xfffffb40ul, 26, 243}, // 11111111|11111111|11111011|01 + {0xfffffce0ul, 27, 244}, // 11111111|11111111|11111100|111 + {0xfffffd00ul, 27, 245}, // 11111111|11111111|11111101|000 + {0xfffffd20ul, 27, 246}, // 11111111|11111111|11111101|001 + {0xfffffd40ul, 27, 247}, // 11111111|11111111|11111101|010 + {0xfffffd60ul, 27, 248}, // 11111111|11111111|11111101|011 + {0xffffffe0ul, 28, 249}, // 11111111|11111111|11111111|1110 + {0xfffffd80ul, 27, 250}, // 11111111|11111111|11111101|100 + {0xfffffda0ul, 27, 251}, // 11111111|11111111|11111101|101 + {0xfffffdc0ul, 27, 252}, // 11111111|11111111|11111101|110 + {0xfffffde0ul, 27, 253}, // 11111111|11111111|11111101|111 + {0xfffffe00ul, 27, 254}, // 11111111|11111111|11111110|000 + {0xfffffb80ul, 26, 255}, // 11111111|11111111|11111011|10 + {0xfffffffcul, 30, 256}, // EOS 11111111|11111111|11111111|111111 + }; + return *kHpackHuffmanCode; +} + +// The "constructor" for a HpackStaticEntry that computes the lengths at +// compile time. +#define STATIC_ENTRY(name, value) \ + { name, SPDY_ARRAYSIZE(name) - 1, value, SPDY_ARRAYSIZE(value) - 1 } + +const std::vector<HpackStaticEntry>& HpackStaticTableVector() { + static const auto* kHpackStaticTable = new std::vector<HpackStaticEntry>{ + STATIC_ENTRY(":authority", ""), // 1 + STATIC_ENTRY(":method", "GET"), // 2 + STATIC_ENTRY(":method", "POST"), // 3 + STATIC_ENTRY(":path", "/"), // 4 + STATIC_ENTRY(":path", "/index.html"), // 5 + STATIC_ENTRY(":scheme", "http"), // 6 + STATIC_ENTRY(":scheme", "https"), // 7 + STATIC_ENTRY(":status", "200"), // 8 + STATIC_ENTRY(":status", "204"), // 9 + STATIC_ENTRY(":status", "206"), // 10 + STATIC_ENTRY(":status", "304"), // 11 + STATIC_ENTRY(":status", "400"), // 12 + STATIC_ENTRY(":status", "404"), // 13 + STATIC_ENTRY(":status", "500"), // 14 + STATIC_ENTRY("accept-charset", ""), // 15 + STATIC_ENTRY("accept-encoding", "gzip, deflate"), // 16 + STATIC_ENTRY("accept-language", ""), // 17 + STATIC_ENTRY("accept-ranges", ""), // 18 + STATIC_ENTRY("accept", ""), // 19 + STATIC_ENTRY("access-control-allow-origin", ""), // 20 + STATIC_ENTRY("age", ""), // 21 + STATIC_ENTRY("allow", ""), // 22 + STATIC_ENTRY("authorization", ""), // 23 + STATIC_ENTRY("cache-control", ""), // 24 + STATIC_ENTRY("content-disposition", ""), // 25 + STATIC_ENTRY("content-encoding", ""), // 26 + STATIC_ENTRY("content-language", ""), // 27 + STATIC_ENTRY("content-length", ""), // 28 + STATIC_ENTRY("content-location", ""), // 29 + STATIC_ENTRY("content-range", ""), // 30 + STATIC_ENTRY("content-type", ""), // 31 + STATIC_ENTRY("cookie", ""), // 32 + STATIC_ENTRY("date", ""), // 33 + STATIC_ENTRY("etag", ""), // 34 + STATIC_ENTRY("expect", ""), // 35 + STATIC_ENTRY("expires", ""), // 36 + STATIC_ENTRY("from", ""), // 37 + STATIC_ENTRY("host", ""), // 38 + STATIC_ENTRY("if-match", ""), // 39 + STATIC_ENTRY("if-modified-since", ""), // 40 + STATIC_ENTRY("if-none-match", ""), // 41 + STATIC_ENTRY("if-range", ""), // 42 + STATIC_ENTRY("if-unmodified-since", ""), // 43 + STATIC_ENTRY("last-modified", ""), // 44 + STATIC_ENTRY("link", ""), // 45 + STATIC_ENTRY("location", ""), // 46 + STATIC_ENTRY("max-forwards", ""), // 47 + STATIC_ENTRY("proxy-authenticate", ""), // 48 + STATIC_ENTRY("proxy-authorization", ""), // 49 + STATIC_ENTRY("range", ""), // 50 + STATIC_ENTRY("referer", ""), // 51 + STATIC_ENTRY("refresh", ""), // 52 + STATIC_ENTRY("retry-after", ""), // 53 + STATIC_ENTRY("server", ""), // 54 + STATIC_ENTRY("set-cookie", ""), // 55 + STATIC_ENTRY("strict-transport-security", ""), // 56 + STATIC_ENTRY("transfer-encoding", ""), // 57 + STATIC_ENTRY("user-agent", ""), // 58 + STATIC_ENTRY("vary", ""), // 59 + STATIC_ENTRY("via", ""), // 60 + STATIC_ENTRY("www-authenticate", ""), // 61 + }; + return *kHpackStaticTable; +} + +#undef STATIC_ENTRY + +const HpackHuffmanTable& ObtainHpackHuffmanTable() { + static const HpackHuffmanTable* const shared_huffman_table = []() { + auto* table = new HpackHuffmanTable(); + CHECK(table->Initialize(HpackHuffmanCodeVector().data(), + HpackHuffmanCodeVector().size())); + CHECK(table->IsInitialized()); + return table; + }(); + return *shared_huffman_table; +} + +const HpackStaticTable& ObtainHpackStaticTable() { + static const HpackStaticTable* const shared_static_table = []() { + auto* table = new HpackStaticTable(); + table->Initialize(HpackStaticTableVector().data(), + HpackStaticTableVector().size()); + CHECK(table->IsInitialized()); + return table; + }(); + return *shared_static_table; +} + +} // namespace spdy diff --git a/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_constants.h b/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_constants.h new file mode 100644 index 00000000000..3f4026bebcf --- /dev/null +++ b/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_constants.h @@ -0,0 +1,95 @@ +// Copyright 2014 The Chromium 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_SPDY_CORE_HPACK_HPACK_CONSTANTS_H_ +#define QUICHE_SPDY_CORE_HPACK_HPACK_CONSTANTS_H_ + +#include <cstddef> +#include <cstdint> +#include <vector> + +#include "net/third_party/quiche/src/spdy/platform/api/spdy_export.h" + +// All section references below are to +// http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-08 + +namespace spdy { + +// An HpackPrefix signifies |bits| stored in the top |bit_size| bits +// of an octet. +struct HpackPrefix { + uint8_t bits; + size_t bit_size; +}; + +// Represents a symbol and its Huffman code (stored in most-significant bits). +struct HpackHuffmanSymbol { + uint32_t code; + uint8_t length; + uint16_t id; +}; + +// An entry in the static table. Must be a POD in order to avoid static +// initializers, i.e. no user-defined constructors or destructors. +struct HpackStaticEntry { + const char* const name; + const size_t name_len; + const char* const value; + const size_t value_len; +}; + +class HpackHuffmanTable; +class HpackStaticTable; + +// Defined in RFC 7540, 6.5.2. +const uint32_t kDefaultHeaderTableSizeSetting = 4096; + +// RFC 7541, 5.2: Flag for a string literal that is stored unmodified (i.e., +// without Huffman encoding). +const HpackPrefix kStringLiteralIdentityEncoded = {0x0, 1}; + +// RFC 7541, 5.2: Flag for a Huffman-coded string literal. +const HpackPrefix kStringLiteralHuffmanEncoded = {0x1, 1}; + +// RFC 7541, 6.1: Opcode for an indexed header field. +const HpackPrefix kIndexedOpcode = {0b1, 1}; + +// RFC 7541, 6.2.1: Opcode for a literal header field with incremental indexing. +const HpackPrefix kLiteralIncrementalIndexOpcode = {0b01, 2}; + +// RFC 7541, 6.2.2: Opcode for a literal header field without indexing. +const HpackPrefix kLiteralNoIndexOpcode = {0b0000, 4}; + +// RFC 7541, 6.2.3: Opcode for a literal header field which is never indexed. +// Currently unused. +// const HpackPrefix kLiteralNeverIndexOpcode = {0b0001, 4}; + +// RFC 7541, 6.3: Opcode for maximum header table size update. Begins a +// varint-encoded table size with a 5-bit prefix. +const HpackPrefix kHeaderTableSizeUpdateOpcode = {0b001, 3}; + +// Symbol code table from RFC 7541, "Appendix C. Huffman Code". +SPDY_EXPORT_PRIVATE const std::vector<HpackHuffmanSymbol>& +HpackHuffmanCodeVector(); + +// Static table from RFC 7541, "Appendix B. Static Table Definition". +SPDY_EXPORT_PRIVATE const std::vector<HpackStaticEntry>& +HpackStaticTableVector(); + +// Returns a HpackHuffmanTable instance initialized with |kHpackHuffmanCode|. +// The instance is read-only, has static lifetime, and is safe to share amoung +// threads. This function is thread-safe. +SPDY_EXPORT_PRIVATE const HpackHuffmanTable& ObtainHpackHuffmanTable(); + +// Returns a HpackStaticTable instance initialized with |kHpackStaticTable|. +// The instance is read-only, has static lifetime, and is safe to share amoung +// threads. This function is thread-safe. +SPDY_EXPORT_PRIVATE const HpackStaticTable& ObtainHpackStaticTable(); + +// Pseudo-headers start with a colon. (HTTP2 8.1.2.1., HPACK 3.1.) +const char kPseudoHeaderPrefix = ':'; + +} // namespace spdy + +#endif // QUICHE_SPDY_CORE_HPACK_HPACK_CONSTANTS_H_ diff --git a/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_decoder_adapter.cc b/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_decoder_adapter.cc new file mode 100644 index 00000000000..309af39a06b --- /dev/null +++ b/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_decoder_adapter.cc @@ -0,0 +1,201 @@ +// 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/spdy/core/hpack/hpack_decoder_adapter.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_status.h" +#include "net/third_party/quiche/src/spdy/platform/api/spdy_estimate_memory_usage.h" + +using ::http2::DecodeBuffer; +using ::http2::HpackEntryType; +using ::http2::HpackString; + +namespace spdy { +namespace { +const size_t kMaxDecodeBufferSizeBytes = 32 * 1024; // 32 KB +} // namespace + +HpackDecoderAdapter::HpackDecoderAdapter() + : hpack_decoder_(&listener_adapter_, kMaxDecodeBufferSizeBytes), + max_decode_buffer_size_bytes_(kMaxDecodeBufferSizeBytes), + header_block_started_(false) {} + +HpackDecoderAdapter::~HpackDecoderAdapter() = default; + +void HpackDecoderAdapter::ApplyHeaderTableSizeSetting(size_t size_setting) { + DVLOG(2) << "HpackDecoderAdapter::ApplyHeaderTableSizeSetting"; + hpack_decoder_.ApplyHeaderTableSizeSetting(size_setting); +} + +void HpackDecoderAdapter::HandleControlFrameHeadersStart( + SpdyHeadersHandlerInterface* handler) { + DVLOG(2) << "HpackDecoderAdapter::HandleControlFrameHeadersStart"; + DCHECK(!header_block_started_); + listener_adapter_.set_handler(handler); +} + +bool HpackDecoderAdapter::HandleControlFrameHeadersData( + const char* headers_data, + size_t headers_data_length) { + DVLOG(2) << "HpackDecoderAdapter::HandleControlFrameHeadersData: len=" + << headers_data_length; + if (!header_block_started_) { + // Initialize the decoding process here rather than in + // HandleControlFrameHeadersStart because that method is not always called. + header_block_started_ = true; + if (!hpack_decoder_.StartDecodingBlock()) { + header_block_started_ = false; + return false; + } + } + + // Sometimes we get a call with headers_data==nullptr and + // headers_data_length==0, in which case we need to avoid creating + // a DecodeBuffer, which would otherwise complain. + if (headers_data_length > 0) { + DCHECK_NE(headers_data, nullptr); + if (headers_data_length > max_decode_buffer_size_bytes_) { + DVLOG(1) << "max_decode_buffer_size_bytes_ < headers_data_length: " + << max_decode_buffer_size_bytes_ << " < " << headers_data_length; + return false; + } + listener_adapter_.AddToTotalHpackBytes(headers_data_length); + http2::DecodeBuffer db(headers_data, headers_data_length); + bool ok = hpack_decoder_.DecodeFragment(&db); + DCHECK(!ok || db.Empty()) << "Remaining=" << db.Remaining(); + return ok; + } + return true; +} + +bool HpackDecoderAdapter::HandleControlFrameHeadersComplete( + size_t* compressed_len) { + DVLOG(2) << "HpackDecoderAdapter::HandleControlFrameHeadersComplete"; + if (compressed_len != nullptr) { + *compressed_len = listener_adapter_.total_hpack_bytes(); + } + if (!hpack_decoder_.EndDecodingBlock()) { + DVLOG(3) << "EndDecodingBlock returned false"; + return false; + } + header_block_started_ = false; + return true; +} + +const SpdyHeaderBlock& HpackDecoderAdapter::decoded_block() const { + return listener_adapter_.decoded_block(); +} + +void HpackDecoderAdapter::SetHeaderTableDebugVisitor( + std::unique_ptr<HpackHeaderTable::DebugVisitorInterface> visitor) { + DVLOG(2) << "HpackDecoderAdapter::SetHeaderTableDebugVisitor"; + if (visitor != nullptr) { + listener_adapter_.SetHeaderTableDebugVisitor(std::move(visitor)); + hpack_decoder_.set_tables_debug_listener(&listener_adapter_); + } else { + hpack_decoder_.set_tables_debug_listener(nullptr); + listener_adapter_.SetHeaderTableDebugVisitor(nullptr); + } +} + +void HpackDecoderAdapter::set_max_decode_buffer_size_bytes( + size_t max_decode_buffer_size_bytes) { + DVLOG(2) << "HpackDecoderAdapter::set_max_decode_buffer_size_bytes"; + max_decode_buffer_size_bytes_ = max_decode_buffer_size_bytes; + hpack_decoder_.set_max_string_size_bytes(max_decode_buffer_size_bytes); +} + +size_t HpackDecoderAdapter::EstimateMemoryUsage() const { + return SpdyEstimateMemoryUsage(hpack_decoder_); +} + +HpackDecoderAdapter::ListenerAdapter::ListenerAdapter() : handler_(nullptr) {} +HpackDecoderAdapter::ListenerAdapter::~ListenerAdapter() = default; + +void HpackDecoderAdapter::ListenerAdapter::set_handler( + SpdyHeadersHandlerInterface* handler) { + handler_ = handler; +} + +void HpackDecoderAdapter::ListenerAdapter::SetHeaderTableDebugVisitor( + std::unique_ptr<HpackHeaderTable::DebugVisitorInterface> visitor) { + visitor_ = std::move(visitor); +} + +void HpackDecoderAdapter::ListenerAdapter::OnHeaderListStart() { + DVLOG(2) << "HpackDecoderAdapter::ListenerAdapter::OnHeaderListStart"; + total_hpack_bytes_ = 0; + total_uncompressed_bytes_ = 0; + decoded_block_.clear(); + if (handler_ != nullptr) { + handler_->OnHeaderBlockStart(); + } +} + +void HpackDecoderAdapter::ListenerAdapter::OnHeader(HpackEntryType entry_type, + const HpackString& name, + const HpackString& value) { + DVLOG(2) << "HpackDecoderAdapter::ListenerAdapter::OnHeader:\n name: " << name + << "\n value: " << value; + total_uncompressed_bytes_ += name.size() + value.size(); + if (handler_ == nullptr) { + DVLOG(3) << "Adding to decoded_block"; + decoded_block_.AppendValueOrAddHeader(name.ToStringPiece(), + value.ToStringPiece()); + } else { + DVLOG(3) << "Passing to handler"; + handler_->OnHeader(name.ToStringPiece(), value.ToStringPiece()); + } +} + +void HpackDecoderAdapter::ListenerAdapter::OnHeaderListEnd() { + DVLOG(2) << "HpackDecoderAdapter::ListenerAdapter::OnHeaderListEnd"; + // We don't clear the SpdyHeaderBlock here to allow access to it until the + // next HPACK block is decoded. + if (handler_ != nullptr) { + handler_->OnHeaderBlockEnd(total_uncompressed_bytes_, total_hpack_bytes_); + handler_ = nullptr; + } +} + +void HpackDecoderAdapter::ListenerAdapter::OnHeaderErrorDetected( + SpdyStringPiece error_message) { + VLOG(1) << error_message; +} + +int64_t HpackDecoderAdapter::ListenerAdapter::OnEntryInserted( + const http2::HpackStringPair& sp, + size_t insert_count) { + DVLOG(2) << "HpackDecoderAdapter::ListenerAdapter::OnEntryInserted: " << sp + << ", insert_count=" << insert_count; + if (visitor_ == nullptr) { + return 0; + } + HpackEntry entry(sp.name.ToStringPiece(), sp.value.ToStringPiece(), + /*is_static*/ false, insert_count); + int64_t time_added = visitor_->OnNewEntry(entry); + DVLOG(2) + << "HpackDecoderAdapter::ListenerAdapter::OnEntryInserted: time_added=" + << time_added; + return time_added; +} + +void HpackDecoderAdapter::ListenerAdapter::OnUseEntry( + const http2::HpackStringPair& sp, + size_t insert_count, + int64_t time_added) { + DVLOG(2) << "HpackDecoderAdapter::ListenerAdapter::OnUseEntry: " << sp + << ", insert_count=" << insert_count + << ", time_added=" << time_added; + if (visitor_ != nullptr) { + HpackEntry entry(sp.name.ToStringPiece(), sp.value.ToStringPiece(), + /*is_static*/ false, insert_count); + entry.set_time_added(time_added); + visitor_->OnUseEntry(entry); + } +} + +} // namespace spdy diff --git a/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_decoder_adapter.h b/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_decoder_adapter.h new file mode 100644 index 00000000000..b497fe7a47c --- /dev/null +++ b/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_decoder_adapter.h @@ -0,0 +1,159 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef QUICHE_SPDY_CORE_HPACK_HPACK_DECODER_ADAPTER_H_ +#define QUICHE_SPDY_CORE_HPACK_HPACK_DECODER_ADAPTER_H_ + +// HpackDecoderAdapter uses http2::HpackDecoder to decode HPACK blocks into +// HTTP/2 header lists as outlined in http://tools.ietf.org/html/rfc7541. + +#include <stddef.h> + +#include <cstdint> +#include <memory> + +#include "base/macros.h" +#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder.h" +#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_listener.h" +#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_tables.h" +#include "net/third_party/quiche/src/http2/hpack/hpack_string.h" +#include "net/third_party/quiche/src/http2/hpack/http2_hpack_constants.h" +#include "net/third_party/quiche/src/spdy/core/hpack/hpack_header_table.h" +#include "net/third_party/quiche/src/spdy/core/spdy_header_block.h" +#include "net/third_party/quiche/src/spdy/core/spdy_headers_handler_interface.h" +#include "net/third_party/quiche/src/spdy/platform/api/spdy_export.h" +#include "net/third_party/quiche/src/spdy/platform/api/spdy_string_piece.h" + +namespace spdy { +namespace test { +class HpackDecoderAdapterPeer; +} // namespace test + +class SPDY_EXPORT_PRIVATE HpackDecoderAdapter { + public: + friend test::HpackDecoderAdapterPeer; + HpackDecoderAdapter(); + HpackDecoderAdapter(const HpackDecoderAdapter&) = delete; + HpackDecoderAdapter& operator=(const HpackDecoderAdapter&) = delete; + ~HpackDecoderAdapter(); + + // Called upon acknowledgement of SETTINGS_HEADER_TABLE_SIZE. + void ApplyHeaderTableSizeSetting(size_t size_setting); + + // If a SpdyHeadersHandlerInterface is provided, the decoder will emit + // headers to it rather than accumulating them in a SpdyHeaderBlock. + // Does not take ownership of the handler, but does use the pointer until + // the current HPACK block is completely decoded. + void HandleControlFrameHeadersStart(SpdyHeadersHandlerInterface* handler); + + // Called as HPACK block fragments arrive. Returns false if an error occurred + // while decoding the block. Does not take ownership of headers_data. + bool HandleControlFrameHeadersData(const char* headers_data, + size_t headers_data_length); + + // Called after a HPACK block has been completely delivered via + // HandleControlFrameHeadersData(). Returns false if an error occurred. + // |compressed_len| if non-null will be set to the size of the encoded + // buffered block that was accumulated in HandleControlFrameHeadersData(), + // to support subsequent calculation of compression percentage. + // Discards the handler supplied at the start of decoding the block. + // TODO(jamessynge): Determine if compressed_len is needed; it is used to + // produce UUMA stat Net.SpdyHpackDecompressionPercentage, but only for + // deprecated SPDY3. + bool HandleControlFrameHeadersComplete(size_t* compressed_len); + + // Accessor for the most recently decoded headers block. Valid until the next + // call to HandleControlFrameHeadersData(). + // TODO(birenroy): Remove this method when all users of HpackDecoder specify + // a SpdyHeadersHandlerInterface. + const SpdyHeaderBlock& decoded_block() const; + + void SetHeaderTableDebugVisitor( + std::unique_ptr<HpackHeaderTable::DebugVisitorInterface> visitor); + + // Set how much encoded data this decoder is willing to buffer. + // TODO(jamessynge): Resolve definition of this value, as it is currently + // too tied to a single implementation. We probably want to limit one or more + // of these: individual name or value strings, header entries, the entire + // header list, or the HPACK block; we probably shouldn't care about the size + // of individual transport buffers. + void set_max_decode_buffer_size_bytes(size_t max_decode_buffer_size_bytes); + + size_t EstimateMemoryUsage() const; + + private: + class SPDY_EXPORT_PRIVATE ListenerAdapter + : public http2::HpackDecoderListener, + public http2::HpackDecoderTablesDebugListener { + public: + ListenerAdapter(); + ~ListenerAdapter() override; + + // If a SpdyHeadersHandlerInterface is provided, the decoder will emit + // headers to it rather than accumulating them in a SpdyHeaderBlock. + // Does not take ownership of the handler, but does use the pointer until + // the current HPACK block is completely decoded. + void set_handler(SpdyHeadersHandlerInterface* handler); + const SpdyHeaderBlock& decoded_block() const { return decoded_block_; } + + void SetHeaderTableDebugVisitor( + std::unique_ptr<HpackHeaderTable::DebugVisitorInterface> visitor); + + // Override the HpackDecoderListener methods: + void OnHeaderListStart() override; + void OnHeader(http2::HpackEntryType entry_type, + const http2::HpackString& name, + const http2::HpackString& value) override; + void OnHeaderListEnd() override; + void OnHeaderErrorDetected(SpdyStringPiece error_message) override; + + // Override the HpackDecoderTablesDebugListener methods: + int64_t OnEntryInserted(const http2::HpackStringPair& entry, + size_t insert_count) override; + void OnUseEntry(const http2::HpackStringPair& entry, + size_t insert_count, + int64_t insert_time) override; + + void AddToTotalHpackBytes(size_t delta) { total_hpack_bytes_ += delta; } + size_t total_hpack_bytes() const { return total_hpack_bytes_; } + + private: + // If the caller doesn't provide a handler, the header list is stored in + // this SpdyHeaderBlock. + SpdyHeaderBlock decoded_block_; + + // If non-NULL, handles decoded headers. Not owned. + SpdyHeadersHandlerInterface* handler_; + + // Total bytes that have been received as input (i.e. HPACK encoded) + // in the current HPACK block. + size_t total_hpack_bytes_; + + // Total bytes of the name and value strings in the current HPACK block. + size_t total_uncompressed_bytes_; + + // visitor_ is used by a QUIC experiment regarding HPACK; remove + // when the experiment is done. + std::unique_ptr<HpackHeaderTable::DebugVisitorInterface> visitor_; + }; + + // Converts calls to HpackDecoderListener into calls to + // SpdyHeadersHandlerInterface. + ListenerAdapter listener_adapter_; + + // The actual decoder. + http2::HpackDecoder hpack_decoder_; + + // How much encoded data this decoder is willing to buffer. + size_t max_decode_buffer_size_bytes_; + + // Flag to keep track of having seen the header block start. Needed at the + // moment because HandleControlFrameHeadersStart won't be called if a handler + // is not being provided by the caller. + bool header_block_started_; +}; + +} // namespace spdy + +#endif // QUICHE_SPDY_CORE_HPACK_HPACK_DECODER_ADAPTER_H_ diff --git a/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_decoder_adapter_test.cc b/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_decoder_adapter_test.cc new file mode 100644 index 00000000000..765c2eac7ac --- /dev/null +++ b/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_decoder_adapter_test.cc @@ -0,0 +1,1097 @@ +// 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/spdy/core/hpack/hpack_decoder_adapter.h" + +// Tests of HpackDecoderAdapter. + +#include <stdint.h> + +#include <tuple> +#include <utility> +#include <vector> + +#include "base/logging.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_state.h" +#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_tables.h" +#include "net/third_party/quiche/src/http2/hpack/tools/hpack_block_builder.h" +#include "net/third_party/quiche/src/http2/test_tools/http2_random.h" +#include "net/third_party/quiche/src/spdy/core/hpack/hpack_constants.h" +#include "net/third_party/quiche/src/spdy/core/hpack/hpack_encoder.h" +#include "net/third_party/quiche/src/spdy/core/hpack/hpack_output_stream.h" +#include "net/third_party/quiche/src/spdy/core/spdy_test_utils.h" +#include "net/third_party/quiche/src/spdy/platform/api/spdy_arraysize.h" +#include "net/third_party/quiche/src/spdy/platform/api/spdy_string.h" +#include "net/third_party/quiche/src/spdy/platform/api/spdy_string_utils.h" + +using ::http2::HpackEntryType; +using ::http2::HpackString; +using ::http2::HpackStringPair; +using ::http2::test::HpackBlockBuilder; +using ::http2::test::HpackDecoderPeer; +using ::testing::ElementsAre; +using ::testing::Pair; + +namespace http2 { +namespace test { + +class HpackDecoderStatePeer { + public: + static HpackDecoderTables* GetDecoderTables(HpackDecoderState* state) { + return &state->decoder_tables_; + } +}; + +class HpackDecoderPeer { + public: + static HpackDecoderState* GetDecoderState(HpackDecoder* decoder) { + return &decoder->decoder_state_; + } + static HpackDecoderTables* GetDecoderTables(HpackDecoder* decoder) { + return HpackDecoderStatePeer::GetDecoderTables(GetDecoderState(decoder)); + } +}; + +} // namespace test +} // namespace http2 + +namespace spdy { +namespace test { + +class HpackDecoderAdapterPeer { + public: + explicit HpackDecoderAdapterPeer(HpackDecoderAdapter* decoder) + : decoder_(decoder) {} + + void HandleHeaderRepresentation(SpdyStringPiece name, SpdyStringPiece value) { + decoder_->listener_adapter_.OnHeader(HpackEntryType::kIndexedLiteralHeader, + HpackString(name), HpackString(value)); + } + + http2::HpackDecoderTables* GetDecoderTables() { + return HpackDecoderPeer::GetDecoderTables(&decoder_->hpack_decoder_); + } + + const HpackStringPair* GetTableEntry(uint32_t index) { + return GetDecoderTables()->Lookup(index); + } + + size_t current_header_table_size() { + return GetDecoderTables()->current_header_table_size(); + } + + size_t header_table_size_limit() { + return GetDecoderTables()->header_table_size_limit(); + } + + void set_header_table_size_limit(size_t size) { + return GetDecoderTables()->DynamicTableSizeUpdate(size); + } + + private: + HpackDecoderAdapter* decoder_; +}; + +class HpackEncoderPeer { + public: + static void CookieToCrumbs(const HpackEncoder::Representation& cookie, + HpackEncoder::Representations* crumbs_out) { + HpackEncoder::CookieToCrumbs(cookie, crumbs_out); + } +}; + +namespace { + +const bool kNoCheckDecodedSize = false; +const char* kCookieKey = "cookie"; + +// Is HandleControlFrameHeadersStart to be called, and with what value? +enum StartChoice { START_WITH_HANDLER, START_WITHOUT_HANDLER, NO_START }; + +class HpackDecoderAdapterTest + : public ::testing::TestWithParam<std::tuple<StartChoice, bool>> { + protected: + HpackDecoderAdapterTest() : decoder_(), decoder_peer_(&decoder_) {} + + void SetUp() override { + std::tie(start_choice_, randomly_split_input_buffer_) = GetParam(); + } + + void HandleControlFrameHeadersStart() { + bytes_passed_in_ = 0; + switch (start_choice_) { + case START_WITH_HANDLER: + decoder_.HandleControlFrameHeadersStart(&handler_); + break; + case START_WITHOUT_HANDLER: + decoder_.HandleControlFrameHeadersStart(nullptr); + break; + case NO_START: + break; + } + } + + bool HandleControlFrameHeadersData(SpdyStringPiece str) { + VLOG(3) << "HandleControlFrameHeadersData:\n" << SpdyHexDump(str); + bytes_passed_in_ += str.size(); + return decoder_.HandleControlFrameHeadersData(str.data(), str.size()); + } + + bool HandleControlFrameHeadersComplete(size_t* size) { + bool rc = decoder_.HandleControlFrameHeadersComplete(size); + if (size != nullptr) { + EXPECT_EQ(*size, bytes_passed_in_); + } + return rc; + } + + bool DecodeHeaderBlock(SpdyStringPiece str, bool check_decoded_size = true) { + // Don't call this again if HandleControlFrameHeadersData failed previously. + EXPECT_FALSE(decode_has_failed_); + HandleControlFrameHeadersStart(); + if (randomly_split_input_buffer_) { + do { + // Decode some fragment of the remaining bytes. + size_t bytes = str.size(); + if (!str.empty()) { + bytes = random_.Uniform(str.size()) + 1; + } + EXPECT_LE(bytes, str.size()); + if (!HandleControlFrameHeadersData(str.substr(0, bytes))) { + decode_has_failed_ = true; + return false; + } + str.remove_prefix(bytes); + } while (!str.empty()); + } else if (!HandleControlFrameHeadersData(str)) { + decode_has_failed_ = true; + return false; + } + // Want to get out the number of compressed bytes that were decoded, + // so pass in a pointer if no handler. + size_t total_hpack_bytes = 0; + if (start_choice_ == START_WITH_HANDLER) { + if (!HandleControlFrameHeadersComplete(nullptr)) { + decode_has_failed_ = true; + return false; + } + total_hpack_bytes = handler_.compressed_header_bytes_parsed(); + } else { + if (!HandleControlFrameHeadersComplete(&total_hpack_bytes)) { + decode_has_failed_ = true; + return false; + } + } + EXPECT_EQ(total_hpack_bytes, bytes_passed_in_); + if (check_decoded_size && start_choice_ == START_WITH_HANDLER) { + EXPECT_EQ(handler_.header_bytes_parsed(), SizeOfHeaders(decoded_block())); + } + return true; + } + + bool EncodeAndDecodeDynamicTableSizeUpdates(size_t first, size_t second) { + HpackBlockBuilder hbb; + hbb.AppendDynamicTableSizeUpdate(first); + if (second != first) { + hbb.AppendDynamicTableSizeUpdate(second); + } + return DecodeHeaderBlock(hbb.buffer()); + } + + const SpdyHeaderBlock& decoded_block() const { + if (start_choice_ == START_WITH_HANDLER) { + return handler_.decoded_block(); + } else { + return decoder_.decoded_block(); + } + } + + static size_t SizeOfHeaders(const SpdyHeaderBlock& headers) { + size_t size = 0; + for (const auto& kv : headers) { + if (kv.first == kCookieKey) { + HpackEncoder::Representations crumbs; + HpackEncoderPeer::CookieToCrumbs(kv, &crumbs); + for (const auto& crumb : crumbs) { + size += crumb.first.size() + crumb.second.size(); + } + } else { + size += kv.first.size() + kv.second.size(); + } + } + return size; + } + + const SpdyHeaderBlock& DecodeBlockExpectingSuccess(SpdyStringPiece str) { + EXPECT_TRUE(DecodeHeaderBlock(str)); + return decoded_block(); + } + + void expectEntry(size_t index, + size_t size, + const SpdyString& name, + const SpdyString& value) { + const HpackStringPair* entry = decoder_peer_.GetTableEntry(index); + EXPECT_EQ(name, entry->name) << "index " << index; + EXPECT_EQ(value, entry->value); + EXPECT_EQ(size, entry->size()); + } + + SpdyHeaderBlock MakeHeaderBlock( + const std::vector<std::pair<SpdyString, SpdyString>>& headers) { + SpdyHeaderBlock result; + for (const auto& kv : headers) { + result.AppendValueOrAddHeader(kv.first, kv.second); + } + return result; + } + + http2::test::Http2Random random_; + HpackDecoderAdapter decoder_; + test::HpackDecoderAdapterPeer decoder_peer_; + TestHeadersHandler handler_; + StartChoice start_choice_; + bool randomly_split_input_buffer_; + bool decode_has_failed_ = false; + size_t bytes_passed_in_; +}; + +INSTANTIATE_TEST_CASE_P( + NoHandler, + HpackDecoderAdapterTest, + ::testing::Combine(::testing::Values(START_WITHOUT_HANDLER, NO_START), + ::testing::Bool())); + +INSTANTIATE_TEST_CASE_P( + WithHandler, + HpackDecoderAdapterTest, + ::testing::Combine(::testing::Values(START_WITH_HANDLER), + ::testing::Bool())); + +TEST_P(HpackDecoderAdapterTest, + AddHeaderDataWithHandleControlFrameHeadersData) { + // The hpack decode buffer size is limited in size. This test verifies that + // adding encoded data under that limit is accepted, and data that exceeds the + // limit is rejected. + HandleControlFrameHeadersStart(); + const size_t kMaxBufferSizeBytes = 50; + const SpdyString a_value = SpdyString(49, 'x'); + decoder_.set_max_decode_buffer_size_bytes(kMaxBufferSizeBytes); + HpackBlockBuilder hbb; + hbb.AppendLiteralNameAndValue(HpackEntryType::kNeverIndexedLiteralHeader, + false, "a", false, a_value); + const SpdyString& s = hbb.buffer(); + EXPECT_GT(s.size(), kMaxBufferSizeBytes); + + // Any one in input buffer must not exceed kMaxBufferSizeBytes. + EXPECT_TRUE(HandleControlFrameHeadersData(s.substr(0, s.size() / 2))); + EXPECT_TRUE(HandleControlFrameHeadersData(s.substr(s.size() / 2))); + + EXPECT_FALSE(HandleControlFrameHeadersData(s)); + SpdyHeaderBlock expected_block = MakeHeaderBlock({{"a", a_value}}); + EXPECT_EQ(expected_block, decoded_block()); +} + +TEST_P(HpackDecoderAdapterTest, NameTooLong) { + // Verify that a name longer than the allowed size generates an error. + const size_t kMaxBufferSizeBytes = 50; + const SpdyString name = SpdyString(2 * kMaxBufferSizeBytes, 'x'); + const SpdyString value = "abc"; + + decoder_.set_max_decode_buffer_size_bytes(kMaxBufferSizeBytes); + + HpackBlockBuilder hbb; + hbb.AppendLiteralNameAndValue(HpackEntryType::kNeverIndexedLiteralHeader, + false, name, false, value); + + const size_t fragment_size = (3 * kMaxBufferSizeBytes) / 2; + const SpdyString fragment = hbb.buffer().substr(0, fragment_size); + + HandleControlFrameHeadersStart(); + EXPECT_FALSE(HandleControlFrameHeadersData(fragment)); +} + +TEST_P(HpackDecoderAdapterTest, HeaderTooLongToBuffer) { + // Verify that a header longer than the allowed size generates an error if + // it isn't all in one input buffer. + const SpdyString name = "some-key"; + const SpdyString value = "some-value"; + const size_t kMaxBufferSizeBytes = name.size() + value.size() - 2; + decoder_.set_max_decode_buffer_size_bytes(kMaxBufferSizeBytes); + + HpackBlockBuilder hbb; + hbb.AppendLiteralNameAndValue(HpackEntryType::kNeverIndexedLiteralHeader, + false, name, false, value); + const size_t fragment_size = hbb.size() - 1; + const SpdyString fragment = hbb.buffer().substr(0, fragment_size); + + HandleControlFrameHeadersStart(); + EXPECT_FALSE(HandleControlFrameHeadersData(fragment)); +} + +// Decode with incomplete data in buffer. +TEST_P(HpackDecoderAdapterTest, DecodeWithIncompleteData) { + HandleControlFrameHeadersStart(); + + // No need to wait for more data. + EXPECT_TRUE(HandleControlFrameHeadersData("\x82\x85\x82")); + std::vector<std::pair<SpdyString, SpdyString>> expected_headers = { + {":method", "GET"}, {":path", "/index.html"}, {":method", "GET"}}; + + SpdyHeaderBlock expected_block1 = MakeHeaderBlock(expected_headers); + EXPECT_EQ(expected_block1, decoded_block()); + + // Full and partial headers, won't add partial to the headers. + EXPECT_TRUE( + HandleControlFrameHeadersData("\x40\x03goo" + "\x03gar\xbe\x40\x04spam")); + expected_headers.push_back({"goo", "gar"}); + expected_headers.push_back({"goo", "gar"}); + + SpdyHeaderBlock expected_block2 = MakeHeaderBlock(expected_headers); + EXPECT_EQ(expected_block2, decoded_block()); + + // Add the needed data. + EXPECT_TRUE(HandleControlFrameHeadersData("\x04gggs")); + + size_t size = 0; + EXPECT_TRUE(HandleControlFrameHeadersComplete(&size)); + EXPECT_EQ(24u, size); + + expected_headers.push_back({"spam", "gggs"}); + + SpdyHeaderBlock expected_block3 = MakeHeaderBlock(expected_headers); + EXPECT_EQ(expected_block3, decoded_block()); +} + +TEST_P(HpackDecoderAdapterTest, HandleHeaderRepresentation) { + // Make sure the decoder is properly initialized. + HandleControlFrameHeadersStart(); + HandleControlFrameHeadersData(""); + + // All cookie crumbs are joined. + decoder_peer_.HandleHeaderRepresentation("cookie", " part 1"); + decoder_peer_.HandleHeaderRepresentation("cookie", "part 2 "); + decoder_peer_.HandleHeaderRepresentation("cookie", "part3"); + + // Already-delimited headers are passed through. + decoder_peer_.HandleHeaderRepresentation("passed-through", + SpdyString("foo\0baz", 7)); + + // Other headers are joined on \0. Case matters. + decoder_peer_.HandleHeaderRepresentation("joined", "not joined"); + decoder_peer_.HandleHeaderRepresentation("joineD", "value 1"); + decoder_peer_.HandleHeaderRepresentation("joineD", "value 2"); + + // Empty headers remain empty. + decoder_peer_.HandleHeaderRepresentation("empty", ""); + + // Joined empty headers work as expected. + decoder_peer_.HandleHeaderRepresentation("empty-joined", ""); + decoder_peer_.HandleHeaderRepresentation("empty-joined", "foo"); + decoder_peer_.HandleHeaderRepresentation("empty-joined", ""); + decoder_peer_.HandleHeaderRepresentation("empty-joined", ""); + + // Non-contiguous cookie crumb. + decoder_peer_.HandleHeaderRepresentation("cookie", " fin!"); + + // Finish and emit all headers. + decoder_.HandleControlFrameHeadersComplete(nullptr); + + // Resulting decoded headers are in the same order as the inputs. + EXPECT_THAT( + decoded_block(), + ElementsAre(Pair("cookie", " part 1; part 2 ; part3; fin!"), + Pair("passed-through", SpdyStringPiece("foo\0baz", 7)), + Pair("joined", "not joined"), + Pair("joineD", SpdyStringPiece("value 1\0value 2", 15)), + Pair("empty", ""), + Pair("empty-joined", SpdyStringPiece("\0foo\0\0", 6)))); +} + +// Decoding indexed static table field should work. +TEST_P(HpackDecoderAdapterTest, IndexedHeaderStatic) { + // Reference static table entries #2 and #5. + const SpdyHeaderBlock& header_set1 = DecodeBlockExpectingSuccess("\x82\x85"); + SpdyHeaderBlock expected_header_set1; + expected_header_set1[":method"] = "GET"; + expected_header_set1[":path"] = "/index.html"; + EXPECT_EQ(expected_header_set1, header_set1); + + // Reference static table entry #2. + const SpdyHeaderBlock& header_set2 = DecodeBlockExpectingSuccess("\x82"); + SpdyHeaderBlock expected_header_set2; + expected_header_set2[":method"] = "GET"; + EXPECT_EQ(expected_header_set2, header_set2); +} + +TEST_P(HpackDecoderAdapterTest, IndexedHeaderDynamic) { + // First header block: add an entry to header table. + const SpdyHeaderBlock& header_set1 = DecodeBlockExpectingSuccess( + "\x40\x03" + "foo" + "\x03" + "bar"); + SpdyHeaderBlock expected_header_set1; + expected_header_set1["foo"] = "bar"; + EXPECT_EQ(expected_header_set1, header_set1); + + // Second header block: add another entry to header table. + const SpdyHeaderBlock& header_set2 = DecodeBlockExpectingSuccess( + "\xbe\x40\x04" + "spam" + "\x04" + "eggs"); + SpdyHeaderBlock expected_header_set2; + expected_header_set2["foo"] = "bar"; + expected_header_set2["spam"] = "eggs"; + EXPECT_EQ(expected_header_set2, header_set2); + + // Third header block: refer to most recently added entry. + const SpdyHeaderBlock& header_set3 = DecodeBlockExpectingSuccess("\xbe"); + SpdyHeaderBlock expected_header_set3; + expected_header_set3["spam"] = "eggs"; + EXPECT_EQ(expected_header_set3, header_set3); +} + +// Test a too-large indexed header. +TEST_P(HpackDecoderAdapterTest, InvalidIndexedHeader) { + // High-bit set, and a prefix of one more than the number of static entries. + EXPECT_FALSE(DecodeHeaderBlock("\xbe")); +} + +TEST_P(HpackDecoderAdapterTest, ContextUpdateMaximumSize) { + EXPECT_EQ(kDefaultHeaderTableSizeSetting, + decoder_peer_.header_table_size_limit()); + SpdyString input; + { + // Maximum-size update with size 126. Succeeds. + HpackOutputStream output_stream; + output_stream.AppendPrefix(kHeaderTableSizeUpdateOpcode); + output_stream.AppendUint32(126); + + output_stream.TakeString(&input); + EXPECT_TRUE(DecodeHeaderBlock(SpdyStringPiece(input))); + EXPECT_EQ(126u, decoder_peer_.header_table_size_limit()); + } + { + // Maximum-size update with kDefaultHeaderTableSizeSetting. Succeeds. + HpackOutputStream output_stream; + output_stream.AppendPrefix(kHeaderTableSizeUpdateOpcode); + output_stream.AppendUint32(kDefaultHeaderTableSizeSetting); + + output_stream.TakeString(&input); + EXPECT_TRUE(DecodeHeaderBlock(SpdyStringPiece(input))); + EXPECT_EQ(kDefaultHeaderTableSizeSetting, + decoder_peer_.header_table_size_limit()); + } + { + // Maximum-size update with kDefaultHeaderTableSizeSetting + 1. Fails. + HpackOutputStream output_stream; + output_stream.AppendPrefix(kHeaderTableSizeUpdateOpcode); + output_stream.AppendUint32(kDefaultHeaderTableSizeSetting + 1); + + output_stream.TakeString(&input); + EXPECT_FALSE(DecodeHeaderBlock(SpdyStringPiece(input))); + EXPECT_EQ(kDefaultHeaderTableSizeSetting, + decoder_peer_.header_table_size_limit()); + } +} + +// Two HeaderTableSizeUpdates may appear at the beginning of the block +TEST_P(HpackDecoderAdapterTest, TwoTableSizeUpdates) { + SpdyString input; + { + // Should accept two table size updates, update to second one + HpackOutputStream output_stream; + output_stream.AppendPrefix(kHeaderTableSizeUpdateOpcode); + output_stream.AppendUint32(0); + output_stream.AppendPrefix(kHeaderTableSizeUpdateOpcode); + output_stream.AppendUint32(122); + + output_stream.TakeString(&input); + EXPECT_TRUE(DecodeHeaderBlock(SpdyStringPiece(input))); + EXPECT_EQ(122u, decoder_peer_.header_table_size_limit()); + } +} + +// Three HeaderTableSizeUpdates should result in an error +TEST_P(HpackDecoderAdapterTest, ThreeTableSizeUpdatesError) { + SpdyString input; + { + // Should reject three table size updates, update to second one + HpackOutputStream output_stream; + output_stream.AppendPrefix(kHeaderTableSizeUpdateOpcode); + output_stream.AppendUint32(5); + output_stream.AppendPrefix(kHeaderTableSizeUpdateOpcode); + output_stream.AppendUint32(10); + output_stream.AppendPrefix(kHeaderTableSizeUpdateOpcode); + output_stream.AppendUint32(15); + + output_stream.TakeString(&input); + + EXPECT_FALSE(DecodeHeaderBlock(SpdyStringPiece(input))); + EXPECT_EQ(10u, decoder_peer_.header_table_size_limit()); + } +} + +// HeaderTableSizeUpdates may only appear at the beginning of the block +// Any other updates should result in an error +TEST_P(HpackDecoderAdapterTest, TableSizeUpdateSecondError) { + SpdyString input; + { + // Should reject a table size update appearing after a different entry + // The table size should remain as the default + HpackOutputStream output_stream; + output_stream.AppendBytes("\x82\x85"); + output_stream.AppendPrefix(kHeaderTableSizeUpdateOpcode); + output_stream.AppendUint32(123); + + output_stream.TakeString(&input); + + EXPECT_FALSE(DecodeHeaderBlock(SpdyStringPiece(input))); + EXPECT_EQ(kDefaultHeaderTableSizeSetting, + decoder_peer_.header_table_size_limit()); + } +} + +// HeaderTableSizeUpdates may only appear at the beginning of the block +// Any other updates should result in an error +TEST_P(HpackDecoderAdapterTest, TableSizeUpdateFirstThirdError) { + SpdyString input; + { + // Should reject the second table size update + // if a different entry appears after the first update + // The table size should update to the first but not the second + HpackOutputStream output_stream; + output_stream.AppendPrefix(kHeaderTableSizeUpdateOpcode); + output_stream.AppendUint32(60); + output_stream.AppendBytes("\x82\x85"); + output_stream.AppendPrefix(kHeaderTableSizeUpdateOpcode); + output_stream.AppendUint32(125); + + output_stream.TakeString(&input); + + EXPECT_FALSE(DecodeHeaderBlock(SpdyStringPiece(input))); + EXPECT_EQ(60u, decoder_peer_.header_table_size_limit()); + } +} + +// Decoding two valid encoded literal headers with no indexing should +// work. +TEST_P(HpackDecoderAdapterTest, LiteralHeaderNoIndexing) { + // First header with indexed name, second header with string literal + // name. + const char input[] = "\x04\x0c/sample/path\x00\x06:path2\x0e/sample/path/2"; + const SpdyHeaderBlock& header_set = DecodeBlockExpectingSuccess( + SpdyStringPiece(input, SPDY_ARRAYSIZE(input) - 1)); + + SpdyHeaderBlock expected_header_set; + expected_header_set[":path"] = "/sample/path"; + expected_header_set[":path2"] = "/sample/path/2"; + EXPECT_EQ(expected_header_set, header_set); +} + +// Decoding two valid encoded literal headers with incremental +// indexing and string literal names should work. +TEST_P(HpackDecoderAdapterTest, LiteralHeaderIncrementalIndexing) { + const char input[] = "\x44\x0c/sample/path\x40\x06:path2\x0e/sample/path/2"; + const SpdyHeaderBlock& header_set = DecodeBlockExpectingSuccess( + SpdyStringPiece(input, SPDY_ARRAYSIZE(input) - 1)); + + SpdyHeaderBlock expected_header_set; + expected_header_set[":path"] = "/sample/path"; + expected_header_set[":path2"] = "/sample/path/2"; + EXPECT_EQ(expected_header_set, header_set); +} + +TEST_P(HpackDecoderAdapterTest, LiteralHeaderWithIndexingInvalidNameIndex) { + decoder_.ApplyHeaderTableSizeSetting(0); + EXPECT_TRUE(EncodeAndDecodeDynamicTableSizeUpdates(0, 0)); + + // Name is the last static index. Works. + EXPECT_TRUE(DecodeHeaderBlock(SpdyStringPiece("\x7d\x03ooo"))); + // Name is one beyond the last static index. Fails. + EXPECT_FALSE(DecodeHeaderBlock(SpdyStringPiece("\x7e\x03ooo"))); +} + +TEST_P(HpackDecoderAdapterTest, LiteralHeaderNoIndexingInvalidNameIndex) { + // Name is the last static index. Works. + EXPECT_TRUE(DecodeHeaderBlock(SpdyStringPiece("\x0f\x2e\x03ooo"))); + // Name is one beyond the last static index. Fails. + EXPECT_FALSE(DecodeHeaderBlock(SpdyStringPiece("\x0f\x2f\x03ooo"))); +} + +TEST_P(HpackDecoderAdapterTest, LiteralHeaderNeverIndexedInvalidNameIndex) { + // Name is the last static index. Works. + EXPECT_TRUE(DecodeHeaderBlock(SpdyStringPiece("\x1f\x2e\x03ooo"))); + // Name is one beyond the last static index. Fails. + EXPECT_FALSE(DecodeHeaderBlock(SpdyStringPiece("\x1f\x2f\x03ooo"))); +} + +TEST_P(HpackDecoderAdapterTest, TruncatedIndex) { + // Indexed Header, varint for index requires multiple bytes, + // but only one provided. + EXPECT_FALSE(DecodeHeaderBlock("\xff")); +} + +TEST_P(HpackDecoderAdapterTest, TruncatedHuffmanLiteral) { + // Literal value, Huffman encoded, but with the last byte missing (i.e. + // drop the final ff shown below). + // + // 41 | == Literal indexed == + // | Indexed name (idx = 1) + // | :authority + // 8c | Literal value (len = 12) + // | Huffman encoded: + // f1e3 c2e5 f23a 6ba0 ab90 f4ff | .....:k..... + // | Decoded: + // | www.example.com + // | -> :authority: www.example.com + + SpdyString first = SpdyHexDecode("418cf1e3c2e5f23a6ba0ab90f4ff"); + EXPECT_TRUE(DecodeHeaderBlock(first)); + first.pop_back(); + EXPECT_FALSE(DecodeHeaderBlock(first)); +} + +TEST_P(HpackDecoderAdapterTest, HuffmanEOSError) { + // Literal value, Huffman encoded, but with an additional ff byte at the end + // of the string, i.e. an EOS that is longer than permitted. + // + // 41 | == Literal indexed == + // | Indexed name (idx = 1) + // | :authority + // 8d | Literal value (len = 13) + // | Huffman encoded: + // f1e3 c2e5 f23a 6ba0 ab90 f4ff | .....:k..... + // | Decoded: + // | www.example.com + // | -> :authority: www.example.com + + SpdyString first = SpdyHexDecode("418cf1e3c2e5f23a6ba0ab90f4ff"); + EXPECT_TRUE(DecodeHeaderBlock(first)); + first = SpdyHexDecode("418df1e3c2e5f23a6ba0ab90f4ffff"); + EXPECT_FALSE(DecodeHeaderBlock(first)); +} + +// Round-tripping the header set from RFC 7541 C.3.1 should work. +// http://httpwg.org/specs/rfc7541.html#rfc.section.C.3.1 +TEST_P(HpackDecoderAdapterTest, BasicC31) { + HpackEncoder encoder(ObtainHpackHuffmanTable()); + + SpdyHeaderBlock expected_header_set; + expected_header_set[":method"] = "GET"; + expected_header_set[":scheme"] = "http"; + expected_header_set[":path"] = "/"; + expected_header_set[":authority"] = "www.example.com"; + + SpdyString encoded_header_set; + EXPECT_TRUE( + encoder.EncodeHeaderSet(expected_header_set, &encoded_header_set)); + + EXPECT_TRUE(DecodeHeaderBlock(encoded_header_set)); + EXPECT_EQ(expected_header_set, decoded_block()); +} + +// RFC 7541, Section C.4: Request Examples with Huffman Coding +// http://httpwg.org/specs/rfc7541.html#rfc.section.C.4 +TEST_P(HpackDecoderAdapterTest, SectionC4RequestHuffmanExamples) { + // TODO(jamessynge): Use http2/hpack/tools/hpack_example.h to parse the + // example directly, instead of having it as a comment. + // + // 82 | == Indexed - Add == + // | idx = 2 + // | -> :method: GET + // 86 | == Indexed - Add == + // | idx = 6 + // | -> :scheme: http + // 84 | == Indexed - Add == + // | idx = 4 + // | -> :path: / + // 41 | == Literal indexed == + // | Indexed name (idx = 1) + // | :authority + // 8c | Literal value (len = 12) + // | Huffman encoded: + // f1e3 c2e5 f23a 6ba0 ab90 f4ff | .....:k..... + // | Decoded: + // | www.example.com + // | -> :authority: www.example.com + SpdyString first = SpdyHexDecode("828684418cf1e3c2e5f23a6ba0ab90f4ff"); + const SpdyHeaderBlock& first_header_set = DecodeBlockExpectingSuccess(first); + + EXPECT_THAT(first_header_set, + ElementsAre( + // clang-format off + Pair(":method", "GET"), + Pair(":scheme", "http"), + Pair(":path", "/"), + Pair(":authority", "www.example.com"))); + // clang-format on + + expectEntry(62, 57, ":authority", "www.example.com"); + EXPECT_EQ(57u, decoder_peer_.current_header_table_size()); + + // 82 | == Indexed - Add == + // | idx = 2 + // | -> :method: GET + // 86 | == Indexed - Add == + // | idx = 6 + // | -> :scheme: http + // 84 | == Indexed - Add == + // | idx = 4 + // | -> :path: / + // be | == Indexed - Add == + // | idx = 62 + // | -> :authority: www.example.com + // 58 | == Literal indexed == + // | Indexed name (idx = 24) + // | cache-control + // 86 | Literal value (len = 8) + // | Huffman encoded: + // a8eb 1064 9cbf | ...d.. + // | Decoded: + // | no-cache + // | -> cache-control: no-cache + + SpdyString second = SpdyHexDecode("828684be5886a8eb10649cbf"); + const SpdyHeaderBlock& second_header_set = + DecodeBlockExpectingSuccess(second); + + EXPECT_THAT(second_header_set, + ElementsAre( + // clang-format off + Pair(":method", "GET"), + Pair(":scheme", "http"), + Pair(":path", "/"), + Pair(":authority", "www.example.com"), + Pair("cache-control", "no-cache"))); + // clang-format on + + expectEntry(62, 53, "cache-control", "no-cache"); + expectEntry(63, 57, ":authority", "www.example.com"); + EXPECT_EQ(110u, decoder_peer_.current_header_table_size()); + + // 82 | == Indexed - Add == + // | idx = 2 + // | -> :method: GET + // 87 | == Indexed - Add == + // | idx = 7 + // | -> :scheme: https + // 85 | == Indexed - Add == + // | idx = 5 + // | -> :path: /index.html + // bf | == Indexed - Add == + // | idx = 63 + // | -> :authority: www.example.com + // 40 | == Literal indexed == + // 88 | Literal name (len = 10) + // | Huffman encoded: + // 25a8 49e9 5ba9 7d7f | %.I.[.}. + // | Decoded: + // | custom-key + // 89 | Literal value (len = 12) + // | Huffman encoded: + // 25a8 49e9 5bb8 e8b4 bf | %.I.[.... + // | Decoded: + // | custom-value + // | -> custom-key: custom-value + SpdyString third = + SpdyHexDecode("828785bf408825a849e95ba97d7f8925a849e95bb8e8b4bf"); + const SpdyHeaderBlock& third_header_set = DecodeBlockExpectingSuccess(third); + + EXPECT_THAT( + third_header_set, + ElementsAre( + // clang-format off + Pair(":method", "GET"), + Pair(":scheme", "https"), + Pair(":path", "/index.html"), + Pair(":authority", "www.example.com"), + Pair("custom-key", "custom-value"))); + // clang-format on + + expectEntry(62, 54, "custom-key", "custom-value"); + expectEntry(63, 53, "cache-control", "no-cache"); + expectEntry(64, 57, ":authority", "www.example.com"); + EXPECT_EQ(164u, decoder_peer_.current_header_table_size()); +} + +// RFC 7541, Section C.6: Response Examples with Huffman Coding +// http://httpwg.org/specs/rfc7541.html#rfc.section.C.6 +TEST_P(HpackDecoderAdapterTest, SectionC6ResponseHuffmanExamples) { + // The example is based on a maximum dynamic table size of 256, + // which allows for testing dynamic table evictions. + decoder_peer_.set_header_table_size_limit(256); + + // 48 | == Literal indexed == + // | Indexed name (idx = 8) + // | :status + // 82 | Literal value (len = 3) + // | Huffman encoded: + // 6402 | d. + // | Decoded: + // | 302 + // | -> :status: 302 + // 58 | == Literal indexed == + // | Indexed name (idx = 24) + // | cache-control + // 85 | Literal value (len = 7) + // | Huffman encoded: + // aec3 771a 4b | ..w.K + // | Decoded: + // | private + // | -> cache-control: private + // 61 | == Literal indexed == + // | Indexed name (idx = 33) + // | date + // 96 | Literal value (len = 29) + // | Huffman encoded: + // d07a be94 1054 d444 a820 0595 040b 8166 | .z...T.D. .....f + // e082 a62d 1bff | ...-.. + // | Decoded: + // | Mon, 21 Oct 2013 20:13:21 + // | GMT + // | -> date: Mon, 21 Oct 2013 + // | 20:13:21 GMT + // 6e | == Literal indexed == + // | Indexed name (idx = 46) + // | location + // 91 | Literal value (len = 23) + // | Huffman encoded: + // 9d29 ad17 1863 c78f 0b97 c8e9 ae82 ae43 | .)...c.........C + // d3 | . + // | Decoded: + // | https://www.example.com + // | -> location: https://www.e + // | xample.com + + SpdyString first = SpdyHexDecode( + "488264025885aec3771a4b6196d07abe" + "941054d444a8200595040b8166e082a6" + "2d1bff6e919d29ad171863c78f0b97c8" + "e9ae82ae43d3"); + const SpdyHeaderBlock& first_header_set = DecodeBlockExpectingSuccess(first); + + EXPECT_THAT(first_header_set, + ElementsAre( + // clang-format off + Pair(":status", "302"), + Pair("cache-control", "private"), + Pair("date", "Mon, 21 Oct 2013 20:13:21 GMT"), + Pair("location", "https://www.example.com"))); + // clang-format on + + expectEntry(62, 63, "location", "https://www.example.com"); + expectEntry(63, 65, "date", "Mon, 21 Oct 2013 20:13:21 GMT"); + expectEntry(64, 52, "cache-control", "private"); + expectEntry(65, 42, ":status", "302"); + EXPECT_EQ(222u, decoder_peer_.current_header_table_size()); + + // 48 | == Literal indexed == + // | Indexed name (idx = 8) + // | :status + // 83 | Literal value (len = 3) + // | Huffman encoded: + // 640e ff | d.. + // | Decoded: + // | 307 + // | - evict: :status: 302 + // | -> :status: 307 + // c1 | == Indexed - Add == + // | idx = 65 + // | -> cache-control: private + // c0 | == Indexed - Add == + // | idx = 64 + // | -> date: Mon, 21 Oct 2013 + // | 20:13:21 GMT + // bf | == Indexed - Add == + // | idx = 63 + // | -> location: + // | https://www.example.com + SpdyString second = SpdyHexDecode("4883640effc1c0bf"); + const SpdyHeaderBlock& second_header_set = + DecodeBlockExpectingSuccess(second); + + EXPECT_THAT(second_header_set, + ElementsAre( + // clang-format off + Pair(":status", "307"), + Pair("cache-control", "private"), + Pair("date", "Mon, 21 Oct 2013 20:13:21 GMT"), + Pair("location", "https://www.example.com"))); + // clang-format on + + expectEntry(62, 42, ":status", "307"); + expectEntry(63, 63, "location", "https://www.example.com"); + expectEntry(64, 65, "date", "Mon, 21 Oct 2013 20:13:21 GMT"); + expectEntry(65, 52, "cache-control", "private"); + EXPECT_EQ(222u, decoder_peer_.current_header_table_size()); + + // 88 | == Indexed - Add == + // | idx = 8 + // | -> :status: 200 + // c1 | == Indexed - Add == + // | idx = 65 + // | -> cache-control: private + // 61 | == Literal indexed == + // | Indexed name (idx = 33) + // | date + // 96 | Literal value (len = 22) + // | Huffman encoded: + // d07a be94 1054 d444 a820 0595 040b 8166 | .z...T.D. .....f + // e084 a62d 1bff | ...-.. + // | Decoded: + // | Mon, 21 Oct 2013 20:13:22 + // | GMT + // | - evict: cache-control: + // | private + // | -> date: Mon, 21 Oct 2013 + // | 20:13:22 GMT + // c0 | == Indexed - Add == + // | idx = 64 + // | -> location: + // | https://www.example.com + // 5a | == Literal indexed == + // | Indexed name (idx = 26) + // | content-encoding + // 83 | Literal value (len = 3) + // | Huffman encoded: + // 9bd9 ab | ... + // | Decoded: + // | gzip + // | - evict: date: Mon, 21 Oct + // | 2013 20:13:21 GMT + // | -> content-encoding: gzip + // 77 | == Literal indexed == + // | Indexed name (idx = 55) + // | set-cookie + // ad | Literal value (len = 45) + // | Huffman encoded: + // 94e7 821d d7f2 e6c7 b335 dfdf cd5b 3960 | .........5...[9` + // d5af 2708 7f36 72c1 ab27 0fb5 291f 9587 | ..'..6r..'..)... + // 3160 65c0 03ed 4ee5 b106 3d50 07 | 1`e...N...=P. + // | Decoded: + // | foo=ASDJKHQKBZXOQWEOPIUAXQ + // | WEOIU; max-age=3600; versi + // | on=1 + // | - evict: location: + // | https://www.example.com + // | - evict: :status: 307 + // | -> set-cookie: foo=ASDJKHQ + // | KBZXOQWEOPIUAXQWEOIU; + // | max-age=3600; version=1 + SpdyString third = SpdyHexDecode( + "88c16196d07abe941054d444a8200595" + "040b8166e084a62d1bffc05a839bd9ab" + "77ad94e7821dd7f2e6c7b335dfdfcd5b" + "3960d5af27087f3672c1ab270fb5291f" + "9587316065c003ed4ee5b1063d5007"); + const SpdyHeaderBlock& third_header_set = DecodeBlockExpectingSuccess(third); + + EXPECT_THAT(third_header_set, + ElementsAre( + // clang-format off + Pair(":status", "200"), + Pair("cache-control", "private"), + Pair("date", "Mon, 21 Oct 2013 20:13:22 GMT"), + Pair("location", "https://www.example.com"), + Pair("content-encoding", "gzip"), + Pair("set-cookie", "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU;" + " max-age=3600; version=1"))); + // clang-format on + + expectEntry(62, 98, "set-cookie", + "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU;" + " max-age=3600; version=1"); + expectEntry(63, 52, "content-encoding", "gzip"); + expectEntry(64, 65, "date", "Mon, 21 Oct 2013 20:13:22 GMT"); + EXPECT_EQ(215u, decoder_peer_.current_header_table_size()); +} + +// Regression test: Found that entries with dynamic indexed names and literal +// values caused "use after free" MSAN failures if the name was evicted as it +// was being re-used. +TEST_P(HpackDecoderAdapterTest, ReuseNameOfEvictedEntry) { + // Each entry is measured as 32 bytes plus the sum of the lengths of the name + // and the value. Set the size big enough for at most one entry, and a fairly + // small one at that (31 ASCII characters). + decoder_.ApplyHeaderTableSizeSetting(63); + + HpackBlockBuilder hbb; + hbb.AppendDynamicTableSizeUpdate(0); + hbb.AppendDynamicTableSizeUpdate(63); + + const SpdyStringPiece name("some-name"); + const SpdyStringPiece value1("some-value"); + const SpdyStringPiece value2("another-value"); + const SpdyStringPiece value3("yet-another-value"); + + // Add an entry that will become the first in the dynamic table, entry 62. + hbb.AppendLiteralNameAndValue(HpackEntryType::kIndexedLiteralHeader, false, + name, false, value1); + + // Confirm that entry has been added by re-using it. + hbb.AppendIndexedHeader(62); + + // Add another entry referring to the name of the first. This will evict the + // first. + hbb.AppendNameIndexAndLiteralValue(HpackEntryType::kIndexedLiteralHeader, 62, + false, value2); + + // Confirm that entry has been added by re-using it. + hbb.AppendIndexedHeader(62); + + // Add another entry referring to the name of the second. This will evict the + // second. + hbb.AppendNameIndexAndLiteralValue(HpackEntryType::kIndexedLiteralHeader, 62, + false, value3); + + // Confirm that entry has been added by re-using it. + hbb.AppendIndexedHeader(62); + + // Can't have DecodeHeaderBlock do the default check for size of the decoded + // data because SpdyHeaderBlock will join multiple headers with the same + // name into a single entry, thus we won't see repeated occurrences of the + // name, instead seeing separators between values. + EXPECT_TRUE(DecodeHeaderBlock(hbb.buffer(), kNoCheckDecodedSize)); + + SpdyHeaderBlock expected_header_set; + expected_header_set.AppendValueOrAddHeader(name, value1); + expected_header_set.AppendValueOrAddHeader(name, value1); + expected_header_set.AppendValueOrAddHeader(name, value2); + expected_header_set.AppendValueOrAddHeader(name, value2); + expected_header_set.AppendValueOrAddHeader(name, value3); + expected_header_set.AppendValueOrAddHeader(name, value3); + + // SpdyHeaderBlock stores these 6 strings as '\0' separated values. + // Make sure that is what happened. + SpdyString joined_values = expected_header_set[name].as_string(); + EXPECT_EQ(joined_values.size(), + 2 * value1.size() + 2 * value2.size() + 2 * value3.size() + 5); + + EXPECT_EQ(expected_header_set, decoded_block()); + + if (start_choice_ == START_WITH_HANDLER) { + EXPECT_EQ(handler_.header_bytes_parsed(), + 6 * name.size() + 2 * value1.size() + 2 * value2.size() + + 2 * value3.size()); + } +} + +// Regression test for https://crbug.com/747395. +TEST_P(HpackDecoderAdapterTest, Cookies) { + SpdyHeaderBlock expected_header_set; + expected_header_set["cookie"] = "foo; bar"; + + EXPECT_TRUE(DecodeHeaderBlock(SpdyHexDecode("608294e76003626172"))); + EXPECT_EQ(expected_header_set, decoded_block()); +} + +} // namespace +} // namespace test +} // namespace spdy diff --git a/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_encoder.cc b/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_encoder.cc new file mode 100644 index 00000000000..a9981b95a73 --- /dev/null +++ b/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_encoder.cc @@ -0,0 +1,365 @@ +// Copyright 2014 The Chromium 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/spdy/core/hpack/hpack_encoder.h" + +#include <algorithm> +#include <limits> + +#include "base/logging.h" +#include "net/third_party/quiche/src/spdy/core/hpack/hpack_constants.h" +#include "net/third_party/quiche/src/spdy/core/hpack/hpack_header_table.h" +#include "net/third_party/quiche/src/spdy/core/hpack/hpack_huffman_table.h" +#include "net/third_party/quiche/src/spdy/core/hpack/hpack_output_stream.h" +#include "net/third_party/quiche/src/spdy/platform/api/spdy_estimate_memory_usage.h" +#include "net/third_party/quiche/src/spdy/platform/api/spdy_ptr_util.h" + +namespace spdy { + +class HpackEncoder::RepresentationIterator { + public: + // |pseudo_headers| and |regular_headers| must outlive the iterator. + RepresentationIterator(const Representations& pseudo_headers, + const Representations& regular_headers) + : pseudo_begin_(pseudo_headers.begin()), + pseudo_end_(pseudo_headers.end()), + regular_begin_(regular_headers.begin()), + regular_end_(regular_headers.end()) {} + + // |headers| must outlive the iterator. + explicit RepresentationIterator(const Representations& headers) + : pseudo_begin_(headers.begin()), + pseudo_end_(headers.end()), + regular_begin_(headers.end()), + regular_end_(headers.end()) {} + + bool HasNext() { + return pseudo_begin_ != pseudo_end_ || regular_begin_ != regular_end_; + } + + const Representation Next() { + if (pseudo_begin_ != pseudo_end_) { + return *pseudo_begin_++; + } else { + return *regular_begin_++; + } + } + + private: + Representations::const_iterator pseudo_begin_; + Representations::const_iterator pseudo_end_; + Representations::const_iterator regular_begin_; + Representations::const_iterator regular_end_; +}; + +namespace { + +// The default header listener. +void NoOpListener(SpdyStringPiece /*name*/, SpdyStringPiece /*value*/) {} + +// The default HPACK indexing policy. +bool DefaultPolicy(SpdyStringPiece name, SpdyStringPiece /* value */) { + if (name.empty()) { + return false; + } + // :authority is always present and rarely changes, and has moderate + // length, therefore it makes a lot of sense to index (insert in the + // dynamic table). + if (name[0] == kPseudoHeaderPrefix) { + return name == ":authority"; + } + return true; +} + +} // namespace + +HpackEncoder::HpackEncoder(const HpackHuffmanTable& table) + : output_stream_(), + huffman_table_(table), + min_table_size_setting_received_(std::numeric_limits<size_t>::max()), + listener_(NoOpListener), + should_index_(DefaultPolicy), + enable_compression_(true), + should_emit_table_size_(false) {} + +HpackEncoder::~HpackEncoder() = default; + +bool HpackEncoder::EncodeHeaderSet(const SpdyHeaderBlock& header_set, + SpdyString* output) { + // Separate header set into pseudo-headers and regular headers. + Representations pseudo_headers; + Representations regular_headers; + bool found_cookie = false; + for (const auto& header : header_set) { + if (!found_cookie && header.first == "cookie") { + // Note that there can only be one "cookie" header, because header_set is + // a map. + found_cookie = true; + CookieToCrumbs(header, ®ular_headers); + } else if (!header.first.empty() && + header.first[0] == kPseudoHeaderPrefix) { + DecomposeRepresentation(header, &pseudo_headers); + } else { + DecomposeRepresentation(header, ®ular_headers); + } + } + + { + RepresentationIterator iter(pseudo_headers, regular_headers); + EncodeRepresentations(&iter, output); + } + return true; +} + +void HpackEncoder::ApplyHeaderTableSizeSetting(size_t size_setting) { + if (size_setting == header_table_.settings_size_bound()) { + return; + } + if (size_setting < header_table_.settings_size_bound()) { + min_table_size_setting_received_ = + std::min(size_setting, min_table_size_setting_received_); + } + header_table_.SetSettingsHeaderTableSize(size_setting); + should_emit_table_size_ = true; +} + +size_t HpackEncoder::EstimateMemoryUsage() const { + // |huffman_table_| is a singleton. It's accounted for in spdy_session_pool.cc + return SpdyEstimateMemoryUsage(header_table_) + + SpdyEstimateMemoryUsage(output_stream_); +} + +void HpackEncoder::EncodeRepresentations(RepresentationIterator* iter, + SpdyString* output) { + MaybeEmitTableSize(); + while (iter->HasNext()) { + const auto header = iter->Next(); + listener_(header.first, header.second); + if (enable_compression_) { + const HpackEntry* entry = + header_table_.GetByNameAndValue(header.first, header.second); + if (entry != nullptr) { + EmitIndex(entry); + } else if (should_index_(header.first, header.second)) { + EmitIndexedLiteral(header); + } else { + EmitNonIndexedLiteral(header); + } + } else { + EmitNonIndexedLiteral(header); + } + } + + output_stream_.TakeString(output); +} + +void HpackEncoder::EmitIndex(const HpackEntry* entry) { + DVLOG(2) << "Emitting index " << header_table_.IndexOf(entry); + output_stream_.AppendPrefix(kIndexedOpcode); + output_stream_.AppendUint32(header_table_.IndexOf(entry)); +} + +void HpackEncoder::EmitIndexedLiteral(const Representation& representation) { + DVLOG(2) << "Emitting indexed literal: (" << representation.first << ", " + << representation.second << ")"; + output_stream_.AppendPrefix(kLiteralIncrementalIndexOpcode); + EmitLiteral(representation); + header_table_.TryAddEntry(representation.first, representation.second); +} + +void HpackEncoder::EmitNonIndexedLiteral(const Representation& representation) { + DVLOG(2) << "Emitting nonindexed literal: (" << representation.first << ", " + << representation.second << ")"; + output_stream_.AppendPrefix(kLiteralNoIndexOpcode); + output_stream_.AppendUint32(0); + EmitString(representation.first); + EmitString(representation.second); +} + +void HpackEncoder::EmitLiteral(const Representation& representation) { + const HpackEntry* name_entry = header_table_.GetByName(representation.first); + if (name_entry != nullptr) { + output_stream_.AppendUint32(header_table_.IndexOf(name_entry)); + } else { + output_stream_.AppendUint32(0); + EmitString(representation.first); + } + EmitString(representation.second); +} + +void HpackEncoder::EmitString(SpdyStringPiece str) { + size_t encoded_size = + enable_compression_ ? huffman_table_.EncodedSize(str) : str.size(); + if (encoded_size < str.size()) { + DVLOG(2) << "Emitted Huffman-encoded string of length " << encoded_size; + output_stream_.AppendPrefix(kStringLiteralHuffmanEncoded); + output_stream_.AppendUint32(encoded_size); + huffman_table_.EncodeString(str, &output_stream_); + } else { + DVLOG(2) << "Emitted literal string of length " << str.size(); + output_stream_.AppendPrefix(kStringLiteralIdentityEncoded); + output_stream_.AppendUint32(str.size()); + output_stream_.AppendBytes(str); + } +} + +void HpackEncoder::MaybeEmitTableSize() { + if (!should_emit_table_size_) { + return; + } + const size_t current_size = CurrentHeaderTableSizeSetting(); + DVLOG(1) << "MaybeEmitTableSize current_size=" << current_size; + DVLOG(1) << "MaybeEmitTableSize min_table_size_setting_received_=" + << min_table_size_setting_received_; + if (min_table_size_setting_received_ < current_size) { + output_stream_.AppendPrefix(kHeaderTableSizeUpdateOpcode); + output_stream_.AppendUint32(min_table_size_setting_received_); + } + output_stream_.AppendPrefix(kHeaderTableSizeUpdateOpcode); + output_stream_.AppendUint32(current_size); + min_table_size_setting_received_ = std::numeric_limits<size_t>::max(); + should_emit_table_size_ = false; +} + +// static +void HpackEncoder::CookieToCrumbs(const Representation& cookie, + Representations* out) { + // See Section 8.1.2.5. "Compressing the Cookie Header Field" in the HTTP/2 + // specification at https://tools.ietf.org/html/draft-ietf-httpbis-http2-14. + // Cookie values are split into individually-encoded HPACK representations. + SpdyStringPiece cookie_value = cookie.second; + // Consume leading and trailing whitespace if present. + SpdyStringPiece::size_type first = cookie_value.find_first_not_of(" \t"); + SpdyStringPiece::size_type last = cookie_value.find_last_not_of(" \t"); + if (first == SpdyStringPiece::npos) { + cookie_value = SpdyStringPiece(); + } else { + cookie_value = cookie_value.substr(first, (last - first) + 1); + } + for (size_t pos = 0;;) { + size_t end = cookie_value.find(";", pos); + + if (end == SpdyStringPiece::npos) { + out->push_back(std::make_pair(cookie.first, cookie_value.substr(pos))); + break; + } + out->push_back( + std::make_pair(cookie.first, cookie_value.substr(pos, end - pos))); + + // Consume next space if present. + pos = end + 1; + if (pos != cookie_value.size() && cookie_value[pos] == ' ') { + pos++; + } + } +} + +// static +void HpackEncoder::DecomposeRepresentation(const Representation& header_field, + Representations* out) { + size_t pos = 0; + size_t end = 0; + while (end != SpdyStringPiece::npos) { + end = header_field.second.find('\0', pos); + out->push_back(std::make_pair( + header_field.first, + header_field.second.substr( + pos, end == SpdyStringPiece::npos ? end : end - pos))); + pos = end + 1; + } +} + +// static +void HpackEncoder::GatherRepresentation(const Representation& header_field, + Representations* out) { + out->push_back(std::make_pair(header_field.first, header_field.second)); +} + +// Iteratively encodes a SpdyHeaderBlock. +class HpackEncoder::Encoderator : public ProgressiveEncoder { + public: + Encoderator(const SpdyHeaderBlock& header_set, HpackEncoder* encoder); + + // Encoderator is neither copyable nor movable. + Encoderator(const Encoderator&) = delete; + Encoderator& operator=(const Encoderator&) = delete; + + // Returns true iff more remains to encode. + bool HasNext() const override { return has_next_; } + + // Encodes up to max_encoded_bytes of the current header block into the + // given output string. + void Next(size_t max_encoded_bytes, SpdyString* output) override; + + private: + HpackEncoder* encoder_; + std::unique_ptr<RepresentationIterator> header_it_; + Representations pseudo_headers_; + Representations regular_headers_; + bool has_next_; +}; + +HpackEncoder::Encoderator::Encoderator(const SpdyHeaderBlock& header_set, + HpackEncoder* encoder) + : encoder_(encoder), has_next_(true) { + // Separate header set into pseudo-headers and regular headers. + const bool use_compression = encoder_->enable_compression_; + bool found_cookie = false; + for (const auto& header : header_set) { + if (!found_cookie && header.first == "cookie") { + // Note that there can only be one "cookie" header, because header_set + // is a map. + found_cookie = true; + CookieToCrumbs(header, ®ular_headers_); + } else if (!header.first.empty() && + header.first[0] == kPseudoHeaderPrefix) { + use_compression ? DecomposeRepresentation(header, &pseudo_headers_) + : GatherRepresentation(header, &pseudo_headers_); + } else { + use_compression ? DecomposeRepresentation(header, ®ular_headers_) + : GatherRepresentation(header, ®ular_headers_); + } + } + header_it_ = + SpdyMakeUnique<RepresentationIterator>(pseudo_headers_, regular_headers_); + + encoder_->MaybeEmitTableSize(); +} + +void HpackEncoder::Encoderator::Next(size_t max_encoded_bytes, + SpdyString* output) { + SPDY_BUG_IF(!has_next_) + << "Encoderator::Next called with nothing left to encode."; + const bool use_compression = encoder_->enable_compression_; + + // Encode up to max_encoded_bytes of headers. + while (header_it_->HasNext() && + encoder_->output_stream_.size() <= max_encoded_bytes) { + const Representation header = header_it_->Next(); + encoder_->listener_(header.first, header.second); + if (use_compression) { + const HpackEntry* entry = encoder_->header_table_.GetByNameAndValue( + header.first, header.second); + if (entry != nullptr) { + encoder_->EmitIndex(entry); + } else if (encoder_->should_index_(header.first, header.second)) { + encoder_->EmitIndexedLiteral(header); + } else { + encoder_->EmitNonIndexedLiteral(header); + } + } else { + encoder_->EmitNonIndexedLiteral(header); + } + } + + has_next_ = encoder_->output_stream_.size() > max_encoded_bytes; + encoder_->output_stream_.BoundedTakeString(max_encoded_bytes, output); +} + +std::unique_ptr<HpackEncoder::ProgressiveEncoder> HpackEncoder::EncodeHeaderSet( + const SpdyHeaderBlock& header_set) { + return SpdyMakeUnique<Encoderator>(header_set, this); +} + +} // namespace spdy diff --git a/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_encoder.h b/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_encoder.h new file mode 100644 index 00000000000..486d53693e2 --- /dev/null +++ b/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_encoder.h @@ -0,0 +1,152 @@ +// Copyright 2014 The Chromium 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_SPDY_CORE_HPACK_HPACK_ENCODER_H_ +#define QUICHE_SPDY_CORE_HPACK_HPACK_ENCODER_H_ + +#include <stddef.h> + +#include <functional> +#include <map> +#include <memory> +#include <utility> +#include <vector> + +#include "base/macros.h" +#include "net/third_party/quiche/src/spdy/core/hpack/hpack_header_table.h" +#include "net/third_party/quiche/src/spdy/core/hpack/hpack_output_stream.h" +#include "net/third_party/quiche/src/spdy/core/spdy_protocol.h" +#include "net/third_party/quiche/src/spdy/platform/api/spdy_export.h" +#include "net/third_party/quiche/src/spdy/platform/api/spdy_string.h" +#include "net/third_party/quiche/src/spdy/platform/api/spdy_string_piece.h" + +// An HpackEncoder encodes header sets as outlined in +// http://tools.ietf.org/html/rfc7541. + +namespace spdy { + +class HpackHuffmanTable; + +namespace test { +class HpackEncoderPeer; +} // namespace test + +class SPDY_EXPORT_PRIVATE HpackEncoder { + public: + using Representation = std::pair<SpdyStringPiece, SpdyStringPiece>; + using Representations = std::vector<Representation>; + + // Callers may provide a HeaderListener to be informed of header name-value + // pairs processed by this encoder. + using HeaderListener = std::function<void(SpdyStringPiece, SpdyStringPiece)>; + + // An indexing policy should return true if the provided header name-value + // pair should be inserted into the HPACK dynamic table. + using IndexingPolicy = std::function<bool(SpdyStringPiece, SpdyStringPiece)>; + + // |table| is an initialized HPACK Huffman table, having an + // externally-managed lifetime which spans beyond HpackEncoder. + explicit HpackEncoder(const HpackHuffmanTable& table); + HpackEncoder(const HpackEncoder&) = delete; + HpackEncoder& operator=(const HpackEncoder&) = delete; + ~HpackEncoder(); + + // Encodes the given header set into the given string. Returns + // whether or not the encoding was successful. + bool EncodeHeaderSet(const SpdyHeaderBlock& header_set, SpdyString* output); + + class SPDY_EXPORT_PRIVATE ProgressiveEncoder { + public: + virtual ~ProgressiveEncoder() {} + + // Returns true iff more remains to encode. + virtual bool HasNext() const = 0; + + // Encodes up to max_encoded_bytes of the current header block into the + // given output string. + virtual void Next(size_t max_encoded_bytes, SpdyString* output) = 0; + }; + + // Returns a ProgressiveEncoder which must be outlived by both the given + // SpdyHeaderBlock and this object. + std::unique_ptr<ProgressiveEncoder> EncodeHeaderSet( + const SpdyHeaderBlock& header_set); + + // Called upon a change to SETTINGS_HEADER_TABLE_SIZE. Specifically, this + // is to be called after receiving (and sending an acknowledgement for) a + // SETTINGS_HEADER_TABLE_SIZE update from the remote decoding endpoint. + void ApplyHeaderTableSizeSetting(size_t size_setting); + + size_t CurrentHeaderTableSizeSetting() const { + return header_table_.settings_size_bound(); + } + + // This HpackEncoder will use |policy| to determine whether to insert header + // name-value pairs into the dynamic table. + void SetIndexingPolicy(IndexingPolicy policy) { should_index_ = policy; } + + // |listener| will be invoked for each header name-value pair processed by + // this encoder. + void SetHeaderListener(HeaderListener listener) { listener_ = listener; } + + void SetHeaderTableDebugVisitor( + std::unique_ptr<HpackHeaderTable::DebugVisitorInterface> visitor) { + header_table_.set_debug_visitor(std::move(visitor)); + } + + void DisableCompression() { enable_compression_ = false; } + + // Returns the estimate of dynamically allocated memory in bytes. + size_t EstimateMemoryUsage() const; + + private: + friend class test::HpackEncoderPeer; + + class RepresentationIterator; + class Encoderator; + + // Encodes a sequence of header name-value pairs as a single header block. + void EncodeRepresentations(RepresentationIterator* iter, SpdyString* output); + + // Emits a static/dynamic indexed representation (Section 7.1). + void EmitIndex(const HpackEntry* entry); + + // Emits a literal representation (Section 7.2). + void EmitIndexedLiteral(const Representation& representation); + void EmitNonIndexedLiteral(const Representation& representation); + void EmitLiteral(const Representation& representation); + + // Emits a Huffman or identity string (whichever is smaller). + void EmitString(SpdyStringPiece str); + + // Emits the current dynamic table size if the table size was recently + // updated and we have not yet emitted it (Section 6.3). + void MaybeEmitTableSize(); + + // Crumbles a cookie header into ";" delimited crumbs. + static void CookieToCrumbs(const Representation& cookie, + Representations* crumbs_out); + + // Crumbles other header field values at \0 delimiters. + static void DecomposeRepresentation(const Representation& header_field, + Representations* out); + + // Gathers headers without crumbling. Used when compression is not enabled. + static void GatherRepresentation(const Representation& header_field, + Representations* out); + + HpackHeaderTable header_table_; + HpackOutputStream output_stream_; + + const HpackHuffmanTable& huffman_table_; + size_t min_table_size_setting_received_; + HeaderListener listener_; + IndexingPolicy should_index_; + bool enable_compression_; + bool should_emit_table_size_; +}; + +} // namespace spdy + +#endif // QUICHE_SPDY_CORE_HPACK_HPACK_ENCODER_H_ diff --git a/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_encoder_test.cc b/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_encoder_test.cc new file mode 100644 index 00000000000..8ead1c278b1 --- /dev/null +++ b/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_encoder_test.cc @@ -0,0 +1,586 @@ +// Copyright 2014 The Chromium 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/spdy/core/hpack/hpack_encoder.h" + +#include <cstdint> +#include <map> + +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "net/third_party/quiche/src/http2/test_tools/http2_random.h" +#include "net/third_party/quiche/src/spdy/core/hpack/hpack_huffman_table.h" +#include "net/third_party/quiche/src/spdy/platform/api/spdy_unsafe_arena.h" + +namespace spdy { + +namespace test { + +class HpackHeaderTablePeer { + public: + explicit HpackHeaderTablePeer(HpackHeaderTable* table) : table_(table) {} + + HpackHeaderTable::EntryTable* dynamic_entries() { + return &table_->dynamic_entries_; + } + + private: + HpackHeaderTable* table_; +}; + +class HpackEncoderPeer { + public: + typedef HpackEncoder::Representation Representation; + typedef HpackEncoder::Representations Representations; + + explicit HpackEncoderPeer(HpackEncoder* encoder) : encoder_(encoder) {} + + bool compression_enabled() const { return encoder_->enable_compression_; } + HpackHeaderTable* table() { return &encoder_->header_table_; } + HpackHeaderTablePeer table_peer() { return HpackHeaderTablePeer(table()); } + const HpackHuffmanTable& huffman_table() const { + return encoder_->huffman_table_; + } + void EmitString(SpdyStringPiece str) { encoder_->EmitString(str); } + void TakeString(SpdyString* out) { encoder_->output_stream_.TakeString(out); } + static void CookieToCrumbs(SpdyStringPiece cookie, + std::vector<SpdyStringPiece>* out) { + Representations tmp; + HpackEncoder::CookieToCrumbs(std::make_pair("", cookie), &tmp); + + out->clear(); + for (size_t i = 0; i != tmp.size(); ++i) { + out->push_back(tmp[i].second); + } + } + static void DecomposeRepresentation(SpdyStringPiece value, + std::vector<SpdyStringPiece>* out) { + Representations tmp; + HpackEncoder::DecomposeRepresentation(std::make_pair("foobar", value), + &tmp); + + out->clear(); + for (size_t i = 0; i != tmp.size(); ++i) { + out->push_back(tmp[i].second); + } + } + + // TODO(dahollings): Remove or clean up these methods when deprecating + // non-incremental encoding path. + static bool EncodeHeaderSet(HpackEncoder* encoder, + const SpdyHeaderBlock& header_set, + SpdyString* output, + bool use_incremental) { + if (use_incremental) { + return EncodeIncremental(encoder, header_set, output); + } else { + return encoder->EncodeHeaderSet(header_set, output); + } + } + + static bool EncodeIncremental(HpackEncoder* encoder, + const SpdyHeaderBlock& header_set, + SpdyString* output) { + std::unique_ptr<HpackEncoder::ProgressiveEncoder> encoderator = + encoder->EncodeHeaderSet(header_set); + SpdyString output_buffer; + http2::test::Http2Random random; + encoderator->Next(random.UniformInRange(0, 16), &output_buffer); + while (encoderator->HasNext()) { + SpdyString second_buffer; + encoderator->Next(random.UniformInRange(0, 16), &second_buffer); + output_buffer.append(second_buffer); + } + *output = std::move(output_buffer); + return true; + } + + private: + HpackEncoder* encoder_; +}; + +} // namespace test + +namespace { + +using testing::ElementsAre; +using testing::Pair; + +class HpackEncoderTest : public ::testing::TestWithParam<bool> { + protected: + typedef test::HpackEncoderPeer::Representations Representations; + + HpackEncoderTest() + : encoder_(ObtainHpackHuffmanTable()), + peer_(&encoder_), + static_(peer_.table()->GetByIndex(1)), + headers_storage_(1024 /* block size */) {} + + void SetUp() override { + use_incremental_ = GetParam(); + + // Populate dynamic entries into the table fixture. For simplicity each + // entry has name.size() + value.size() == 10. + key_1_ = peer_.table()->TryAddEntry("key1", "value1"); + key_2_ = peer_.table()->TryAddEntry("key2", "value2"); + cookie_a_ = peer_.table()->TryAddEntry("cookie", "a=bb"); + cookie_c_ = peer_.table()->TryAddEntry("cookie", "c=dd"); + + // No further insertions may occur without evictions. + peer_.table()->SetMaxSize(peer_.table()->size()); + } + + void SaveHeaders(SpdyStringPiece name, SpdyStringPiece value) { + SpdyStringPiece n(headers_storage_.Memdup(name.data(), name.size()), + name.size()); + SpdyStringPiece v(headers_storage_.Memdup(value.data(), value.size()), + value.size()); + headers_observed_.push_back(std::make_pair(n, v)); + } + + void ExpectIndex(size_t index) { + expected_.AppendPrefix(kIndexedOpcode); + expected_.AppendUint32(index); + } + void ExpectIndexedLiteral(const HpackEntry* key_entry, + SpdyStringPiece value) { + expected_.AppendPrefix(kLiteralIncrementalIndexOpcode); + expected_.AppendUint32(IndexOf(key_entry)); + ExpectString(&expected_, value); + } + void ExpectIndexedLiteral(SpdyStringPiece name, SpdyStringPiece value) { + expected_.AppendPrefix(kLiteralIncrementalIndexOpcode); + expected_.AppendUint32(0); + ExpectString(&expected_, name); + ExpectString(&expected_, value); + } + void ExpectNonIndexedLiteral(SpdyStringPiece name, SpdyStringPiece value) { + expected_.AppendPrefix(kLiteralNoIndexOpcode); + expected_.AppendUint32(0); + ExpectString(&expected_, name); + ExpectString(&expected_, value); + } + void ExpectString(HpackOutputStream* stream, SpdyStringPiece str) { + const HpackHuffmanTable& huffman_table = peer_.huffman_table(); + size_t encoded_size = peer_.compression_enabled() + ? huffman_table.EncodedSize(str) + : str.size(); + if (encoded_size < str.size()) { + expected_.AppendPrefix(kStringLiteralHuffmanEncoded); + expected_.AppendUint32(encoded_size); + huffman_table.EncodeString(str, stream); + } else { + expected_.AppendPrefix(kStringLiteralIdentityEncoded); + expected_.AppendUint32(str.size()); + expected_.AppendBytes(str); + } + } + void ExpectHeaderTableSizeUpdate(uint32_t size) { + expected_.AppendPrefix(kHeaderTableSizeUpdateOpcode); + expected_.AppendUint32(size); + } + void CompareWithExpectedEncoding(const SpdyHeaderBlock& header_set) { + SpdyString expected_out, actual_out; + expected_.TakeString(&expected_out); + EXPECT_TRUE(test::HpackEncoderPeer::EncodeHeaderSet( + &encoder_, header_set, &actual_out, use_incremental_)); + EXPECT_EQ(expected_out, actual_out); + } + size_t IndexOf(const HpackEntry* entry) { + return peer_.table()->IndexOf(entry); + } + + HpackEncoder encoder_; + test::HpackEncoderPeer peer_; + + const HpackEntry* static_; + const HpackEntry* key_1_; + const HpackEntry* key_2_; + const HpackEntry* cookie_a_; + const HpackEntry* cookie_c_; + + SpdyUnsafeArena headers_storage_; + std::vector<std::pair<SpdyStringPiece, SpdyStringPiece>> headers_observed_; + + HpackOutputStream expected_; + bool use_incremental_; +}; + +INSTANTIATE_TEST_CASE_P(HpackEncoderTests, HpackEncoderTest, ::testing::Bool()); + +TEST_P(HpackEncoderTest, SingleDynamicIndex) { + encoder_.SetHeaderListener( + [this](SpdyStringPiece name, SpdyStringPiece value) { + this->SaveHeaders(name, value); + }); + + ExpectIndex(IndexOf(key_2_)); + + SpdyHeaderBlock headers; + headers[key_2_->name()] = key_2_->value(); + CompareWithExpectedEncoding(headers); + EXPECT_THAT(headers_observed_, + ElementsAre(Pair(key_2_->name(), key_2_->value()))); +} + +TEST_P(HpackEncoderTest, SingleStaticIndex) { + ExpectIndex(IndexOf(static_)); + + SpdyHeaderBlock headers; + headers[static_->name()] = static_->value(); + CompareWithExpectedEncoding(headers); +} + +TEST_P(HpackEncoderTest, SingleStaticIndexTooLarge) { + peer_.table()->SetMaxSize(1); // Also evicts all fixtures. + ExpectIndex(IndexOf(static_)); + + SpdyHeaderBlock headers; + headers[static_->name()] = static_->value(); + CompareWithExpectedEncoding(headers); + + EXPECT_EQ(0u, peer_.table_peer().dynamic_entries()->size()); +} + +TEST_P(HpackEncoderTest, SingleLiteralWithIndexName) { + ExpectIndexedLiteral(key_2_, "value3"); + + SpdyHeaderBlock headers; + headers[key_2_->name()] = "value3"; + CompareWithExpectedEncoding(headers); + + // A new entry was inserted and added to the reference set. + HpackEntry* new_entry = &peer_.table_peer().dynamic_entries()->front(); + EXPECT_EQ(new_entry->name(), key_2_->name()); + EXPECT_EQ(new_entry->value(), "value3"); +} + +TEST_P(HpackEncoderTest, SingleLiteralWithLiteralName) { + ExpectIndexedLiteral("key3", "value3"); + + SpdyHeaderBlock headers; + headers["key3"] = "value3"; + CompareWithExpectedEncoding(headers); + + HpackEntry* new_entry = &peer_.table_peer().dynamic_entries()->front(); + EXPECT_EQ(new_entry->name(), "key3"); + EXPECT_EQ(new_entry->value(), "value3"); +} + +TEST_P(HpackEncoderTest, SingleLiteralTooLarge) { + peer_.table()->SetMaxSize(1); // Also evicts all fixtures. + + ExpectIndexedLiteral("key3", "value3"); + + // A header overflowing the header table is still emitted. + // The header table is empty. + SpdyHeaderBlock headers; + headers["key3"] = "value3"; + CompareWithExpectedEncoding(headers); + + EXPECT_EQ(0u, peer_.table_peer().dynamic_entries()->size()); +} + +TEST_P(HpackEncoderTest, EmitThanEvict) { + // |key_1_| is toggled and placed into the reference set, + // and then immediately evicted by "key3". + ExpectIndex(IndexOf(key_1_)); + ExpectIndexedLiteral("key3", "value3"); + + SpdyHeaderBlock headers; + headers[key_1_->name()] = key_1_->value(); + headers["key3"] = "value3"; + CompareWithExpectedEncoding(headers); +} + +TEST_P(HpackEncoderTest, CookieHeaderIsCrumbled) { + ExpectIndex(IndexOf(cookie_a_)); + ExpectIndex(IndexOf(cookie_c_)); + ExpectIndexedLiteral(peer_.table()->GetByName("cookie"), "e=ff"); + + SpdyHeaderBlock headers; + headers["cookie"] = "a=bb; c=dd; e=ff"; + CompareWithExpectedEncoding(headers); +} + +TEST_P(HpackEncoderTest, StringsDynamicallySelectHuffmanCoding) { + // Compactable string. Uses Huffman coding. + peer_.EmitString("feedbeef"); + expected_.AppendPrefix(kStringLiteralHuffmanEncoded); + expected_.AppendUint32(6); + expected_.AppendBytes("\x94\xA5\x92\x32\x96_"); + + // Non-compactable. Uses identity coding. + peer_.EmitString("@@@@@@"); + expected_.AppendPrefix(kStringLiteralIdentityEncoded); + expected_.AppendUint32(6); + expected_.AppendBytes("@@@@@@"); + + SpdyString expected_out, actual_out; + expected_.TakeString(&expected_out); + peer_.TakeString(&actual_out); + EXPECT_EQ(expected_out, actual_out); +} + +TEST_P(HpackEncoderTest, EncodingWithoutCompression) { + encoder_.SetHeaderListener( + [this](SpdyStringPiece name, SpdyStringPiece value) { + this->SaveHeaders(name, value); + }); + encoder_.DisableCompression(); + + ExpectNonIndexedLiteral(":path", "/index.html"); + ExpectNonIndexedLiteral("cookie", "foo=bar"); + ExpectNonIndexedLiteral("cookie", "baz=bing"); + ExpectNonIndexedLiteral("hello", "goodbye"); + + SpdyHeaderBlock headers; + headers[":path"] = "/index.html"; + headers["cookie"] = "foo=bar; baz=bing"; + headers["hello"] = "goodbye"; + + CompareWithExpectedEncoding(headers); + + EXPECT_THAT( + headers_observed_, + ElementsAre(Pair(":path", "/index.html"), Pair("cookie", "foo=bar"), + Pair("cookie", "baz=bing"), Pair("hello", "goodbye"))); +} + +TEST_P(HpackEncoderTest, MultipleEncodingPasses) { + encoder_.SetHeaderListener( + [this](SpdyStringPiece name, SpdyStringPiece value) { + this->SaveHeaders(name, value); + }); + + // Pass 1. + { + SpdyHeaderBlock headers; + headers["key1"] = "value1"; + headers["cookie"] = "a=bb"; + + ExpectIndex(IndexOf(key_1_)); + ExpectIndex(IndexOf(cookie_a_)); + CompareWithExpectedEncoding(headers); + } + // Header table is: + // 65: key1: value1 + // 64: key2: value2 + // 63: cookie: a=bb + // 62: cookie: c=dd + // Pass 2. + { + SpdyHeaderBlock headers; + headers["key2"] = "value2"; + headers["cookie"] = "c=dd; e=ff"; + + // "key2: value2" + ExpectIndex(64); + // "cookie: c=dd" + ExpectIndex(62); + // This cookie evicts |key1| from the dynamic table. + ExpectIndexedLiteral(peer_.table()->GetByName("cookie"), "e=ff"); + + CompareWithExpectedEncoding(headers); + } + // Header table is: + // 65: key2: value2 + // 64: cookie: a=bb + // 63: cookie: c=dd + // 62: cookie: e=ff + // Pass 3. + { + SpdyHeaderBlock headers; + headers["key2"] = "value2"; + headers["cookie"] = "a=bb; b=cc; c=dd"; + + // "key2: value2" + ExpectIndex(65); + // "cookie: a=bb" + ExpectIndex(64); + // This cookie evicts |key2| from the dynamic table. + ExpectIndexedLiteral(peer_.table()->GetByName("cookie"), "b=cc"); + // "cookie: c=dd" + ExpectIndex(64); + + CompareWithExpectedEncoding(headers); + } + + // clang-format off + EXPECT_THAT(headers_observed_, + ElementsAre(Pair("key1", "value1"), + Pair("cookie", "a=bb"), + Pair("key2", "value2"), + Pair("cookie", "c=dd"), + Pair("cookie", "e=ff"), + Pair("key2", "value2"), + Pair("cookie", "a=bb"), + Pair("cookie", "b=cc"), + Pair("cookie", "c=dd"))); + // clang-format on +} + +TEST_P(HpackEncoderTest, PseudoHeadersFirst) { + SpdyHeaderBlock headers; + // A pseudo-header that should not be indexed. + headers[":path"] = "/spam/eggs.html"; + // A pseudo-header to be indexed. + headers[":authority"] = "www.example.com"; + // A regular header which precedes ":" alphabetically, should still be encoded + // after pseudo-headers. + headers["-foo"] = "bar"; + headers["foo"] = "bar"; + headers["cookie"] = "c=dd"; + + // Headers are indexed in the order in which they were added. + // This entry pushes "cookie: a=bb" back to 63. + ExpectNonIndexedLiteral(":path", "/spam/eggs.html"); + ExpectIndexedLiteral(peer_.table()->GetByName(":authority"), + "www.example.com"); + ExpectIndexedLiteral("-foo", "bar"); + ExpectIndexedLiteral("foo", "bar"); + ExpectIndexedLiteral(peer_.table()->GetByName("cookie"), "c=dd"); + CompareWithExpectedEncoding(headers); +} + +TEST_P(HpackEncoderTest, CookieToCrumbs) { + test::HpackEncoderPeer peer(nullptr); + std::vector<SpdyStringPiece> out; + + // Leading and trailing whitespace is consumed. A space after ';' is consumed. + // All other spaces remain. ';' at beginning and end of string produce empty + // crumbs. + // See section 8.1.3.4 "Compressing the Cookie Header Field" in the HTTP/2 + // specification at http://tools.ietf.org/html/draft-ietf-httpbis-http2-11 + peer.CookieToCrumbs(" foo=1;bar=2 ; bar=3; bing=4; ", &out); + EXPECT_THAT(out, ElementsAre("foo=1", "bar=2 ", "bar=3", " bing=4", "")); + + peer.CookieToCrumbs(";;foo = bar ;; ;baz =bing", &out); + EXPECT_THAT(out, ElementsAre("", "", "foo = bar ", "", "", "baz =bing")); + + peer.CookieToCrumbs("baz=bing; foo=bar; baz=bing", &out); + EXPECT_THAT(out, ElementsAre("baz=bing", "foo=bar", "baz=bing")); + + peer.CookieToCrumbs("baz=bing", &out); + EXPECT_THAT(out, ElementsAre("baz=bing")); + + peer.CookieToCrumbs("", &out); + EXPECT_THAT(out, ElementsAre("")); + + peer.CookieToCrumbs("foo;bar; baz;baz;bing;", &out); + EXPECT_THAT(out, ElementsAre("foo", "bar", "baz", "baz", "bing", "")); + + peer.CookieToCrumbs(" \t foo=1;bar=2 ; bar=3;\t ", &out); + EXPECT_THAT(out, ElementsAre("foo=1", "bar=2 ", "bar=3", "")); + + peer.CookieToCrumbs(" \t foo=1;bar=2 ; bar=3 \t ", &out); + EXPECT_THAT(out, ElementsAre("foo=1", "bar=2 ", "bar=3")); +} + +TEST_P(HpackEncoderTest, DecomposeRepresentation) { + test::HpackEncoderPeer peer(nullptr); + std::vector<SpdyStringPiece> out; + + peer.DecomposeRepresentation("", &out); + EXPECT_THAT(out, ElementsAre("")); + + peer.DecomposeRepresentation("foobar", &out); + EXPECT_THAT(out, ElementsAre("foobar")); + + peer.DecomposeRepresentation(SpdyStringPiece("foo\0bar", 7), &out); + EXPECT_THAT(out, ElementsAre("foo", "bar")); + + peer.DecomposeRepresentation(SpdyStringPiece("\0foo\0bar", 8), &out); + EXPECT_THAT(out, ElementsAre("", "foo", "bar")); + + peer.DecomposeRepresentation(SpdyStringPiece("foo\0bar\0", 8), &out); + EXPECT_THAT(out, ElementsAre("foo", "bar", "")); + + peer.DecomposeRepresentation(SpdyStringPiece("\0foo\0bar\0", 9), &out); + EXPECT_THAT(out, ElementsAre("", "foo", "bar", "")); +} + +// Test that encoded headers do not have \0-delimited multiple values, as this +// became disallowed in HTTP/2 draft-14. +TEST_P(HpackEncoderTest, CrumbleNullByteDelimitedValue) { + SpdyHeaderBlock headers; + // A header field to be crumbled: "spam: foo\0bar". + headers["spam"] = SpdyString("foo\0bar", 7); + + ExpectIndexedLiteral("spam", "foo"); + expected_.AppendPrefix(kLiteralIncrementalIndexOpcode); + expected_.AppendUint32(62); + expected_.AppendPrefix(kStringLiteralIdentityEncoded); + expected_.AppendUint32(3); + expected_.AppendBytes("bar"); + CompareWithExpectedEncoding(headers); +} + +TEST_P(HpackEncoderTest, HeaderTableSizeUpdate) { + encoder_.ApplyHeaderTableSizeSetting(1024); + ExpectHeaderTableSizeUpdate(1024); + ExpectIndexedLiteral("key3", "value3"); + + SpdyHeaderBlock headers; + headers["key3"] = "value3"; + CompareWithExpectedEncoding(headers); + + HpackEntry* new_entry = &peer_.table_peer().dynamic_entries()->front(); + EXPECT_EQ(new_entry->name(), "key3"); + EXPECT_EQ(new_entry->value(), "value3"); +} + +TEST_P(HpackEncoderTest, HeaderTableSizeUpdateWithMin) { + const size_t starting_size = peer_.table()->settings_size_bound(); + encoder_.ApplyHeaderTableSizeSetting(starting_size - 2); + encoder_.ApplyHeaderTableSizeSetting(starting_size - 1); + // We must encode the low watermark, so the peer knows to evict entries + // if necessary. + ExpectHeaderTableSizeUpdate(starting_size - 2); + ExpectHeaderTableSizeUpdate(starting_size - 1); + ExpectIndexedLiteral("key3", "value3"); + + SpdyHeaderBlock headers; + headers["key3"] = "value3"; + CompareWithExpectedEncoding(headers); + + HpackEntry* new_entry = &peer_.table_peer().dynamic_entries()->front(); + EXPECT_EQ(new_entry->name(), "key3"); + EXPECT_EQ(new_entry->value(), "value3"); +} + +TEST_P(HpackEncoderTest, HeaderTableSizeUpdateWithExistingSize) { + encoder_.ApplyHeaderTableSizeSetting(peer_.table()->settings_size_bound()); + // No encoded size update. + ExpectIndexedLiteral("key3", "value3"); + + SpdyHeaderBlock headers; + headers["key3"] = "value3"; + CompareWithExpectedEncoding(headers); + + HpackEntry* new_entry = &peer_.table_peer().dynamic_entries()->front(); + EXPECT_EQ(new_entry->name(), "key3"); + EXPECT_EQ(new_entry->value(), "value3"); +} + +TEST_P(HpackEncoderTest, HeaderTableSizeUpdatesWithGreaterSize) { + const size_t starting_size = peer_.table()->settings_size_bound(); + encoder_.ApplyHeaderTableSizeSetting(starting_size + 1); + encoder_.ApplyHeaderTableSizeSetting(starting_size + 2); + // Only a single size update to the final size. + ExpectHeaderTableSizeUpdate(starting_size + 2); + ExpectIndexedLiteral("key3", "value3"); + + SpdyHeaderBlock headers; + headers["key3"] = "value3"; + CompareWithExpectedEncoding(headers); + + HpackEntry* new_entry = &peer_.table_peer().dynamic_entries()->front(); + EXPECT_EQ(new_entry->name(), "key3"); + EXPECT_EQ(new_entry->value(), "value3"); +} + +} // namespace + +} // namespace spdy diff --git a/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_entry.cc b/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_entry.cc new file mode 100644 index 00000000000..90d53e65d73 --- /dev/null +++ b/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_entry.cc @@ -0,0 +1,89 @@ +// Copyright 2014 The Chromium 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/spdy/core/hpack/hpack_entry.h" + +#include "base/logging.h" +#include "net/third_party/quiche/src/spdy/platform/api/spdy_estimate_memory_usage.h" +#include "net/third_party/quiche/src/spdy/platform/api/spdy_string_utils.h" + +namespace spdy { + +const size_t HpackEntry::kSizeOverhead = 32; + +HpackEntry::HpackEntry(SpdyStringPiece name, + SpdyStringPiece value, + bool is_static, + size_t insertion_index) + : name_(name.data(), name.size()), + value_(value.data(), value.size()), + name_ref_(name_), + value_ref_(value_), + insertion_index_(insertion_index), + type_(is_static ? STATIC : DYNAMIC), + time_added_(0) {} + +HpackEntry::HpackEntry(SpdyStringPiece name, SpdyStringPiece value) + : name_ref_(name), + value_ref_(value), + insertion_index_(0), + type_(LOOKUP), + time_added_(0) {} + +HpackEntry::HpackEntry() : insertion_index_(0), type_(LOOKUP), time_added_(0) {} + +HpackEntry::HpackEntry(const HpackEntry& other) + : insertion_index_(other.insertion_index_), + type_(other.type_), + time_added_(0) { + if (type_ == LOOKUP) { + name_ref_ = other.name_ref_; + value_ref_ = other.value_ref_; + } else { + name_ = other.name_; + value_ = other.value_; + name_ref_ = SpdyStringPiece(name_.data(), name_.size()); + value_ref_ = SpdyStringPiece(value_.data(), value_.size()); + } +} + +HpackEntry& HpackEntry::operator=(const HpackEntry& other) { + insertion_index_ = other.insertion_index_; + type_ = other.type_; + if (type_ == LOOKUP) { + name_.clear(); + value_.clear(); + name_ref_ = other.name_ref_; + value_ref_ = other.value_ref_; + return *this; + } + name_ = other.name_; + value_ = other.value_; + name_ref_ = SpdyStringPiece(name_.data(), name_.size()); + value_ref_ = SpdyStringPiece(value_.data(), value_.size()); + return *this; +} + +HpackEntry::~HpackEntry() = default; + +// static +size_t HpackEntry::Size(SpdyStringPiece name, SpdyStringPiece value) { + return name.size() + value.size() + kSizeOverhead; +} +size_t HpackEntry::Size() const { + return Size(name(), value()); +} + +SpdyString HpackEntry::GetDebugString() const { + return SpdyStrCat( + "{ name: \"", name_ref_, "\", value: \"", value_ref_, + "\", index: ", insertion_index_, " ", + (IsStatic() ? " static" : (IsLookup() ? " lookup" : " dynamic")), " }"); +} + +size_t HpackEntry::EstimateMemoryUsage() const { + return SpdyEstimateMemoryUsage(name_) + SpdyEstimateMemoryUsage(value_); +} + +} // namespace spdy diff --git a/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_entry.h b/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_entry.h new file mode 100644 index 00000000000..28dee47807a --- /dev/null +++ b/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_entry.h @@ -0,0 +1,110 @@ +// Copyright 2014 The Chromium 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_SPDY_CORE_HPACK_HPACK_ENTRY_H_ +#define QUICHE_SPDY_CORE_HPACK_HPACK_ENTRY_H_ + +#include <cstddef> +#include <cstdint> + +#include "base/macros.h" +#include "net/third_party/quiche/src/spdy/platform/api/spdy_export.h" +#include "net/third_party/quiche/src/spdy/platform/api/spdy_string.h" +#include "net/third_party/quiche/src/spdy/platform/api/spdy_string_piece.h" + +// All section references below are to +// http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-08 + +namespace spdy { + +// A structure for an entry in the static table (3.3.1) +// and the header table (3.3.2). +class SPDY_EXPORT_PRIVATE HpackEntry { + public: + // The constant amount added to name().size() and value().size() to + // get the size of an HpackEntry as defined in 5.1. + static const size_t kSizeOverhead; + + // Creates an entry. Preconditions: + // - |is_static| captures whether this entry is a member of the static + // or dynamic header table. + // - |insertion_index| is this entry's index in the total set of entries ever + // inserted into the header table (including static entries). + // + // The combination of |is_static| and |insertion_index| allows an + // HpackEntryTable to determine the index of an HpackEntry in O(1) time. + // Copies |name| and |value|. + HpackEntry(SpdyStringPiece name, + SpdyStringPiece value, + bool is_static, + size_t insertion_index); + + // Create a 'lookup' entry (only) suitable for querying a HpackEntrySet. The + // instance InsertionIndex() always returns 0 and IsLookup() returns true. + // The memory backing |name| and |value| must outlive this object. + HpackEntry(SpdyStringPiece name, SpdyStringPiece value); + + HpackEntry(const HpackEntry& other); + HpackEntry& operator=(const HpackEntry& other); + + // Creates an entry with empty name and value. Only defined so that + // entries can be stored in STL containers. + HpackEntry(); + + ~HpackEntry(); + + SpdyStringPiece name() const { return name_ref_; } + SpdyStringPiece value() const { return value_ref_; } + + // Returns whether this entry is a member of the static (as opposed to + // dynamic) table. + bool IsStatic() const { return type_ == STATIC; } + + // Returns whether this entry is a lookup-only entry. + bool IsLookup() const { return type_ == LOOKUP; } + + // Used to compute the entry's index in the header table. + size_t InsertionIndex() const { return insertion_index_; } + + // Returns the size of an entry as defined in 5.1. + static size_t Size(SpdyStringPiece name, SpdyStringPiece value); + size_t Size() const; + + SpdyString GetDebugString() const; + + int64_t time_added() const { return time_added_; } + void set_time_added(int64_t now) { time_added_ = now; } + + // Returns the estimate of dynamically allocated memory in bytes. + size_t EstimateMemoryUsage() const; + + private: + enum EntryType { + LOOKUP, + DYNAMIC, + STATIC, + }; + + // These members are not used for LOOKUP entries. + SpdyString name_; + SpdyString value_; + + // These members are always valid. For DYNAMIC and STATIC entries, they + // always point to |name_| and |value_|. + SpdyStringPiece name_ref_; + SpdyStringPiece value_ref_; + + // The entry's index in the total set of entries ever inserted into the header + // table. + size_t insertion_index_; + + EntryType type_; + + // For HpackHeaderTable::DebugVisitorInterface + int64_t time_added_; +}; + +} // namespace spdy + +#endif // QUICHE_SPDY_CORE_HPACK_HPACK_ENTRY_H_ diff --git a/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_entry_test.cc b/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_entry_test.cc new file mode 100644 index 00000000000..507c851d136 --- /dev/null +++ b/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_entry_test.cc @@ -0,0 +1,122 @@ +// Copyright 2014 The Chromium 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/spdy/core/hpack/hpack_entry.h" + +#include "testing/gtest/include/gtest/gtest.h" + +namespace spdy { + +namespace { + +class HpackEntryTest : public ::testing::Test { + protected: + HpackEntryTest() + : name_("header-name"), + value_("header value"), + total_insertions_(0), + table_size_(0) {} + + // These builders maintain the same external table invariants that a "real" + // table (ie HpackHeaderTable) would. + HpackEntry StaticEntry() { + return HpackEntry(name_, value_, true, total_insertions_++); + } + HpackEntry DynamicEntry() { + ++table_size_; + size_t index = total_insertions_++; + return HpackEntry(name_, value_, false, index); + } + void DropEntry() { --table_size_; } + + size_t IndexOf(const HpackEntry& entry) const { + if (entry.IsStatic()) { + return 1 + entry.InsertionIndex() + table_size_; + } else { + return total_insertions_ - entry.InsertionIndex(); + } + } + + size_t Size() { + return name_.size() + value_.size() + HpackEntry::kSizeOverhead; + } + + SpdyString name_, value_; + + private: + // Referenced by HpackEntry instances. + size_t total_insertions_; + size_t table_size_; +}; + +TEST_F(HpackEntryTest, StaticConstructor) { + HpackEntry entry(StaticEntry()); + + EXPECT_EQ(name_, entry.name()); + EXPECT_EQ(value_, entry.value()); + EXPECT_TRUE(entry.IsStatic()); + EXPECT_EQ(1u, IndexOf(entry)); + EXPECT_EQ(Size(), entry.Size()); +} + +TEST_F(HpackEntryTest, DynamicConstructor) { + HpackEntry entry(DynamicEntry()); + + EXPECT_EQ(name_, entry.name()); + EXPECT_EQ(value_, entry.value()); + EXPECT_FALSE(entry.IsStatic()); + EXPECT_EQ(1u, IndexOf(entry)); + EXPECT_EQ(Size(), entry.Size()); +} + +TEST_F(HpackEntryTest, LookupConstructor) { + HpackEntry entry(name_, value_); + + EXPECT_EQ(name_, entry.name()); + EXPECT_EQ(value_, entry.value()); + EXPECT_FALSE(entry.IsStatic()); + EXPECT_EQ(0u, IndexOf(entry)); + EXPECT_EQ(Size(), entry.Size()); +} + +TEST_F(HpackEntryTest, DefaultConstructor) { + HpackEntry entry; + + EXPECT_TRUE(entry.name().empty()); + EXPECT_TRUE(entry.value().empty()); + EXPECT_EQ(HpackEntry::kSizeOverhead, entry.Size()); +} + +TEST_F(HpackEntryTest, IndexUpdate) { + HpackEntry static1(StaticEntry()); + HpackEntry static2(StaticEntry()); + + EXPECT_EQ(1u, IndexOf(static1)); + EXPECT_EQ(2u, IndexOf(static2)); + + HpackEntry dynamic1(DynamicEntry()); + HpackEntry dynamic2(DynamicEntry()); + + EXPECT_EQ(1u, IndexOf(dynamic2)); + EXPECT_EQ(2u, IndexOf(dynamic1)); + EXPECT_EQ(3u, IndexOf(static1)); + EXPECT_EQ(4u, IndexOf(static2)); + + DropEntry(); // Drops |dynamic1|. + + EXPECT_EQ(1u, IndexOf(dynamic2)); + EXPECT_EQ(2u, IndexOf(static1)); + EXPECT_EQ(3u, IndexOf(static2)); + + HpackEntry dynamic3(DynamicEntry()); + + EXPECT_EQ(1u, IndexOf(dynamic3)); + EXPECT_EQ(2u, IndexOf(dynamic2)); + EXPECT_EQ(3u, IndexOf(static1)); + EXPECT_EQ(4u, IndexOf(static2)); +} + +} // namespace + +} // namespace spdy diff --git a/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_header_table.cc b/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_header_table.cc new file mode 100644 index 00000000000..dbe565f1c27 --- /dev/null +++ b/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_header_table.cc @@ -0,0 +1,274 @@ +// Copyright 2014 The Chromium 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/spdy/core/hpack/hpack_header_table.h" + +#include <algorithm> + +#include "base/logging.h" +#include "net/third_party/quiche/src/spdy/core/hpack/hpack_constants.h" +#include "net/third_party/quiche/src/spdy/core/hpack/hpack_static_table.h" +#include "net/third_party/quiche/src/spdy/platform/api/spdy_containers.h" +#include "net/third_party/quiche/src/spdy/platform/api/spdy_estimate_memory_usage.h" + +namespace spdy { + +size_t HpackHeaderTable::EntryHasher::operator()( + const HpackEntry* entry) const { + return SpdyHashStringPair(entry->name(), entry->value()); +} + +bool HpackHeaderTable::EntriesEq::operator()(const HpackEntry* lhs, + const HpackEntry* rhs) const { + if (lhs == nullptr) { + return rhs == nullptr; + } + if (rhs == nullptr) { + return false; + } + return lhs->name() == rhs->name() && lhs->value() == rhs->value(); +} + +HpackHeaderTable::HpackHeaderTable() + : static_entries_(ObtainHpackStaticTable().GetStaticEntries()), + static_index_(ObtainHpackStaticTable().GetStaticIndex()), + static_name_index_(ObtainHpackStaticTable().GetStaticNameIndex()), + settings_size_bound_(kDefaultHeaderTableSizeSetting), + size_(0), + max_size_(kDefaultHeaderTableSizeSetting), + total_insertions_(static_entries_.size()) {} + +HpackHeaderTable::~HpackHeaderTable() = default; + +const HpackEntry* HpackHeaderTable::GetByIndex(size_t index) { + if (index == 0) { + return nullptr; + } + index -= 1; + if (index < static_entries_.size()) { + return &static_entries_[index]; + } + index -= static_entries_.size(); + if (index < dynamic_entries_.size()) { + const HpackEntry* result = &dynamic_entries_[index]; + if (debug_visitor_ != nullptr) { + debug_visitor_->OnUseEntry(*result); + } + return result; + } + return nullptr; +} + +const HpackEntry* HpackHeaderTable::GetByName(SpdyStringPiece name) { + { + auto it = static_name_index_.find(name); + if (it != static_name_index_.end()) { + return it->second; + } + } + { + NameToEntryMap::const_iterator it = dynamic_name_index_.find(name); + if (it != dynamic_name_index_.end()) { + const HpackEntry* result = it->second; + if (debug_visitor_ != nullptr) { + debug_visitor_->OnUseEntry(*result); + } + return result; + } + } + return nullptr; +} + +const HpackEntry* HpackHeaderTable::GetByNameAndValue(SpdyStringPiece name, + SpdyStringPiece value) { + HpackEntry query(name, value); + { + auto it = static_index_.find(&query); + if (it != static_index_.end()) { + return *it; + } + } + { + auto it = dynamic_index_.find(&query); + if (it != dynamic_index_.end()) { + const HpackEntry* result = *it; + if (debug_visitor_ != nullptr) { + debug_visitor_->OnUseEntry(*result); + } + return result; + } + } + return nullptr; +} + +size_t HpackHeaderTable::IndexOf(const HpackEntry* entry) const { + if (entry->IsLookup()) { + return 0; + } else if (entry->IsStatic()) { + return 1 + entry->InsertionIndex(); + } else { + return total_insertions_ - entry->InsertionIndex() + static_entries_.size(); + } +} + +void HpackHeaderTable::SetMaxSize(size_t max_size) { + CHECK_LE(max_size, settings_size_bound_); + + max_size_ = max_size; + if (size_ > max_size_) { + Evict(EvictionCountToReclaim(size_ - max_size_)); + CHECK_LE(size_, max_size_); + } +} + +void HpackHeaderTable::SetSettingsHeaderTableSize(size_t settings_size) { + settings_size_bound_ = settings_size; + SetMaxSize(settings_size_bound_); +} + +void HpackHeaderTable::EvictionSet(SpdyStringPiece name, + SpdyStringPiece value, + EntryTable::iterator* begin_out, + EntryTable::iterator* end_out) { + size_t eviction_count = EvictionCountForEntry(name, value); + *begin_out = dynamic_entries_.end() - eviction_count; + *end_out = dynamic_entries_.end(); +} + +size_t HpackHeaderTable::EvictionCountForEntry(SpdyStringPiece name, + SpdyStringPiece value) const { + size_t available_size = max_size_ - size_; + size_t entry_size = HpackEntry::Size(name, value); + + if (entry_size <= available_size) { + // No evictions are required. + return 0; + } + return EvictionCountToReclaim(entry_size - available_size); +} + +size_t HpackHeaderTable::EvictionCountToReclaim(size_t reclaim_size) const { + size_t count = 0; + for (auto it = dynamic_entries_.rbegin(); + it != dynamic_entries_.rend() && reclaim_size != 0; ++it, ++count) { + reclaim_size -= std::min(reclaim_size, it->Size()); + } + return count; +} + +void HpackHeaderTable::Evict(size_t count) { + for (size_t i = 0; i != count; ++i) { + CHECK(!dynamic_entries_.empty()); + HpackEntry* entry = &dynamic_entries_.back(); + + size_ -= entry->Size(); + auto it = dynamic_index_.find(entry); + DCHECK(it != dynamic_index_.end()); + // Only remove an entry from the index if its insertion index matches; + // otherwise, the index refers to another entry with the same name and + // value. + if ((*it)->InsertionIndex() == entry->InsertionIndex()) { + dynamic_index_.erase(it); + } + auto name_it = dynamic_name_index_.find(entry->name()); + DCHECK(name_it != dynamic_name_index_.end()); + // Only remove an entry from the literal index if its insertion index + /// matches; otherwise, the index refers to another entry with the same + // name. + if (name_it->second->InsertionIndex() == entry->InsertionIndex()) { + dynamic_name_index_.erase(name_it); + } + dynamic_entries_.pop_back(); + } +} + +const HpackEntry* HpackHeaderTable::TryAddEntry(SpdyStringPiece name, + SpdyStringPiece value) { + Evict(EvictionCountForEntry(name, value)); + + size_t entry_size = HpackEntry::Size(name, value); + if (entry_size > (max_size_ - size_)) { + // Entire table has been emptied, but there's still insufficient room. + DCHECK(dynamic_entries_.empty()); + DCHECK_EQ(0u, size_); + return nullptr; + } + dynamic_entries_.push_front(HpackEntry(name, value, + false, // is_static + total_insertions_)); + HpackEntry* new_entry = &dynamic_entries_.front(); + auto index_result = dynamic_index_.insert(new_entry); + if (!index_result.second) { + // An entry with the same name and value already exists in the dynamic + // index. We should replace it with the newly added entry. + DVLOG(1) << "Found existing entry: " + << (*index_result.first)->GetDebugString() + << " replacing with: " << new_entry->GetDebugString(); + DCHECK_GT(new_entry->InsertionIndex(), + (*index_result.first)->InsertionIndex()); + dynamic_index_.erase(index_result.first); + CHECK(dynamic_index_.insert(new_entry).second); + } + + auto name_result = + dynamic_name_index_.insert(std::make_pair(new_entry->name(), new_entry)); + if (!name_result.second) { + // An entry with the same name already exists in the dynamic index. We + // should replace it with the newly added entry. + DVLOG(1) << "Found existing entry: " + << name_result.first->second->GetDebugString() + << " replacing with: " << new_entry->GetDebugString(); + DCHECK_GT(new_entry->InsertionIndex(), + name_result.first->second->InsertionIndex()); + dynamic_name_index_.erase(name_result.first); + auto insert_result = dynamic_name_index_.insert( + std::make_pair(new_entry->name(), new_entry)); + CHECK(insert_result.second); + } + + size_ += entry_size; + ++total_insertions_; + if (debug_visitor_ != nullptr) { + // Call |debug_visitor_->OnNewEntry()| to get the current time. + HpackEntry& entry = dynamic_entries_.front(); + entry.set_time_added(debug_visitor_->OnNewEntry(entry)); + DVLOG(2) << "HpackHeaderTable::OnNewEntry: name=" << entry.name() + << ", value=" << entry.value() + << ", insert_index=" << entry.InsertionIndex() + << ", time_added=" << entry.time_added(); + } + + return &dynamic_entries_.front(); +} + +void HpackHeaderTable::DebugLogTableState() const { + DVLOG(2) << "Dynamic table:"; + for (auto it = dynamic_entries_.begin(); it != dynamic_entries_.end(); ++it) { + DVLOG(2) << " " << it->GetDebugString(); + } + DVLOG(2) << "Full Static Index:"; + for (const auto* entry : static_index_) { + DVLOG(2) << " " << entry->GetDebugString(); + } + DVLOG(2) << "Full Static Name Index:"; + for (const auto it : static_name_index_) { + DVLOG(2) << " " << it.first << ": " << it.second->GetDebugString(); + } + DVLOG(2) << "Full Dynamic Index:"; + for (const auto* entry : dynamic_index_) { + DVLOG(2) << " " << entry->GetDebugString(); + } + DVLOG(2) << "Full Dynamic Name Index:"; + for (const auto it : dynamic_name_index_) { + DVLOG(2) << " " << it.first << ": " << it.second->GetDebugString(); + } +} + +size_t HpackHeaderTable::EstimateMemoryUsage() const { + return SpdyEstimateMemoryUsage(dynamic_entries_) + + SpdyEstimateMemoryUsage(dynamic_index_) + + SpdyEstimateMemoryUsage(dynamic_name_index_); +} + +} // namespace spdy diff --git a/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_header_table.h b/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_header_table.h new file mode 100644 index 00000000000..e85edb7896b --- /dev/null +++ b/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_header_table.h @@ -0,0 +1,180 @@ +// Copyright 2014 The Chromium 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_SPDY_CORE_HPACK_HPACK_HEADER_TABLE_H_ +#define QUICHE_SPDY_CORE_HPACK_HPACK_HEADER_TABLE_H_ + +#include <cstddef> +#include <cstdint> +#include <deque> +#include <memory> + +#include "base/macros.h" +#include "net/third_party/quiche/src/spdy/core/hpack/hpack_entry.h" +#include "net/third_party/quiche/src/spdy/platform/api/spdy_containers.h" +#include "net/third_party/quiche/src/spdy/platform/api/spdy_export.h" +#include "net/third_party/quiche/src/spdy/platform/api/spdy_macros.h" +#include "net/third_party/quiche/src/spdy/platform/api/spdy_string_piece.h" + +// All section references below are to http://tools.ietf.org/html/rfc7541. + +namespace spdy { + +namespace test { +class HpackHeaderTablePeer; +} // namespace test + +// A data structure for the static table (2.3.1) and the dynamic table (2.3.2). +class SPDY_EXPORT_PRIVATE HpackHeaderTable { + public: + friend class test::HpackHeaderTablePeer; + + // Debug visitor my be used to extract debug/internal information + // about the HpackHeaderTable as it operates. + // + // Most HpackHeaderTable implementations do not need to bother with + // this interface at all. + class DebugVisitorInterface { + public: + virtual ~DebugVisitorInterface() {} + + // |OnNewEntry()| and |OnUseEntry()| can be used together to + // gather data about the distribution of time intervals between + // creation and reference of entries in the dynamic table. The + // data is desired to sanity check a proposed extension to HPACK + // for QUIC that would eliminate inter-stream head of line + // blocking (due to standard HPACK). The visitor should return + // the current time from |OnNewEntry()|, which will be passed + // to |OnUseEntry()| each time that particular entry is used to + // emit an indexed representation. + virtual int64_t OnNewEntry(const HpackEntry& entry) = 0; + virtual void OnUseEntry(const HpackEntry& entry) = 0; + }; + + // HpackHeaderTable takes advantage of the deque property that references + // remain valid, so long as insertions & deletions are at the head & tail. + // This precludes the use of base::circular_deque. + // + // If this changes (we want to change to circular_deque or we start to drop + // entries from the middle of the table), this should to be a std::list, in + // which case |*_index_| can be trivially extended to map to list iterators. + using EntryTable = std::deque<HpackEntry>; + + struct SPDY_EXPORT_PRIVATE EntryHasher { + size_t operator()(const HpackEntry* entry) const; + }; + struct SPDY_EXPORT_PRIVATE EntriesEq { + bool operator()(const HpackEntry* lhs, const HpackEntry* rhs) const; + }; + using UnorderedEntrySet = SpdyHashSet<HpackEntry*, EntryHasher, EntriesEq>; + using NameToEntryMap = + SpdyHashMap<SpdyStringPiece, const HpackEntry*, SpdyStringPieceHash>; + + HpackHeaderTable(); + HpackHeaderTable(const HpackHeaderTable&) = delete; + HpackHeaderTable& operator=(const HpackHeaderTable&) = delete; + + ~HpackHeaderTable(); + + // Last-acknowledged value of SETTINGS_HEADER_TABLE_SIZE. + size_t settings_size_bound() const { return settings_size_bound_; } + + // Current and maximum estimated byte size of the table, as described in + // 4.1. Notably, this is /not/ the number of entries in the table. + size_t size() const { return size_; } + size_t max_size() const { return max_size_; } + + // Returns the entry matching the index, or NULL. + const HpackEntry* GetByIndex(size_t index); + + // Returns the lowest-value entry having |name|, or NULL. + const HpackEntry* GetByName(SpdyStringPiece name); + + // Returns the lowest-index matching entry, or NULL. + const HpackEntry* GetByNameAndValue(SpdyStringPiece name, + SpdyStringPiece value); + + // Returns the index of an entry within this header table. + size_t IndexOf(const HpackEntry* entry) const; + + // Sets the maximum size of the header table, evicting entries if + // necessary as described in 5.2. + void SetMaxSize(size_t max_size); + + // Sets the SETTINGS_HEADER_TABLE_SIZE bound of the table. Will call + // SetMaxSize() as needed to preserve max_size() <= settings_size_bound(). + void SetSettingsHeaderTableSize(size_t settings_size); + + // Determine the set of entries which would be evicted by the insertion + // of |name| & |value| into the table, as per section 4.4. No eviction + // actually occurs. The set is returned via range [begin_out, end_out). + void EvictionSet(SpdyStringPiece name, + SpdyStringPiece value, + EntryTable::iterator* begin_out, + EntryTable::iterator* end_out); + + // Adds an entry for the representation, evicting entries as needed. |name| + // and |value| must not be owned by an entry which could be evicted. The + // added HpackEntry is returned, or NULL is returned if all entries were + // evicted and the empty table is of insufficent size for the representation. + const HpackEntry* TryAddEntry(SpdyStringPiece name, SpdyStringPiece value); + + void DebugLogTableState() const SPDY_UNUSED; + + void set_debug_visitor(std::unique_ptr<DebugVisitorInterface> visitor) { + debug_visitor_ = std::move(visitor); + } + + // Returns the estimate of dynamically allocated memory in bytes. + size_t EstimateMemoryUsage() const; + + private: + // Returns number of evictions required to enter |name| & |value|. + size_t EvictionCountForEntry(SpdyStringPiece name, + SpdyStringPiece value) const; + + // Returns number of evictions required to reclaim |reclaim_size| table size. + size_t EvictionCountToReclaim(size_t reclaim_size) const; + + // Evicts |count| oldest entries from the table. + void Evict(size_t count); + + // |static_entries_|, |static_index_|, and |static_name_index_| are owned by + // HpackStaticTable singleton. + + // Tracks HpackEntries by index. + const EntryTable& static_entries_; + EntryTable dynamic_entries_; + + // Tracks the unique HpackEntry for a given header name and value. + const UnorderedEntrySet& static_index_; + + // Tracks the first static entry for each name in the static table. + const NameToEntryMap& static_name_index_; + + // Tracks the most recently inserted HpackEntry for a given header name and + // value. + UnorderedEntrySet dynamic_index_; + + // Tracks the most recently inserted HpackEntry for a given header name. + NameToEntryMap dynamic_name_index_; + + // Last acknowledged value for SETTINGS_HEADER_TABLE_SIZE. + size_t settings_size_bound_; + + // Estimated current and maximum byte size of the table. + // |max_size_| <= |settings_size_bound_| + size_t size_; + size_t max_size_; + + // Total number of table insertions which have occurred. Referenced by + // IndexOf() for determination of an HpackEntry's table index. + size_t total_insertions_; + + std::unique_ptr<DebugVisitorInterface> debug_visitor_; +}; + +} // namespace spdy + +#endif // QUICHE_SPDY_CORE_HPACK_HPACK_HEADER_TABLE_H_ diff --git a/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_header_table_test.cc b/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_header_table_test.cc new file mode 100644 index 00000000000..b032aba435c --- /dev/null +++ b/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_header_table_test.cc @@ -0,0 +1,446 @@ +// Copyright 2014 The Chromium 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/spdy/core/hpack/hpack_header_table.h" + +#include <algorithm> +#include <cstdint> +#include <set> +#include <vector> + +#include "base/macros.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "net/third_party/quiche/src/spdy/core/hpack/hpack_constants.h" +#include "net/third_party/quiche/src/spdy/core/hpack/hpack_entry.h" +#include "net/third_party/quiche/src/spdy/platform/api/spdy_string.h" + +namespace spdy { + +using std::distance; + +namespace test { + +class HpackHeaderTablePeer { + public: + explicit HpackHeaderTablePeer(HpackHeaderTable* table) : table_(table) {} + + const HpackHeaderTable::EntryTable& dynamic_entries() { + return table_->dynamic_entries_; + } + const HpackHeaderTable::EntryTable& static_entries() { + return table_->static_entries_; + } + size_t index_size() { + return table_->static_index_.size() + table_->dynamic_index_.size(); + } + std::vector<HpackEntry*> EvictionSet(SpdyStringPiece name, + SpdyStringPiece value) { + HpackHeaderTable::EntryTable::iterator begin, end; + table_->EvictionSet(name, value, &begin, &end); + std::vector<HpackEntry*> result; + for (; begin != end; ++begin) { + result.push_back(&(*begin)); + } + return result; + } + size_t total_insertions() { return table_->total_insertions_; } + size_t dynamic_entries_count() { return table_->dynamic_entries_.size(); } + size_t EvictionCountForEntry(SpdyStringPiece name, SpdyStringPiece value) { + return table_->EvictionCountForEntry(name, value); + } + size_t EvictionCountToReclaim(size_t reclaim_size) { + return table_->EvictionCountToReclaim(reclaim_size); + } + void Evict(size_t count) { return table_->Evict(count); } + + void AddDynamicEntry(SpdyStringPiece name, SpdyStringPiece value) { + table_->dynamic_entries_.push_back( + HpackEntry(name, value, false, table_->total_insertions_++)); + } + + private: + HpackHeaderTable* table_; +}; + +} // namespace test + +namespace { + +class HpackHeaderTableTest : public ::testing::Test { + protected: + typedef std::vector<HpackEntry> HpackEntryVector; + + HpackHeaderTableTest() : table_(), peer_(&table_) {} + + // Returns an entry whose Size() is equal to the given one. + static HpackEntry MakeEntryOfSize(uint32_t size) { + EXPECT_GE(size, HpackEntry::kSizeOverhead); + SpdyString name((size - HpackEntry::kSizeOverhead) / 2, 'n'); + SpdyString value(size - HpackEntry::kSizeOverhead - name.size(), 'v'); + HpackEntry entry(name, value, false, 0); + EXPECT_EQ(size, entry.Size()); + return entry; + } + + // Returns a vector of entries whose total size is equal to the given + // one. + static HpackEntryVector MakeEntriesOfTotalSize(uint32_t total_size) { + EXPECT_GE(total_size, HpackEntry::kSizeOverhead); + uint32_t entry_size = HpackEntry::kSizeOverhead; + uint32_t remaining_size = total_size; + HpackEntryVector entries; + while (remaining_size > 0) { + EXPECT_LE(entry_size, remaining_size); + entries.push_back(MakeEntryOfSize(entry_size)); + remaining_size -= entry_size; + entry_size = std::min(remaining_size, entry_size + 32); + } + return entries; + } + + // Adds the given vector of entries to the given header table, + // expecting no eviction to happen. + void AddEntriesExpectNoEviction(const HpackEntryVector& entries) { + for (auto it = entries.begin(); it != entries.end(); ++it) { + HpackHeaderTable::EntryTable::iterator begin, end; + + table_.EvictionSet(it->name(), it->value(), &begin, &end); + EXPECT_EQ(0, distance(begin, end)); + + const HpackEntry* entry = table_.TryAddEntry(it->name(), it->value()); + EXPECT_NE(entry, static_cast<HpackEntry*>(nullptr)); + } + + for (size_t i = 0; i != entries.size(); ++i) { + // Static table has 61 entries, dynamic entries follow those. + size_t index = 61 + entries.size() - i; + const HpackEntry* entry = table_.GetByIndex(index); + EXPECT_EQ(entries[i].name(), entry->name()); + EXPECT_EQ(entries[i].value(), entry->value()); + EXPECT_EQ(index, table_.IndexOf(entry)); + } + } + + HpackEntry DynamicEntry(const SpdyString& name, const SpdyString& value) { + peer_.AddDynamicEntry(name, value); + return peer_.dynamic_entries().back(); + } + + HpackHeaderTable table_; + test::HpackHeaderTablePeer peer_; +}; + +TEST_F(HpackHeaderTableTest, StaticTableInitialization) { + EXPECT_EQ(0u, table_.size()); + EXPECT_EQ(kDefaultHeaderTableSizeSetting, table_.max_size()); + EXPECT_EQ(kDefaultHeaderTableSizeSetting, table_.settings_size_bound()); + + EXPECT_EQ(0u, peer_.dynamic_entries_count()); + EXPECT_EQ(peer_.static_entries().size(), peer_.total_insertions()); + + // Static entries have been populated and inserted into the table & index. + EXPECT_NE(0u, peer_.static_entries().size()); + EXPECT_EQ(peer_.index_size(), peer_.static_entries().size()); + for (size_t i = 0; i != peer_.static_entries().size(); ++i) { + const HpackEntry* entry = &peer_.static_entries()[i]; + + EXPECT_TRUE(entry->IsStatic()); + EXPECT_EQ(entry, table_.GetByIndex(i + 1)); + EXPECT_EQ(entry, table_.GetByNameAndValue(entry->name(), entry->value())); + } +} + +TEST_F(HpackHeaderTableTest, BasicDynamicEntryInsertionAndEviction) { + size_t static_count = peer_.total_insertions(); + const HpackEntry* first_static_entry = table_.GetByIndex(1); + + EXPECT_EQ(1u, table_.IndexOf(first_static_entry)); + + const HpackEntry* entry = table_.TryAddEntry("header-key", "Header Value"); + EXPECT_EQ("header-key", entry->name()); + EXPECT_EQ("Header Value", entry->value()); + EXPECT_FALSE(entry->IsStatic()); + + // Table counts were updated appropriately. + EXPECT_EQ(entry->Size(), table_.size()); + EXPECT_EQ(1u, peer_.dynamic_entries_count()); + EXPECT_EQ(peer_.dynamic_entries().size(), peer_.dynamic_entries_count()); + EXPECT_EQ(static_count + 1, peer_.total_insertions()); + EXPECT_EQ(static_count + 1, peer_.index_size()); + + // Index() of entries reflects the insertion. + EXPECT_EQ(1u, table_.IndexOf(first_static_entry)); + // Static table has 61 entries. + EXPECT_EQ(62u, table_.IndexOf(entry)); + EXPECT_EQ(first_static_entry, table_.GetByIndex(1)); + EXPECT_EQ(entry, table_.GetByIndex(62)); + + // Evict |entry|. Table counts are again updated appropriately. + peer_.Evict(1); + EXPECT_EQ(0u, table_.size()); + EXPECT_EQ(0u, peer_.dynamic_entries_count()); + EXPECT_EQ(peer_.dynamic_entries().size(), peer_.dynamic_entries_count()); + EXPECT_EQ(static_count + 1, peer_.total_insertions()); + EXPECT_EQ(static_count, peer_.index_size()); + + // Index() of |first_static_entry| reflects the eviction. + EXPECT_EQ(1u, table_.IndexOf(first_static_entry)); + EXPECT_EQ(first_static_entry, table_.GetByIndex(1)); +} + +TEST_F(HpackHeaderTableTest, EntryIndexing) { + const HpackEntry* first_static_entry = table_.GetByIndex(1); + + // Static entries are queryable by name & value. + EXPECT_EQ(first_static_entry, table_.GetByName(first_static_entry->name())); + EXPECT_EQ(first_static_entry, + table_.GetByNameAndValue(first_static_entry->name(), + first_static_entry->value())); + + // Create a mix of entries which duplicate names, and names & values of both + // dynamic and static entries. + const HpackEntry* entry1 = table_.TryAddEntry(first_static_entry->name(), + first_static_entry->value()); + const HpackEntry* entry2 = + table_.TryAddEntry(first_static_entry->name(), "Value Four"); + const HpackEntry* entry3 = table_.TryAddEntry("key-1", "Value One"); + const HpackEntry* entry4 = table_.TryAddEntry("key-2", "Value Three"); + const HpackEntry* entry5 = table_.TryAddEntry("key-1", "Value Two"); + const HpackEntry* entry6 = table_.TryAddEntry("key-2", "Value Three"); + const HpackEntry* entry7 = table_.TryAddEntry("key-2", "Value Four"); + + // Entries are queryable under their current index. + EXPECT_EQ(entry7, table_.GetByIndex(62)); + EXPECT_EQ(entry6, table_.GetByIndex(63)); + EXPECT_EQ(entry5, table_.GetByIndex(64)); + EXPECT_EQ(entry4, table_.GetByIndex(65)); + EXPECT_EQ(entry3, table_.GetByIndex(66)); + EXPECT_EQ(entry2, table_.GetByIndex(67)); + EXPECT_EQ(entry1, table_.GetByIndex(68)); + EXPECT_EQ(first_static_entry, table_.GetByIndex(1)); + + // Querying by name returns the most recently added matching entry. + EXPECT_EQ(entry5, table_.GetByName("key-1")); + EXPECT_EQ(entry7, table_.GetByName("key-2")); + EXPECT_EQ(entry2->name(), + table_.GetByName(first_static_entry->name())->name()); + EXPECT_EQ(nullptr, table_.GetByName("not-present")); + + // Querying by name & value returns the lowest-index matching entry among + // static entries, and the highest-index one among dynamic entries. + EXPECT_EQ(entry3, table_.GetByNameAndValue("key-1", "Value One")); + EXPECT_EQ(entry5, table_.GetByNameAndValue("key-1", "Value Two")); + EXPECT_EQ(entry6, table_.GetByNameAndValue("key-2", "Value Three")); + EXPECT_EQ(entry7, table_.GetByNameAndValue("key-2", "Value Four")); + EXPECT_EQ(first_static_entry, + table_.GetByNameAndValue(first_static_entry->name(), + first_static_entry->value())); + EXPECT_EQ(entry2, + table_.GetByNameAndValue(first_static_entry->name(), "Value Four")); + EXPECT_EQ(nullptr, table_.GetByNameAndValue("key-1", "Not Present")); + EXPECT_EQ(nullptr, table_.GetByNameAndValue("not-present", "Value One")); + + // Evict |entry1|. Queries for its name & value now return the static entry. + // |entry2| remains queryable. + peer_.Evict(1); + EXPECT_EQ(first_static_entry, + table_.GetByNameAndValue(first_static_entry->name(), + first_static_entry->value())); + EXPECT_EQ(entry2, + table_.GetByNameAndValue(first_static_entry->name(), "Value Four")); + + // Evict |entry2|. Queries by its name & value are not found. + peer_.Evict(1); + EXPECT_EQ(nullptr, + table_.GetByNameAndValue(first_static_entry->name(), "Value Four")); +} + +TEST_F(HpackHeaderTableTest, SetSizes) { + SpdyString key = "key", value = "value"; + const HpackEntry* entry1 = table_.TryAddEntry(key, value); + const HpackEntry* entry2 = table_.TryAddEntry(key, value); + const HpackEntry* entry3 = table_.TryAddEntry(key, value); + + // Set exactly large enough. No Evictions. + size_t max_size = entry1->Size() + entry2->Size() + entry3->Size(); + table_.SetMaxSize(max_size); + EXPECT_EQ(3u, peer_.dynamic_entries().size()); + + // Set just too small. One eviction. + max_size = entry1->Size() + entry2->Size() + entry3->Size() - 1; + table_.SetMaxSize(max_size); + EXPECT_EQ(2u, peer_.dynamic_entries().size()); + + // Changing SETTINGS_HEADER_TABLE_SIZE. + EXPECT_EQ(kDefaultHeaderTableSizeSetting, table_.settings_size_bound()); + // In production, the size passed to SetSettingsHeaderTableSize is never + // larger than table_.settings_size_bound(). + table_.SetSettingsHeaderTableSize(kDefaultHeaderTableSizeSetting * 3 + 1); + EXPECT_EQ(kDefaultHeaderTableSizeSetting * 3 + 1, table_.max_size()); + + // SETTINGS_HEADER_TABLE_SIZE upper-bounds |table_.max_size()|, + // and will force evictions. + max_size = entry3->Size() - 1; + table_.SetSettingsHeaderTableSize(max_size); + EXPECT_EQ(max_size, table_.max_size()); + EXPECT_EQ(max_size, table_.settings_size_bound()); + EXPECT_EQ(0u, peer_.dynamic_entries().size()); +} + +TEST_F(HpackHeaderTableTest, EvictionCountForEntry) { + SpdyString key = "key", value = "value"; + const HpackEntry* entry1 = table_.TryAddEntry(key, value); + const HpackEntry* entry2 = table_.TryAddEntry(key, value); + size_t entry3_size = HpackEntry::Size(key, value); + + // Just enough capacity for third entry. + table_.SetMaxSize(entry1->Size() + entry2->Size() + entry3_size); + EXPECT_EQ(0u, peer_.EvictionCountForEntry(key, value)); + EXPECT_EQ(1u, peer_.EvictionCountForEntry(key, value + "x")); + + // No extra capacity. Third entry would force evictions. + table_.SetMaxSize(entry1->Size() + entry2->Size()); + EXPECT_EQ(1u, peer_.EvictionCountForEntry(key, value)); + EXPECT_EQ(2u, peer_.EvictionCountForEntry(key, value + "x")); +} + +TEST_F(HpackHeaderTableTest, EvictionCountToReclaim) { + SpdyString key = "key", value = "value"; + const HpackEntry* entry1 = table_.TryAddEntry(key, value); + const HpackEntry* entry2 = table_.TryAddEntry(key, value); + + EXPECT_EQ(1u, peer_.EvictionCountToReclaim(1)); + EXPECT_EQ(1u, peer_.EvictionCountToReclaim(entry1->Size())); + EXPECT_EQ(2u, peer_.EvictionCountToReclaim(entry1->Size() + 1)); + EXPECT_EQ(2u, peer_.EvictionCountToReclaim(entry1->Size() + entry2->Size())); +} + +// Fill a header table with entries. Make sure the entries are in +// reverse order in the header table. +TEST_F(HpackHeaderTableTest, TryAddEntryBasic) { + EXPECT_EQ(0u, table_.size()); + EXPECT_EQ(table_.settings_size_bound(), table_.max_size()); + + HpackEntryVector entries = MakeEntriesOfTotalSize(table_.max_size()); + + // Most of the checks are in AddEntriesExpectNoEviction(). + AddEntriesExpectNoEviction(entries); + EXPECT_EQ(table_.max_size(), table_.size()); + EXPECT_EQ(table_.settings_size_bound(), table_.size()); +} + +// Fill a header table with entries, and then ramp the table's max +// size down to evict an entry one at a time. Make sure the eviction +// happens as expected. +TEST_F(HpackHeaderTableTest, SetMaxSize) { + HpackEntryVector entries = + MakeEntriesOfTotalSize(kDefaultHeaderTableSizeSetting / 2); + AddEntriesExpectNoEviction(entries); + + for (auto it = entries.begin(); it != entries.end(); ++it) { + size_t expected_count = distance(it, entries.end()); + EXPECT_EQ(expected_count, peer_.dynamic_entries().size()); + + table_.SetMaxSize(table_.size() + 1); + EXPECT_EQ(expected_count, peer_.dynamic_entries().size()); + + table_.SetMaxSize(table_.size()); + EXPECT_EQ(expected_count, peer_.dynamic_entries().size()); + + --expected_count; + table_.SetMaxSize(table_.size() - 1); + EXPECT_EQ(expected_count, peer_.dynamic_entries().size()); + } + EXPECT_EQ(0u, table_.size()); +} + +// Fill a header table with entries, and then add an entry just big +// enough to cause eviction of all but one entry. Make sure the +// eviction happens as expected and the long entry is inserted into +// the table. +TEST_F(HpackHeaderTableTest, TryAddEntryEviction) { + HpackEntryVector entries = MakeEntriesOfTotalSize(table_.max_size()); + AddEntriesExpectNoEviction(entries); + + const HpackEntry* survivor_entry = table_.GetByIndex(61 + 1); + HpackEntry long_entry = + MakeEntryOfSize(table_.max_size() - survivor_entry->Size()); + + // All dynamic entries but the first are to be evicted. + EXPECT_EQ(peer_.dynamic_entries().size() - 1, + peer_.EvictionSet(long_entry.name(), long_entry.value()).size()); + + const HpackEntry* new_entry = + table_.TryAddEntry(long_entry.name(), long_entry.value()); + EXPECT_EQ(62u, table_.IndexOf(new_entry)); + EXPECT_EQ(2u, peer_.dynamic_entries().size()); + EXPECT_EQ(table_.GetByIndex(63), survivor_entry); + EXPECT_EQ(table_.GetByIndex(62), new_entry); +} + +// Fill a header table with entries, and then add an entry bigger than +// the entire table. Make sure no entry remains in the table. +TEST_F(HpackHeaderTableTest, TryAddTooLargeEntry) { + HpackEntryVector entries = MakeEntriesOfTotalSize(table_.max_size()); + AddEntriesExpectNoEviction(entries); + + const HpackEntry long_entry = MakeEntryOfSize(table_.max_size() + 1); + + // All entries are to be evicted. + EXPECT_EQ(peer_.dynamic_entries().size(), + peer_.EvictionSet(long_entry.name(), long_entry.value()).size()); + + const HpackEntry* new_entry = + table_.TryAddEntry(long_entry.name(), long_entry.value()); + EXPECT_EQ(new_entry, static_cast<HpackEntry*>(nullptr)); + EXPECT_EQ(0u, peer_.dynamic_entries().size()); +} + +TEST_F(HpackHeaderTableTest, EntryNamesDiffer) { + HpackEntry entry1("header", "value"); + HpackEntry entry2("HEADER", "value"); + + HpackHeaderTable::EntryHasher hasher; + EXPECT_NE(hasher(&entry1), hasher(&entry2)); + + HpackHeaderTable::EntriesEq eq; + EXPECT_FALSE(eq(&entry1, &entry2)); +} + +TEST_F(HpackHeaderTableTest, EntryValuesDiffer) { + HpackEntry entry1("header", "value"); + HpackEntry entry2("header", "VALUE"); + + HpackHeaderTable::EntryHasher hasher; + EXPECT_NE(hasher(&entry1), hasher(&entry2)); + + HpackHeaderTable::EntriesEq eq; + EXPECT_FALSE(eq(&entry1, &entry2)); +} + +TEST_F(HpackHeaderTableTest, EntriesEqual) { + HpackEntry entry1(DynamicEntry("name", "value")); + HpackEntry entry2(DynamicEntry("name", "value")); + + HpackHeaderTable::EntryHasher hasher; + EXPECT_EQ(hasher(&entry1), hasher(&entry2)); + + HpackHeaderTable::EntriesEq eq; + EXPECT_TRUE(eq(&entry1, &entry2)); +} + +TEST_F(HpackHeaderTableTest, StaticAndDynamicEntriesEqual) { + HpackEntry entry1("name", "value"); + HpackEntry entry2(DynamicEntry("name", "value")); + + HpackHeaderTable::EntryHasher hasher; + EXPECT_EQ(hasher(&entry1), hasher(&entry2)); + + HpackHeaderTable::EntriesEq eq; + EXPECT_TRUE(eq(&entry1, &entry2)); +} + +} // namespace + +} // namespace spdy diff --git a/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_huffman_table.cc b/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_huffman_table.cc new file mode 100644 index 00000000000..c917767aa07 --- /dev/null +++ b/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_huffman_table.cc @@ -0,0 +1,151 @@ +// Copyright 2014 The Chromium 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/spdy/core/hpack/hpack_huffman_table.h" + +#include <algorithm> +#include <cmath> +#include <limits> +#include <memory> + +#include "base/logging.h" +#include "net/third_party/quiche/src/spdy/core/hpack/hpack_output_stream.h" +#include "net/third_party/quiche/src/spdy/platform/api/spdy_estimate_memory_usage.h" + +namespace spdy { + +namespace { + +bool SymbolLengthAndIdCompare(const HpackHuffmanSymbol& a, + const HpackHuffmanSymbol& b) { + if (a.length == b.length) { + return a.id < b.id; + } + return a.length < b.length; +} +bool SymbolIdCompare(const HpackHuffmanSymbol& a, const HpackHuffmanSymbol& b) { + return a.id < b.id; +} + +} // namespace + +HpackHuffmanTable::HpackHuffmanTable() : pad_bits_(0), failed_symbol_id_(0) {} + +HpackHuffmanTable::~HpackHuffmanTable() = default; + +bool HpackHuffmanTable::Initialize(const HpackHuffmanSymbol* input_symbols, + size_t symbol_count) { + CHECK(!IsInitialized()); + DCHECK_LE(symbol_count, std::numeric_limits<uint16_t>::max()); + + std::vector<Symbol> symbols(symbol_count); + // Validate symbol id sequence, and copy into |symbols|. + for (uint16_t i = 0; i < symbol_count; i++) { + if (i != input_symbols[i].id) { + failed_symbol_id_ = i; + return false; + } + symbols[i] = input_symbols[i]; + } + // Order on length and ID ascending, to verify symbol codes are canonical. + std::sort(symbols.begin(), symbols.end(), SymbolLengthAndIdCompare); + if (symbols[0].code != 0) { + failed_symbol_id_ = 0; + return false; + } + for (size_t i = 1; i != symbols.size(); i++) { + unsigned code_shift = 32 - symbols[i - 1].length; + uint32_t code = symbols[i - 1].code + (1 << code_shift); + + if (code != symbols[i].code) { + failed_symbol_id_ = symbols[i].id; + return false; + } + if (code < symbols[i - 1].code) { + // An integer overflow occurred. This implies the input + // lengths do not represent a valid Huffman code. + failed_symbol_id_ = symbols[i].id; + return false; + } + } + if (symbols.back().length < 8) { + // At least one code (such as an EOS symbol) must be 8 bits or longer. + // Without this, some inputs will not be encodable in a whole number + // of bytes. + return false; + } + pad_bits_ = static_cast<uint8_t>(symbols.back().code >> 24); + + // Order on symbol ID ascending. + std::sort(symbols.begin(), symbols.end(), SymbolIdCompare); + BuildEncodeTable(symbols); + return true; +} + +void HpackHuffmanTable::BuildEncodeTable(const std::vector<Symbol>& symbols) { + for (size_t i = 0; i != symbols.size(); i++) { + const Symbol& symbol = symbols[i]; + CHECK_EQ(i, symbol.id); + code_by_id_.push_back(symbol.code); + length_by_id_.push_back(symbol.length); + } +} + +bool HpackHuffmanTable::IsInitialized() const { + return !code_by_id_.empty(); +} + +void HpackHuffmanTable::EncodeString(SpdyStringPiece in, + HpackOutputStream* out) const { + size_t bit_remnant = 0; + for (size_t i = 0; i != in.size(); i++) { + uint16_t symbol_id = static_cast<uint8_t>(in[i]); + CHECK_GT(code_by_id_.size(), symbol_id); + + // Load, and shift code to low bits. + unsigned length = length_by_id_[symbol_id]; + uint32_t code = code_by_id_[symbol_id] >> (32 - length); + + bit_remnant = (bit_remnant + length) % 8; + + if (length > 24) { + out->AppendBits(static_cast<uint8_t>(code >> 24), length - 24); + length = 24; + } + if (length > 16) { + out->AppendBits(static_cast<uint8_t>(code >> 16), length - 16); + length = 16; + } + if (length > 8) { + out->AppendBits(static_cast<uint8_t>(code >> 8), length - 8); + length = 8; + } + out->AppendBits(static_cast<uint8_t>(code), length); + } + if (bit_remnant != 0) { + // Pad current byte as required. + out->AppendBits(pad_bits_ >> bit_remnant, 8 - bit_remnant); + } +} + +size_t HpackHuffmanTable::EncodedSize(SpdyStringPiece in) const { + size_t bit_count = 0; + for (size_t i = 0; i != in.size(); i++) { + uint16_t symbol_id = static_cast<uint8_t>(in[i]); + CHECK_GT(code_by_id_.size(), symbol_id); + + bit_count += length_by_id_[symbol_id]; + } + if (bit_count % 8 != 0) { + bit_count += 8 - bit_count % 8; + } + return bit_count / 8; +} + +size_t HpackHuffmanTable::EstimateMemoryUsage() const { + return SpdyEstimateMemoryUsage(code_by_id_) + + SpdyEstimateMemoryUsage(length_by_id_); +} + +} // namespace spdy diff --git a/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_huffman_table.h b/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_huffman_table.h new file mode 100644 index 00000000000..80d9e5262b7 --- /dev/null +++ b/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_huffman_table.h @@ -0,0 +1,76 @@ +// Copyright 2014 The Chromium 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_SPDY_CORE_HPACK_HPACK_HUFFMAN_TABLE_H_ +#define QUICHE_SPDY_CORE_HPACK_HPACK_HUFFMAN_TABLE_H_ + +#include <cstddef> +#include <cstdint> +#include <vector> + +#include "net/third_party/quiche/src/spdy/core/hpack/hpack_constants.h" +#include "net/third_party/quiche/src/spdy/platform/api/spdy_export.h" +#include "net/third_party/quiche/src/spdy/platform/api/spdy_string.h" +#include "net/third_party/quiche/src/spdy/platform/api/spdy_string_piece.h" + +namespace spdy { + +namespace test { +class HpackHuffmanTablePeer; +} // namespace test + +class HpackOutputStream; + +// HpackHuffmanTable encodes string literals using a constructed canonical +// Huffman code. Once initialized, an instance is read only and may be accessed +// only through its const interface. +class SPDY_EXPORT_PRIVATE HpackHuffmanTable { + public: + friend class test::HpackHuffmanTablePeer; + + typedef HpackHuffmanSymbol Symbol; + + HpackHuffmanTable(); + ~HpackHuffmanTable(); + + // Prepares HpackHuffmanTable to encode the canonical Huffman code as + // determined by the given symbols. Must be called exactly once. + // Returns false if the input symbols define an invalid coding, and true + // otherwise. Symbols must be presented in ascending ID order with no gaps, + // and |symbol_count| must fit in a uint16_t. + bool Initialize(const Symbol* input_symbols, size_t symbol_count); + + // Returns whether Initialize() has been successfully called. + bool IsInitialized() const; + + // Encodes the input string to the output stream using the table's Huffman + // context. + void EncodeString(SpdyStringPiece in, HpackOutputStream* out) const; + + // Returns the encoded size of the input string. + size_t EncodedSize(SpdyStringPiece in) const; + + // Returns the estimate of dynamically allocated memory in bytes. + size_t EstimateMemoryUsage() const; + + private: + // Expects symbols ordered on ID ascending. + void BuildEncodeTable(const std::vector<Symbol>& symbols); + + // Symbol code and code length, in ascending symbol ID order. + // Codes are stored in the most-significant bits of the word. + std::vector<uint32_t> code_by_id_; + std::vector<uint8_t> length_by_id_; + + // The first 8 bits of the longest code. Applied when generating padding bits. + uint8_t pad_bits_; + + // If initialization fails, preserve the symbol ID which failed validation + // for examination in tests. + uint16_t failed_symbol_id_; +}; + +} // namespace spdy + +#endif // QUICHE_SPDY_CORE_HPACK_HPACK_HUFFMAN_TABLE_H_ diff --git a/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_huffman_table_test.cc b/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_huffman_table_test.cc new file mode 100644 index 00000000000..f30926c35e6 --- /dev/null +++ b/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_huffman_table_test.cc @@ -0,0 +1,309 @@ +// Copyright 2014 The Chromium 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/spdy/core/hpack/hpack_huffman_table.h" + +#include <utility> + +#include "base/macros.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "net/third_party/quiche/src/http2/hpack/huffman/hpack_huffman_decoder.h" +#include "net/third_party/quiche/src/spdy/core/hpack/hpack_constants.h" +#include "net/third_party/quiche/src/spdy/core/hpack/hpack_output_stream.h" +#include "net/third_party/quiche/src/spdy/platform/api/spdy_arraysize.h" +#include "net/third_party/quiche/src/spdy/platform/api/spdy_string_utils.h" + +namespace spdy { + +namespace test { + +class HpackHuffmanTablePeer { + public: + explicit HpackHuffmanTablePeer(const HpackHuffmanTable& table) + : table_(table) {} + + const std::vector<uint32_t>& code_by_id() const { return table_.code_by_id_; } + const std::vector<uint8_t>& length_by_id() const { + return table_.length_by_id_; + } + uint8_t pad_bits() const { return table_.pad_bits_; } + uint16_t failed_symbol_id() const { return table_.failed_symbol_id_; } + + private: + const HpackHuffmanTable& table_; +}; + +namespace { + +// Tests of the ability to encode some canonical Huffman code, +// not just the one defined in the RFC 7541. +class GenericHuffmanTableTest : public ::testing::Test { + protected: + GenericHuffmanTableTest() : table_(), peer_(table_) {} + + SpdyString EncodeString(SpdyStringPiece input) { + SpdyString result; + HpackOutputStream output_stream; + table_.EncodeString(input, &output_stream); + + output_stream.TakeString(&result); + // Verify EncodedSize() agrees with EncodeString(). + EXPECT_EQ(result.size(), table_.EncodedSize(input)); + return result; + } + + HpackHuffmanTable table_; + HpackHuffmanTablePeer peer_; +}; + +TEST_F(GenericHuffmanTableTest, InitializeEdgeCases) { + { + // Verify eight symbols can be encoded with 3 bits per symbol. + HpackHuffmanSymbol code[] = {{0b00000000000000000000000000000000, 3, 0}, + {0b00100000000000000000000000000000, 3, 1}, + {0b01000000000000000000000000000000, 3, 2}, + {0b01100000000000000000000000000000, 3, 3}, + {0b10000000000000000000000000000000, 3, 4}, + {0b10100000000000000000000000000000, 3, 5}, + {0b11000000000000000000000000000000, 3, 6}, + {0b11100000000000000000000000000000, 8, 7}}; + HpackHuffmanTable table; + EXPECT_TRUE(table.Initialize(code, SPDY_ARRAYSIZE(code))); + } + { + // But using 2 bits with one symbol overflows the code. + HpackHuffmanSymbol code[] = { + {0b01000000000000000000000000000000, 3, 0}, + {0b01100000000000000000000000000000, 3, 1}, + {0b00000000000000000000000000000000, 2, 2}, + {0b10000000000000000000000000000000, 3, 3}, + {0b10100000000000000000000000000000, 3, 4}, + {0b11000000000000000000000000000000, 3, 5}, + {0b11100000000000000000000000000000, 3, 6}, + {0b00000000000000000000000000000000, 8, 7}}; // Overflow. + HpackHuffmanTable table; + EXPECT_FALSE(table.Initialize(code, SPDY_ARRAYSIZE(code))); + EXPECT_EQ(7, HpackHuffmanTablePeer(table).failed_symbol_id()); + } + { + // Verify four symbols can be encoded with incremental bits per symbol. + HpackHuffmanSymbol code[] = {{0b00000000000000000000000000000000, 1, 0}, + {0b10000000000000000000000000000000, 2, 1}, + {0b11000000000000000000000000000000, 3, 2}, + {0b11100000000000000000000000000000, 8, 3}}; + HpackHuffmanTable table; + EXPECT_TRUE(table.Initialize(code, SPDY_ARRAYSIZE(code))); + } + { + // But repeating a length overflows the code. + HpackHuffmanSymbol code[] = { + {0b00000000000000000000000000000000, 1, 0}, + {0b10000000000000000000000000000000, 2, 1}, + {0b11000000000000000000000000000000, 2, 2}, + {0b00000000000000000000000000000000, 8, 3}}; // Overflow. + HpackHuffmanTable table; + EXPECT_FALSE(table.Initialize(code, SPDY_ARRAYSIZE(code))); + EXPECT_EQ(3, HpackHuffmanTablePeer(table).failed_symbol_id()); + } + { + // Symbol IDs must be assigned sequentially with no gaps. + HpackHuffmanSymbol code[] = { + {0b00000000000000000000000000000000, 1, 0}, + {0b10000000000000000000000000000000, 2, 1}, + {0b11000000000000000000000000000000, 3, 1}, // Repeat. + {0b11100000000000000000000000000000, 8, 3}}; + HpackHuffmanTable table; + EXPECT_FALSE(table.Initialize(code, SPDY_ARRAYSIZE(code))); + EXPECT_EQ(2, HpackHuffmanTablePeer(table).failed_symbol_id()); + } + { + // Canonical codes must begin with zero. + HpackHuffmanSymbol code[] = {{0b10000000000000000000000000000000, 4, 0}, + {0b10010000000000000000000000000000, 4, 1}, + {0b10100000000000000000000000000000, 4, 2}, + {0b10110000000000000000000000000000, 8, 3}}; + HpackHuffmanTable table; + EXPECT_FALSE(table.Initialize(code, SPDY_ARRAYSIZE(code))); + EXPECT_EQ(0, HpackHuffmanTablePeer(table).failed_symbol_id()); + } + { + // Codes must match the expected canonical sequence. + HpackHuffmanSymbol code[] = { + {0b00000000000000000000000000000000, 2, 0}, + {0b01000000000000000000000000000000, 2, 1}, + {0b11000000000000000000000000000000, 2, 2}, // Code not canonical. + {0b10000000000000000000000000000000, 8, 3}}; + HpackHuffmanTable table; + EXPECT_FALSE(table.Initialize(code, SPDY_ARRAYSIZE(code))); + EXPECT_EQ(2, HpackHuffmanTablePeer(table).failed_symbol_id()); + } + { + // At least one code must have a length of 8 bits (to ensure pad-ability). + HpackHuffmanSymbol code[] = {{0b00000000000000000000000000000000, 1, 0}, + {0b10000000000000000000000000000000, 2, 1}, + {0b11000000000000000000000000000000, 3, 2}, + {0b11100000000000000000000000000000, 7, 3}}; + HpackHuffmanTable table; + EXPECT_FALSE(table.Initialize(code, SPDY_ARRAYSIZE(code))); + } +} + +TEST_F(GenericHuffmanTableTest, ValidateInternalsWithSmallCode) { + HpackHuffmanSymbol code[] = { + {0b01100000000000000000000000000000, 4, 0}, // 3rd. + {0b01110000000000000000000000000000, 4, 1}, // 4th. + {0b00000000000000000000000000000000, 2, 2}, // 1st assigned code. + {0b01000000000000000000000000000000, 3, 3}, // 2nd. + {0b10000000000000000000000000000000, 5, 4}, // 5th. + {0b10001000000000000000000000000000, 5, 5}, // 6th. + {0b10011000000000000000000000000000, 8, 6}, // 8th. + {0b10010000000000000000000000000000, 5, 7}}; // 7th. + EXPECT_TRUE(table_.Initialize(code, SPDY_ARRAYSIZE(code))); + + ASSERT_EQ(SPDY_ARRAYSIZE(code), peer_.code_by_id().size()); + ASSERT_EQ(SPDY_ARRAYSIZE(code), peer_.length_by_id().size()); + for (size_t i = 0; i < SPDY_ARRAYSIZE(code); ++i) { + EXPECT_EQ(code[i].code, peer_.code_by_id()[i]); + EXPECT_EQ(code[i].length, peer_.length_by_id()[i]); + } + + EXPECT_EQ(0b10011000, peer_.pad_bits()); + + char input_storage[] = {2, 3, 2, 7, 4}; + SpdyStringPiece input(input_storage, SPDY_ARRAYSIZE(input_storage)); + // By symbol: (2) 00 (3) 010 (2) 00 (7) 10010 (4) 10000 (6 as pad) 1001100. + char expect_storage[] = {0b00010001, 0b00101000, 0b01001100}; + SpdyStringPiece expect(expect_storage, SPDY_ARRAYSIZE(expect_storage)); + EXPECT_EQ(expect, EncodeString(input)); +} + +// Tests of the ability to encode the HPACK Huffman Code, defined in: +// https://httpwg.github.io/specs/rfc7541.html#huffman.code +class HpackHuffmanTableTest : public GenericHuffmanTableTest { + protected: + void SetUp() override { + EXPECT_TRUE(table_.Initialize(HpackHuffmanCodeVector().data(), + HpackHuffmanCodeVector().size())); + EXPECT_TRUE(table_.IsInitialized()); + } + + // Use http2::HpackHuffmanDecoder for roundtrip tests. + void DecodeString(const SpdyString& encoded, SpdyString* out) { + http2::HpackHuffmanDecoder decoder; + out->clear(); + EXPECT_TRUE(decoder.Decode(encoded, out)); + } +}; + +TEST_F(HpackHuffmanTableTest, InitializeHpackCode) { + EXPECT_EQ(peer_.pad_bits(), 0b11111111); // First 8 bits of EOS. +} + +TEST_F(HpackHuffmanTableTest, SpecRequestExamples) { + SpdyString buffer; + SpdyString test_table[] = { + SpdyHexDecode("f1e3c2e5f23a6ba0ab90f4ff"), + "www.example.com", + SpdyHexDecode("a8eb10649cbf"), + "no-cache", + SpdyHexDecode("25a849e95ba97d7f"), + "custom-key", + SpdyHexDecode("25a849e95bb8e8b4bf"), + "custom-value", + }; + // Round-trip each test example. + for (size_t i = 0; i != SPDY_ARRAYSIZE(test_table); i += 2) { + const SpdyString& encodedFixture(test_table[i]); + const SpdyString& decodedFixture(test_table[i + 1]); + DecodeString(encodedFixture, &buffer); + EXPECT_EQ(decodedFixture, buffer); + buffer = EncodeString(decodedFixture); + EXPECT_EQ(encodedFixture, buffer); + } +} + +TEST_F(HpackHuffmanTableTest, SpecResponseExamples) { + SpdyString buffer; + SpdyString test_table[] = { + SpdyHexDecode("6402"), + "302", + SpdyHexDecode("aec3771a4b"), + "private", + SpdyHexDecode("d07abe941054d444a8200595040b8166" + "e082a62d1bff"), + "Mon, 21 Oct 2013 20:13:21 GMT", + SpdyHexDecode("9d29ad171863c78f0b97c8e9ae82ae43" + "d3"), + "https://www.example.com", + SpdyHexDecode("94e7821dd7f2e6c7b335dfdfcd5b3960" + "d5af27087f3672c1ab270fb5291f9587" + "316065c003ed4ee5b1063d5007"), + "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1", + }; + // Round-trip each test example. + for (size_t i = 0; i != SPDY_ARRAYSIZE(test_table); i += 2) { + const SpdyString& encodedFixture(test_table[i]); + const SpdyString& decodedFixture(test_table[i + 1]); + DecodeString(encodedFixture, &buffer); + EXPECT_EQ(decodedFixture, buffer); + buffer = EncodeString(decodedFixture); + EXPECT_EQ(encodedFixture, buffer); + } +} + +TEST_F(HpackHuffmanTableTest, RoundTripIndividualSymbols) { + for (size_t i = 0; i != 256; i++) { + char c = static_cast<char>(i); + char storage[3] = {c, c, c}; + SpdyStringPiece input(storage, SPDY_ARRAYSIZE(storage)); + SpdyString buffer_in = EncodeString(input); + SpdyString buffer_out; + DecodeString(buffer_in, &buffer_out); + EXPECT_EQ(input, buffer_out); + } +} + +TEST_F(HpackHuffmanTableTest, RoundTripSymbolSequence) { + char storage[512]; + for (size_t i = 0; i != 256; i++) { + storage[i] = static_cast<char>(i); + storage[511 - i] = static_cast<char>(i); + } + SpdyStringPiece input(storage, SPDY_ARRAYSIZE(storage)); + SpdyString buffer_in = EncodeString(input); + SpdyString buffer_out; + DecodeString(buffer_in, &buffer_out); + EXPECT_EQ(input, buffer_out); +} + +TEST_F(HpackHuffmanTableTest, EncodedSizeAgreesWithEncodeString) { + SpdyString test_table[] = { + "", + "Mon, 21 Oct 2013 20:13:21 GMT", + "https://www.example.com", + "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1", + SpdyString(1, '\0'), + SpdyString("foo\0bar", 7), + SpdyString(256, '\0'), + }; + for (size_t i = 0; i != 256; ++i) { + // Expand last |test_table| entry to cover all codes. + test_table[SPDY_ARRAYSIZE(test_table) - 1][i] = static_cast<char>(i); + } + + HpackOutputStream output_stream; + SpdyString encoding; + for (size_t i = 0; i != SPDY_ARRAYSIZE(test_table); ++i) { + table_.EncodeString(test_table[i], &output_stream); + output_stream.TakeString(&encoding); + EXPECT_EQ(encoding.size(), table_.EncodedSize(test_table[i])); + } +} + +} // namespace + +} // namespace test + +} // namespace spdy diff --git a/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_output_stream.cc b/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_output_stream.cc new file mode 100644 index 00000000000..30b566294a5 --- /dev/null +++ b/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_output_stream.cc @@ -0,0 +1,97 @@ +// Copyright 2014 The Chromium 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/spdy/core/hpack/hpack_output_stream.h" + +#include <utility> + +#include "base/logging.h" +#include "net/third_party/quiche/src/spdy/platform/api/spdy_estimate_memory_usage.h" + +namespace spdy { + +HpackOutputStream::HpackOutputStream() : bit_offset_(0) {} + +HpackOutputStream::~HpackOutputStream() = default; + +void HpackOutputStream::AppendBits(uint8_t bits, size_t bit_size) { + DCHECK_GT(bit_size, 0u); + DCHECK_LE(bit_size, 8u); + DCHECK_EQ(bits >> bit_size, 0); + size_t new_bit_offset = bit_offset_ + bit_size; + if (bit_offset_ == 0) { + // Buffer ends on a byte boundary. + DCHECK_LE(bit_size, 8u); + buffer_.append(1, bits << (8 - bit_size)); + } else if (new_bit_offset <= 8) { + // Buffer does not end on a byte boundary but the given bits fit + // in the remainder of the last byte. + buffer_.back() |= bits << (8 - new_bit_offset); + } else { + // Buffer does not end on a byte boundary and the given bits do + // not fit in the remainder of the last byte. + buffer_.back() |= bits >> (new_bit_offset - 8); + buffer_.append(1, bits << (16 - new_bit_offset)); + } + bit_offset_ = new_bit_offset % 8; +} + +void HpackOutputStream::AppendPrefix(HpackPrefix prefix) { + AppendBits(prefix.bits, prefix.bit_size); +} + +void HpackOutputStream::AppendBytes(SpdyStringPiece buffer) { + DCHECK_EQ(bit_offset_, 0u); + buffer_.append(buffer.data(), buffer.size()); +} + +void HpackOutputStream::AppendUint32(uint32_t I) { + // The algorithm below is adapted from the pseudocode in 6.1. + size_t N = 8 - bit_offset_; + uint8_t max_first_byte = static_cast<uint8_t>((1 << N) - 1); + if (I < max_first_byte) { + AppendBits(static_cast<uint8_t>(I), N); + } else { + AppendBits(max_first_byte, N); + I -= max_first_byte; + while ((I & ~0x7f) != 0) { + buffer_.append(1, (I & 0x7f) | 0x80); + I >>= 7; + } + AppendBits(static_cast<uint8_t>(I), 8); + } +} + +void HpackOutputStream::TakeString(SpdyString* output) { + // This must hold, since all public functions cause the buffer to + // end on a byte boundary. + DCHECK_EQ(bit_offset_, 0u); + buffer_.swap(*output); + buffer_.clear(); + bit_offset_ = 0; +} + +void HpackOutputStream::BoundedTakeString(size_t max_size, SpdyString* output) { + if (buffer_.size() > max_size) { + // Save off overflow bytes to temporary string (causes a copy). + SpdyString overflow(buffer_.data() + max_size, buffer_.size() - max_size); + + // Resize buffer down to the given limit. + buffer_.resize(max_size); + + // Give buffer to output string. + *output = std::move(buffer_); + + // Reset to contain overflow. + buffer_ = std::move(overflow); + } else { + TakeString(output); + } +} + +size_t HpackOutputStream::EstimateMemoryUsage() const { + return SpdyEstimateMemoryUsage(buffer_); +} + +} // namespace spdy diff --git a/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_output_stream.h b/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_output_stream.h new file mode 100644 index 00000000000..450803ff471 --- /dev/null +++ b/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_output_stream.h @@ -0,0 +1,76 @@ +// Copyright 2014 The Chromium 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_SPDY_CORE_HPACK_HPACK_OUTPUT_STREAM_H_ +#define QUICHE_SPDY_CORE_HPACK_HPACK_OUTPUT_STREAM_H_ + +#include <cstdint> +#include <map> + +#include "base/macros.h" +#include "net/third_party/quiche/src/spdy/core/hpack/hpack_constants.h" +#include "net/third_party/quiche/src/spdy/platform/api/spdy_export.h" +#include "net/third_party/quiche/src/spdy/platform/api/spdy_string.h" +#include "net/third_party/quiche/src/spdy/platform/api/spdy_string_piece.h" + +// All section references below are to +// http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-08 + +namespace spdy { + +// An HpackOutputStream handles all the low-level details of encoding +// header fields. +class SPDY_EXPORT_PRIVATE HpackOutputStream { + public: + HpackOutputStream(); + HpackOutputStream(const HpackOutputStream&) = delete; + HpackOutputStream& operator=(const HpackOutputStream&) = delete; + ~HpackOutputStream(); + + // Appends the lower |bit_size| bits of |bits| to the internal buffer. + // + // |bit_size| must be > 0 and <= 8. |bits| must not have any bits + // set other than the lower |bit_size| bits. + void AppendBits(uint8_t bits, size_t bit_size); + + // Simply forwards to AppendBits(prefix.bits, prefix.bit-size). + void AppendPrefix(HpackPrefix prefix); + + // Directly appends |buffer|. + void AppendBytes(SpdyStringPiece buffer); + + // Appends the given integer using the representation described in + // 6.1. If the internal buffer ends on a byte boundary, the prefix + // length N is taken to be 8; otherwise, it is taken to be the + // number of bits to the next byte boundary. + // + // It is guaranteed that the internal buffer will end on a byte + // boundary after this function is called. + void AppendUint32(uint32_t I); + + // Swaps the internal buffer with |output|, then resets state. + void TakeString(SpdyString* output); + + // Gives up to |max_size| bytes of the internal buffer to |output|. Resets + // internal state with the overflow. + void BoundedTakeString(size_t max_size, SpdyString* output); + + // Size in bytes of stream's internal buffer. + size_t size() const { return buffer_.size(); } + + // Returns the estimate of dynamically allocated memory in bytes. + size_t EstimateMemoryUsage() const; + + private: + // The internal bit buffer. + SpdyString buffer_; + + // If 0, the buffer ends on a byte boundary. If non-zero, the buffer + // ends on the nth most significant bit. Guaranteed to be < 8. + size_t bit_offset_; +}; + +} // namespace spdy + +#endif // QUICHE_SPDY_CORE_HPACK_HPACK_OUTPUT_STREAM_H_ diff --git a/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_output_stream_test.cc b/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_output_stream_test.cc new file mode 100644 index 00000000000..823c41bf0c3 --- /dev/null +++ b/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_output_stream_test.cc @@ -0,0 +1,276 @@ +// Copyright 2014 The Chromium 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/spdy/core/hpack/hpack_output_stream.h" + +#include <cstddef> + +#include "testing/gtest/include/gtest/gtest.h" + +namespace spdy { + +namespace { + +// Make sure that AppendBits() appends bits starting from the most +// significant bit, and that it can handle crossing a byte boundary. +TEST(HpackOutputStreamTest, AppendBits) { + HpackOutputStream output_stream; + SpdyString expected_str; + + output_stream.AppendBits(0x1, 1); + expected_str.append(1, 0x00); + expected_str.back() |= (0x1 << 7); + + output_stream.AppendBits(0x0, 1); + + output_stream.AppendBits(0x3, 2); + *expected_str.rbegin() |= (0x3 << 4); + + output_stream.AppendBits(0x0, 2); + + // Byte-crossing append. + output_stream.AppendBits(0x7, 3); + *expected_str.rbegin() |= (0x7 >> 1); + expected_str.append(1, 0x00); + expected_str.back() |= (0x7 << 7); + + output_stream.AppendBits(0x0, 7); + + SpdyString str; + output_stream.TakeString(&str); + EXPECT_EQ(expected_str, str); +} + +// Utility function to return I as a string encoded with an N-bit +// prefix. +SpdyString EncodeUint32(uint8_t N, uint32_t I) { + HpackOutputStream output_stream; + if (N < 8) { + output_stream.AppendBits(0x00, 8 - N); + } + output_stream.AppendUint32(I); + SpdyString str; + output_stream.TakeString(&str); + return str; +} + +// The {Number}ByteIntegersEightBitPrefix tests below test that +// certain integers are encoded correctly with an 8-bit prefix in +// exactly {Number} bytes. + +TEST(HpackOutputStreamTest, OneByteIntegersEightBitPrefix) { + // Minimum. + EXPECT_EQ(SpdyString("\x00", 1), EncodeUint32(8, 0x00)); + EXPECT_EQ("\x7f", EncodeUint32(8, 0x7f)); + // Maximum. + EXPECT_EQ("\xfe", EncodeUint32(8, 0xfe)); +} + +TEST(HpackOutputStreamTest, TwoByteIntegersEightBitPrefix) { + // Minimum. + EXPECT_EQ(SpdyString("\xff\x00", 2), EncodeUint32(8, 0xff)); + EXPECT_EQ("\xff\x01", EncodeUint32(8, 0x0100)); + // Maximum. + EXPECT_EQ("\xff\x7f", EncodeUint32(8, 0x017e)); +} + +TEST(HpackOutputStreamTest, ThreeByteIntegersEightBitPrefix) { + // Minimum. + EXPECT_EQ("\xff\x80\x01", EncodeUint32(8, 0x017f)); + EXPECT_EQ("\xff\x80\x1e", EncodeUint32(8, 0x0fff)); + // Maximum. + EXPECT_EQ("\xff\xff\x7f", EncodeUint32(8, 0x40fe)); +} + +TEST(HpackOutputStreamTest, FourByteIntegersEightBitPrefix) { + // Minimum. + EXPECT_EQ("\xff\x80\x80\x01", EncodeUint32(8, 0x40ff)); + EXPECT_EQ("\xff\x80\xfe\x03", EncodeUint32(8, 0xffff)); + // Maximum. + EXPECT_EQ("\xff\xff\xff\x7f", EncodeUint32(8, 0x002000fe)); +} + +TEST(HpackOutputStreamTest, FiveByteIntegersEightBitPrefix) { + // Minimum. + EXPECT_EQ("\xff\x80\x80\x80\x01", EncodeUint32(8, 0x002000ff)); + EXPECT_EQ("\xff\x80\xfe\xff\x07", EncodeUint32(8, 0x00ffffff)); + // Maximum. + EXPECT_EQ("\xff\xff\xff\xff\x7f", EncodeUint32(8, 0x100000fe)); +} + +TEST(HpackOutputStreamTest, SixByteIntegersEightBitPrefix) { + // Minimum. + EXPECT_EQ("\xff\x80\x80\x80\x80\x01", EncodeUint32(8, 0x100000ff)); + // Maximum. + EXPECT_EQ("\xff\x80\xfe\xff\xff\x0f", EncodeUint32(8, 0xffffffff)); +} + +// The {Number}ByteIntegersOneToSevenBitPrefix tests below test that +// certain integers are encoded correctly with an N-bit prefix in +// exactly {Number} bytes for N in {1, 2, ..., 7}. + +TEST(HpackOutputStreamTest, OneByteIntegersOneToSevenBitPrefixes) { + // Minimums. + EXPECT_EQ(SpdyString("\x00", 1), EncodeUint32(7, 0x00)); + EXPECT_EQ(SpdyString("\x00", 1), EncodeUint32(6, 0x00)); + EXPECT_EQ(SpdyString("\x00", 1), EncodeUint32(5, 0x00)); + EXPECT_EQ(SpdyString("\x00", 1), EncodeUint32(4, 0x00)); + EXPECT_EQ(SpdyString("\x00", 1), EncodeUint32(3, 0x00)); + EXPECT_EQ(SpdyString("\x00", 1), EncodeUint32(2, 0x00)); + EXPECT_EQ(SpdyString("\x00", 1), EncodeUint32(1, 0x00)); + + // Maximums. + EXPECT_EQ("\x7e", EncodeUint32(7, 0x7e)); + EXPECT_EQ("\x3e", EncodeUint32(6, 0x3e)); + EXPECT_EQ("\x1e", EncodeUint32(5, 0x1e)); + EXPECT_EQ("\x0e", EncodeUint32(4, 0x0e)); + EXPECT_EQ("\x06", EncodeUint32(3, 0x06)); + EXPECT_EQ("\x02", EncodeUint32(2, 0x02)); + EXPECT_EQ(SpdyString("\x00", 1), EncodeUint32(1, 0x00)); +} + +TEST(HpackOutputStreamTest, TwoByteIntegersOneToSevenBitPrefixes) { + // Minimums. + EXPECT_EQ(SpdyString("\x7f\x00", 2), EncodeUint32(7, 0x7f)); + EXPECT_EQ(SpdyString("\x3f\x00", 2), EncodeUint32(6, 0x3f)); + EXPECT_EQ(SpdyString("\x1f\x00", 2), EncodeUint32(5, 0x1f)); + EXPECT_EQ(SpdyString("\x0f\x00", 2), EncodeUint32(4, 0x0f)); + EXPECT_EQ(SpdyString("\x07\x00", 2), EncodeUint32(3, 0x07)); + EXPECT_EQ(SpdyString("\x03\x00", 2), EncodeUint32(2, 0x03)); + EXPECT_EQ(SpdyString("\x01\x00", 2), EncodeUint32(1, 0x01)); + + // Maximums. + EXPECT_EQ("\x7f\x7f", EncodeUint32(7, 0xfe)); + EXPECT_EQ("\x3f\x7f", EncodeUint32(6, 0xbe)); + EXPECT_EQ("\x1f\x7f", EncodeUint32(5, 0x9e)); + EXPECT_EQ("\x0f\x7f", EncodeUint32(4, 0x8e)); + EXPECT_EQ("\x07\x7f", EncodeUint32(3, 0x86)); + EXPECT_EQ("\x03\x7f", EncodeUint32(2, 0x82)); + EXPECT_EQ("\x01\x7f", EncodeUint32(1, 0x80)); +} + +TEST(HpackOutputStreamTest, ThreeByteIntegersOneToSevenBitPrefixes) { + // Minimums. + EXPECT_EQ("\x7f\x80\x01", EncodeUint32(7, 0xff)); + EXPECT_EQ("\x3f\x80\x01", EncodeUint32(6, 0xbf)); + EXPECT_EQ("\x1f\x80\x01", EncodeUint32(5, 0x9f)); + EXPECT_EQ("\x0f\x80\x01", EncodeUint32(4, 0x8f)); + EXPECT_EQ("\x07\x80\x01", EncodeUint32(3, 0x87)); + EXPECT_EQ("\x03\x80\x01", EncodeUint32(2, 0x83)); + EXPECT_EQ("\x01\x80\x01", EncodeUint32(1, 0x81)); + + // Maximums. + EXPECT_EQ("\x7f\xff\x7f", EncodeUint32(7, 0x407e)); + EXPECT_EQ("\x3f\xff\x7f", EncodeUint32(6, 0x403e)); + EXPECT_EQ("\x1f\xff\x7f", EncodeUint32(5, 0x401e)); + EXPECT_EQ("\x0f\xff\x7f", EncodeUint32(4, 0x400e)); + EXPECT_EQ("\x07\xff\x7f", EncodeUint32(3, 0x4006)); + EXPECT_EQ("\x03\xff\x7f", EncodeUint32(2, 0x4002)); + EXPECT_EQ("\x01\xff\x7f", EncodeUint32(1, 0x4000)); +} + +TEST(HpackOutputStreamTest, FourByteIntegersOneToSevenBitPrefixes) { + // Minimums. + EXPECT_EQ("\x7f\x80\x80\x01", EncodeUint32(7, 0x407f)); + EXPECT_EQ("\x3f\x80\x80\x01", EncodeUint32(6, 0x403f)); + EXPECT_EQ("\x1f\x80\x80\x01", EncodeUint32(5, 0x401f)); + EXPECT_EQ("\x0f\x80\x80\x01", EncodeUint32(4, 0x400f)); + EXPECT_EQ("\x07\x80\x80\x01", EncodeUint32(3, 0x4007)); + EXPECT_EQ("\x03\x80\x80\x01", EncodeUint32(2, 0x4003)); + EXPECT_EQ("\x01\x80\x80\x01", EncodeUint32(1, 0x4001)); + + // Maximums. + EXPECT_EQ("\x7f\xff\xff\x7f", EncodeUint32(7, 0x20007e)); + EXPECT_EQ("\x3f\xff\xff\x7f", EncodeUint32(6, 0x20003e)); + EXPECT_EQ("\x1f\xff\xff\x7f", EncodeUint32(5, 0x20001e)); + EXPECT_EQ("\x0f\xff\xff\x7f", EncodeUint32(4, 0x20000e)); + EXPECT_EQ("\x07\xff\xff\x7f", EncodeUint32(3, 0x200006)); + EXPECT_EQ("\x03\xff\xff\x7f", EncodeUint32(2, 0x200002)); + EXPECT_EQ("\x01\xff\xff\x7f", EncodeUint32(1, 0x200000)); +} + +TEST(HpackOutputStreamTest, FiveByteIntegersOneToSevenBitPrefixes) { + // Minimums. + EXPECT_EQ("\x7f\x80\x80\x80\x01", EncodeUint32(7, 0x20007f)); + EXPECT_EQ("\x3f\x80\x80\x80\x01", EncodeUint32(6, 0x20003f)); + EXPECT_EQ("\x1f\x80\x80\x80\x01", EncodeUint32(5, 0x20001f)); + EXPECT_EQ("\x0f\x80\x80\x80\x01", EncodeUint32(4, 0x20000f)); + EXPECT_EQ("\x07\x80\x80\x80\x01", EncodeUint32(3, 0x200007)); + EXPECT_EQ("\x03\x80\x80\x80\x01", EncodeUint32(2, 0x200003)); + EXPECT_EQ("\x01\x80\x80\x80\x01", EncodeUint32(1, 0x200001)); + + // Maximums. + EXPECT_EQ("\x7f\xff\xff\xff\x7f", EncodeUint32(7, 0x1000007e)); + EXPECT_EQ("\x3f\xff\xff\xff\x7f", EncodeUint32(6, 0x1000003e)); + EXPECT_EQ("\x1f\xff\xff\xff\x7f", EncodeUint32(5, 0x1000001e)); + EXPECT_EQ("\x0f\xff\xff\xff\x7f", EncodeUint32(4, 0x1000000e)); + EXPECT_EQ("\x07\xff\xff\xff\x7f", EncodeUint32(3, 0x10000006)); + EXPECT_EQ("\x03\xff\xff\xff\x7f", EncodeUint32(2, 0x10000002)); + EXPECT_EQ("\x01\xff\xff\xff\x7f", EncodeUint32(1, 0x10000000)); +} + +TEST(HpackOutputStreamTest, SixByteIntegersOneToSevenBitPrefixes) { + // Minimums. + EXPECT_EQ("\x7f\x80\x80\x80\x80\x01", EncodeUint32(7, 0x1000007f)); + EXPECT_EQ("\x3f\x80\x80\x80\x80\x01", EncodeUint32(6, 0x1000003f)); + EXPECT_EQ("\x1f\x80\x80\x80\x80\x01", EncodeUint32(5, 0x1000001f)); + EXPECT_EQ("\x0f\x80\x80\x80\x80\x01", EncodeUint32(4, 0x1000000f)); + EXPECT_EQ("\x07\x80\x80\x80\x80\x01", EncodeUint32(3, 0x10000007)); + EXPECT_EQ("\x03\x80\x80\x80\x80\x01", EncodeUint32(2, 0x10000003)); + EXPECT_EQ("\x01\x80\x80\x80\x80\x01", EncodeUint32(1, 0x10000001)); + + // Maximums. + EXPECT_EQ("\x7f\x80\xff\xff\xff\x0f", EncodeUint32(7, 0xffffffff)); + EXPECT_EQ("\x3f\xc0\xff\xff\xff\x0f", EncodeUint32(6, 0xffffffff)); + EXPECT_EQ("\x1f\xe0\xff\xff\xff\x0f", EncodeUint32(5, 0xffffffff)); + EXPECT_EQ("\x0f\xf0\xff\xff\xff\x0f", EncodeUint32(4, 0xffffffff)); + EXPECT_EQ("\x07\xf8\xff\xff\xff\x0f", EncodeUint32(3, 0xffffffff)); + EXPECT_EQ("\x03\xfc\xff\xff\xff\x0f", EncodeUint32(2, 0xffffffff)); + EXPECT_EQ("\x01\xfe\xff\xff\xff\x0f", EncodeUint32(1, 0xffffffff)); +} + +// Test that encoding an integer with an N-bit prefix preserves the +// upper (8-N) bits of the first byte. +TEST(HpackOutputStreamTest, AppendUint32PreservesUpperBits) { + HpackOutputStream output_stream; + output_stream.AppendBits(0x7f, 7); + output_stream.AppendUint32(0x01); + SpdyString str; + output_stream.TakeString(&str); + EXPECT_EQ(SpdyString("\xff\x00", 2), str); +} + +TEST(HpackOutputStreamTest, AppendBytes) { + HpackOutputStream output_stream; + + output_stream.AppendBytes("buffer1"); + output_stream.AppendBytes("buffer2"); + + SpdyString str; + output_stream.TakeString(&str); + EXPECT_EQ("buffer1buffer2", str); +} + +TEST(HpackOutputStreamTest, BoundedTakeString) { + HpackOutputStream output_stream; + + output_stream.AppendBytes("buffer12"); + output_stream.AppendBytes("buffer456"); + + SpdyString str; + output_stream.BoundedTakeString(9, &str); + EXPECT_EQ("buffer12b", str); + + output_stream.AppendBits(0x7f, 7); + output_stream.AppendUint32(0x11); + output_stream.BoundedTakeString(9, &str); + EXPECT_EQ("uffer456\xff", str); + + output_stream.BoundedTakeString(9, &str); + EXPECT_EQ("\x10", str); +} + +} // namespace + +} // namespace spdy diff --git a/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_round_trip_test.cc b/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_round_trip_test.cc new file mode 100644 index 00000000000..4b3a8481cb2 --- /dev/null +++ b/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_round_trip_test.cc @@ -0,0 +1,227 @@ +// Copyright 2014 The Chromium 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 <cmath> +#include <cstdint> +#include <ctime> +#include <vector> + +#include "testing/gtest/include/gtest/gtest.h" +#include "net/third_party/quiche/src/http2/test_tools/http2_random.h" +#include "net/third_party/quiche/src/spdy/core/hpack/hpack_constants.h" +#include "net/third_party/quiche/src/spdy/core/hpack/hpack_decoder_adapter.h" +#include "net/third_party/quiche/src/spdy/core/hpack/hpack_encoder.h" +#include "net/third_party/quiche/src/spdy/core/spdy_test_utils.h" +#include "net/third_party/quiche/src/spdy/platform/api/spdy_string.h" + +namespace spdy { +namespace test { + +namespace { + +// Supports testing with the input split at every byte boundary. +enum InputSizeParam { ALL_INPUT, ONE_BYTE, ZERO_THEN_ONE_BYTE }; + +class HpackRoundTripTest : public ::testing::TestWithParam<InputSizeParam> { + protected: + HpackRoundTripTest() : encoder_(ObtainHpackHuffmanTable()), decoder_() {} + + void SetUp() override { + // Use a small table size to tickle eviction handling. + encoder_.ApplyHeaderTableSizeSetting(256); + decoder_.ApplyHeaderTableSizeSetting(256); + } + + bool RoundTrip(const SpdyHeaderBlock& header_set) { + SpdyString encoded; + encoder_.EncodeHeaderSet(header_set, &encoded); + + bool success = true; + if (GetParam() == ALL_INPUT) { + // Pass all the input to the decoder at once. + success = decoder_.HandleControlFrameHeadersData(encoded.data(), + encoded.size()); + } else if (GetParam() == ONE_BYTE) { + // Pass the input to the decoder one byte at a time. + const char* data = encoded.data(); + for (size_t ndx = 0; ndx < encoded.size() && success; ++ndx) { + success = decoder_.HandleControlFrameHeadersData(data + ndx, 1); + } + } else if (GetParam() == ZERO_THEN_ONE_BYTE) { + // Pass the input to the decoder one byte at a time, but before each + // byte pass an empty buffer. + const char* data = encoded.data(); + for (size_t ndx = 0; ndx < encoded.size() && success; ++ndx) { + success = (decoder_.HandleControlFrameHeadersData(data + ndx, 0) && + decoder_.HandleControlFrameHeadersData(data + ndx, 1)); + } + } else { + ADD_FAILURE() << "Unknown param: " << GetParam(); + } + + if (success) { + success = decoder_.HandleControlFrameHeadersComplete(nullptr); + } + + EXPECT_EQ(header_set, decoder_.decoded_block()); + return success; + } + + size_t SampleExponential(size_t mean, size_t sanity_bound) { + return std::min<size_t>(-std::log(random_.RandDouble()) * mean, + sanity_bound); + } + + http2::test::Http2Random random_; + HpackEncoder encoder_; + HpackDecoderAdapter decoder_; +}; + +INSTANTIATE_TEST_CASE_P(Tests, + HpackRoundTripTest, + ::testing::Values(ALL_INPUT, + ONE_BYTE, + ZERO_THEN_ONE_BYTE)); + +TEST_P(HpackRoundTripTest, ResponseFixtures) { + { + SpdyHeaderBlock headers; + headers[":status"] = "302"; + headers["cache-control"] = "private"; + headers["date"] = "Mon, 21 Oct 2013 20:13:21 GMT"; + headers["location"] = "https://www.example.com"; + EXPECT_TRUE(RoundTrip(headers)); + } + { + SpdyHeaderBlock headers; + headers[":status"] = "200"; + headers["cache-control"] = "private"; + headers["date"] = "Mon, 21 Oct 2013 20:13:21 GMT"; + headers["location"] = "https://www.example.com"; + EXPECT_TRUE(RoundTrip(headers)); + } + { + SpdyHeaderBlock headers; + headers[":status"] = "200"; + headers["cache-control"] = "private"; + headers["content-encoding"] = "gzip"; + headers["date"] = "Mon, 21 Oct 2013 20:13:22 GMT"; + headers["location"] = "https://www.example.com"; + headers["set-cookie"] = + "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU;" + " max-age=3600; version=1"; + headers["multivalue"] = SpdyString("foo\0bar", 7); + EXPECT_TRUE(RoundTrip(headers)); + } +} + +TEST_P(HpackRoundTripTest, RequestFixtures) { + { + SpdyHeaderBlock headers; + headers[":authority"] = "www.example.com"; + headers[":method"] = "GET"; + headers[":path"] = "/"; + headers[":scheme"] = "http"; + headers["cookie"] = "baz=bing; foo=bar"; + EXPECT_TRUE(RoundTrip(headers)); + } + { + SpdyHeaderBlock headers; + headers[":authority"] = "www.example.com"; + headers[":method"] = "GET"; + headers[":path"] = "/"; + headers[":scheme"] = "http"; + headers["cache-control"] = "no-cache"; + headers["cookie"] = "foo=bar; spam=eggs"; + EXPECT_TRUE(RoundTrip(headers)); + } + { + SpdyHeaderBlock headers; + headers[":authority"] = "www.example.com"; + headers[":method"] = "GET"; + headers[":path"] = "/index.html"; + headers[":scheme"] = "https"; + headers["custom-key"] = "custom-value"; + headers["cookie"] = "baz=bing; fizzle=fazzle; garbage"; + headers["multivalue"] = SpdyString("foo\0bar", 7); + EXPECT_TRUE(RoundTrip(headers)); + } +} + +TEST_P(HpackRoundTripTest, RandomizedExamples) { + // Grow vectors of names & values, which are seeded with fixtures and then + // expanded with dynamically generated data. Samples are taken using the + // exponential distribution. + std::vector<SpdyString> pseudo_header_names, random_header_names; + pseudo_header_names.push_back(":authority"); + pseudo_header_names.push_back(":path"); + pseudo_header_names.push_back(":status"); + + // TODO(jgraettinger): Enable "cookie" as a name fixture. Crumbs may be + // reconstructed in any order, which breaks the simple validation used here. + + std::vector<SpdyString> values; + values.push_back("/"); + values.push_back("/index.html"); + values.push_back("200"); + values.push_back("404"); + values.push_back(""); + values.push_back("baz=bing; foo=bar; garbage"); + values.push_back("baz=bing; fizzle=fazzle; garbage"); + + for (size_t i = 0; i != 2000; ++i) { + SpdyHeaderBlock headers; + + // Choose a random number of headers to add, and of these a random subset + // will be HTTP/2 pseudo headers. + size_t header_count = 1 + SampleExponential(7, 50); + size_t pseudo_header_count = + std::min(header_count, 1 + SampleExponential(7, 50)); + EXPECT_LE(pseudo_header_count, header_count); + for (size_t j = 0; j != header_count; ++j) { + SpdyString name, value; + // Pseudo headers must be added before regular headers. + if (j < pseudo_header_count) { + // Choose one of the defined pseudo headers at random. + size_t name_index = random_.Uniform(pseudo_header_names.size()); + name = pseudo_header_names[name_index]; + } else { + // Randomly reuse an existing header name, or generate a new one. + size_t name_index = SampleExponential(20, 200); + if (name_index >= random_header_names.size()) { + name = random_.RandString(1 + SampleExponential(5, 30)); + // A regular header cannot begin with the pseudo header prefix ":". + if (name[0] == ':') { + name[0] = 'x'; + } + random_header_names.push_back(name); + } else { + name = random_header_names[name_index]; + } + } + + // Randomly reuse an existing value, or generate a new one. + size_t value_index = SampleExponential(20, 200); + if (value_index >= values.size()) { + SpdyString newvalue = random_.RandString(1 + SampleExponential(15, 75)); + // Currently order is not preserved in the encoder. In particular, + // when a value is decomposed at \0 delimiters, its parts might get + // encoded out of order if some but not all of them already exist in + // the header table. For now, avoid \0 bytes in values. + std::replace(newvalue.begin(), newvalue.end(), '\x00', '\x01'); + values.push_back(newvalue); + value = values.back(); + } else { + value = values[value_index]; + } + headers[name] = value; + } + EXPECT_TRUE(RoundTrip(headers)); + } +} + +} // namespace + +} // namespace test +} // namespace spdy diff --git a/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_static_table.cc b/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_static_table.cc new file mode 100644 index 00000000000..14e282ec5ff --- /dev/null +++ b/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_static_table.cc @@ -0,0 +1,50 @@ +// Copyright 2014 The Chromium 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/spdy/core/hpack/hpack_static_table.h" + +#include "base/logging.h" +#include "net/third_party/quiche/src/spdy/core/hpack/hpack_constants.h" +#include "net/third_party/quiche/src/spdy/core/hpack/hpack_entry.h" +#include "net/third_party/quiche/src/spdy/platform/api/spdy_estimate_memory_usage.h" +#include "net/third_party/quiche/src/spdy/platform/api/spdy_string_piece.h" + +namespace spdy { + +HpackStaticTable::HpackStaticTable() = default; + +HpackStaticTable::~HpackStaticTable() = default; + +void HpackStaticTable::Initialize(const HpackStaticEntry* static_entry_table, + size_t static_entry_count) { + CHECK(!IsInitialized()); + + int total_insertions = 0; + for (const HpackStaticEntry* it = static_entry_table; + it != static_entry_table + static_entry_count; ++it) { + static_entries_.push_back( + HpackEntry(SpdyStringPiece(it->name, it->name_len), + SpdyStringPiece(it->value, it->value_len), + true, // is_static + total_insertions)); + HpackEntry* entry = &static_entries_.back(); + CHECK(static_index_.insert(entry).second); + // Multiple static entries may have the same name, so inserts may fail. + static_name_index_.insert(std::make_pair(entry->name(), entry)); + + ++total_insertions; + } +} + +bool HpackStaticTable::IsInitialized() const { + return !static_entries_.empty(); +} + +size_t HpackStaticTable::EstimateMemoryUsage() const { + return SpdyEstimateMemoryUsage(static_entries_) + + SpdyEstimateMemoryUsage(static_index_) + + SpdyEstimateMemoryUsage(static_name_index_); +} + +} // namespace spdy diff --git a/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_static_table.h b/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_static_table.h new file mode 100644 index 00000000000..f354a120ca5 --- /dev/null +++ b/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_static_table.h @@ -0,0 +1,54 @@ +// Copyright 2014 The Chromium 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_SPDY_CORE_HPACK_HPACK_STATIC_TABLE_H_ +#define QUICHE_SPDY_CORE_HPACK_HPACK_STATIC_TABLE_H_ + +#include "net/third_party/quiche/src/spdy/core/hpack/hpack_header_table.h" +#include "net/third_party/quiche/src/spdy/platform/api/spdy_export.h" + +namespace spdy { + +struct HpackStaticEntry; + +// HpackStaticTable provides |static_entries_| and |static_index_| for HPACK +// encoding and decoding contexts. Once initialized, an instance is read only +// and may be accessed only through its const interface. Such an instance may +// be shared accross multiple HPACK contexts. +class SPDY_EXPORT_PRIVATE HpackStaticTable { + public: + HpackStaticTable(); + ~HpackStaticTable(); + + // Prepares HpackStaticTable by filling up static_entries_ and static_index_ + // from an array of struct HpackStaticEntry. Must be called exactly once. + void Initialize(const HpackStaticEntry* static_entry_table, + size_t static_entry_count); + + // Returns whether Initialize() has been called. + bool IsInitialized() const; + + // Accessors. + const HpackHeaderTable::EntryTable& GetStaticEntries() const { + return static_entries_; + } + const HpackHeaderTable::UnorderedEntrySet& GetStaticIndex() const { + return static_index_; + } + const HpackHeaderTable::NameToEntryMap& GetStaticNameIndex() const { + return static_name_index_; + } + + // Returns the estimate of dynamically allocated memory in bytes. + size_t EstimateMemoryUsage() const; + + private: + HpackHeaderTable::EntryTable static_entries_; + HpackHeaderTable::UnorderedEntrySet static_index_; + HpackHeaderTable::NameToEntryMap static_name_index_; +}; + +} // namespace spdy + +#endif // QUICHE_SPDY_CORE_HPACK_HPACK_STATIC_TABLE_H_ diff --git a/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_static_table_test.cc b/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_static_table_test.cc new file mode 100644 index 00000000000..4550be018a6 --- /dev/null +++ b/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_static_table_test.cc @@ -0,0 +1,60 @@ +// Copyright 2014 The Chromium 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/spdy/core/hpack/hpack_static_table.h" + +#include <set> +#include <vector> + +#include "testing/gtest/include/gtest/gtest.h" +#include "net/third_party/quiche/src/spdy/core/hpack/hpack_constants.h" +#include "net/third_party/quiche/src/spdy/platform/api/spdy_string_piece.h" + +namespace spdy { + +namespace test { + +namespace { + +class HpackStaticTableTest : public ::testing::Test { + protected: + HpackStaticTableTest() : table_() {} + + HpackStaticTable table_; +}; + +// Check that an initialized instance has the right number of entries. +TEST_F(HpackStaticTableTest, Initialize) { + EXPECT_FALSE(table_.IsInitialized()); + table_.Initialize(HpackStaticTableVector().data(), + HpackStaticTableVector().size()); + EXPECT_TRUE(table_.IsInitialized()); + + HpackHeaderTable::EntryTable static_entries = table_.GetStaticEntries(); + EXPECT_EQ(HpackStaticTableVector().size(), static_entries.size()); + + HpackHeaderTable::UnorderedEntrySet static_index = table_.GetStaticIndex(); + EXPECT_EQ(HpackStaticTableVector().size(), static_index.size()); + + HpackHeaderTable::NameToEntryMap static_name_index = + table_.GetStaticNameIndex(); + std::set<SpdyStringPiece> names; + for (auto* entry : static_index) { + names.insert(entry->name()); + } + EXPECT_EQ(names.size(), static_name_index.size()); +} + +// Test that ObtainHpackStaticTable returns the same instance every time. +TEST_F(HpackStaticTableTest, IsSingleton) { + const HpackStaticTable* static_table_one = &ObtainHpackStaticTable(); + const HpackStaticTable* static_table_two = &ObtainHpackStaticTable(); + EXPECT_EQ(static_table_one, static_table_two); +} + +} // namespace + +} // namespace test + +} // namespace spdy diff --git a/chromium/net/third_party/quiche/src/spdy/core/http2_frame_decoder_adapter.cc b/chromium/net/third_party/quiche/src/spdy/core/http2_frame_decoder_adapter.cc new file mode 100644 index 00000000000..c4e728d2334 --- /dev/null +++ b/chromium/net/third_party/quiche/src/spdy/core/http2_frame_decoder_adapter.cc @@ -0,0 +1,1022 @@ +// Copyright 2016 The Chromium 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/spdy/core/http2_frame_decoder_adapter.h" + +// Logging policy: If an error in the input is detected, VLOG(n) is used so that +// the option exists to debug the situation. Otherwise, this code mostly uses +// DVLOG so that the logging does not slow down production code when things are +// working OK. + +#include <stddef.h> + +#include <cstdint> +#include <cstring> +#include <utility> + +#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.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_string.h" +#include "net/third_party/quiche/src/spdy/core/hpack/hpack_decoder_adapter.h" +#include "net/third_party/quiche/src/spdy/core/hpack/hpack_header_table.h" +#include "net/third_party/quiche/src/spdy/core/spdy_alt_svc_wire_format.h" +#include "net/third_party/quiche/src/spdy/core/spdy_header_block.h" +#include "net/third_party/quiche/src/spdy/core/spdy_headers_handler_interface.h" +#include "net/third_party/quiche/src/spdy/core/spdy_protocol.h" +#include "net/third_party/quiche/src/spdy/platform/api/spdy_bug_tracker.h" +#include "net/third_party/quiche/src/spdy/platform/api/spdy_endianness_util.h" +#include "net/third_party/quiche/src/spdy/platform/api/spdy_estimate_memory_usage.h" +#include "net/third_party/quiche/src/spdy/platform/api/spdy_flags.h" +#include "net/third_party/quiche/src/spdy/platform/api/spdy_ptr_util.h" +#include "net/third_party/quiche/src/spdy/platform/api/spdy_string_utils.h" + +using ::spdy::ExtensionVisitorInterface; +using ::spdy::HpackDecoderAdapter; +using ::spdy::HpackHeaderTable; +using ::spdy::ParseErrorCode; +using ::spdy::ParseFrameType; +using ::spdy::SpdyAltSvcWireFormat; +using ::spdy::SpdyErrorCode; +using ::spdy::SpdyEstimateMemoryUsage; +using ::spdy::SpdyFramerDebugVisitorInterface; +using ::spdy::SpdyFramerVisitorInterface; +using ::spdy::SpdyFrameType; +using ::spdy::SpdyHeadersHandlerInterface; +using ::spdy::SpdyKnownSettingsId; +using ::spdy::SpdyMakeUnique; +using ::spdy::SpdySettingsId; + +namespace http2 { +namespace { + +const bool kHasPriorityFields = true; +const bool kNotHasPriorityFields = false; + +bool IsPaddable(Http2FrameType type) { + return type == Http2FrameType::DATA || type == Http2FrameType::HEADERS || + type == Http2FrameType::PUSH_PROMISE; +} + +SpdyFrameType ToSpdyFrameType(Http2FrameType type) { + return ParseFrameType(static_cast<uint8_t>(type)); +} + +uint64_t ToSpdyPingId(const Http2PingFields& ping) { + uint64_t v; + std::memcpy(&v, ping.opaque_bytes, Http2PingFields::EncodedSize()); + return spdy::SpdyNetToHost64(v); +} + +// Overwrites the fields of the header with invalid values, for the purpose +// of identifying reading of unset fields. Only takes effect for debug builds. +// In Address Sanatizer builds, it also marks the fields as un-readable. +void CorruptFrameHeader(Http2FrameHeader* header) { +#ifndef NDEBUG + // Beyond a valid payload length, which is 2^24 - 1. + header->payload_length = 0x1010dead; + // An unsupported frame type. + header->type = Http2FrameType(0x80); + DCHECK(!IsSupportedHttp2FrameType(header->type)); + // Frame flag bits that aren't used by any supported frame type. + header->flags = Http2FrameFlag(0xd2); + // A stream id with the reserved high-bit (R in the RFC) set. + // 2129510127 when the high-bit is cleared. + header->stream_id = 0xfeedbeef; +#endif +} + +} // namespace + +const char* Http2DecoderAdapter::StateToString(int state) { + switch (state) { + case SPDY_ERROR: + return "ERROR"; + case SPDY_FRAME_COMPLETE: + return "FRAME_COMPLETE"; + case SPDY_READY_FOR_FRAME: + return "READY_FOR_FRAME"; + case SPDY_READING_COMMON_HEADER: + return "READING_COMMON_HEADER"; + case SPDY_CONTROL_FRAME_PAYLOAD: + return "CONTROL_FRAME_PAYLOAD"; + case SPDY_READ_DATA_FRAME_PADDING_LENGTH: + return "SPDY_READ_DATA_FRAME_PADDING_LENGTH"; + case SPDY_CONSUME_PADDING: + return "SPDY_CONSUME_PADDING"; + case SPDY_IGNORE_REMAINING_PAYLOAD: + return "IGNORE_REMAINING_PAYLOAD"; + case SPDY_FORWARD_STREAM_FRAME: + return "FORWARD_STREAM_FRAME"; + case SPDY_CONTROL_FRAME_BEFORE_HEADER_BLOCK: + return "SPDY_CONTROL_FRAME_BEFORE_HEADER_BLOCK"; + case SPDY_CONTROL_FRAME_HEADER_BLOCK: + return "SPDY_CONTROL_FRAME_HEADER_BLOCK"; + case SPDY_GOAWAY_FRAME_PAYLOAD: + return "SPDY_GOAWAY_FRAME_PAYLOAD"; + case SPDY_SETTINGS_FRAME_HEADER: + return "SPDY_SETTINGS_FRAME_HEADER"; + case SPDY_SETTINGS_FRAME_PAYLOAD: + return "SPDY_SETTINGS_FRAME_PAYLOAD"; + case SPDY_ALTSVC_FRAME_PAYLOAD: + return "SPDY_ALTSVC_FRAME_PAYLOAD"; + } + return "UNKNOWN_STATE"; +} + +const char* Http2DecoderAdapter::SpdyFramerErrorToString( + SpdyFramerError spdy_framer_error) { + switch (spdy_framer_error) { + case SPDY_NO_ERROR: + return "NO_ERROR"; + case SPDY_INVALID_STREAM_ID: + return "INVALID_STREAM_ID"; + case SPDY_INVALID_CONTROL_FRAME: + return "INVALID_CONTROL_FRAME"; + case SPDY_CONTROL_PAYLOAD_TOO_LARGE: + return "CONTROL_PAYLOAD_TOO_LARGE"; + case SPDY_ZLIB_INIT_FAILURE: + return "ZLIB_INIT_FAILURE"; + case SPDY_UNSUPPORTED_VERSION: + return "UNSUPPORTED_VERSION"; + case SPDY_DECOMPRESS_FAILURE: + return "DECOMPRESS_FAILURE"; + case SPDY_COMPRESS_FAILURE: + return "COMPRESS_FAILURE"; + case SPDY_GOAWAY_FRAME_CORRUPT: + return "GOAWAY_FRAME_CORRUPT"; + case SPDY_RST_STREAM_FRAME_CORRUPT: + return "RST_STREAM_FRAME_CORRUPT"; + case SPDY_INVALID_PADDING: + return "INVALID_PADDING"; + case SPDY_INVALID_DATA_FRAME_FLAGS: + return "INVALID_DATA_FRAME_FLAGS"; + case SPDY_INVALID_CONTROL_FRAME_FLAGS: + return "INVALID_CONTROL_FRAME_FLAGS"; + case SPDY_UNEXPECTED_FRAME: + return "UNEXPECTED_FRAME"; + case SPDY_INTERNAL_FRAMER_ERROR: + return "INTERNAL_FRAMER_ERROR"; + case SPDY_INVALID_CONTROL_FRAME_SIZE: + return "INVALID_CONTROL_FRAME_SIZE"; + case SPDY_OVERSIZED_PAYLOAD: + return "OVERSIZED_PAYLOAD"; + case LAST_ERROR: + return "UNKNOWN_ERROR"; + } + return "UNKNOWN_ERROR"; +} + +Http2DecoderAdapter::Http2DecoderAdapter() { + DVLOG(1) << "Http2DecoderAdapter ctor"; + ResetInternal(); +} + +Http2DecoderAdapter::~Http2DecoderAdapter() = default; + +void Http2DecoderAdapter::set_visitor(SpdyFramerVisitorInterface* visitor) { + visitor_ = visitor; +} + +void Http2DecoderAdapter::set_debug_visitor( + SpdyFramerDebugVisitorInterface* debug_visitor) { + debug_visitor_ = debug_visitor; +} + +void Http2DecoderAdapter::set_process_single_input_frame(bool v) { + process_single_input_frame_ = v; +} + +void Http2DecoderAdapter::set_extension_visitor( + ExtensionVisitorInterface* visitor) { + extension_ = visitor; +} + +// Passes the call on to the HPACK decoder. +void Http2DecoderAdapter::SetDecoderHeaderTableDebugVisitor( + std::unique_ptr<HpackHeaderTable::DebugVisitorInterface> visitor) { + GetHpackDecoder()->SetHeaderTableDebugVisitor(std::move(visitor)); +} + +size_t Http2DecoderAdapter::ProcessInput(const char* data, size_t len) { + size_t limit = recv_frame_size_limit_; + frame_decoder_->set_maximum_payload_size(limit); + + size_t total_processed = 0; + while (len > 0 && spdy_state_ != SPDY_ERROR) { + // Process one at a time so that we update the adapter's internal + // state appropriately. + const size_t processed = ProcessInputFrame(data, len); + + // We had some data, and weren't in an error state, so should have + // processed/consumed at least one byte of it, even if we then ended up + // in an error state. + DCHECK(processed > 0) << "processed=" << processed + << " spdy_state_=" << spdy_state_ + << " spdy_framer_error_=" << spdy_framer_error_; + + data += processed; + len -= processed; + total_processed += processed; + if (process_single_input_frame() || processed == 0) { + break; + } + } + return total_processed; +} + +void Http2DecoderAdapter::Reset() { + ResetInternal(); +} + +Http2DecoderAdapter::SpdyState Http2DecoderAdapter::state() const { + return spdy_state_; +} + +Http2DecoderAdapter::SpdyFramerError Http2DecoderAdapter::spdy_framer_error() + const { + return spdy_framer_error_; +} + +bool Http2DecoderAdapter::probable_http_response() const { + return latched_probable_http_response_; +} + +size_t Http2DecoderAdapter::EstimateMemoryUsage() const { + // Skip |frame_decoder_|, |frame_header_| and |hpack_first_frame_header_| as + // they don't allocate. + return SpdyEstimateMemoryUsage(alt_svc_origin_) + + SpdyEstimateMemoryUsage(alt_svc_value_); +} + +// =========================================================================== +// Implementations of the methods declared by Http2FrameDecoderListener. + +// Called once the common frame header has been decoded for any frame. +// This function is largely based on Http2DecoderAdapter::ValidateFrameHeader +// and some parts of Http2DecoderAdapter::ProcessCommonHeader. +bool Http2DecoderAdapter::OnFrameHeader(const Http2FrameHeader& header) { + DVLOG(1) << "OnFrameHeader: " << header; + decoded_frame_header_ = true; + if (!latched_probable_http_response_) { + latched_probable_http_response_ = header.IsProbableHttpResponse(); + } + const uint8_t raw_frame_type = static_cast<uint8_t>(header.type); + visitor()->OnCommonHeader(header.stream_id, header.payload_length, + raw_frame_type, header.flags); + if (has_expected_frame_type_ && header.type != expected_frame_type_) { + // Report an unexpected frame error and close the connection if we + // expect a known frame type (probably CONTINUATION) and receive an + // unknown frame. + VLOG(1) << "The framer was expecting to receive a " << expected_frame_type_ + << " frame, but instead received an unknown frame of type " + << header.type; + SetSpdyErrorAndNotify(SpdyFramerError::SPDY_UNEXPECTED_FRAME); + return false; + } + if (!IsSupportedHttp2FrameType(header.type)) { + if (extension_ != nullptr) { + // Unknown frames will be passed to the registered extension. + return true; + } + // In HTTP2 we ignore unknown frame types for extensibility, as long as + // the rest of the control frame header is valid. + // We rely on the visitor to check validity of stream_id. + bool valid_stream = + visitor()->OnUnknownFrame(header.stream_id, raw_frame_type); + if (!valid_stream) { + // Report an invalid frame error if the stream_id is not valid. + VLOG(1) << "Unknown control frame type " << header.type + << " received on invalid stream " << header.stream_id; + SetSpdyErrorAndNotify(SpdyFramerError::SPDY_INVALID_CONTROL_FRAME); + return false; + } else { + DVLOG(1) << "Ignoring unknown frame type " << header.type; + return true; + } + } + + SpdyFrameType frame_type = ToSpdyFrameType(header.type); + if (!IsValidHTTP2FrameStreamId(header.stream_id, frame_type)) { + VLOG(1) << "The framer received an invalid streamID of " << header.stream_id + << " for a frame of type " << header.type; + SetSpdyErrorAndNotify(SpdyFramerError::SPDY_INVALID_STREAM_ID); + return false; + } + + if (has_expected_frame_type_ && header.type != expected_frame_type_) { + VLOG(1) << "Expected frame type " << expected_frame_type_ << ", not " + << header.type; + SetSpdyErrorAndNotify(SpdyFramerError::SPDY_UNEXPECTED_FRAME); + return false; + } + + if (!has_expected_frame_type_ && + header.type == Http2FrameType::CONTINUATION) { + VLOG(1) << "Got CONTINUATION frame when not expected."; + SetSpdyErrorAndNotify(SpdyFramerError::SPDY_UNEXPECTED_FRAME); + return false; + } + + if (header.type == Http2FrameType::DATA) { + // For some reason SpdyFramer still rejects invalid DATA frame flags. + uint8_t valid_flags = Http2FrameFlag::PADDED | Http2FrameFlag::END_STREAM; + if (header.HasAnyFlags(~valid_flags)) { + SetSpdyErrorAndNotify(SpdyFramerError::SPDY_INVALID_DATA_FRAME_FLAGS); + return false; + } + } + + return true; +} + +void Http2DecoderAdapter::OnDataStart(const Http2FrameHeader& header) { + DVLOG(1) << "OnDataStart: " << header; + + if (IsOkToStartFrame(header) && HasRequiredStreamId(header)) { + frame_header_ = header; + has_frame_header_ = true; + visitor()->OnDataFrameHeader(header.stream_id, header.payload_length, + header.IsEndStream()); + } +} + +void Http2DecoderAdapter::OnDataPayload(const char* data, size_t len) { + DVLOG(1) << "OnDataPayload: len=" << len; + DCHECK(has_frame_header_); + DCHECK_EQ(frame_header_.type, Http2FrameType::DATA); + visitor()->OnStreamFrameData(frame_header().stream_id, data, len); +} + +void Http2DecoderAdapter::OnDataEnd() { + DVLOG(1) << "OnDataEnd"; + DCHECK(has_frame_header_); + DCHECK_EQ(frame_header_.type, Http2FrameType::DATA); + if (frame_header().IsEndStream()) { + visitor()->OnStreamEnd(frame_header().stream_id); + } + opt_pad_length_.reset(); +} + +void Http2DecoderAdapter::OnHeadersStart(const Http2FrameHeader& header) { + DVLOG(1) << "OnHeadersStart: " << header; + if (IsOkToStartFrame(header) && HasRequiredStreamId(header)) { + frame_header_ = header; + has_frame_header_ = true; + if (header.HasPriority()) { + // Once we've got the priority fields, then we can report the arrival + // of this HEADERS frame. + on_headers_called_ = false; + return; + } + on_headers_called_ = true; + ReportReceiveCompressedFrame(header); + visitor()->OnHeaders(header.stream_id, kNotHasPriorityFields, + 0, // priority + 0, // parent_stream_id + false, // exclusive + header.IsEndStream(), header.IsEndHeaders()); + CommonStartHpackBlock(); + } +} + +void Http2DecoderAdapter::OnHeadersPriority( + const Http2PriorityFields& priority) { + DVLOG(1) << "OnHeadersPriority: " << priority; + DCHECK(has_frame_header_); + DCHECK_EQ(frame_type(), Http2FrameType::HEADERS) << frame_header_; + DCHECK(frame_header_.HasPriority()); + DCHECK(!on_headers_called_); + on_headers_called_ = true; + ReportReceiveCompressedFrame(frame_header_); + visitor()->OnHeaders(frame_header_.stream_id, kHasPriorityFields, + priority.weight, priority.stream_dependency, + priority.is_exclusive, frame_header_.IsEndStream(), + frame_header_.IsEndHeaders()); + CommonStartHpackBlock(); +} + +void Http2DecoderAdapter::OnHpackFragment(const char* data, size_t len) { + DVLOG(1) << "OnHpackFragment: len=" << len; + on_hpack_fragment_called_ = true; + if (!GetHpackDecoder()->HandleControlFrameHeadersData(data, len)) { + SetSpdyErrorAndNotify(SpdyFramerError::SPDY_DECOMPRESS_FAILURE); + return; + } +} + +void Http2DecoderAdapter::OnHeadersEnd() { + DVLOG(1) << "OnHeadersEnd"; + CommonHpackFragmentEnd(); + opt_pad_length_.reset(); +} + +void Http2DecoderAdapter::OnPriorityFrame(const Http2FrameHeader& header, + const Http2PriorityFields& priority) { + DVLOG(1) << "OnPriorityFrame: " << header << "; priority: " << priority; + if (IsOkToStartFrame(header) && HasRequiredStreamId(header)) { + visitor()->OnPriority(header.stream_id, priority.stream_dependency, + priority.weight, priority.is_exclusive); + } +} + +void Http2DecoderAdapter::OnContinuationStart(const Http2FrameHeader& header) { + DVLOG(1) << "OnContinuationStart: " << header; + if (IsOkToStartFrame(header) && HasRequiredStreamId(header)) { + DCHECK(has_hpack_first_frame_header_); + if (header.stream_id != hpack_first_frame_header_.stream_id) { + SetSpdyErrorAndNotify(SpdyFramerError::SPDY_UNEXPECTED_FRAME); + return; + } + frame_header_ = header; + has_frame_header_ = true; + ReportReceiveCompressedFrame(header); + visitor()->OnContinuation(header.stream_id, header.IsEndHeaders()); + } +} + +void Http2DecoderAdapter::OnContinuationEnd() { + DVLOG(1) << "OnContinuationEnd"; + CommonHpackFragmentEnd(); +} + +void Http2DecoderAdapter::OnPadLength(size_t trailing_length) { + DVLOG(1) << "OnPadLength: " << trailing_length; + opt_pad_length_ = trailing_length; + DCHECK_LT(trailing_length, 256u); + if (frame_header_.type == Http2FrameType::DATA) { + visitor()->OnStreamPadLength(stream_id(), trailing_length); + } +} + +void Http2DecoderAdapter::OnPadding(const char* padding, + size_t skipped_length) { + DVLOG(1) << "OnPadding: " << skipped_length; + if (frame_header_.type == Http2FrameType::DATA) { + visitor()->OnStreamPadding(stream_id(), skipped_length); + } else { + MaybeAnnounceEmptyFirstHpackFragment(); + } +} + +void Http2DecoderAdapter::OnRstStream(const Http2FrameHeader& header, + Http2ErrorCode http2_error_code) { + DVLOG(1) << "OnRstStream: " << header << "; code=" << http2_error_code; + if (IsOkToStartFrame(header) && HasRequiredStreamId(header)) { + SpdyErrorCode error_code = + ParseErrorCode(static_cast<uint32_t>(http2_error_code)); + visitor()->OnRstStream(header.stream_id, error_code); + } +} + +void Http2DecoderAdapter::OnSettingsStart(const Http2FrameHeader& header) { + DVLOG(1) << "OnSettingsStart: " << header; + if (IsOkToStartFrame(header) && HasRequiredStreamIdZero(header)) { + frame_header_ = header; + has_frame_header_ = true; + visitor()->OnSettings(); + } +} + +void Http2DecoderAdapter::OnSetting(const Http2SettingFields& setting_fields) { + DVLOG(1) << "OnSetting: " << setting_fields; + const auto parameter = static_cast<SpdySettingsId>(setting_fields.parameter); + visitor()->OnSetting(parameter, setting_fields.value); + if (extension_ != nullptr) { + extension_->OnSetting(parameter, setting_fields.value); + } +} + +void Http2DecoderAdapter::OnSettingsEnd() { + DVLOG(1) << "OnSettingsEnd"; + visitor()->OnSettingsEnd(); +} + +void Http2DecoderAdapter::OnSettingsAck(const Http2FrameHeader& header) { + DVLOG(1) << "OnSettingsAck: " << header; + if (IsOkToStartFrame(header) && HasRequiredStreamIdZero(header)) { + visitor()->OnSettingsAck(); + } +} + +void Http2DecoderAdapter::OnPushPromiseStart( + const Http2FrameHeader& header, + const Http2PushPromiseFields& promise, + size_t total_padding_length) { + DVLOG(1) << "OnPushPromiseStart: " << header << "; promise: " << promise + << "; total_padding_length: " << total_padding_length; + if (IsOkToStartFrame(header) && HasRequiredStreamId(header)) { + if (promise.promised_stream_id == 0) { + SetSpdyErrorAndNotify(SpdyFramerError::SPDY_INVALID_CONTROL_FRAME); + return; + } + frame_header_ = header; + has_frame_header_ = true; + ReportReceiveCompressedFrame(header); + visitor()->OnPushPromise(header.stream_id, promise.promised_stream_id, + header.IsEndHeaders()); + CommonStartHpackBlock(); + } +} + +void Http2DecoderAdapter::OnPushPromiseEnd() { + DVLOG(1) << "OnPushPromiseEnd"; + CommonHpackFragmentEnd(); + opt_pad_length_.reset(); +} + +void Http2DecoderAdapter::OnPing(const Http2FrameHeader& header, + const Http2PingFields& ping) { + DVLOG(1) << "OnPing: " << header << "; ping: " << ping; + if (IsOkToStartFrame(header) && HasRequiredStreamIdZero(header)) { + visitor()->OnPing(ToSpdyPingId(ping), false); + } +} + +void Http2DecoderAdapter::OnPingAck(const Http2FrameHeader& header, + const Http2PingFields& ping) { + DVLOG(1) << "OnPingAck: " << header << "; ping: " << ping; + if (IsOkToStartFrame(header) && HasRequiredStreamIdZero(header)) { + visitor()->OnPing(ToSpdyPingId(ping), true); + } +} + +void Http2DecoderAdapter::OnGoAwayStart(const Http2FrameHeader& header, + const Http2GoAwayFields& goaway) { + DVLOG(1) << "OnGoAwayStart: " << header << "; goaway: " << goaway; + if (IsOkToStartFrame(header) && HasRequiredStreamIdZero(header)) { + frame_header_ = header; + has_frame_header_ = true; + SpdyErrorCode error_code = + ParseErrorCode(static_cast<uint32_t>(goaway.error_code)); + visitor()->OnGoAway(goaway.last_stream_id, error_code); + } +} + +void Http2DecoderAdapter::OnGoAwayOpaqueData(const char* data, size_t len) { + DVLOG(1) << "OnGoAwayOpaqueData: len=" << len; + visitor()->OnGoAwayFrameData(data, len); +} + +void Http2DecoderAdapter::OnGoAwayEnd() { + DVLOG(1) << "OnGoAwayEnd"; + visitor()->OnGoAwayFrameData(nullptr, 0); +} + +void Http2DecoderAdapter::OnWindowUpdate(const Http2FrameHeader& header, + uint32_t increment) { + DVLOG(1) << "OnWindowUpdate: " << header << "; increment=" << increment; + if (IsOkToStartFrame(header)) { + visitor()->OnWindowUpdate(header.stream_id, increment); + } +} + +// Per RFC7838, an ALTSVC frame on stream 0 with origin_length == 0, or one on +// a stream other than stream 0 with origin_length != 0 MUST be ignored. All +// frames are decoded by Http2DecoderAdapter, and it is left to the consumer +// (listener) to implement this behavior. +void Http2DecoderAdapter::OnAltSvcStart(const Http2FrameHeader& header, + size_t origin_length, + size_t value_length) { + DVLOG(1) << "OnAltSvcStart: " << header + << "; origin_length: " << origin_length + << "; value_length: " << value_length; + if (!IsOkToStartFrame(header)) { + return; + } + frame_header_ = header; + has_frame_header_ = true; + alt_svc_origin_.clear(); + alt_svc_value_.clear(); +} + +void Http2DecoderAdapter::OnAltSvcOriginData(const char* data, size_t len) { + DVLOG(1) << "OnAltSvcOriginData: len=" << len; + alt_svc_origin_.append(data, len); +} + +// Called when decoding the Alt-Svc-Field-Value of an ALTSVC; +// the field is uninterpreted. +void Http2DecoderAdapter::OnAltSvcValueData(const char* data, size_t len) { + DVLOG(1) << "OnAltSvcValueData: len=" << len; + alt_svc_value_.append(data, len); +} + +void Http2DecoderAdapter::OnAltSvcEnd() { + DVLOG(1) << "OnAltSvcEnd: origin.size(): " << alt_svc_origin_.size() + << "; value.size(): " << alt_svc_value_.size(); + SpdyAltSvcWireFormat::AlternativeServiceVector altsvc_vector; + if (!SpdyAltSvcWireFormat::ParseHeaderFieldValue(alt_svc_value_, + &altsvc_vector)) { + DLOG(ERROR) << "SpdyAltSvcWireFormat::ParseHeaderFieldValue failed."; + SetSpdyErrorAndNotify(SpdyFramerError::SPDY_INVALID_CONTROL_FRAME); + return; + } + visitor()->OnAltSvc(frame_header_.stream_id, alt_svc_origin_, altsvc_vector); + // We assume that ALTSVC frames are rare, so get rid of the storage. + alt_svc_origin_.clear(); + alt_svc_origin_.shrink_to_fit(); + alt_svc_value_.clear(); + alt_svc_value_.shrink_to_fit(); +} + +// Except for BLOCKED frames, all other unknown frames are either dropped or +// passed to a registered extension. +void Http2DecoderAdapter::OnUnknownStart(const Http2FrameHeader& header) { + DVLOG(1) << "OnUnknownStart: " << header; + if (IsOkToStartFrame(header)) { + if (extension_ != nullptr) { + const uint8_t type = static_cast<uint8_t>(header.type); + const uint8_t flags = static_cast<uint8_t>(header.flags); + handling_extension_payload_ = extension_->OnFrameHeader( + header.stream_id, header.payload_length, type, flags); + } + } +} + +void Http2DecoderAdapter::OnUnknownPayload(const char* data, size_t len) { + if (handling_extension_payload_) { + extension_->OnFramePayload(data, len); + } else { + DVLOG(1) << "OnUnknownPayload: len=" << len; + } +} + +void Http2DecoderAdapter::OnUnknownEnd() { + DVLOG(1) << "OnUnknownEnd"; + handling_extension_payload_ = false; +} + +void Http2DecoderAdapter::OnPaddingTooLong(const Http2FrameHeader& header, + size_t missing_length) { + DVLOG(1) << "OnPaddingTooLong: " << header + << "; missing_length: " << missing_length; + if (header.type == Http2FrameType::DATA) { + if (header.payload_length == 0) { + DCHECK_EQ(1u, missing_length); + SetSpdyErrorAndNotify(SpdyFramerError::SPDY_INVALID_DATA_FRAME_FLAGS); + return; + } + visitor()->OnStreamPadding(header.stream_id, 1); + } + SetSpdyErrorAndNotify(SpdyFramerError::SPDY_INVALID_PADDING); +} + +void Http2DecoderAdapter::OnFrameSizeError(const Http2FrameHeader& header) { + DVLOG(1) << "OnFrameSizeError: " << header; + size_t recv_limit = recv_frame_size_limit_; + if (header.payload_length > recv_limit) { + SetSpdyErrorAndNotify(SpdyFramerError::SPDY_OVERSIZED_PAYLOAD); + return; + } + if (header.type != Http2FrameType::DATA && + header.payload_length > recv_limit) { + SetSpdyErrorAndNotify(SpdyFramerError::SPDY_CONTROL_PAYLOAD_TOO_LARGE); + return; + } + switch (header.type) { + case Http2FrameType::GOAWAY: + case Http2FrameType::ALTSVC: + SetSpdyErrorAndNotify(SpdyFramerError::SPDY_INVALID_CONTROL_FRAME); + break; + default: + SetSpdyErrorAndNotify(SpdyFramerError::SPDY_INVALID_CONTROL_FRAME_SIZE); + } +} + +// Decodes the input up to the next frame boundary (i.e. at most one frame), +// stopping early if an error is detected. +size_t Http2DecoderAdapter::ProcessInputFrame(const char* data, size_t len) { + DCHECK_NE(spdy_state_, SpdyState::SPDY_ERROR); + DecodeBuffer db(data, len); + DecodeStatus status = frame_decoder_->DecodeFrame(&db); + if (spdy_state_ != SpdyState::SPDY_ERROR) { + DetermineSpdyState(status); + } else { + VLOG(1) << "ProcessInputFrame spdy_framer_error_=" + << SpdyFramerErrorToString(spdy_framer_error_); + if (spdy_framer_error_ == SpdyFramerError::SPDY_INVALID_PADDING && + has_frame_header_ && frame_type() != Http2FrameType::DATA) { + // spdy_framer_test checks that all of the available frame payload + // has been consumed, so do that. + size_t total = remaining_total_payload(); + if (total <= frame_header().payload_length) { + size_t avail = db.MinLengthRemaining(total); + VLOG(1) << "Skipping past " << avail << " bytes, of " << total + << " total remaining in the frame's payload."; + db.AdvanceCursor(avail); + } else { + SPDY_BUG << "Total remaining (" << total + << ") should not be greater than the payload length; " + << frame_header(); + } + } + } + return db.Offset(); +} + +// After decoding, determine the next SpdyState. Only called if the current +// state is NOT SpdyState::SPDY_ERROR (i.e. if none of the callback methods +// detected an error condition), because otherwise we assume that the callback +// method has set spdy_framer_error_ appropriately. +void Http2DecoderAdapter::DetermineSpdyState(DecodeStatus status) { + DCHECK_EQ(spdy_framer_error_, SPDY_NO_ERROR); + DCHECK(!HasError()) << spdy_framer_error_; + switch (status) { + case DecodeStatus::kDecodeDone: + DVLOG(1) << "ProcessInputFrame -> DecodeStatus::kDecodeDone"; + ResetBetweenFrames(); + break; + case DecodeStatus::kDecodeInProgress: + DVLOG(1) << "ProcessInputFrame -> DecodeStatus::kDecodeInProgress"; + if (decoded_frame_header_) { + if (IsDiscardingPayload()) { + set_spdy_state(SpdyState::SPDY_IGNORE_REMAINING_PAYLOAD); + } else if (has_frame_header_ && frame_type() == Http2FrameType::DATA) { + if (IsReadingPaddingLength()) { + set_spdy_state(SpdyState::SPDY_READ_DATA_FRAME_PADDING_LENGTH); + } else if (IsSkippingPadding()) { + set_spdy_state(SpdyState::SPDY_CONSUME_PADDING); + } else { + set_spdy_state(SpdyState::SPDY_FORWARD_STREAM_FRAME); + } + } else { + set_spdy_state(SpdyState::SPDY_CONTROL_FRAME_PAYLOAD); + } + } else { + set_spdy_state(SpdyState::SPDY_READING_COMMON_HEADER); + } + break; + case DecodeStatus::kDecodeError: + VLOG(1) << "ProcessInputFrame -> DecodeStatus::kDecodeError"; + if (IsDiscardingPayload()) { + if (remaining_total_payload() == 0) { + // Push the Http2FrameDecoder out of state kDiscardPayload now + // since doing so requires no input. + DecodeBuffer tmp("", 0); + DecodeStatus status = frame_decoder_->DecodeFrame(&tmp); + if (status != DecodeStatus::kDecodeDone) { + SPDY_BUG << "Expected to be done decoding the frame, not " + << status; + SetSpdyErrorAndNotify(SPDY_INTERNAL_FRAMER_ERROR); + } else if (spdy_framer_error_ != SPDY_NO_ERROR) { + SPDY_BUG << "Expected to have no error, not " + << SpdyFramerErrorToString(spdy_framer_error_); + } else { + ResetBetweenFrames(); + } + } else { + set_spdy_state(SpdyState::SPDY_IGNORE_REMAINING_PAYLOAD); + } + } else { + SetSpdyErrorAndNotify(SPDY_INVALID_CONTROL_FRAME); + } + break; + } +} + +void Http2DecoderAdapter::ResetBetweenFrames() { + CorruptFrameHeader(&frame_header_); + decoded_frame_header_ = false; + has_frame_header_ = false; + set_spdy_state(SpdyState::SPDY_READY_FOR_FRAME); +} + +// ResetInternal is called from the constructor, and during tests, but not +// otherwise (i.e. not between every frame). +void Http2DecoderAdapter::ResetInternal() { + set_spdy_state(SpdyState::SPDY_READY_FOR_FRAME); + spdy_framer_error_ = SpdyFramerError::SPDY_NO_ERROR; + + decoded_frame_header_ = false; + has_frame_header_ = false; + on_headers_called_ = false; + on_hpack_fragment_called_ = false; + latched_probable_http_response_ = false; + has_expected_frame_type_ = false; + + CorruptFrameHeader(&frame_header_); + CorruptFrameHeader(&hpack_first_frame_header_); + + frame_decoder_ = SpdyMakeUnique<Http2FrameDecoder>(this); + hpack_decoder_ = nullptr; +} + +void Http2DecoderAdapter::set_spdy_state(SpdyState v) { + DVLOG(2) << "set_spdy_state(" << StateToString(v) << ")"; + spdy_state_ = v; +} + +void Http2DecoderAdapter::SetSpdyErrorAndNotify(SpdyFramerError error) { + if (HasError()) { + DCHECK_EQ(spdy_state_, SpdyState::SPDY_ERROR); + } else { + VLOG(2) << "SetSpdyErrorAndNotify(" << SpdyFramerErrorToString(error) + << ")"; + DCHECK_NE(error, SpdyFramerError::SPDY_NO_ERROR); + spdy_framer_error_ = error; + set_spdy_state(SpdyState::SPDY_ERROR); + frame_decoder_->set_listener(&no_op_listener_); + visitor()->OnError(error); + } +} + +bool Http2DecoderAdapter::HasError() const { + if (spdy_state_ == SpdyState::SPDY_ERROR) { + DCHECK_NE(spdy_framer_error(), SpdyFramerError::SPDY_NO_ERROR); + return true; + } else { + DCHECK_EQ(spdy_framer_error(), SpdyFramerError::SPDY_NO_ERROR); + return false; + } +} + +const Http2FrameHeader& Http2DecoderAdapter::frame_header() const { + DCHECK(has_frame_header_); + return frame_header_; +} + +uint32_t Http2DecoderAdapter::stream_id() const { + return frame_header().stream_id; +} + +Http2FrameType Http2DecoderAdapter::frame_type() const { + return frame_header().type; +} + +size_t Http2DecoderAdapter::remaining_total_payload() const { + DCHECK(has_frame_header_); + size_t remaining = frame_decoder_->remaining_payload(); + if (IsPaddable(frame_type()) && frame_header_.IsPadded()) { + remaining += frame_decoder_->remaining_padding(); + } + return remaining; +} + +bool Http2DecoderAdapter::IsReadingPaddingLength() { + bool result = frame_header_.IsPadded() && !opt_pad_length_; + DVLOG(2) << "Http2DecoderAdapter::IsReadingPaddingLength: " << result; + return result; +} +bool Http2DecoderAdapter::IsSkippingPadding() { + bool result = frame_header_.IsPadded() && opt_pad_length_ && + frame_decoder_->remaining_payload() == 0 && + frame_decoder_->remaining_padding() > 0; + DVLOG(2) << "Http2DecoderAdapter::IsSkippingPadding: " << result; + return result; +} +bool Http2DecoderAdapter::IsDiscardingPayload() { + bool result = decoded_frame_header_ && frame_decoder_->IsDiscardingPayload(); + DVLOG(2) << "Http2DecoderAdapter::IsDiscardingPayload: " << result; + return result; +} +// Called from OnXyz or OnXyzStart methods to decide whether it is OK to +// handle the callback. +bool Http2DecoderAdapter::IsOkToStartFrame(const Http2FrameHeader& header) { + DVLOG(3) << "IsOkToStartFrame"; + if (HasError()) { + VLOG(2) << "HasError()"; + return false; + } + DCHECK(!has_frame_header_); + if (has_expected_frame_type_ && header.type != expected_frame_type_) { + VLOG(1) << "Expected frame type " << expected_frame_type_ << ", not " + << header.type; + SetSpdyErrorAndNotify(SpdyFramerError::SPDY_UNEXPECTED_FRAME); + return false; + } + + return true; +} + +bool Http2DecoderAdapter::HasRequiredStreamId(uint32_t stream_id) { + DVLOG(3) << "HasRequiredStreamId: " << stream_id; + if (HasError()) { + VLOG(2) << "HasError()"; + return false; + } + if (stream_id != 0) { + return true; + } + VLOG(1) << "Stream Id is required, but zero provided"; + SetSpdyErrorAndNotify(SpdyFramerError::SPDY_INVALID_STREAM_ID); + return false; +} + +bool Http2DecoderAdapter::HasRequiredStreamId(const Http2FrameHeader& header) { + return HasRequiredStreamId(header.stream_id); +} + +bool Http2DecoderAdapter::HasRequiredStreamIdZero(uint32_t stream_id) { + DVLOG(3) << "HasRequiredStreamIdZero: " << stream_id; + if (HasError()) { + VLOG(2) << "HasError()"; + return false; + } + if (stream_id == 0) { + return true; + } + VLOG(1) << "Stream Id was not zero, as required: " << stream_id; + SetSpdyErrorAndNotify(SpdyFramerError::SPDY_INVALID_STREAM_ID); + return false; +} + +bool Http2DecoderAdapter::HasRequiredStreamIdZero( + const Http2FrameHeader& header) { + return HasRequiredStreamIdZero(header.stream_id); +} + +void Http2DecoderAdapter::ReportReceiveCompressedFrame( + const Http2FrameHeader& header) { + if (debug_visitor() != nullptr) { + size_t total = header.payload_length + Http2FrameHeader::EncodedSize(); + debug_visitor()->OnReceiveCompressedFrame( + header.stream_id, ToSpdyFrameType(header.type), total); + } +} + +HpackDecoderAdapter* Http2DecoderAdapter::GetHpackDecoder() { + if (hpack_decoder_ == nullptr) { + hpack_decoder_ = SpdyMakeUnique<HpackDecoderAdapter>(); + } + return hpack_decoder_.get(); +} + +void Http2DecoderAdapter::CommonStartHpackBlock() { + DVLOG(1) << "CommonStartHpackBlock"; + DCHECK(!has_hpack_first_frame_header_); + if (!frame_header_.IsEndHeaders()) { + hpack_first_frame_header_ = frame_header_; + has_hpack_first_frame_header_ = true; + } else { + CorruptFrameHeader(&hpack_first_frame_header_); + } + on_hpack_fragment_called_ = false; + SpdyHeadersHandlerInterface* handler = + visitor()->OnHeaderFrameStart(stream_id()); + if (handler == nullptr) { + SPDY_BUG << "visitor_->OnHeaderFrameStart returned nullptr"; + SetSpdyErrorAndNotify(SpdyFramerError::SPDY_INTERNAL_FRAMER_ERROR); + return; + } + GetHpackDecoder()->HandleControlFrameHeadersStart(handler); +} + +// SpdyFramer calls HandleControlFrameHeadersData even if there are zero +// fragment bytes in the first frame, so do the same. +void Http2DecoderAdapter::MaybeAnnounceEmptyFirstHpackFragment() { + if (!on_hpack_fragment_called_) { + OnHpackFragment(nullptr, 0); + DCHECK(on_hpack_fragment_called_); + } +} + +void Http2DecoderAdapter::CommonHpackFragmentEnd() { + DVLOG(1) << "CommonHpackFragmentEnd: stream_id=" << stream_id(); + if (HasError()) { + VLOG(1) << "HasError(), returning"; + return; + } + DCHECK(has_frame_header_); + MaybeAnnounceEmptyFirstHpackFragment(); + if (frame_header_.IsEndHeaders()) { + DCHECK_EQ(has_hpack_first_frame_header_, + frame_type() == Http2FrameType::CONTINUATION) + << frame_header(); + has_expected_frame_type_ = false; + if (GetHpackDecoder()->HandleControlFrameHeadersComplete(nullptr)) { + visitor()->OnHeaderFrameEnd(stream_id()); + } else { + SetSpdyErrorAndNotify(SpdyFramerError::SPDY_DECOMPRESS_FAILURE); + return; + } + const Http2FrameHeader& first = frame_type() == Http2FrameType::CONTINUATION + ? hpack_first_frame_header_ + : frame_header_; + if (first.type == Http2FrameType::HEADERS && first.IsEndStream()) { + visitor()->OnStreamEnd(first.stream_id); + } + has_hpack_first_frame_header_ = false; + CorruptFrameHeader(&hpack_first_frame_header_); + } else { + DCHECK(has_hpack_first_frame_header_); + has_expected_frame_type_ = true; + expected_frame_type_ = Http2FrameType::CONTINUATION; + } +} + +} // namespace http2 + +namespace spdy { + +bool SpdyFramerVisitorInterface::OnGoAwayFrameData(const char* goaway_data, + size_t len) { + return true; +} + +} // namespace spdy diff --git a/chromium/net/third_party/quiche/src/spdy/core/http2_frame_decoder_adapter.h b/chromium/net/third_party/quiche/src/spdy/core/http2_frame_decoder_adapter.h new file mode 100644 index 00000000000..02deafdf909 --- /dev/null +++ b/chromium/net/third_party/quiche/src/spdy/core/http2_frame_decoder_adapter.h @@ -0,0 +1,514 @@ +// Copyright 2016 The Chromium 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_SPDY_CORE_HTTP2_FRAME_DECODER_ADAPTER_H_ +#define QUICHE_SPDY_CORE_HTTP2_FRAME_DECODER_ADAPTER_H_ + +#include <stddef.h> + +#include <cstdint> +#include <memory> + +#include "net/third_party/quiche/src/http2/decoder/http2_frame_decoder.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_optional.h" +#include "net/third_party/quiche/src/spdy/core/hpack/hpack_decoder_adapter.h" +#include "net/third_party/quiche/src/spdy/core/hpack/hpack_header_table.h" +#include "net/third_party/quiche/src/spdy/core/spdy_alt_svc_wire_format.h" +#include "net/third_party/quiche/src/spdy/core/spdy_headers_handler_interface.h" +#include "net/third_party/quiche/src/spdy/core/spdy_protocol.h" +#include "net/third_party/quiche/src/spdy/platform/api/spdy_string_piece.h" + +namespace spdy { + +class SpdyFramerVisitorInterface; +class ExtensionVisitorInterface; + +} // namespace spdy + +// TODO(dahollings): Perform various renames/moves suggested in cl/164660364. + +namespace http2 { + +// Adapts SpdyFramer interface to use Http2FrameDecoder. +class SPDY_EXPORT_PRIVATE Http2DecoderAdapter + : public http2::Http2FrameDecoderListener { + public: + // HTTP2 states. + enum SpdyState { + SPDY_ERROR, + SPDY_READY_FOR_FRAME, // Framer is ready for reading the next frame. + SPDY_FRAME_COMPLETE, // Framer has finished reading a frame, need to reset. + SPDY_READING_COMMON_HEADER, + SPDY_CONTROL_FRAME_PAYLOAD, + SPDY_READ_DATA_FRAME_PADDING_LENGTH, + SPDY_CONSUME_PADDING, + SPDY_IGNORE_REMAINING_PAYLOAD, + SPDY_FORWARD_STREAM_FRAME, + SPDY_CONTROL_FRAME_BEFORE_HEADER_BLOCK, + SPDY_CONTROL_FRAME_HEADER_BLOCK, + SPDY_GOAWAY_FRAME_PAYLOAD, + SPDY_SETTINGS_FRAME_HEADER, + SPDY_SETTINGS_FRAME_PAYLOAD, + SPDY_ALTSVC_FRAME_PAYLOAD, + SPDY_EXTENSION_FRAME_PAYLOAD, + }; + + // Framer error codes. + enum SpdyFramerError { + SPDY_NO_ERROR, + SPDY_INVALID_STREAM_ID, // Stream ID is invalid + SPDY_INVALID_CONTROL_FRAME, // Control frame is mal-formatted. + SPDY_CONTROL_PAYLOAD_TOO_LARGE, // Control frame payload was too large. + SPDY_ZLIB_INIT_FAILURE, // The Zlib library could not initialize. + SPDY_UNSUPPORTED_VERSION, // Control frame has unsupported version. + SPDY_DECOMPRESS_FAILURE, // There was an error decompressing. + SPDY_COMPRESS_FAILURE, // There was an error compressing. + SPDY_GOAWAY_FRAME_CORRUPT, // GOAWAY frame could not be parsed. + SPDY_RST_STREAM_FRAME_CORRUPT, // RST_STREAM frame could not be parsed. + SPDY_INVALID_PADDING, // HEADERS or DATA frame padding invalid + SPDY_INVALID_DATA_FRAME_FLAGS, // Data frame has invalid flags. + SPDY_INVALID_CONTROL_FRAME_FLAGS, // Control frame has invalid flags. + SPDY_UNEXPECTED_FRAME, // Frame received out of order. + SPDY_INTERNAL_FRAMER_ERROR, // SpdyFramer was used incorrectly. + SPDY_INVALID_CONTROL_FRAME_SIZE, // Control frame not sized to spec + SPDY_OVERSIZED_PAYLOAD, // Payload size was too large + + LAST_ERROR, // Must be the last entry in the enum. + }; + + // For debugging. + static const char* StateToString(int state); + static const char* SpdyFramerErrorToString(SpdyFramerError spdy_framer_error); + + Http2DecoderAdapter(); + ~Http2DecoderAdapter() override; + + // Set callbacks to be called from the framer. A visitor must be set, or + // else the framer will likely crash. It is acceptable for the visitor + // to do nothing. If this is called multiple times, only the last visitor + // will be used. + void set_visitor(spdy::SpdyFramerVisitorInterface* visitor); + spdy::SpdyFramerVisitorInterface* visitor() const { return visitor_; } + + // Set extension callbacks to be called from the framer or decoder. Optional. + // If called multiple times, only the last visitor will be used. + void set_extension_visitor(spdy::ExtensionVisitorInterface* visitor); + + // Set debug callbacks to be called from the framer. The debug visitor is + // completely optional and need not be set in order for normal operation. + // If this is called multiple times, only the last visitor will be used. + void set_debug_visitor(spdy::SpdyFramerDebugVisitorInterface* debug_visitor); + spdy::SpdyFramerDebugVisitorInterface* debug_visitor() const { + return debug_visitor_; + } + + // Set debug callbacks to be called from the HPACK decoder. + void SetDecoderHeaderTableDebugVisitor( + std::unique_ptr<spdy::HpackHeaderTable::DebugVisitorInterface> visitor); + + // Sets whether or not ProcessInput returns after finishing a frame, or + // continues processing additional frames. Normally ProcessInput processes + // all input, but this method enables the caller (and visitor) to work with + // a single frame at a time (or that portion of the frame which is provided + // as input). Reset() does not change the value of this flag. + void set_process_single_input_frame(bool v); + bool process_single_input_frame() const { + return process_single_input_frame_; + } + + // Decode the |len| bytes of encoded HTTP/2 starting at |*data|. Returns + // the number of bytes consumed. It is safe to pass more bytes in than + // may be consumed. Should process (or otherwise buffer) as much as + // available, unless process_single_input_frame is true. + size_t ProcessInput(const char* data, size_t len); + + // Reset the decoder (used just for tests at this time). + void Reset(); + + // Current state of the decoder. + SpdyState state() const; + + // Current error code (NO_ERROR if state != ERROR). + SpdyFramerError spdy_framer_error() const; + + // Has any frame header looked like the start of an HTTP/1.1 (or earlier) + // response? Used to detect if a backend/server that we sent a request to + // has responded with an HTTP/1.1 (or earlier) response. + bool probable_http_response() const; + + // Returns the estimate of dynamically allocated memory in bytes. + size_t EstimateMemoryUsage() const; + + spdy::HpackDecoderAdapter* GetHpackDecoder(); + + bool HasError() const; + + private: + 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 http2_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; + + size_t ProcessInputFrame(const char* data, size_t len); + + void DetermineSpdyState(DecodeStatus status); + void ResetBetweenFrames(); + + // ResetInternal is called from the constructor, and during tests, but not + // otherwise (i.e. not between every frame). + void ResetInternal(); + + void set_spdy_state(SpdyState v); + + void SetSpdyErrorAndNotify(SpdyFramerError error); + + const Http2FrameHeader& frame_header() const; + + uint32_t stream_id() const; + Http2FrameType frame_type() const; + + size_t remaining_total_payload() const; + + bool IsReadingPaddingLength(); + bool IsSkippingPadding(); + bool IsDiscardingPayload(); + // Called from OnXyz or OnXyzStart methods to decide whether it is OK to + // handle the callback. + bool IsOkToStartFrame(const Http2FrameHeader& header); + bool HasRequiredStreamId(uint32_t stream_id); + + bool HasRequiredStreamId(const Http2FrameHeader& header); + + bool HasRequiredStreamIdZero(uint32_t stream_id); + + bool HasRequiredStreamIdZero(const Http2FrameHeader& header); + + void ReportReceiveCompressedFrame(const Http2FrameHeader& header); + + void CommonStartHpackBlock(); + + // SpdyFramer calls HandleControlFrameHeadersData even if there are zero + // fragment bytes in the first frame, so do the same. + void MaybeAnnounceEmptyFirstHpackFragment(); + void CommonHpackFragmentEnd(); + + // The most recently decoded frame header; invalid after we reached the end + // of that frame. + Http2FrameHeader frame_header_; + + // If decoding an HPACK block that is split across multiple frames, this holds + // the frame header of the HEADERS or PUSH_PROMISE that started the block. + Http2FrameHeader hpack_first_frame_header_; + + // Amount of trailing padding. Currently used just as an indicator of whether + // OnPadLength has been called. + Http2Optional<size_t> opt_pad_length_; + + // Temporary buffers for the AltSvc fields. + Http2String alt_svc_origin_; + Http2String alt_svc_value_; + + // Listener used if we transition to an error state; the listener ignores all + // the callbacks. + Http2FrameDecoderNoOpListener no_op_listener_; + + spdy::SpdyFramerVisitorInterface* visitor_ = nullptr; + spdy::SpdyFramerDebugVisitorInterface* debug_visitor_ = nullptr; + + // If non-null, unknown frames and settings are passed to the extension. + spdy::ExtensionVisitorInterface* extension_ = nullptr; + + // The HPACK decoder to be used for this adapter. User is responsible for + // clearing if the adapter is to be used for another connection. + std::unique_ptr<spdy::HpackDecoderAdapter> hpack_decoder_ = nullptr; + + // The HTTP/2 frame decoder. Accessed via a unique_ptr to allow replacement + // (e.g. in tests) when Reset() is called. + std::unique_ptr<Http2FrameDecoder> frame_decoder_; + + // Next frame type expected. Currently only used for CONTINUATION frames, + // but could be used for detecting whether the first frame is a SETTINGS + // frame. + // TODO(jamessyng): Provide means to indicate that decoder should require + // SETTINGS frame as the first frame. + Http2FrameType expected_frame_type_; + + // Attempt to duplicate the SpdyState and SpdyFramerError values that + // SpdyFramer sets. Values determined by getting tests to pass. + SpdyState spdy_state_; + SpdyFramerError spdy_framer_error_; + + // The limit on the size of received HTTP/2 payloads as specified in the + // SETTINGS_MAX_FRAME_SIZE advertised to peer. + size_t recv_frame_size_limit_ = spdy::kHttp2DefaultFramePayloadLimit; + + // Has OnFrameHeader been called? + bool decoded_frame_header_ = false; + + // Have we recorded an Http2FrameHeader for the current frame? + // We only do so if the decoder will make multiple callbacks for + // the frame; for example, for PING frames we don't make record + // the frame header, but for ALTSVC we do. + bool has_frame_header_ = false; + + // Have we recorded an Http2FrameHeader for the current HPACK block? + // True only for multi-frame HPACK blocks. + bool has_hpack_first_frame_header_ = false; + + // Has OnHeaders() already been called for current HEADERS block? Only + // meaningful between OnHeadersStart and OnHeadersPriority. + bool on_headers_called_; + + // Has OnHpackFragment() already been called for current HPACK block? + // SpdyFramer will pass an empty buffer to the HPACK decoder if a HEADERS + // or PUSH_PROMISE has no HPACK data in it (e.g. a HEADERS frame with only + // padding). Detect that condition and replicate the behavior using this + // field. + bool on_hpack_fragment_called_; + + // Have we seen a frame header that appears to be an HTTP/1 response? + bool latched_probable_http_response_ = false; + + // Is expected_frame_type_ set? + bool has_expected_frame_type_ = false; + + // Is the current frame payload destined for |extension_|? + bool handling_extension_payload_ = false; + + bool process_single_input_frame_ = false; +}; + +} // namespace http2 + +namespace spdy { + +// Http2DecoderAdapter will use the given visitor implementing this +// interface to deliver event callbacks as frames are decoded. +// +// Control frames that contain HTTP2 header blocks (HEADER, and PUSH_PROMISE) +// are processed in fashion that allows the decompressed header block to be +// delivered in chunks to the visitor. +// The following steps are followed: +// 1. OnHeaders, or OnPushPromise is called. +// 2. OnHeaderFrameStart is called; visitor is expected to return an instance +// of SpdyHeadersHandlerInterface that will receive the header key-value +// pairs. +// 3. OnHeaderFrameEnd is called, indicating that the full header block has +// been delivered for the control frame. +// During step 2, if the visitor is not interested in accepting the header data, +// it should return a no-op implementation of SpdyHeadersHandlerInterface. +class SPDY_EXPORT_PRIVATE SpdyFramerVisitorInterface { + public: + virtual ~SpdyFramerVisitorInterface() {} + + // Called if an error is detected in the SpdyFrame protocol. + virtual void OnError(http2::Http2DecoderAdapter::SpdyFramerError error) = 0; + + // Called when the common header for a frame is received. Validating the + // common header occurs in later processing. + virtual void OnCommonHeader(SpdyStreamId /*stream_id*/, + size_t /*length*/, + uint8_t /*type*/, + uint8_t /*flags*/) {} + + // Called when a data frame header is received. The frame's data + // payload will be provided via subsequent calls to + // OnStreamFrameData(). + virtual void OnDataFrameHeader(SpdyStreamId stream_id, + size_t length, + bool fin) = 0; + + // Called when data is received. + // |stream_id| The stream receiving data. + // |data| A buffer containing the data received. + // |len| The length of the data buffer. + virtual void OnStreamFrameData(SpdyStreamId stream_id, + const char* data, + size_t len) = 0; + + // Called when the other side has finished sending data on this stream. + // |stream_id| The stream that was receiving data. + virtual void OnStreamEnd(SpdyStreamId stream_id) = 0; + + // Called when padding length field is received on a DATA frame. + // |stream_id| The stream receiving data. + // |value| The value of the padding length field. + virtual void OnStreamPadLength(SpdyStreamId stream_id, size_t value) {} + + // Called when padding is received (the trailing octets, not pad_len field) on + // a DATA frame. + // |stream_id| The stream receiving data. + // |len| The number of padding octets. + virtual void OnStreamPadding(SpdyStreamId stream_id, size_t len) = 0; + + // Called just before processing the payload of a frame containing header + // data. Should return an implementation of SpdyHeadersHandlerInterface that + // will receive headers for stream |stream_id|. The caller will not take + // ownership of the headers handler. The same instance should remain live + // and be returned for all header frames comprising a logical header block + // (i.e. until OnHeaderFrameEnd() is called). + virtual SpdyHeadersHandlerInterface* OnHeaderFrameStart( + SpdyStreamId stream_id) = 0; + + // Called after processing the payload of a frame containing header data. + virtual void OnHeaderFrameEnd(SpdyStreamId stream_id) = 0; + + // Called when a RST_STREAM frame has been parsed. + virtual void OnRstStream(SpdyStreamId stream_id, + SpdyErrorCode error_code) = 0; + + // Called when a SETTINGS frame is received. + virtual void OnSettings() {} + + // Called when a complete setting within a SETTINGS frame has been parsed. + // Note that |id| may or may not be a SETTINGS ID defined in the HTTP/2 spec. + virtual void OnSetting(SpdySettingsId id, uint32_t value) = 0; + + // Called when a SETTINGS frame is received with the ACK flag set. + virtual void OnSettingsAck() {} + + // Called before and after parsing SETTINGS id and value tuples. + virtual void OnSettingsEnd() = 0; + + // Called when a PING frame has been parsed. + virtual void OnPing(SpdyPingId unique_id, bool is_ack) = 0; + + // Called when a GOAWAY frame has been parsed. + virtual void OnGoAway(SpdyStreamId last_accepted_stream_id, + SpdyErrorCode error_code) = 0; + + // Called when a HEADERS frame is received. + // Note that header block data is not included. See OnHeaderFrameStart(). + // |stream_id| The stream receiving the header. + // |has_priority| Whether or not the headers frame included a priority value, + // and stream dependency info. + // |weight| If |has_priority| is true, then weight (in the range [1, 256]) + // for the receiving stream, otherwise 0. + // |parent_stream_id| If |has_priority| is true the parent stream of the + // receiving stream, else 0. + // |exclusive| If |has_priority| is true the exclusivity of dependence on the + // parent stream, else false. + // |fin| Whether FIN flag is set in frame headers. + // |end| False if HEADERs frame is to be followed by a CONTINUATION frame, + // or true if not. + virtual void OnHeaders(SpdyStreamId stream_id, + bool has_priority, + int weight, + SpdyStreamId parent_stream_id, + bool exclusive, + bool fin, + bool end) = 0; + + // Called when a WINDOW_UPDATE frame has been parsed. + virtual void OnWindowUpdate(SpdyStreamId stream_id, + int delta_window_size) = 0; + + // Called when a goaway frame opaque data is available. + // |goaway_data| A buffer containing the opaque GOAWAY data chunk received. + // |len| The length of the header data buffer. A length of zero indicates + // that the header data block has been completely sent. + // When this function returns true the visitor indicates that it accepted + // all of the data. Returning false indicates that that an error has + // occurred while processing the data. Default implementation returns true. + virtual bool OnGoAwayFrameData(const char* goaway_data, size_t len); + + // Called when a PUSH_PROMISE frame is received. + // Note that header block data is not included. See OnHeaderFrameStart(). + virtual void OnPushPromise(SpdyStreamId stream_id, + SpdyStreamId promised_stream_id, + bool end) = 0; + + // Called when a CONTINUATION frame is received. + // Note that header block data is not included. See OnHeaderFrameStart(). + virtual void OnContinuation(SpdyStreamId stream_id, bool end) = 0; + + // Called when an ALTSVC frame has been parsed. + virtual void OnAltSvc( + SpdyStreamId /*stream_id*/, + SpdyStringPiece /*origin*/, + const SpdyAltSvcWireFormat::AlternativeServiceVector& /*altsvc_vector*/) { + } + + // Called when a PRIORITY frame is received. + // |stream_id| The stream to update the priority of. + // |parent_stream_id| The parent stream of |stream_id|. + // |weight| Stream weight, in the range [1, 256]. + // |exclusive| Whether |stream_id| should be an only child of + // |parent_stream_id|. + virtual void OnPriority(SpdyStreamId stream_id, + SpdyStreamId parent_stream_id, + int weight, + bool exclusive) = 0; + + // Called when a frame type we don't recognize is received. + // Return true if this appears to be a valid extension frame, false otherwise. + // We distinguish between extension frames and nonsense by checking + // whether the stream id is valid. + virtual bool OnUnknownFrame(SpdyStreamId stream_id, uint8_t frame_type) = 0; +}; + +class SPDY_EXPORT_PRIVATE ExtensionVisitorInterface { + public: + virtual ~ExtensionVisitorInterface() {} + + // Called when SETTINGS are received, including non-standard SETTINGS. + virtual void OnSetting(SpdySettingsId id, uint32_t value) = 0; + + // Called when non-standard frames are received. + virtual bool OnFrameHeader(SpdyStreamId stream_id, + size_t length, + uint8_t type, + uint8_t flags) = 0; + + // The payload for a single frame may be delivered as multiple calls to + // OnFramePayload. Since the length field is passed in OnFrameHeader, there is + // no explicit indication of the end of the frame payload. + virtual void OnFramePayload(const char* data, size_t len) = 0; +}; + +} // namespace spdy + +#endif // QUICHE_SPDY_CORE_HTTP2_FRAME_DECODER_ADAPTER_H_ diff --git a/chromium/net/third_party/quiche/src/spdy/core/mock_spdy_framer_visitor.cc b/chromium/net/third_party/quiche/src/spdy/core/mock_spdy_framer_visitor.cc new file mode 100644 index 00000000000..053c25518e5 --- /dev/null +++ b/chromium/net/third_party/quiche/src/spdy/core/mock_spdy_framer_visitor.cc @@ -0,0 +1,19 @@ +// Copyright 2014 The Chromium 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/spdy/core/mock_spdy_framer_visitor.h" + +namespace spdy { + +namespace test { + +MockSpdyFramerVisitor::MockSpdyFramerVisitor() { + DelegateHeaderHandling(); +} + +MockSpdyFramerVisitor::~MockSpdyFramerVisitor() = default; + +} // namespace test + +} // namespace spdy diff --git a/chromium/net/third_party/quiche/src/spdy/core/mock_spdy_framer_visitor.h b/chromium/net/third_party/quiche/src/spdy/core/mock_spdy_framer_visitor.h new file mode 100644 index 00000000000..124efe584f4 --- /dev/null +++ b/chromium/net/third_party/quiche/src/spdy/core/mock_spdy_framer_visitor.h @@ -0,0 +1,103 @@ +// Copyright 2014 The Chromium 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_SPDY_CORE_MOCK_SPDY_FRAMER_VISITOR_H_ +#define QUICHE_SPDY_CORE_MOCK_SPDY_FRAMER_VISITOR_H_ + +#include <cstdint> +#include <memory> + +#include "testing/gmock/include/gmock/gmock.h" +#include "net/third_party/quiche/src/spdy/core/http2_frame_decoder_adapter.h" +#include "net/third_party/quiche/src/spdy/core/spdy_test_utils.h" +#include "net/third_party/quiche/src/spdy/platform/api/spdy_ptr_util.h" +#include "net/third_party/quiche/src/spdy/platform/api/spdy_string_piece.h" + +namespace spdy { + +namespace test { + +class MockSpdyFramerVisitor : public SpdyFramerVisitorInterface { + public: + MockSpdyFramerVisitor(); + ~MockSpdyFramerVisitor() override; + + MOCK_METHOD1(OnError, + void(http2::Http2DecoderAdapter::SpdyFramerError error)); + MOCK_METHOD3(OnDataFrameHeader, + void(SpdyStreamId stream_id, size_t length, bool fin)); + MOCK_METHOD3(OnStreamFrameData, + void(SpdyStreamId stream_id, const char* data, size_t len)); + MOCK_METHOD1(OnStreamEnd, void(SpdyStreamId stream_id)); + MOCK_METHOD2(OnStreamPadLength, void(SpdyStreamId stream_id, size_t value)); + MOCK_METHOD2(OnStreamPadding, void(SpdyStreamId stream_id, size_t len)); + MOCK_METHOD1(OnHeaderFrameStart, + SpdyHeadersHandlerInterface*(SpdyStreamId stream_id)); + MOCK_METHOD1(OnHeaderFrameEnd, void(SpdyStreamId stream_id)); + MOCK_METHOD2(OnRstStream, + void(SpdyStreamId stream_id, SpdyErrorCode error_code)); + MOCK_METHOD0(OnSettings, void()); + MOCK_METHOD2(OnSetting, void(SpdySettingsId id, uint32_t value)); + MOCK_METHOD2(OnPing, void(SpdyPingId unique_id, bool is_ack)); + MOCK_METHOD0(OnSettingsEnd, void()); + MOCK_METHOD2(OnGoAway, + void(SpdyStreamId last_accepted_stream_id, + SpdyErrorCode error_code)); + MOCK_METHOD7(OnHeaders, + void(SpdyStreamId stream_id, + bool has_priority, + int weight, + SpdyStreamId parent_stream_id, + bool exclusive, + bool fin, + bool end)); + MOCK_METHOD2(OnWindowUpdate, + void(SpdyStreamId stream_id, int delta_window_size)); + MOCK_METHOD3(OnPushPromise, + void(SpdyStreamId stream_id, + SpdyStreamId promised_stream_id, + bool end)); + MOCK_METHOD2(OnContinuation, void(SpdyStreamId stream_id, bool end)); + MOCK_METHOD3(OnAltSvc, + void(SpdyStreamId stream_id, + SpdyStringPiece origin, + const SpdyAltSvcWireFormat::AlternativeServiceVector& + altsvc_vector)); + MOCK_METHOD4(OnPriority, + void(SpdyStreamId stream_id, + SpdyStreamId parent_stream_id, + int weight, + bool exclusive)); + MOCK_METHOD2(OnUnknownFrame, + bool(SpdyStreamId stream_id, uint8_t frame_type)); + + void DelegateHeaderHandling() { + ON_CALL(*this, OnHeaderFrameStart(testing::_)) + .WillByDefault(testing::Invoke( + this, &MockSpdyFramerVisitor::ReturnTestHeadersHandler)); + ON_CALL(*this, OnHeaderFrameEnd(testing::_)) + .WillByDefault(testing::Invoke( + this, &MockSpdyFramerVisitor::ResetTestHeadersHandler)); + } + + SpdyHeadersHandlerInterface* ReturnTestHeadersHandler( + SpdyStreamId /* stream_id */) { + if (headers_handler_ == nullptr) { + headers_handler_ = SpdyMakeUnique<TestHeadersHandler>(); + } + return headers_handler_.get(); + } + + void ResetTestHeadersHandler(SpdyStreamId /* stream_id */) { + headers_handler_.reset(); + } + + std::unique_ptr<SpdyHeadersHandlerInterface> headers_handler_; +}; + +} // namespace test + +} // namespace spdy + +#endif // QUICHE_SPDY_CORE_MOCK_SPDY_FRAMER_VISITOR_H_ diff --git a/chromium/net/third_party/quiche/src/spdy/core/priority_write_scheduler.h b/chromium/net/third_party/quiche/src/spdy/core/priority_write_scheduler.h new file mode 100644 index 00000000000..d5c3c44f3af --- /dev/null +++ b/chromium/net/third_party/quiche/src/spdy/core/priority_write_scheduler.h @@ -0,0 +1,322 @@ +// Copyright (c) 2015 The Chromium 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_SPDY_CORE_PRIORITY_WRITE_SCHEDULER_H_ +#define QUICHE_SPDY_CORE_PRIORITY_WRITE_SCHEDULER_H_ + +#include <algorithm> +#include <cstddef> +#include <cstdint> +#include <tuple> +#include <unordered_map> +#include <utility> +#include <vector> + +#include "base/logging.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_containers.h" +#include "net/third_party/quiche/src/spdy/core/spdy_protocol.h" +#include "net/third_party/quiche/src/spdy/core/write_scheduler.h" +#include "net/third_party/quiche/src/spdy/platform/api/spdy_bug_tracker.h" +#include "net/third_party/quiche/src/spdy/platform/api/spdy_macros.h" +#include "net/third_party/quiche/src/spdy/platform/api/spdy_string.h" +#include "net/third_party/quiche/src/spdy/platform/api/spdy_string_utils.h" + +namespace spdy { + +namespace test { +template <typename StreamIdType> +class PriorityWriteSchedulerPeer; +} + +// WriteScheduler implementation that manages the order in which streams are +// written using the SPDY priority scheme described at: +// https://www.chromium.org/spdy/spdy-protocol/spdy-protocol-draft3-1#TOC-2.3.3-Stream-priority +// +// Internally, PriorityWriteScheduler consists of 8 PriorityInfo objects, one +// for each priority value. Each PriorityInfo contains a list of streams of +// that priority that are ready to write, as well as a timestamp of the last +// I/O event that occurred for a stream of that priority. +// +// DO NOT USE. Deprecated. +template <typename StreamIdType> +class PriorityWriteScheduler : public WriteScheduler<StreamIdType> { + public: + using typename WriteScheduler<StreamIdType>::StreamPrecedenceType; + + // Creates scheduler with no streams. + PriorityWriteScheduler() = default; + + void RegisterStream(StreamIdType stream_id, + const StreamPrecedenceType& precedence) override { + // TODO(mpw): verify |precedence.is_spdy3_priority() == true| once + // Http2PriorityWriteScheduler enabled for HTTP/2. + + // parent_id not used here, but may as well validate it. However, + // parent_id may legitimately not be registered yet--see b/15676312. + StreamIdType parent_id = precedence.parent_id(); + SPDY_DVLOG_IF( + 1, parent_id != kHttp2RootStreamId && !StreamRegistered(parent_id)) + << "Parent stream " << parent_id << " not registered"; + + if (stream_id == kHttp2RootStreamId) { + SPDY_BUG << "Stream " << kHttp2RootStreamId << " already registered"; + return; + } + StreamInfo stream_info = {precedence.spdy3_priority(), stream_id, false}; + bool inserted = + stream_infos_.insert(std::make_pair(stream_id, stream_info)).second; + SPDY_BUG_IF(!inserted) << "Stream " << stream_id << " already registered"; + } + + void UnregisterStream(StreamIdType stream_id) override { + auto it = stream_infos_.find(stream_id); + if (it == stream_infos_.end()) { + SPDY_BUG << "Stream " << stream_id << " not registered"; + return; + } + StreamInfo& stream_info = it->second; + if (stream_info.ready) { + bool erased = + Erase(&priority_infos_[stream_info.priority].ready_list, stream_info); + DCHECK(erased); + } + stream_infos_.erase(it); + } + + bool StreamRegistered(StreamIdType stream_id) const override { + return stream_infos_.find(stream_id) != stream_infos_.end(); + } + + StreamPrecedenceType GetStreamPrecedence( + StreamIdType stream_id) const override { + auto it = stream_infos_.find(stream_id); + if (it == stream_infos_.end()) { + DVLOG(1) << "Stream " << stream_id << " not registered"; + return StreamPrecedenceType(kV3LowestPriority); + } + return StreamPrecedenceType(it->second.priority); + } + + void UpdateStreamPrecedence(StreamIdType stream_id, + const StreamPrecedenceType& precedence) override { + // TODO(mpw): verify |precedence.is_spdy3_priority() == true| once + // Http2PriorityWriteScheduler enabled for HTTP/2. + + // parent_id not used here, but may as well validate it. However, + // parent_id may legitimately not be registered yet--see b/15676312. + StreamIdType parent_id = precedence.parent_id(); + SPDY_DVLOG_IF( + 1, parent_id != kHttp2RootStreamId && !StreamRegistered(parent_id)) + << "Parent stream " << parent_id << " not registered"; + + auto it = stream_infos_.find(stream_id); + if (it == stream_infos_.end()) { + // TODO(mpw): add to stream_infos_ on demand--see b/15676312. + DVLOG(1) << "Stream " << stream_id << " not registered"; + return; + } + StreamInfo& stream_info = it->second; + SpdyPriority new_priority = precedence.spdy3_priority(); + if (stream_info.priority == new_priority) { + return; + } + if (stream_info.ready) { + bool erased = + Erase(&priority_infos_[stream_info.priority].ready_list, stream_info); + DCHECK(erased); + priority_infos_[new_priority].ready_list.push_back(&stream_info); + ++num_ready_streams_; + } + stream_info.priority = new_priority; + } + + std::vector<StreamIdType> GetStreamChildren( + StreamIdType stream_id) const override { + return std::vector<StreamIdType>(); + } + + void RecordStreamEventTime(StreamIdType stream_id, + int64_t now_in_usec) override { + auto it = stream_infos_.find(stream_id); + if (it == stream_infos_.end()) { + SPDY_BUG << "Stream " << stream_id << " not registered"; + return; + } + PriorityInfo& priority_info = priority_infos_[it->second.priority]; + priority_info.last_event_time_usec = + std::max(priority_info.last_event_time_usec, now_in_usec); + } + + int64_t GetLatestEventWithPrecedence(StreamIdType stream_id) const override { + auto it = stream_infos_.find(stream_id); + if (it == stream_infos_.end()) { + SPDY_BUG << "Stream " << stream_id << " not registered"; + return 0; + } + int64_t last_event_time_usec = 0; + const StreamInfo& stream_info = it->second; + for (SpdyPriority p = kV3HighestPriority; p < stream_info.priority; ++p) { + last_event_time_usec = std::max(last_event_time_usec, + priority_infos_[p].last_event_time_usec); + } + return last_event_time_usec; + } + + StreamIdType PopNextReadyStream() override { + return std::get<0>(PopNextReadyStreamAndPrecedence()); + } + + // Returns the next ready stream and its precedence. + std::tuple<StreamIdType, StreamPrecedenceType> + PopNextReadyStreamAndPrecedence() override { + for (SpdyPriority p = kV3HighestPriority; p <= kV3LowestPriority; ++p) { + ReadyList& ready_list = priority_infos_[p].ready_list; + if (!ready_list.empty()) { + StreamInfo* info = ready_list.front(); + ready_list.pop_front(); + --num_ready_streams_; + + DCHECK(stream_infos_.find(info->stream_id) != stream_infos_.end()); + info->ready = false; + return std::make_tuple(info->stream_id, + StreamPrecedenceType(info->priority)); + } + } + SPDY_BUG << "No ready streams available"; + return std::make_tuple(0, StreamPrecedenceType(kV3LowestPriority)); + } + + bool ShouldYield(StreamIdType stream_id) const override { + auto it = stream_infos_.find(stream_id); + if (it == stream_infos_.end()) { + SPDY_BUG << "Stream " << stream_id << " not registered"; + return false; + } + + // If there's a higher priority stream, this stream should yield. + const StreamInfo& stream_info = it->second; + for (SpdyPriority p = kV3HighestPriority; p < stream_info.priority; ++p) { + if (!priority_infos_[p].ready_list.empty()) { + return true; + } + } + + // If this priority level is empty, or this stream is the next up, there's + // no need to yield. + const auto& ready_list = priority_infos_[it->second.priority].ready_list; + if (ready_list.empty() || ready_list.front()->stream_id == stream_id) { + return false; + } + + // There are other streams in this priority level which take precedence. + // Yield. + return true; + } + + void MarkStreamReady(StreamIdType stream_id, bool add_to_front) override { + auto it = stream_infos_.find(stream_id); + if (it == stream_infos_.end()) { + SPDY_BUG << "Stream " << stream_id << " not registered"; + return; + } + StreamInfo& stream_info = it->second; + if (stream_info.ready) { + return; + } + ReadyList& ready_list = priority_infos_[stream_info.priority].ready_list; + if (add_to_front) { + ready_list.push_front(&stream_info); + } else { + ready_list.push_back(&stream_info); + } + ++num_ready_streams_; + stream_info.ready = true; + } + + void MarkStreamNotReady(StreamIdType stream_id) override { + auto it = stream_infos_.find(stream_id); + if (it == stream_infos_.end()) { + SPDY_BUG << "Stream " << stream_id << " not registered"; + return; + } + StreamInfo& stream_info = it->second; + if (!stream_info.ready) { + return; + } + bool erased = + Erase(&priority_infos_[stream_info.priority].ready_list, stream_info); + DCHECK(erased); + stream_info.ready = false; + } + + // Returns true iff the number of ready streams is non-zero. + bool HasReadyStreams() const override { return num_ready_streams_ > 0; } + + // Returns the number of ready streams. + size_t NumReadyStreams() const override { return num_ready_streams_; } + + SpdyString DebugString() const override { + return SpdyStrCat( + "PriorityWriteScheduler {num_streams=", stream_infos_.size(), + " num_ready_streams=", NumReadyStreams(), "}"); + } + + // Returns true if a stream is ready. + bool IsStreamReady(StreamIdType stream_id) const { + auto it = stream_infos_.find(stream_id); + if (it == stream_infos_.end()) { + DLOG(INFO) << "Stream " << stream_id << " not registered"; + return false; + } + return it->second.ready; + } + + private: + friend class test::PriorityWriteSchedulerPeer<StreamIdType>; + + // State kept for all registered streams. All ready streams have ready = true + // and should be present in priority_infos_[priority].ready_list. + struct StreamInfo { + SpdyPriority priority; + StreamIdType stream_id; + bool ready; + }; + + // O(1) size lookup, O(1) insert at front or back (amortized). + using ReadyList = http2::Http2Deque<StreamInfo*>; + + // State kept for each priority level. + struct PriorityInfo { + // IDs of streams that are ready to write. + ReadyList ready_list; + // Time of latest write event for stream of this priority, in microseconds. + int64_t last_event_time_usec = 0; + }; + + typedef std::unordered_map<StreamIdType, StreamInfo> StreamInfoMap; + + // Erases first occurrence (which should be the only one) of |info| in + // |ready_list|, returning true if found (and erased), or false otherwise. + // Decrements |num_ready_streams_| if an entry is erased. + bool Erase(ReadyList* ready_list, const StreamInfo& info) { + auto it = std::find(ready_list->begin(), ready_list->end(), &info); + if (it == ready_list->end()) { + return false; + } + ready_list->erase(it); + --num_ready_streams_; + return true; + } + + // Number of ready streams. + size_t num_ready_streams_ = 0; + // Per-priority state, including ready lists. + PriorityInfo priority_infos_[kV3LowestPriority + 1]; + // StreamInfos for all registered streams. + StreamInfoMap stream_infos_; +}; + +} // namespace spdy + +#endif // QUICHE_SPDY_CORE_PRIORITY_WRITE_SCHEDULER_H_ diff --git a/chromium/net/third_party/quiche/src/spdy/core/priority_write_scheduler_test.cc b/chromium/net/third_party/quiche/src/spdy/core/priority_write_scheduler_test.cc new file mode 100644 index 00000000000..a285cb2e4bd --- /dev/null +++ b/chromium/net/third_party/quiche/src/spdy/core/priority_write_scheduler_test.cc @@ -0,0 +1,371 @@ +// Copyright (c) 2015 The Chromium 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/spdy/core/priority_write_scheduler.h" + +#include "testing/gtest/include/gtest/gtest.h" +#include "net/third_party/quiche/src/spdy/core/spdy_protocol.h" +#include "net/third_party/quiche/src/spdy/core/spdy_test_utils.h" + +namespace spdy { +namespace test { + +template <typename StreamIdType> +class PriorityWriteSchedulerPeer { + public: + explicit PriorityWriteSchedulerPeer( + PriorityWriteScheduler<StreamIdType>* scheduler) + : scheduler_(scheduler) {} + + size_t NumReadyStreams(SpdyPriority priority) const { + return scheduler_->priority_infos_[priority].ready_list.size(); + } + + private: + PriorityWriteScheduler<StreamIdType>* scheduler_; +}; + +namespace { + +class PriorityWriteSchedulerTest : public ::testing::Test { + public: + PriorityWriteSchedulerTest() : peer_(&scheduler_) {} + + PriorityWriteScheduler<SpdyStreamId> scheduler_; + PriorityWriteSchedulerPeer<SpdyStreamId> peer_; +}; + +TEST_F(PriorityWriteSchedulerTest, RegisterUnregisterStreams) { + EXPECT_FALSE(scheduler_.HasReadyStreams()); + EXPECT_FALSE(scheduler_.StreamRegistered(1)); + scheduler_.RegisterStream(1, SpdyStreamPrecedence(1)); + EXPECT_TRUE(scheduler_.StreamRegistered(1)); + + // Root stream counts as already registered. + EXPECT_SPDY_BUG( + scheduler_.RegisterStream(kHttp2RootStreamId, SpdyStreamPrecedence(1)), + "Stream 0 already registered"); + + // Try redundant registrations. + EXPECT_SPDY_BUG(scheduler_.RegisterStream(1, SpdyStreamPrecedence(1)), + "Stream 1 already registered"); + EXPECT_SPDY_BUG(scheduler_.RegisterStream(1, SpdyStreamPrecedence(2)), + "Stream 1 already registered"); + + scheduler_.RegisterStream(2, SpdyStreamPrecedence(3)); + + // Verify registration != ready. + EXPECT_FALSE(scheduler_.HasReadyStreams()); + + scheduler_.UnregisterStream(1); + scheduler_.UnregisterStream(2); + + // Try redundant unregistration. + EXPECT_SPDY_BUG(scheduler_.UnregisterStream(1), "Stream 1 not registered"); + EXPECT_SPDY_BUG(scheduler_.UnregisterStream(2), "Stream 2 not registered"); +} + +TEST_F(PriorityWriteSchedulerTest, RegisterStreamWithHttp2StreamDependency) { + EXPECT_FALSE(scheduler_.HasReadyStreams()); + EXPECT_FALSE(scheduler_.StreamRegistered(1)); + scheduler_.RegisterStream( + 1, SpdyStreamPrecedence(kHttp2RootStreamId, 123, false)); + EXPECT_TRUE(scheduler_.StreamRegistered(1)); + EXPECT_TRUE(scheduler_.GetStreamPrecedence(1).is_spdy3_priority()); + EXPECT_EQ(3, scheduler_.GetStreamPrecedence(1).spdy3_priority()); + EXPECT_FALSE(scheduler_.HasReadyStreams()); + + EXPECT_SPDY_BUG(scheduler_.RegisterStream( + 1, SpdyStreamPrecedence(kHttp2RootStreamId, 256, false)), + "Stream 1 already registered"); + EXPECT_TRUE(scheduler_.GetStreamPrecedence(1).is_spdy3_priority()); + EXPECT_EQ(3, scheduler_.GetStreamPrecedence(1).spdy3_priority()); + + // Registering stream with a non-existent parent stream is permissible, per + // b/15676312, but parent stream will always be reset to 0. + scheduler_.RegisterStream(2, SpdyStreamPrecedence(3, 123, false)); + EXPECT_TRUE(scheduler_.StreamRegistered(2)); + EXPECT_FALSE(scheduler_.StreamRegistered(3)); + EXPECT_EQ(kHttp2RootStreamId, scheduler_.GetStreamPrecedence(2).parent_id()); +} + +TEST_F(PriorityWriteSchedulerTest, GetStreamPrecedence) { + // Unknown streams tolerated due to b/15676312. However, return lowest + // priority. + EXPECT_EQ(kV3LowestPriority, + scheduler_.GetStreamPrecedence(1).spdy3_priority()); + + scheduler_.RegisterStream(1, SpdyStreamPrecedence(3)); + EXPECT_TRUE(scheduler_.GetStreamPrecedence(1).is_spdy3_priority()); + EXPECT_EQ(3, scheduler_.GetStreamPrecedence(1).spdy3_priority()); + + // Redundant registration shouldn't change stream priority. + EXPECT_SPDY_BUG(scheduler_.RegisterStream(1, SpdyStreamPrecedence(4)), + "Stream 1 already registered"); + EXPECT_EQ(3, scheduler_.GetStreamPrecedence(1).spdy3_priority()); + + scheduler_.UpdateStreamPrecedence(1, SpdyStreamPrecedence(5)); + EXPECT_EQ(5, scheduler_.GetStreamPrecedence(1).spdy3_priority()); + + // Toggling ready state shouldn't change stream priority. + scheduler_.MarkStreamReady(1, true); + EXPECT_EQ(5, scheduler_.GetStreamPrecedence(1).spdy3_priority()); + + // Test changing priority of ready stream. + EXPECT_EQ(1u, peer_.NumReadyStreams(5)); + scheduler_.UpdateStreamPrecedence(1, SpdyStreamPrecedence(6)); + EXPECT_EQ(6, scheduler_.GetStreamPrecedence(1).spdy3_priority()); + EXPECT_EQ(0u, peer_.NumReadyStreams(5)); + EXPECT_EQ(1u, peer_.NumReadyStreams(6)); + + EXPECT_EQ(1u, scheduler_.PopNextReadyStream()); + EXPECT_EQ(6, scheduler_.GetStreamPrecedence(1).spdy3_priority()); + + scheduler_.UnregisterStream(1); + EXPECT_EQ(kV3LowestPriority, + scheduler_.GetStreamPrecedence(1).spdy3_priority()); +} + +TEST_F(PriorityWriteSchedulerTest, PopNextReadyStreamAndPrecedence) { + scheduler_.RegisterStream(1, SpdyStreamPrecedence(3)); + scheduler_.MarkStreamReady(1, true); + EXPECT_EQ(std::make_tuple(1u, SpdyStreamPrecedence(3)), + scheduler_.PopNextReadyStreamAndPrecedence()); + scheduler_.UnregisterStream(1); +} + +TEST_F(PriorityWriteSchedulerTest, UpdateStreamPrecedence) { + // For the moment, updating stream precedence on a non-registered stream + // should have no effect. In the future, it will lazily cause the stream to + // be registered (b/15676312). + EXPECT_EQ(kV3LowestPriority, + scheduler_.GetStreamPrecedence(3).spdy3_priority()); + EXPECT_FALSE(scheduler_.StreamRegistered(3)); + scheduler_.UpdateStreamPrecedence(3, SpdyStreamPrecedence(1)); + EXPECT_FALSE(scheduler_.StreamRegistered(3)); + EXPECT_EQ(kV3LowestPriority, + scheduler_.GetStreamPrecedence(3).spdy3_priority()); + + scheduler_.RegisterStream(3, SpdyStreamPrecedence(1)); + EXPECT_EQ(1, scheduler_.GetStreamPrecedence(3).spdy3_priority()); + scheduler_.UpdateStreamPrecedence(3, SpdyStreamPrecedence(2)); + EXPECT_EQ(2, scheduler_.GetStreamPrecedence(3).spdy3_priority()); + + // Updating priority of stream to current priority value is valid, but has no + // effect. + scheduler_.UpdateStreamPrecedence(3, SpdyStreamPrecedence(2)); + EXPECT_EQ(2, scheduler_.GetStreamPrecedence(3).spdy3_priority()); + + // Even though stream 4 is marked ready after stream 5, it should be returned + // first by PopNextReadyStream() since it has higher priority. + scheduler_.RegisterStream(4, SpdyStreamPrecedence(1)); + scheduler_.MarkStreamReady(3, false); // priority 2 + EXPECT_TRUE(scheduler_.IsStreamReady(3)); + scheduler_.MarkStreamReady(4, false); // priority 1 + EXPECT_TRUE(scheduler_.IsStreamReady(4)); + EXPECT_EQ(4u, scheduler_.PopNextReadyStream()); + EXPECT_FALSE(scheduler_.IsStreamReady(4)); + EXPECT_EQ(3u, scheduler_.PopNextReadyStream()); + EXPECT_FALSE(scheduler_.IsStreamReady(3)); + + // Verify that lowering priority of stream 4 causes it to be returned later + // by PopNextReadyStream(). + scheduler_.MarkStreamReady(3, false); // priority 2 + scheduler_.MarkStreamReady(4, false); // priority 1 + scheduler_.UpdateStreamPrecedence(4, SpdyStreamPrecedence(3)); + EXPECT_EQ(3u, scheduler_.PopNextReadyStream()); + EXPECT_EQ(4u, scheduler_.PopNextReadyStream()); + + scheduler_.UnregisterStream(3); +} + +TEST_F(PriorityWriteSchedulerTest, + UpdateStreamPrecedenceWithHttp2StreamDependency) { + // Unknown streams tolerated due to b/15676312, but should have no effect. + scheduler_.UpdateStreamPrecedence(3, SpdyStreamPrecedence(0, 100, false)); + EXPECT_FALSE(scheduler_.StreamRegistered(3)); + + scheduler_.RegisterStream(3, SpdyStreamPrecedence(3)); + scheduler_.UpdateStreamPrecedence(3, SpdyStreamPrecedence(0, 100, false)); + EXPECT_TRUE(scheduler_.GetStreamPrecedence(3).is_spdy3_priority()); + EXPECT_EQ(4, scheduler_.GetStreamPrecedence(3).spdy3_priority()); + + scheduler_.UnregisterStream(3); + scheduler_.UpdateStreamPrecedence(3, SpdyStreamPrecedence(0, 100, false)); + EXPECT_FALSE(scheduler_.StreamRegistered(3)); +} + +TEST_F(PriorityWriteSchedulerTest, MarkStreamReadyBack) { + EXPECT_FALSE(scheduler_.HasReadyStreams()); + EXPECT_SPDY_BUG(scheduler_.MarkStreamReady(1, false), + "Stream 1 not registered"); + EXPECT_FALSE(scheduler_.HasReadyStreams()); + EXPECT_SPDY_BUG(EXPECT_EQ(0u, scheduler_.PopNextReadyStream()), + "No ready streams available"); + + // Add a bunch of ready streams to tail of per-priority lists. + // Expected order: (P2) 4, (P3) 1, 2, 3, (P5) 5. + scheduler_.RegisterStream(1, SpdyStreamPrecedence(3)); + scheduler_.MarkStreamReady(1, false); + EXPECT_TRUE(scheduler_.HasReadyStreams()); + scheduler_.RegisterStream(2, SpdyStreamPrecedence(3)); + scheduler_.MarkStreamReady(2, false); + scheduler_.RegisterStream(3, SpdyStreamPrecedence(3)); + scheduler_.MarkStreamReady(3, false); + scheduler_.RegisterStream(4, SpdyStreamPrecedence(2)); + scheduler_.MarkStreamReady(4, false); + scheduler_.RegisterStream(5, SpdyStreamPrecedence(5)); + scheduler_.MarkStreamReady(5, false); + + EXPECT_EQ(4u, scheduler_.PopNextReadyStream()); + EXPECT_EQ(1u, scheduler_.PopNextReadyStream()); + EXPECT_EQ(2u, scheduler_.PopNextReadyStream()); + EXPECT_EQ(3u, scheduler_.PopNextReadyStream()); + EXPECT_EQ(5u, scheduler_.PopNextReadyStream()); + EXPECT_SPDY_BUG(EXPECT_EQ(0u, scheduler_.PopNextReadyStream()), + "No ready streams available"); +} + +TEST_F(PriorityWriteSchedulerTest, MarkStreamReadyFront) { + EXPECT_FALSE(scheduler_.HasReadyStreams()); + EXPECT_SPDY_BUG(scheduler_.MarkStreamReady(1, true), + "Stream 1 not registered"); + EXPECT_FALSE(scheduler_.HasReadyStreams()); + EXPECT_SPDY_BUG(EXPECT_EQ(0u, scheduler_.PopNextReadyStream()), + "No ready streams available"); + + // Add a bunch of ready streams to head of per-priority lists. + // Expected order: (P2) 4, (P3) 3, 2, 1, (P5) 5 + scheduler_.RegisterStream(1, SpdyStreamPrecedence(3)); + scheduler_.MarkStreamReady(1, true); + EXPECT_TRUE(scheduler_.HasReadyStreams()); + scheduler_.RegisterStream(2, SpdyStreamPrecedence(3)); + scheduler_.MarkStreamReady(2, true); + scheduler_.RegisterStream(3, SpdyStreamPrecedence(3)); + scheduler_.MarkStreamReady(3, true); + scheduler_.RegisterStream(4, SpdyStreamPrecedence(2)); + scheduler_.MarkStreamReady(4, true); + scheduler_.RegisterStream(5, SpdyStreamPrecedence(5)); + scheduler_.MarkStreamReady(5, true); + + EXPECT_EQ(4u, scheduler_.PopNextReadyStream()); + EXPECT_EQ(3u, scheduler_.PopNextReadyStream()); + EXPECT_EQ(2u, scheduler_.PopNextReadyStream()); + EXPECT_EQ(1u, scheduler_.PopNextReadyStream()); + EXPECT_EQ(5u, scheduler_.PopNextReadyStream()); + EXPECT_SPDY_BUG(EXPECT_EQ(0u, scheduler_.PopNextReadyStream()), + "No ready streams available"); +} + +TEST_F(PriorityWriteSchedulerTest, MarkStreamReadyBackAndFront) { + scheduler_.RegisterStream(1, SpdyStreamPrecedence(4)); + scheduler_.RegisterStream(2, SpdyStreamPrecedence(3)); + scheduler_.RegisterStream(3, SpdyStreamPrecedence(3)); + scheduler_.RegisterStream(4, SpdyStreamPrecedence(3)); + scheduler_.RegisterStream(5, SpdyStreamPrecedence(4)); + scheduler_.RegisterStream(6, SpdyStreamPrecedence(1)); + + // Add a bunch of ready streams to per-priority lists, with variety of adding + // at head and tail. + // Expected order: (P1) 6, (P3) 4, 2, 3, (P4) 1, 5 + scheduler_.MarkStreamReady(1, true); + scheduler_.MarkStreamReady(2, true); + scheduler_.MarkStreamReady(3, false); + scheduler_.MarkStreamReady(4, true); + scheduler_.MarkStreamReady(5, false); + scheduler_.MarkStreamReady(6, true); + + EXPECT_EQ(6u, scheduler_.PopNextReadyStream()); + EXPECT_EQ(4u, scheduler_.PopNextReadyStream()); + EXPECT_EQ(2u, scheduler_.PopNextReadyStream()); + EXPECT_EQ(3u, scheduler_.PopNextReadyStream()); + EXPECT_EQ(1u, scheduler_.PopNextReadyStream()); + EXPECT_EQ(5u, scheduler_.PopNextReadyStream()); + EXPECT_SPDY_BUG(EXPECT_EQ(0u, scheduler_.PopNextReadyStream()), + "No ready streams available"); +} + +TEST_F(PriorityWriteSchedulerTest, MarkStreamNotReady) { + // Verify ready state reflected in NumReadyStreams(). + scheduler_.RegisterStream(1, SpdyStreamPrecedence(1)); + EXPECT_EQ(0u, scheduler_.NumReadyStreams()); + scheduler_.MarkStreamReady(1, false); + EXPECT_EQ(1u, scheduler_.NumReadyStreams()); + scheduler_.MarkStreamNotReady(1); + EXPECT_EQ(0u, scheduler_.NumReadyStreams()); + + // Empty pop should fail. + EXPECT_SPDY_BUG(EXPECT_EQ(0u, scheduler_.PopNextReadyStream()), + "No ready streams available"); + + // Tolerate redundant marking of a stream as not ready. + scheduler_.MarkStreamNotReady(1); + EXPECT_EQ(0u, scheduler_.NumReadyStreams()); + + // Should only be able to mark registered streams. + EXPECT_SPDY_BUG(scheduler_.MarkStreamNotReady(3), "Stream 3 not registered"); +} + +TEST_F(PriorityWriteSchedulerTest, UnregisterRemovesStream) { + scheduler_.RegisterStream(3, SpdyStreamPrecedence(4)); + scheduler_.MarkStreamReady(3, false); + EXPECT_EQ(1u, scheduler_.NumReadyStreams()); + + // Unregistering a stream should remove it from set of ready streams. + scheduler_.UnregisterStream(3); + EXPECT_EQ(0u, scheduler_.NumReadyStreams()); + EXPECT_SPDY_BUG(EXPECT_EQ(0u, scheduler_.PopNextReadyStream()), + "No ready streams available"); +} + +TEST_F(PriorityWriteSchedulerTest, ShouldYield) { + scheduler_.RegisterStream(1, SpdyStreamPrecedence(1)); + scheduler_.RegisterStream(4, SpdyStreamPrecedence(4)); + scheduler_.RegisterStream(5, SpdyStreamPrecedence(4)); + scheduler_.RegisterStream(7, SpdyStreamPrecedence(7)); + + // Make sure we don't yield when the list is empty. + EXPECT_FALSE(scheduler_.ShouldYield(1)); + + // Add a low priority stream. + scheduler_.MarkStreamReady(4, false); + // 4 should not yield to itself. + EXPECT_FALSE(scheduler_.ShouldYield(4)); + // 7 should yield as 4 is blocked and a higher priority. + EXPECT_TRUE(scheduler_.ShouldYield(7)); + // 5 should yield to 4 as they are the same priority. + EXPECT_TRUE(scheduler_.ShouldYield(5)); + // 1 should not yield as 1 is higher priority. + EXPECT_FALSE(scheduler_.ShouldYield(1)); + + // Add a second stream in that priority class. + scheduler_.MarkStreamReady(5, false); + // 4 and 5 are both blocked, but 4 is at the front so should not yield. + EXPECT_FALSE(scheduler_.ShouldYield(4)); + EXPECT_TRUE(scheduler_.ShouldYield(5)); +} + +TEST_F(PriorityWriteSchedulerTest, GetLatestEventWithPrecedence) { + EXPECT_SPDY_BUG(scheduler_.RecordStreamEventTime(3, 5), + "Stream 3 not registered"); + EXPECT_SPDY_BUG(EXPECT_EQ(0, scheduler_.GetLatestEventWithPrecedence(4)), + "Stream 4 not registered"); + + for (int i = 1; i < 5; ++i) { + scheduler_.RegisterStream(i, SpdyStreamPrecedence(i)); + } + for (int i = 1; i < 5; ++i) { + EXPECT_EQ(0, scheduler_.GetLatestEventWithPrecedence(i)); + } + for (int i = 1; i < 5; ++i) { + scheduler_.RecordStreamEventTime(i, i * 100); + } + for (int i = 1; i < 5; ++i) { + EXPECT_EQ((i - 1) * 100, scheduler_.GetLatestEventWithPrecedence(i)); + } +} + +} // namespace +} // namespace test +} // namespace spdy diff --git a/chromium/net/third_party/quiche/src/spdy/core/spdy_alt_svc_wire_format.cc b/chromium/net/third_party/quiche/src/spdy/core/spdy_alt_svc_wire_format.cc new file mode 100644 index 00000000000..15234a6e445 --- /dev/null +++ b/chromium/net/third_party/quiche/src/spdy/core/spdy_alt_svc_wire_format.cc @@ -0,0 +1,390 @@ +// Copyright (c) 2015 The Chromium 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/spdy/core/spdy_alt_svc_wire_format.h" + +#include <algorithm> +#include <cctype> +#include <limits> + +#include "base/logging.h" +#include "net/third_party/quiche/src/spdy/platform/api/spdy_string_utils.h" + +namespace spdy { + +namespace { + +template <class T> +bool ParsePositiveIntegerImpl(SpdyStringPiece::const_iterator c, + SpdyStringPiece::const_iterator end, + T* value) { + *value = 0; + for (; c != end && std::isdigit(*c); ++c) { + if (*value > std::numeric_limits<T>::max() / 10) { + return false; + } + *value *= 10; + if (*value > std::numeric_limits<T>::max() - (*c - '0')) { + return false; + } + *value += *c - '0'; + } + return (c == end && *value > 0); +} + +} // namespace + +SpdyAltSvcWireFormat::AlternativeService::AlternativeService() = default; + +SpdyAltSvcWireFormat::AlternativeService::AlternativeService( + const SpdyString& protocol_id, + const SpdyString& host, + uint16_t port, + uint32_t max_age, + VersionVector version) + : protocol_id(protocol_id), + host(host), + port(port), + max_age(max_age), + version(std::move(version)) {} + +SpdyAltSvcWireFormat::AlternativeService::~AlternativeService() = default; + +SpdyAltSvcWireFormat::AlternativeService::AlternativeService( + const AlternativeService& other) = default; + +// static +bool SpdyAltSvcWireFormat::ParseHeaderFieldValue( + SpdyStringPiece value, + AlternativeServiceVector* altsvc_vector) { + // Empty value is invalid according to the specification. + if (value.empty()) { + return false; + } + altsvc_vector->clear(); + if (value == SpdyStringPiece("clear")) { + return true; + } + SpdyStringPiece::const_iterator c = value.begin(); + while (c != value.end()) { + // Parse protocol-id. + SpdyStringPiece::const_iterator percent_encoded_protocol_id_end = + std::find(c, value.end(), '='); + SpdyString protocol_id; + if (percent_encoded_protocol_id_end == c || + !PercentDecode(c, percent_encoded_protocol_id_end, &protocol_id)) { + return false; + } + // Check for IETF format for advertising QUIC: + // hq=":443";quic=51303338;quic=51303334 + const bool is_ietf_format_quic = (protocol_id == "hq"); + c = percent_encoded_protocol_id_end; + if (c == value.end()) { + return false; + } + // Parse alt-authority. + DCHECK_EQ('=', *c); + ++c; + if (c == value.end() || *c != '"') { + return false; + } + ++c; + SpdyStringPiece::const_iterator alt_authority_begin = c; + for (; c != value.end() && *c != '"'; ++c) { + // Decode backslash encoding. + if (*c != '\\') { + continue; + } + ++c; + if (c == value.end()) { + return false; + } + } + if (c == alt_authority_begin || c == value.end()) { + return false; + } + DCHECK_EQ('"', *c); + SpdyString host; + uint16_t port; + if (!ParseAltAuthority(alt_authority_begin, c, &host, &port)) { + return false; + } + ++c; + // Parse parameters. + uint32_t max_age = 86400; + VersionVector version; + SpdyStringPiece::const_iterator parameters_end = + std::find(c, value.end(), ','); + while (c != parameters_end) { + SkipWhiteSpace(&c, parameters_end); + if (c == parameters_end) { + break; + } + if (*c != ';') { + return false; + } + ++c; + SkipWhiteSpace(&c, parameters_end); + if (c == parameters_end) { + break; + } + SpdyString parameter_name; + for (; c != parameters_end && *c != '=' && *c != ' ' && *c != '\t'; ++c) { + parameter_name.push_back(tolower(*c)); + } + SkipWhiteSpace(&c, parameters_end); + if (c == parameters_end || *c != '=') { + return false; + } + ++c; + SkipWhiteSpace(&c, parameters_end); + SpdyStringPiece::const_iterator parameter_value_begin = c; + for (; c != parameters_end && *c != ';' && *c != ' ' && *c != '\t'; ++c) { + } + if (c == parameter_value_begin) { + return false; + } + if (parameter_name == "ma") { + if (!ParsePositiveInteger32(parameter_value_begin, c, &max_age)) { + return false; + } + } else if (!is_ietf_format_quic && parameter_name == "v") { + // Version is a comma separated list of positive integers enclosed in + // quotation marks. Since it can contain commas, which are not + // delineating alternative service entries, |parameters_end| and |c| can + // be invalid. + if (*parameter_value_begin != '"') { + return false; + } + c = std::find(parameter_value_begin + 1, value.end(), '"'); + if (c == value.end()) { + return false; + } + ++c; + parameters_end = std::find(c, value.end(), ','); + SpdyStringPiece::const_iterator v_begin = parameter_value_begin + 1; + while (v_begin < c) { + SpdyStringPiece::const_iterator v_end = v_begin; + while (v_end < c - 1 && *v_end != ',') { + ++v_end; + } + uint16_t v; + if (!ParsePositiveInteger16(v_begin, v_end, &v)) { + return false; + } + version.push_back(v); + v_begin = v_end + 1; + if (v_begin == c - 1) { + // List ends in comma. + return false; + } + } + } else if (is_ietf_format_quic && parameter_name == "quic") { + // IETF format for advertising QUIC. Version is hex encoding of QUIC + // version tag. Hex-encoded string should not include leading "0x" or + // leading zeros. + // Example for advertising QUIC versions "Q038" and "Q034": + // hq=":443";quic=51303338;quic=51303334 + if (*parameter_value_begin == '0') { + return false; + } + // Versions will be stored as the uint32_t hex decoding of the param + // value string. Example: QUIC version "Q038", which is advertised as: + // hq=":443";quic=51303338 + // ... will be stored in |versions| as 0x51303338. + uint32_t quic_version; + if (!SpdyHexDecodeToUInt32(SpdyStringPiece(parameter_value_begin, + c - parameter_value_begin), + &quic_version) || + quic_version == 0) { + return false; + } + version.push_back(quic_version); + } + } + altsvc_vector->emplace_back(protocol_id, host, port, max_age, version); + for (; c != value.end() && (*c == ' ' || *c == '\t' || *c == ','); ++c) { + } + } + return true; +} + +// static +SpdyString SpdyAltSvcWireFormat::SerializeHeaderFieldValue( + const AlternativeServiceVector& altsvc_vector) { + if (altsvc_vector.empty()) { + return SpdyString("clear"); + } + const char kNibbleToHex[] = "0123456789ABCDEF"; + SpdyString value; + for (const AlternativeService& altsvc : altsvc_vector) { + if (!value.empty()) { + value.push_back(','); + } + // Check for IETF format for advertising QUIC. + const bool is_ietf_format_quic = (altsvc.protocol_id == "hq"); + // Percent escape protocol id according to + // http://tools.ietf.org/html/rfc7230#section-3.2.6. + for (char c : altsvc.protocol_id) { + if (isalnum(c)) { + value.push_back(c); + continue; + } + switch (c) { + case '!': + case '#': + case '$': + case '&': + case '\'': + case '*': + case '+': + case '-': + case '.': + case '^': + case '_': + case '`': + case '|': + case '~': + value.push_back(c); + break; + default: + value.push_back('%'); + // Network byte order is big-endian. + value.push_back(kNibbleToHex[c >> 4]); + value.push_back(kNibbleToHex[c & 0x0f]); + break; + } + } + value.push_back('='); + value.push_back('"'); + for (char c : altsvc.host) { + if (c == '"' || c == '\\') { + value.push_back('\\'); + } + value.push_back(c); + } + value.append(SpdyStrCat(":", altsvc.port, "\"")); + if (altsvc.max_age != 86400) { + value.append(SpdyStrCat("; ma=", altsvc.max_age)); + } + if (!altsvc.version.empty()) { + if (is_ietf_format_quic) { + for (uint32_t quic_version : altsvc.version) { + value.append("; quic="); + value.append(SpdyHexEncodeUInt32AndTrim(quic_version)); + } + } else { + value.append("; v=\""); + for (auto it = altsvc.version.begin(); it != altsvc.version.end(); + ++it) { + if (it != altsvc.version.begin()) { + value.append(","); + } + value.append(SpdyStrCat(*it)); + } + value.append("\""); + } + } + } + return value; +} + +// static +void SpdyAltSvcWireFormat::SkipWhiteSpace(SpdyStringPiece::const_iterator* c, + SpdyStringPiece::const_iterator end) { + for (; *c != end && (**c == ' ' || **c == '\t'); ++*c) { + } +} + +// static +bool SpdyAltSvcWireFormat::PercentDecode(SpdyStringPiece::const_iterator c, + SpdyStringPiece::const_iterator end, + SpdyString* output) { + output->clear(); + for (; c != end; ++c) { + if (*c != '%') { + output->push_back(*c); + continue; + } + DCHECK_EQ('%', *c); + ++c; + if (c == end || !std::isxdigit(*c)) { + return false; + } + // Network byte order is big-endian. + char decoded = SpdyHexDigitToInt(*c) << 4; + ++c; + if (c == end || !std::isxdigit(*c)) { + return false; + } + decoded += SpdyHexDigitToInt(*c); + output->push_back(decoded); + } + return true; +} + +// static +bool SpdyAltSvcWireFormat::ParseAltAuthority( + SpdyStringPiece::const_iterator c, + SpdyStringPiece::const_iterator end, + SpdyString* host, + uint16_t* port) { + host->clear(); + if (c == end) { + return false; + } + if (*c == '[') { + for (; c != end && *c != ']'; ++c) { + if (*c == '"') { + // Port is mandatory. + return false; + } + host->push_back(*c); + } + if (c == end) { + return false; + } + DCHECK_EQ(']', *c); + host->push_back(*c); + ++c; + } else { + for (; c != end && *c != ':'; ++c) { + if (*c == '"') { + // Port is mandatory. + return false; + } + if (*c == '\\') { + ++c; + if (c == end) { + return false; + } + } + host->push_back(*c); + } + } + if (c == end || *c != ':') { + return false; + } + DCHECK_EQ(':', *c); + ++c; + return ParsePositiveInteger16(c, end, port); +} + +// static +bool SpdyAltSvcWireFormat::ParsePositiveInteger16( + SpdyStringPiece::const_iterator c, + SpdyStringPiece::const_iterator end, + uint16_t* value) { + return ParsePositiveIntegerImpl<uint16_t>(c, end, value); +} + +// static +bool SpdyAltSvcWireFormat::ParsePositiveInteger32( + SpdyStringPiece::const_iterator c, + SpdyStringPiece::const_iterator end, + uint32_t* value) { + return ParsePositiveIntegerImpl<uint32_t>(c, end, value); +} + +} // namespace spdy diff --git a/chromium/net/third_party/quiche/src/spdy/core/spdy_alt_svc_wire_format.h b/chromium/net/third_party/quiche/src/spdy/core/spdy_alt_svc_wire_format.h new file mode 100644 index 00000000000..ac834bc851a --- /dev/null +++ b/chromium/net/third_party/quiche/src/spdy/core/spdy_alt_svc_wire_format.h @@ -0,0 +1,88 @@ +// Copyright (c) 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file contains data structures and utility functions used for serializing +// and parsing alternative service header values, common to HTTP/1.1 header +// fields and HTTP/2 and QUIC ALTSVC frames. See specification at +// https://httpwg.github.io/http-extensions/alt-svc.html. + +#ifndef QUICHE_SPDY_CORE_SPDY_ALT_SVC_WIRE_FORMAT_H_ +#define QUICHE_SPDY_CORE_SPDY_ALT_SVC_WIRE_FORMAT_H_ + +#include <cstdint> +#include <vector> + +#include "net/third_party/quiche/src/spdy/platform/api/spdy_containers.h" +#include "net/third_party/quiche/src/spdy/platform/api/spdy_export.h" +#include "net/third_party/quiche/src/spdy/platform/api/spdy_string.h" +#include "net/third_party/quiche/src/spdy/platform/api/spdy_string_piece.h" + +namespace spdy { + +namespace test { +class SpdyAltSvcWireFormatPeer; +} // namespace test + +class SPDY_EXPORT_PRIVATE SpdyAltSvcWireFormat { + public: + using VersionVector = SpdyInlinedVector<uint32_t, 8>; + + struct SPDY_EXPORT_PRIVATE AlternativeService { + SpdyString protocol_id; + SpdyString host; + + // Default is 0: invalid port. + uint16_t port = 0; + // Default is one day. + uint32_t max_age = 86400; + // Default is empty: unspecified version. + VersionVector version; + + AlternativeService(); + AlternativeService(const SpdyString& protocol_id, + const SpdyString& host, + uint16_t port, + uint32_t max_age, + VersionVector version); + AlternativeService(const AlternativeService& other); + ~AlternativeService(); + + bool operator==(const AlternativeService& other) const { + return protocol_id == other.protocol_id && host == other.host && + port == other.port && version == other.version && + max_age == other.max_age; + } + }; + // An empty vector means alternative services should be cleared for given + // origin. Note that the wire format for this is the string "clear", not an + // empty value (which is invalid). + typedef std::vector<AlternativeService> AlternativeServiceVector; + + friend class test::SpdyAltSvcWireFormatPeer; + static bool ParseHeaderFieldValue(SpdyStringPiece value, + AlternativeServiceVector* altsvc_vector); + static SpdyString SerializeHeaderFieldValue( + const AlternativeServiceVector& altsvc_vector); + + private: + static void SkipWhiteSpace(SpdyStringPiece::const_iterator* c, + SpdyStringPiece::const_iterator end); + static bool PercentDecode(SpdyStringPiece::const_iterator c, + SpdyStringPiece::const_iterator end, + SpdyString* output); + static bool ParseAltAuthority(SpdyStringPiece::const_iterator c, + SpdyStringPiece::const_iterator end, + SpdyString* host, + uint16_t* port); + static bool ParsePositiveInteger16(SpdyStringPiece::const_iterator c, + SpdyStringPiece::const_iterator end, + uint16_t* value); + static bool ParsePositiveInteger32(SpdyStringPiece::const_iterator c, + SpdyStringPiece::const_iterator end, + uint32_t* value); +}; + +} // namespace spdy + +#endif // QUICHE_SPDY_CORE_SPDY_ALT_SVC_WIRE_FORMAT_H_ diff --git a/chromium/net/third_party/quiche/src/spdy/core/spdy_alt_svc_wire_format_test.cc b/chromium/net/third_party/quiche/src/spdy/core/spdy_alt_svc_wire_format_test.cc new file mode 100644 index 00000000000..c2595f0b4ec --- /dev/null +++ b/chromium/net/third_party/quiche/src/spdy/core/spdy_alt_svc_wire_format_test.cc @@ -0,0 +1,573 @@ +// Copyright (c) 2015 The Chromium 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/spdy/core/spdy_alt_svc_wire_format.h" + +#include "base/logging.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace spdy { + +namespace test { + +// Expose all private methods of class SpdyAltSvcWireFormat. +class SpdyAltSvcWireFormatPeer { + public: + static void SkipWhiteSpace(SpdyStringPiece::const_iterator* c, + SpdyStringPiece::const_iterator end) { + SpdyAltSvcWireFormat::SkipWhiteSpace(c, end); + } + static bool PercentDecode(SpdyStringPiece::const_iterator c, + SpdyStringPiece::const_iterator end, + SpdyString* output) { + return SpdyAltSvcWireFormat::PercentDecode(c, end, output); + } + static bool ParseAltAuthority(SpdyStringPiece::const_iterator c, + SpdyStringPiece::const_iterator end, + SpdyString* host, + uint16_t* port) { + return SpdyAltSvcWireFormat::ParseAltAuthority(c, end, host, port); + } + static bool ParsePositiveInteger16(SpdyStringPiece::const_iterator c, + SpdyStringPiece::const_iterator end, + uint16_t* max_age) { + return SpdyAltSvcWireFormat::ParsePositiveInteger16(c, end, max_age); + } + static bool ParsePositiveInteger32(SpdyStringPiece::const_iterator c, + SpdyStringPiece::const_iterator end, + uint32_t* max_age) { + return SpdyAltSvcWireFormat::ParsePositiveInteger32(c, end, max_age); + } +}; + +} // namespace test + +namespace { + +// Generate header field values, possibly with multiply defined parameters and +// random case, and corresponding AlternativeService entries. +void FuzzHeaderFieldValue( + int i, + SpdyString* header_field_value, + SpdyAltSvcWireFormat::AlternativeService* expected_altsvc) { + if (!header_field_value->empty()) { + header_field_value->push_back(','); + } + // TODO(b/77515496): use struct of bools instead of int |i| to generate the + // header field value. + bool is_ietf_format_quic = (i & 1 << 0) != 0; + if (i & 1 << 0) { + expected_altsvc->protocol_id = "hq"; + header_field_value->append("hq=\""); + } else { + expected_altsvc->protocol_id = "a=b%c"; + header_field_value->append("a%3Db%25c=\""); + } + if (i & 1 << 1) { + expected_altsvc->host = "foo\"bar\\baz"; + header_field_value->append("foo\\\"bar\\\\baz"); + } else { + expected_altsvc->host = ""; + } + expected_altsvc->port = 42; + header_field_value->append(":42\""); + if (i & 1 << 2) { + header_field_value->append(" "); + } + if (i & 3 << 3) { + expected_altsvc->max_age = 1111; + header_field_value->append(";"); + if (i & 1 << 3) { + header_field_value->append(" "); + } + header_field_value->append("mA=1111"); + if (i & 2 << 3) { + header_field_value->append(" "); + } + } + if (i & 1 << 5) { + header_field_value->append("; J=s"); + } + if (i & 1 << 6) { + if (is_ietf_format_quic) { + if (i & 1 << 7) { + expected_altsvc->version.push_back(0x923457e); + header_field_value->append("; quic=923457E"); + } else { + expected_altsvc->version.push_back(1); + expected_altsvc->version.push_back(0xFFFFFFFF); + header_field_value->append("; quic=1; quic=fFfFffFf"); + } + } else { + if (i & i << 7) { + expected_altsvc->version.push_back(24); + header_field_value->append("; v=\"24\""); + } else { + expected_altsvc->version.push_back(1); + expected_altsvc->version.push_back(65535); + header_field_value->append("; v=\"1,65535\""); + } + } + } + if (i & 1 << 8) { + expected_altsvc->max_age = 999999999; + header_field_value->append("; Ma=999999999"); + } + if (i & 1 << 9) { + header_field_value->append(";"); + } + if (i & 1 << 10) { + header_field_value->append(" "); + } + if (i & 1 << 11) { + header_field_value->append(","); + } + if (i & 1 << 12) { + header_field_value->append(" "); + } +} + +// Generate AlternativeService entries and corresponding header field values in +// canonical form, that is, what SerializeHeaderFieldValue() should output. +void FuzzAlternativeService(int i, + SpdyAltSvcWireFormat::AlternativeService* altsvc, + SpdyString* expected_header_field_value) { + if (!expected_header_field_value->empty()) { + expected_header_field_value->push_back(','); + } + altsvc->protocol_id = "a=b%c"; + altsvc->port = 42; + expected_header_field_value->append("a%3Db%25c=\""); + if (i & 1 << 0) { + altsvc->host = "foo\"bar\\baz"; + expected_header_field_value->append("foo\\\"bar\\\\baz"); + } + expected_header_field_value->append(":42\""); + if (i & 1 << 1) { + altsvc->max_age = 1111; + expected_header_field_value->append("; ma=1111"); + } + if (i & 1 << 2) { + altsvc->version.push_back(24); + altsvc->version.push_back(25); + expected_header_field_value->append("; v=\"24,25\""); + } +} + +// Tests of public API. + +TEST(SpdyAltSvcWireFormatTest, DefaultValues) { + SpdyAltSvcWireFormat::AlternativeService altsvc; + EXPECT_EQ("", altsvc.protocol_id); + EXPECT_EQ("", altsvc.host); + EXPECT_EQ(0u, altsvc.port); + EXPECT_EQ(86400u, altsvc.max_age); + EXPECT_TRUE(altsvc.version.empty()); +} + +TEST(SpdyAltSvcWireFormatTest, ParseInvalidEmptyHeaderFieldValue) { + SpdyAltSvcWireFormat::AlternativeServiceVector altsvc_vector; + ASSERT_FALSE(SpdyAltSvcWireFormat::ParseHeaderFieldValue("", &altsvc_vector)); +} + +TEST(SpdyAltSvcWireFormatTest, ParseHeaderFieldValueClear) { + SpdyAltSvcWireFormat::AlternativeServiceVector altsvc_vector; + ASSERT_TRUE( + SpdyAltSvcWireFormat::ParseHeaderFieldValue("clear", &altsvc_vector)); + EXPECT_EQ(0u, altsvc_vector.size()); +} + +// Fuzz test of ParseHeaderFieldValue() with optional whitespaces, ignored +// parameters, duplicate parameters, trailing space, trailing alternate service +// separator, etc. Single alternative service at a time. +TEST(SpdyAltSvcWireFormatTest, ParseHeaderFieldValue) { + for (int i = 0; i < 1 << 13; ++i) { + SpdyString header_field_value; + SpdyAltSvcWireFormat::AlternativeService expected_altsvc; + FuzzHeaderFieldValue(i, &header_field_value, &expected_altsvc); + SpdyAltSvcWireFormat::AlternativeServiceVector altsvc_vector; + ASSERT_TRUE(SpdyAltSvcWireFormat::ParseHeaderFieldValue(header_field_value, + &altsvc_vector)); + ASSERT_EQ(1u, altsvc_vector.size()); + EXPECT_EQ(expected_altsvc.protocol_id, altsvc_vector[0].protocol_id); + EXPECT_EQ(expected_altsvc.host, altsvc_vector[0].host); + EXPECT_EQ(expected_altsvc.port, altsvc_vector[0].port); + EXPECT_EQ(expected_altsvc.max_age, altsvc_vector[0].max_age); + EXPECT_EQ(expected_altsvc.version, altsvc_vector[0].version); + + // Roundtrip test starting with |altsvc_vector|. + SpdyString reserialized_header_field_value = + SpdyAltSvcWireFormat::SerializeHeaderFieldValue(altsvc_vector); + SpdyAltSvcWireFormat::AlternativeServiceVector roundtrip_altsvc_vector; + ASSERT_TRUE(SpdyAltSvcWireFormat::ParseHeaderFieldValue( + reserialized_header_field_value, &roundtrip_altsvc_vector)); + ASSERT_EQ(1u, roundtrip_altsvc_vector.size()); + EXPECT_EQ(expected_altsvc.protocol_id, + roundtrip_altsvc_vector[0].protocol_id); + EXPECT_EQ(expected_altsvc.host, roundtrip_altsvc_vector[0].host); + EXPECT_EQ(expected_altsvc.port, roundtrip_altsvc_vector[0].port); + EXPECT_EQ(expected_altsvc.max_age, roundtrip_altsvc_vector[0].max_age); + EXPECT_EQ(expected_altsvc.version, roundtrip_altsvc_vector[0].version); + } +} + +// Fuzz test of ParseHeaderFieldValue() with optional whitespaces, ignored +// parameters, duplicate parameters, trailing space, trailing alternate service +// separator, etc. Possibly multiple alternative service at a time. +TEST(SpdyAltSvcWireFormatTest, ParseHeaderFieldValueMultiple) { + for (int i = 0; i < 1 << 13;) { + SpdyString header_field_value; + SpdyAltSvcWireFormat::AlternativeServiceVector expected_altsvc_vector; + // This will generate almost two hundred header field values with two, + // three, four, five, six, and seven alternative services each, and + // thousands with a single one. + do { + SpdyAltSvcWireFormat::AlternativeService expected_altsvc; + FuzzHeaderFieldValue(i, &header_field_value, &expected_altsvc); + expected_altsvc_vector.push_back(expected_altsvc); + ++i; + } while (i % 6 < i % 7); + SpdyAltSvcWireFormat::AlternativeServiceVector altsvc_vector; + ASSERT_TRUE(SpdyAltSvcWireFormat::ParseHeaderFieldValue(header_field_value, + &altsvc_vector)); + ASSERT_EQ(expected_altsvc_vector.size(), altsvc_vector.size()); + for (unsigned int j = 0; j < altsvc_vector.size(); ++j) { + EXPECT_EQ(expected_altsvc_vector[j].protocol_id, + altsvc_vector[j].protocol_id); + EXPECT_EQ(expected_altsvc_vector[j].host, altsvc_vector[j].host); + EXPECT_EQ(expected_altsvc_vector[j].port, altsvc_vector[j].port); + EXPECT_EQ(expected_altsvc_vector[j].max_age, altsvc_vector[j].max_age); + EXPECT_EQ(expected_altsvc_vector[j].version, altsvc_vector[j].version); + } + + // Roundtrip test starting with |altsvc_vector|. + SpdyString reserialized_header_field_value = + SpdyAltSvcWireFormat::SerializeHeaderFieldValue(altsvc_vector); + SpdyAltSvcWireFormat::AlternativeServiceVector roundtrip_altsvc_vector; + ASSERT_TRUE(SpdyAltSvcWireFormat::ParseHeaderFieldValue( + reserialized_header_field_value, &roundtrip_altsvc_vector)); + ASSERT_EQ(expected_altsvc_vector.size(), roundtrip_altsvc_vector.size()); + for (unsigned int j = 0; j < roundtrip_altsvc_vector.size(); ++j) { + EXPECT_EQ(expected_altsvc_vector[j].protocol_id, + roundtrip_altsvc_vector[j].protocol_id); + EXPECT_EQ(expected_altsvc_vector[j].host, + roundtrip_altsvc_vector[j].host); + EXPECT_EQ(expected_altsvc_vector[j].port, + roundtrip_altsvc_vector[j].port); + EXPECT_EQ(expected_altsvc_vector[j].max_age, + roundtrip_altsvc_vector[j].max_age); + EXPECT_EQ(expected_altsvc_vector[j].version, + roundtrip_altsvc_vector[j].version); + } + } +} + +TEST(SpdyAltSvcWireFormatTest, SerializeEmptyHeaderFieldValue) { + SpdyAltSvcWireFormat::AlternativeServiceVector altsvc_vector; + EXPECT_EQ("clear", + SpdyAltSvcWireFormat::SerializeHeaderFieldValue(altsvc_vector)); +} + +// Test ParseHeaderFieldValue() and SerializeHeaderFieldValue() on the same pair +// of |expected_header_field_value| and |altsvc|, with and without hostname and +// each +// parameter. Single alternative service at a time. +TEST(SpdyAltSvcWireFormatTest, RoundTrip) { + for (int i = 0; i < 1 << 3; ++i) { + SpdyAltSvcWireFormat::AlternativeService altsvc; + SpdyString expected_header_field_value; + FuzzAlternativeService(i, &altsvc, &expected_header_field_value); + + // Test ParseHeaderFieldValue(). + SpdyAltSvcWireFormat::AlternativeServiceVector parsed_altsvc_vector; + ASSERT_TRUE(SpdyAltSvcWireFormat::ParseHeaderFieldValue( + expected_header_field_value, &parsed_altsvc_vector)); + ASSERT_EQ(1u, parsed_altsvc_vector.size()); + EXPECT_EQ(altsvc.protocol_id, parsed_altsvc_vector[0].protocol_id); + EXPECT_EQ(altsvc.host, parsed_altsvc_vector[0].host); + EXPECT_EQ(altsvc.port, parsed_altsvc_vector[0].port); + EXPECT_EQ(altsvc.max_age, parsed_altsvc_vector[0].max_age); + EXPECT_EQ(altsvc.version, parsed_altsvc_vector[0].version); + + // Test SerializeHeaderFieldValue(). + SpdyAltSvcWireFormat::AlternativeServiceVector altsvc_vector; + altsvc_vector.push_back(altsvc); + EXPECT_EQ(expected_header_field_value, + SpdyAltSvcWireFormat::SerializeHeaderFieldValue(altsvc_vector)); + } +} + +// Test ParseHeaderFieldValue() and SerializeHeaderFieldValue() on the same pair +// of |expected_header_field_value| and |altsvc|, with and without hostname and +// each +// parameter. Multiple alternative services at a time. +TEST(SpdyAltSvcWireFormatTest, RoundTripMultiple) { + SpdyAltSvcWireFormat::AlternativeServiceVector altsvc_vector; + SpdyString expected_header_field_value; + for (int i = 0; i < 1 << 3; ++i) { + SpdyAltSvcWireFormat::AlternativeService altsvc; + FuzzAlternativeService(i, &altsvc, &expected_header_field_value); + altsvc_vector.push_back(altsvc); + } + + // Test ParseHeaderFieldValue(). + SpdyAltSvcWireFormat::AlternativeServiceVector parsed_altsvc_vector; + ASSERT_TRUE(SpdyAltSvcWireFormat::ParseHeaderFieldValue( + expected_header_field_value, &parsed_altsvc_vector)); + ASSERT_EQ(altsvc_vector.size(), parsed_altsvc_vector.size()); + auto expected_it = altsvc_vector.begin(); + auto parsed_it = parsed_altsvc_vector.begin(); + for (; expected_it != altsvc_vector.end(); ++expected_it, ++parsed_it) { + EXPECT_EQ(expected_it->protocol_id, parsed_it->protocol_id); + EXPECT_EQ(expected_it->host, parsed_it->host); + EXPECT_EQ(expected_it->port, parsed_it->port); + EXPECT_EQ(expected_it->max_age, parsed_it->max_age); + EXPECT_EQ(expected_it->version, parsed_it->version); + } + + // Test SerializeHeaderFieldValue(). + EXPECT_EQ(expected_header_field_value, + SpdyAltSvcWireFormat::SerializeHeaderFieldValue(altsvc_vector)); +} + +// ParseHeaderFieldValue() should return false on malformed field values: +// invalid percent encoding, unmatched quotation mark, empty port, non-numeric +// characters in numeric fields. +TEST(SpdyAltSvcWireFormatTest, ParseHeaderFieldValueInvalid) { + SpdyAltSvcWireFormat::AlternativeServiceVector altsvc_vector; + const char* invalid_field_value_array[] = {"a%", + "a%x", + "a%b", + "a%9z", + "a=", + "a=\"", + "a=\"b\"", + "a=\":\"", + "a=\"c:\"", + "a=\"c:foo\"", + "a=\"c:42foo\"", + "a=\"b:42\"bar", + "a=\"b:42\" ; m", + "a=\"b:42\" ; min-age", + "a=\"b:42\" ; ma", + "a=\"b:42\" ; ma=", + "a=\"b:42\" ; v=\"..\"", + "a=\"b:42\" ; ma=ma", + "a=\"b:42\" ; ma=123bar", + "a=\"b:42\" ; v=24", + "a=\"b:42\" ; v=24,25", + "a=\"b:42\" ; v=\"-3\"", + "a=\"b:42\" ; v=\"1.2\"", + "a=\"b:42\" ; v=\"24,\""}; + for (const char* invalid_field_value : invalid_field_value_array) { + EXPECT_FALSE(SpdyAltSvcWireFormat::ParseHeaderFieldValue( + invalid_field_value, &altsvc_vector)) + << invalid_field_value; + } +} + +// ParseHeaderFieldValue() should return false on a field values truncated +// before closing quotation mark, without trying to access memory beyond the end +// of the input. +TEST(SpdyAltSvcWireFormatTest, ParseTruncatedHeaderFieldValue) { + SpdyAltSvcWireFormat::AlternativeServiceVector altsvc_vector; + const char* field_value_array[] = {"a=\":137\"", "a=\"foo:137\"", + "a%25=\"foo\\\"bar\\\\baz:137\""}; + for (const SpdyString& field_value : field_value_array) { + for (size_t len = 1; len < field_value.size(); ++len) { + EXPECT_FALSE(SpdyAltSvcWireFormat::ParseHeaderFieldValue( + field_value.substr(0, len), &altsvc_vector)) + << len; + } + } +} + +// Tests of private methods. + +// Test SkipWhiteSpace(). +TEST(SpdyAltSvcWireFormatTest, SkipWhiteSpace) { + SpdyStringPiece input("a \tb "); + SpdyStringPiece::const_iterator c = input.begin(); + test::SpdyAltSvcWireFormatPeer::SkipWhiteSpace(&c, input.end()); + ASSERT_EQ(input.begin(), c); + ++c; + test::SpdyAltSvcWireFormatPeer::SkipWhiteSpace(&c, input.end()); + ASSERT_EQ(input.begin() + 3, c); + ++c; + test::SpdyAltSvcWireFormatPeer::SkipWhiteSpace(&c, input.end()); + ASSERT_EQ(input.end(), c); +} + +// Test PercentDecode() on valid input. +TEST(SpdyAltSvcWireFormatTest, PercentDecodeValid) { + SpdyStringPiece input(""); + SpdyString output; + ASSERT_TRUE(test::SpdyAltSvcWireFormatPeer::PercentDecode( + input.begin(), input.end(), &output)); + EXPECT_EQ("", output); + + input = SpdyStringPiece("foo"); + output.clear(); + ASSERT_TRUE(test::SpdyAltSvcWireFormatPeer::PercentDecode( + input.begin(), input.end(), &output)); + EXPECT_EQ("foo", output); + + input = SpdyStringPiece("%2ca%5Cb"); + output.clear(); + ASSERT_TRUE(test::SpdyAltSvcWireFormatPeer::PercentDecode( + input.begin(), input.end(), &output)); + EXPECT_EQ(",a\\b", output); +} + +// Test PercentDecode() on invalid input. +TEST(SpdyAltSvcWireFormatTest, PercentDecodeInvalid) { + const char* invalid_input_array[] = {"a%", "a%x", "a%b", "%J22", "%9z"}; + for (const char* invalid_input : invalid_input_array) { + SpdyStringPiece input(invalid_input); + SpdyString output; + EXPECT_FALSE(test::SpdyAltSvcWireFormatPeer::PercentDecode( + input.begin(), input.end(), &output)) + << input; + } +} + +// Test ParseAltAuthority() on valid input. +TEST(SpdyAltSvcWireFormatTest, ParseAltAuthorityValid) { + SpdyStringPiece input(":42"); + SpdyString host; + uint16_t port; + ASSERT_TRUE(test::SpdyAltSvcWireFormatPeer::ParseAltAuthority( + input.begin(), input.end(), &host, &port)); + EXPECT_TRUE(host.empty()); + EXPECT_EQ(42, port); + + input = SpdyStringPiece("foo:137"); + ASSERT_TRUE(test::SpdyAltSvcWireFormatPeer::ParseAltAuthority( + input.begin(), input.end(), &host, &port)); + EXPECT_EQ("foo", host); + EXPECT_EQ(137, port); + + input = SpdyStringPiece("[2003:8:0:16::509d:9615]:443"); + ASSERT_TRUE(test::SpdyAltSvcWireFormatPeer::ParseAltAuthority( + input.begin(), input.end(), &host, &port)); + EXPECT_EQ("[2003:8:0:16::509d:9615]", host); + EXPECT_EQ(443, port); +} + +// Test ParseAltAuthority() on invalid input: empty string, no port, zero port, +// non-digit characters following port. +TEST(SpdyAltSvcWireFormatTest, ParseAltAuthorityInvalid) { + const char* invalid_input_array[] = {"", + ":", + "foo:", + ":bar", + ":0", + "foo:0", + ":12bar", + "foo:23bar", + " ", + ":12 ", + "foo:12 ", + "[2003:8:0:16::509d:9615]", + "[2003:8:0:16::509d:9615]:", + "[2003:8:0:16::509d:9615]foo:443", + "[2003:8:0:16::509d:9615:443", + "2003:8:0:16::509d:9615]:443"}; + for (const char* invalid_input : invalid_input_array) { + SpdyStringPiece input(invalid_input); + SpdyString host; + uint16_t port; + EXPECT_FALSE(test::SpdyAltSvcWireFormatPeer::ParseAltAuthority( + input.begin(), input.end(), &host, &port)) + << input; + } +} + +// Test ParseInteger() on valid input. +TEST(SpdyAltSvcWireFormatTest, ParseIntegerValid) { + SpdyStringPiece input("3"); + uint16_t value; + ASSERT_TRUE(test::SpdyAltSvcWireFormatPeer::ParsePositiveInteger16( + input.begin(), input.end(), &value)); + EXPECT_EQ(3, value); + + input = SpdyStringPiece("1337"); + ASSERT_TRUE(test::SpdyAltSvcWireFormatPeer::ParsePositiveInteger16( + input.begin(), input.end(), &value)); + EXPECT_EQ(1337, value); +} + +// Test ParseIntegerValid() on invalid input: empty, zero, non-numeric, trailing +// non-numeric characters. +TEST(SpdyAltSvcWireFormatTest, ParseIntegerInvalid) { + const char* invalid_input_array[] = {"", " ", "a", "0", "00", "1 ", "12b"}; + for (const char* invalid_input : invalid_input_array) { + SpdyStringPiece input(invalid_input); + uint16_t value; + EXPECT_FALSE(test::SpdyAltSvcWireFormatPeer::ParsePositiveInteger16( + input.begin(), input.end(), &value)) + << input; + } +} + +// Test ParseIntegerValid() around overflow limit. +TEST(SpdyAltSvcWireFormatTest, ParseIntegerOverflow) { + // Largest possible uint16_t value. + SpdyStringPiece input("65535"); + uint16_t value16; + ASSERT_TRUE(test::SpdyAltSvcWireFormatPeer::ParsePositiveInteger16( + input.begin(), input.end(), &value16)); + EXPECT_EQ(65535, value16); + + // Overflow uint16_t, ParsePositiveInteger16() should return false. + input = SpdyStringPiece("65536"); + ASSERT_FALSE(test::SpdyAltSvcWireFormatPeer::ParsePositiveInteger16( + input.begin(), input.end(), &value16)); + + // However, even if overflow is not checked for, 65536 overflows to 0, which + // returns false anyway. Check for a larger number which overflows to 1. + input = SpdyStringPiece("65537"); + ASSERT_FALSE(test::SpdyAltSvcWireFormatPeer::ParsePositiveInteger16( + input.begin(), input.end(), &value16)); + + // Largest possible uint32_t value. + input = SpdyStringPiece("4294967295"); + uint32_t value32; + ASSERT_TRUE(test::SpdyAltSvcWireFormatPeer::ParsePositiveInteger32( + input.begin(), input.end(), &value32)); + EXPECT_EQ(4294967295, value32); + + // Overflow uint32_t, ParsePositiveInteger32() should return false. + input = SpdyStringPiece("4294967296"); + ASSERT_FALSE(test::SpdyAltSvcWireFormatPeer::ParsePositiveInteger32( + input.begin(), input.end(), &value32)); + + // However, even if overflow is not checked for, 4294967296 overflows to 0, + // which returns false anyway. Check for a larger number which overflows to + // 1. + input = SpdyStringPiece("4294967297"); + ASSERT_FALSE(test::SpdyAltSvcWireFormatPeer::ParsePositiveInteger32( + input.begin(), input.end(), &value32)); +} + +// Test parsing an Alt-Svc entry with IP literal hostname. +// Regression test for https://crbug.com/664173. +TEST(SpdyAltSvcWireFormatTest, ParseIPLiteral) { + const char* input = + "quic=\"[2003:8:0:16::509d:9615]:443\"; v=\"36,35\"; ma=60"; + SpdyAltSvcWireFormat::AlternativeServiceVector altsvc_vector; + ASSERT_TRUE( + SpdyAltSvcWireFormat::ParseHeaderFieldValue(input, &altsvc_vector)); + EXPECT_EQ(1u, altsvc_vector.size()); + EXPECT_EQ("quic", altsvc_vector[0].protocol_id); + EXPECT_EQ("[2003:8:0:16::509d:9615]", altsvc_vector[0].host); + EXPECT_EQ(443u, altsvc_vector[0].port); + EXPECT_EQ(60u, altsvc_vector[0].max_age); + EXPECT_THAT(altsvc_vector[0].version, ::testing::ElementsAre(36, 35)); +} + +} // namespace + +} // namespace spdy diff --git a/chromium/net/third_party/quiche/src/spdy/core/spdy_bitmasks.h b/chromium/net/third_party/quiche/src/spdy/core/spdy_bitmasks.h new file mode 100644 index 00000000000..657bd1761e9 --- /dev/null +++ b/chromium/net/third_party/quiche/src/spdy/core/spdy_bitmasks.h @@ -0,0 +1,18 @@ +// Copyright (c) 2012 The Chromium 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_SPDY_CORE_SPDY_BITMASKS_H_ +#define QUICHE_SPDY_CORE_SPDY_BITMASKS_H_ + +namespace spdy { + +// StreamId mask from the SpdyHeader +const unsigned int kStreamIdMask = 0x7fffffff; + +// Mask the lower 24 bits. +const unsigned int kLengthMask = 0xffffff; + +} // namespace spdy + +#endif // QUICHE_SPDY_CORE_SPDY_BITMASKS_H_ diff --git a/chromium/net/third_party/quiche/src/spdy/core/spdy_deframer_visitor.cc b/chromium/net/third_party/quiche/src/spdy/core/spdy_deframer_visitor.cc new file mode 100644 index 00000000000..76188e9ce8b --- /dev/null +++ b/chromium/net/third_party/quiche/src/spdy/core/spdy_deframer_visitor.cc @@ -0,0 +1,1028 @@ +// Copyright 2016 The Chromium 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/spdy/core/spdy_deframer_visitor.h" + +#include <stdlib.h> + +#include <algorithm> +#include <cstdint> +#include <limits> +#include <memory> + +#include "base/logging.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_macros.h" +#include "net/third_party/quiche/src/spdy/core/hpack/hpack_constants.h" +#include "net/third_party/quiche/src/spdy/core/mock_spdy_framer_visitor.h" +#include "net/third_party/quiche/src/spdy/core/spdy_frame_reader.h" +#include "net/third_party/quiche/src/spdy/core/spdy_protocol.h" +#include "net/third_party/quiche/src/spdy/core/spdy_test_utils.h" +#include "net/third_party/quiche/src/spdy/platform/api/spdy_flags.h" +#include "net/third_party/quiche/src/spdy/platform/api/spdy_ptr_util.h" +#include "net/third_party/quiche/src/spdy/platform/api/spdy_string_piece.h" + +using ::testing::AssertionFailure; +using ::testing::AssertionResult; +using ::testing::AssertionSuccess; + +namespace spdy { +namespace test { + +// Specify whether to process headers as request or response in visitor-related +// params. +enum class HeaderDirection { REQUEST, RESPONSE }; + +// Types of HTTP/2 frames, per RFC 7540. +// TODO(jamessynge): Switch to using http2/http2_constants.h when ready. +enum Http2FrameType { + DATA = 0, + HEADERS = 1, + PRIORITY = 2, + RST_STREAM = 3, + SETTINGS = 4, + PUSH_PROMISE = 5, + PING = 6, + GOAWAY = 7, + WINDOW_UPDATE = 8, + CONTINUATION = 9, + ALTSVC = 10, + + // Not a frame type. + UNSET = -1, + UNKNOWN = -2, +}; + +// TODO(jamessynge): Switch to using http2/http2_constants.h when ready. +const char* Http2FrameTypeToString(Http2FrameType v) { + switch (v) { + case DATA: + return "DATA"; + case HEADERS: + return "HEADERS"; + case PRIORITY: + return "PRIORITY"; + case RST_STREAM: + return "RST_STREAM"; + case SETTINGS: + return "SETTINGS"; + case PUSH_PROMISE: + return "PUSH_PROMISE"; + case PING: + return "PING"; + case GOAWAY: + return "GOAWAY"; + case WINDOW_UPDATE: + return "WINDOW_UPDATE"; + case CONTINUATION: + return "CONTINUATION"; + case ALTSVC: + return "ALTSVC"; + case UNSET: + return "UNSET"; + case UNKNOWN: + return "UNKNOWN"; + default: + return "Invalid Http2FrameType"; + } +} + +// TODO(jamessynge): Switch to using http2/http2_constants.h when ready. +inline std::ostream& operator<<(std::ostream& out, Http2FrameType v) { + return out << Http2FrameTypeToString(v); +} + +// Flag bits in the flag field of the common header of HTTP/2 frames +// (see https://httpwg.github.io/specs/rfc7540.html#FrameHeader for details on +// the fixed 9-octet header structure shared by all frames). +// Flag bits are only valid for specified frame types. +// TODO(jamessynge): Switch to using http2/http2_constants.h when ready. +enum Http2HeaderFlag { + NO_FLAGS = 0, + + END_STREAM_FLAG = 0x1, + ACK_FLAG = 0x1, + END_HEADERS_FLAG = 0x4, + PADDED_FLAG = 0x8, + PRIORITY_FLAG = 0x20, +}; + +// Returns name of frame type. +// TODO(jamessynge): Switch to using http2/http2_constants.h when ready. +const char* Http2FrameTypeToString(Http2FrameType v); + +void SpdyDeframerVisitorInterface::OnPingAck( + std::unique_ptr<SpdyPingIR> frame) { + OnPing(std::move(frame)); +} + +void SpdyDeframerVisitorInterface::OnSettingsAck( + std::unique_ptr<SpdySettingsIR> frame) { + OnSettings(std::move(frame), nullptr); +} + +class SpdyTestDeframerImpl : public SpdyTestDeframer, + public SpdyHeadersHandlerInterface { + public: + explicit SpdyTestDeframerImpl( + std::unique_ptr<SpdyDeframerVisitorInterface> listener) + : listener_(std::move(listener)) { + CHECK(listener_ != nullptr); + } + SpdyTestDeframerImpl(const SpdyTestDeframerImpl&) = delete; + SpdyTestDeframerImpl& operator=(const SpdyTestDeframerImpl&) = delete; + ~SpdyTestDeframerImpl() override = default; + + bool AtFrameEnd() override; + + // Callbacks defined in SpdyFramerVisitorInterface. These are in the + // alphabetical order for ease of navigation, and are not in same order + // as in SpdyFramerVisitorInterface. + void OnAltSvc(SpdyStreamId stream_id, + SpdyStringPiece origin, + const SpdyAltSvcWireFormat::AlternativeServiceVector& + altsvc_vector) override; + void OnContinuation(SpdyStreamId stream_id, bool end) override; + SpdyHeadersHandlerInterface* OnHeaderFrameStart( + SpdyStreamId stream_id) override; + void OnHeaderFrameEnd(SpdyStreamId stream_id) override; + void OnDataFrameHeader(SpdyStreamId stream_id, + size_t length, + bool fin) override; + void OnError(http2::Http2DecoderAdapter::SpdyFramerError error) override; + void OnGoAway(SpdyStreamId last_accepted_stream_id, + SpdyErrorCode error_code) override; + bool OnGoAwayFrameData(const char* goaway_data, size_t len) override; + void OnHeaders(SpdyStreamId stream_id, + bool has_priority, + int weight, + SpdyStreamId parent_stream_id, + bool exclusive, + bool fin, + bool end) override; + void OnPing(SpdyPingId unique_id, bool is_ack) override; + void OnPriority(SpdyStreamId stream_id, + SpdyStreamId parent_stream_id, + int weight, + bool exclusive) override; + void OnPushPromise(SpdyStreamId stream_id, + SpdyStreamId promised_stream_id, + bool end) override; + void OnRstStream(SpdyStreamId stream_id, SpdyErrorCode error_code) override; + void OnSetting(SpdySettingsId id, uint32_t value) override; + void OnSettings() override; + void OnSettingsAck() override; + void OnSettingsEnd() override; + void OnStreamFrameData(SpdyStreamId stream_id, + const char* data, + size_t len) override; + void OnStreamEnd(SpdyStreamId stream_id) override; + void OnStreamPadLength(SpdyStreamId stream_id, size_t value) override; + void OnStreamPadding(SpdyStreamId stream_id, size_t len) override; + bool OnUnknownFrame(SpdyStreamId stream_id, uint8_t frame_type) override; + void OnWindowUpdate(SpdyStreamId stream_id, int delta_window_size) override; + + // Callbacks defined in SpdyHeadersHandlerInterface. + + void OnHeaderBlockStart() override; + void OnHeader(SpdyStringPiece key, SpdyStringPiece value) override; + void OnHeaderBlockEnd(size_t header_bytes_parsed, + size_t compressed_header_bytes_parsed) override; + + protected: + void AtDataEnd(); + void AtGoAwayEnd(); + void AtHeadersEnd(); + void AtPushPromiseEnd(); + + // Per-physical frame state. + // Frame type of the frame currently being processed. + Http2FrameType frame_type_ = UNSET; + // Stream id of the frame currently being processed. + SpdyStreamId stream_id_; + // Did the most recent frame header include the END_HEADERS flag? + bool end_ = false; + // Did the most recent frame header include the ack flag? + bool ack_ = false; + + // Per-HPACK block state. Only valid while processing a HEADERS or + // PUSH_PROMISE frame, and its CONTINUATION frames. + // Did the most recent HEADERS or PUSH_PROMISE include the END_STREAM flag? + // Note that this does not necessarily indicate that the current frame is + // the last frame for the stream (may be followed by CONTINUATION frames, + // may only half close). + bool fin_ = false; + bool got_hpack_end_ = false; + + std::unique_ptr<SpdyString> data_; + + // Total length of the data frame. + size_t data_len_ = 0; + + // Amount of skipped padding (i.e. total length of padding, including Pad + // Length field). + size_t padding_len_ = 0; + + std::unique_ptr<SpdyString> goaway_description_; + std::unique_ptr<StringPairVector> headers_; + std::unique_ptr<SettingVector> settings_; + std::unique_ptr<TestHeadersHandler> headers_handler_; + + std::unique_ptr<SpdyGoAwayIR> goaway_ir_; + std::unique_ptr<SpdyHeadersIR> headers_ir_; + std::unique_ptr<SpdyPushPromiseIR> push_promise_ir_; + std::unique_ptr<SpdySettingsIR> settings_ir_; + + private: + std::unique_ptr<SpdyDeframerVisitorInterface> listener_; +}; + +// static +std::unique_ptr<SpdyTestDeframer> SpdyTestDeframer::CreateConverter( + std::unique_ptr<SpdyDeframerVisitorInterface> listener) { + return SpdyMakeUnique<SpdyTestDeframerImpl>(std::move(listener)); +} + +void SpdyTestDeframerImpl::AtDataEnd() { + DVLOG(1) << "AtDataEnd"; + CHECK_EQ(data_len_, padding_len_ + data_->size()); + auto ptr = SpdyMakeUnique<SpdyDataIR>(stream_id_, std::move(*data_)); + CHECK_EQ(0u, data_->size()); + data_.reset(); + + CHECK_LE(0u, padding_len_); + CHECK_LE(padding_len_, 256u); + if (padding_len_ > 0) { + ptr->set_padding_len(padding_len_); + } + padding_len_ = 0; + + ptr->set_fin(fin_); + listener_->OnData(std::move(ptr)); + frame_type_ = UNSET; + fin_ = false; + data_len_ = 0; +} + +void SpdyTestDeframerImpl::AtGoAwayEnd() { + DVLOG(1) << "AtDataEnd"; + CHECK_EQ(frame_type_, GOAWAY); + if (HTTP2_DIE_IF_NULL(goaway_description_)->empty()) { + listener_->OnGoAway(std::move(goaway_ir_)); + } else { + listener_->OnGoAway(SpdyMakeUnique<SpdyGoAwayIR>( + goaway_ir_->last_good_stream_id(), goaway_ir_->error_code(), + std::move(*goaway_description_))); + CHECK_EQ(0u, goaway_description_->size()); + } + goaway_description_.reset(); + goaway_ir_.reset(); + frame_type_ = UNSET; +} + +void SpdyTestDeframerImpl::AtHeadersEnd() { + DVLOG(1) << "AtDataEnd"; + CHECK(frame_type_ == HEADERS || frame_type_ == CONTINUATION) + << " frame_type_=" << Http2FrameTypeToString(frame_type_); + CHECK(end_) << " frame_type_=" << Http2FrameTypeToString(frame_type_); + CHECK(got_hpack_end_); + + CHECK(headers_ir_ != nullptr); + CHECK(headers_ != nullptr); + CHECK(headers_handler_ != nullptr); + + CHECK_LE(0u, padding_len_); + CHECK_LE(padding_len_, 256u); + if (padding_len_ > 0) { + headers_ir_->set_padding_len(padding_len_); + } + padding_len_ = 0; + + headers_ir_->set_header_block(headers_handler_->decoded_block().Clone()); + headers_handler_.reset(); + listener_->OnHeaders(std::move(headers_ir_), std::move(headers_)); + + frame_type_ = UNSET; + fin_ = false; + end_ = false; + got_hpack_end_ = false; +} + +void SpdyTestDeframerImpl::AtPushPromiseEnd() { + DVLOG(1) << "AtDataEnd"; + CHECK(frame_type_ == PUSH_PROMISE || frame_type_ == CONTINUATION) + << " frame_type_=" << Http2FrameTypeToString(frame_type_); + CHECK(end_) << " frame_type_=" << Http2FrameTypeToString(frame_type_); + + CHECK(push_promise_ir_ != nullptr); + CHECK(headers_ != nullptr); + CHECK(headers_handler_ != nullptr); + + CHECK_EQ(headers_ir_.get(), nullptr); + + CHECK_LE(0u, padding_len_); + CHECK_LE(padding_len_, 256u); + if (padding_len_ > 0) { + push_promise_ir_->set_padding_len(padding_len_); + } + padding_len_ = 0; + + push_promise_ir_->set_header_block(headers_handler_->decoded_block().Clone()); + headers_handler_.reset(); + listener_->OnPushPromise(std::move(push_promise_ir_), std::move(headers_)); + + frame_type_ = UNSET; + end_ = false; +} + +bool SpdyTestDeframerImpl::AtFrameEnd() { + bool incomplete_logical_header = false; + // The caller says that the SpdyFrame has reached the end of the frame, + // so if we have any accumulated data, flush it. + switch (frame_type_) { + case DATA: + AtDataEnd(); + break; + + case GOAWAY: + AtGoAwayEnd(); + break; + + case HEADERS: + if (end_) { + AtHeadersEnd(); + } else { + incomplete_logical_header = true; + } + break; + + case PUSH_PROMISE: + if (end_) { + AtPushPromiseEnd(); + } else { + incomplete_logical_header = true; + } + break; + + case CONTINUATION: + if (end_) { + if (headers_ir_) { + AtHeadersEnd(); + } else if (push_promise_ir_) { + AtPushPromiseEnd(); + } else { + LOG(FATAL) << "Where is the SpdyFrameIR for the headers!"; + } + } else { + incomplete_logical_header = true; + } + break; + + case UNSET: + // Except for the frame types above, the others don't leave any record + // in the state of this object. Make sure nothing got left by accident. + CHECK_EQ(data_.get(), nullptr); + CHECK_EQ(goaway_description_.get(), nullptr); + CHECK_EQ(goaway_ir_.get(), nullptr); + CHECK_EQ(headers_.get(), nullptr); + CHECK_EQ(headers_handler_.get(), nullptr); + CHECK_EQ(headers_ir_.get(), nullptr); + CHECK_EQ(push_promise_ir_.get(), nullptr); + CHECK_EQ(settings_.get(), nullptr); + CHECK_EQ(settings_ir_.get(), nullptr); + break; + + default: + SPDY_BUG << "Expected UNSET, instead frame_type_==" << frame_type_; + return false; + } + frame_type_ = UNSET; + stream_id_ = 0; + end_ = false; + ack_ = false; + if (!incomplete_logical_header) { + fin_ = false; + } + return true; +} + +// Overridden methods from SpdyFramerVisitorInterface in alpha order... + +void SpdyTestDeframerImpl::OnAltSvc( + SpdyStreamId stream_id, + SpdyStringPiece origin, + const SpdyAltSvcWireFormat::AlternativeServiceVector& altsvc_vector) { + DVLOG(1) << "OnAltSvc stream_id: " << stream_id; + CHECK_EQ(frame_type_, UNSET) + << " frame_type_=" << Http2FrameTypeToString(frame_type_); + CHECK_GT(stream_id, 0u); + auto ptr = SpdyMakeUnique<SpdyAltSvcIR>(stream_id); + ptr->set_origin(SpdyString(origin)); + for (auto& altsvc : altsvc_vector) { + ptr->add_altsvc(altsvc); + } + listener_->OnAltSvc(std::move(ptr)); +} + +// A CONTINUATION frame contains a Header Block Fragment, and immediately +// follows another frame that contains a Header Block Fragment (HEADERS, +// PUSH_PROMISE or CONTINUATION). The last such frame has the END flag set. +// SpdyFramer ensures that the behavior is correct before calling the visitor. +void SpdyTestDeframerImpl::OnContinuation(SpdyStreamId stream_id, bool end) { + DVLOG(1) << "OnContinuation stream_id: " << stream_id; + CHECK_EQ(frame_type_, UNSET) + << " frame_type_=" << Http2FrameTypeToString(frame_type_); + CHECK_GT(stream_id, 0u); + CHECK_NE(nullptr, headers_.get()); + frame_type_ = CONTINUATION; + + stream_id_ = stream_id; + end_ = end; +} + +// Note that length includes the padding length (0 to 256, when the optional +// padding length field is counted). Padding comes after the payload, both +// for DATA frames and for control frames. +void SpdyTestDeframerImpl::OnDataFrameHeader(SpdyStreamId stream_id, + size_t length, + bool fin) { + DVLOG(1) << "OnDataFrameHeader stream_id: " << stream_id; + CHECK_EQ(frame_type_, UNSET) + << " frame_type_=" << Http2FrameTypeToString(frame_type_); + CHECK_GT(stream_id, 0u); + CHECK_EQ(data_.get(), nullptr); + frame_type_ = DATA; + + stream_id_ = stream_id; + fin_ = fin; + data_len_ = length; + data_ = SpdyMakeUnique<SpdyString>(); +} + +// The SpdyFramer will not process any more data at this point. +void SpdyTestDeframerImpl::OnError( + http2::Http2DecoderAdapter::SpdyFramerError error) { + DVLOG(1) << "SpdyFramer detected an error in the stream: " + << http2::Http2DecoderAdapter::SpdyFramerErrorToString(error) + << " frame_type_: " << Http2FrameTypeToString(frame_type_); + listener_->OnError(error, this); +} + +// Received a GOAWAY frame from the peer. The last stream id it accepted from us +// is |last_accepted_stream_id|. |status| is a protocol defined error code. +// The frame may also contain data. After this OnGoAwayFrameData will be called +// for any non-zero amount of data, and after that it will be called with len==0 +// to indicate the end of the GOAWAY frame. +void SpdyTestDeframerImpl::OnGoAway(SpdyStreamId last_good_stream_id, + SpdyErrorCode error_code) { + DVLOG(1) << "OnGoAway last_good_stream_id: " << last_good_stream_id + << " error code: " << error_code; + CHECK_EQ(frame_type_, UNSET) + << " frame_type_=" << Http2FrameTypeToString(frame_type_); + frame_type_ = GOAWAY; + goaway_ir_ = + SpdyMakeUnique<SpdyGoAwayIR>(last_good_stream_id, error_code, ""); + goaway_description_ = SpdyMakeUnique<SpdyString>(); +} + +// If len==0 then we've reached the end of the GOAWAY frame. +bool SpdyTestDeframerImpl::OnGoAwayFrameData(const char* goaway_data, + size_t len) { + DVLOG(1) << "OnGoAwayFrameData"; + CHECK_EQ(frame_type_, GOAWAY) + << " frame_type_=" << Http2FrameTypeToString(frame_type_); + CHECK(goaway_description_ != nullptr); + goaway_description_->append(goaway_data, len); + return true; +} + +SpdyHeadersHandlerInterface* SpdyTestDeframerImpl::OnHeaderFrameStart( + SpdyStreamId stream_id) { + return this; +} + +void SpdyTestDeframerImpl::OnHeaderFrameEnd(SpdyStreamId stream_id) { + DVLOG(1) << "OnHeaderFrameEnd stream_id: " << stream_id; +} + +// Received the fixed portion of a HEADERS frame. Called before the variable +// length (including zero length) Header Block Fragment is processed. If fin +// is true then there will be no DATA or trailing HEADERS after this HEADERS +// frame. +// If end is true, then there will be no CONTINUATION frame(s) following this +// frame; else if true then there will be CONTINATION frames(s) immediately +// following this frame, terminated by a CONTINUATION frame with end==true. +void SpdyTestDeframerImpl::OnHeaders(SpdyStreamId stream_id, + bool has_priority, + int weight, + SpdyStreamId parent_stream_id, + bool exclusive, + bool fin, + bool end) { + DVLOG(1) << "OnHeaders stream_id: " << stream_id; + CHECK_EQ(frame_type_, UNSET) + << " frame_type_=" << Http2FrameTypeToString(frame_type_); + CHECK_GT(stream_id, 0u); + frame_type_ = HEADERS; + + stream_id_ = stream_id; + fin_ = fin; + end_ = end; + + headers_ = SpdyMakeUnique<StringPairVector>(); + headers_handler_ = SpdyMakeUnique<TestHeadersHandler>(); + headers_ir_ = SpdyMakeUnique<SpdyHeadersIR>(stream_id); + headers_ir_->set_fin(fin); + if (has_priority) { + headers_ir_->set_has_priority(true); + headers_ir_->set_weight(weight); + headers_ir_->set_parent_stream_id(parent_stream_id); + headers_ir_->set_exclusive(exclusive); + } +} + +// The HTTP/2 protocol refers to the payload, |unique_id| here, as 8 octets of +// opaque data that is to be echoed back to the sender, with the ACK bit added. +// It isn't defined as a counter, +// or frame id, as the SpdyPingId naming might imply. +// Responding to a PING is supposed to be at the highest priority. +void SpdyTestDeframerImpl::OnPing(uint64_t unique_id, bool is_ack) { + DVLOG(1) << "OnPing unique_id: " << unique_id + << " is_ack: " << (is_ack ? "true" : "false"); + CHECK_EQ(frame_type_, UNSET) + << " frame_type_=" << Http2FrameTypeToString(frame_type_); + auto ptr = SpdyMakeUnique<SpdyPingIR>(unique_id); + if (is_ack) { + ptr->set_is_ack(is_ack); + listener_->OnPingAck(std::move(ptr)); + } else { + listener_->OnPing(std::move(ptr)); + } +} + +void SpdyTestDeframerImpl::OnPriority(SpdyStreamId stream_id, + SpdyStreamId parent_stream_id, + int weight, + bool exclusive) { + DVLOG(1) << "OnPriority stream_id: " << stream_id; + CHECK_EQ(frame_type_, UNSET) + << " frame_type_=" << Http2FrameTypeToString(frame_type_); + CHECK_GT(stream_id, 0u); + + listener_->OnPriority(SpdyMakeUnique<SpdyPriorityIR>( + stream_id, parent_stream_id, weight, exclusive)); +} + +void SpdyTestDeframerImpl::OnPushPromise(SpdyStreamId stream_id, + SpdyStreamId promised_stream_id, + bool end) { + DVLOG(1) << "OnPushPromise stream_id: " << stream_id; + CHECK_EQ(frame_type_, UNSET) + << " frame_type_=" << Http2FrameTypeToString(frame_type_); + CHECK_GT(stream_id, 0u); + + frame_type_ = PUSH_PROMISE; + stream_id_ = stream_id; + end_ = end; + + headers_ = SpdyMakeUnique<StringPairVector>(); + headers_handler_ = SpdyMakeUnique<TestHeadersHandler>(); + push_promise_ir_ = + SpdyMakeUnique<SpdyPushPromiseIR>(stream_id, promised_stream_id); +} + +// Closes the specified stream. After this the sender may still send PRIORITY +// frames for this stream, which we can ignore. +void SpdyTestDeframerImpl::OnRstStream(SpdyStreamId stream_id, + SpdyErrorCode error_code) { + DVLOG(1) << "OnRstStream stream_id: " << stream_id + << " error code: " << error_code; + CHECK_EQ(frame_type_, UNSET) + << " frame_type_=" << Http2FrameTypeToString(frame_type_); + CHECK_GT(stream_id, 0u); + + listener_->OnRstStream( + SpdyMakeUnique<SpdyRstStreamIR>(stream_id, error_code)); +} + +// Called for an individual setting. There is no negotiation; the sender is +// stating the value that the sender is using. +void SpdyTestDeframerImpl::OnSetting(SpdySettingsId id, uint32_t value) { + DVLOG(1) << "OnSetting id: " << id << std::hex << " value: " << value; + CHECK_EQ(frame_type_, SETTINGS) + << " frame_type_=" << Http2FrameTypeToString(frame_type_); + CHECK(settings_ != nullptr); + SpdyKnownSettingsId known_id; + if (ParseSettingsId(id, &known_id)) { + settings_->push_back(std::make_pair(known_id, value)); + settings_ir_->AddSetting(known_id, value); + } +} + +// Called at the start of a SETTINGS frame with setting entries, but not the +// (required) ACK of a SETTINGS frame. There is no stream_id because +// the settings apply to the entire connection, not to an individual stream. +void SpdyTestDeframerImpl::OnSettings() { + DVLOG(1) << "OnSettings"; + CHECK_EQ(frame_type_, UNSET) + << " frame_type_=" << Http2FrameTypeToString(frame_type_); + CHECK_EQ(nullptr, settings_ir_.get()); + CHECK_EQ(nullptr, settings_.get()); + frame_type_ = SETTINGS; + ack_ = false; + + settings_ = SpdyMakeUnique<SettingVector>(); + settings_ir_ = SpdyMakeUnique<SpdySettingsIR>(); +} + +void SpdyTestDeframerImpl::OnSettingsAck() { + DVLOG(1) << "OnSettingsAck"; + CHECK_EQ(frame_type_, UNSET) + << " frame_type_=" << Http2FrameTypeToString(frame_type_); + auto ptr = SpdyMakeUnique<SpdySettingsIR>(); + ptr->set_is_ack(true); + listener_->OnSettingsAck(std::move(ptr)); +} + +void SpdyTestDeframerImpl::OnSettingsEnd() { + DVLOG(1) << "OnSettingsEnd"; + CHECK_EQ(frame_type_, SETTINGS) + << " frame_type_=" << Http2FrameTypeToString(frame_type_); + CHECK(!ack_); + CHECK_NE(nullptr, settings_ir_.get()); + CHECK_NE(nullptr, settings_.get()); + listener_->OnSettings(std::move(settings_ir_), std::move(settings_)); + frame_type_ = UNSET; +} + +// Called for a zero length DATA frame with the END_STREAM flag set, or at the +// end a complete HPACK block (and its padding) that started with a HEADERS +// frame with the END_STREAM flag set. Doesn't apply to PUSH_PROMISE frames +// because they don't have END_STREAM flags. +void SpdyTestDeframerImpl::OnStreamEnd(SpdyStreamId stream_id) { + DVLOG(1) << "OnStreamEnd stream_id: " << stream_id; + CHECK_EQ(stream_id_, stream_id); + CHECK(frame_type_ == DATA || frame_type_ == HEADERS || + frame_type_ == CONTINUATION) + << " frame_type_=" << Http2FrameTypeToString(frame_type_); + CHECK(fin_); +} + +// The data arg points into the non-padding payload of a DATA frame. +// This must be a DATA frame (i.e. this method will not be +// called for HEADERS or CONTINUATION frames). +// This method may be called multiple times for a single DATA frame, depending +// upon buffer boundaries. +void SpdyTestDeframerImpl::OnStreamFrameData(SpdyStreamId stream_id, + const char* data, + size_t len) { + DVLOG(1) << "OnStreamFrameData stream_id: " << stream_id + << " len: " << len; + CHECK_EQ(stream_id_, stream_id); + CHECK_EQ(frame_type_, DATA); + data_->append(data, len); +} + +// Called when receiving the padding length field at the start of the DATA frame +// payload. value will be in the range 0 to 255. +void SpdyTestDeframerImpl::OnStreamPadLength(SpdyStreamId stream_id, + size_t value) { + DVLOG(1) << "OnStreamPadding stream_id: " << stream_id + << " value: " << value; + CHECK(frame_type_ == DATA || frame_type_ == HEADERS || + frame_type_ == PUSH_PROMISE) + << " frame_type_=" << Http2FrameTypeToString(frame_type_); + CHECK_EQ(stream_id_, stream_id); + CHECK_GE(255u, value); + // Count the padding length byte against total padding. + padding_len_ += 1; + CHECK_EQ(1u, padding_len_); +} + +// Called when padding is skipped over at the end of the DATA frame. len will +// be in the range 1 to 255. +void SpdyTestDeframerImpl::OnStreamPadding(SpdyStreamId stream_id, size_t len) { + DVLOG(1) << "OnStreamPadding stream_id: " << stream_id << " len: " << len; + CHECK(frame_type_ == DATA || frame_type_ == HEADERS || + frame_type_ == PUSH_PROMISE) + << " frame_type_=" << Http2FrameTypeToString(frame_type_); + CHECK_EQ(stream_id_, stream_id); + CHECK_LE(1u, len); + CHECK_GE(255u, len); + padding_len_ += len; + CHECK_LE(padding_len_, 256u) << "len=" << len; +} + +// WINDOW_UPDATE is supposed to be hop-by-hop, according to the spec. +// stream_id is 0 if the update applies to the connection, else stream_id +// will be the id of a stream previously seen, which maybe half or fully +// closed. +void SpdyTestDeframerImpl::OnWindowUpdate(SpdyStreamId stream_id, + int delta_window_size) { + DVLOG(1) << "OnWindowUpdate stream_id: " << stream_id + << " delta_window_size: " << delta_window_size; + CHECK_EQ(frame_type_, UNSET) + << " frame_type_=" << Http2FrameTypeToString(frame_type_); + CHECK_NE(0, delta_window_size); + + listener_->OnWindowUpdate( + SpdyMakeUnique<SpdyWindowUpdateIR>(stream_id, delta_window_size)); +} + +// Return true to indicate that the stream_id is valid; if not valid then +// SpdyFramer considers the connection corrupted. Requires keeping track +// of the set of currently open streams. For now we'll assume that unknown +// frame types are unsupported. +bool SpdyTestDeframerImpl::OnUnknownFrame(SpdyStreamId stream_id, + uint8_t frame_type) { + DVLOG(1) << "OnAltSvc stream_id: " << stream_id; + CHECK_EQ(frame_type_, UNSET) + << " frame_type_=" << Http2FrameTypeToString(frame_type_); + frame_type_ = UNKNOWN; + + stream_id_ = stream_id; + return false; +} + +// Callbacks defined in SpdyHeadersHandlerInterface. + +void SpdyTestDeframerImpl::OnHeaderBlockStart() { + CHECK(frame_type_ == HEADERS || frame_type_ == PUSH_PROMISE) + << " frame_type_=" << Http2FrameTypeToString(frame_type_); + CHECK(headers_ != nullptr); + CHECK_EQ(0u, headers_->size()); + got_hpack_end_ = false; +} + +void SpdyTestDeframerImpl::OnHeader(SpdyStringPiece key, + SpdyStringPiece value) { + CHECK(frame_type_ == HEADERS || frame_type_ == CONTINUATION || + frame_type_ == PUSH_PROMISE) + << " frame_type_=" << Http2FrameTypeToString(frame_type_); + CHECK(!got_hpack_end_); + HTTP2_DIE_IF_NULL(headers_)->emplace_back(SpdyString(key), SpdyString(value)); + HTTP2_DIE_IF_NULL(headers_handler_)->OnHeader(key, value); +} + +void SpdyTestDeframerImpl::OnHeaderBlockEnd( + size_t /* header_bytes_parsed */, + size_t /* compressed_header_bytes_parsed */) { + CHECK(headers_ != nullptr); + CHECK(frame_type_ == HEADERS || frame_type_ == CONTINUATION || + frame_type_ == PUSH_PROMISE) + << " frame_type_=" << Http2FrameTypeToString(frame_type_); + CHECK(end_); + CHECK(!got_hpack_end_); + got_hpack_end_ = true; +} + +class LoggingSpdyDeframerDelegate : public SpdyDeframerVisitorInterface { + public: + explicit LoggingSpdyDeframerDelegate( + std::unique_ptr<SpdyDeframerVisitorInterface> wrapped) + : wrapped_(std::move(wrapped)) { + if (!wrapped_) { + wrapped_ = SpdyMakeUnique<SpdyDeframerVisitorInterface>(); + } + } + ~LoggingSpdyDeframerDelegate() override = default; + + void OnAltSvc(std::unique_ptr<SpdyAltSvcIR> frame) override { + DVLOG(1) << "LoggingSpdyDeframerDelegate::OnAltSvc"; + wrapped_->OnAltSvc(std::move(frame)); + } + void OnData(std::unique_ptr<SpdyDataIR> frame) override { + DVLOG(1) << "LoggingSpdyDeframerDelegate::OnData"; + wrapped_->OnData(std::move(frame)); + } + void OnGoAway(std::unique_ptr<SpdyGoAwayIR> frame) override { + DVLOG(1) << "LoggingSpdyDeframerDelegate::OnGoAway"; + wrapped_->OnGoAway(std::move(frame)); + } + + // SpdyHeadersIR and SpdyPushPromiseIR each has a SpdyHeaderBlock which + // significantly modifies the headers, so the actual header entries (name + // and value strings) are provided in a vector. + void OnHeaders(std::unique_ptr<SpdyHeadersIR> frame, + std::unique_ptr<StringPairVector> headers) override { + DVLOG(1) << "LoggingSpdyDeframerDelegate::OnHeaders"; + wrapped_->OnHeaders(std::move(frame), std::move(headers)); + } + + void OnPing(std::unique_ptr<SpdyPingIR> frame) override { + DVLOG(1) << "LoggingSpdyDeframerDelegate::OnPing"; + wrapped_->OnPing(std::move(frame)); + } + void OnPingAck(std::unique_ptr<SpdyPingIR> frame) override { + DVLOG(1) << "LoggingSpdyDeframerDelegate::OnPingAck"; + wrapped_->OnPingAck(std::move(frame)); + } + + void OnPriority(std::unique_ptr<SpdyPriorityIR> frame) override { + DVLOG(1) << "LoggingSpdyDeframerDelegate::OnPriority"; + wrapped_->OnPriority(std::move(frame)); + } + + // SpdyHeadersIR and SpdyPushPromiseIR each has a SpdyHeaderBlock which + // significantly modifies the headers, so the actual header entries (name + // and value strings) are provided in a vector. + void OnPushPromise(std::unique_ptr<SpdyPushPromiseIR> frame, + std::unique_ptr<StringPairVector> headers) override { + DVLOG(1) << "LoggingSpdyDeframerDelegate::OnPushPromise"; + wrapped_->OnPushPromise(std::move(frame), std::move(headers)); + } + + void OnRstStream(std::unique_ptr<SpdyRstStreamIR> frame) override { + DVLOG(1) << "LoggingSpdyDeframerDelegate::OnRstStream"; + wrapped_->OnRstStream(std::move(frame)); + } + + // SpdySettingsIR has a map for settings, so loses info about the order of + // settings, and whether the same setting appeared more than once, so the + // the actual settings (parameter and value) are provided in a vector. + void OnSettings(std::unique_ptr<SpdySettingsIR> frame, + std::unique_ptr<SettingVector> settings) override { + DVLOG(1) << "LoggingSpdyDeframerDelegate::OnSettings"; + wrapped_->OnSettings(std::move(frame), std::move(settings)); + } + + // A settings frame with an ACK has no content, but for uniformity passing + // a frame with the ACK flag set. + void OnSettingsAck(std::unique_ptr<SpdySettingsIR> frame) override { + DVLOG(1) << "LoggingSpdyDeframerDelegate::OnSettingsAck"; + wrapped_->OnSettingsAck(std::move(frame)); + } + + void OnWindowUpdate(std::unique_ptr<SpdyWindowUpdateIR> frame) override { + DVLOG(1) << "LoggingSpdyDeframerDelegate::OnWindowUpdate"; + wrapped_->OnWindowUpdate(std::move(frame)); + } + + // The SpdyFramer will not process any more data at this point. + void OnError(http2::Http2DecoderAdapter::SpdyFramerError error, + SpdyTestDeframer* deframer) override { + DVLOG(1) << "LoggingSpdyDeframerDelegate::OnError"; + wrapped_->OnError(error, deframer); + } + + private: + std::unique_ptr<SpdyDeframerVisitorInterface> wrapped_; +}; + +// static +std::unique_ptr<SpdyDeframerVisitorInterface> +SpdyDeframerVisitorInterface::LogBeforeVisiting( + std::unique_ptr<SpdyDeframerVisitorInterface> wrapped_listener) { + return SpdyMakeUnique<LoggingSpdyDeframerDelegate>( + std::move(wrapped_listener)); +} + +CollectedFrame::CollectedFrame() = default; + +CollectedFrame::CollectedFrame(CollectedFrame&& other) + : frame_ir(std::move(other.frame_ir)), + headers(std::move(other.headers)), + settings(std::move(other.settings)), + error_reported(other.error_reported) {} + +CollectedFrame::~CollectedFrame() = default; + +CollectedFrame& CollectedFrame::operator=(CollectedFrame&& other) { + frame_ir = std::move(other.frame_ir); + headers = std::move(other.headers); + settings = std::move(other.settings); + error_reported = other.error_reported; + return *this; +} + +AssertionResult CollectedFrame::VerifyHasHeaders( + const StringPairVector& expected_headers) const { + VERIFY_NE(headers.get(), nullptr); + VERIFY_THAT(*headers, ::testing::ContainerEq(expected_headers)); + return AssertionSuccess(); +} + +AssertionResult CollectedFrame::VerifyHasSettings( + const SettingVector& expected_settings) const { + VERIFY_NE(settings.get(), nullptr); + VERIFY_THAT(*settings, testing::ContainerEq(expected_settings)); + return AssertionSuccess(); +} + +DeframerCallbackCollector::DeframerCallbackCollector( + std::vector<CollectedFrame>* collected_frames) + : collected_frames_(HTTP2_DIE_IF_NULL(collected_frames)) {} + +void DeframerCallbackCollector::OnAltSvc( + std::unique_ptr<SpdyAltSvcIR> frame_ir) { + CollectedFrame cf; + cf.frame_ir = std::move(frame_ir); + collected_frames_->push_back(std::move(cf)); +} +void DeframerCallbackCollector::OnData(std::unique_ptr<SpdyDataIR> frame_ir) { + CollectedFrame cf; + cf.frame_ir = std::move(frame_ir); + collected_frames_->push_back(std::move(cf)); +} +void DeframerCallbackCollector::OnGoAway( + std::unique_ptr<SpdyGoAwayIR> frame_ir) { + CollectedFrame cf; + cf.frame_ir = std::move(frame_ir); + collected_frames_->push_back(std::move(cf)); +} + +// SpdyHeadersIR and SpdyPushPromiseIR each has a SpdyHeaderBlock which +// significantly modifies the headers, so the actual header entries (name +// and value strings) are provided in a vector. +void DeframerCallbackCollector::OnHeaders( + std::unique_ptr<SpdyHeadersIR> frame_ir, + std::unique_ptr<StringPairVector> headers) { + CollectedFrame cf; + cf.frame_ir = std::move(frame_ir); + cf.headers = std::move(headers); + collected_frames_->push_back(std::move(cf)); +} + +void DeframerCallbackCollector::OnPing(std::unique_ptr<SpdyPingIR> frame_ir) { + EXPECT_TRUE(frame_ir && !frame_ir->is_ack()); + CollectedFrame cf; + cf.frame_ir = std::move(frame_ir); + collected_frames_->push_back(std::move(cf)); +} + +void DeframerCallbackCollector::OnPingAck( + std::unique_ptr<SpdyPingIR> frame_ir) { + EXPECT_TRUE(frame_ir && frame_ir->is_ack()); + CollectedFrame cf; + cf.frame_ir = std::move(frame_ir); + collected_frames_->push_back(std::move(cf)); +} + +void DeframerCallbackCollector::OnPriority( + std::unique_ptr<SpdyPriorityIR> frame_ir) { + CollectedFrame cf; + cf.frame_ir = std::move(frame_ir); + collected_frames_->push_back(std::move(cf)); +} + +// SpdyHeadersIR and SpdyPushPromiseIR each has a SpdyHeaderBlock which +// significantly modifies the headers, so the actual header entries (name +// and value strings) are provided in a vector. +void DeframerCallbackCollector::OnPushPromise( + std::unique_ptr<SpdyPushPromiseIR> frame_ir, + std::unique_ptr<StringPairVector> headers) { + CollectedFrame cf; + cf.frame_ir = std::move(frame_ir); + cf.headers = std::move(headers); + collected_frames_->push_back(std::move(cf)); +} + +void DeframerCallbackCollector::OnRstStream( + std::unique_ptr<SpdyRstStreamIR> frame_ir) { + CollectedFrame cf; + cf.frame_ir = std::move(frame_ir); + collected_frames_->push_back(std::move(cf)); +} + +// SpdySettingsIR has a map for settings, so loses info about the order of +// settings, and whether the same setting appeared more than once, so the +// the actual settings (parameter and value) are provided in a vector. +void DeframerCallbackCollector::OnSettings( + std::unique_ptr<SpdySettingsIR> frame_ir, + std::unique_ptr<SettingVector> settings) { + EXPECT_TRUE(frame_ir && !frame_ir->is_ack()); + CollectedFrame cf; + cf.frame_ir = std::move(frame_ir); + cf.settings = std::move(settings); + collected_frames_->push_back(std::move(cf)); +} + +// A settings frame_ir with an ACK has no content, but for uniformity passing +// a frame_ir with the ACK flag set. +void DeframerCallbackCollector::OnSettingsAck( + std::unique_ptr<SpdySettingsIR> frame_ir) { + EXPECT_TRUE(frame_ir && frame_ir->is_ack()); + CollectedFrame cf; + cf.frame_ir = std::move(frame_ir); + collected_frames_->push_back(std::move(cf)); +} + +void DeframerCallbackCollector::OnWindowUpdate( + std::unique_ptr<SpdyWindowUpdateIR> frame_ir) { + CollectedFrame cf; + cf.frame_ir = std::move(frame_ir); + collected_frames_->push_back(std::move(cf)); +} + +// The SpdyFramer will not process any more data at this point. +void DeframerCallbackCollector::OnError( + http2::Http2DecoderAdapter::SpdyFramerError error, + SpdyTestDeframer* deframer) { + CollectedFrame cf; + cf.error_reported = true; + collected_frames_->push_back(std::move(cf)); +} + +} // namespace test +} // namespace spdy diff --git a/chromium/net/third_party/quiche/src/spdy/core/spdy_deframer_visitor.h b/chromium/net/third_party/quiche/src/spdy/core/spdy_deframer_visitor.h new file mode 100644 index 00000000000..50d9987b811 --- /dev/null +++ b/chromium/net/third_party/quiche/src/spdy/core/spdy_deframer_visitor.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_SPDY_CORE_SPDY_DEFRAMER_VISITOR_H_ +#define QUICHE_SPDY_CORE_SPDY_DEFRAMER_VISITOR_H_ + +// Supports testing by converting callbacks to SpdyFramerVisitorInterface into +// callbacks to SpdyDeframerVisitorInterface, whose arguments are generally +// SpdyFrameIR instances. This enables a test client or test backend to operate +// at a level between the low-level callbacks of SpdyFramerVisitorInterface and +// the much higher level of entire messages (i.e. headers, body, trailers). +// Where possible the converter (SpdyTestDeframer) tries to preserve information +// that might be useful to tests (e.g. the order of headers or the amount of +// padding); the design also aims to allow tests to be concise, ideally +// supporting gMock style EXPECT_CALL(visitor, OnHeaders(...matchers...)) +// without too much boilerplate. +// +// Only supports HTTP/2 for the moment. +// +// Example of usage: +// +// SpdyFramer framer(HTTP2); +// +// // Need to call SpdyTestDeframer::AtFrameEnd() after processing each +// // frame, so tell SpdyFramer to stop after each. +// framer.set_process_single_input_frame(true); +// +// // Need the new OnHeader callbacks. +// framer.set_use_new_methods_for_test(true); +// +// // Create your visitor, a subclass of SpdyDeframerVisitorInterface. +// // For example, using DeframerCallbackCollector to collect frames: +// std::vector<CollectedFrame> collected_frames; +// auto your_visitor = SpdyMakeUnique<DeframerCallbackCollector>( +// &collected_frames); +// +// // Transfer ownership of your visitor to the converter, which ensures that +// // your visitor stays alive while the converter needs to call it. +// auto the_deframer = SpdyTestDeframer::CreateConverter( +// std::move(your_visitor)); +// +// // Tell the framer to notify SpdyTestDeframer of the decoded frame +// // details. +// framer.set_visitor(the_deframer.get()); +// +// // Process frames. +// SpdyStringPiece input = ... +// while (!input.empty() && !framer.HasError()) { +// size_t consumed = framer.ProcessInput(input.data(), input.size()); +// input.remove_prefix(consumed); +// if (framer.state() == SpdyFramer::SPDY_READY_FOR_FRAME) { +// the_deframer->AtFrameEnd(); +// } +// } +// +// // Make sure that the correct frames were received. For example: +// ASSERT_EQ(collected_frames.size(), 3); +// +// SpdyDataIR expected1(7 /*stream_id*/, "Data Payload"); +// expected1.set_padding_len(17); +// EXPECT_TRUE(collected_frames[0].VerifyEquals(expected1)); +// +// // Repeat for the other frames. +// +// Note that you could also seed the subclass of SpdyDeframerVisitorInterface +// with the expected frames, which it would pop-off the list as its expectations +// are met. + +#include <cstdint> + +#include <memory> +#include <type_traits> +#include <utility> +#include <vector> + +#include "base/logging.h" +#include "base/macros.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "net/third_party/quiche/src/spdy/core/http2_frame_decoder_adapter.h" +#include "net/third_party/quiche/src/spdy/core/spdy_protocol.h" +#include "net/third_party/quiche/src/spdy/core/spdy_protocol_test_utils.h" +#include "net/third_party/quiche/src/spdy/core/spdy_test_utils.h" +#include "net/third_party/quiche/src/spdy/platform/api/spdy_string.h" + +namespace spdy { +namespace test { + +// Non-lossy representation of a SETTINGS frame payload. +typedef std::vector<std::pair<SpdyKnownSettingsId, uint32_t>> SettingVector; + +// StringPairVector is used to record information lost by SpdyHeaderBlock, in +// particular the order of each header entry, though it doesn't expose the +// inner details of the HPACK block, such as the type of encoding selected +// for each header entry, nor dynamic table size changes. +typedef std::pair<SpdyString, SpdyString> StringPair; +typedef std::vector<StringPair> StringPairVector; + +// Forward decl. +class SpdyTestDeframer; + +// Note that this only roughly captures the frames, as padding bytes are lost, +// continuation frames are combined with their leading HEADERS or PUSH_PROMISE, +// the details of the HPACK encoding are lost, leaving +// only the list of header entries (name and value strings). If really helpful, +// we could add a SpdyRawDeframerVisitorInterface that gets the HPACK bytes, +// and receives continuation frames. For more info we'd need to improve +// SpdyFramerVisitorInterface. +class SpdyDeframerVisitorInterface { + public: + virtual ~SpdyDeframerVisitorInterface() {} + + // Wrap a visitor in another SpdyDeframerVisitorInterface that will + // DVLOG each call, and will then forward the calls to the wrapped visitor + // (if provided; nullptr is OK). Takes ownership of the wrapped visitor. + static std::unique_ptr<SpdyDeframerVisitorInterface> LogBeforeVisiting( + std::unique_ptr<SpdyDeframerVisitorInterface> wrapped_visitor); + + virtual void OnAltSvc(std::unique_ptr<SpdyAltSvcIR> frame) {} + virtual void OnData(std::unique_ptr<SpdyDataIR> frame) {} + virtual void OnGoAway(std::unique_ptr<SpdyGoAwayIR> frame) {} + + // SpdyHeadersIR and SpdyPushPromiseIR each has a SpdyHeaderBlock which + // significantly modifies the headers, so the actual header entries (name + // and value strings) are provided in a vector. + virtual void OnHeaders(std::unique_ptr<SpdyHeadersIR> frame, + std::unique_ptr<StringPairVector> headers) {} + + virtual void OnPing(std::unique_ptr<SpdyPingIR> frame) {} + virtual void OnPingAck(std::unique_ptr<SpdyPingIR> frame); + virtual void OnPriority(std::unique_ptr<SpdyPriorityIR> frame) {} + + // SpdyHeadersIR and SpdyPushPromiseIR each has a SpdyHeaderBlock which + // significantly modifies the headers, so the actual header entries (name + // and value strings) are provided in a vector. + virtual void OnPushPromise(std::unique_ptr<SpdyPushPromiseIR> frame, + std::unique_ptr<StringPairVector> headers) {} + + virtual void OnRstStream(std::unique_ptr<SpdyRstStreamIR> frame) {} + + // SpdySettingsIR has a map for settings, so loses info about the order of + // settings, and whether the same setting appeared more than once, so the + // the actual settings (parameter and value) are provided in a vector. + virtual void OnSettings(std::unique_ptr<SpdySettingsIR> frame, + std::unique_ptr<SettingVector> settings) {} + + // A settings frame with an ACK has no content, but for uniformity passing + // a frame with the ACK flag set. + virtual void OnSettingsAck(std::unique_ptr<SpdySettingsIR> frame); + + virtual void OnWindowUpdate(std::unique_ptr<SpdyWindowUpdateIR> frame) {} + + // The SpdyFramer will not process any more data at this point. + virtual void OnError(http2::Http2DecoderAdapter::SpdyFramerError error, + SpdyTestDeframer* deframer) {} +}; + +class SpdyTestDeframer : public SpdyFramerVisitorInterface { + public: + ~SpdyTestDeframer() override {} + + // Creates a SpdyFramerVisitorInterface that builds SpdyFrameIR concrete + // instances based on the callbacks it receives; when an entire frame is + // decoded/reconstructed it calls the passed in SpdyDeframerVisitorInterface. + // Transfers ownership of visitor to the new SpdyTestDeframer, which ensures + // that it continues to exist while the SpdyTestDeframer exists. + static std::unique_ptr<SpdyTestDeframer> CreateConverter( + std::unique_ptr<SpdyDeframerVisitorInterface> visitor); + + // Call to notify the deframer that the SpdyFramer has returned after reaching + // the end of decoding a frame. This is used to flush info about some frame + // types where we don't get a clear end signal; others are flushed (i.e. the + // appropriate call to the SpdyDeframerVisitorInterface method is invoked) + // as they're decoded by SpdyFramer and it calls the deframer. See the + // example in the comments at the top of this file. + virtual bool AtFrameEnd() = 0; + + protected: + SpdyTestDeframer() {} + SpdyTestDeframer(const SpdyTestDeframer&) = delete; + SpdyTestDeframer& operator=(const SpdyTestDeframer&) = delete; +}; + +// CollectedFrame holds the result of one call to SpdyDeframerVisitorInterface, +// as recorded by DeframerCallbackCollector. +struct CollectedFrame { + CollectedFrame(); + CollectedFrame(CollectedFrame&& other); + ~CollectedFrame(); + CollectedFrame& operator=(CollectedFrame&& other); + + // Compare a SpdyFrameIR sub-class instance, expected_ir, against the + // collected SpdyFrameIR. + template <class T, + typename X = + typename std::enable_if<std::is_base_of<SpdyFrameIR, T>::value>> + ::testing::AssertionResult VerifyHasFrame(const T& expected_ir) const { + return VerifySpdyFrameIREquals(expected_ir, frame_ir.get()); + } + + // Compare the collected headers against a StringPairVector. Ignores + // this->frame_ir. + ::testing::AssertionResult VerifyHasHeaders( + const StringPairVector& expected_headers) const; + + // Compare the collected settings (parameter and value pairs) against + // expected_settings. Ignores this->frame_ir. + ::testing::AssertionResult VerifyHasSettings( + const SettingVector& expected_settings) const; + + std::unique_ptr<SpdyFrameIR> frame_ir; + std::unique_ptr<StringPairVector> headers; + std::unique_ptr<SettingVector> settings; + bool error_reported = false; +}; + +// Creates a CollectedFrame instance for each callback, storing it in the +// vector provided to the constructor. +class DeframerCallbackCollector : public SpdyDeframerVisitorInterface { + public: + explicit DeframerCallbackCollector( + std::vector<CollectedFrame>* collected_frames); + ~DeframerCallbackCollector() override {} + + void OnAltSvc(std::unique_ptr<SpdyAltSvcIR> frame_ir) override; + void OnData(std::unique_ptr<SpdyDataIR> frame_ir) override; + void OnGoAway(std::unique_ptr<SpdyGoAwayIR> frame_ir) override; + void OnHeaders(std::unique_ptr<SpdyHeadersIR> frame_ir, + std::unique_ptr<StringPairVector> headers) override; + void OnPing(std::unique_ptr<SpdyPingIR> frame_ir) override; + void OnPingAck(std::unique_ptr<SpdyPingIR> frame_ir) override; + void OnPriority(std::unique_ptr<SpdyPriorityIR> frame_ir) override; + void OnPushPromise(std::unique_ptr<SpdyPushPromiseIR> frame_ir, + std::unique_ptr<StringPairVector> headers) override; + void OnRstStream(std::unique_ptr<SpdyRstStreamIR> frame_ir) override; + void OnSettings(std::unique_ptr<SpdySettingsIR> frame_ir, + std::unique_ptr<SettingVector> settings) override; + void OnSettingsAck(std::unique_ptr<SpdySettingsIR> frame_ir) override; + void OnWindowUpdate(std::unique_ptr<SpdyWindowUpdateIR> frame_ir) override; + void OnError(http2::Http2DecoderAdapter::SpdyFramerError error, + SpdyTestDeframer* deframer) override; + + private: + std::vector<CollectedFrame>* collected_frames_; +}; + +} // namespace test +} // namespace spdy + +#endif // QUICHE_SPDY_CORE_SPDY_DEFRAMER_VISITOR_H_ diff --git a/chromium/net/third_party/quiche/src/spdy/core/spdy_deframer_visitor_test.cc b/chromium/net/third_party/quiche/src/spdy/core/spdy_deframer_visitor_test.cc new file mode 100644 index 00000000000..ede1a20b733 --- /dev/null +++ b/chromium/net/third_party/quiche/src/spdy/core/spdy_deframer_visitor_test.cc @@ -0,0 +1,247 @@ +// Copyright 2016 The Chromium 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/spdy/core/spdy_deframer_visitor.h" + +#include <stdlib.h> + +#include <algorithm> +#include <limits> + +#include "base/logging.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "net/third_party/quiche/src/http2/test_tools/http2_random.h" +#include "net/third_party/quiche/src/spdy/core/hpack/hpack_constants.h" +#include "net/third_party/quiche/src/spdy/core/mock_spdy_framer_visitor.h" +#include "net/third_party/quiche/src/spdy/core/spdy_frame_builder.h" +#include "net/third_party/quiche/src/spdy/core/spdy_frame_reader.h" +#include "net/third_party/quiche/src/spdy/core/spdy_framer.h" +#include "net/third_party/quiche/src/spdy/core/spdy_protocol.h" +#include "net/third_party/quiche/src/spdy/core/spdy_protocol_test_utils.h" +#include "net/third_party/quiche/src/spdy/core/spdy_test_utils.h" +#include "net/third_party/quiche/src/spdy/platform/api/spdy_ptr_util.h" + +namespace spdy { +namespace test { +namespace { + +class SpdyDeframerVisitorTest : public ::testing::Test { + protected: + SpdyDeframerVisitorTest() : encoder_(SpdyFramer::ENABLE_COMPRESSION) { + decoder_.set_process_single_input_frame(true); + auto collector = + SpdyMakeUnique<DeframerCallbackCollector>(&collected_frames_); + auto log_and_collect = + SpdyDeframerVisitorInterface::LogBeforeVisiting(std::move(collector)); + deframer_ = SpdyTestDeframer::CreateConverter(std::move(log_and_collect)); + decoder_.set_visitor(deframer_.get()); + } + + bool DeframeInput(const char* input, size_t size) { + size_t input_remaining = size; + while (input_remaining > 0 && + decoder_.spdy_framer_error() == + http2::Http2DecoderAdapter::SPDY_NO_ERROR) { + // To make the tests more interesting, we feed random (and small) chunks + // into the framer. This simulates getting strange-sized reads from + // the socket. + const size_t kMaxReadSize = 32; + size_t bytes_read = + (random_.Uniform(std::min(input_remaining, kMaxReadSize))) + 1; + size_t bytes_processed = decoder_.ProcessInput(input, bytes_read); + input_remaining -= bytes_processed; + input += bytes_processed; + if (decoder_.state() == + http2::Http2DecoderAdapter::SPDY_READY_FOR_FRAME) { + deframer_->AtFrameEnd(); + } + } + return (input_remaining == 0 && + decoder_.spdy_framer_error() == + http2::Http2DecoderAdapter::SPDY_NO_ERROR); + } + + SpdyFramer encoder_; + http2::Http2DecoderAdapter decoder_; + std::vector<CollectedFrame> collected_frames_; + std::unique_ptr<SpdyTestDeframer> deframer_; + + private: + http2::test::Http2Random random_; +}; + +TEST_F(SpdyDeframerVisitorTest, DataFrame) { + const char kFrameData[] = { + 0x00, 0x00, 0x0d, // Length = 13. + 0x00, // DATA + 0x08, // PADDED + 0x00, 0x00, 0x00, 0x01, // Stream 1 + 0x07, // Pad length field. + 'h', 'e', 'l', 'l', // Data + 'o', // More Data + 0x00, 0x00, 0x00, 0x00, // Padding + 0x00, 0x00, 0x00 // More Padding + }; + + EXPECT_TRUE(DeframeInput(kFrameData, sizeof kFrameData)); + ASSERT_EQ(1u, collected_frames_.size()); + const CollectedFrame& cf0 = collected_frames_[0]; + ASSERT_NE(cf0.frame_ir, nullptr); + + SpdyDataIR expected_ir(/* stream_id = */ 1, "hello"); + expected_ir.set_padding_len(8); + EXPECT_TRUE(cf0.VerifyHasFrame(expected_ir)); +} + +TEST_F(SpdyDeframerVisitorTest, HeaderFrameWithContinuation) { + const char kFrameData[] = { + 0x00, 0x00, 0x05, // Payload Length: 5 + 0x01, // Type: HEADERS + 0x09, // Flags: PADDED | END_STREAM + 0x00, 0x00, 0x00, 0x01, // Stream: 1 + 0x04, // Padding Length: 4 + 0x00, 0x00, 0x00, 0x00, // Padding + /* Second Frame */ + 0x00, 0x00, 0x12, // Payload Length: 18 + 0x09, // Type: CONTINUATION + 0x04, // Flags: END_HEADERS + 0x00, 0x00, 0x00, 0x01, // Stream: 1 + 0x00, // Unindexed, literal name & value + 0x03, 0x62, 0x61, 0x72, // Name len and name (3, "bar") + 0x03, 0x66, 0x6f, 0x6f, // Value len and value (3, "foo") + 0x00, // Unindexed, literal name & value + 0x03, 0x66, 0x6f, 0x6f, // Name len and name (3, "foo") + 0x03, 0x62, 0x61, 0x72, // Value len and value (3, "bar") + }; + + EXPECT_TRUE(DeframeInput(kFrameData, sizeof kFrameData)); + ASSERT_EQ(1u, collected_frames_.size()); + const CollectedFrame& cf0 = collected_frames_[0]; + + StringPairVector headers; + headers.push_back({"bar", "foo"}); + headers.push_back({"foo", "bar"}); + + EXPECT_TRUE(cf0.VerifyHasHeaders(headers)); + + SpdyHeadersIR expected_ir(/* stream_id = */ 1); + // Yet again SpdyFramerVisitorInterface is lossy: it doesn't call OnPadding + // for HEADERS, just for DATA. Sigh. + // expected_ir.set_padding_len(5); + expected_ir.set_fin(true); + for (const auto& nv : headers) { + expected_ir.SetHeader(nv.first, nv.second); + } + + EXPECT_TRUE(cf0.VerifyHasFrame(expected_ir)); + + // Confirm that mismatches are also detected. + headers.push_back({"baz", "bing"}); + EXPECT_FALSE(cf0.VerifyHasHeaders(headers)); + EXPECT_TRUE(cf0.VerifyHasFrame(expected_ir)); + + headers.pop_back(); + EXPECT_TRUE(cf0.VerifyHasHeaders(headers)); + EXPECT_TRUE(cf0.VerifyHasFrame(expected_ir)); + + expected_ir.SetHeader("baz", "bing"); + EXPECT_FALSE(cf0.VerifyHasFrame(expected_ir)); + EXPECT_TRUE(cf0.VerifyHasHeaders(headers)); +} + +TEST_F(SpdyDeframerVisitorTest, PriorityFrame) { + const char kFrameData[] = { + 0x00, 0x00, 0x05, // Length: 5 + 0x02, // Type: PRIORITY + 0x00, // Flags: none + 0x00, 0x00, 0x00, 0x65, // Stream: 101 + '\x80', 0x00, 0x00, 0x01, // Parent: 1 (Exclusive) + 0x10, // Weight: 17 + }; + + EXPECT_TRUE(DeframeInput(kFrameData, sizeof kFrameData)); + ASSERT_EQ(1u, collected_frames_.size()); + const CollectedFrame& cf0 = collected_frames_[0]; + + SpdyPriorityIR expected_ir(/* stream_id = */ 101, + /* parent_stream_id = */ 1, /* weight = */ 17, + /* exclusive = */ true); + EXPECT_TRUE(cf0.VerifyHasFrame(expected_ir)); + + // Confirm that mismatches are also detected. + EXPECT_FALSE(cf0.VerifyHasFrame(SpdyPriorityIR(101, 1, 16, true))); + EXPECT_FALSE(cf0.VerifyHasFrame(SpdyPriorityIR(101, 50, 17, true))); + EXPECT_FALSE(cf0.VerifyHasFrame(SpdyPriorityIR(201, 1, 17, true))); + EXPECT_FALSE(cf0.VerifyHasFrame(SpdyPriorityIR(101, 1, 17, false))); +} + +TEST_F(SpdyDeframerVisitorTest, DISABLED_RstStreamFrame) { + // TODO(jamessynge): Please implement. +} + +TEST_F(SpdyDeframerVisitorTest, SettingsFrame) { + // Settings frame with two entries for the same parameter but with different + // values. The last one will be in the decoded SpdySettingsIR, but the vector + // of settings will have both, in the same order. + const char kFrameData[] = { + 0x00, 0x00, 0x0c, // Length + 0x04, // Type (SETTINGS) + 0x00, // Flags + 0x00, 0x00, 0x00, 0x00, // Stream id (must be zero) + 0x00, 0x04, // Setting id (SETTINGS_INITIAL_WINDOW_SIZE) + 0x0a, 0x0b, 0x0c, 0x0d, // Setting value + 0x00, 0x04, // Setting id (SETTINGS_INITIAL_WINDOW_SIZE) + 0x00, 0x00, 0x00, '\xff', // Setting value + }; + + EXPECT_TRUE(DeframeInput(kFrameData, sizeof kFrameData)); + ASSERT_EQ(1u, collected_frames_.size()); + const CollectedFrame& cf0 = collected_frames_[0]; + ASSERT_NE(cf0.frame_ir, nullptr); + + SpdySettingsIR expected_ir; + expected_ir.AddSetting(SETTINGS_INITIAL_WINDOW_SIZE, 255); + EXPECT_TRUE(cf0.VerifyHasFrame(expected_ir)); + + SettingVector expected_settings; + expected_settings.push_back({SETTINGS_INITIAL_WINDOW_SIZE, 0x0a0b0c0d}); + expected_settings.push_back({SETTINGS_INITIAL_WINDOW_SIZE, 255}); + + EXPECT_TRUE(cf0.VerifyHasSettings(expected_settings)); + + // Confirm that mismatches are also detected. + expected_settings.push_back({SETTINGS_INITIAL_WINDOW_SIZE, 65536}); + EXPECT_FALSE(cf0.VerifyHasSettings(expected_settings)); + + expected_ir.AddSetting(SETTINGS_INITIAL_WINDOW_SIZE, 65536); + EXPECT_FALSE(cf0.VerifyHasFrame(expected_ir)); + + SpdySettingsIR unexpected_ir; + unexpected_ir.set_is_ack(true); + EXPECT_FALSE(cf0.VerifyHasFrame(unexpected_ir)); +} + +TEST_F(SpdyDeframerVisitorTest, DISABLED_PushPromiseFrame) { + // TODO(jamessynge): Please implement. +} + +TEST_F(SpdyDeframerVisitorTest, DISABLED_PingFrame) { + // TODO(jamessynge): Please implement. +} + +TEST_F(SpdyDeframerVisitorTest, DISABLED_GoAwayFrame) { + // TODO(jamessynge): Please implement. +} + +TEST_F(SpdyDeframerVisitorTest, DISABLED_WindowUpdateFrame) { + // TODO(jamessynge): Please implement. +} + +TEST_F(SpdyDeframerVisitorTest, DISABLED_AltSvcFrame) { + // TODO(jamessynge): Please implement. +} + +} // namespace +} // namespace test +} // namespace spdy diff --git a/chromium/net/third_party/quiche/src/spdy/core/spdy_frame_builder.cc b/chromium/net/third_party/quiche/src/spdy/core/spdy_frame_builder.cc new file mode 100644 index 00000000000..a056b70f68c --- /dev/null +++ b/chromium/net/third_party/quiche/src/spdy/core/spdy_frame_builder.cc @@ -0,0 +1,183 @@ +// Copyright (c) 2012 The Chromium 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/spdy/core/spdy_frame_builder.h" + +#include <algorithm> +#include <cstdint> +#include <limits> +#include <new> + +#include "base/logging.h" +#include "net/third_party/quiche/src/spdy/core/spdy_protocol.h" +#include "net/third_party/quiche/src/spdy/core/zero_copy_output_buffer.h" +#include "net/third_party/quiche/src/spdy/platform/api/spdy_bug_tracker.h" + +namespace spdy { + +SpdyFrameBuilder::SpdyFrameBuilder(size_t size) + : buffer_(new char[size]), capacity_(size), length_(0), offset_(0) {} + +SpdyFrameBuilder::SpdyFrameBuilder(size_t size, ZeroCopyOutputBuffer* output) + : buffer_(output == nullptr ? new char[size] : nullptr), + output_(output), + capacity_(size), + length_(0), + offset_(0) {} + +SpdyFrameBuilder::~SpdyFrameBuilder() = default; + +char* SpdyFrameBuilder::GetWritableBuffer(size_t length) { + if (!CanWrite(length)) { + return nullptr; + } + return buffer_.get() + offset_ + length_; +} + +char* SpdyFrameBuilder::GetWritableOutput(size_t length, + size_t* actual_length) { + char* dest = nullptr; + int size = 0; + + if (!CanWrite(length)) { + return nullptr; + } + output_->Next(&dest, &size); + *actual_length = std::min<size_t>(length, size); + return dest; +} + +bool SpdyFrameBuilder::Seek(size_t length) { + if (!CanWrite(length)) { + return false; + } + if (output_ == nullptr) { + length_ += length; + } else { + output_->AdvanceWritePtr(length); + length_ += length; + } + return true; +} + +bool SpdyFrameBuilder::BeginNewFrame(SpdyFrameType type, + uint8_t flags, + SpdyStreamId stream_id) { + uint8_t raw_frame_type = SerializeFrameType(type); + DCHECK(IsDefinedFrameType(raw_frame_type)); + DCHECK_EQ(0u, stream_id & ~kStreamIdMask); + bool success = true; + if (length_ > 0) { + SPDY_BUG << "SpdyFrameBuilder doesn't have a clean state when BeginNewFrame" + << "is called. Leftover length_ is " << length_; + offset_ += length_; + length_ = 0; + } + + success &= WriteUInt24(capacity_ - offset_ - kFrameHeaderSize); + success &= WriteUInt8(raw_frame_type); + success &= WriteUInt8(flags); + success &= WriteUInt32(stream_id); + DCHECK_EQ(kDataFrameMinimumSize, length_); + return success; +} + +bool SpdyFrameBuilder::BeginNewFrame(SpdyFrameType type, + uint8_t flags, + SpdyStreamId stream_id, + size_t length) { + uint8_t raw_frame_type = SerializeFrameType(type); + DCHECK(IsDefinedFrameType(raw_frame_type)); + DCHECK_EQ(0u, stream_id & ~kStreamIdMask); + SPDY_BUG_IF(length > kHttp2DefaultFramePayloadLimit) + << "Frame length " << length_ << " is longer than frame size limit."; + return BeginNewFrameInternal(raw_frame_type, flags, stream_id, length); +} + +bool SpdyFrameBuilder::BeginNewUncheckedFrame(uint8_t raw_frame_type, + uint8_t flags, + SpdyStreamId stream_id, + size_t length) { + return BeginNewFrameInternal(raw_frame_type, flags, stream_id, length); +} + +bool SpdyFrameBuilder::BeginNewFrameInternal(uint8_t raw_frame_type, + uint8_t flags, + SpdyStreamId stream_id, + size_t length) { + DCHECK_EQ(length, length & kLengthMask); + bool success = true; + + offset_ += length_; + length_ = 0; + + success &= WriteUInt24(length); + success &= WriteUInt8(raw_frame_type); + success &= WriteUInt8(flags); + success &= WriteUInt32(stream_id); + DCHECK_EQ(kDataFrameMinimumSize, length_); + return success; +} + +bool SpdyFrameBuilder::WriteStringPiece32(const SpdyStringPiece value) { + if (!WriteUInt32(value.size())) { + return false; + } + + return WriteBytes(value.data(), value.size()); +} + +bool SpdyFrameBuilder::WriteBytes(const void* data, uint32_t data_len) { + if (!CanWrite(data_len)) { + return false; + } + + if (output_ == nullptr) { + char* dest = GetWritableBuffer(data_len); + memcpy(dest, data, data_len); + Seek(data_len); + } else { + char* dest = nullptr; + size_t size = 0; + size_t total_written = 0; + const char* data_ptr = reinterpret_cast<const char*>(data); + while (data_len > 0) { + dest = GetWritableOutput(data_len, &size); + if (dest == nullptr || size == 0) { + // Unable to make progress. + return false; + } + uint32_t to_copy = std::min<uint32_t>(data_len, size); + const char* src = data_ptr + total_written; + memcpy(dest, src, to_copy); + Seek(to_copy); + data_len -= to_copy; + total_written += to_copy; + } + } + return true; +} + +bool SpdyFrameBuilder::CanWrite(size_t length) const { + if (length > kLengthMask) { + DCHECK(false); + return false; + } + + if (output_ == nullptr) { + if (offset_ + length_ + length > capacity_) { + DLOG(FATAL) << "Requested: " << length << " capacity: " << capacity_ + << " used: " << offset_ + length_; + return false; + } + } else { + if (length > output_->BytesFree()) { + return false; + } + } + + return true; +} + +} // namespace spdy diff --git a/chromium/net/third_party/quiche/src/spdy/core/spdy_frame_builder.h b/chromium/net/third_party/quiche/src/spdy/core/spdy_frame_builder.h new file mode 100644 index 00000000000..c569c8c9377 --- /dev/null +++ b/chromium/net/third_party/quiche/src/spdy/core/spdy_frame_builder.h @@ -0,0 +1,142 @@ +// Copyright (c) 2012 The Chromium 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_SPDY_CORE_SPDY_FRAME_BUILDER_H_ +#define QUICHE_SPDY_CORE_SPDY_FRAME_BUILDER_H_ + +#include <cstddef> +#include <cstdint> +#include <memory> + +#include "net/third_party/quiche/src/spdy/core/spdy_protocol.h" +#include "net/third_party/quiche/src/spdy/core/zero_copy_output_buffer.h" +#include "net/third_party/quiche/src/spdy/platform/api/spdy_bug_tracker.h" +#include "net/third_party/quiche/src/spdy/platform/api/spdy_endianness_util.h" +#include "net/third_party/quiche/src/spdy/platform/api/spdy_export.h" +#include "net/third_party/quiche/src/spdy/platform/api/spdy_string_piece.h" +#include "net/third_party/quiche/src/spdy/platform/api/spdy_test_utils_prod.h" + +namespace spdy { + +// This class provides facilities for basic binary value packing +// into Spdy frames. +// +// The SpdyFrameBuilder supports appending primitive values (int, string, etc) +// to a frame instance. The SpdyFrameBuilder grows its internal memory buffer +// dynamically to hold the sequence of primitive values. The internal memory +// buffer is exposed as the "data" of the SpdyFrameBuilder. +class SPDY_EXPORT_PRIVATE SpdyFrameBuilder { + public: + // Initializes a SpdyFrameBuilder with a buffer of given size + explicit SpdyFrameBuilder(size_t size); + // Doesn't take ownership of output. + SpdyFrameBuilder(size_t size, ZeroCopyOutputBuffer* output); + + ~SpdyFrameBuilder(); + + // Returns the total size of the SpdyFrameBuilder's data, which may include + // multiple frames. + size_t length() const { return offset_ + length_; } + + // Seeks forward by the given number of bytes. Useful in conjunction with + // GetWriteableBuffer() above. + bool Seek(size_t length); + + // Populates this frame with a HTTP2 frame prefix using length information + // from |capacity_|. The given type must be a control frame type. + bool BeginNewFrame(SpdyFrameType type, uint8_t flags, SpdyStreamId stream_id); + + // Populates this frame with a HTTP2 frame prefix with type and length + // information. |type| must be a defined frame type. + bool BeginNewFrame(SpdyFrameType type, + uint8_t flags, + SpdyStreamId stream_id, + size_t length); + + // Populates this frame with a HTTP2 frame prefix with type and length + // information. |raw_frame_type| may be a defined or undefined frame type. + bool BeginNewUncheckedFrame(uint8_t raw_frame_type, + uint8_t flags, + SpdyStreamId stream_id, + size_t length); + + // Takes the buffer from the SpdyFrameBuilder. + SpdySerializedFrame take() { + SPDY_BUG_IF(output_ != nullptr) << "ZeroCopyOutputBuffer is used to build " + << "frames. take() shouldn't be called"; + SPDY_BUG_IF(kMaxFrameSizeLimit < length_) + << "Frame length " << length_ + << " is longer than the maximum possible allowed length."; + SpdySerializedFrame rv(buffer_.release(), length(), true); + capacity_ = 0; + length_ = 0; + offset_ = 0; + return rv; + } + + // Methods for adding to the payload. These values are appended to the end + // of the SpdyFrameBuilder payload. Note - binary integers are converted from + // host to network form. + bool WriteUInt8(uint8_t value) { return WriteBytes(&value, sizeof(value)); } + bool WriteUInt16(uint16_t value) { + value = SpdyHostToNet16(value); + return WriteBytes(&value, sizeof(value)); + } + bool WriteUInt24(uint32_t value) { + value = SpdyHostToNet32(value); + return WriteBytes(reinterpret_cast<char*>(&value) + 1, sizeof(value) - 1); + } + bool WriteUInt32(uint32_t value) { + value = SpdyHostToNet32(value); + return WriteBytes(&value, sizeof(value)); + } + bool WriteUInt64(uint64_t value) { + uint32_t upper = SpdyHostToNet32(static_cast<uint32_t>(value >> 32)); + uint32_t lower = SpdyHostToNet32(static_cast<uint32_t>(value)); + return (WriteBytes(&upper, sizeof(upper)) && + WriteBytes(&lower, sizeof(lower))); + } + bool WriteStringPiece32(const SpdyStringPiece value); + bool WriteBytes(const void* data, uint32_t data_len); + + private: + SPDY_FRIEND_TEST(SpdyFrameBuilderTest, GetWritableBuffer); + SPDY_FRIEND_TEST(SpdyFrameBuilderTest, GetWritableOutput); + SPDY_FRIEND_TEST(SpdyFrameBuilderTest, GetWritableOutputNegative); + + // Populates this frame with a HTTP2 frame prefix with type and length + // information. + bool BeginNewFrameInternal(uint8_t raw_frame_type, + uint8_t flags, + SpdyStreamId stream_id, + size_t length); + + // Returns a writeable buffer of given size in bytes, to be appended to the + // currently written frame. Does bounds checking on length but does not + // increment the underlying iterator. To do so, consumers should subsequently + // call Seek(). + // In general, consumers should use Write*() calls instead of this. + // Returns NULL on failure. + char* GetWritableBuffer(size_t length); + char* GetWritableOutput(size_t desired_length, size_t* actual_length); + + // Checks to make sure that there is an appropriate amount of space for a + // write of given size, in bytes. + bool CanWrite(size_t length) const; + + // A buffer to be created whenever a new frame needs to be written. Used only + // if |output_| is nullptr. + std::unique_ptr<char[]> buffer_; + // A pre-allocated buffer. If not-null, serialized frame data is written to + // this buffer. + ZeroCopyOutputBuffer* output_ = nullptr; // Does not own. + + size_t capacity_; // Allocation size of payload, set by constructor. + size_t length_; // Length of the latest frame in the buffer. + size_t offset_; // Position at which the latest frame begins. +}; + +} // namespace spdy + +#endif // QUICHE_SPDY_CORE_SPDY_FRAME_BUILDER_H_ diff --git a/chromium/net/third_party/quiche/src/spdy/core/spdy_frame_builder_test.cc b/chromium/net/third_party/quiche/src/spdy/core/spdy_frame_builder_test.cc new file mode 100644 index 00000000000..97d10f12e93 --- /dev/null +++ b/chromium/net/third_party/quiche/src/spdy/core/spdy_frame_builder_test.cc @@ -0,0 +1,68 @@ +// Copyright (c) 2013 The Chromium 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/spdy/core/spdy_frame_builder.h" + +#include <memory> + +#include "testing/gtest/include/gtest/gtest.h" +#include "net/third_party/quiche/src/spdy/core/array_output_buffer.h" +#include "net/third_party/quiche/src/spdy/core/spdy_framer.h" +#include "net/third_party/quiche/src/spdy/core/spdy_protocol.h" + +namespace spdy { + +namespace { + +const int64_t kSize = 64 * 1024; +char output_buffer[kSize] = ""; + +} // namespace + +// Verifies that SpdyFrameBuilder::GetWritableBuffer() can be used to build a +// SpdySerializedFrame. +TEST(SpdyFrameBuilderTest, GetWritableBuffer) { + const size_t kBuilderSize = 10; + SpdyFrameBuilder builder(kBuilderSize); + char* writable_buffer = builder.GetWritableBuffer(kBuilderSize); + memset(writable_buffer, ~1, kBuilderSize); + EXPECT_TRUE(builder.Seek(kBuilderSize)); + SpdySerializedFrame frame(builder.take()); + char expected[kBuilderSize]; + memset(expected, ~1, kBuilderSize); + EXPECT_EQ(SpdyStringPiece(expected, kBuilderSize), + SpdyStringPiece(frame.data(), kBuilderSize)); +} + +// Verifies that SpdyFrameBuilder::GetWritableBuffer() can be used to build a +// SpdySerializedFrame to the output buffer. +TEST(SpdyFrameBuilderTest, GetWritableOutput) { + ArrayOutputBuffer output(output_buffer, kSize); + const size_t kBuilderSize = 10; + SpdyFrameBuilder builder(kBuilderSize, &output); + size_t actual_size = 0; + char* writable_buffer = builder.GetWritableOutput(kBuilderSize, &actual_size); + memset(writable_buffer, ~1, kBuilderSize); + EXPECT_TRUE(builder.Seek(kBuilderSize)); + SpdySerializedFrame frame(output.Begin(), kBuilderSize, false); + char expected[kBuilderSize]; + memset(expected, ~1, kBuilderSize); + EXPECT_EQ(SpdyStringPiece(expected, kBuilderSize), + SpdyStringPiece(frame.data(), kBuilderSize)); +} + +// Verifies the case that the buffer's capacity is too small. +TEST(SpdyFrameBuilderTest, GetWritableOutputNegative) { + size_t small_cap = 1; + ArrayOutputBuffer output(output_buffer, small_cap); + const size_t kBuilderSize = 10; + SpdyFrameBuilder builder(kBuilderSize, &output); + size_t actual_size = 0; + char* writable_buffer = builder.GetWritableOutput(kBuilderSize, &actual_size); + builder.GetWritableOutput(kBuilderSize, &actual_size); + EXPECT_EQ(0u, actual_size); + EXPECT_EQ(nullptr, writable_buffer); +} + +} // namespace spdy diff --git a/chromium/net/third_party/quiche/src/spdy/core/spdy_frame_reader.cc b/chromium/net/third_party/quiche/src/spdy/core/spdy_frame_reader.cc new file mode 100644 index 00000000000..b9bf4c1c8e8 --- /dev/null +++ b/chromium/net/third_party/quiche/src/spdy/core/spdy_frame_reader.cc @@ -0,0 +1,200 @@ +// Copyright (c) 2012 The Chromium 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/spdy/core/spdy_frame_reader.h" + +#include "net/third_party/quiche/src/spdy/core/spdy_protocol.h" +#include "net/third_party/quiche/src/spdy/platform/api/spdy_endianness_util.h" + +namespace spdy { + +SpdyFrameReader::SpdyFrameReader(const char* data, const size_t len) + : data_(data), len_(len), ofs_(0) {} + +bool SpdyFrameReader::ReadUInt8(uint8_t* result) { + // Make sure that we have the whole uint8_t. + if (!CanRead(1)) { + OnFailure(); + return false; + } + + // Read into result. + *result = *reinterpret_cast<const uint8_t*>(data_ + ofs_); + + // Iterate. + ofs_ += 1; + + return true; +} + +bool SpdyFrameReader::ReadUInt16(uint16_t* result) { + // Make sure that we have the whole uint16_t. + if (!CanRead(2)) { + OnFailure(); + return false; + } + + // Read into result. + *result = SpdyNetToHost16(*(reinterpret_cast<const uint16_t*>(data_ + ofs_))); + + // Iterate. + ofs_ += 2; + + return true; +} + +bool SpdyFrameReader::ReadUInt32(uint32_t* result) { + // Make sure that we have the whole uint32_t. + if (!CanRead(4)) { + OnFailure(); + return false; + } + + // Read into result. + *result = SpdyNetToHost32(*(reinterpret_cast<const uint32_t*>(data_ + ofs_))); + + // Iterate. + ofs_ += 4; + + return true; +} + +bool SpdyFrameReader::ReadUInt64(uint64_t* result) { + // Make sure that we have the whole uint64_t. + if (!CanRead(8)) { + OnFailure(); + return false; + } + + // Read into result. Network byte order is big-endian. + uint64_t upper = + SpdyNetToHost32(*(reinterpret_cast<const uint32_t*>(data_ + ofs_))); + uint64_t lower = + SpdyNetToHost32(*(reinterpret_cast<const uint32_t*>(data_ + ofs_ + 4))); + *result = (upper << 32) + lower; + + // Iterate. + ofs_ += 8; + + return true; +} + +bool SpdyFrameReader::ReadUInt31(uint32_t* result) { + bool success = ReadUInt32(result); + + // Zero out highest-order bit. + if (success) { + *result &= 0x7fffffff; + } + + return success; +} + +bool SpdyFrameReader::ReadUInt24(uint32_t* result) { + // Make sure that we have the whole uint24_t. + if (!CanRead(3)) { + OnFailure(); + return false; + } + + // Read into result. + *result = 0; + memcpy(reinterpret_cast<char*>(result) + 1, data_ + ofs_, 3); + *result = SpdyNetToHost32(*result); + + // Iterate. + ofs_ += 3; + + return true; +} + +bool SpdyFrameReader::ReadStringPiece16(SpdyStringPiece* result) { + // Read resultant length. + uint16_t result_len; + if (!ReadUInt16(&result_len)) { + // OnFailure() already called. + return false; + } + + // Make sure that we have the whole string. + if (!CanRead(result_len)) { + OnFailure(); + return false; + } + + // Set result. + *result = SpdyStringPiece(data_ + ofs_, result_len); + + // Iterate. + ofs_ += result_len; + + return true; +} + +bool SpdyFrameReader::ReadStringPiece32(SpdyStringPiece* result) { + // Read resultant length. + uint32_t result_len; + if (!ReadUInt32(&result_len)) { + // OnFailure() already called. + return false; + } + + // Make sure that we have the whole string. + if (!CanRead(result_len)) { + OnFailure(); + return false; + } + + // Set result. + *result = SpdyStringPiece(data_ + ofs_, result_len); + + // Iterate. + ofs_ += result_len; + + return true; +} + +bool SpdyFrameReader::ReadBytes(void* result, size_t size) { + // Make sure that we have enough data to read. + if (!CanRead(size)) { + OnFailure(); + return false; + } + + // Read into result. + memcpy(result, data_ + ofs_, size); + + // Iterate. + ofs_ += size; + + return true; +} + +bool SpdyFrameReader::Seek(size_t size) { + if (!CanRead(size)) { + OnFailure(); + return false; + } + + // Iterate. + ofs_ += size; + + return true; +} + +bool SpdyFrameReader::IsDoneReading() const { + return len_ == ofs_; +} + +bool SpdyFrameReader::CanRead(size_t bytes) const { + return bytes <= (len_ - ofs_); +} + +void SpdyFrameReader::OnFailure() { + // Set our iterator to the end of the buffer so that further reads fail + // immediately. + ofs_ = len_; +} + +} // namespace spdy diff --git a/chromium/net/third_party/quiche/src/spdy/core/spdy_frame_reader.h b/chromium/net/third_party/quiche/src/spdy/core/spdy_frame_reader.h new file mode 100644 index 00000000000..dc6c0640fdb --- /dev/null +++ b/chromium/net/third_party/quiche/src/spdy/core/spdy_frame_reader.h @@ -0,0 +1,129 @@ +// Copyright (c) 2012 The Chromium 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_SPDY_CORE_SPDY_FRAME_READER_H_ +#define QUICHE_SPDY_CORE_SPDY_FRAME_READER_H_ + +#include <cstdint> + +#include "net/third_party/quiche/src/spdy/platform/api/spdy_export.h" +#include "net/third_party/quiche/src/spdy/platform/api/spdy_string_piece.h" + +namespace spdy { + +// Used for reading SPDY frames. Though there isn't really anything terribly +// SPDY-specific here, it's a helper class that's useful when doing SPDY +// framing. +// +// To use, simply construct a SpdyFramerReader using the underlying buffer that +// you'd like to read fields from, then call one of the Read*() methods to +// actually do some reading. +// +// This class keeps an internal iterator to keep track of what's already been +// read and each successive Read*() call automatically increments said iterator +// on success. On failure, internal state of the SpdyFrameReader should not be +// trusted and it is up to the caller to throw away the failed instance and +// handle the error as appropriate. None of the Read*() methods should ever be +// called after failure, as they will also fail immediately. +class SPDY_EXPORT_PRIVATE SpdyFrameReader { + public: + // Caller must provide an underlying buffer to work on. + SpdyFrameReader(const char* data, const size_t len); + + // Empty destructor. + ~SpdyFrameReader() {} + + // Reads an 8-bit unsigned integer into the given output parameter. + // Forwards the internal iterator on success. + // Returns true on success, false otherwise. + bool ReadUInt8(uint8_t* result); + + // Reads a 16-bit unsigned integer into the given output parameter. + // Forwards the internal iterator on success. + // Returns true on success, false otherwise. + bool ReadUInt16(uint16_t* result); + + // Reads a 32-bit unsigned integer into the given output parameter. + // Forwards the internal iterator on success. + // Returns true on success, false otherwise. + bool ReadUInt32(uint32_t* result); + + // Reads a 64-bit unsigned integer into the given output parameter. + // Forwards the internal iterator on success. + // Returns true on success, false otherwise. + bool ReadUInt64(uint64_t* result); + + // Reads a 31-bit unsigned integer into the given output parameter. This is + // equivalent to ReadUInt32() above except that the highest-order bit is + // discarded. + // Forwards the internal iterator (by 4B) on success. + // Returns true on success, false otherwise. + bool ReadUInt31(uint32_t* result); + + // Reads a 24-bit unsigned integer into the given output parameter. + // Forwards the internal iterator (by 3B) on success. + // Returns true on success, false otherwise. + bool ReadUInt24(uint32_t* result); + + // Reads a string prefixed with 16-bit length into the given output parameter. + // + // NOTE: Does not copy but rather references strings in the underlying buffer. + // This should be kept in mind when handling memory management! + // + // Forwards the internal iterator on success. + // Returns true on success, false otherwise. + bool ReadStringPiece16(SpdyStringPiece* result); + + // Reads a string prefixed with 32-bit length into the given output parameter. + // + // NOTE: Does not copy but rather references strings in the underlying buffer. + // This should be kept in mind when handling memory management! + // + // Forwards the internal iterator on success. + // Returns true on success, false otherwise. + bool ReadStringPiece32(SpdyStringPiece* result); + + // Reads a given number of bytes into the given buffer. The buffer + // must be of adequate size. + // Forwards the internal iterator on success. + // Returns true on success, false otherwise. + bool ReadBytes(void* result, size_t size); + + // Seeks a given number of bytes into the buffer from the current offset. + // Equivelant to an empty read. + // Forwards the internal iterator. + // Returns true on success, false otherwise. + bool Seek(size_t size); + + // Rewinds this reader to the beginning of the frame. + void Rewind() { ofs_ = 0; } + + // Returns true if the entirety of the underlying buffer has been read via + // Read*() calls. + bool IsDoneReading() const; + + // Returns the number of bytes that have been consumed by the reader so far. + size_t GetBytesConsumed() const { return ofs_; } + + private: + // Returns true if the underlying buffer has enough room to read the given + // amount of bytes. + bool CanRead(size_t bytes) const; + + // To be called when a read fails for any reason. + void OnFailure(); + + // The data buffer that we're reading from. + const char* data_; + + // The length of the data buffer that we're reading from. + const size_t len_; + + // The location of the next read from our data buffer. + size_t ofs_; +}; + +} // namespace spdy + +#endif // QUICHE_SPDY_CORE_SPDY_FRAME_READER_H_ diff --git a/chromium/net/third_party/quiche/src/spdy/core/spdy_frame_reader_test.cc b/chromium/net/third_party/quiche/src/spdy/core/spdy_frame_reader_test.cc new file mode 100644 index 00000000000..8caf60f552e --- /dev/null +++ b/chromium/net/third_party/quiche/src/spdy/core/spdy_frame_reader_test.cc @@ -0,0 +1,247 @@ +// Copyright (c) 2012 The Chromium 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/spdy/core/spdy_frame_reader.h" + +#include <cstdint> + +#include "testing/gtest/include/gtest/gtest.h" +#include "net/third_party/quiche/src/spdy/platform/api/spdy_arraysize.h" +#include "net/third_party/quiche/src/spdy/platform/api/spdy_endianness_util.h" + +namespace spdy { + +TEST(SpdyFrameReaderTest, ReadUInt16) { + // Frame data in network byte order. + const uint16_t kFrameData[] = { + SpdyHostToNet16(1), + SpdyHostToNet16(1 << 15), + }; + + SpdyFrameReader frame_reader(reinterpret_cast<const char*>(kFrameData), + sizeof(kFrameData)); + EXPECT_FALSE(frame_reader.IsDoneReading()); + + uint16_t uint16_val; + EXPECT_TRUE(frame_reader.ReadUInt16(&uint16_val)); + EXPECT_FALSE(frame_reader.IsDoneReading()); + EXPECT_EQ(1, uint16_val); + + EXPECT_TRUE(frame_reader.ReadUInt16(&uint16_val)); + EXPECT_TRUE(frame_reader.IsDoneReading()); + EXPECT_EQ(1 << 15, uint16_val); +} + +TEST(SpdyFrameReaderTest, ReadUInt32) { + // Frame data in network byte order. + const uint32_t kFrameData[] = { + SpdyHostToNet32(1), + SpdyHostToNet32(0x80000000), + }; + + SpdyFrameReader frame_reader(reinterpret_cast<const char*>(kFrameData), + SPDY_ARRAYSIZE(kFrameData) * sizeof(uint32_t)); + EXPECT_FALSE(frame_reader.IsDoneReading()); + + uint32_t uint32_val; + EXPECT_TRUE(frame_reader.ReadUInt32(&uint32_val)); + EXPECT_FALSE(frame_reader.IsDoneReading()); + EXPECT_EQ(1u, uint32_val); + + EXPECT_TRUE(frame_reader.ReadUInt32(&uint32_val)); + EXPECT_TRUE(frame_reader.IsDoneReading()); + EXPECT_EQ(1u << 31, uint32_val); +} + +TEST(SpdyFrameReaderTest, ReadStringPiece16) { + // Frame data in network byte order. + const char kFrameData[] = { + 0x00, 0x02, // uint16_t(2) + 0x48, 0x69, // "Hi" + 0x00, 0x10, // uint16_t(16) + 0x54, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x2c, + 0x20, 0x31, 0x2c, 0x20, 0x32, 0x2c, 0x20, 0x33, // "Testing, 1, 2, 3" + }; + + SpdyFrameReader frame_reader(kFrameData, SPDY_ARRAYSIZE(kFrameData)); + EXPECT_FALSE(frame_reader.IsDoneReading()); + + SpdyStringPiece stringpiece_val; + EXPECT_TRUE(frame_reader.ReadStringPiece16(&stringpiece_val)); + EXPECT_FALSE(frame_reader.IsDoneReading()); + EXPECT_EQ(0, stringpiece_val.compare("Hi")); + + EXPECT_TRUE(frame_reader.ReadStringPiece16(&stringpiece_val)); + EXPECT_TRUE(frame_reader.IsDoneReading()); + EXPECT_EQ(0, stringpiece_val.compare("Testing, 1, 2, 3")); +} + +TEST(SpdyFrameReaderTest, ReadStringPiece32) { + // Frame data in network byte order. + const char kFrameData[] = { + 0x00, 0x00, 0x00, 0x03, // uint32_t(3) + 0x66, 0x6f, 0x6f, // "foo" + 0x00, 0x00, 0x00, 0x10, // uint32_t(16) + 0x54, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x2c, + 0x20, 0x34, 0x2c, 0x20, 0x35, 0x2c, 0x20, 0x36, // "Testing, 4, 5, 6" + }; + + SpdyFrameReader frame_reader(kFrameData, SPDY_ARRAYSIZE(kFrameData)); + EXPECT_FALSE(frame_reader.IsDoneReading()); + + SpdyStringPiece stringpiece_val; + EXPECT_TRUE(frame_reader.ReadStringPiece32(&stringpiece_val)); + EXPECT_FALSE(frame_reader.IsDoneReading()); + EXPECT_EQ(0, stringpiece_val.compare("foo")); + + EXPECT_TRUE(frame_reader.ReadStringPiece32(&stringpiece_val)); + EXPECT_TRUE(frame_reader.IsDoneReading()); + EXPECT_EQ(0, stringpiece_val.compare("Testing, 4, 5, 6")); +} + +TEST(SpdyFrameReaderTest, ReadUInt16WithBufferTooSmall) { + // Frame data in network byte order. + const char kFrameData[] = { + 0x00, // part of a uint16_t + }; + + SpdyFrameReader frame_reader(kFrameData, SPDY_ARRAYSIZE(kFrameData)); + EXPECT_FALSE(frame_reader.IsDoneReading()); + + uint16_t uint16_val; + EXPECT_FALSE(frame_reader.ReadUInt16(&uint16_val)); +} + +TEST(SpdyFrameReaderTest, ReadUInt32WithBufferTooSmall) { + // Frame data in network byte order. + const char kFrameData[] = { + 0x00, 0x00, 0x00, // part of a uint32_t + }; + + SpdyFrameReader frame_reader(kFrameData, SPDY_ARRAYSIZE(kFrameData)); + EXPECT_FALSE(frame_reader.IsDoneReading()); + + uint32_t uint32_val; + EXPECT_FALSE(frame_reader.ReadUInt32(&uint32_val)); + + // Also make sure that trying to read a uint16_t, which technically could + // work, fails immediately due to previously encountered failed read. + uint16_t uint16_val; + EXPECT_FALSE(frame_reader.ReadUInt16(&uint16_val)); +} + +// Tests ReadStringPiece16() with a buffer too small to fit the entire string. +TEST(SpdyFrameReaderTest, ReadStringPiece16WithBufferTooSmall) { + // Frame data in network byte order. + const char kFrameData[] = { + 0x00, 0x03, // uint16_t(3) + 0x48, 0x69, // "Hi" + }; + + SpdyFrameReader frame_reader(kFrameData, SPDY_ARRAYSIZE(kFrameData)); + EXPECT_FALSE(frame_reader.IsDoneReading()); + + SpdyStringPiece stringpiece_val; + EXPECT_FALSE(frame_reader.ReadStringPiece16(&stringpiece_val)); + + // Also make sure that trying to read a uint16_t, which technically could + // work, fails immediately due to previously encountered failed read. + uint16_t uint16_val; + EXPECT_FALSE(frame_reader.ReadUInt16(&uint16_val)); +} + +// Tests ReadStringPiece16() with a buffer too small even to fit the length. +TEST(SpdyFrameReaderTest, ReadStringPiece16WithBufferWayTooSmall) { + // Frame data in network byte order. + const char kFrameData[] = { + 0x00, // part of a uint16_t + }; + + SpdyFrameReader frame_reader(kFrameData, SPDY_ARRAYSIZE(kFrameData)); + EXPECT_FALSE(frame_reader.IsDoneReading()); + + SpdyStringPiece stringpiece_val; + EXPECT_FALSE(frame_reader.ReadStringPiece16(&stringpiece_val)); + + // Also make sure that trying to read a uint16_t, which technically could + // work, fails immediately due to previously encountered failed read. + uint16_t uint16_val; + EXPECT_FALSE(frame_reader.ReadUInt16(&uint16_val)); +} + +// Tests ReadStringPiece32() with a buffer too small to fit the entire string. +TEST(SpdyFrameReaderTest, ReadStringPiece32WithBufferTooSmall) { + // Frame data in network byte order. + const char kFrameData[] = { + 0x00, 0x00, 0x00, 0x03, // uint32_t(3) + 0x48, 0x69, // "Hi" + }; + + SpdyFrameReader frame_reader(kFrameData, SPDY_ARRAYSIZE(kFrameData)); + EXPECT_FALSE(frame_reader.IsDoneReading()); + + SpdyStringPiece stringpiece_val; + EXPECT_FALSE(frame_reader.ReadStringPiece32(&stringpiece_val)); + + // Also make sure that trying to read a uint16_t, which technically could + // work, fails immediately due to previously encountered failed read. + uint16_t uint16_val; + EXPECT_FALSE(frame_reader.ReadUInt16(&uint16_val)); +} + +// Tests ReadStringPiece32() with a buffer too small even to fit the length. +TEST(SpdyFrameReaderTest, ReadStringPiece32WithBufferWayTooSmall) { + // Frame data in network byte order. + const char kFrameData[] = { + 0x00, 0x00, 0x00, // part of a uint32_t + }; + + SpdyFrameReader frame_reader(kFrameData, SPDY_ARRAYSIZE(kFrameData)); + EXPECT_FALSE(frame_reader.IsDoneReading()); + + SpdyStringPiece stringpiece_val; + EXPECT_FALSE(frame_reader.ReadStringPiece32(&stringpiece_val)); + + // Also make sure that trying to read a uint16_t, which technically could + // work, fails immediately due to previously encountered failed read. + uint16_t uint16_val; + EXPECT_FALSE(frame_reader.ReadUInt16(&uint16_val)); +} + +TEST(SpdyFrameReaderTest, ReadBytes) { + // Frame data in network byte order. + const char kFrameData[] = { + 0x66, 0x6f, 0x6f, // "foo" + 0x48, 0x69, // "Hi" + }; + + SpdyFrameReader frame_reader(kFrameData, SPDY_ARRAYSIZE(kFrameData)); + EXPECT_FALSE(frame_reader.IsDoneReading()); + + char dest1[3] = {}; + EXPECT_TRUE(frame_reader.ReadBytes(&dest1, SPDY_ARRAYSIZE(dest1))); + EXPECT_FALSE(frame_reader.IsDoneReading()); + EXPECT_EQ("foo", SpdyStringPiece(dest1, SPDY_ARRAYSIZE(dest1))); + + char dest2[2] = {}; + EXPECT_TRUE(frame_reader.ReadBytes(&dest2, SPDY_ARRAYSIZE(dest2))); + EXPECT_TRUE(frame_reader.IsDoneReading()); + EXPECT_EQ("Hi", SpdyStringPiece(dest2, SPDY_ARRAYSIZE(dest2))); +} + +TEST(SpdyFrameReaderTest, ReadBytesWithBufferTooSmall) { + // Frame data in network byte order. + const char kFrameData[] = { + 0x01, + }; + + SpdyFrameReader frame_reader(kFrameData, SPDY_ARRAYSIZE(kFrameData)); + EXPECT_FALSE(frame_reader.IsDoneReading()); + + char dest[SPDY_ARRAYSIZE(kFrameData) + 2] = {}; + EXPECT_FALSE(frame_reader.ReadBytes(&dest, SPDY_ARRAYSIZE(kFrameData) + 1)); + EXPECT_STREQ("", dest); +} + +} // namespace spdy diff --git a/chromium/net/third_party/quiche/src/spdy/core/spdy_framer.cc b/chromium/net/third_party/quiche/src/spdy/core/spdy_framer.cc new file mode 100644 index 00000000000..a9252a1af34 --- /dev/null +++ b/chromium/net/third_party/quiche/src/spdy/core/spdy_framer.cc @@ -0,0 +1,1295 @@ +// Copyright (c) 2012 The Chromium 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/spdy/core/spdy_framer.h" + +#include <algorithm> +#include <cstdint> +#include <iterator> +#include <list> +#include <new> + +#include "base/logging.h" +#include "net/third_party/quiche/src/http2/platform/api/http2_macros.h" +#include "net/third_party/quiche/src/spdy/core/hpack/hpack_constants.h" +#include "net/third_party/quiche/src/spdy/core/spdy_bitmasks.h" +#include "net/third_party/quiche/src/spdy/core/spdy_frame_builder.h" +#include "net/third_party/quiche/src/spdy/core/spdy_frame_reader.h" +#include "net/third_party/quiche/src/spdy/platform/api/spdy_bug_tracker.h" +#include "net/third_party/quiche/src/spdy/platform/api/spdy_estimate_memory_usage.h" +#include "net/third_party/quiche/src/spdy/platform/api/spdy_ptr_util.h" +#include "net/third_party/quiche/src/spdy/platform/api/spdy_string_utils.h" + +namespace spdy { + +namespace { + +// Pack parent stream ID and exclusive flag into the format used by HTTP/2 +// headers and priority frames. +uint32_t PackStreamDependencyValues(bool exclusive, + SpdyStreamId parent_stream_id) { + // Make sure the highest-order bit in the parent stream id is zeroed out. + uint32_t parent = parent_stream_id & 0x7fffffff; + // Set the one-bit exclusivity flag. + uint32_t e_bit = exclusive ? 0x80000000 : 0; + return parent | e_bit; +} + +// Used to indicate no flags in a HTTP2 flags field. +const uint8_t kNoFlags = 0; + +// Wire size of pad length field. +const size_t kPadLengthFieldSize = 1; + +// The size of one parameter in SETTINGS frame. +const size_t kOneSettingParameterSize = 6; + +size_t GetUncompressedSerializedLength(const SpdyHeaderBlock& headers) { + const size_t num_name_value_pairs_size = sizeof(uint32_t); + const size_t length_of_name_size = num_name_value_pairs_size; + const size_t length_of_value_size = num_name_value_pairs_size; + + size_t total_length = num_name_value_pairs_size; + for (const auto& header : headers) { + // We add space for the length of the name and the length of the value as + // well as the length of the name and the length of the value. + total_length += length_of_name_size + header.first.size() + + length_of_value_size + header.second.size(); + } + return total_length; +} + +// Serializes the flags octet for a given SpdyHeadersIR. +uint8_t SerializeHeaderFrameFlags(const SpdyHeadersIR& header_ir, + const bool end_headers) { + uint8_t flags = 0; + if (header_ir.fin()) { + flags |= CONTROL_FLAG_FIN; + } + if (end_headers) { + flags |= HEADERS_FLAG_END_HEADERS; + } + if (header_ir.padded()) { + flags |= HEADERS_FLAG_PADDED; + } + if (header_ir.has_priority()) { + flags |= HEADERS_FLAG_PRIORITY; + } + return flags; +} + +// Serializes the flags octet for a given SpdyPushPromiseIR. +uint8_t SerializePushPromiseFrameFlags(const SpdyPushPromiseIR& push_promise_ir, + const bool end_headers) { + uint8_t flags = 0; + if (push_promise_ir.padded()) { + flags = flags | PUSH_PROMISE_FLAG_PADDED; + } + if (end_headers) { + flags |= PUSH_PROMISE_FLAG_END_PUSH_PROMISE; + } + return flags; +} + +// Serializes a HEADERS frame from the given SpdyHeadersIR and encoded header +// block. Does not need or use the SpdyHeaderBlock inside SpdyHeadersIR. +// Return false if the serialization fails. |encoding| should not be empty. +bool SerializeHeadersGivenEncoding(const SpdyHeadersIR& headers, + const SpdyString& encoding, + const bool end_headers, + ZeroCopyOutputBuffer* output) { + const size_t frame_size = + GetHeaderFrameSizeSansBlock(headers) + encoding.size(); + SpdyFrameBuilder builder(frame_size, output); + bool ret = builder.BeginNewFrame( + SpdyFrameType::HEADERS, SerializeHeaderFrameFlags(headers, end_headers), + headers.stream_id(), frame_size - kFrameHeaderSize); + DCHECK_EQ(kFrameHeaderSize, builder.length()); + + if (ret && headers.padded()) { + ret &= builder.WriteUInt8(headers.padding_payload_len()); + } + + if (ret && headers.has_priority()) { + int weight = ClampHttp2Weight(headers.weight()); + ret &= builder.WriteUInt32(PackStreamDependencyValues( + headers.exclusive(), headers.parent_stream_id())); + // Per RFC 7540 section 6.3, serialized weight value is actual value - 1. + ret &= builder.WriteUInt8(weight - 1); + } + + if (ret) { + ret &= builder.WriteBytes(encoding.data(), encoding.size()); + } + + if (ret && headers.padding_payload_len() > 0) { + SpdyString padding(headers.padding_payload_len(), 0); + ret &= builder.WriteBytes(padding.data(), padding.length()); + } + + if (!ret) { + DLOG(WARNING) << "Failed to build HEADERS. Not enough space in output"; + } + return ret; +} + +// Serializes a PUSH_PROMISE frame from the given SpdyPushPromiseIR and +// encoded header block. Does not need or use the SpdyHeaderBlock inside +// SpdyPushPromiseIR. +bool SerializePushPromiseGivenEncoding(const SpdyPushPromiseIR& push_promise, + const SpdyString& encoding, + const bool end_headers, + ZeroCopyOutputBuffer* output) { + const size_t frame_size = + GetPushPromiseFrameSizeSansBlock(push_promise) + encoding.size(); + SpdyFrameBuilder builder(frame_size, output); + bool ok = builder.BeginNewFrame( + SpdyFrameType::PUSH_PROMISE, + SerializePushPromiseFrameFlags(push_promise, end_headers), + push_promise.stream_id(), frame_size - kFrameHeaderSize); + + if (push_promise.padded()) { + ok = ok && builder.WriteUInt8(push_promise.padding_payload_len()); + } + ok = ok && builder.WriteUInt32(push_promise.promised_stream_id()) && + builder.WriteBytes(encoding.data(), encoding.size()); + if (ok && push_promise.padding_payload_len() > 0) { + SpdyString padding(push_promise.padding_payload_len(), 0); + ok = builder.WriteBytes(padding.data(), padding.length()); + } + + DLOG_IF(ERROR, !ok) << "Failed to write PUSH_PROMISE encoding, not enough " + << "space in output"; + return ok; +} + +bool WritePayloadWithContinuation(SpdyFrameBuilder* builder, + const SpdyString& hpack_encoding, + SpdyStreamId stream_id, + SpdyFrameType type, + int padding_payload_len) { + uint8_t end_flag = 0; + uint8_t flags = 0; + if (type == SpdyFrameType::HEADERS) { + end_flag = HEADERS_FLAG_END_HEADERS; + } else if (type == SpdyFrameType::PUSH_PROMISE) { + end_flag = PUSH_PROMISE_FLAG_END_PUSH_PROMISE; + } else { + DLOG(FATAL) << "CONTINUATION frames cannot be used with frame type " + << FrameTypeToString(type); + } + + // Write all the padding payload and as much of the data payload as possible + // into the initial frame. + size_t bytes_remaining = 0; + bytes_remaining = hpack_encoding.size() - + std::min(hpack_encoding.size(), + kHttp2MaxControlFrameSendSize - builder->length() - + padding_payload_len); + bool ret = builder->WriteBytes(&hpack_encoding[0], + hpack_encoding.size() - bytes_remaining); + if (padding_payload_len > 0) { + SpdyString padding = SpdyString(padding_payload_len, 0); + ret &= builder->WriteBytes(padding.data(), padding.length()); + } + + // Tack on CONTINUATION frames for the overflow. + while (bytes_remaining > 0 && ret) { + size_t bytes_to_write = + std::min(bytes_remaining, + kHttp2MaxControlFrameSendSize - kContinuationFrameMinimumSize); + // Write CONTINUATION frame prefix. + if (bytes_remaining == bytes_to_write) { + flags |= end_flag; + } + ret &= builder->BeginNewFrame(SpdyFrameType::CONTINUATION, flags, stream_id, + bytes_to_write); + // Write payload fragment. + ret &= builder->WriteBytes( + &hpack_encoding[hpack_encoding.size() - bytes_remaining], + bytes_to_write); + bytes_remaining -= bytes_to_write; + } + return ret; +} + +void SerializeDataBuilderHelper(const SpdyDataIR& data_ir, + uint8_t* flags, + int* num_padding_fields, + size_t* size_with_padding) { + if (data_ir.fin()) { + *flags = DATA_FLAG_FIN; + } + + if (data_ir.padded()) { + *flags = *flags | DATA_FLAG_PADDED; + ++*num_padding_fields; + } + + *size_with_padding = *num_padding_fields + data_ir.data_len() + + data_ir.padding_payload_len() + kDataFrameMinimumSize; +} + +void SerializeDataFrameHeaderWithPaddingLengthFieldBuilderHelper( + const SpdyDataIR& data_ir, + uint8_t* flags, + size_t* frame_size, + size_t* num_padding_fields) { + *flags = DATA_FLAG_NONE; + if (data_ir.fin()) { + *flags = DATA_FLAG_FIN; + } + + *frame_size = kDataFrameMinimumSize; + if (data_ir.padded()) { + *flags = *flags | DATA_FLAG_PADDED; + ++(*num_padding_fields); + *frame_size = *frame_size + *num_padding_fields; + } +} + +void SerializeSettingsBuilderHelper(const SpdySettingsIR& settings, + uint8_t* flags, + const SettingsMap* values, + size_t* size) { + if (settings.is_ack()) { + *flags = *flags | SETTINGS_FLAG_ACK; + } + *size = + kSettingsFrameMinimumSize + (values->size() * kOneSettingParameterSize); +} + +void SerializeAltSvcBuilderHelper(const SpdyAltSvcIR& altsvc_ir, + SpdyString* value, + size_t* size) { + *size = kGetAltSvcFrameMinimumSize; + *size = *size + altsvc_ir.origin().length(); + *value = SpdyAltSvcWireFormat::SerializeHeaderFieldValue( + altsvc_ir.altsvc_vector()); + *size = *size + value->length(); +} + +} // namespace + +SpdyFramer::SpdyFramer(CompressionOption option) + : debug_visitor_(nullptr), compression_option_(option) { + static_assert(kHttp2MaxControlFrameSendSize <= kHttp2DefaultFrameSizeLimit, + "Our send limit should be at most our receive limit."); +} + +SpdyFramer::~SpdyFramer() = default; + +void SpdyFramer::set_debug_visitor( + SpdyFramerDebugVisitorInterface* debug_visitor) { + debug_visitor_ = debug_visitor; +} + +SpdyFramer::SpdyFrameIterator::SpdyFrameIterator(SpdyFramer* framer) + : framer_(framer), is_first_frame_(true), has_next_frame_(true) {} + +SpdyFramer::SpdyFrameIterator::~SpdyFrameIterator() = default; + +size_t SpdyFramer::SpdyFrameIterator::NextFrame(ZeroCopyOutputBuffer* output) { + const SpdyFrameIR& frame_ir = GetIR(); + if (!has_next_frame_) { + SPDY_BUG << "SpdyFramer::SpdyFrameIterator::NextFrame called without " + << "a next frame."; + return false; + } + + const size_t size_without_block = + is_first_frame_ ? GetFrameSizeSansBlock() : kContinuationFrameMinimumSize; + auto encoding = SpdyMakeUnique<SpdyString>(); + encoder_->Next(kHttp2MaxControlFrameSendSize - size_without_block, + encoding.get()); + has_next_frame_ = encoder_->HasNext(); + + if (framer_->debug_visitor_ != nullptr) { + const auto& header_block_frame_ir = + static_cast<const SpdyFrameWithHeaderBlockIR&>(frame_ir); + const size_t header_list_size = + GetUncompressedSerializedLength(header_block_frame_ir.header_block()); + framer_->debug_visitor_->OnSendCompressedFrame( + frame_ir.stream_id(), + is_first_frame_ ? frame_ir.frame_type() : SpdyFrameType::CONTINUATION, + header_list_size, size_without_block + encoding->size()); + } + + const size_t free_bytes_before = output->BytesFree(); + bool ok = false; + if (is_first_frame_) { + is_first_frame_ = false; + ok = SerializeGivenEncoding(*encoding, output); + } else { + SpdyContinuationIR continuation_ir(frame_ir.stream_id()); + continuation_ir.take_encoding(std::move(encoding)); + continuation_ir.set_end_headers(!has_next_frame_); + ok = framer_->SerializeContinuation(continuation_ir, output); + } + return ok ? free_bytes_before - output->BytesFree() : 0; +} + +bool SpdyFramer::SpdyFrameIterator::HasNextFrame() const { + return has_next_frame_; +} + +SpdyFramer::SpdyHeaderFrameIterator::SpdyHeaderFrameIterator( + SpdyFramer* framer, + std::unique_ptr<const SpdyHeadersIR> headers_ir) + : SpdyFrameIterator(framer), headers_ir_(std::move(headers_ir)) { + SetEncoder(headers_ir_.get()); +} + +SpdyFramer::SpdyHeaderFrameIterator::~SpdyHeaderFrameIterator() = default; + +const SpdyFrameIR& SpdyFramer::SpdyHeaderFrameIterator::GetIR() const { + return *(headers_ir_.get()); +} + +size_t SpdyFramer::SpdyHeaderFrameIterator::GetFrameSizeSansBlock() const { + return GetHeaderFrameSizeSansBlock(*headers_ir_); +} + +bool SpdyFramer::SpdyHeaderFrameIterator::SerializeGivenEncoding( + const SpdyString& encoding, + ZeroCopyOutputBuffer* output) const { + return SerializeHeadersGivenEncoding(*headers_ir_, encoding, + !has_next_frame(), output); +} + +SpdyFramer::SpdyPushPromiseFrameIterator::SpdyPushPromiseFrameIterator( + SpdyFramer* framer, + std::unique_ptr<const SpdyPushPromiseIR> push_promise_ir) + : SpdyFrameIterator(framer), push_promise_ir_(std::move(push_promise_ir)) { + SetEncoder(push_promise_ir_.get()); +} + +SpdyFramer::SpdyPushPromiseFrameIterator::~SpdyPushPromiseFrameIterator() = + default; + +const SpdyFrameIR& SpdyFramer::SpdyPushPromiseFrameIterator::GetIR() const { + return *(push_promise_ir_.get()); +} + +size_t SpdyFramer::SpdyPushPromiseFrameIterator::GetFrameSizeSansBlock() const { + return GetPushPromiseFrameSizeSansBlock(*push_promise_ir_); +} + +bool SpdyFramer::SpdyPushPromiseFrameIterator::SerializeGivenEncoding( + const SpdyString& encoding, + ZeroCopyOutputBuffer* output) const { + return SerializePushPromiseGivenEncoding(*push_promise_ir_, encoding, + !has_next_frame(), output); +} + +SpdyFramer::SpdyControlFrameIterator::SpdyControlFrameIterator( + SpdyFramer* framer, + std::unique_ptr<const SpdyFrameIR> frame_ir) + : framer_(framer), frame_ir_(std::move(frame_ir)) {} + +SpdyFramer::SpdyControlFrameIterator::~SpdyControlFrameIterator() = default; + +size_t SpdyFramer::SpdyControlFrameIterator::NextFrame( + ZeroCopyOutputBuffer* output) { + size_t size_written = framer_->SerializeFrame(*frame_ir_, output); + has_next_frame_ = false; + return size_written; +} + +bool SpdyFramer::SpdyControlFrameIterator::HasNextFrame() const { + return has_next_frame_; +} + +const SpdyFrameIR& SpdyFramer::SpdyControlFrameIterator::GetIR() const { + return *(frame_ir_.get()); +} + +// TODO(yasong): remove all the static_casts. +std::unique_ptr<SpdyFrameSequence> SpdyFramer::CreateIterator( + SpdyFramer* framer, + std::unique_ptr<const SpdyFrameIR> frame_ir) { + switch (frame_ir->frame_type()) { + case SpdyFrameType::HEADERS: { + return SpdyMakeUnique<SpdyHeaderFrameIterator>( + framer, + SpdyWrapUnique(static_cast<const SpdyHeadersIR*>(frame_ir.release()))); + } + case SpdyFrameType::PUSH_PROMISE: { + return SpdyMakeUnique<SpdyPushPromiseFrameIterator>( + framer, SpdyWrapUnique( + static_cast<const SpdyPushPromiseIR*>(frame_ir.release()))); + } + case SpdyFrameType::DATA: { + DVLOG(1) << "Serialize a stream end DATA frame for VTL"; + HTTP2_FALLTHROUGH; + } + default: { + return SpdyMakeUnique<SpdyControlFrameIterator>(framer, + std::move(frame_ir)); + } + } +} + +SpdySerializedFrame SpdyFramer::SerializeData(const SpdyDataIR& data_ir) { + uint8_t flags = DATA_FLAG_NONE; + int num_padding_fields = 0; + size_t size_with_padding = 0; + SerializeDataBuilderHelper(data_ir, &flags, &num_padding_fields, + &size_with_padding); + + SpdyFrameBuilder builder(size_with_padding); + builder.BeginNewFrame(SpdyFrameType::DATA, flags, data_ir.stream_id()); + if (data_ir.padded()) { + builder.WriteUInt8(data_ir.padding_payload_len() & 0xff); + } + builder.WriteBytes(data_ir.data(), data_ir.data_len()); + if (data_ir.padding_payload_len() > 0) { + SpdyString padding(data_ir.padding_payload_len(), 0); + builder.WriteBytes(padding.data(), padding.length()); + } + DCHECK_EQ(size_with_padding, builder.length()); + return builder.take(); +} + +SpdySerializedFrame SpdyFramer::SerializeDataFrameHeaderWithPaddingLengthField( + const SpdyDataIR& data_ir) { + uint8_t flags = DATA_FLAG_NONE; + size_t frame_size = 0; + size_t num_padding_fields = 0; + SerializeDataFrameHeaderWithPaddingLengthFieldBuilderHelper( + data_ir, &flags, &frame_size, &num_padding_fields); + + SpdyFrameBuilder builder(frame_size); + builder.BeginNewFrame( + SpdyFrameType::DATA, flags, data_ir.stream_id(), + num_padding_fields + data_ir.data_len() + data_ir.padding_payload_len()); + if (data_ir.padded()) { + builder.WriteUInt8(data_ir.padding_payload_len() & 0xff); + } + DCHECK_EQ(frame_size, builder.length()); + return builder.take(); +} + +SpdySerializedFrame SpdyFramer::SerializeRstStream( + const SpdyRstStreamIR& rst_stream) const { + size_t expected_length = kRstStreamFrameSize; + SpdyFrameBuilder builder(expected_length); + + builder.BeginNewFrame(SpdyFrameType::RST_STREAM, 0, rst_stream.stream_id()); + + builder.WriteUInt32(rst_stream.error_code()); + + DCHECK_EQ(expected_length, builder.length()); + return builder.take(); +} + +SpdySerializedFrame SpdyFramer::SerializeSettings( + const SpdySettingsIR& settings) const { + uint8_t flags = 0; + // Size, in bytes, of this SETTINGS frame. + size_t size = 0; + const SettingsMap* values = &(settings.values()); + SerializeSettingsBuilderHelper(settings, &flags, values, &size); + SpdyFrameBuilder builder(size); + builder.BeginNewFrame(SpdyFrameType::SETTINGS, flags, 0); + + // If this is an ACK, payload should be empty. + if (settings.is_ack()) { + return builder.take(); + } + + DCHECK_EQ(kSettingsFrameMinimumSize, builder.length()); + for (auto it = values->begin(); it != values->end(); ++it) { + int setting_id = it->first; + DCHECK_GE(setting_id, 0); + builder.WriteUInt16(static_cast<SpdySettingsId>(setting_id)); + builder.WriteUInt32(it->second); + } + DCHECK_EQ(size, builder.length()); + return builder.take(); +} + +SpdySerializedFrame SpdyFramer::SerializePing(const SpdyPingIR& ping) const { + SpdyFrameBuilder builder(kPingFrameSize); + uint8_t flags = 0; + if (ping.is_ack()) { + flags |= PING_FLAG_ACK; + } + builder.BeginNewFrame(SpdyFrameType::PING, flags, 0); + builder.WriteUInt64(ping.id()); + DCHECK_EQ(kPingFrameSize, builder.length()); + return builder.take(); +} + +SpdySerializedFrame SpdyFramer::SerializeGoAway( + const SpdyGoAwayIR& goaway) const { + // Compute the output buffer size, take opaque data into account. + size_t expected_length = kGoawayFrameMinimumSize; + expected_length += goaway.description().size(); + SpdyFrameBuilder builder(expected_length); + + // Serialize the GOAWAY frame. + builder.BeginNewFrame(SpdyFrameType::GOAWAY, 0, 0); + + // GOAWAY frames specify the last good stream id. + builder.WriteUInt32(goaway.last_good_stream_id()); + + // GOAWAY frames also specify the error code. + builder.WriteUInt32(goaway.error_code()); + + // GOAWAY frames may also specify opaque data. + if (!goaway.description().empty()) { + builder.WriteBytes(goaway.description().data(), + goaway.description().size()); + } + + DCHECK_EQ(expected_length, builder.length()); + return builder.take(); +} + +void SpdyFramer::SerializeHeadersBuilderHelper(const SpdyHeadersIR& headers, + uint8_t* flags, + size_t* size, + SpdyString* hpack_encoding, + int* weight, + size_t* length_field) { + if (headers.fin()) { + *flags = *flags | CONTROL_FLAG_FIN; + } + // This will get overwritten if we overflow into a CONTINUATION frame. + *flags = *flags | HEADERS_FLAG_END_HEADERS; + if (headers.has_priority()) { + *flags = *flags | HEADERS_FLAG_PRIORITY; + } + if (headers.padded()) { + *flags = *flags | HEADERS_FLAG_PADDED; + } + + *size = kHeadersFrameMinimumSize; + + if (headers.padded()) { + *size = *size + kPadLengthFieldSize; + *size = *size + headers.padding_payload_len(); + } + + if (headers.has_priority()) { + *weight = ClampHttp2Weight(headers.weight()); + *size = *size + 5; + } + + GetHpackEncoder()->EncodeHeaderSet(headers.header_block(), hpack_encoding); + *size = *size + hpack_encoding->size(); + if (*size > kHttp2MaxControlFrameSendSize) { + *size = *size + GetNumberRequiredContinuationFrames(*size) * + kContinuationFrameMinimumSize; + *flags = *flags & ~HEADERS_FLAG_END_HEADERS; + } + // Compute frame length field. + if (headers.padded()) { + *length_field = *length_field + kPadLengthFieldSize; + } + if (headers.has_priority()) { + *length_field = *length_field + 4; // Dependency field. + *length_field = *length_field + 1; // Weight field. + } + *length_field = *length_field + headers.padding_payload_len(); + *length_field = *length_field + hpack_encoding->size(); + // If the HEADERS frame with payload would exceed the max frame size, then + // WritePayloadWithContinuation() will serialize CONTINUATION frames as + // necessary. + *length_field = + std::min(*length_field, kHttp2MaxControlFrameSendSize - kFrameHeaderSize); +} + +SpdySerializedFrame SpdyFramer::SerializeHeaders(const SpdyHeadersIR& headers) { + uint8_t flags = 0; + // The size of this frame, including padding (if there is any) and + // variable-length header block. + size_t size = 0; + SpdyString hpack_encoding; + int weight = 0; + size_t length_field = 0; + SerializeHeadersBuilderHelper(headers, &flags, &size, &hpack_encoding, + &weight, &length_field); + + SpdyFrameBuilder builder(size); + builder.BeginNewFrame(SpdyFrameType::HEADERS, flags, headers.stream_id(), + length_field); + + DCHECK_EQ(kHeadersFrameMinimumSize, builder.length()); + + int padding_payload_len = 0; + if (headers.padded()) { + builder.WriteUInt8(headers.padding_payload_len()); + padding_payload_len = headers.padding_payload_len(); + } + if (headers.has_priority()) { + builder.WriteUInt32(PackStreamDependencyValues(headers.exclusive(), + headers.parent_stream_id())); + // Per RFC 7540 section 6.3, serialized weight value is actual value - 1. + builder.WriteUInt8(weight - 1); + } + WritePayloadWithContinuation(&builder, hpack_encoding, headers.stream_id(), + SpdyFrameType::HEADERS, padding_payload_len); + + if (debug_visitor_) { + const size_t header_list_size = + GetUncompressedSerializedLength(headers.header_block()); + debug_visitor_->OnSendCompressedFrame(headers.stream_id(), + SpdyFrameType::HEADERS, + header_list_size, builder.length()); + } + + return builder.take(); +} + +SpdySerializedFrame SpdyFramer::SerializeWindowUpdate( + const SpdyWindowUpdateIR& window_update) { + SpdyFrameBuilder builder(kWindowUpdateFrameSize); + builder.BeginNewFrame(SpdyFrameType::WINDOW_UPDATE, kNoFlags, + window_update.stream_id()); + builder.WriteUInt32(window_update.delta()); + DCHECK_EQ(kWindowUpdateFrameSize, builder.length()); + return builder.take(); +} + +void SpdyFramer::SerializePushPromiseBuilderHelper( + const SpdyPushPromiseIR& push_promise, + uint8_t* flags, + SpdyString* hpack_encoding, + size_t* size) { + *flags = 0; + // This will get overwritten if we overflow into a CONTINUATION frame. + *flags = *flags | PUSH_PROMISE_FLAG_END_PUSH_PROMISE; + // The size of this frame, including variable-length name-value block. + *size = kPushPromiseFrameMinimumSize; + + if (push_promise.padded()) { + *flags = *flags | PUSH_PROMISE_FLAG_PADDED; + *size = *size + kPadLengthFieldSize; + *size = *size + push_promise.padding_payload_len(); + } + + GetHpackEncoder()->EncodeHeaderSet(push_promise.header_block(), + hpack_encoding); + *size = *size + hpack_encoding->size(); + if (*size > kHttp2MaxControlFrameSendSize) { + *size = *size + GetNumberRequiredContinuationFrames(*size) * + kContinuationFrameMinimumSize; + *flags = *flags & ~PUSH_PROMISE_FLAG_END_PUSH_PROMISE; + } +} + +SpdySerializedFrame SpdyFramer::SerializePushPromise( + const SpdyPushPromiseIR& push_promise) { + uint8_t flags = 0; + size_t size = 0; + SpdyString hpack_encoding; + SerializePushPromiseBuilderHelper(push_promise, &flags, &hpack_encoding, + &size); + + SpdyFrameBuilder builder(size); + size_t length = + std::min(size, kHttp2MaxControlFrameSendSize) - kFrameHeaderSize; + builder.BeginNewFrame(SpdyFrameType::PUSH_PROMISE, flags, + push_promise.stream_id(), length); + int padding_payload_len = 0; + if (push_promise.padded()) { + builder.WriteUInt8(push_promise.padding_payload_len()); + builder.WriteUInt32(push_promise.promised_stream_id()); + DCHECK_EQ(kPushPromiseFrameMinimumSize + kPadLengthFieldSize, + builder.length()); + + padding_payload_len = push_promise.padding_payload_len(); + } else { + builder.WriteUInt32(push_promise.promised_stream_id()); + DCHECK_EQ(kPushPromiseFrameMinimumSize, builder.length()); + } + + WritePayloadWithContinuation( + &builder, hpack_encoding, push_promise.stream_id(), + SpdyFrameType::PUSH_PROMISE, padding_payload_len); + + if (debug_visitor_) { + const size_t header_list_size = + GetUncompressedSerializedLength(push_promise.header_block()); + debug_visitor_->OnSendCompressedFrame(push_promise.stream_id(), + SpdyFrameType::PUSH_PROMISE, + header_list_size, builder.length()); + } + + return builder.take(); +} + +SpdySerializedFrame SpdyFramer::SerializeContinuation( + const SpdyContinuationIR& continuation) const { + const SpdyString& encoding = continuation.encoding(); + size_t frame_size = kContinuationFrameMinimumSize + encoding.size(); + SpdyFrameBuilder builder(frame_size); + uint8_t flags = continuation.end_headers() ? HEADERS_FLAG_END_HEADERS : 0; + builder.BeginNewFrame(SpdyFrameType::CONTINUATION, flags, + continuation.stream_id()); + DCHECK_EQ(kFrameHeaderSize, builder.length()); + + builder.WriteBytes(encoding.data(), encoding.size()); + return builder.take(); +} + +SpdySerializedFrame SpdyFramer::SerializeAltSvc(const SpdyAltSvcIR& altsvc_ir) { + SpdyString value; + size_t size = 0; + SerializeAltSvcBuilderHelper(altsvc_ir, &value, &size); + SpdyFrameBuilder builder(size); + builder.BeginNewFrame(SpdyFrameType::ALTSVC, kNoFlags, altsvc_ir.stream_id()); + + builder.WriteUInt16(altsvc_ir.origin().length()); + builder.WriteBytes(altsvc_ir.origin().data(), altsvc_ir.origin().length()); + builder.WriteBytes(value.data(), value.length()); + DCHECK_LT(kGetAltSvcFrameMinimumSize, builder.length()); + return builder.take(); +} + +SpdySerializedFrame SpdyFramer::SerializePriority( + const SpdyPriorityIR& priority) const { + SpdyFrameBuilder builder(kPriorityFrameSize); + builder.BeginNewFrame(SpdyFrameType::PRIORITY, kNoFlags, + priority.stream_id()); + + builder.WriteUInt32(PackStreamDependencyValues(priority.exclusive(), + priority.parent_stream_id())); + // Per RFC 7540 section 6.3, serialized weight value is actual value - 1. + builder.WriteUInt8(priority.weight() - 1); + DCHECK_EQ(kPriorityFrameSize, builder.length()); + return builder.take(); +} + +SpdySerializedFrame SpdyFramer::SerializeUnknown( + const SpdyUnknownIR& unknown) const { + const size_t total_size = kFrameHeaderSize + unknown.payload().size(); + SpdyFrameBuilder builder(total_size); + builder.BeginNewUncheckedFrame(unknown.type(), unknown.flags(), + unknown.stream_id(), unknown.length()); + builder.WriteBytes(unknown.payload().data(), unknown.payload().size()); + return builder.take(); +} + +namespace { + +class FrameSerializationVisitor : public SpdyFrameVisitor { + public: + explicit FrameSerializationVisitor(SpdyFramer* framer) + : framer_(framer), frame_() {} + ~FrameSerializationVisitor() override = default; + + SpdySerializedFrame ReleaseSerializedFrame() { return std::move(frame_); } + + void VisitData(const SpdyDataIR& data) override { + frame_ = framer_->SerializeData(data); + } + void VisitRstStream(const SpdyRstStreamIR& rst_stream) override { + frame_ = framer_->SerializeRstStream(rst_stream); + } + void VisitSettings(const SpdySettingsIR& settings) override { + frame_ = framer_->SerializeSettings(settings); + } + void VisitPing(const SpdyPingIR& ping) override { + frame_ = framer_->SerializePing(ping); + } + void VisitGoAway(const SpdyGoAwayIR& goaway) override { + frame_ = framer_->SerializeGoAway(goaway); + } + void VisitHeaders(const SpdyHeadersIR& headers) override { + frame_ = framer_->SerializeHeaders(headers); + } + void VisitWindowUpdate(const SpdyWindowUpdateIR& window_update) override { + frame_ = framer_->SerializeWindowUpdate(window_update); + } + void VisitPushPromise(const SpdyPushPromiseIR& push_promise) override { + frame_ = framer_->SerializePushPromise(push_promise); + } + void VisitContinuation(const SpdyContinuationIR& continuation) override { + frame_ = framer_->SerializeContinuation(continuation); + } + void VisitAltSvc(const SpdyAltSvcIR& altsvc) override { + frame_ = framer_->SerializeAltSvc(altsvc); + } + void VisitPriority(const SpdyPriorityIR& priority) override { + frame_ = framer_->SerializePriority(priority); + } + void VisitUnknown(const SpdyUnknownIR& unknown) override { + frame_ = framer_->SerializeUnknown(unknown); + } + + private: + SpdyFramer* framer_; + SpdySerializedFrame frame_; +}; + +// TODO(diannahu): Use also in frame serialization. +class FlagsSerializationVisitor : public SpdyFrameVisitor { + public: + void VisitData(const SpdyDataIR& data) override { + flags_ = DATA_FLAG_NONE; + if (data.fin()) { + flags_ |= DATA_FLAG_FIN; + } + if (data.padded()) { + flags_ |= DATA_FLAG_PADDED; + } + } + + void VisitRstStream(const SpdyRstStreamIR& rst_stream) override { + flags_ = kNoFlags; + } + + void VisitSettings(const SpdySettingsIR& settings) override { + flags_ = kNoFlags; + if (settings.is_ack()) { + flags_ |= SETTINGS_FLAG_ACK; + } + } + + void VisitPing(const SpdyPingIR& ping) override { + flags_ = kNoFlags; + if (ping.is_ack()) { + flags_ |= PING_FLAG_ACK; + } + } + + void VisitGoAway(const SpdyGoAwayIR& goaway) override { flags_ = kNoFlags; } + + // TODO(diannahu): The END_HEADERS flag is incorrect for HEADERS that require + // CONTINUATION frames. + void VisitHeaders(const SpdyHeadersIR& headers) override { + flags_ = HEADERS_FLAG_END_HEADERS; + if (headers.fin()) { + flags_ |= CONTROL_FLAG_FIN; + } + if (headers.padded()) { + flags_ |= HEADERS_FLAG_PADDED; + } + if (headers.has_priority()) { + flags_ |= HEADERS_FLAG_PRIORITY; + } + } + + void VisitWindowUpdate(const SpdyWindowUpdateIR& window_update) override { + flags_ = kNoFlags; + } + + // TODO(diannahu): The END_PUSH_PROMISE flag is incorrect for PUSH_PROMISEs + // that require CONTINUATION frames. + void VisitPushPromise(const SpdyPushPromiseIR& push_promise) override { + flags_ = PUSH_PROMISE_FLAG_END_PUSH_PROMISE; + if (push_promise.padded()) { + flags_ |= PUSH_PROMISE_FLAG_PADDED; + } + } + + // TODO(diannahu): The END_HEADERS flag is incorrect for CONTINUATIONs that + // require CONTINUATION frames. + void VisitContinuation(const SpdyContinuationIR& continuation) override { + flags_ = HEADERS_FLAG_END_HEADERS; + } + + void VisitAltSvc(const SpdyAltSvcIR& altsvc) override { flags_ = kNoFlags; } + + void VisitPriority(const SpdyPriorityIR& priority) override { + flags_ = kNoFlags; + } + + uint8_t flags() const { return flags_; } + + private: + uint8_t flags_ = kNoFlags; +}; + +} // namespace + +SpdySerializedFrame SpdyFramer::SerializeFrame(const SpdyFrameIR& frame) { + FrameSerializationVisitor visitor(this); + frame.Visit(&visitor); + return visitor.ReleaseSerializedFrame(); +} + +uint8_t SpdyFramer::GetSerializedFlags(const SpdyFrameIR& frame) { + FlagsSerializationVisitor visitor; + frame.Visit(&visitor); + return visitor.flags(); +} + +bool SpdyFramer::SerializeData(const SpdyDataIR& data_ir, + ZeroCopyOutputBuffer* output) const { + uint8_t flags = DATA_FLAG_NONE; + int num_padding_fields = 0; + size_t size_with_padding = 0; + SerializeDataBuilderHelper(data_ir, &flags, &num_padding_fields, + &size_with_padding); + SpdyFrameBuilder builder(size_with_padding, output); + + bool ok = + builder.BeginNewFrame(SpdyFrameType::DATA, flags, data_ir.stream_id()); + + if (data_ir.padded()) { + ok = ok && builder.WriteUInt8(data_ir.padding_payload_len() & 0xff); + } + + ok = ok && builder.WriteBytes(data_ir.data(), data_ir.data_len()); + if (data_ir.padding_payload_len() > 0) { + SpdyString padding; + padding = SpdyString(data_ir.padding_payload_len(), 0); + ok = ok && builder.WriteBytes(padding.data(), padding.length()); + } + DCHECK_EQ(size_with_padding, builder.length()); + return ok; +} + +bool SpdyFramer::SerializeDataFrameHeaderWithPaddingLengthField( + const SpdyDataIR& data_ir, + ZeroCopyOutputBuffer* output) const { + uint8_t flags = DATA_FLAG_NONE; + size_t frame_size = 0; + size_t num_padding_fields = 0; + SerializeDataFrameHeaderWithPaddingLengthFieldBuilderHelper( + data_ir, &flags, &frame_size, &num_padding_fields); + + SpdyFrameBuilder builder(frame_size, output); + bool ok = true; + ok = ok && + builder.BeginNewFrame(SpdyFrameType::DATA, flags, data_ir.stream_id(), + num_padding_fields + data_ir.data_len() + + data_ir.padding_payload_len()); + if (data_ir.padded()) { + ok = ok && builder.WriteUInt8(data_ir.padding_payload_len() & 0xff); + } + DCHECK_EQ(frame_size, builder.length()); + return ok; +} + +bool SpdyFramer::SerializeRstStream(const SpdyRstStreamIR& rst_stream, + ZeroCopyOutputBuffer* output) const { + size_t expected_length = kRstStreamFrameSize; + SpdyFrameBuilder builder(expected_length, output); + bool ok = builder.BeginNewFrame(SpdyFrameType::RST_STREAM, 0, + rst_stream.stream_id()); + ok = ok && builder.WriteUInt32(rst_stream.error_code()); + + DCHECK_EQ(expected_length, builder.length()); + return ok; +} + +bool SpdyFramer::SerializeSettings(const SpdySettingsIR& settings, + ZeroCopyOutputBuffer* output) const { + uint8_t flags = 0; + // Size, in bytes, of this SETTINGS frame. + size_t size = 0; + const SettingsMap* values = &(settings.values()); + SerializeSettingsBuilderHelper(settings, &flags, values, &size); + SpdyFrameBuilder builder(size, output); + bool ok = builder.BeginNewFrame(SpdyFrameType::SETTINGS, flags, 0); + + // If this is an ACK, payload should be empty. + if (settings.is_ack()) { + return ok; + } + + DCHECK_EQ(kSettingsFrameMinimumSize, builder.length()); + for (auto it = values->begin(); it != values->end(); ++it) { + int setting_id = it->first; + DCHECK_GE(setting_id, 0); + ok = ok && builder.WriteUInt16(static_cast<SpdySettingsId>(setting_id)) && + builder.WriteUInt32(it->second); + } + DCHECK_EQ(size, builder.length()); + return ok; +} + +bool SpdyFramer::SerializePing(const SpdyPingIR& ping, + ZeroCopyOutputBuffer* output) const { + SpdyFrameBuilder builder(kPingFrameSize, output); + uint8_t flags = 0; + if (ping.is_ack()) { + flags |= PING_FLAG_ACK; + } + bool ok = builder.BeginNewFrame(SpdyFrameType::PING, flags, 0); + ok = ok && builder.WriteUInt64(ping.id()); + DCHECK_EQ(kPingFrameSize, builder.length()); + return ok; +} + +bool SpdyFramer::SerializeGoAway(const SpdyGoAwayIR& goaway, + ZeroCopyOutputBuffer* output) const { + // Compute the output buffer size, take opaque data into account. + size_t expected_length = kGoawayFrameMinimumSize; + expected_length += goaway.description().size(); + SpdyFrameBuilder builder(expected_length, output); + + // Serialize the GOAWAY frame. + bool ok = builder.BeginNewFrame(SpdyFrameType::GOAWAY, 0, 0); + + // GOAWAY frames specify the last good stream id. + ok = ok && builder.WriteUInt32(goaway.last_good_stream_id()) && + // GOAWAY frames also specify the error status code. + builder.WriteUInt32(goaway.error_code()); + + // GOAWAY frames may also specify opaque data. + if (!goaway.description().empty()) { + ok = ok && builder.WriteBytes(goaway.description().data(), + goaway.description().size()); + } + + DCHECK_EQ(expected_length, builder.length()); + return ok; +} + +bool SpdyFramer::SerializeHeaders(const SpdyHeadersIR& headers, + ZeroCopyOutputBuffer* output) { + uint8_t flags = 0; + // The size of this frame, including padding (if there is any) and + // variable-length header block. + size_t size = 0; + SpdyString hpack_encoding; + int weight = 0; + size_t length_field = 0; + SerializeHeadersBuilderHelper(headers, &flags, &size, &hpack_encoding, + &weight, &length_field); + + bool ok = true; + SpdyFrameBuilder builder(size, output); + ok = ok && builder.BeginNewFrame(SpdyFrameType::HEADERS, flags, + headers.stream_id(), length_field); + DCHECK_EQ(kHeadersFrameMinimumSize, builder.length()); + + int padding_payload_len = 0; + if (headers.padded()) { + ok = ok && builder.WriteUInt8(headers.padding_payload_len()); + padding_payload_len = headers.padding_payload_len(); + } + if (headers.has_priority()) { + ok = ok && + builder.WriteUInt32(PackStreamDependencyValues( + headers.exclusive(), headers.parent_stream_id())) && + // Per RFC 7540 section 6.3, serialized weight value is weight - 1. + builder.WriteUInt8(weight - 1); + } + ok = ok && WritePayloadWithContinuation( + &builder, hpack_encoding, headers.stream_id(), + SpdyFrameType::HEADERS, padding_payload_len); + + if (debug_visitor_) { + const size_t header_list_size = + GetUncompressedSerializedLength(headers.header_block()); + debug_visitor_->OnSendCompressedFrame(headers.stream_id(), + SpdyFrameType::HEADERS, + header_list_size, builder.length()); + } + + return ok; +} + +bool SpdyFramer::SerializeWindowUpdate(const SpdyWindowUpdateIR& window_update, + ZeroCopyOutputBuffer* output) const { + SpdyFrameBuilder builder(kWindowUpdateFrameSize, output); + bool ok = builder.BeginNewFrame(SpdyFrameType::WINDOW_UPDATE, kNoFlags, + window_update.stream_id()); + ok = ok && builder.WriteUInt32(window_update.delta()); + DCHECK_EQ(kWindowUpdateFrameSize, builder.length()); + return ok; +} + +bool SpdyFramer::SerializePushPromise(const SpdyPushPromiseIR& push_promise, + ZeroCopyOutputBuffer* output) { + uint8_t flags = 0; + size_t size = 0; + SpdyString hpack_encoding; + SerializePushPromiseBuilderHelper(push_promise, &flags, &hpack_encoding, + &size); + + bool ok = true; + SpdyFrameBuilder builder(size, output); + size_t length = + std::min(size, kHttp2MaxControlFrameSendSize) - kFrameHeaderSize; + ok = builder.BeginNewFrame(SpdyFrameType::PUSH_PROMISE, flags, + push_promise.stream_id(), length); + + int padding_payload_len = 0; + if (push_promise.padded()) { + ok = ok && builder.WriteUInt8(push_promise.padding_payload_len()) && + builder.WriteUInt32(push_promise.promised_stream_id()); + DCHECK_EQ(kPushPromiseFrameMinimumSize + kPadLengthFieldSize, + builder.length()); + + padding_payload_len = push_promise.padding_payload_len(); + } else { + ok = ok && builder.WriteUInt32(push_promise.promised_stream_id()); + DCHECK_EQ(kPushPromiseFrameMinimumSize, builder.length()); + } + + ok = ok && WritePayloadWithContinuation( + &builder, hpack_encoding, push_promise.stream_id(), + SpdyFrameType::PUSH_PROMISE, padding_payload_len); + + if (debug_visitor_) { + const size_t header_list_size = + GetUncompressedSerializedLength(push_promise.header_block()); + debug_visitor_->OnSendCompressedFrame(push_promise.stream_id(), + SpdyFrameType::PUSH_PROMISE, + header_list_size, builder.length()); + } + + return ok; +} + +bool SpdyFramer::SerializeContinuation(const SpdyContinuationIR& continuation, + ZeroCopyOutputBuffer* output) const { + const SpdyString& encoding = continuation.encoding(); + size_t frame_size = kContinuationFrameMinimumSize + encoding.size(); + SpdyFrameBuilder builder(frame_size, output); + uint8_t flags = continuation.end_headers() ? HEADERS_FLAG_END_HEADERS : 0; + bool ok = builder.BeginNewFrame(SpdyFrameType::CONTINUATION, flags, + continuation.stream_id(), + frame_size - kFrameHeaderSize); + DCHECK_EQ(kFrameHeaderSize, builder.length()); + + ok = ok && builder.WriteBytes(encoding.data(), encoding.size()); + return ok; +} + +bool SpdyFramer::SerializeAltSvc(const SpdyAltSvcIR& altsvc_ir, + ZeroCopyOutputBuffer* output) { + SpdyString value; + size_t size = 0; + SerializeAltSvcBuilderHelper(altsvc_ir, &value, &size); + SpdyFrameBuilder builder(size, output); + bool ok = builder.BeginNewFrame(SpdyFrameType::ALTSVC, kNoFlags, + altsvc_ir.stream_id()) && + builder.WriteUInt16(altsvc_ir.origin().length()) && + builder.WriteBytes(altsvc_ir.origin().data(), + altsvc_ir.origin().length()) && + builder.WriteBytes(value.data(), value.length()); + DCHECK_LT(kGetAltSvcFrameMinimumSize, builder.length()); + return ok; +} + +bool SpdyFramer::SerializePriority(const SpdyPriorityIR& priority, + ZeroCopyOutputBuffer* output) const { + SpdyFrameBuilder builder(kPriorityFrameSize, output); + bool ok = builder.BeginNewFrame(SpdyFrameType::PRIORITY, kNoFlags, + priority.stream_id()); + ok = ok && + builder.WriteUInt32(PackStreamDependencyValues( + priority.exclusive(), priority.parent_stream_id())) && + // Per RFC 7540 section 6.3, serialized weight value is actual value - 1. + builder.WriteUInt8(priority.weight() - 1); + DCHECK_EQ(kPriorityFrameSize, builder.length()); + return ok; +} + +bool SpdyFramer::SerializeUnknown(const SpdyUnknownIR& unknown, + ZeroCopyOutputBuffer* output) const { + const size_t total_size = kFrameHeaderSize + unknown.payload().size(); + SpdyFrameBuilder builder(total_size, output); + bool ok = builder.BeginNewUncheckedFrame( + unknown.type(), unknown.flags(), unknown.stream_id(), unknown.length()); + ok = ok && + builder.WriteBytes(unknown.payload().data(), unknown.payload().size()); + return ok; +} + +namespace { + +class FrameSerializationVisitorWithOutput : public SpdyFrameVisitor { + public: + explicit FrameSerializationVisitorWithOutput(SpdyFramer* framer, + ZeroCopyOutputBuffer* output) + : framer_(framer), output_(output), result_(false) {} + ~FrameSerializationVisitorWithOutput() override = default; + + size_t Result() { return result_; } + + void VisitData(const SpdyDataIR& data) override { + result_ = framer_->SerializeData(data, output_); + } + void VisitRstStream(const SpdyRstStreamIR& rst_stream) override { + result_ = framer_->SerializeRstStream(rst_stream, output_); + } + void VisitSettings(const SpdySettingsIR& settings) override { + result_ = framer_->SerializeSettings(settings, output_); + } + void VisitPing(const SpdyPingIR& ping) override { + result_ = framer_->SerializePing(ping, output_); + } + void VisitGoAway(const SpdyGoAwayIR& goaway) override { + result_ = framer_->SerializeGoAway(goaway, output_); + } + void VisitHeaders(const SpdyHeadersIR& headers) override { + result_ = framer_->SerializeHeaders(headers, output_); + } + void VisitWindowUpdate(const SpdyWindowUpdateIR& window_update) override { + result_ = framer_->SerializeWindowUpdate(window_update, output_); + } + void VisitPushPromise(const SpdyPushPromiseIR& push_promise) override { + result_ = framer_->SerializePushPromise(push_promise, output_); + } + void VisitContinuation(const SpdyContinuationIR& continuation) override { + result_ = framer_->SerializeContinuation(continuation, output_); + } + void VisitAltSvc(const SpdyAltSvcIR& altsvc) override { + result_ = framer_->SerializeAltSvc(altsvc, output_); + } + void VisitPriority(const SpdyPriorityIR& priority) override { + result_ = framer_->SerializePriority(priority, output_); + } + void VisitUnknown(const SpdyUnknownIR& unknown) override { + result_ = framer_->SerializeUnknown(unknown, output_); + } + + private: + SpdyFramer* framer_; + ZeroCopyOutputBuffer* output_; + bool result_; +}; + +} // namespace + +size_t SpdyFramer::SerializeFrame(const SpdyFrameIR& frame, + ZeroCopyOutputBuffer* output) { + FrameSerializationVisitorWithOutput visitor(this, output); + size_t free_bytes_before = output->BytesFree(); + frame.Visit(&visitor); + return visitor.Result() ? free_bytes_before - output->BytesFree() : 0; +} + +HpackEncoder* SpdyFramer::GetHpackEncoder() { + if (hpack_encoder_ == nullptr) { + hpack_encoder_ = SpdyMakeUnique<HpackEncoder>(ObtainHpackHuffmanTable()); + if (!compression_enabled()) { + hpack_encoder_->DisableCompression(); + } + } + return hpack_encoder_.get(); +} + +void SpdyFramer::UpdateHeaderEncoderTableSize(uint32_t value) { + GetHpackEncoder()->ApplyHeaderTableSizeSetting(value); +} + +size_t SpdyFramer::header_encoder_table_size() const { + if (hpack_encoder_ == nullptr) { + return kDefaultHeaderTableSizeSetting; + } else { + return hpack_encoder_->CurrentHeaderTableSizeSetting(); + } +} + +void SpdyFramer::SetEncoderHeaderTableDebugVisitor( + std::unique_ptr<HpackHeaderTable::DebugVisitorInterface> visitor) { + GetHpackEncoder()->SetHeaderTableDebugVisitor(std::move(visitor)); +} + +size_t SpdyFramer::EstimateMemoryUsage() const { + return SpdyEstimateMemoryUsage(hpack_encoder_); +} + +} // namespace spdy diff --git a/chromium/net/third_party/quiche/src/spdy/core/spdy_framer.h b/chromium/net/third_party/quiche/src/spdy/core/spdy_framer.h new file mode 100644 index 00000000000..e268994e89b --- /dev/null +++ b/chromium/net/third_party/quiche/src/spdy/core/spdy_framer.h @@ -0,0 +1,365 @@ +// Copyright (c) 2012 The Chromium 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_SPDY_CORE_SPDY_FRAMER_H_ +#define QUICHE_SPDY_CORE_SPDY_FRAMER_H_ + +#include <stddef.h> + +#include <cstdint> +#include <map> +#include <memory> +#include <utility> + +#include "net/third_party/quiche/src/spdy/core/hpack/hpack_encoder.h" +#include "net/third_party/quiche/src/spdy/core/spdy_alt_svc_wire_format.h" +#include "net/third_party/quiche/src/spdy/core/spdy_header_block.h" +#include "net/third_party/quiche/src/spdy/core/spdy_headers_handler_interface.h" +#include "net/third_party/quiche/src/spdy/core/spdy_protocol.h" +#include "net/third_party/quiche/src/spdy/core/zero_copy_output_buffer.h" +#include "net/third_party/quiche/src/spdy/platform/api/spdy_export.h" +#include "net/third_party/quiche/src/spdy/platform/api/spdy_string.h" +#include "net/third_party/quiche/src/spdy/platform/api/spdy_string_piece.h" + +namespace spdy { + +namespace test { + +class SpdyFramerPeer; +class SpdyFramerTest_MultipleContinuationFramesWithIterator_Test; +class SpdyFramerTest_PushPromiseFramesWithIterator_Test; + +} // namespace test + +class SPDY_EXPORT_PRIVATE SpdyFrameSequence { + public: + virtual ~SpdyFrameSequence() {} + + // Serializes the next frame in the sequence to |output|. Returns the number + // of bytes written to |output|. + virtual size_t NextFrame(ZeroCopyOutputBuffer* output) = 0; + + // Returns true iff there is at least one more frame in the sequence. + virtual bool HasNextFrame() const = 0; + + // Get SpdyFrameIR of the frame to be serialized. + virtual const SpdyFrameIR& GetIR() const = 0; +}; + +class SPDY_EXPORT_PRIVATE SpdyFramer { + public: + enum CompressionOption { + ENABLE_COMPRESSION, + DISABLE_COMPRESSION, + }; + + // Create a SpdyFrameSequence to serialize |frame_ir|. + static std::unique_ptr<SpdyFrameSequence> CreateIterator( + SpdyFramer* framer, + std::unique_ptr<const SpdyFrameIR> frame_ir); + + // Gets the serialized flags for the given |frame|. + static uint8_t GetSerializedFlags(const SpdyFrameIR& frame); + + // Serialize a data frame. + static SpdySerializedFrame SerializeData(const SpdyDataIR& data_ir); + // Serializes the data frame header and optionally padding length fields, + // excluding actual data payload and padding. + static SpdySerializedFrame SerializeDataFrameHeaderWithPaddingLengthField( + const SpdyDataIR& data_ir); + + // Serializes a WINDOW_UPDATE frame. The WINDOW_UPDATE + // frame is used to implement per stream flow control. + static SpdySerializedFrame SerializeWindowUpdate( + const SpdyWindowUpdateIR& window_update); + + explicit SpdyFramer(CompressionOption option); + + virtual ~SpdyFramer(); + + // Set debug callbacks to be called from the framer. The debug visitor is + // completely optional and need not be set in order for normal operation. + // If this is called multiple times, only the last visitor will be used. + void set_debug_visitor(SpdyFramerDebugVisitorInterface* debug_visitor); + + SpdySerializedFrame SerializeRstStream( + const SpdyRstStreamIR& rst_stream) const; + + // Serializes a SETTINGS frame. The SETTINGS frame is + // used to communicate name/value pairs relevant to the communication channel. + SpdySerializedFrame SerializeSettings(const SpdySettingsIR& settings) const; + + // Serializes a PING frame. The unique_id is used to + // identify the ping request/response. + SpdySerializedFrame SerializePing(const SpdyPingIR& ping) const; + + // Serializes a GOAWAY frame. The GOAWAY frame is used + // prior to the shutting down of the TCP connection, and includes the + // stream_id of the last stream the sender of the frame is willing to process + // to completion. + SpdySerializedFrame SerializeGoAway(const SpdyGoAwayIR& goaway) const; + + // Serializes a HEADERS frame. The HEADERS frame is used + // for sending headers. + SpdySerializedFrame SerializeHeaders(const SpdyHeadersIR& headers); + + // Serializes a PUSH_PROMISE frame. The PUSH_PROMISE frame is used + // to inform the client that it will be receiving an additional stream + // in response to the original request. The frame includes synthesized + // headers to explain the upcoming data. + SpdySerializedFrame SerializePushPromise( + const SpdyPushPromiseIR& push_promise); + + // Serializes a CONTINUATION frame. The CONTINUATION frame is used + // to continue a sequence of header block fragments. + SpdySerializedFrame SerializeContinuation( + const SpdyContinuationIR& continuation) const; + + // Serializes an ALTSVC frame. The ALTSVC frame advertises the + // availability of an alternative service to the client. + SpdySerializedFrame SerializeAltSvc(const SpdyAltSvcIR& altsvc); + + // Serializes a PRIORITY frame. The PRIORITY frame advises a change in + // the relative priority of the given stream. + SpdySerializedFrame SerializePriority(const SpdyPriorityIR& priority) const; + + // Serializes an unknown frame given a frame header and payload. + SpdySerializedFrame SerializeUnknown(const SpdyUnknownIR& unknown) const; + + // Serialize a frame of unknown type. + SpdySerializedFrame SerializeFrame(const SpdyFrameIR& frame); + + // Serialize a data frame. + bool SerializeData(const SpdyDataIR& data, + ZeroCopyOutputBuffer* output) const; + + // Serializes the data frame header and optionally padding length fields, + // excluding actual data payload and padding. + bool SerializeDataFrameHeaderWithPaddingLengthField( + const SpdyDataIR& data, + ZeroCopyOutputBuffer* output) const; + + bool SerializeRstStream(const SpdyRstStreamIR& rst_stream, + ZeroCopyOutputBuffer* output) const; + + // Serializes a SETTINGS frame. The SETTINGS frame is + // used to communicate name/value pairs relevant to the communication channel. + bool SerializeSettings(const SpdySettingsIR& settings, + ZeroCopyOutputBuffer* output) const; + + // Serializes a PING frame. The unique_id is used to + // identify the ping request/response. + bool SerializePing(const SpdyPingIR& ping, + ZeroCopyOutputBuffer* output) const; + + // Serializes a GOAWAY frame. The GOAWAY frame is used + // prior to the shutting down of the TCP connection, and includes the + // stream_id of the last stream the sender of the frame is willing to process + // to completion. + bool SerializeGoAway(const SpdyGoAwayIR& goaway, + ZeroCopyOutputBuffer* output) const; + + // Serializes a HEADERS frame. The HEADERS frame is used + // for sending headers. + bool SerializeHeaders(const SpdyHeadersIR& headers, + ZeroCopyOutputBuffer* output); + + // Serializes a WINDOW_UPDATE frame. The WINDOW_UPDATE + // frame is used to implement per stream flow control. + bool SerializeWindowUpdate(const SpdyWindowUpdateIR& window_update, + ZeroCopyOutputBuffer* output) const; + + // Serializes a PUSH_PROMISE frame. The PUSH_PROMISE frame is used + // to inform the client that it will be receiving an additional stream + // in response to the original request. The frame includes synthesized + // headers to explain the upcoming data. + bool SerializePushPromise(const SpdyPushPromiseIR& push_promise, + ZeroCopyOutputBuffer* output); + + // Serializes a CONTINUATION frame. The CONTINUATION frame is used + // to continue a sequence of header block fragments. + bool SerializeContinuation(const SpdyContinuationIR& continuation, + ZeroCopyOutputBuffer* output) const; + + // Serializes an ALTSVC frame. The ALTSVC frame advertises the + // availability of an alternative service to the client. + bool SerializeAltSvc(const SpdyAltSvcIR& altsvc, + ZeroCopyOutputBuffer* output); + + // Serializes a PRIORITY frame. The PRIORITY frame advises a change in + // the relative priority of the given stream. + bool SerializePriority(const SpdyPriorityIR& priority, + ZeroCopyOutputBuffer* output) const; + + // Serializes an unknown frame given a frame header and payload. + bool SerializeUnknown(const SpdyUnknownIR& unknown, + ZeroCopyOutputBuffer* output) const; + + // Serialize a frame of unknown type. + size_t SerializeFrame(const SpdyFrameIR& frame, ZeroCopyOutputBuffer* output); + + // Returns whether this SpdyFramer will compress header blocks using HPACK. + bool compression_enabled() const { + return compression_option_ == ENABLE_COMPRESSION; + } + + void SetHpackIndexingPolicy(HpackEncoder::IndexingPolicy policy) { + GetHpackEncoder()->SetIndexingPolicy(std::move(policy)); + } + + // Updates the maximum size of the header encoder compression table. + void UpdateHeaderEncoderTableSize(uint32_t value); + + // Returns the maximum size of the header encoder compression table. + size_t header_encoder_table_size() const; + + void SetEncoderHeaderTableDebugVisitor( + std::unique_ptr<HpackHeaderTable::DebugVisitorInterface> visitor); + + // Get (and lazily initialize) the HPACK encoder state. + HpackEncoder* GetHpackEncoder(); + + // Returns the estimate of dynamically allocated memory in bytes. + size_t EstimateMemoryUsage() const; + + protected: + friend class test::SpdyFramerPeer; + friend class test::SpdyFramerTest_MultipleContinuationFramesWithIterator_Test; + friend class test::SpdyFramerTest_PushPromiseFramesWithIterator_Test; + + // Iteratively converts a SpdyFrameIR into an appropriate sequence of Spdy + // frames. + // Example usage: + // std::unique_ptr<SpdyFrameSequence> it = CreateIterator(framer, frame_ir); + // while (it->HasNextFrame()) { + // if(it->NextFrame(output) == 0) { + // // Write failed; + // } + // } + class SPDY_EXPORT_PRIVATE SpdyFrameIterator : public SpdyFrameSequence { + public: + // Creates an iterator with the provided framer. + // Does not take ownership of |framer|. + // |framer| must outlive this instance. + explicit SpdyFrameIterator(SpdyFramer* framer); + ~SpdyFrameIterator() override; + + // Serializes the next frame in the sequence to |output|. Returns the number + // of bytes written to |output|. + size_t NextFrame(ZeroCopyOutputBuffer* output) override; + + // Returns true iff there is at least one more frame in the sequence. + bool HasNextFrame() const override; + + // SpdyFrameIterator is neither copyable nor movable. + SpdyFrameIterator(const SpdyFrameIterator&) = delete; + SpdyFrameIterator& operator=(const SpdyFrameIterator&) = delete; + + protected: + virtual size_t GetFrameSizeSansBlock() const = 0; + virtual bool SerializeGivenEncoding(const SpdyString& encoding, + ZeroCopyOutputBuffer* output) const = 0; + + SpdyFramer* GetFramer() const { return framer_; } + + void SetEncoder(const SpdyFrameWithHeaderBlockIR* ir) { + encoder_ = + framer_->GetHpackEncoder()->EncodeHeaderSet(ir->header_block()); + } + + bool has_next_frame() const { return has_next_frame_; } + + private: + SpdyFramer* const framer_; + std::unique_ptr<HpackEncoder::ProgressiveEncoder> encoder_; + bool is_first_frame_; + bool has_next_frame_; + }; + + // Iteratively converts a SpdyHeadersIR (with a possibly huge + // SpdyHeaderBlock) into an appropriate sequence of SpdySerializedFrames, and + // write to the output. + class SPDY_EXPORT_PRIVATE SpdyHeaderFrameIterator : public SpdyFrameIterator { + public: + // Does not take ownership of |framer|. Take ownership of |headers_ir|. + SpdyHeaderFrameIterator(SpdyFramer* framer, + std::unique_ptr<const SpdyHeadersIR> headers_ir); + + ~SpdyHeaderFrameIterator() override; + + private: + const SpdyFrameIR& GetIR() const override; + size_t GetFrameSizeSansBlock() const override; + bool SerializeGivenEncoding(const SpdyString& encoding, + ZeroCopyOutputBuffer* output) const override; + + const std::unique_ptr<const SpdyHeadersIR> headers_ir_; + }; + + // Iteratively converts a SpdyPushPromiseIR (with a possibly huge + // SpdyHeaderBlock) into an appropriate sequence of SpdySerializedFrames, and + // write to the output. + class SPDY_EXPORT_PRIVATE SpdyPushPromiseFrameIterator + : public SpdyFrameIterator { + public: + // Does not take ownership of |framer|. Take ownership of |push_promise_ir|. + SpdyPushPromiseFrameIterator( + SpdyFramer* framer, + std::unique_ptr<const SpdyPushPromiseIR> push_promise_ir); + + ~SpdyPushPromiseFrameIterator() override; + + private: + const SpdyFrameIR& GetIR() const override; + size_t GetFrameSizeSansBlock() const override; + bool SerializeGivenEncoding(const SpdyString& encoding, + ZeroCopyOutputBuffer* output) const override; + + const std::unique_ptr<const SpdyPushPromiseIR> push_promise_ir_; + }; + + // Converts a SpdyFrameIR into one Spdy frame (a sequence of length 1), and + // write it to the output. + class SPDY_EXPORT_PRIVATE SpdyControlFrameIterator + : public SpdyFrameSequence { + public: + SpdyControlFrameIterator(SpdyFramer* framer, + std::unique_ptr<const SpdyFrameIR> frame_ir); + ~SpdyControlFrameIterator() override; + + size_t NextFrame(ZeroCopyOutputBuffer* output) override; + + bool HasNextFrame() const override; + + const SpdyFrameIR& GetIR() const override; + + private: + SpdyFramer* const framer_; + std::unique_ptr<const SpdyFrameIR> frame_ir_; + bool has_next_frame_ = true; + }; + + private: + void SerializeHeadersBuilderHelper(const SpdyHeadersIR& headers, + uint8_t* flags, + size_t* size, + SpdyString* hpack_encoding, + int* weight, + size_t* length_field); + void SerializePushPromiseBuilderHelper(const SpdyPushPromiseIR& push_promise, + uint8_t* flags, + SpdyString* hpack_encoding, + size_t* size); + + std::unique_ptr<HpackEncoder> hpack_encoder_; + + SpdyFramerDebugVisitorInterface* debug_visitor_; + + // Determines whether HPACK compression is used. + const CompressionOption compression_option_; +}; + +} // namespace spdy + +#endif // QUICHE_SPDY_CORE_SPDY_FRAMER_H_ diff --git a/chromium/net/third_party/quiche/src/spdy/core/spdy_framer_test.cc b/chromium/net/third_party/quiche/src/spdy/core/spdy_framer_test.cc new file mode 100644 index 00000000000..a08d7d78100 --- /dev/null +++ b/chromium/net/third_party/quiche/src/spdy/core/spdy_framer_test.cc @@ -0,0 +1,4833 @@ +// Copyright (c) 2012 The Chromium 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/spdy/core/spdy_framer.h" + +#include <stdlib.h> + +#include <algorithm> +#include <cstdint> +#include <limits> +#include <tuple> +#include <vector> + +#include "base/logging.h" +#include "base/macros.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "net/third_party/quiche/src/spdy/core/array_output_buffer.h" +#include "net/third_party/quiche/src/spdy/core/hpack/hpack_constants.h" +#include "net/third_party/quiche/src/spdy/core/mock_spdy_framer_visitor.h" +#include "net/third_party/quiche/src/spdy/core/spdy_bitmasks.h" +#include "net/third_party/quiche/src/spdy/core/spdy_frame_builder.h" +#include "net/third_party/quiche/src/spdy/core/spdy_frame_reader.h" +#include "net/third_party/quiche/src/spdy/core/spdy_protocol.h" +#include "net/third_party/quiche/src/spdy/core/spdy_test_utils.h" +#include "net/third_party/quiche/src/spdy/platform/api/spdy_arraysize.h" +#include "net/third_party/quiche/src/spdy/platform/api/spdy_flags.h" +#include "net/third_party/quiche/src/spdy/platform/api/spdy_ptr_util.h" +#include "net/third_party/quiche/src/spdy/platform/api/spdy_string.h" +#include "net/third_party/quiche/src/spdy/platform/api/spdy_string_utils.h" + +using ::http2::Http2DecoderAdapter; +using ::testing::_; + +namespace spdy { + +namespace test { + +namespace { + +const int64_t kSize = 1024 * 1024; +char output_buffer[kSize] = ""; + +// frame_list_char is used to hold frames to be compared with output_buffer. +const int64_t buffer_size = 64 * 1024; +char frame_list_char[buffer_size] = ""; +} // namespace + +class MockDebugVisitor : public SpdyFramerDebugVisitorInterface { + public: + MOCK_METHOD4(OnSendCompressedFrame, + void(SpdyStreamId stream_id, + SpdyFrameType type, + size_t payload_len, + size_t frame_len)); + + MOCK_METHOD3(OnReceiveCompressedFrame, + void(SpdyStreamId stream_id, + SpdyFrameType type, + size_t frame_len)); +}; + +MATCHER_P(IsFrameUnionOf, frame_list, "") { + size_t size_verified = 0; + for (const auto& frame : *frame_list) { + if (arg.size() < size_verified + frame.size()) { + LOG(FATAL) << "Incremental header serialization should not lead to a " + << "higher total frame length than non-incremental method."; + return false; + } + if (memcmp(arg.data() + size_verified, frame.data(), frame.size())) { + CompareCharArraysWithHexError( + "Header serialization methods should be equivalent: ", + reinterpret_cast<unsigned char*>(arg.data() + size_verified), + frame.size(), reinterpret_cast<unsigned char*>(frame.data()), + frame.size()); + return false; + } + size_verified += frame.size(); + } + return size_verified == arg.size(); +} + +class SpdyFramerPeer { + public: + // TODO(dahollings): Remove these methods when deprecating non-incremental + // header serialization path. + static std::unique_ptr<SpdyHeadersIR> CloneSpdyHeadersIR( + const SpdyHeadersIR& headers) { + auto new_headers = SpdyMakeUnique<SpdyHeadersIR>( + headers.stream_id(), headers.header_block().Clone()); + new_headers->set_fin(headers.fin()); + new_headers->set_has_priority(headers.has_priority()); + new_headers->set_weight(headers.weight()); + new_headers->set_parent_stream_id(headers.parent_stream_id()); + new_headers->set_exclusive(headers.exclusive()); + if (headers.padded()) { + new_headers->set_padding_len(headers.padding_payload_len() + 1); + } + return new_headers; + } + + static SpdySerializedFrame SerializeHeaders(SpdyFramer* framer, + const SpdyHeadersIR& headers) { + SpdySerializedFrame serialized_headers_old_version( + framer->SerializeHeaders(headers)); + framer->hpack_encoder_.reset(nullptr); + auto* saved_debug_visitor = framer->debug_visitor_; + framer->debug_visitor_ = nullptr; + + std::vector<SpdySerializedFrame> frame_list; + ArrayOutputBuffer frame_list_buffer(frame_list_char, buffer_size); + SpdyFramer::SpdyHeaderFrameIterator it(framer, CloneSpdyHeadersIR(headers)); + while (it.HasNextFrame()) { + size_t size_before = frame_list_buffer.Size(); + EXPECT_GT(it.NextFrame(&frame_list_buffer), 0u); + frame_list.emplace_back( + SpdySerializedFrame(frame_list_buffer.Begin() + size_before, + frame_list_buffer.Size() - size_before, false)); + } + framer->debug_visitor_ = saved_debug_visitor; + + EXPECT_THAT(serialized_headers_old_version, IsFrameUnionOf(&frame_list)); + return serialized_headers_old_version; + } + + static SpdySerializedFrame SerializeHeaders(SpdyFramer* framer, + const SpdyHeadersIR& headers, + ArrayOutputBuffer* output) { + if (output == nullptr) { + return SerializeHeaders(framer, headers); + } + output->Reset(); + EXPECT_TRUE(framer->SerializeHeaders(headers, output)); + SpdySerializedFrame serialized_headers_old_version(output->Begin(), + output->Size(), false); + framer->hpack_encoder_.reset(nullptr); + auto* saved_debug_visitor = framer->debug_visitor_; + framer->debug_visitor_ = nullptr; + + std::vector<SpdySerializedFrame> frame_list; + ArrayOutputBuffer frame_list_buffer(frame_list_char, buffer_size); + SpdyFramer::SpdyHeaderFrameIterator it(framer, CloneSpdyHeadersIR(headers)); + while (it.HasNextFrame()) { + size_t size_before = frame_list_buffer.Size(); + EXPECT_GT(it.NextFrame(&frame_list_buffer), 0u); + frame_list.emplace_back( + SpdySerializedFrame(frame_list_buffer.Begin() + size_before, + frame_list_buffer.Size() - size_before, false)); + } + framer->debug_visitor_ = saved_debug_visitor; + + EXPECT_THAT(serialized_headers_old_version, IsFrameUnionOf(&frame_list)); + return serialized_headers_old_version; + } + + static std::unique_ptr<SpdyPushPromiseIR> CloneSpdyPushPromiseIR( + const SpdyPushPromiseIR& push_promise) { + auto new_push_promise = SpdyMakeUnique<SpdyPushPromiseIR>( + push_promise.stream_id(), push_promise.promised_stream_id(), + push_promise.header_block().Clone()); + new_push_promise->set_fin(push_promise.fin()); + if (push_promise.padded()) { + new_push_promise->set_padding_len(push_promise.padding_payload_len() + 1); + } + return new_push_promise; + } + + static SpdySerializedFrame SerializePushPromise( + SpdyFramer* framer, + const SpdyPushPromiseIR& push_promise) { + SpdySerializedFrame serialized_headers_old_version = + framer->SerializePushPromise(push_promise); + framer->hpack_encoder_.reset(nullptr); + auto* saved_debug_visitor = framer->debug_visitor_; + framer->debug_visitor_ = nullptr; + + std::vector<SpdySerializedFrame> frame_list; + ArrayOutputBuffer frame_list_buffer(frame_list_char, buffer_size); + frame_list_buffer.Reset(); + SpdyFramer::SpdyPushPromiseFrameIterator it( + framer, CloneSpdyPushPromiseIR(push_promise)); + while (it.HasNextFrame()) { + size_t size_before = frame_list_buffer.Size(); + EXPECT_GT(it.NextFrame(&frame_list_buffer), 0u); + frame_list.emplace_back( + SpdySerializedFrame(frame_list_buffer.Begin() + size_before, + frame_list_buffer.Size() - size_before, false)); + } + framer->debug_visitor_ = saved_debug_visitor; + + EXPECT_THAT(serialized_headers_old_version, IsFrameUnionOf(&frame_list)); + return serialized_headers_old_version; + } + + static SpdySerializedFrame SerializePushPromise( + SpdyFramer* framer, + const SpdyPushPromiseIR& push_promise, + ArrayOutputBuffer* output) { + if (output == nullptr) { + return SerializePushPromise(framer, push_promise); + } + output->Reset(); + EXPECT_TRUE(framer->SerializePushPromise(push_promise, output)); + SpdySerializedFrame serialized_headers_old_version(output->Begin(), + output->Size(), false); + framer->hpack_encoder_.reset(nullptr); + auto* saved_debug_visitor = framer->debug_visitor_; + framer->debug_visitor_ = nullptr; + + std::vector<SpdySerializedFrame> frame_list; + ArrayOutputBuffer frame_list_buffer(frame_list_char, buffer_size); + frame_list_buffer.Reset(); + SpdyFramer::SpdyPushPromiseFrameIterator it( + framer, CloneSpdyPushPromiseIR(push_promise)); + while (it.HasNextFrame()) { + size_t size_before = frame_list_buffer.Size(); + EXPECT_GT(it.NextFrame(&frame_list_buffer), 0u); + frame_list.emplace_back( + SpdySerializedFrame(frame_list_buffer.Begin() + size_before, + frame_list_buffer.Size() - size_before, false)); + } + framer->debug_visitor_ = saved_debug_visitor; + + EXPECT_THAT(serialized_headers_old_version, IsFrameUnionOf(&frame_list)); + return serialized_headers_old_version; + } +}; + +class TestSpdyVisitor : public SpdyFramerVisitorInterface, + public SpdyFramerDebugVisitorInterface { + public: + // This is larger than our max frame size because header blocks that + // are too long can spill over into CONTINUATION frames. + static const size_t kDefaultHeaderBufferSize = 16 * 1024 * 1024; + + explicit TestSpdyVisitor(SpdyFramer::CompressionOption option) + : framer_(option), + error_count_(0), + headers_frame_count_(0), + push_promise_frame_count_(0), + goaway_count_(0), + setting_count_(0), + settings_ack_sent_(0), + settings_ack_received_(0), + continuation_count_(0), + altsvc_count_(0), + priority_count_(0), + on_unknown_frame_result_(false), + last_window_update_stream_(0), + last_window_update_delta_(0), + last_push_promise_stream_(0), + last_push_promise_promised_stream_(0), + data_bytes_(0), + fin_frame_count_(0), + fin_flag_count_(0), + end_of_stream_count_(0), + control_frame_header_data_count_(0), + zero_length_control_frame_header_data_count_(0), + data_frame_count_(0), + last_payload_len_(0), + last_frame_len_(0), + header_buffer_(new char[kDefaultHeaderBufferSize]), + header_buffer_length_(0), + header_buffer_size_(kDefaultHeaderBufferSize), + header_stream_id_(static_cast<SpdyStreamId>(-1)), + header_control_type_(SpdyFrameType::DATA), + header_buffer_valid_(false) {} + + void OnError(Http2DecoderAdapter::SpdyFramerError error) override { + VLOG(1) << "SpdyFramer Error: " + << Http2DecoderAdapter::SpdyFramerErrorToString(error); + ++error_count_; + } + + void OnDataFrameHeader(SpdyStreamId stream_id, + size_t length, + bool fin) override { + VLOG(1) << "OnDataFrameHeader(" << stream_id << ", " << length << ", " + << fin << ")"; + ++data_frame_count_; + header_stream_id_ = stream_id; + } + + void OnStreamFrameData(SpdyStreamId stream_id, + const char* data, + size_t len) override { + VLOG(1) << "OnStreamFrameData(" << stream_id << ", data, " << len << ", " + << ") data:\n" + << SpdyHexDump(SpdyStringPiece(data, len)); + EXPECT_EQ(header_stream_id_, stream_id); + + data_bytes_ += len; + } + + void OnStreamEnd(SpdyStreamId stream_id) override { + VLOG(1) << "OnStreamEnd(" << stream_id << ")"; + EXPECT_EQ(header_stream_id_, stream_id); + ++end_of_stream_count_; + } + + void OnStreamPadLength(SpdyStreamId stream_id, size_t value) override { + VLOG(1) << "OnStreamPadding(" << stream_id << ", " << value << ")\n"; + EXPECT_EQ(header_stream_id_, stream_id); + // Count the padding length field byte against total data bytes. + data_bytes_ += 1; + } + + void OnStreamPadding(SpdyStreamId stream_id, size_t len) override { + VLOG(1) << "OnStreamPadding(" << stream_id << ", " << len << ")\n"; + EXPECT_EQ(header_stream_id_, stream_id); + data_bytes_ += len; + } + + SpdyHeadersHandlerInterface* OnHeaderFrameStart( + SpdyStreamId stream_id) override { + if (headers_handler_ == nullptr) { + headers_handler_ = SpdyMakeUnique<TestHeadersHandler>(); + } + return headers_handler_.get(); + } + + void OnHeaderFrameEnd(SpdyStreamId stream_id) override { + CHECK(headers_handler_ != nullptr); + headers_ = headers_handler_->decoded_block().Clone(); + header_bytes_received_ = headers_handler_->header_bytes_parsed(); + headers_handler_.reset(); + } + + void OnRstStream(SpdyStreamId stream_id, SpdyErrorCode error_code) override { + VLOG(1) << "OnRstStream(" << stream_id << ", " << error_code << ")"; + ++fin_frame_count_; + } + + void OnSetting(SpdySettingsId id, uint32_t value) override { + VLOG(1) << "OnSetting(" << id << ", " << std::hex << value << ")"; + ++setting_count_; + } + + void OnSettingsAck() override { + VLOG(1) << "OnSettingsAck"; + ++settings_ack_received_; + } + + void OnSettingsEnd() override { + VLOG(1) << "OnSettingsEnd"; + ++settings_ack_sent_; + } + + void OnPing(SpdyPingId unique_id, bool is_ack) override { + LOG(DFATAL) << "OnPing(" << unique_id << ", " << (is_ack ? 1 : 0) << ")"; + } + + void OnGoAway(SpdyStreamId last_accepted_stream_id, + SpdyErrorCode error_code) override { + VLOG(1) << "OnGoAway(" << last_accepted_stream_id << ", " << error_code + << ")"; + ++goaway_count_; + } + + void OnHeaders(SpdyStreamId stream_id, + bool has_priority, + int weight, + SpdyStreamId parent_stream_id, + bool exclusive, + bool fin, + bool end) override { + VLOG(1) << "OnHeaders(" << stream_id << ", " << has_priority << ", " + << weight << ", " << parent_stream_id << ", " << exclusive << ", " + << fin << ", " << end << ")"; + ++headers_frame_count_; + InitHeaderStreaming(SpdyFrameType::HEADERS, stream_id); + if (fin) { + ++fin_flag_count_; + } + header_has_priority_ = has_priority; + header_parent_stream_id_ = parent_stream_id; + header_exclusive_ = exclusive; + } + + void OnWindowUpdate(SpdyStreamId stream_id, int delta_window_size) override { + VLOG(1) << "OnWindowUpdate(" << stream_id << ", " << delta_window_size + << ")"; + last_window_update_stream_ = stream_id; + last_window_update_delta_ = delta_window_size; + } + + void OnPushPromise(SpdyStreamId stream_id, + SpdyStreamId promised_stream_id, + bool end) override { + VLOG(1) << "OnPushPromise(" << stream_id << ", " << promised_stream_id + << ", " << end << ")"; + ++push_promise_frame_count_; + InitHeaderStreaming(SpdyFrameType::PUSH_PROMISE, stream_id); + last_push_promise_stream_ = stream_id; + last_push_promise_promised_stream_ = promised_stream_id; + } + + void OnContinuation(SpdyStreamId stream_id, bool end) override { + VLOG(1) << "OnContinuation(" << stream_id << ", " << end << ")"; + ++continuation_count_; + } + + void OnAltSvc(SpdyStreamId stream_id, + SpdyStringPiece origin, + const SpdyAltSvcWireFormat::AlternativeServiceVector& + altsvc_vector) override { + VLOG(1) << "OnAltSvc(" << stream_id << ", \"" << origin + << "\", altsvc_vector)"; + test_altsvc_ir_ = SpdyMakeUnique<SpdyAltSvcIR>(stream_id); + if (origin.length() > 0) { + test_altsvc_ir_->set_origin(SpdyString(origin)); + } + for (const auto& altsvc : altsvc_vector) { + test_altsvc_ir_->add_altsvc(altsvc); + } + ++altsvc_count_; + } + + void OnPriority(SpdyStreamId stream_id, + SpdyStreamId parent_stream_id, + int weight, + bool exclusive) override { + VLOG(1) << "OnPriority(" << stream_id << ", " << parent_stream_id << ", " + << weight << ", " << (exclusive ? 1 : 0) << ")"; + ++priority_count_; + } + + bool OnUnknownFrame(SpdyStreamId stream_id, uint8_t frame_type) override { + VLOG(1) << "OnUnknownFrame(" << stream_id << ", " << frame_type << ")"; + return on_unknown_frame_result_; + } + + void OnSendCompressedFrame(SpdyStreamId stream_id, + SpdyFrameType type, + size_t payload_len, + size_t frame_len) override { + VLOG(1) << "OnSendCompressedFrame(" << stream_id << ", " << type << ", " + << payload_len << ", " << frame_len << ")"; + last_payload_len_ = payload_len; + last_frame_len_ = frame_len; + } + + void OnReceiveCompressedFrame(SpdyStreamId stream_id, + SpdyFrameType type, + size_t frame_len) override { + VLOG(1) << "OnReceiveCompressedFrame(" << stream_id << ", " << type << ", " + << frame_len << ")"; + last_frame_len_ = frame_len; + } + + // Convenience function which runs a framer simulation with particular input. + void SimulateInFramer(const unsigned char* input, size_t size) { + deframer_.set_visitor(this); + size_t input_remaining = size; + const char* input_ptr = reinterpret_cast<const char*>(input); + while (input_remaining > 0 && deframer_.spdy_framer_error() == + Http2DecoderAdapter::SPDY_NO_ERROR) { + // To make the tests more interesting, we feed random (and small) chunks + // into the framer. This simulates getting strange-sized reads from + // the socket. + const size_t kMaxReadSize = 32; + size_t bytes_read = + (rand() % std::min(input_remaining, kMaxReadSize)) + 1; + size_t bytes_processed = deframer_.ProcessInput(input_ptr, bytes_read); + input_remaining -= bytes_processed; + input_ptr += bytes_processed; + } + } + + void InitHeaderStreaming(SpdyFrameType header_control_type, + SpdyStreamId stream_id) { + if (!IsDefinedFrameType(SerializeFrameType(header_control_type))) { + DLOG(FATAL) << "Attempted to init header streaming with " + << "invalid control frame type: " << header_control_type; + } + memset(header_buffer_.get(), 0, header_buffer_size_); + header_buffer_length_ = 0; + header_stream_id_ = stream_id; + header_control_type_ = header_control_type; + header_buffer_valid_ = true; + } + + void set_extension_visitor(ExtensionVisitorInterface* extension) { + deframer_.set_extension_visitor(extension); + } + + // Override the default buffer size (16K). Call before using the framer! + void set_header_buffer_size(size_t header_buffer_size) { + header_buffer_size_ = header_buffer_size; + header_buffer_.reset(new char[header_buffer_size]); + } + + SpdyFramer framer_; + Http2DecoderAdapter deframer_; + + // Counters from the visitor callbacks. + int error_count_; + int headers_frame_count_; + int push_promise_frame_count_; + int goaway_count_; + int setting_count_; + int settings_ack_sent_; + int settings_ack_received_; + int continuation_count_; + int altsvc_count_; + int priority_count_; + std::unique_ptr<SpdyAltSvcIR> test_altsvc_ir_; + bool on_unknown_frame_result_; + SpdyStreamId last_window_update_stream_; + int last_window_update_delta_; + SpdyStreamId last_push_promise_stream_; + SpdyStreamId last_push_promise_promised_stream_; + int data_bytes_; + int fin_frame_count_; // The count of RST_STREAM type frames received. + int fin_flag_count_; // The count of frames with the FIN flag set. + int end_of_stream_count_; // The count of zero-length data frames. + int control_frame_header_data_count_; // The count of chunks received. + // The count of zero-length control frame header data chunks received. + int zero_length_control_frame_header_data_count_; + int data_frame_count_; + size_t last_payload_len_; + size_t last_frame_len_; + + // Header block streaming state: + std::unique_ptr<char[]> header_buffer_; + size_t header_buffer_length_; + size_t header_buffer_size_; + size_t header_bytes_received_; + SpdyStreamId header_stream_id_; + SpdyFrameType header_control_type_; + bool header_buffer_valid_; + std::unique_ptr<TestHeadersHandler> headers_handler_; + SpdyHeaderBlock headers_; + bool header_has_priority_; + SpdyStreamId header_parent_stream_id_; + bool header_exclusive_; +}; + +class TestExtension : public ExtensionVisitorInterface { + public: + void OnSetting(SpdySettingsId id, uint32_t value) override { + settings_received_.push_back({id, value}); + } + + // Called when non-standard frames are received. + bool OnFrameHeader(SpdyStreamId stream_id, + size_t length, + uint8_t type, + uint8_t flags) override { + stream_id_ = stream_id; + length_ = length; + type_ = type; + flags_ = flags; + return true; + } + + // The payload for a single frame may be delivered as multiple calls to + // OnFramePayload. + void OnFramePayload(const char* data, size_t len) override { + payload_.append(data, len); + } + + std::vector<std::pair<SpdySettingsId, uint32_t>> settings_received_; + SpdyStreamId stream_id_ = 0; + size_t length_ = 0; + uint8_t type_ = 0; + uint8_t flags_ = 0; + SpdyString payload_; +}; + +// Exposes SpdyUnknownIR::set_length() for testing purposes. +class TestSpdyUnknownIR : public SpdyUnknownIR { + public: + using SpdyUnknownIR::set_length; + using SpdyUnknownIR::SpdyUnknownIR; +}; + +enum Output { USE, NOT_USE }; + +class SpdyFramerTest : public ::testing::TestWithParam<Output> { + public: + SpdyFramerTest() + : output_(output_buffer, kSize), + framer_(SpdyFramer::ENABLE_COMPRESSION) {} + + protected: + void SetUp() override { + switch (GetParam()) { + case USE: + use_output_ = true; + break; + case NOT_USE: + // TODO(yasong): remove this case after + // gfe2_reloadable_flag_write_queue_zero_copy_buffer deprecates. + use_output_ = false; + break; + } + } + + void CompareFrame(const SpdyString& description, + const SpdySerializedFrame& actual_frame, + const unsigned char* expected, + const int expected_len) { + const unsigned char* actual = + reinterpret_cast<const unsigned char*>(actual_frame.data()); + CompareCharArraysWithHexError(description, actual, actual_frame.size(), + expected, expected_len); + } + + bool use_output_ = false; + ArrayOutputBuffer output_; + SpdyFramer framer_; + Http2DecoderAdapter deframer_; +}; + +INSTANTIATE_TEST_CASE_P(SpdyFramerTests, + SpdyFramerTest, + ::testing::Values(USE, NOT_USE)); + +// Test that we can encode and decode a SpdyHeaderBlock in serialized form. +TEST_P(SpdyFramerTest, HeaderBlockInBuffer) { + SpdyFramer framer(SpdyFramer::DISABLE_COMPRESSION); + + // Encode the header block into a Headers frame. + SpdyHeadersIR headers(/* stream_id = */ 1); + headers.SetHeader("alpha", "beta"); + headers.SetHeader("gamma", "charlie"); + headers.SetHeader("cookie", "key1=value1; key2=value2"); + SpdySerializedFrame frame( + SpdyFramerPeer::SerializeHeaders(&framer, headers, &output_)); + + TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION); + visitor.SimulateInFramer(reinterpret_cast<unsigned char*>(frame.data()), + frame.size()); + + EXPECT_EQ(0, visitor.zero_length_control_frame_header_data_count_); + EXPECT_EQ(headers.header_block(), visitor.headers_); +} + +// Test that if there's not a full frame, we fail to parse it. +TEST_P(SpdyFramerTest, UndersizedHeaderBlockInBuffer) { + SpdyFramer framer(SpdyFramer::DISABLE_COMPRESSION); + + // Encode the header block into a Headers frame. + SpdyHeadersIR headers(/* stream_id = */ 1); + headers.SetHeader("alpha", "beta"); + headers.SetHeader("gamma", "charlie"); + SpdySerializedFrame frame( + SpdyFramerPeer::SerializeHeaders(&framer, headers, &output_)); + + TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION); + visitor.SimulateInFramer(reinterpret_cast<unsigned char*>(frame.data()), + frame.size() - 2); + + EXPECT_EQ(0, visitor.zero_length_control_frame_header_data_count_); + EXPECT_THAT(visitor.headers_, testing::IsEmpty()); +} + +// Test that we can encode and decode stream dependency values in a header +// frame. +TEST_P(SpdyFramerTest, HeaderStreamDependencyValues) { + SpdyFramer framer(SpdyFramer::DISABLE_COMPRESSION); + + const SpdyStreamId parent_stream_id_test_array[] = {0, 3}; + for (SpdyStreamId parent_stream_id : parent_stream_id_test_array) { + const bool exclusive_test_array[] = {true, false}; + for (bool exclusive : exclusive_test_array) { + SpdyHeadersIR headers(1); + headers.set_has_priority(true); + headers.set_parent_stream_id(parent_stream_id); + headers.set_exclusive(exclusive); + SpdySerializedFrame frame( + SpdyFramerPeer::SerializeHeaders(&framer, headers, &output_)); + + TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION); + visitor.SimulateInFramer(reinterpret_cast<unsigned char*>(frame.data()), + frame.size()); + + EXPECT_TRUE(visitor.header_has_priority_); + EXPECT_EQ(parent_stream_id, visitor.header_parent_stream_id_); + EXPECT_EQ(exclusive, visitor.header_exclusive_); + } + } +} + +// Test that if we receive a frame with payload length field at the +// advertised max size, we do not set an error in ProcessInput. +TEST_P(SpdyFramerTest, AcceptMaxFrameSizeSetting) { + testing::StrictMock<test::MockSpdyFramerVisitor> visitor; + deframer_.set_visitor(&visitor); + + // DATA frame with maximum allowed payload length. + unsigned char kH2FrameData[] = { + 0x00, 0x40, 0x00, // Length: 2^14 + 0x00, // Type: HEADERS + 0x00, // Flags: None + 0x00, 0x00, 0x00, 0x01, // Stream: 1 + 0x00, 0x00, 0x00, 0x00, // Junk payload + }; + + SpdySerializedFrame frame(reinterpret_cast<char*>(kH2FrameData), + sizeof(kH2FrameData), false); + + EXPECT_CALL(visitor, OnDataFrameHeader(1, 1 << 14, false)); + EXPECT_CALL(visitor, OnStreamFrameData(1, _, 4)); + deframer_.ProcessInput(frame.data(), frame.size()); + EXPECT_FALSE(deframer_.HasError()); +} + +// Test that if we receive a frame with payload length larger than the +// advertised max size, we set an error of SPDY_INVALID_CONTROL_FRAME_SIZE. +TEST_P(SpdyFramerTest, ExceedMaxFrameSizeSetting) { + testing::StrictMock<test::MockSpdyFramerVisitor> visitor; + deframer_.set_visitor(&visitor); + + // DATA frame with too large payload length. + unsigned char kH2FrameData[] = { + 0x00, 0x40, 0x01, // Length: 2^14 + 1 + 0x00, // Type: HEADERS + 0x00, // Flags: None + 0x00, 0x00, 0x00, 0x01, // Stream: 1 + 0x00, 0x00, 0x00, 0x00, // Junk payload + }; + + SpdySerializedFrame frame(reinterpret_cast<char*>(kH2FrameData), + sizeof(kH2FrameData), false); + + EXPECT_CALL(visitor, OnError(Http2DecoderAdapter::SPDY_OVERSIZED_PAYLOAD)); + deframer_.ProcessInput(frame.data(), frame.size()); + EXPECT_TRUE(deframer_.HasError()); + EXPECT_EQ(Http2DecoderAdapter::SPDY_OVERSIZED_PAYLOAD, + deframer_.spdy_framer_error()) + << Http2DecoderAdapter::SpdyFramerErrorToString( + deframer_.spdy_framer_error()); +} + +// Test that if we receive a DATA frame with padding length larger than the +// payload length, we set an error of SPDY_INVALID_PADDING +TEST_P(SpdyFramerTest, OversizedDataPaddingError) { + testing::StrictMock<test::MockSpdyFramerVisitor> visitor; + deframer_.set_visitor(&visitor); + + // DATA frame with invalid padding length. + // |kH2FrameData| has to be |unsigned char|, because Chromium on Windows uses + // MSVC, where |char| is signed by default, which would not compile because of + // the element exceeding 127. + unsigned char kH2FrameData[] = { + 0x00, 0x00, 0x05, // Length: 5 + 0x00, // Type: DATA + 0x09, // Flags: END_STREAM|PADDED + 0x00, 0x00, 0x00, 0x01, // Stream: 1 + 0xff, // PadLen: 255 trailing bytes (Too Long) + 0x00, 0x00, 0x00, 0x00, // Padding + }; + + SpdySerializedFrame frame(reinterpret_cast<char*>(kH2FrameData), + sizeof(kH2FrameData), false); + + { + testing::InSequence seq; + EXPECT_CALL(visitor, OnDataFrameHeader(1, 5, 1)); + EXPECT_CALL(visitor, OnStreamPadding(1, 1)); + EXPECT_CALL(visitor, OnError(Http2DecoderAdapter::SPDY_INVALID_PADDING)); + } + EXPECT_GT(frame.size(), deframer_.ProcessInput(frame.data(), frame.size())); + EXPECT_TRUE(deframer_.HasError()); + EXPECT_EQ(Http2DecoderAdapter::SPDY_INVALID_PADDING, + deframer_.spdy_framer_error()) + << Http2DecoderAdapter::SpdyFramerErrorToString( + deframer_.spdy_framer_error()); +} + +// Test that if we receive a DATA frame with padding length not larger than the +// payload length, we do not set an error of SPDY_INVALID_PADDING +TEST_P(SpdyFramerTest, CorrectlySizedDataPaddingNoError) { + testing::StrictMock<test::MockSpdyFramerVisitor> visitor; + + deframer_.set_visitor(&visitor); + + // DATA frame with valid Padding length + char kH2FrameData[] = { + 0x00, 0x00, 0x05, // Length: 5 + 0x00, // Type: DATA + 0x08, // Flags: PADDED + 0x00, 0x00, 0x00, 0x01, // Stream: 1 + 0x04, // PadLen: 4 trailing bytes + 0x00, 0x00, 0x00, 0x00, // Padding + }; + + SpdySerializedFrame frame(kH2FrameData, sizeof(kH2FrameData), false); + + { + testing::InSequence seq; + EXPECT_CALL(visitor, OnDataFrameHeader(1, 5, false)); + EXPECT_CALL(visitor, OnStreamPadLength(1, 4)); + EXPECT_CALL(visitor, OnError(_)).Times(0); + // Note that OnStreamFrameData(1, _, 1)) is never called + // since there is no data, only padding + EXPECT_CALL(visitor, OnStreamPadding(1, 4)); + } + + EXPECT_EQ(frame.size(), deframer_.ProcessInput(frame.data(), frame.size())); + EXPECT_FALSE(deframer_.HasError()); + EXPECT_EQ(Http2DecoderAdapter::SPDY_NO_ERROR, deframer_.spdy_framer_error()) + << Http2DecoderAdapter::SpdyFramerErrorToString( + deframer_.spdy_framer_error()); +} + +// Test that if we receive a HEADERS frame with padding length larger than the +// payload length, we set an error of SPDY_INVALID_PADDING +TEST_P(SpdyFramerTest, OversizedHeadersPaddingError) { + testing::StrictMock<test::MockSpdyFramerVisitor> visitor; + + deframer_.set_visitor(&visitor); + + // HEADERS frame with invalid padding length. + // |kH2FrameData| has to be |unsigned char|, because Chromium on Windows uses + // MSVC, where |char| is signed by default, which would not compile because of + // the element exceeding 127. + unsigned char kH2FrameData[] = { + 0x00, 0x00, 0x05, // Length: 5 + 0x01, // Type: HEADERS + 0x08, // Flags: PADDED + 0x00, 0x00, 0x00, 0x01, // Stream: 1 + 0xff, // PadLen: 255 trailing bytes (Too Long) + 0x00, 0x00, 0x00, 0x00, // Padding + }; + + SpdySerializedFrame frame(reinterpret_cast<char*>(kH2FrameData), + sizeof(kH2FrameData), false); + + EXPECT_CALL(visitor, OnHeaders(1, false, 0, 0, false, false, false)); + EXPECT_CALL(visitor, OnHeaderFrameStart(1)).Times(1); + EXPECT_CALL(visitor, OnError(Http2DecoderAdapter::SPDY_INVALID_PADDING)); + EXPECT_EQ(frame.size(), deframer_.ProcessInput(frame.data(), frame.size())); + EXPECT_TRUE(deframer_.HasError()); + EXPECT_EQ(Http2DecoderAdapter::SPDY_INVALID_PADDING, + deframer_.spdy_framer_error()) + << Http2DecoderAdapter::SpdyFramerErrorToString( + deframer_.spdy_framer_error()); +} + +// Test that if we receive a HEADERS frame with padding length not larger +// than the payload length, we do not set an error of SPDY_INVALID_PADDING +TEST_P(SpdyFramerTest, CorrectlySizedHeadersPaddingNoError) { + testing::StrictMock<test::MockSpdyFramerVisitor> visitor; + + deframer_.set_visitor(&visitor); + + // HEADERS frame with invalid Padding length + char kH2FrameData[] = { + 0x00, 0x00, 0x05, // Length: 5 + 0x01, // Type: HEADERS + 0x08, // Flags: PADDED + 0x00, 0x00, 0x00, 0x01, // Stream: 1 + 0x04, // PadLen: 4 trailing bytes + 0x00, 0x00, 0x00, 0x00, // Padding + }; + + SpdySerializedFrame frame(kH2FrameData, sizeof(kH2FrameData), false); + + EXPECT_CALL(visitor, OnHeaders(1, false, 0, 0, false, false, false)); + EXPECT_CALL(visitor, OnHeaderFrameStart(1)).Times(1); + + EXPECT_EQ(frame.size(), deframer_.ProcessInput(frame.data(), frame.size())); + EXPECT_FALSE(deframer_.HasError()); + EXPECT_EQ(Http2DecoderAdapter::SPDY_NO_ERROR, deframer_.spdy_framer_error()) + << Http2DecoderAdapter::SpdyFramerErrorToString( + deframer_.spdy_framer_error()); +} + +// Test that if we receive a DATA with stream ID zero, we signal an error +// (but don't crash). +TEST_P(SpdyFramerTest, DataWithStreamIdZero) { + testing::StrictMock<test::MockSpdyFramerVisitor> visitor; + + deframer_.set_visitor(&visitor); + + const char bytes[] = "hello"; + SpdyDataIR data_ir(/* stream_id = */ 0, bytes); + SpdySerializedFrame frame(framer_.SerializeData(data_ir)); + + // We shouldn't have to read the whole frame before we signal an error. + EXPECT_CALL(visitor, OnError(Http2DecoderAdapter::SPDY_INVALID_STREAM_ID)); + EXPECT_GT(frame.size(), deframer_.ProcessInput(frame.data(), frame.size())); + EXPECT_TRUE(deframer_.HasError()); + EXPECT_EQ(Http2DecoderAdapter::SPDY_INVALID_STREAM_ID, + deframer_.spdy_framer_error()) + << Http2DecoderAdapter::SpdyFramerErrorToString( + deframer_.spdy_framer_error()); +} + +// Test that if we receive a HEADERS with stream ID zero, we signal an error +// (but don't crash). +TEST_P(SpdyFramerTest, HeadersWithStreamIdZero) { + testing::StrictMock<test::MockSpdyFramerVisitor> visitor; + + deframer_.set_visitor(&visitor); + + SpdyHeadersIR headers(/* stream_id = */ 0); + headers.SetHeader("alpha", "beta"); + SpdySerializedFrame frame( + SpdyFramerPeer::SerializeHeaders(&framer_, headers, &output_)); + + // We shouldn't have to read the whole frame before we signal an error. + EXPECT_CALL(visitor, OnError(Http2DecoderAdapter::SPDY_INVALID_STREAM_ID)); + EXPECT_GT(frame.size(), deframer_.ProcessInput(frame.data(), frame.size())); + EXPECT_TRUE(deframer_.HasError()); + EXPECT_EQ(Http2DecoderAdapter::SPDY_INVALID_STREAM_ID, + deframer_.spdy_framer_error()) + << Http2DecoderAdapter::SpdyFramerErrorToString( + deframer_.spdy_framer_error()); +} + +// Test that if we receive a PRIORITY with stream ID zero, we signal an error +// (but don't crash). +TEST_P(SpdyFramerTest, PriorityWithStreamIdZero) { + testing::StrictMock<test::MockSpdyFramerVisitor> visitor; + + deframer_.set_visitor(&visitor); + + SpdyPriorityIR priority_ir(/* stream_id = */ 0, + /* parent_stream_id = */ 1, + /* weight = */ 16, + /* exclusive = */ true); + SpdySerializedFrame frame(framer_.SerializeFrame(priority_ir)); + if (use_output_) { + EXPECT_EQ(framer_.SerializeFrame(priority_ir, &output_), frame.size()); + frame = SpdySerializedFrame(output_.Begin(), output_.Size(), false); + } + + // We shouldn't have to read the whole frame before we signal an error. + EXPECT_CALL(visitor, OnError(Http2DecoderAdapter::SPDY_INVALID_STREAM_ID)); + EXPECT_GT(frame.size(), deframer_.ProcessInput(frame.data(), frame.size())); + EXPECT_TRUE(deframer_.HasError()); + EXPECT_EQ(Http2DecoderAdapter::SPDY_INVALID_STREAM_ID, + deframer_.spdy_framer_error()) + << Http2DecoderAdapter::SpdyFramerErrorToString( + deframer_.spdy_framer_error()); +} + +// Test that if we receive a RST_STREAM with stream ID zero, we signal an error +// (but don't crash). +TEST_P(SpdyFramerTest, RstStreamWithStreamIdZero) { + testing::StrictMock<test::MockSpdyFramerVisitor> visitor; + + deframer_.set_visitor(&visitor); + + SpdyRstStreamIR rst_stream_ir(/* stream_id = */ 0, ERROR_CODE_PROTOCOL_ERROR); + SpdySerializedFrame frame(framer_.SerializeRstStream(rst_stream_ir)); + if (use_output_) { + EXPECT_TRUE(framer_.SerializeRstStream(rst_stream_ir, &output_)); + frame = SpdySerializedFrame(output_.Begin(), output_.Size(), false); + } + + // We shouldn't have to read the whole frame before we signal an error. + EXPECT_CALL(visitor, OnError(Http2DecoderAdapter::SPDY_INVALID_STREAM_ID)); + EXPECT_GT(frame.size(), deframer_.ProcessInput(frame.data(), frame.size())); + EXPECT_TRUE(deframer_.HasError()); + EXPECT_EQ(Http2DecoderAdapter::SPDY_INVALID_STREAM_ID, + deframer_.spdy_framer_error()) + << Http2DecoderAdapter::SpdyFramerErrorToString( + deframer_.spdy_framer_error()); +} + +// Test that if we receive a SETTINGS with stream ID other than zero, +// we signal an error (but don't crash). +TEST_P(SpdyFramerTest, SettingsWithStreamIdNotZero) { + testing::StrictMock<test::MockSpdyFramerVisitor> visitor; + + deframer_.set_visitor(&visitor); + + // Settings frame with invalid StreamID of 0x01 + char kH2FrameData[] = { + 0x00, 0x00, 0x06, // Length: 6 + 0x04, // Type: SETTINGS + 0x00, // Flags: none + 0x00, 0x00, 0x00, 0x01, // Stream: 1 + 0x00, 0x04, // Param: INITIAL_WINDOW_SIZE + 0x0a, 0x0b, 0x0c, 0x0d, // Value: 168496141 + }; + + SpdySerializedFrame frame(kH2FrameData, sizeof(kH2FrameData), false); + + // We shouldn't have to read the whole frame before we signal an error. + EXPECT_CALL(visitor, OnError(Http2DecoderAdapter::SPDY_INVALID_STREAM_ID)); + EXPECT_GT(frame.size(), deframer_.ProcessInput(frame.data(), frame.size())); + EXPECT_TRUE(deframer_.HasError()); + EXPECT_EQ(Http2DecoderAdapter::SPDY_INVALID_STREAM_ID, + deframer_.spdy_framer_error()) + << Http2DecoderAdapter::SpdyFramerErrorToString( + deframer_.spdy_framer_error()); +} + +// Test that if we receive a GOAWAY with stream ID other than zero, +// we signal an error (but don't crash). +TEST_P(SpdyFramerTest, GoawayWithStreamIdNotZero) { + testing::StrictMock<test::MockSpdyFramerVisitor> visitor; + + deframer_.set_visitor(&visitor); + + // GOAWAY frame with invalid StreamID of 0x01 + char kH2FrameData[] = { + 0x00, 0x00, 0x0a, // Length: 10 + 0x07, // Type: GOAWAY + 0x00, // Flags: none + 0x00, 0x00, 0x00, 0x01, // Stream: 1 + 0x00, 0x00, 0x00, 0x00, // Last: 0 + 0x00, 0x00, 0x00, 0x00, // Error: NO_ERROR + 0x47, 0x41, // Description + }; + + SpdySerializedFrame frame(kH2FrameData, sizeof(kH2FrameData), false); + + // We shouldn't have to read the whole frame before we signal an error. + EXPECT_CALL(visitor, OnError(Http2DecoderAdapter::SPDY_INVALID_STREAM_ID)); + EXPECT_GT(frame.size(), deframer_.ProcessInput(frame.data(), frame.size())); + EXPECT_TRUE(deframer_.HasError()); + EXPECT_EQ(Http2DecoderAdapter::SPDY_INVALID_STREAM_ID, + deframer_.spdy_framer_error()) + << Http2DecoderAdapter::SpdyFramerErrorToString( + deframer_.spdy_framer_error()); +} + +// Test that if we receive a CONTINUATION with stream ID zero, we signal +// SPDY_INVALID_STREAM_ID. +TEST_P(SpdyFramerTest, ContinuationWithStreamIdZero) { + testing::StrictMock<test::MockSpdyFramerVisitor> visitor; + + deframer_.set_visitor(&visitor); + + SpdyContinuationIR continuation(/* stream_id = */ 0); + auto some_nonsense_encoding = + SpdyMakeUnique<SpdyString>("some nonsense encoding"); + continuation.take_encoding(std::move(some_nonsense_encoding)); + continuation.set_end_headers(true); + SpdySerializedFrame frame(framer_.SerializeContinuation(continuation)); + if (use_output_) { + ASSERT_TRUE(framer_.SerializeContinuation(continuation, &output_)); + frame = SpdySerializedFrame(output_.Begin(), output_.Size(), false); + } + + // We shouldn't have to read the whole frame before we signal an error. + EXPECT_CALL(visitor, OnError(Http2DecoderAdapter::SPDY_INVALID_STREAM_ID)); + EXPECT_GT(frame.size(), deframer_.ProcessInput(frame.data(), frame.size())); + EXPECT_TRUE(deframer_.HasError()); + EXPECT_EQ(Http2DecoderAdapter::SPDY_INVALID_STREAM_ID, + deframer_.spdy_framer_error()) + << Http2DecoderAdapter::SpdyFramerErrorToString( + deframer_.spdy_framer_error()); +} + +// Test that if we receive a PUSH_PROMISE with stream ID zero, we signal +// SPDY_INVALID_STREAM_ID. +TEST_P(SpdyFramerTest, PushPromiseWithStreamIdZero) { + testing::StrictMock<test::MockSpdyFramerVisitor> visitor; + + deframer_.set_visitor(&visitor); + + SpdyPushPromiseIR push_promise(/* stream_id = */ 0, + /* promised_stream_id = */ 4); + push_promise.SetHeader("alpha", "beta"); + SpdySerializedFrame frame(SpdyFramerPeer::SerializePushPromise( + &framer_, push_promise, use_output_ ? &output_ : nullptr)); + + // We shouldn't have to read the whole frame before we signal an error. + EXPECT_CALL(visitor, OnError(Http2DecoderAdapter::SPDY_INVALID_STREAM_ID)); + EXPECT_GT(frame.size(), deframer_.ProcessInput(frame.data(), frame.size())); + EXPECT_TRUE(deframer_.HasError()); + EXPECT_EQ(Http2DecoderAdapter::SPDY_INVALID_STREAM_ID, + deframer_.spdy_framer_error()) + << Http2DecoderAdapter::SpdyFramerErrorToString( + deframer_.spdy_framer_error()); +} + +// Test that if we receive a PUSH_PROMISE with promised stream ID zero, we +// signal SPDY_INVALID_CONTROL_FRAME. +TEST_P(SpdyFramerTest, PushPromiseWithPromisedStreamIdZero) { + testing::StrictMock<test::MockSpdyFramerVisitor> visitor; + + deframer_.set_visitor(&visitor); + + SpdyPushPromiseIR push_promise(/* stream_id = */ 3, + /* promised_stream_id = */ 0); + push_promise.SetHeader("alpha", "beta"); + SpdySerializedFrame frame(SpdyFramerPeer::SerializePushPromise( + &framer_, push_promise, use_output_ ? &output_ : nullptr)); + + EXPECT_CALL(visitor, + OnError(Http2DecoderAdapter::SPDY_INVALID_CONTROL_FRAME)); + deframer_.ProcessInput(frame.data(), frame.size()); + EXPECT_TRUE(deframer_.HasError()); + EXPECT_EQ(Http2DecoderAdapter::SPDY_INVALID_CONTROL_FRAME, + deframer_.spdy_framer_error()) + << Http2DecoderAdapter::SpdyFramerErrorToString( + deframer_.spdy_framer_error()); +} + +TEST_P(SpdyFramerTest, MultiValueHeader) { + SpdyFramer framer(SpdyFramer::DISABLE_COMPRESSION); + SpdyString value("value1\0value2", 13); + // TODO(jgraettinger): If this pattern appears again, move to test class. + SpdyHeaderBlock header_set; + header_set["name"] = value; + SpdyString buffer; + HpackEncoder encoder(ObtainHpackHuffmanTable()); + encoder.DisableCompression(); + encoder.EncodeHeaderSet(header_set, &buffer); + // Frame builder with plentiful buffer size. + SpdyFrameBuilder frame(1024); + frame.BeginNewFrame(SpdyFrameType::HEADERS, + HEADERS_FLAG_PRIORITY | HEADERS_FLAG_END_HEADERS, 3, + buffer.size() + 5 /* priority */); + frame.WriteUInt32(0); // Priority exclusivity and dependent stream. + frame.WriteUInt8(255); // Priority weight. + frame.WriteBytes(&buffer[0], buffer.size()); + + SpdySerializedFrame control_frame(frame.take()); + + TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION); + visitor.SimulateInFramer( + reinterpret_cast<unsigned char*>(control_frame.data()), + control_frame.size()); + + EXPECT_THAT(visitor.headers_, testing::ElementsAre(testing::Pair( + "name", SpdyStringPiece(value)))); +} + +TEST_P(SpdyFramerTest, CompressEmptyHeaders) { + // See https://crbug.com/172383/ + SpdyHeadersIR headers(1); + headers.SetHeader("server", "SpdyServer 1.0"); + headers.SetHeader("date", "Mon 12 Jan 2009 12:12:12 PST"); + headers.SetHeader("status", "200"); + headers.SetHeader("version", "HTTP/1.1"); + headers.SetHeader("content-type", "text/html"); + headers.SetHeader("content-length", "12"); + headers.SetHeader("x-empty-header", ""); + + SpdyFramer framer(SpdyFramer::ENABLE_COMPRESSION); + SpdySerializedFrame frame1( + SpdyFramerPeer::SerializeHeaders(&framer, headers, &output_)); +} + +TEST_P(SpdyFramerTest, Basic) { + // Send HEADERS frames with PRIORITY and END_HEADERS set. + // frame-format off + const unsigned char kH2Input[] = { + 0x00, 0x00, 0x05, // Length: 5 + 0x01, // Type: HEADERS + 0x24, // Flags: END_HEADERS|PRIORITY + 0x00, 0x00, 0x00, 0x01, // Stream: 1 + 0x00, 0x00, 0x00, 0x00, // Parent: 0 + 0x82, // Weight: 131 + + 0x00, 0x00, 0x01, // Length: 1 + 0x01, // Type: HEADERS + 0x04, // Flags: END_HEADERS + 0x00, 0x00, 0x00, 0x01, // Stream: 1 + 0x8c, // :status: 200 + + 0x00, 0x00, 0x0c, // Length: 12 + 0x00, // Type: DATA + 0x00, // Flags: none + 0x00, 0x00, 0x00, 0x01, // Stream: 1 + 0xde, 0xad, 0xbe, 0xef, // Payload + 0xde, 0xad, 0xbe, 0xef, // + 0xde, 0xad, 0xbe, 0xef, // + + 0x00, 0x00, 0x05, // Length: 5 + 0x01, // Type: HEADERS + 0x24, // Flags: END_HEADERS|PRIORITY + 0x00, 0x00, 0x00, 0x03, // Stream: 3 + 0x00, 0x00, 0x00, 0x00, // Parent: 0 + 0x82, // Weight: 131 + + 0x00, 0x00, 0x08, // Length: 8 + 0x00, // Type: DATA + 0x00, // Flags: none + 0x00, 0x00, 0x00, 0x03, // Stream: 3 + 0xde, 0xad, 0xbe, 0xef, // Payload + 0xde, 0xad, 0xbe, 0xef, // + + 0x00, 0x00, 0x04, // Length: 4 + 0x00, // Type: DATA + 0x00, // Flags: none + 0x00, 0x00, 0x00, 0x01, // Stream: 1 + 0xde, 0xad, 0xbe, 0xef, // Payload + + 0x00, 0x00, 0x04, // Length: 4 + 0x03, // Type: RST_STREAM + 0x00, // Flags: none + 0x00, 0x00, 0x00, 0x01, // Stream: 1 + 0x00, 0x00, 0x00, 0x08, // Error: CANCEL + + 0x00, 0x00, 0x00, // Length: 0 + 0x00, // Type: DATA + 0x00, // Flags: none + 0x00, 0x00, 0x00, 0x03, // Stream: 3 + + 0x00, 0x00, 0x04, // Length: 4 + 0x03, // Type: RST_STREAM + 0x00, // Flags: none + 0x00, 0x00, 0x00, 0x03, // Stream: 3 + 0x00, 0x00, 0x00, 0x08, // Error: CANCEL + }; + // frame-format on + + TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION); + visitor.SimulateInFramer(kH2Input, sizeof(kH2Input)); + + EXPECT_EQ(24, visitor.data_bytes_); + EXPECT_EQ(0, visitor.error_count_); + EXPECT_EQ(2, visitor.fin_frame_count_); + + EXPECT_EQ(3, visitor.headers_frame_count_); + + EXPECT_EQ(0, visitor.fin_flag_count_); + EXPECT_EQ(0, visitor.end_of_stream_count_); + EXPECT_EQ(4, visitor.data_frame_count_); +} + +// Test that the FIN flag on a data frame signifies EOF. +TEST_P(SpdyFramerTest, FinOnDataFrame) { + // Send HEADERS frames with END_HEADERS set. + // frame-format off + const unsigned char kH2Input[] = { + 0x00, 0x00, 0x05, // Length: 5 + 0x01, // Type: HEADERS + 0x24, // Flags: END_HEADERS|PRIORITY + 0x00, 0x00, 0x00, 0x01, // Stream: 1 + 0x00, 0x00, 0x00, 0x00, // Parent: 0 + 0x82, // Weight: 131 + + 0x00, 0x00, 0x01, // Length: 1 + 0x01, // Type: HEADERS + 0x04, // Flags: END_HEADERS + 0x00, 0x00, 0x00, 0x01, // Stream: 1 + 0x8c, // :status: 200 + + 0x00, 0x00, 0x0c, // Length: 12 + 0x00, // Type: DATA + 0x00, // Flags: none + 0x00, 0x00, 0x00, 0x01, // Stream: 1 + 0xde, 0xad, 0xbe, 0xef, // Payload + 0xde, 0xad, 0xbe, 0xef, // + 0xde, 0xad, 0xbe, 0xef, // + + 0x00, 0x00, 0x04, // Length: 4 + 0x00, // Type: DATA + 0x01, // Flags: END_STREAM + 0x00, 0x00, 0x00, 0x01, // Stream: 1 + 0xde, 0xad, 0xbe, 0xef, // Payload + }; + // frame-format on + + TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION); + visitor.SimulateInFramer(kH2Input, sizeof(kH2Input)); + + EXPECT_EQ(0, visitor.error_count_); + EXPECT_EQ(2, visitor.headers_frame_count_); + EXPECT_EQ(16, visitor.data_bytes_); + EXPECT_EQ(0, visitor.fin_frame_count_); + EXPECT_EQ(0, visitor.fin_flag_count_); + EXPECT_EQ(1, visitor.end_of_stream_count_); + EXPECT_EQ(2, visitor.data_frame_count_); +} + +TEST_P(SpdyFramerTest, FinOnHeadersFrame) { + // Send HEADERS frames with END_HEADERS set. + // frame-format off + const unsigned char kH2Input[] = { + 0x00, 0x00, 0x05, // Length: 5 + 0x01, // Type: HEADERS + 0x24, // Flags: END_HEADERS|PRIORITY + 0x00, 0x00, 0x00, 0x01, // Stream: 1 + 0x00, 0x00, 0x00, 0x00, // Parent: 0 + 0x82, // Weight: 131 + + 0x00, 0x00, 0x01, // Length: 1 + 0x01, // Type: HEADERS + 0x05, // Flags: END_STREAM|END_HEADERS + 0x00, 0x00, 0x00, 0x01, // Stream: 1 + 0x8c, // :status: 200 + }; + // frame-format on + + TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION); + visitor.SimulateInFramer(kH2Input, sizeof(kH2Input)); + + EXPECT_EQ(0, visitor.error_count_); + EXPECT_EQ(2, visitor.headers_frame_count_); + EXPECT_EQ(0, visitor.data_bytes_); + EXPECT_EQ(0, visitor.fin_frame_count_); + EXPECT_EQ(1, visitor.fin_flag_count_); + EXPECT_EQ(1, visitor.end_of_stream_count_); + EXPECT_EQ(0, visitor.data_frame_count_); +} + +// Verify we can decompress the stream even if handed over to the +// framer 1 byte at a time. +TEST_P(SpdyFramerTest, UnclosedStreamDataCompressorsOneByteAtATime) { + const char kHeader1[] = "header1"; + const char kHeader2[] = "header2"; + const char kValue1[] = "value1"; + const char kValue2[] = "value2"; + + SpdyHeadersIR headers(/* stream_id = */ 1); + headers.SetHeader(kHeader1, kValue1); + headers.SetHeader(kHeader2, kValue2); + SpdySerializedFrame headers_frame(SpdyFramerPeer::SerializeHeaders( + &framer_, headers, use_output_ ? &output_ : nullptr)); + + const char bytes[] = "this is a test test test test test!"; + SpdyDataIR data_ir(/* stream_id = */ 1, + SpdyStringPiece(bytes, SPDY_ARRAYSIZE(bytes))); + data_ir.set_fin(true); + SpdySerializedFrame send_frame(framer_.SerializeData(data_ir)); + + // Run the inputs through the framer. + TestSpdyVisitor visitor(SpdyFramer::ENABLE_COMPRESSION); + const unsigned char* data; + data = reinterpret_cast<const unsigned char*>(headers_frame.data()); + for (size_t idx = 0; idx < headers_frame.size(); ++idx) { + visitor.SimulateInFramer(data + idx, 1); + ASSERT_EQ(0, visitor.error_count_); + } + data = reinterpret_cast<const unsigned char*>(send_frame.data()); + for (size_t idx = 0; idx < send_frame.size(); ++idx) { + visitor.SimulateInFramer(data + idx, 1); + ASSERT_EQ(0, visitor.error_count_); + } + + EXPECT_EQ(0, visitor.error_count_); + EXPECT_EQ(1, visitor.headers_frame_count_); + EXPECT_EQ(SPDY_ARRAYSIZE(bytes), static_cast<unsigned>(visitor.data_bytes_)); + EXPECT_EQ(0, visitor.fin_frame_count_); + EXPECT_EQ(0, visitor.fin_flag_count_); + EXPECT_EQ(1, visitor.end_of_stream_count_); + EXPECT_EQ(1, visitor.data_frame_count_); +} + +TEST_P(SpdyFramerTest, WindowUpdateFrame) { + SpdyWindowUpdateIR window_update(/* stream_id = */ 1, + /* delta = */ 0x12345678); + SpdySerializedFrame frame(framer_.SerializeWindowUpdate(window_update)); + if (use_output_) { + ASSERT_TRUE(framer_.SerializeWindowUpdate(window_update, &output_)); + frame = SpdySerializedFrame(output_.Begin(), output_.Size(), false); + } + + const char kDescription[] = "WINDOW_UPDATE frame, stream 1, delta 0x12345678"; + const unsigned char kH2FrameData[] = { + 0x00, 0x00, 0x04, // Length: 4 + 0x08, // Type: WINDOW_UPDATE + 0x00, // Flags: none + 0x00, 0x00, 0x00, 0x01, // Stream: 1 + 0x12, 0x34, 0x56, 0x78, // Increment: 305419896 + }; + + CompareFrame(kDescription, frame, kH2FrameData, SPDY_ARRAYSIZE(kH2FrameData)); +} + +TEST_P(SpdyFramerTest, CreateDataFrame) { + { + const char kDescription[] = "'hello' data frame, no FIN"; + // frame-format off + const unsigned char kH2FrameData[] = { + 0x00, 0x00, 0x05, // Length: 5 + 0x00, // Type: DATA + 0x00, // Flags: none + 0x00, 0x00, 0x00, 0x01, // Stream: 1 + 'h', 'e', 'l', 'l', // Payload + 'o', // + }; + // frame-format on + const char bytes[] = "hello"; + + SpdyDataIR data_ir(/* stream_id = */ 1, bytes); + SpdySerializedFrame frame(framer_.SerializeData(data_ir)); + CompareFrame(kDescription, frame, kH2FrameData, + SPDY_ARRAYSIZE(kH2FrameData)); + + SpdyDataIR data_header_ir(/* stream_id = */ 1); + data_header_ir.SetDataShallow(bytes); + frame = + framer_.SerializeDataFrameHeaderWithPaddingLengthField(data_header_ir); + CompareCharArraysWithHexError( + kDescription, reinterpret_cast<const unsigned char*>(frame.data()), + kDataFrameMinimumSize, kH2FrameData, kDataFrameMinimumSize); + } + + { + const char kDescription[] = "'hello' data frame with more padding, no FIN"; + // clang-format off + // frame-format off + const unsigned char kH2FrameData[] = { + 0x00, 0x00, 0xfd, // Length: 253 + 0x00, // Type: DATA + 0x08, // Flags: PADDED + 0x00, 0x00, 0x00, 0x01, // Stream: 1 + 0xf7, // PadLen: 247 trailing bytes + 'h', 'e', 'l', 'l', // Payload + 'o', // + // Padding of 247 0x00(s). + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + }; + // frame-format on + // clang-format on + const char bytes[] = "hello"; + + SpdyDataIR data_ir(/* stream_id = */ 1, bytes); + // 247 zeros and the pad length field make the overall padding to be 248 + // bytes. + data_ir.set_padding_len(248); + SpdySerializedFrame frame(framer_.SerializeData(data_ir)); + CompareFrame(kDescription, frame, kH2FrameData, + SPDY_ARRAYSIZE(kH2FrameData)); + + frame = framer_.SerializeDataFrameHeaderWithPaddingLengthField(data_ir); + CompareCharArraysWithHexError( + kDescription, reinterpret_cast<const unsigned char*>(frame.data()), + kDataFrameMinimumSize, kH2FrameData, kDataFrameMinimumSize); + } + + { + const char kDescription[] = "'hello' data frame with few padding, no FIN"; + // frame-format off + const unsigned char kH2FrameData[] = { + 0x00, 0x00, 0x0d, // Length: 13 + 0x00, // Type: DATA + 0x08, // Flags: PADDED + 0x00, 0x00, 0x00, 0x01, // Stream: 1 + 0x07, // PadLen: 7 trailing bytes + 'h', 'e', 'l', 'l', // Payload + 'o', // + 0x00, 0x00, 0x00, 0x00, // Padding + 0x00, 0x00, 0x00, // Padding + }; + // frame-format on + const char bytes[] = "hello"; + + SpdyDataIR data_ir(/* stream_id = */ 1, bytes); + // 7 zeros and the pad length field make the overall padding to be 8 bytes. + data_ir.set_padding_len(8); + SpdySerializedFrame frame(framer_.SerializeData(data_ir)); + CompareFrame(kDescription, frame, kH2FrameData, + SPDY_ARRAYSIZE(kH2FrameData)); + + frame = framer_.SerializeDataFrameHeaderWithPaddingLengthField(data_ir); + CompareCharArraysWithHexError( + kDescription, reinterpret_cast<const unsigned char*>(frame.data()), + kDataFrameMinimumSize, kH2FrameData, kDataFrameMinimumSize); + } + + { + const char kDescription[] = + "'hello' data frame with 1 byte padding, no FIN"; + // frame-format off + const unsigned char kH2FrameData[] = { + 0x00, 0x00, 0x06, // Length: 6 + 0x00, // Type: DATA + 0x08, // Flags: PADDED + 0x00, 0x00, 0x00, 0x01, // Stream: 1 + 0x00, // PadLen: 0 trailing bytes + 'h', 'e', 'l', 'l', // Payload + 'o', // + }; + // frame-format on + const char bytes[] = "hello"; + + SpdyDataIR data_ir(/* stream_id = */ 1, bytes); + // The pad length field itself is used for the 1-byte padding and no padding + // payload is needed. + data_ir.set_padding_len(1); + SpdySerializedFrame frame(framer_.SerializeData(data_ir)); + CompareFrame(kDescription, frame, kH2FrameData, + SPDY_ARRAYSIZE(kH2FrameData)); + + frame = framer_.SerializeDataFrameHeaderWithPaddingLengthField(data_ir); + CompareCharArraysWithHexError( + kDescription, reinterpret_cast<const unsigned char*>(frame.data()), + kDataFrameMinimumSize, kH2FrameData, kDataFrameMinimumSize); + } + + { + const char kDescription[] = "Data frame with negative data byte, no FIN"; + const unsigned char kH2FrameData[] = { + 0x00, 0x00, 0x01, // Length: 1 + 0x00, // Type: DATA + 0x00, // Flags: none + 0x00, 0x00, 0x00, 0x01, // Stream: 1 + 0xff, // Payload + }; + SpdyDataIR data_ir(/* stream_id = */ 1, "\xff"); + SpdySerializedFrame frame(framer_.SerializeData(data_ir)); + CompareFrame(kDescription, frame, kH2FrameData, + SPDY_ARRAYSIZE(kH2FrameData)); + } + + { + const char kDescription[] = "'hello' data frame, with FIN"; + const unsigned char kH2FrameData[] = { + 0x00, 0x00, 0x05, // Length: 5 + 0x00, // Type: DATA + 0x01, // Flags: END_STREAM + 0x00, 0x00, 0x00, 0x01, // Stream: 1 + 0x68, 0x65, 0x6c, 0x6c, // Payload + 0x6f, // + }; + SpdyDataIR data_ir(/* stream_id = */ 1, "hello"); + data_ir.set_fin(true); + SpdySerializedFrame frame(framer_.SerializeData(data_ir)); + CompareFrame(kDescription, frame, kH2FrameData, + SPDY_ARRAYSIZE(kH2FrameData)); + } + + { + const char kDescription[] = "Empty data frame"; + const unsigned char kH2FrameData[] = { + 0x00, 0x00, 0x00, // Length: 0 + 0x00, // Type: DATA + 0x00, // Flags: none + 0x00, 0x00, 0x00, 0x01, // Stream: 1 + }; + SpdyDataIR data_ir(/* stream_id = */ 1, ""); + SpdySerializedFrame frame(framer_.SerializeData(data_ir)); + CompareFrame(kDescription, frame, kH2FrameData, + SPDY_ARRAYSIZE(kH2FrameData)); + + frame = framer_.SerializeDataFrameHeaderWithPaddingLengthField(data_ir); + CompareCharArraysWithHexError( + kDescription, reinterpret_cast<const unsigned char*>(frame.data()), + kDataFrameMinimumSize, kH2FrameData, kDataFrameMinimumSize); + } + + { + const char kDescription[] = "Data frame with max stream ID"; + const unsigned char kH2FrameData[] = { + 0x00, 0x00, 0x05, // Length: 5 + 0x00, // Type: DATA + 0x01, // Flags: END_STREAM + 0x7f, 0xff, 0xff, 0xff, // Stream: 0x7fffffff + 0x68, 0x65, 0x6c, 0x6c, // Payload + 0x6f, // + }; + SpdyDataIR data_ir(/* stream_id = */ 0x7fffffff, "hello"); + data_ir.set_fin(true); + SpdySerializedFrame frame(framer_.SerializeData(data_ir)); + CompareFrame(kDescription, frame, kH2FrameData, + SPDY_ARRAYSIZE(kH2FrameData)); + } +} + +TEST_P(SpdyFramerTest, CreateRstStream) { + { + const char kDescription[] = "RST_STREAM frame"; + const unsigned char kH2FrameData[] = { + 0x00, 0x00, 0x04, // Length: 4 + 0x03, // Type: RST_STREAM + 0x00, // Flags: none + 0x00, 0x00, 0x00, 0x01, // Stream: 1 + 0x00, 0x00, 0x00, 0x01, // Error: PROTOCOL_ERROR + }; + SpdyRstStreamIR rst_stream(/* stream_id = */ 1, ERROR_CODE_PROTOCOL_ERROR); + SpdySerializedFrame frame(framer_.SerializeRstStream(rst_stream)); + if (use_output_) { + ASSERT_TRUE(framer_.SerializeRstStream(rst_stream, &output_)); + frame = SpdySerializedFrame(output_.Begin(), output_.Size(), false); + } + CompareFrame(kDescription, frame, kH2FrameData, + SPDY_ARRAYSIZE(kH2FrameData)); + } + + { + const char kDescription[] = "RST_STREAM frame with max stream ID"; + const unsigned char kH2FrameData[] = { + 0x00, 0x00, 0x04, // Length: 4 + 0x03, // Type: RST_STREAM + 0x00, // Flags: none + 0x7f, 0xff, 0xff, 0xff, // Stream: 0x7fffffff + 0x00, 0x00, 0x00, 0x01, // Error: PROTOCOL_ERROR + }; + SpdyRstStreamIR rst_stream(/* stream_id = */ 0x7FFFFFFF, + ERROR_CODE_PROTOCOL_ERROR); + SpdySerializedFrame frame(framer_.SerializeRstStream(rst_stream)); + if (use_output_) { + output_.Reset(); + ASSERT_TRUE(framer_.SerializeRstStream(rst_stream, &output_)); + frame = SpdySerializedFrame(output_.Begin(), output_.Size(), false); + } + CompareFrame(kDescription, frame, kH2FrameData, + SPDY_ARRAYSIZE(kH2FrameData)); + } + + { + const char kDescription[] = "RST_STREAM frame with max status code"; + const unsigned char kH2FrameData[] = { + 0x00, 0x00, 0x04, // Length: 4 + 0x03, // Type: RST_STREAM + 0x00, // Flags: none + 0x7f, 0xff, 0xff, 0xff, // Stream: 0x7fffffff + 0x00, 0x00, 0x00, 0x02, // Error: INTERNAL_ERROR + }; + SpdyRstStreamIR rst_stream(/* stream_id = */ 0x7FFFFFFF, + ERROR_CODE_INTERNAL_ERROR); + SpdySerializedFrame frame(framer_.SerializeRstStream(rst_stream)); + if (use_output_) { + output_.Reset(); + ASSERT_TRUE(framer_.SerializeRstStream(rst_stream, &output_)); + frame = SpdySerializedFrame(output_.Begin(), output_.Size(), false); + } + CompareFrame(kDescription, frame, kH2FrameData, + SPDY_ARRAYSIZE(kH2FrameData)); + } +} + +TEST_P(SpdyFramerTest, CreateSettings) { + { + const char kDescription[] = "Network byte order SETTINGS frame"; + const unsigned char kH2FrameData[] = { + 0x00, 0x00, 0x06, // Length: 6 + 0x04, // Type: SETTINGS + 0x00, // Flags: none + 0x00, 0x00, 0x00, 0x00, // Stream: 0 + 0x00, 0x04, // Param: INITIAL_WINDOW_SIZE + 0x0a, 0x0b, 0x0c, 0x0d, // Value: 168496141 + }; + + uint32_t kValue = 0x0a0b0c0d; + SpdySettingsIR settings_ir; + + SpdyKnownSettingsId kId = SETTINGS_INITIAL_WINDOW_SIZE; + settings_ir.AddSetting(kId, kValue); + + SpdySerializedFrame frame(framer_.SerializeSettings(settings_ir)); + if (use_output_) { + ASSERT_TRUE(framer_.SerializeSettings(settings_ir, &output_)); + frame = SpdySerializedFrame(output_.Begin(), output_.Size(), false); + } + CompareFrame(kDescription, frame, kH2FrameData, + SPDY_ARRAYSIZE(kH2FrameData)); + } + + { + const char kDescription[] = "Basic SETTINGS frame"; + // These end up seemingly out of order because of the way that our internal + // ordering for settings_ir works. HTTP2 has no requirement on ordering on + // the wire. + const unsigned char kH2FrameData[] = { + 0x00, 0x00, 0x18, // Length: 24 + 0x04, // Type: SETTINGS + 0x00, // Flags: none + 0x00, 0x00, 0x00, 0x00, // Stream: 0 + 0x00, 0x01, // Param: HEADER_TABLE_SIZE + 0x00, 0x00, 0x00, 0x05, // Value: 5 + 0x00, 0x02, // Param: ENABLE_PUSH + 0x00, 0x00, 0x00, 0x06, // Value: 6 + 0x00, 0x03, // Param: MAX_CONCURRENT_STREAMS + 0x00, 0x00, 0x00, 0x07, // Value: 7 + 0x00, 0x04, // Param: INITIAL_WINDOW_SIZE + 0x00, 0x00, 0x00, 0x08, // Value: 8 + }; + + SpdySettingsIR settings_ir; + settings_ir.AddSetting(SETTINGS_HEADER_TABLE_SIZE, 5); + settings_ir.AddSetting(SETTINGS_ENABLE_PUSH, 6); + settings_ir.AddSetting(SETTINGS_MAX_CONCURRENT_STREAMS, 7); + settings_ir.AddSetting(SETTINGS_INITIAL_WINDOW_SIZE, 8); + SpdySerializedFrame frame(framer_.SerializeSettings(settings_ir)); + if (use_output_) { + output_.Reset(); + ASSERT_TRUE(framer_.SerializeSettings(settings_ir, &output_)); + frame = SpdySerializedFrame(output_.Begin(), output_.Size(), false); + } + + CompareFrame(kDescription, frame, kH2FrameData, + SPDY_ARRAYSIZE(kH2FrameData)); + } + + { + const char kDescription[] = "Empty SETTINGS frame"; + const unsigned char kH2FrameData[] = { + 0x00, 0x00, 0x00, // Length: 0 + 0x04, // Type: SETTINGS + 0x00, // Flags: none + 0x00, 0x00, 0x00, 0x00, // Stream: 0 + }; + SpdySettingsIR settings_ir; + SpdySerializedFrame frame(framer_.SerializeSettings(settings_ir)); + if (use_output_) { + output_.Reset(); + ASSERT_TRUE(framer_.SerializeSettings(settings_ir, &output_)); + frame = SpdySerializedFrame(output_.Begin(), output_.Size(), false); + } + + CompareFrame(kDescription, frame, kH2FrameData, + SPDY_ARRAYSIZE(kH2FrameData)); + } +} + +TEST_P(SpdyFramerTest, CreatePingFrame) { + { + const char kDescription[] = "PING frame"; + const unsigned char kH2FrameData[] = { + 0x00, 0x00, 0x08, // Length: 8 + 0x06, // Type: PING + 0x00, // Flags: none + 0x00, 0x00, 0x00, 0x00, // Stream: 0 + 0x12, 0x34, 0x56, 0x78, // Opaque + 0x9a, 0xbc, 0xde, 0xff, // Data + }; + const unsigned char kH2FrameDataWithAck[] = { + 0x00, 0x00, 0x08, // Length: 8 + 0x06, // Type: PING + 0x01, // Flags: ACK + 0x00, 0x00, 0x00, 0x00, // Stream: 0 + 0x12, 0x34, 0x56, 0x78, // Opaque + 0x9a, 0xbc, 0xde, 0xff, // Data + }; + const SpdyPingId kPingId = 0x123456789abcdeffULL; + SpdyPingIR ping_ir(kPingId); + // Tests SpdyPingIR when the ping is not an ack. + ASSERT_FALSE(ping_ir.is_ack()); + SpdySerializedFrame frame(framer_.SerializePing(ping_ir)); + if (use_output_) { + ASSERT_TRUE(framer_.SerializePing(ping_ir, &output_)); + frame = SpdySerializedFrame(output_.Begin(), output_.Size(), false); + } + CompareFrame(kDescription, frame, kH2FrameData, + SPDY_ARRAYSIZE(kH2FrameData)); + + // Tests SpdyPingIR when the ping is an ack. + ping_ir.set_is_ack(true); + frame = framer_.SerializePing(ping_ir); + if (use_output_) { + output_.Reset(); + ASSERT_TRUE(framer_.SerializePing(ping_ir, &output_)); + frame = SpdySerializedFrame(output_.Begin(), output_.Size(), false); + } + CompareFrame(kDescription, frame, kH2FrameDataWithAck, + SPDY_ARRAYSIZE(kH2FrameDataWithAck)); + } +} + +TEST_P(SpdyFramerTest, CreateGoAway) { + { + const char kDescription[] = "GOAWAY frame"; + const unsigned char kH2FrameData[] = { + 0x00, 0x00, 0x0a, // Length: 10 + 0x07, // Type: GOAWAY + 0x00, // Flags: none + 0x00, 0x00, 0x00, 0x00, // Stream: 0 + 0x00, 0x00, 0x00, 0x00, // Last: 0 + 0x00, 0x00, 0x00, 0x00, // Error: NO_ERROR + 0x47, 0x41, // Description + }; + SpdyGoAwayIR goaway_ir(/* last_good_stream_id = */ 0, ERROR_CODE_NO_ERROR, + "GA"); + SpdySerializedFrame frame(framer_.SerializeGoAway(goaway_ir)); + if (use_output_) { + ASSERT_TRUE(framer_.SerializeGoAway(goaway_ir, &output_)); + frame = SpdySerializedFrame(output_.Begin(), output_.Size(), false); + } + CompareFrame(kDescription, frame, kH2FrameData, + SPDY_ARRAYSIZE(kH2FrameData)); + } + + { + const char kDescription[] = "GOAWAY frame with max stream ID, status"; + const unsigned char kH2FrameData[] = { + 0x00, 0x00, 0x0a, // Length: 10 + 0x07, // Type: GOAWAY + 0x00, // Flags: none + 0x00, 0x00, 0x00, 0x00, // Stream: 0 + 0x7f, 0xff, 0xff, 0xff, // Last: 0x7fffffff + 0x00, 0x00, 0x00, 0x02, // Error: INTERNAL_ERROR + 0x47, 0x41, // Description + }; + SpdyGoAwayIR goaway_ir(/* last_good_stream_id = */ 0x7FFFFFFF, + ERROR_CODE_INTERNAL_ERROR, "GA"); + SpdySerializedFrame frame(framer_.SerializeGoAway(goaway_ir)); + if (use_output_) { + output_.Reset(); + ASSERT_TRUE(framer_.SerializeGoAway(goaway_ir, &output_)); + frame = SpdySerializedFrame(output_.Begin(), output_.Size(), false); + } + CompareFrame(kDescription, frame, kH2FrameData, + SPDY_ARRAYSIZE(kH2FrameData)); + } +} + +TEST_P(SpdyFramerTest, CreateHeadersUncompressed) { + SpdyFramer framer(SpdyFramer::DISABLE_COMPRESSION); + + { + const char kDescription[] = "HEADERS frame, no FIN"; + // frame-format off + const unsigned char kH2FrameData[] = { + 0x00, 0x00, 0x12, // Length: 18 + 0x01, // Type: HEADERS + 0x04, // Flags: END_HEADERS + 0x00, 0x00, 0x00, 0x01, // Stream: 1 + + 0x00, // Unindexed Entry + 0x03, // Name Len: 3 + 0x62, 0x61, 0x72, // bar + 0x03, // Value Len: 3 + 0x66, 0x6f, 0x6f, // foo + + 0x00, // Unindexed Entry + 0x03, // Name Len: 3 + 0x66, 0x6f, 0x6f, // foo + 0x03, // Value Len: 3 + 0x62, 0x61, 0x72, // bar + }; + // frame-format on + SpdyHeadersIR headers(/* stream_id = */ 1); + headers.SetHeader("bar", "foo"); + headers.SetHeader("foo", "bar"); + SpdySerializedFrame frame(SpdyFramerPeer::SerializeHeaders( + &framer, headers, use_output_ ? &output_ : nullptr)); + CompareFrame(kDescription, frame, kH2FrameData, + SPDY_ARRAYSIZE(kH2FrameData)); + } + + { + const char kDescription[] = + "HEADERS frame with a 0-length header name, FIN, max stream ID"; + // frame-format off + const unsigned char kH2FrameData[] = { + 0x00, 0x00, 0x0f, // Length: 15 + 0x01, // Type: HEADERS + 0x05, // Flags: END_STREAM|END_HEADERS + 0x7f, 0xff, 0xff, 0xff, // Stream: 2147483647 + + 0x00, // Unindexed Entry + 0x00, // Name Len: 0 + 0x03, // Value Len: 3 + 0x66, 0x6f, 0x6f, // foo + + 0x00, // Unindexed Entry + 0x03, // Name Len: 3 + 0x66, 0x6f, 0x6f, // foo + 0x03, // Value Len: 3 + 0x62, 0x61, 0x72, // bar + }; + // frame-format on + SpdyHeadersIR headers(/* stream_id = */ 0x7fffffff); + headers.set_fin(true); + headers.SetHeader("", "foo"); + headers.SetHeader("foo", "bar"); + SpdySerializedFrame frame(SpdyFramerPeer::SerializeHeaders( + &framer, headers, use_output_ ? &output_ : nullptr)); + CompareFrame(kDescription, frame, kH2FrameData, + SPDY_ARRAYSIZE(kH2FrameData)); + } + + { + const char kDescription[] = + "HEADERS frame with a 0-length header val, FIN, max stream ID"; + // frame-format off + const unsigned char kH2FrameData[] = { + 0x00, 0x00, 0x0f, // Length: 15 + 0x01, // Type: HEADERS + 0x05, // Flags: END_STREAM|END_HEADERS + 0x7f, 0xff, 0xff, 0xff, // Stream: 2147483647 + + 0x00, // Unindexed Entry + 0x03, // Name Len: 3 + 0x62, 0x61, 0x72, // bar + 0x03, // Value Len: 3 + 0x66, 0x6f, 0x6f, // foo + + 0x00, // Unindexed Entry + 0x03, // Name Len: 3 + 0x66, 0x6f, 0x6f, // foo + 0x00, // Value Len: 0 + }; + // frame-format on + SpdyHeadersIR headers_ir(/* stream_id = */ 0x7fffffff); + headers_ir.set_fin(true); + headers_ir.SetHeader("bar", "foo"); + headers_ir.SetHeader("foo", ""); + SpdySerializedFrame frame(SpdyFramerPeer::SerializeHeaders( + &framer, headers_ir, use_output_ ? &output_ : nullptr)); + CompareFrame(kDescription, frame, kH2FrameData, + SPDY_ARRAYSIZE(kH2FrameData)); + } + + { + const char kDescription[] = + "HEADERS frame with a 0-length header val, FIN, max stream ID, pri"; + + // frame-format off + const unsigned char kH2FrameData[] = { + 0x00, 0x00, 0x14, // Length: 20 + 0x01, // Type: HEADERS + 0x25, // Flags: END_STREAM|END_HEADERS|PRIORITY + 0x7f, 0xff, 0xff, 0xff, // Stream: 2147483647 + 0x00, 0x00, 0x00, 0x00, // Parent: 0 + 0xdb, // Weight: 220 + + 0x00, // Unindexed Entry + 0x03, // Name Len: 3 + 0x62, 0x61, 0x72, // bar + 0x03, // Value Len: 3 + 0x66, 0x6f, 0x6f, // foo + + 0x00, // Unindexed Entry + 0x03, // Name Len: 3 + 0x66, 0x6f, 0x6f, // foo + 0x00, // Value Len: 0 + }; + // frame-format on + SpdyHeadersIR headers_ir(/* stream_id = */ 0x7fffffff); + headers_ir.set_fin(true); + headers_ir.set_has_priority(true); + headers_ir.set_weight(220); + headers_ir.SetHeader("bar", "foo"); + headers_ir.SetHeader("foo", ""); + SpdySerializedFrame frame(SpdyFramerPeer::SerializeHeaders( + &framer, headers_ir, use_output_ ? &output_ : nullptr)); + CompareFrame(kDescription, frame, kH2FrameData, + SPDY_ARRAYSIZE(kH2FrameData)); + } + + { + const char kDescription[] = + "HEADERS frame with a 0-length header val, FIN, max stream ID, pri, " + "exclusive=true, parent_stream=0"; + + // frame-format off + const unsigned char kV4FrameData[] = { + 0x00, 0x00, 0x14, // Length: 20 + 0x01, // Type: HEADERS + 0x25, // Flags: END_STREAM|END_HEADERS|PRIORITY + 0x7f, 0xff, 0xff, 0xff, // Stream: 2147483647 + 0x80, 0x00, 0x00, 0x00, // Parent: 0 (Exclusive) + 0xdb, // Weight: 220 + + 0x00, // Unindexed Entry + 0x03, // Name Len: 3 + 0x62, 0x61, 0x72, // bar + 0x03, // Value Len: 3 + 0x66, 0x6f, 0x6f, // foo + + 0x00, // Unindexed Entry + 0x03, // Name Len: 3 + 0x66, 0x6f, 0x6f, // foo + 0x00, // Value Len: 0 + }; + // frame-format on + SpdyHeadersIR headers_ir(/* stream_id = */ 0x7fffffff); + headers_ir.set_fin(true); + headers_ir.set_has_priority(true); + headers_ir.set_weight(220); + headers_ir.set_exclusive(true); + headers_ir.set_parent_stream_id(0); + headers_ir.SetHeader("bar", "foo"); + headers_ir.SetHeader("foo", ""); + SpdySerializedFrame frame(SpdyFramerPeer::SerializeHeaders( + &framer, headers_ir, use_output_ ? &output_ : nullptr)); + CompareFrame(kDescription, frame, kV4FrameData, + SPDY_ARRAYSIZE(kV4FrameData)); + } + + { + const char kDescription[] = + "HEADERS frame with a 0-length header val, FIN, max stream ID, pri, " + "exclusive=false, parent_stream=max stream ID"; + + // frame-format off + const unsigned char kV4FrameData[] = { + 0x00, 0x00, 0x14, // Length: 20 + 0x01, // Type: HEADERS + 0x25, // Flags: END_STREAM|END_HEADERS|PRIORITY + 0x7f, 0xff, 0xff, 0xff, // Stream: 2147483647 + 0x7f, 0xff, 0xff, 0xff, // Parent: 2147483647 + 0xdb, // Weight: 220 + + 0x00, // Unindexed Entry + 0x03, // Name Len: 3 + 0x62, 0x61, 0x72, // bar + 0x03, // Value Len: 3 + 0x66, 0x6f, 0x6f, // foo + + 0x00, // Unindexed Entry + 0x03, // Name Len: 3 + 0x66, 0x6f, 0x6f, // foo + 0x00, // Value Len: 0 + }; + // frame-format on + SpdyHeadersIR headers_ir(/* stream_id = */ 0x7fffffff); + headers_ir.set_fin(true); + headers_ir.set_has_priority(true); + headers_ir.set_weight(220); + headers_ir.set_exclusive(false); + headers_ir.set_parent_stream_id(0x7fffffff); + headers_ir.SetHeader("bar", "foo"); + headers_ir.SetHeader("foo", ""); + SpdySerializedFrame frame(SpdyFramerPeer::SerializeHeaders( + &framer, headers_ir, use_output_ ? &output_ : nullptr)); + CompareFrame(kDescription, frame, kV4FrameData, + SPDY_ARRAYSIZE(kV4FrameData)); + } + + { + const char kDescription[] = + "HEADERS frame with a 0-length header name, FIN, max stream ID, padded"; + + // frame-format off + const unsigned char kH2FrameData[] = { + 0x00, 0x00, 0x15, // Length: 21 + 0x01, // Type: HEADERS + 0x0d, // Flags: END_STREAM|END_HEADERS|PADDED + 0x7f, 0xff, 0xff, 0xff, // Stream: 2147483647 + 0x05, // PadLen: 5 trailing bytes + + 0x00, // Unindexed Entry + 0x00, // Name Len: 0 + 0x03, // Value Len: 3 + 0x66, 0x6f, 0x6f, // foo + + 0x00, // Unindexed Entry + 0x03, // Name Len: 3 + 0x66, 0x6f, 0x6f, // foo + 0x03, // Value Len: 3 + 0x62, 0x61, 0x72, // bar + + 0x00, 0x00, 0x00, 0x00, // Padding + 0x00, // Padding + }; + // frame-format on + SpdyHeadersIR headers_ir(/* stream_id = */ 0x7fffffff); + headers_ir.set_fin(true); + headers_ir.SetHeader("", "foo"); + headers_ir.SetHeader("foo", "bar"); + headers_ir.set_padding_len(6); + SpdySerializedFrame frame(SpdyFramerPeer::SerializeHeaders( + &framer, headers_ir, use_output_ ? &output_ : nullptr)); + CompareFrame(kDescription, frame, kH2FrameData, + SPDY_ARRAYSIZE(kH2FrameData)); + } +} + +TEST_P(SpdyFramerTest, CreateWindowUpdate) { + { + const char kDescription[] = "WINDOW_UPDATE frame"; + const unsigned char kH2FrameData[] = { + 0x00, 0x00, 0x04, // Length: 4 + 0x08, // Type: WINDOW_UPDATE + 0x00, // Flags: none + 0x00, 0x00, 0x00, 0x01, // Stream: 1 + 0x00, 0x00, 0x00, 0x01, // Increment: 1 + }; + SpdySerializedFrame frame(framer_.SerializeWindowUpdate( + SpdyWindowUpdateIR(/* stream_id = */ 1, /* delta = */ 1))); + if (use_output_) { + output_.Reset(); + ASSERT_TRUE(framer_.SerializeWindowUpdate( + SpdyWindowUpdateIR(/* stream_id = */ 1, /* delta = */ 1), &output_)); + frame = SpdySerializedFrame(output_.Begin(), output_.Size(), false); + } + CompareFrame(kDescription, frame, kH2FrameData, + SPDY_ARRAYSIZE(kH2FrameData)); + } + + { + const char kDescription[] = "WINDOW_UPDATE frame with max stream ID"; + const unsigned char kH2FrameData[] = { + 0x00, 0x00, 0x04, // Length: 4 + 0x08, // Type: WINDOW_UPDATE + 0x00, // Flags: none + 0x7f, 0xff, 0xff, 0xff, // Stream: 0x7fffffff + 0x00, 0x00, 0x00, 0x01, // Increment: 1 + }; + SpdySerializedFrame frame(framer_.SerializeWindowUpdate( + SpdyWindowUpdateIR(/* stream_id = */ 0x7FFFFFFF, /* delta = */ 1))); + if (use_output_) { + output_.Reset(); + ASSERT_TRUE(framer_.SerializeWindowUpdate( + SpdyWindowUpdateIR(/* stream_id = */ 0x7FFFFFFF, /* delta = */ 1), + &output_)); + frame = SpdySerializedFrame(output_.Begin(), output_.Size(), false); + } + CompareFrame(kDescription, frame, kH2FrameData, + SPDY_ARRAYSIZE(kH2FrameData)); + } + + { + const char kDescription[] = "WINDOW_UPDATE frame with max window delta"; + const unsigned char kH2FrameData[] = { + 0x00, 0x00, 0x04, // Length: 4 + 0x08, // Type: WINDOW_UPDATE + 0x00, // Flags: none + 0x00, 0x00, 0x00, 0x01, // Stream: 1 + 0x7f, 0xff, 0xff, 0xff, // Increment: 0x7fffffff + }; + SpdySerializedFrame frame(framer_.SerializeWindowUpdate( + SpdyWindowUpdateIR(/* stream_id = */ 1, /* delta = */ 0x7FFFFFFF))); + if (use_output_) { + output_.Reset(); + ASSERT_TRUE(framer_.SerializeWindowUpdate( + SpdyWindowUpdateIR(/* stream_id = */ 1, /* delta = */ 0x7FFFFFFF), + &output_)); + frame = SpdySerializedFrame(output_.Begin(), output_.Size(), false); + } + CompareFrame(kDescription, frame, kH2FrameData, + SPDY_ARRAYSIZE(kH2FrameData)); + } +} + +TEST_P(SpdyFramerTest, CreatePushPromiseUncompressed) { + { + // Test framing PUSH_PROMISE without padding. + SpdyFramer framer(SpdyFramer::DISABLE_COMPRESSION); + const char kDescription[] = "PUSH_PROMISE frame without padding"; + + // frame-format off + const unsigned char kFrameData[] = { + 0x00, 0x00, 0x16, // Length: 22 + 0x05, // Type: PUSH_PROMISE + 0x04, // Flags: END_HEADERS + 0x00, 0x00, 0x00, 0x29, // Stream: 41 + 0x00, 0x00, 0x00, 0x3a, // Promise: 58 + + 0x00, // Unindexed Entry + 0x03, // Name Len: 3 + 0x62, 0x61, 0x72, // bar + 0x03, // Value Len: 3 + 0x66, 0x6f, 0x6f, // foo + + 0x00, // Unindexed Entry + 0x03, // Name Len: 3 + 0x66, 0x6f, 0x6f, // foo + 0x03, // Value Len: 3 + 0x62, 0x61, 0x72, // bar + }; + // frame-format on + + SpdyPushPromiseIR push_promise(/* stream_id = */ 41, + /* promised_stream_id = */ 58); + push_promise.SetHeader("bar", "foo"); + push_promise.SetHeader("foo", "bar"); + SpdySerializedFrame frame(SpdyFramerPeer::SerializePushPromise( + &framer, push_promise, use_output_ ? &output_ : nullptr)); + CompareFrame(kDescription, frame, kFrameData, SPDY_ARRAYSIZE(kFrameData)); + } + + { + // Test framing PUSH_PROMISE with one byte of padding. + SpdyFramer framer(SpdyFramer::DISABLE_COMPRESSION); + const char kDescription[] = "PUSH_PROMISE frame with one byte of padding"; + + // frame-format off + const unsigned char kFrameData[] = { + 0x00, 0x00, 0x17, // Length: 23 + 0x05, // Type: PUSH_PROMISE + 0x0c, // Flags: END_HEADERS|PADDED + 0x00, 0x00, 0x00, 0x29, // Stream: 41 + 0x00, // PadLen: 0 trailing bytes + 0x00, 0x00, 0x00, 0x3a, // Promise: 58 + + 0x00, // Unindexed Entry + 0x03, // Name Len: 3 + 0x62, 0x61, 0x72, // bar + 0x03, // Value Len: 3 + 0x66, 0x6f, 0x6f, // foo + + 0x00, // Unindexed Entry + 0x03, // Name Len: 3 + 0x66, 0x6f, 0x6f, // foo + 0x03, // Value Len: 3 + 0x62, 0x61, 0x72, // bar + }; + // frame-format on + + SpdyPushPromiseIR push_promise(/* stream_id = */ 41, + /* promised_stream_id = */ 58); + push_promise.set_padding_len(1); + push_promise.SetHeader("bar", "foo"); + push_promise.SetHeader("foo", "bar"); + output_.Reset(); + SpdySerializedFrame frame(SpdyFramerPeer::SerializePushPromise( + &framer, push_promise, use_output_ ? &output_ : nullptr)); + + CompareFrame(kDescription, frame, kFrameData, SPDY_ARRAYSIZE(kFrameData)); + } + + { + // Test framing PUSH_PROMISE with 177 bytes of padding. + SpdyFramer framer(SpdyFramer::DISABLE_COMPRESSION); + const char kDescription[] = "PUSH_PROMISE frame with 177 bytes of padding"; + + // frame-format off + // clang-format off + const unsigned char kFrameData[] = { + 0x00, 0x00, 0xc7, // Length: 199 + 0x05, // Type: PUSH_PROMISE + 0x0c, // Flags: END_HEADERS|PADDED + 0x00, 0x00, 0x00, 0x2a, // Stream: 42 + 0xb0, // PadLen: 176 trailing bytes + 0x00, 0x00, 0x00, 0x39, // Promise: 57 + + 0x00, // Unindexed Entry + 0x03, // Name Len: 3 + 0x62, 0x61, 0x72, // bar + 0x03, // Value Len: 3 + 0x66, 0x6f, 0x6f, // foo + + 0x00, // Unindexed Entry + 0x03, // Name Len: 3 + 0x66, 0x6f, 0x6f, // foo + 0x03, // Value Len: 3 + 0x62, 0x61, 0x72, // bar + + // Padding of 176 0x00(s). + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + }; + // clang-format on + // frame-format on + + SpdyPushPromiseIR push_promise(/* stream_id = */ 42, + /* promised_stream_id = */ 57); + push_promise.set_padding_len(177); + push_promise.SetHeader("bar", "foo"); + push_promise.SetHeader("foo", "bar"); + output_.Reset(); + SpdySerializedFrame frame(SpdyFramerPeer::SerializePushPromise( + &framer, push_promise, use_output_ ? &output_ : nullptr)); + + CompareFrame(kDescription, frame, kFrameData, SPDY_ARRAYSIZE(kFrameData)); + } +} + +// Regression test for https://crbug.com/464748. +TEST_P(SpdyFramerTest, GetNumberRequiredContinuationFrames) { + EXPECT_EQ(1u, GetNumberRequiredContinuationFrames(16383 + 16374)); + EXPECT_EQ(2u, GetNumberRequiredContinuationFrames(16383 + 16374 + 1)); + EXPECT_EQ(2u, GetNumberRequiredContinuationFrames(16383 + 2 * 16374)); + EXPECT_EQ(3u, GetNumberRequiredContinuationFrames(16383 + 2 * 16374 + 1)); +} + +TEST_P(SpdyFramerTest, CreateContinuationUncompressed) { + SpdyFramer framer(SpdyFramer::DISABLE_COMPRESSION); + const char kDescription[] = "CONTINUATION frame"; + + // frame-format off + const unsigned char kFrameData[] = { + 0x00, 0x00, 0x12, // Length: 18 + 0x09, // Type: CONTINUATION + 0x04, // Flags: END_HEADERS + 0x00, 0x00, 0x00, 0x2a, // Stream: 42 + + 0x00, // Unindexed Entry + 0x03, // Name Len: 3 + 0x62, 0x61, 0x72, // bar + 0x03, // Value Len: 3 + 0x66, 0x6f, 0x6f, // foo + + 0x00, // Unindexed Entry + 0x03, // Name Len: 3 + 0x66, 0x6f, 0x6f, // foo + 0x03, // Value Len: 3 + 0x62, 0x61, 0x72, // bar + }; + // frame-format on + + SpdyHeaderBlock header_block; + header_block["bar"] = "foo"; + header_block["foo"] = "bar"; + auto buffer = SpdyMakeUnique<SpdyString>(); + HpackEncoder encoder(ObtainHpackHuffmanTable()); + encoder.DisableCompression(); + encoder.EncodeHeaderSet(header_block, buffer.get()); + + SpdyContinuationIR continuation(/* stream_id = */ 42); + continuation.take_encoding(std::move(buffer)); + continuation.set_end_headers(true); + + SpdySerializedFrame frame(framer.SerializeContinuation(continuation)); + if (use_output_) { + ASSERT_TRUE(framer.SerializeContinuation(continuation, &output_)); + frame = SpdySerializedFrame(output_.Begin(), output_.Size(), false); + } + CompareFrame(kDescription, frame, kFrameData, SPDY_ARRAYSIZE(kFrameData)); +} + +// Test that if we send an unexpected CONTINUATION +// we signal an error (but don't crash). +TEST_P(SpdyFramerTest, SendUnexpectedContinuation) { + testing::StrictMock<test::MockSpdyFramerVisitor> visitor; + + deframer_.set_visitor(&visitor); + + // frame-format off + char kH2FrameData[] = { + 0x00, 0x00, 0x12, // Length: 18 + 0x09, // Type: CONTINUATION + 0x04, // Flags: END_HEADERS + 0x00, 0x00, 0x00, 0x2a, // Stream: 42 + + 0x00, // Unindexed Entry + 0x03, // Name Len: 3 + 0x62, 0x61, 0x72, // bar + 0x03, // Value Len: 3 + 0x66, 0x6f, 0x6f, // foo + + 0x00, // Unindexed Entry + 0x03, // Name Len: 3 + 0x66, 0x6f, 0x6f, // foo + 0x03, // Value Len: 3 + 0x62, 0x61, 0x72, // bar + }; + // frame-format on + + SpdySerializedFrame frame(kH2FrameData, sizeof(kH2FrameData), false); + + // We shouldn't have to read the whole frame before we signal an error. + EXPECT_CALL(visitor, OnError(Http2DecoderAdapter::SPDY_UNEXPECTED_FRAME)); + EXPECT_GT(frame.size(), deframer_.ProcessInput(frame.data(), frame.size())); + EXPECT_TRUE(deframer_.HasError()); + EXPECT_EQ(Http2DecoderAdapter::SPDY_UNEXPECTED_FRAME, + deframer_.spdy_framer_error()) + << Http2DecoderAdapter::SpdyFramerErrorToString( + deframer_.spdy_framer_error()); +} + +TEST_P(SpdyFramerTest, CreatePushPromiseThenContinuationUncompressed) { + { + // Test framing in a case such that a PUSH_PROMISE frame, with one byte of + // padding, cannot hold all the data payload, which is overflowed to the + // consecutive CONTINUATION frame. + SpdyFramer framer(SpdyFramer::DISABLE_COMPRESSION); + const char kDescription[] = + "PUSH_PROMISE and CONTINUATION frames with one byte of padding"; + + // frame-format off + const unsigned char kPartialPushPromiseFrameData[] = { + 0x00, 0x3f, 0xf6, // Length: 16374 + 0x05, // Type: PUSH_PROMISE + 0x08, // Flags: PADDED + 0x00, 0x00, 0x00, 0x2a, // Stream: 42 + 0x00, // PadLen: 0 trailing bytes + 0x00, 0x00, 0x00, 0x39, // Promise: 57 + + 0x00, // Unindexed Entry + 0x03, // Name Len: 3 + 0x78, 0x78, 0x78, // xxx + 0x7f, 0x80, 0x7f, // Value Len: 16361 + 0x78, 0x78, 0x78, 0x78, // xxxx + 0x78, 0x78, 0x78, 0x78, // xxxx + 0x78, 0x78, 0x78, 0x78, // xxxx + 0x78, 0x78, 0x78, 0x78, // xxxx + 0x78, 0x78, 0x78, 0x78, // xxxx + 0x78, 0x78, 0x78, 0x78, // xxxx + 0x78, 0x78, 0x78, 0x78, // xxxx + 0x78, 0x78, 0x78, 0x78, // xxxx + 0x78, 0x78, 0x78, 0x78, // xxxx + 0x78, 0x78, 0x78, 0x78, // xxxx + 0x78, 0x78, 0x78, 0x78, // xxxx + 0x78, 0x78, 0x78, 0x78, // xxxx + 0x78, 0x78, 0x78, 0x78, // xxxx + 0x78, 0x78, 0x78, 0x78, // xxxx + 0x78, 0x78, 0x78, 0x78, // xxxx + 0x78, 0x78, 0x78, 0x78, // xxxx + 0x78, 0x78, 0x78, 0x78, // xxxx + 0x78, 0x78, 0x78, 0x78, // xxxx + 0x78, 0x78, 0x78, 0x78, // xxxx + 0x78, 0x78, 0x78, 0x78, // xxxx + 0x78, 0x78, 0x78, 0x78, // xxxx + }; + const unsigned char kContinuationFrameData[] = { + 0x00, 0x00, 0x16, // Length: 22 + 0x09, // Type: CONTINUATION + 0x04, // Flags: END_HEADERS + 0x00, 0x00, 0x00, 0x2a, // Stream: 42 + 0x78, 0x78, 0x78, 0x78, // xxxx + 0x78, 0x78, 0x78, 0x78, // xxxx + 0x78, 0x78, 0x78, 0x78, // xxxx + 0x78, 0x78, 0x78, 0x78, // xxxx + 0x78, 0x78, 0x78, 0x78, // xxxx + 0x78, // x + }; + // frame-format on + + SpdyPushPromiseIR push_promise(/* stream_id = */ 42, + /* promised_stream_id = */ 57); + push_promise.set_padding_len(1); + SpdyString big_value(kHttp2MaxControlFrameSendSize, 'x'); + push_promise.SetHeader("xxx", big_value); + SpdySerializedFrame frame(SpdyFramerPeer::SerializePushPromise( + &framer, push_promise, use_output_ ? &output_ : nullptr)); + + // The entire frame should look like below: + // Name Length in Byte + // ------------------------------------------- Begin of PUSH_PROMISE frame + // PUSH_PROMISE header 9 + // Pad length field 1 + // Promised stream 4 + // Length field of key 2 + // Content of key 3 + // Length field of value 3 + // Part of big_value 16361 + // ------------------------------------------- Begin of CONTINUATION frame + // CONTINUATION header 9 + // Remaining of big_value 22 + // ------------------------------------------- End + + // Length of everything listed above except big_value. + int len_non_data_payload = 31; + EXPECT_EQ(kHttp2MaxControlFrameSendSize + len_non_data_payload, + frame.size()); + + // Partially compare the PUSH_PROMISE frame against the template. + const unsigned char* frame_data = + reinterpret_cast<const unsigned char*>(frame.data()); + CompareCharArraysWithHexError(kDescription, frame_data, + SPDY_ARRAYSIZE(kPartialPushPromiseFrameData), + kPartialPushPromiseFrameData, + SPDY_ARRAYSIZE(kPartialPushPromiseFrameData)); + + // Compare the CONTINUATION frame against the template. + frame_data += kHttp2MaxControlFrameSendSize; + CompareCharArraysWithHexError( + kDescription, frame_data, SPDY_ARRAYSIZE(kContinuationFrameData), + kContinuationFrameData, SPDY_ARRAYSIZE(kContinuationFrameData)); + } +} + +TEST_P(SpdyFramerTest, CreateAltSvc) { + const char kDescription[] = "ALTSVC frame"; + const unsigned char kType = SerializeFrameType(SpdyFrameType::ALTSVC); + const unsigned char kFrameData[] = { + 0x00, 0x00, 0x49, kType, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x06, 'o', + 'r', 'i', 'g', 'i', 'n', 'p', 'i', 'd', '1', '=', '"', 'h', + 'o', 's', 't', ':', '4', '4', '3', '"', ';', ' ', 'm', 'a', + '=', '5', ',', 'p', '%', '2', '2', '%', '3', 'D', 'i', '%', + '3', 'A', 'd', '=', '"', 'h', '_', '\\', '\\', 'o', '\\', '"', + 's', 't', ':', '1', '2', '3', '"', ';', ' ', 'm', 'a', '=', + '4', '2', ';', ' ', 'v', '=', '"', '2', '4', '"'}; + SpdyAltSvcIR altsvc_ir(/* stream_id = */ 3); + altsvc_ir.set_origin("origin"); + altsvc_ir.add_altsvc(SpdyAltSvcWireFormat::AlternativeService( + "pid1", "host", 443, 5, SpdyAltSvcWireFormat::VersionVector())); + altsvc_ir.add_altsvc(SpdyAltSvcWireFormat::AlternativeService( + "p\"=i:d", "h_\\o\"st", 123, 42, + SpdyAltSvcWireFormat::VersionVector{24})); + SpdySerializedFrame frame(framer_.SerializeFrame(altsvc_ir)); + if (use_output_) { + EXPECT_EQ(framer_.SerializeFrame(altsvc_ir, &output_), frame.size()); + frame = SpdySerializedFrame(output_.Begin(), output_.Size(), false); + } + CompareFrame(kDescription, frame, kFrameData, SPDY_ARRAYSIZE(kFrameData)); +} + +TEST_P(SpdyFramerTest, CreatePriority) { + const char kDescription[] = "PRIORITY frame"; + const unsigned char kFrameData[] = { + 0x00, 0x00, 0x05, // Length: 5 + 0x02, // Type: PRIORITY + 0x00, // Flags: none + 0x00, 0x00, 0x00, 0x02, // Stream: 2 + 0x80, 0x00, 0x00, 0x01, // Parent: 1 (Exclusive) + 0x10, // Weight: 17 + }; + SpdyPriorityIR priority_ir(/* stream_id = */ 2, + /* parent_stream_id = */ 1, + /* weight = */ 17, + /* exclusive = */ true); + SpdySerializedFrame frame(framer_.SerializeFrame(priority_ir)); + if (use_output_) { + EXPECT_EQ(framer_.SerializeFrame(priority_ir, &output_), frame.size()); + frame = SpdySerializedFrame(output_.Begin(), output_.Size(), false); + } + CompareFrame(kDescription, frame, kFrameData, SPDY_ARRAYSIZE(kFrameData)); +} + +TEST_P(SpdyFramerTest, CreateUnknown) { + const char kDescription[] = "Unknown frame"; + const uint8_t kType = 0xaf; + const uint8_t kFlags = 0x11; + const uint8_t kLength = strlen(kDescription); + const unsigned char kFrameData[] = { + 0x00, 0x00, kLength, // Length: 13 + kType, // Type: undefined + kFlags, // Flags: arbitrary, undefined + 0x00, 0x00, 0x00, 0x02, // Stream: 2 + 0x55, 0x6e, 0x6b, 0x6e, // "Unkn" + 0x6f, 0x77, 0x6e, 0x20, // "own " + 0x66, 0x72, 0x61, 0x6d, // "fram" + 0x65, // "e" + }; + SpdyUnknownIR unknown_ir(/* stream_id = */ 2, + /* type = */ kType, + /* flags = */ kFlags, + /* payload = */ kDescription); + SpdySerializedFrame frame(framer_.SerializeFrame(unknown_ir)); + if (use_output_) { + EXPECT_EQ(framer_.SerializeFrame(unknown_ir, &output_), frame.size()); + frame = SpdySerializedFrame(output_.Begin(), output_.Size(), false); + } + CompareFrame(kDescription, frame, kFrameData, SPDY_ARRAYSIZE(kFrameData)); +} + +// Test serialization of a SpdyUnknownIR with a defined type, a length field +// that does not match the payload size and in fact exceeds framer limits, and a +// stream ID that effectively flips the reserved bit. +TEST_P(SpdyFramerTest, CreateUnknownUnchecked) { + const char kDescription[] = "Unknown frame"; + const uint8_t kType = 0x00; + const uint8_t kFlags = 0x11; + const uint8_t kLength = std::numeric_limits<uint8_t>::max(); + const unsigned int kStreamId = kStreamIdMask + 42; + const unsigned char kFrameData[] = { + 0x00, 0x00, kLength, // Length: 16426 + kType, // Type: DATA, defined + kFlags, // Flags: arbitrary, undefined + 0x80, 0x00, 0x00, 0x29, // Stream: 2147483689 + 0x55, 0x6e, 0x6b, 0x6e, // "Unkn" + 0x6f, 0x77, 0x6e, 0x20, // "own " + 0x66, 0x72, 0x61, 0x6d, // "fram" + 0x65, // "e" + }; + TestSpdyUnknownIR unknown_ir(/* stream_id = */ kStreamId, + /* type = */ kType, + /* flags = */ kFlags, + /* payload = */ kDescription); + unknown_ir.set_length(kLength); + SpdySerializedFrame frame(framer_.SerializeFrame(unknown_ir)); + if (use_output_) { + EXPECT_EQ(framer_.SerializeFrame(unknown_ir, &output_), frame.size()); + frame = SpdySerializedFrame(output_.Begin(), output_.Size(), false); + } + CompareFrame(kDescription, frame, kFrameData, SPDY_ARRAYSIZE(kFrameData)); +} + +TEST_P(SpdyFramerTest, ReadCompressedHeadersHeaderBlock) { + SpdyHeadersIR headers_ir(/* stream_id = */ 1); + headers_ir.SetHeader("alpha", "beta"); + headers_ir.SetHeader("gamma", "delta"); + SpdySerializedFrame control_frame(SpdyFramerPeer::SerializeHeaders( + &framer_, headers_ir, use_output_ ? &output_ : nullptr)); + TestSpdyVisitor visitor(SpdyFramer::ENABLE_COMPRESSION); + visitor.SimulateInFramer( + reinterpret_cast<unsigned char*>(control_frame.data()), + control_frame.size()); + EXPECT_EQ(1, visitor.headers_frame_count_); + EXPECT_EQ(0, visitor.control_frame_header_data_count_); + EXPECT_EQ(0, visitor.zero_length_control_frame_header_data_count_); + EXPECT_EQ(0, visitor.end_of_stream_count_); + EXPECT_EQ(headers_ir.header_block(), visitor.headers_); +} + +TEST_P(SpdyFramerTest, ReadCompressedHeadersHeaderBlockWithHalfClose) { + SpdyHeadersIR headers_ir(/* stream_id = */ 1); + headers_ir.set_fin(true); + headers_ir.SetHeader("alpha", "beta"); + headers_ir.SetHeader("gamma", "delta"); + SpdySerializedFrame control_frame(SpdyFramerPeer::SerializeHeaders( + &framer_, headers_ir, use_output_ ? &output_ : nullptr)); + TestSpdyVisitor visitor(SpdyFramer::ENABLE_COMPRESSION); + visitor.SimulateInFramer( + reinterpret_cast<unsigned char*>(control_frame.data()), + control_frame.size()); + EXPECT_EQ(1, visitor.headers_frame_count_); + EXPECT_EQ(0, visitor.control_frame_header_data_count_); + EXPECT_EQ(0, visitor.zero_length_control_frame_header_data_count_); + EXPECT_EQ(1, visitor.end_of_stream_count_); + EXPECT_EQ(headers_ir.header_block(), visitor.headers_); +} + +TEST_P(SpdyFramerTest, TooLargeHeadersFrameUsesContinuation) { + SpdyFramer framer(SpdyFramer::DISABLE_COMPRESSION); + SpdyHeadersIR headers(/* stream_id = */ 1); + headers.set_padding_len(256); + + // Exact payload length will change with HPACK, but this should be long + // enough to cause an overflow. + const size_t kBigValueSize = kHttp2MaxControlFrameSendSize; + SpdyString big_value(kBigValueSize, 'x'); + headers.SetHeader("aa", big_value); + SpdySerializedFrame control_frame(SpdyFramerPeer::SerializeHeaders( + &framer, headers, use_output_ ? &output_ : nullptr)); + EXPECT_GT(control_frame.size(), kHttp2MaxControlFrameSendSize); + + TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION); + visitor.SimulateInFramer( + reinterpret_cast<unsigned char*>(control_frame.data()), + control_frame.size()); + EXPECT_TRUE(visitor.header_buffer_valid_); + EXPECT_EQ(0, visitor.error_count_); + EXPECT_EQ(1, visitor.headers_frame_count_); + EXPECT_EQ(1, visitor.continuation_count_); + EXPECT_EQ(0, visitor.zero_length_control_frame_header_data_count_); +} + +TEST_P(SpdyFramerTest, MultipleContinuationFramesWithIterator) { + SpdyFramer framer(SpdyFramer::DISABLE_COMPRESSION); + auto headers = SpdyMakeUnique<SpdyHeadersIR>(/* stream_id = */ 1); + headers->set_padding_len(256); + + // Exact payload length will change with HPACK, but this should be long + // enough to cause an overflow. + const size_t kBigValueSize = kHttp2MaxControlFrameSendSize; + SpdyString big_valuex(kBigValueSize, 'x'); + headers->SetHeader("aa", big_valuex); + SpdyString big_valuez(kBigValueSize, 'z'); + headers->SetHeader("bb", big_valuez); + + SpdyFramer::SpdyHeaderFrameIterator frame_it(&framer, std::move(headers)); + + EXPECT_TRUE(frame_it.HasNextFrame()); + EXPECT_GT(frame_it.NextFrame(&output_), 0u); + SpdySerializedFrame headers_frame(output_.Begin(), output_.Size(), false); + EXPECT_EQ(headers_frame.size(), kHttp2MaxControlFrameSendSize); + + TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION); + visitor.SimulateInFramer( + reinterpret_cast<unsigned char*>(headers_frame.data()), + headers_frame.size()); + EXPECT_TRUE(visitor.header_buffer_valid_); + EXPECT_EQ(0, visitor.error_count_); + EXPECT_EQ(1, visitor.headers_frame_count_); + EXPECT_EQ(0, visitor.continuation_count_); + EXPECT_EQ(0, visitor.zero_length_control_frame_header_data_count_); + + output_.Reset(); + EXPECT_TRUE(frame_it.HasNextFrame()); + EXPECT_GT(frame_it.NextFrame(&output_), 0u); + SpdySerializedFrame first_cont_frame(output_.Begin(), output_.Size(), false); + EXPECT_EQ(first_cont_frame.size(), kHttp2MaxControlFrameSendSize); + + visitor.SimulateInFramer( + reinterpret_cast<unsigned char*>(first_cont_frame.data()), + first_cont_frame.size()); + EXPECT_TRUE(visitor.header_buffer_valid_); + EXPECT_EQ(0, visitor.error_count_); + EXPECT_EQ(1, visitor.headers_frame_count_); + EXPECT_EQ(1, visitor.continuation_count_); + EXPECT_EQ(0, visitor.zero_length_control_frame_header_data_count_); + + output_.Reset(); + EXPECT_TRUE(frame_it.HasNextFrame()); + EXPECT_GT(frame_it.NextFrame(&output_), 0u); + SpdySerializedFrame second_cont_frame(output_.Begin(), output_.Size(), false); + EXPECT_LT(second_cont_frame.size(), kHttp2MaxControlFrameSendSize); + + visitor.SimulateInFramer( + reinterpret_cast<unsigned char*>(second_cont_frame.data()), + second_cont_frame.size()); + EXPECT_TRUE(visitor.header_buffer_valid_); + EXPECT_EQ(0, visitor.error_count_); + EXPECT_EQ(1, visitor.headers_frame_count_); + EXPECT_EQ(2, visitor.continuation_count_); + EXPECT_EQ(0, visitor.zero_length_control_frame_header_data_count_); + + EXPECT_FALSE(frame_it.HasNextFrame()); +} + +TEST_P(SpdyFramerTest, PushPromiseFramesWithIterator) { + SpdyFramer framer(SpdyFramer::DISABLE_COMPRESSION); + auto push_promise = + SpdyMakeUnique<SpdyPushPromiseIR>(/* stream_id = */ 1, + /* promised_stream_id = */ 2); + push_promise->set_padding_len(256); + + // Exact payload length will change with HPACK, but this should be long + // enough to cause an overflow. + const size_t kBigValueSize = kHttp2MaxControlFrameSendSize; + SpdyString big_valuex(kBigValueSize, 'x'); + push_promise->SetHeader("aa", big_valuex); + SpdyString big_valuez(kBigValueSize, 'z'); + push_promise->SetHeader("bb", big_valuez); + + SpdyFramer::SpdyPushPromiseFrameIterator frame_it(&framer, + std::move(push_promise)); + + EXPECT_TRUE(frame_it.HasNextFrame()); + EXPECT_GT(frame_it.NextFrame(&output_), 0u); + SpdySerializedFrame push_promise_frame(output_.Begin(), output_.Size(), + false); + EXPECT_EQ(push_promise_frame.size(), kHttp2MaxControlFrameSendSize); + + TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION); + visitor.SimulateInFramer( + reinterpret_cast<unsigned char*>(push_promise_frame.data()), + push_promise_frame.size()); + EXPECT_TRUE(visitor.header_buffer_valid_); + EXPECT_EQ(0, visitor.error_count_); + EXPECT_EQ(1, visitor.push_promise_frame_count_); + EXPECT_EQ(0, visitor.continuation_count_); + EXPECT_EQ(0, visitor.zero_length_control_frame_header_data_count_); + + EXPECT_TRUE(frame_it.HasNextFrame()); + output_.Reset(); + EXPECT_GT(frame_it.NextFrame(&output_), 0u); + SpdySerializedFrame first_cont_frame(output_.Begin(), output_.Size(), false); + + EXPECT_EQ(first_cont_frame.size(), kHttp2MaxControlFrameSendSize); + visitor.SimulateInFramer( + reinterpret_cast<unsigned char*>(first_cont_frame.data()), + first_cont_frame.size()); + EXPECT_TRUE(visitor.header_buffer_valid_); + EXPECT_EQ(0, visitor.error_count_); + EXPECT_EQ(1, visitor.push_promise_frame_count_); + EXPECT_EQ(1, visitor.continuation_count_); + EXPECT_EQ(0, visitor.zero_length_control_frame_header_data_count_); + + EXPECT_TRUE(frame_it.HasNextFrame()); + output_.Reset(); + EXPECT_GT(frame_it.NextFrame(&output_), 0u); + SpdySerializedFrame second_cont_frame(output_.Begin(), output_.Size(), false); + EXPECT_LT(second_cont_frame.size(), kHttp2MaxControlFrameSendSize); + + visitor.SimulateInFramer( + reinterpret_cast<unsigned char*>(second_cont_frame.data()), + second_cont_frame.size()); + EXPECT_TRUE(visitor.header_buffer_valid_); + EXPECT_EQ(0, visitor.error_count_); + EXPECT_EQ(1, visitor.push_promise_frame_count_); + EXPECT_EQ(2, visitor.continuation_count_); + EXPECT_EQ(0, visitor.zero_length_control_frame_header_data_count_); + + EXPECT_FALSE(frame_it.HasNextFrame()); +} + +class SpdyControlFrameIteratorTest : public ::testing::Test { + public: + SpdyControlFrameIteratorTest() : output_(output_buffer, kSize) {} + + void RunTest(std::unique_ptr<SpdyFrameIR> ir) { + SpdyFramer framer(SpdyFramer::DISABLE_COMPRESSION); + SpdySerializedFrame frame(framer.SerializeFrame(*ir)); + std::unique_ptr<SpdyFrameSequence> it = + SpdyFramer::CreateIterator(&framer, std::move(ir)); + EXPECT_TRUE(it->HasNextFrame()); + EXPECT_EQ(it->NextFrame(&output_), frame.size()); + EXPECT_FALSE(it->HasNextFrame()); + } + + private: + ArrayOutputBuffer output_; +}; + +TEST_F(SpdyControlFrameIteratorTest, RstStreamFrameWithIterator) { + auto ir = SpdyMakeUnique<SpdyRstStreamIR>(0, ERROR_CODE_PROTOCOL_ERROR); + RunTest(std::move(ir)); +} + +TEST_F(SpdyControlFrameIteratorTest, SettingsFrameWithIterator) { + auto ir = SpdyMakeUnique<SpdySettingsIR>(); + uint32_t kValue = 0x0a0b0c0d; + SpdyKnownSettingsId kId = SETTINGS_INITIAL_WINDOW_SIZE; + ir->AddSetting(kId, kValue); + RunTest(std::move(ir)); +} + +TEST_F(SpdyControlFrameIteratorTest, PingFrameWithIterator) { + const SpdyPingId kPingId = 0x123456789abcdeffULL; + auto ir = SpdyMakeUnique<SpdyPingIR>(kPingId); + RunTest(std::move(ir)); +} + +TEST_F(SpdyControlFrameIteratorTest, GoAwayFrameWithIterator) { + auto ir = SpdyMakeUnique<SpdyGoAwayIR>(0, ERROR_CODE_NO_ERROR, "GA"); + RunTest(std::move(ir)); +} + +TEST_F(SpdyControlFrameIteratorTest, WindowUpdateFrameWithIterator) { + auto ir = SpdyMakeUnique<SpdyWindowUpdateIR>(1, 1); + RunTest(std::move(ir)); +} + +TEST_F(SpdyControlFrameIteratorTest, AtlSvcFrameWithIterator) { + auto ir = SpdyMakeUnique<SpdyAltSvcIR>(3); + ir->set_origin("origin"); + ir->add_altsvc(SpdyAltSvcWireFormat::AlternativeService( + "pid1", "host", 443, 5, SpdyAltSvcWireFormat::VersionVector())); + ir->add_altsvc(SpdyAltSvcWireFormat::AlternativeService( + "p\"=i:d", "h_\\o\"st", 123, 42, + SpdyAltSvcWireFormat::VersionVector{24})); + RunTest(std::move(ir)); +} + +TEST_F(SpdyControlFrameIteratorTest, PriorityFrameWithIterator) { + auto ir = SpdyMakeUnique<SpdyPriorityIR>(2, 1, 17, true); + RunTest(std::move(ir)); +} + +TEST_P(SpdyFramerTest, TooLargePushPromiseFrameUsesContinuation) { + SpdyFramer framer(SpdyFramer::DISABLE_COMPRESSION); + SpdyPushPromiseIR push_promise(/* stream_id = */ 1, + /* promised_stream_id = */ 2); + push_promise.set_padding_len(256); + + // Exact payload length will change with HPACK, but this should be long + // enough to cause an overflow. + const size_t kBigValueSize = kHttp2MaxControlFrameSendSize; + SpdyString big_value(kBigValueSize, 'x'); + push_promise.SetHeader("aa", big_value); + SpdySerializedFrame control_frame(SpdyFramerPeer::SerializePushPromise( + &framer, push_promise, use_output_ ? &output_ : nullptr)); + EXPECT_GT(control_frame.size(), kHttp2MaxControlFrameSendSize); + + TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION); + visitor.SimulateInFramer( + reinterpret_cast<unsigned char*>(control_frame.data()), + control_frame.size()); + EXPECT_TRUE(visitor.header_buffer_valid_); + EXPECT_EQ(0, visitor.error_count_); + EXPECT_EQ(1, visitor.push_promise_frame_count_); + EXPECT_EQ(1, visitor.continuation_count_); + EXPECT_EQ(0, visitor.zero_length_control_frame_header_data_count_); +} + +// Check that the framer stops delivering header data chunks once the visitor +// declares it doesn't want any more. This is important to guard against +// "zip bomb" types of attacks. +TEST_P(SpdyFramerTest, ControlFrameMuchTooLarge) { + const size_t kHeaderBufferChunks = 4; + const size_t kHeaderBufferSize = + kHttp2DefaultFramePayloadLimit / kHeaderBufferChunks; + const size_t kBigValueSize = kHeaderBufferSize * 2; + SpdyString big_value(kBigValueSize, 'x'); + SpdyHeadersIR headers(/* stream_id = */ 1); + headers.set_fin(true); + headers.SetHeader("aa", big_value); + SpdySerializedFrame control_frame(SpdyFramerPeer::SerializeHeaders( + &framer_, headers, use_output_ ? &output_ : nullptr)); + TestSpdyVisitor visitor(SpdyFramer::ENABLE_COMPRESSION); + visitor.set_header_buffer_size(kHeaderBufferSize); + visitor.SimulateInFramer( + reinterpret_cast<unsigned char*>(control_frame.data()), + control_frame.size()); + // It's up to the visitor to ignore extraneous header data; the framer + // won't throw an error. + EXPECT_GT(visitor.header_bytes_received_, visitor.header_buffer_size_); + EXPECT_EQ(1, visitor.end_of_stream_count_); +} + +TEST_P(SpdyFramerTest, ControlFrameSizesAreValidated) { + // Create a GoAway frame that has a few extra bytes at the end. + const size_t length = 20; + + // HTTP/2 GOAWAY frames are only bound by a minimal length, since they may + // carry opaque data. Verify that minimal length is tested. + ASSERT_GT(kGoawayFrameMinimumSize, kFrameHeaderSize); + const size_t less_than_min_length = + kGoawayFrameMinimumSize - kFrameHeaderSize - 1; + ASSERT_LE(less_than_min_length, std::numeric_limits<unsigned char>::max()); + const unsigned char kH2Len = static_cast<unsigned char>(less_than_min_length); + const unsigned char kH2FrameData[] = { + 0x00, 0x00, kH2Len, // Length: min length - 1 + 0x07, // Type: GOAWAY + 0x00, // Flags: none + 0x00, 0x00, 0x00, 0x00, // Stream: 0 + 0x00, 0x00, 0x00, 0x00, // Last: 0 + 0x00, 0x00, 0x00, // Truncated Status Field + }; + const size_t pad_length = length + kFrameHeaderSize - sizeof(kH2FrameData); + SpdyString pad(pad_length, 'A'); + TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION); + + visitor.SimulateInFramer(kH2FrameData, sizeof(kH2FrameData)); + visitor.SimulateInFramer(reinterpret_cast<const unsigned char*>(pad.c_str()), + pad.length()); + + EXPECT_EQ(1, visitor.error_count_); // This generated an error. + EXPECT_EQ(Http2DecoderAdapter::SPDY_INVALID_CONTROL_FRAME, + visitor.deframer_.spdy_framer_error()) + << Http2DecoderAdapter::SpdyFramerErrorToString( + visitor.deframer_.spdy_framer_error()); + EXPECT_EQ(0, visitor.goaway_count_); // Frame not parsed. +} + +TEST_P(SpdyFramerTest, ReadZeroLenSettingsFrame) { + SpdySettingsIR settings_ir; + SpdySerializedFrame control_frame(framer_.SerializeSettings(settings_ir)); + if (use_output_) { + ASSERT_TRUE(framer_.SerializeSettings(settings_ir, &output_)); + control_frame = SpdySerializedFrame(output_.Begin(), output_.Size(), false); + } + SetFrameLength(&control_frame, 0); + TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION); + visitor.SimulateInFramer( + reinterpret_cast<unsigned char*>(control_frame.data()), kFrameHeaderSize); + // Zero-len settings frames are permitted as of HTTP/2. + EXPECT_EQ(0, visitor.error_count_); +} + +// Tests handling of SETTINGS frames with invalid length. +TEST_P(SpdyFramerTest, ReadBogusLenSettingsFrame) { + SpdySettingsIR settings_ir; + + // Add settings to more than fill the frame so that we don't get a buffer + // overflow when calling SimulateInFramer() below. These settings must be + // distinct parameters because SpdySettingsIR has a map for settings, and + // will collapse multiple copies of the same parameter. + settings_ir.AddSetting(SETTINGS_INITIAL_WINDOW_SIZE, 0x00000002); + settings_ir.AddSetting(SETTINGS_MAX_CONCURRENT_STREAMS, 0x00000002); + SpdySerializedFrame control_frame(framer_.SerializeSettings(settings_ir)); + if (use_output_) { + ASSERT_TRUE(framer_.SerializeSettings(settings_ir, &output_)); + control_frame = SpdySerializedFrame(output_.Begin(), output_.Size(), false); + } + const size_t kNewLength = 8; + SetFrameLength(&control_frame, kNewLength); + TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION); + visitor.SimulateInFramer( + reinterpret_cast<unsigned char*>(control_frame.data()), + kFrameHeaderSize + kNewLength); + // Should generate an error, since its not possible to have a + // settings frame of length kNewLength. + EXPECT_EQ(1, visitor.error_count_); + EXPECT_EQ(Http2DecoderAdapter::SPDY_INVALID_CONTROL_FRAME_SIZE, + visitor.deframer_.spdy_framer_error()) + << Http2DecoderAdapter::SpdyFramerErrorToString( + visitor.deframer_.spdy_framer_error()); +} + +// Tests handling of larger SETTINGS frames. +TEST_P(SpdyFramerTest, ReadLargeSettingsFrame) { + SpdySettingsIR settings_ir; + settings_ir.AddSetting(SETTINGS_HEADER_TABLE_SIZE, 5); + settings_ir.AddSetting(SETTINGS_ENABLE_PUSH, 6); + settings_ir.AddSetting(SETTINGS_MAX_CONCURRENT_STREAMS, 7); + + SpdySerializedFrame control_frame(framer_.SerializeSettings(settings_ir)); + if (use_output_) { + ASSERT_TRUE(framer_.SerializeSettings(settings_ir, &output_)); + control_frame = SpdySerializedFrame(output_.Begin(), output_.Size(), false); + } + + TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION); + + // Read all at once. + visitor.SimulateInFramer( + reinterpret_cast<unsigned char*>(control_frame.data()), + control_frame.size()); + EXPECT_EQ(0, visitor.error_count_); + EXPECT_EQ(3, visitor.setting_count_); + EXPECT_EQ(1, visitor.settings_ack_sent_); + + // Read data in small chunks. + size_t framed_data = 0; + size_t unframed_data = control_frame.size(); + size_t kReadChunkSize = 5; // Read five bytes at a time. + while (unframed_data > 0) { + size_t to_read = std::min(kReadChunkSize, unframed_data); + visitor.SimulateInFramer( + reinterpret_cast<unsigned char*>(control_frame.data() + framed_data), + to_read); + unframed_data -= to_read; + framed_data += to_read; + } + EXPECT_EQ(0, visitor.error_count_); + EXPECT_EQ(3 * 2, visitor.setting_count_); + EXPECT_EQ(2, visitor.settings_ack_sent_); +} + +// Tests handling of SETTINGS frame with duplicate entries. +TEST_P(SpdyFramerTest, ReadDuplicateSettings) { + const unsigned char kH2FrameData[] = { + 0x00, 0x00, 0x12, // Length: 18 + 0x04, // Type: SETTINGS + 0x00, // Flags: none + 0x00, 0x00, 0x00, 0x00, // Stream: 0 + 0x00, 0x01, // Param: HEADER_TABLE_SIZE + 0x00, 0x00, 0x00, 0x02, // Value: 2 + 0x00, 0x01, // Param: HEADER_TABLE_SIZE + 0x00, 0x00, 0x00, 0x03, // Value: 3 + 0x00, 0x03, // Param: MAX_CONCURRENT_STREAMS + 0x00, 0x00, 0x00, 0x03, // Value: 3 + }; + + TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION); + visitor.SimulateInFramer(kH2FrameData, sizeof(kH2FrameData)); + + // In HTTP/2, duplicate settings are allowed; + // each setting replaces the previous value for that setting. + EXPECT_EQ(3, visitor.setting_count_); + EXPECT_EQ(0, visitor.error_count_); + EXPECT_EQ(1, visitor.settings_ack_sent_); +} + +// Tests handling of SETTINGS frame with a setting we don't recognize. +TEST_P(SpdyFramerTest, ReadUnknownSettingsId) { + const unsigned char kH2FrameData[] = { + 0x00, 0x00, 0x06, // Length: 6 + 0x04, // Type: SETTINGS + 0x00, // Flags: none + 0x00, 0x00, 0x00, 0x00, // Stream: 0 + 0x00, 0x10, // Param: 16 + 0x00, 0x00, 0x00, 0x02, // Value: 2 + }; + + TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION); + visitor.SimulateInFramer(kH2FrameData, sizeof(kH2FrameData)); + + // In HTTP/2, we ignore unknown settings because of extensions. However, we + // pass the SETTINGS to the visitor, which can decide how to handle them. + EXPECT_EQ(1, visitor.setting_count_); + EXPECT_EQ(0, visitor.error_count_); +} + +TEST_P(SpdyFramerTest, ReadKnownAndUnknownSettingsWithExtension) { + const unsigned char kH2FrameData[] = { + 0x00, 0x00, 0x12, // Length: 18 + 0x04, // Type: SETTINGS + 0x00, // Flags: none + 0x00, 0x00, 0x00, 0x00, // Stream: 0 + 0x00, 0x10, // Param: 16 + 0x00, 0x00, 0x00, 0x02, // Value: 2 + 0x00, 0x5f, // Param: 95 + 0x00, 0x01, 0x00, 0x02, // Value: 65538 + 0x00, 0x02, // Param: ENABLE_PUSH + 0x00, 0x00, 0x00, 0x01, // Value: 1 + }; + + TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION); + TestExtension extension; + visitor.set_extension_visitor(&extension); + visitor.SimulateInFramer(kH2FrameData, sizeof(kH2FrameData)); + + // In HTTP/2, we ignore unknown settings because of extensions. However, we + // pass the SETTINGS to the visitor, which can decide how to handle them. + EXPECT_EQ(3, visitor.setting_count_); + EXPECT_EQ(0, visitor.error_count_); + + // The extension receives all SETTINGS, including the non-standard SETTINGS. + EXPECT_THAT( + extension.settings_received_, + testing::ElementsAre(testing::Pair(16, 2), testing::Pair(95, 65538), + testing::Pair(2, 1))); +} + +// Tests handling of SETTINGS frame with entries out of order. +TEST_P(SpdyFramerTest, ReadOutOfOrderSettings) { + const unsigned char kH2FrameData[] = { + 0x00, 0x00, 0x12, // Length: 18 + 0x04, // Type: SETTINGS + 0x00, // Flags: none + 0x00, 0x00, 0x00, 0x00, // Stream: 0 + 0x00, 0x02, // Param: ENABLE_PUSH + 0x00, 0x00, 0x00, 0x02, // Value: 2 + 0x00, 0x01, // Param: HEADER_TABLE_SIZE + 0x00, 0x00, 0x00, 0x03, // Value: 3 + 0x00, 0x03, // Param: MAX_CONCURRENT_STREAMS + 0x00, 0x00, 0x00, 0x03, // Value: 3 + }; + + TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION); + visitor.SimulateInFramer(kH2FrameData, sizeof(kH2FrameData)); + + // In HTTP/2, settings are allowed in any order. + EXPECT_EQ(3, visitor.setting_count_); + EXPECT_EQ(0, visitor.error_count_); +} + +TEST_P(SpdyFramerTest, ProcessSettingsAckFrame) { + const unsigned char kFrameData[] = { + 0x00, 0x00, 0x00, // Length: 0 + 0x04, // Type: SETTINGS + 0x01, // Flags: ACK + 0x00, 0x00, 0x00, 0x00, // Stream: 0 + }; + + TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION); + visitor.SimulateInFramer(kFrameData, sizeof(kFrameData)); + + EXPECT_EQ(0, visitor.error_count_); + EXPECT_EQ(0, visitor.setting_count_); + EXPECT_EQ(1, visitor.settings_ack_received_); +} + +TEST_P(SpdyFramerTest, ProcessDataFrameWithPadding) { + const int kPaddingLen = 119; + const char data_payload[] = "hello"; + + testing::StrictMock<test::MockSpdyFramerVisitor> visitor; + deframer_.set_visitor(&visitor); + + SpdyDataIR data_ir(/* stream_id = */ 1, data_payload); + data_ir.set_padding_len(kPaddingLen); + SpdySerializedFrame frame(framer_.SerializeData(data_ir)); + + int bytes_consumed = 0; + + // Send the frame header. + EXPECT_CALL(visitor, + OnDataFrameHeader(1, kPaddingLen + strlen(data_payload), false)); + CHECK_EQ(kDataFrameMinimumSize, + deframer_.ProcessInput(frame.data(), kDataFrameMinimumSize)); + CHECK_EQ(deframer_.state(), + Http2DecoderAdapter::SPDY_READ_DATA_FRAME_PADDING_LENGTH); + CHECK_EQ(deframer_.spdy_framer_error(), Http2DecoderAdapter::SPDY_NO_ERROR); + bytes_consumed += kDataFrameMinimumSize; + + // Send the padding length field. + EXPECT_CALL(visitor, OnStreamPadLength(1, kPaddingLen - 1)); + CHECK_EQ(1u, deframer_.ProcessInput(frame.data() + bytes_consumed, 1)); + CHECK_EQ(deframer_.state(), Http2DecoderAdapter::SPDY_FORWARD_STREAM_FRAME); + CHECK_EQ(deframer_.spdy_framer_error(), Http2DecoderAdapter::SPDY_NO_ERROR); + bytes_consumed += 1; + + // Send the first two bytes of the data payload, i.e., "he". + EXPECT_CALL(visitor, OnStreamFrameData(1, _, 2)); + CHECK_EQ(2u, deframer_.ProcessInput(frame.data() + bytes_consumed, 2)); + CHECK_EQ(deframer_.state(), Http2DecoderAdapter::SPDY_FORWARD_STREAM_FRAME); + CHECK_EQ(deframer_.spdy_framer_error(), Http2DecoderAdapter::SPDY_NO_ERROR); + bytes_consumed += 2; + + // Send the rest three bytes of the data payload, i.e., "llo". + EXPECT_CALL(visitor, OnStreamFrameData(1, _, 3)); + CHECK_EQ(3u, deframer_.ProcessInput(frame.data() + bytes_consumed, 3)); + CHECK_EQ(deframer_.state(), Http2DecoderAdapter::SPDY_CONSUME_PADDING); + CHECK_EQ(deframer_.spdy_framer_error(), Http2DecoderAdapter::SPDY_NO_ERROR); + bytes_consumed += 3; + + // Send the first 100 bytes of the padding payload. + EXPECT_CALL(visitor, OnStreamPadding(1, 100)); + CHECK_EQ(100u, deframer_.ProcessInput(frame.data() + bytes_consumed, 100)); + CHECK_EQ(deframer_.state(), Http2DecoderAdapter::SPDY_CONSUME_PADDING); + CHECK_EQ(deframer_.spdy_framer_error(), Http2DecoderAdapter::SPDY_NO_ERROR); + bytes_consumed += 100; + + // Send rest of the padding payload. + EXPECT_CALL(visitor, OnStreamPadding(1, 18)); + CHECK_EQ(18u, deframer_.ProcessInput(frame.data() + bytes_consumed, 18)); + CHECK_EQ(deframer_.state(), Http2DecoderAdapter::SPDY_READY_FOR_FRAME); + CHECK_EQ(deframer_.spdy_framer_error(), Http2DecoderAdapter::SPDY_NO_ERROR); +} + +TEST_P(SpdyFramerTest, ReadWindowUpdate) { + SpdySerializedFrame control_frame(framer_.SerializeWindowUpdate( + SpdyWindowUpdateIR(/* stream_id = */ 1, /* delta = */ 2))); + if (use_output_) { + ASSERT_TRUE(framer_.SerializeWindowUpdate( + SpdyWindowUpdateIR(/* stream_id = */ 1, /* delta = */ 2), &output_)); + control_frame = SpdySerializedFrame(output_.Begin(), output_.Size(), false); + } + TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION); + visitor.SimulateInFramer( + reinterpret_cast<unsigned char*>(control_frame.data()), + control_frame.size()); + EXPECT_EQ(1u, visitor.last_window_update_stream_); + EXPECT_EQ(2, visitor.last_window_update_delta_); +} + +TEST_P(SpdyFramerTest, ReadCompressedPushPromise) { + SpdyPushPromiseIR push_promise(/* stream_id = */ 42, + /* promised_stream_id = */ 57); + push_promise.SetHeader("foo", "bar"); + push_promise.SetHeader("bar", "foofoo"); + SpdySerializedFrame frame(SpdyFramerPeer::SerializePushPromise( + &framer_, push_promise, use_output_ ? &output_ : nullptr)); + TestSpdyVisitor visitor(SpdyFramer::ENABLE_COMPRESSION); + visitor.SimulateInFramer(reinterpret_cast<unsigned char*>(frame.data()), + frame.size()); + EXPECT_EQ(42u, visitor.last_push_promise_stream_); + EXPECT_EQ(57u, visitor.last_push_promise_promised_stream_); + EXPECT_EQ(push_promise.header_block(), visitor.headers_); +} + +TEST_P(SpdyFramerTest, ReadHeadersWithContinuation) { + // frame-format off + const unsigned char kInput[] = { + 0x00, 0x00, 0x14, // Length: 20 + 0x01, // Type: HEADERS + 0x08, // Flags: PADDED + 0x00, 0x00, 0x00, 0x01, // Stream: 1 + 0x03, // PadLen: 3 trailing bytes + 0x00, // Unindexed Entry + 0x06, // Name Len: 6 + 'c', 'o', 'o', 'k', 'i', 'e', // Name + 0x07, // Value Len: 7 + 'f', 'o', 'o', '=', 'b', 'a', 'r', // Value + 0x00, 0x00, 0x00, // Padding + + 0x00, 0x00, 0x14, // Length: 20 + 0x09, // Type: CONTINUATION + 0x00, // Flags: none + 0x00, 0x00, 0x00, 0x01, // Stream: 1 + 0x00, // Unindexed Entry + 0x06, // Name Len: 6 + 'c', 'o', 'o', 'k', 'i', 'e', // Name + 0x08, // Value Len: 7 + 'b', 'a', 'z', '=', 'b', 'i', 'n', 'g', // Value + 0x00, // Unindexed Entry + 0x06, // Name Len: 6 + 'c', // Name (split) + + 0x00, 0x00, 0x12, // Length: 18 + 0x09, // Type: CONTINUATION + 0x04, // Flags: END_HEADERS + 0x00, 0x00, 0x00, 0x01, // Stream: 1 + 'o', 'o', 'k', 'i', 'e', // Name (continued) + 0x00, // Value Len: 0 + 0x00, // Unindexed Entry + 0x04, // Name Len: 4 + 'n', 'a', 'm', 'e', // Name + 0x05, // Value Len: 5 + 'v', 'a', 'l', 'u', 'e', // Value + }; + // frame-format on + + TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION); + visitor.SimulateInFramer(kInput, sizeof(kInput)); + + EXPECT_EQ(0, visitor.error_count_); + EXPECT_EQ(1, visitor.headers_frame_count_); + EXPECT_EQ(2, visitor.continuation_count_); + EXPECT_EQ(0, visitor.zero_length_control_frame_header_data_count_); + EXPECT_EQ(0, visitor.end_of_stream_count_); + + EXPECT_THAT( + visitor.headers_, + testing::ElementsAre(testing::Pair("cookie", "foo=bar; baz=bing; "), + testing::Pair("name", "value"))); +} + +TEST_P(SpdyFramerTest, ReadHeadersWithContinuationAndFin) { + // frame-format off + const unsigned char kInput[] = { + 0x00, 0x00, 0x10, // Length: 20 + 0x01, // Type: HEADERS + 0x01, // Flags: END_STREAM + 0x00, 0x00, 0x00, 0x01, // Stream: 1 + 0x00, // Unindexed Entry + 0x06, // Name Len: 6 + 'c', 'o', 'o', 'k', 'i', 'e', // Name + 0x07, // Value Len: 7 + 'f', 'o', 'o', '=', 'b', 'a', 'r', // Value + + 0x00, 0x00, 0x14, // Length: 20 + 0x09, // Type: CONTINUATION + 0x00, // Flags: none + 0x00, 0x00, 0x00, 0x01, // Stream: 1 + 0x00, // Unindexed Entry + 0x06, // Name Len: 6 + 'c', 'o', 'o', 'k', 'i', 'e', // Name + 0x08, // Value Len: 7 + 'b', 'a', 'z', '=', 'b', 'i', 'n', 'g', // Value + 0x00, // Unindexed Entry + 0x06, // Name Len: 6 + 'c', // Name (split) + + 0x00, 0x00, 0x12, // Length: 18 + 0x09, // Type: CONTINUATION + 0x04, // Flags: END_HEADERS + 0x00, 0x00, 0x00, 0x01, // Stream: 1 + 'o', 'o', 'k', 'i', 'e', // Name (continued) + 0x00, // Value Len: 0 + 0x00, // Unindexed Entry + 0x04, // Name Len: 4 + 'n', 'a', 'm', 'e', // Name + 0x05, // Value Len: 5 + 'v', 'a', 'l', 'u', 'e', // Value + }; + // frame-format on + + TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION); + visitor.SimulateInFramer(kInput, sizeof(kInput)); + + EXPECT_EQ(0, visitor.error_count_); + EXPECT_EQ(1, visitor.headers_frame_count_); + EXPECT_EQ(2, visitor.continuation_count_); + EXPECT_EQ(1, visitor.fin_flag_count_); + EXPECT_EQ(0, visitor.zero_length_control_frame_header_data_count_); + EXPECT_EQ(1, visitor.end_of_stream_count_); + + EXPECT_THAT( + visitor.headers_, + testing::ElementsAre(testing::Pair("cookie", "foo=bar; baz=bing; "), + testing::Pair("name", "value"))); +} + +TEST_P(SpdyFramerTest, ReadPushPromiseWithContinuation) { + // frame-format off + const unsigned char kInput[] = { + 0x00, 0x00, 0x17, // Length: 23 + 0x05, // Type: PUSH_PROMISE + 0x08, // Flags: PADDED + 0x00, 0x00, 0x00, 0x01, // Stream: 1 + 0x02, // PadLen: 2 trailing bytes + 0x00, 0x00, 0x00, 0x2a, // Promise: 42 + 0x00, // Unindexed Entry + 0x06, // Name Len: 6 + 'c', 'o', 'o', 'k', 'i', 'e', // Name + 0x07, // Value Len: 7 + 'f', 'o', 'o', '=', 'b', 'a', 'r', // Value + 0x00, 0x00, // Padding + + 0x00, 0x00, 0x14, // Length: 20 + 0x09, // Type: CONTINUATION + 0x00, // Flags: none + 0x00, 0x00, 0x00, 0x01, // Stream: 1 + 0x00, // Unindexed Entry + 0x06, // Name Len: 6 + 'c', 'o', 'o', 'k', 'i', 'e', // Name + 0x08, // Value Len: 7 + 'b', 'a', 'z', '=', 'b', 'i', 'n', 'g', // Value + 0x00, // Unindexed Entry + 0x06, // Name Len: 6 + 'c', // Name (split) + + 0x00, 0x00, 0x12, // Length: 18 + 0x09, // Type: CONTINUATION + 0x04, // Flags: END_HEADERS + 0x00, 0x00, 0x00, 0x01, // Stream: 1 + 'o', 'o', 'k', 'i', 'e', // Name (continued) + 0x00, // Value Len: 0 + 0x00, // Unindexed Entry + 0x04, // Name Len: 4 + 'n', 'a', 'm', 'e', // Name + 0x05, // Value Len: 5 + 'v', 'a', 'l', 'u', 'e', // Value + }; + // frame-format on + + TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION); + visitor.SimulateInFramer(kInput, sizeof(kInput)); + + EXPECT_EQ(0, visitor.error_count_); + EXPECT_EQ(1u, visitor.last_push_promise_stream_); + EXPECT_EQ(42u, visitor.last_push_promise_promised_stream_); + EXPECT_EQ(2, visitor.continuation_count_); + EXPECT_EQ(0, visitor.zero_length_control_frame_header_data_count_); + EXPECT_EQ(0, visitor.end_of_stream_count_); + + EXPECT_THAT( + visitor.headers_, + testing::ElementsAre(testing::Pair("cookie", "foo=bar; baz=bing; "), + testing::Pair("name", "value"))); +} + +// Receiving an unknown frame when a continuation is expected should +// result in a SPDY_UNEXPECTED_FRAME error +TEST_P(SpdyFramerTest, ReceiveUnknownMidContinuation) { + const unsigned char kInput[] = { + 0x00, 0x00, 0x10, // Length: 16 + 0x01, // Type: HEADERS + 0x00, // Flags: none + 0x00, 0x00, 0x00, 0x01, // Stream: 1 + 0x00, 0x06, 0x63, 0x6f, // HPACK + 0x6f, 0x6b, 0x69, 0x65, // + 0x07, 0x66, 0x6f, 0x6f, // + 0x3d, 0x62, 0x61, 0x72, // + + 0x00, 0x00, 0x14, // Length: 20 + 0xa9, // Type: UnknownFrameType(169) + 0x00, // Flags: none + 0x00, 0x00, 0x00, 0x01, // Stream: 1 + 0x00, 0x06, 0x63, 0x6f, // Payload + 0x6f, 0x6b, 0x69, 0x65, // + 0x08, 0x62, 0x61, 0x7a, // + 0x3d, 0x62, 0x69, 0x6e, // + 0x67, 0x00, 0x06, 0x63, // + }; + + TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION); + // Assume the unknown frame is allowed + visitor.on_unknown_frame_result_ = true; + deframer_.set_visitor(&visitor); + visitor.SimulateInFramer(kInput, sizeof(kInput)); + + EXPECT_EQ(1, visitor.error_count_); + EXPECT_EQ(Http2DecoderAdapter::SPDY_UNEXPECTED_FRAME, + visitor.deframer_.spdy_framer_error()) + << Http2DecoderAdapter::SpdyFramerErrorToString( + visitor.deframer_.spdy_framer_error()); + EXPECT_EQ(1, visitor.headers_frame_count_); + EXPECT_EQ(0, visitor.continuation_count_); + EXPECT_EQ(0u, visitor.header_buffer_length_); +} + +// Receiving an unknown frame when a continuation is expected should +// result in a SPDY_UNEXPECTED_FRAME error +TEST_P(SpdyFramerTest, ReceiveUnknownMidContinuationWithExtension) { + const unsigned char kInput[] = { + 0x00, 0x00, 0x10, // Length: 16 + 0x01, // Type: HEADERS + 0x00, // Flags: none + 0x00, 0x00, 0x00, 0x01, // Stream: 1 + 0x00, 0x06, 0x63, 0x6f, // HPACK + 0x6f, 0x6b, 0x69, 0x65, // + 0x07, 0x66, 0x6f, 0x6f, // + 0x3d, 0x62, 0x61, 0x72, // + + 0x00, 0x00, 0x14, // Length: 20 + 0xa9, // Type: UnknownFrameType(169) + 0x00, // Flags: none + 0x00, 0x00, 0x00, 0x01, // Stream: 1 + 0x00, 0x06, 0x63, 0x6f, // Payload + 0x6f, 0x6b, 0x69, 0x65, // + 0x08, 0x62, 0x61, 0x7a, // + 0x3d, 0x62, 0x69, 0x6e, // + 0x67, 0x00, 0x06, 0x63, // + }; + + TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION); + TestExtension extension; + visitor.set_extension_visitor(&extension); + deframer_.set_visitor(&visitor); + visitor.SimulateInFramer(kInput, sizeof(kInput)); + + EXPECT_EQ(1, visitor.error_count_); + EXPECT_EQ(Http2DecoderAdapter::SPDY_UNEXPECTED_FRAME, + visitor.deframer_.spdy_framer_error()) + << Http2DecoderAdapter::SpdyFramerErrorToString( + visitor.deframer_.spdy_framer_error()); + EXPECT_EQ(1, visitor.headers_frame_count_); + EXPECT_EQ(0, visitor.continuation_count_); + EXPECT_EQ(0u, visitor.header_buffer_length_); +} + +TEST_P(SpdyFramerTest, ReceiveContinuationOnWrongStream) { + const unsigned char kInput[] = { + 0x00, 0x00, 0x10, // Length: 16 + 0x01, // Type: HEADERS + 0x00, // Flags: none + 0x00, 0x00, 0x00, 0x01, // Stream: 1 + 0x00, 0x06, 0x63, 0x6f, // HPACK + 0x6f, 0x6b, 0x69, 0x65, // + 0x07, 0x66, 0x6f, 0x6f, // + 0x3d, 0x62, 0x61, 0x72, // + + 0x00, 0x00, 0x14, // Length: 20 + 0x09, // Type: CONTINUATION + 0x00, // Flags: none + 0x00, 0x00, 0x00, 0x02, // Stream: 2 + 0x00, 0x06, 0x63, 0x6f, // HPACK + 0x6f, 0x6b, 0x69, 0x65, // + 0x08, 0x62, 0x61, 0x7a, // + 0x3d, 0x62, 0x69, 0x6e, // + 0x67, 0x00, 0x06, 0x63, // + }; + + TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION); + deframer_.set_visitor(&visitor); + visitor.SimulateInFramer(kInput, sizeof(kInput)); + + EXPECT_EQ(1, visitor.error_count_); + EXPECT_EQ(Http2DecoderAdapter::SPDY_UNEXPECTED_FRAME, + visitor.deframer_.spdy_framer_error()) + << Http2DecoderAdapter::SpdyFramerErrorToString( + visitor.deframer_.spdy_framer_error()); + EXPECT_EQ(1, visitor.headers_frame_count_); + EXPECT_EQ(0, visitor.continuation_count_); + EXPECT_EQ(0u, visitor.header_buffer_length_); +} + +TEST_P(SpdyFramerTest, ReadContinuationOutOfOrder) { + const unsigned char kInput[] = { + 0x00, 0x00, 0x18, // Length: 24 + 0x09, // Type: CONTINUATION + 0x00, // Flags: none + 0x00, 0x00, 0x00, 0x01, // Stream: 1 + 0x00, 0x06, 0x63, 0x6f, // HPACK + 0x6f, 0x6b, 0x69, 0x65, // + 0x07, 0x66, 0x6f, 0x6f, // + 0x3d, 0x62, 0x61, 0x72, // + }; + + TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION); + deframer_.set_visitor(&visitor); + visitor.SimulateInFramer(kInput, sizeof(kInput)); + + EXPECT_EQ(1, visitor.error_count_); + EXPECT_EQ(Http2DecoderAdapter::SPDY_UNEXPECTED_FRAME, + visitor.deframer_.spdy_framer_error()) + << Http2DecoderAdapter::SpdyFramerErrorToString( + visitor.deframer_.spdy_framer_error()); + EXPECT_EQ(0, visitor.continuation_count_); + EXPECT_EQ(0u, visitor.header_buffer_length_); +} + +TEST_P(SpdyFramerTest, ExpectContinuationReceiveData) { + const unsigned char kInput[] = { + 0x00, 0x00, 0x10, // Length: 16 + 0x01, // Type: HEADERS + 0x00, // Flags: none + 0x00, 0x00, 0x00, 0x01, // Stream: 1 + 0x00, 0x06, 0x63, 0x6f, // HPACK + 0x6f, 0x6b, 0x69, 0x65, // + 0x07, 0x66, 0x6f, 0x6f, // + 0x3d, 0x62, 0x61, 0x72, // + + 0x00, 0x00, 0x00, // Length: 0 + 0x00, // Type: DATA + 0x01, // Flags: END_STREAM + 0x00, 0x00, 0x00, 0x04, // Stream: 4 + + 0xde, 0xad, 0xbe, 0xef, // Truncated Frame Header + }; + + TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION); + deframer_.set_visitor(&visitor); + visitor.SimulateInFramer(kInput, sizeof(kInput)); + + EXPECT_EQ(1, visitor.error_count_); + EXPECT_EQ(Http2DecoderAdapter::SPDY_UNEXPECTED_FRAME, + visitor.deframer_.spdy_framer_error()) + << Http2DecoderAdapter::SpdyFramerErrorToString( + visitor.deframer_.spdy_framer_error()); + EXPECT_EQ(1, visitor.headers_frame_count_); + EXPECT_EQ(0, visitor.continuation_count_); + EXPECT_EQ(0u, visitor.header_buffer_length_); + EXPECT_EQ(0, visitor.data_frame_count_); +} + +TEST_P(SpdyFramerTest, ExpectContinuationReceiveControlFrame) { + const unsigned char kInput[] = { + 0x00, 0x00, 0x10, // Length: 16 + 0x01, // Type: HEADERS + 0x00, // Flags: none + 0x00, 0x00, 0x00, 0x01, // Stream: 1 + 0x00, 0x06, 0x63, 0x6f, // HPACK + 0x6f, 0x6b, 0x69, 0x65, // + 0x07, 0x66, 0x6f, 0x6f, // + 0x3d, 0x62, 0x61, 0x72, // + + 0x00, 0x00, 0x10, // Length: 16 + 0x01, // Type: HEADERS + 0x00, // Flags: none + 0x00, 0x00, 0x00, 0x01, // Stream: 1 + 0x00, 0x06, 0x63, 0x6f, // HPACK + 0x6f, 0x6b, 0x69, 0x65, // + 0x07, 0x66, 0x6f, 0x6f, // + 0x3d, 0x62, 0x61, 0x72, // + }; + + TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION); + deframer_.set_visitor(&visitor); + visitor.SimulateInFramer(kInput, sizeof(kInput)); + + EXPECT_EQ(1, visitor.error_count_); + EXPECT_EQ(Http2DecoderAdapter::SPDY_UNEXPECTED_FRAME, + visitor.deframer_.spdy_framer_error()) + << Http2DecoderAdapter::SpdyFramerErrorToString( + visitor.deframer_.spdy_framer_error()); + EXPECT_EQ(1, visitor.headers_frame_count_); + EXPECT_EQ(0, visitor.continuation_count_); + EXPECT_EQ(0u, visitor.header_buffer_length_); + EXPECT_EQ(0, visitor.data_frame_count_); +} + +TEST_P(SpdyFramerTest, ReadGarbage) { + unsigned char garbage_frame[256]; + memset(garbage_frame, ~0, sizeof(garbage_frame)); + TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION); + visitor.SimulateInFramer(garbage_frame, sizeof(garbage_frame)); + EXPECT_EQ(1, visitor.error_count_); +} + +TEST_P(SpdyFramerTest, ReadUnknownExtensionFrame) { + // The unrecognized frame type should still have a valid length. + const unsigned char unknown_frame[] = { + 0x00, 0x00, 0x08, // Length: 8 + 0xff, // Type: UnknownFrameType(255) + 0xff, // Flags: 0xff + 0xff, 0xff, 0xff, 0xff, // Stream: 0x7fffffff (R-bit set) + 0xff, 0xff, 0xff, 0xff, // Payload + 0xff, 0xff, 0xff, 0xff, // + }; + TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION); + + // Simulate the case where the stream id validation checks out. + visitor.on_unknown_frame_result_ = true; + visitor.SimulateInFramer(unknown_frame, SPDY_ARRAYSIZE(unknown_frame)); + EXPECT_EQ(0, visitor.error_count_); + + // Follow it up with a valid control frame to make sure we handle + // subsequent frames correctly. + SpdySettingsIR settings_ir; + settings_ir.AddSetting(SETTINGS_HEADER_TABLE_SIZE, 10); + SpdySerializedFrame control_frame(framer_.SerializeSettings(settings_ir)); + if (use_output_) { + ASSERT_TRUE(framer_.SerializeSettings(settings_ir, &output_)); + control_frame = SpdySerializedFrame(output_.Begin(), output_.Size(), false); + } + visitor.SimulateInFramer( + reinterpret_cast<unsigned char*>(control_frame.data()), + control_frame.size()); + EXPECT_EQ(0, visitor.error_count_); + EXPECT_EQ(1, visitor.setting_count_); + EXPECT_EQ(1, visitor.settings_ack_sent_); +} + +TEST_P(SpdyFramerTest, ReadUnknownExtensionFrameWithExtension) { + // The unrecognized frame type should still have a valid length. + const unsigned char unknown_frame[] = { + 0x00, 0x00, 0x14, // Length: 20 + 0xff, // Type: UnknownFrameType(255) + 0xff, // Flags: 0xff + 0xff, 0xff, 0xff, 0xff, // Stream: 0x7fffffff (R-bit set) + 0xff, 0xff, 0xff, 0xff, // Payload + 0xff, 0xff, 0xff, 0xff, // + 0xff, 0xff, 0xff, 0xff, // + 0xff, 0xff, 0xff, 0xff, // + 0xff, 0xff, 0xff, 0xff, // + }; + TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION); + TestExtension extension; + visitor.set_extension_visitor(&extension); + visitor.SimulateInFramer(unknown_frame, SPDY_ARRAYSIZE(unknown_frame)); + EXPECT_EQ(0, visitor.error_count_); + EXPECT_EQ(0x7fffffffu, extension.stream_id_); + EXPECT_EQ(20u, extension.length_); + EXPECT_EQ(255, extension.type_); + EXPECT_EQ(0xff, extension.flags_); + EXPECT_EQ(SpdyString(20, '\xff'), extension.payload_); + + // Follow it up with a valid control frame to make sure we handle + // subsequent frames correctly. + SpdySettingsIR settings_ir; + settings_ir.AddSetting(SETTINGS_HEADER_TABLE_SIZE, 10); + SpdySerializedFrame control_frame(framer_.SerializeSettings(settings_ir)); + visitor.SimulateInFramer( + reinterpret_cast<unsigned char*>(control_frame.data()), + control_frame.size()); + EXPECT_EQ(0, visitor.error_count_); + EXPECT_EQ(1, visitor.setting_count_); + EXPECT_EQ(1, visitor.settings_ack_sent_); +} + +TEST_P(SpdyFramerTest, ReadGarbageWithValidLength) { + const unsigned char kFrameData[] = { + 0x00, 0x00, 0x08, // Length: 8 + 0xff, // Type: UnknownFrameType(255) + 0xff, // Flags: 0xff + 0xff, 0xff, 0xff, 0xff, // Stream: 0x7fffffff (R-bit set) + 0xff, 0xff, 0xff, 0xff, // Payload + 0xff, 0xff, 0xff, 0xff, // + }; + TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION); + visitor.SimulateInFramer(kFrameData, SPDY_ARRAYSIZE(kFrameData)); + EXPECT_EQ(1, visitor.error_count_); +} + +TEST_P(SpdyFramerTest, ReadGarbageHPACKEncoding) { + const unsigned char kInput[] = { + 0x00, 0x12, 0x01, // Length: 4609 + 0x04, // Type: SETTINGS + 0x00, // Flags: none + 0x00, 0x00, 0x01, 0xef, // Stream: 495 + 0xef, 0xff, // Param: 61439 + 0xff, 0xff, 0xff, 0xff, // Value: 4294967295 + 0xff, 0xff, // Param: 0xffff + 0xff, 0xff, 0xff, 0xff, // Value: 4294967295 + 0xff, 0xff, 0xff, 0xff, // Settings (Truncated) + 0xff, // + }; + + TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION); + visitor.SimulateInFramer(kInput, SPDY_ARRAYSIZE(kInput)); + EXPECT_EQ(1, visitor.error_count_); +} + +TEST_P(SpdyFramerTest, SizesTest) { + EXPECT_EQ(9u, kFrameHeaderSize); + EXPECT_EQ(9u, kDataFrameMinimumSize); + EXPECT_EQ(9u, kHeadersFrameMinimumSize); + EXPECT_EQ(14u, kPriorityFrameSize); + EXPECT_EQ(13u, kRstStreamFrameSize); + EXPECT_EQ(9u, kSettingsFrameMinimumSize); + EXPECT_EQ(13u, kPushPromiseFrameMinimumSize); + EXPECT_EQ(17u, kPingFrameSize); + EXPECT_EQ(17u, kGoawayFrameMinimumSize); + EXPECT_EQ(13u, kWindowUpdateFrameSize); + EXPECT_EQ(9u, kContinuationFrameMinimumSize); + EXPECT_EQ(11u, kGetAltSvcFrameMinimumSize); + EXPECT_EQ(9u, kFrameMinimumSize); + + EXPECT_EQ(16384u, kHttp2DefaultFramePayloadLimit); + EXPECT_EQ(16393u, kHttp2DefaultFrameSizeLimit); +} + +TEST_P(SpdyFramerTest, StateToStringTest) { + EXPECT_STREQ("ERROR", Http2DecoderAdapter::StateToString( + Http2DecoderAdapter::SPDY_ERROR)); + EXPECT_STREQ("FRAME_COMPLETE", Http2DecoderAdapter::StateToString( + Http2DecoderAdapter::SPDY_FRAME_COMPLETE)); + EXPECT_STREQ("READY_FOR_FRAME", + Http2DecoderAdapter::StateToString( + Http2DecoderAdapter::SPDY_READY_FOR_FRAME)); + EXPECT_STREQ("READING_COMMON_HEADER", + Http2DecoderAdapter::StateToString( + Http2DecoderAdapter::SPDY_READING_COMMON_HEADER)); + EXPECT_STREQ("CONTROL_FRAME_PAYLOAD", + Http2DecoderAdapter::StateToString( + Http2DecoderAdapter::SPDY_CONTROL_FRAME_PAYLOAD)); + EXPECT_STREQ("IGNORE_REMAINING_PAYLOAD", + Http2DecoderAdapter::StateToString( + Http2DecoderAdapter::SPDY_IGNORE_REMAINING_PAYLOAD)); + EXPECT_STREQ("FORWARD_STREAM_FRAME", + Http2DecoderAdapter::StateToString( + Http2DecoderAdapter::SPDY_FORWARD_STREAM_FRAME)); + EXPECT_STREQ( + "SPDY_CONTROL_FRAME_BEFORE_HEADER_BLOCK", + Http2DecoderAdapter::StateToString( + Http2DecoderAdapter::SPDY_CONTROL_FRAME_BEFORE_HEADER_BLOCK)); + EXPECT_STREQ("SPDY_CONTROL_FRAME_HEADER_BLOCK", + Http2DecoderAdapter::StateToString( + Http2DecoderAdapter::SPDY_CONTROL_FRAME_HEADER_BLOCK)); + EXPECT_STREQ("SPDY_SETTINGS_FRAME_PAYLOAD", + Http2DecoderAdapter::StateToString( + Http2DecoderAdapter::SPDY_SETTINGS_FRAME_PAYLOAD)); + EXPECT_STREQ("SPDY_ALTSVC_FRAME_PAYLOAD", + Http2DecoderAdapter::StateToString( + Http2DecoderAdapter::SPDY_ALTSVC_FRAME_PAYLOAD)); + EXPECT_STREQ("UNKNOWN_STATE", + Http2DecoderAdapter::StateToString( + Http2DecoderAdapter::SPDY_ALTSVC_FRAME_PAYLOAD + 1)); +} + +TEST_P(SpdyFramerTest, SpdyFramerErrorToStringTest) { + EXPECT_STREQ("NO_ERROR", Http2DecoderAdapter::SpdyFramerErrorToString( + Http2DecoderAdapter::SPDY_NO_ERROR)); + EXPECT_STREQ("INVALID_STREAM_ID", + Http2DecoderAdapter::SpdyFramerErrorToString( + Http2DecoderAdapter::SPDY_INVALID_STREAM_ID)); + EXPECT_STREQ("INVALID_CONTROL_FRAME", + Http2DecoderAdapter::SpdyFramerErrorToString( + Http2DecoderAdapter::SPDY_INVALID_CONTROL_FRAME)); + EXPECT_STREQ("CONTROL_PAYLOAD_TOO_LARGE", + Http2DecoderAdapter::SpdyFramerErrorToString( + Http2DecoderAdapter::SPDY_CONTROL_PAYLOAD_TOO_LARGE)); + EXPECT_STREQ("ZLIB_INIT_FAILURE", + Http2DecoderAdapter::SpdyFramerErrorToString( + Http2DecoderAdapter::SPDY_ZLIB_INIT_FAILURE)); + EXPECT_STREQ("UNSUPPORTED_VERSION", + Http2DecoderAdapter::SpdyFramerErrorToString( + Http2DecoderAdapter::SPDY_UNSUPPORTED_VERSION)); + EXPECT_STREQ("DECOMPRESS_FAILURE", + Http2DecoderAdapter::SpdyFramerErrorToString( + Http2DecoderAdapter::SPDY_DECOMPRESS_FAILURE)); + EXPECT_STREQ("COMPRESS_FAILURE", + Http2DecoderAdapter::SpdyFramerErrorToString( + Http2DecoderAdapter::SPDY_COMPRESS_FAILURE)); + EXPECT_STREQ("GOAWAY_FRAME_CORRUPT", + Http2DecoderAdapter::SpdyFramerErrorToString( + Http2DecoderAdapter::SPDY_GOAWAY_FRAME_CORRUPT)); + EXPECT_STREQ("RST_STREAM_FRAME_CORRUPT", + Http2DecoderAdapter::SpdyFramerErrorToString( + Http2DecoderAdapter::SPDY_RST_STREAM_FRAME_CORRUPT)); + EXPECT_STREQ("INVALID_PADDING", + Http2DecoderAdapter::SpdyFramerErrorToString( + Http2DecoderAdapter::SPDY_INVALID_PADDING)); + EXPECT_STREQ("INVALID_DATA_FRAME_FLAGS", + Http2DecoderAdapter::SpdyFramerErrorToString( + Http2DecoderAdapter::SPDY_INVALID_DATA_FRAME_FLAGS)); + EXPECT_STREQ("INVALID_CONTROL_FRAME_FLAGS", + Http2DecoderAdapter::SpdyFramerErrorToString( + Http2DecoderAdapter::SPDY_INVALID_CONTROL_FRAME_FLAGS)); + EXPECT_STREQ("UNEXPECTED_FRAME", + Http2DecoderAdapter::SpdyFramerErrorToString( + Http2DecoderAdapter::SPDY_UNEXPECTED_FRAME)); + EXPECT_STREQ("INTERNAL_FRAMER_ERROR", + Http2DecoderAdapter::SpdyFramerErrorToString( + Http2DecoderAdapter::SPDY_INTERNAL_FRAMER_ERROR)); + EXPECT_STREQ("INVALID_CONTROL_FRAME_SIZE", + Http2DecoderAdapter::SpdyFramerErrorToString( + Http2DecoderAdapter::SPDY_INVALID_CONTROL_FRAME_SIZE)); + EXPECT_STREQ("OVERSIZED_PAYLOAD", + Http2DecoderAdapter::SpdyFramerErrorToString( + Http2DecoderAdapter::SPDY_OVERSIZED_PAYLOAD)); + EXPECT_STREQ("UNKNOWN_ERROR", Http2DecoderAdapter::SpdyFramerErrorToString( + Http2DecoderAdapter::LAST_ERROR)); + EXPECT_STREQ("UNKNOWN_ERROR", + Http2DecoderAdapter::SpdyFramerErrorToString( + static_cast<Http2DecoderAdapter::SpdyFramerError>( + Http2DecoderAdapter::LAST_ERROR + 1))); +} + +TEST_P(SpdyFramerTest, DataFrameFlagsV4) { + uint8_t valid_data_flags = DATA_FLAG_FIN | DATA_FLAG_PADDED; + + uint8_t flags = 0; + do { + SCOPED_TRACE(testing::Message() + << "Flags " << std::hex << static_cast<int>(flags)); + + testing::StrictMock<test::MockSpdyFramerVisitor> visitor; + + deframer_.set_visitor(&visitor); + + SpdyDataIR data_ir(/* stream_id = */ 1, "hello"); + SpdySerializedFrame frame(framer_.SerializeData(data_ir)); + SetFrameFlags(&frame, flags); + + if (flags & ~valid_data_flags) { + EXPECT_CALL(visitor, OnError(_)); + } else { + EXPECT_CALL(visitor, OnDataFrameHeader(1, 5, flags & DATA_FLAG_FIN)); + if (flags & DATA_FLAG_PADDED) { + // The first byte of payload is parsed as padding length, but 'h' + // (0x68) is too large a padding length for a 5 byte payload. + EXPECT_CALL(visitor, OnStreamPadding(_, 1)); + // Expect Error since the frame ends prematurely. + EXPECT_CALL(visitor, OnError(_)); + } else { + EXPECT_CALL(visitor, OnStreamFrameData(_, _, 5)); + if (flags & DATA_FLAG_FIN) { + EXPECT_CALL(visitor, OnStreamEnd(_)); + } + } + } + + deframer_.ProcessInput(frame.data(), frame.size()); + if (flags & ~valid_data_flags) { + EXPECT_EQ(Http2DecoderAdapter::SPDY_ERROR, deframer_.state()); + EXPECT_EQ(Http2DecoderAdapter::SPDY_INVALID_DATA_FRAME_FLAGS, + deframer_.spdy_framer_error()) + << Http2DecoderAdapter::SpdyFramerErrorToString( + deframer_.spdy_framer_error()); + } else if (flags & DATA_FLAG_PADDED) { + EXPECT_EQ(Http2DecoderAdapter::SPDY_ERROR, deframer_.state()); + EXPECT_EQ(Http2DecoderAdapter::SPDY_INVALID_PADDING, + deframer_.spdy_framer_error()) + << Http2DecoderAdapter::SpdyFramerErrorToString( + deframer_.spdy_framer_error()); + } else { + EXPECT_EQ(Http2DecoderAdapter::SPDY_READY_FOR_FRAME, deframer_.state()); + EXPECT_EQ(Http2DecoderAdapter::SPDY_NO_ERROR, + deframer_.spdy_framer_error()) + << Http2DecoderAdapter::SpdyFramerErrorToString( + deframer_.spdy_framer_error()); + } + deframer_.Reset(); + } while (++flags != 0); +} + +TEST_P(SpdyFramerTest, RstStreamFrameFlags) { + uint8_t flags = 0; + do { + SCOPED_TRACE(testing::Message() + << "Flags " << std::hex << static_cast<int>(flags)); + + testing::StrictMock<test::MockSpdyFramerVisitor> visitor; + deframer_.set_visitor(&visitor); + + SpdyRstStreamIR rst_stream(/* stream_id = */ 13, ERROR_CODE_CANCEL); + SpdySerializedFrame frame(framer_.SerializeRstStream(rst_stream)); + if (use_output_) { + output_.Reset(); + ASSERT_TRUE(framer_.SerializeRstStream(rst_stream, &output_)); + frame = SpdySerializedFrame(output_.Begin(), output_.Size(), false); + } + SetFrameFlags(&frame, flags); + + EXPECT_CALL(visitor, OnRstStream(13, ERROR_CODE_CANCEL)); + + deframer_.ProcessInput(frame.data(), frame.size()); + EXPECT_EQ(Http2DecoderAdapter::SPDY_READY_FOR_FRAME, deframer_.state()); + EXPECT_EQ(Http2DecoderAdapter::SPDY_NO_ERROR, deframer_.spdy_framer_error()) + << Http2DecoderAdapter::SpdyFramerErrorToString( + deframer_.spdy_framer_error()); + deframer_.Reset(); + } while (++flags != 0); +} + +TEST_P(SpdyFramerTest, SettingsFrameFlags) { + uint8_t flags = 0; + do { + SCOPED_TRACE(testing::Message() + << "Flags " << std::hex << static_cast<int>(flags)); + + testing::StrictMock<test::MockSpdyFramerVisitor> visitor; + deframer_.set_visitor(&visitor); + + SpdySettingsIR settings_ir; + settings_ir.AddSetting(SETTINGS_INITIAL_WINDOW_SIZE, 16); + SpdySerializedFrame frame(framer_.SerializeSettings(settings_ir)); + if (use_output_) { + output_.Reset(); + ASSERT_TRUE(framer_.SerializeSettings(settings_ir, &output_)); + frame = SpdySerializedFrame(output_.Begin(), output_.Size(), false); + } + SetFrameFlags(&frame, flags); + + if (flags & SETTINGS_FLAG_ACK) { + EXPECT_CALL(visitor, OnError(_)); + } else { + EXPECT_CALL(visitor, OnSettings()); + EXPECT_CALL(visitor, OnSetting(SETTINGS_INITIAL_WINDOW_SIZE, 16)); + EXPECT_CALL(visitor, OnSettingsEnd()); + } + + deframer_.ProcessInput(frame.data(), frame.size()); + if (flags & SETTINGS_FLAG_ACK) { + // The frame is invalid because ACK frames should have no payload. + EXPECT_EQ(Http2DecoderAdapter::SPDY_ERROR, deframer_.state()); + EXPECT_EQ(Http2DecoderAdapter::SPDY_INVALID_CONTROL_FRAME_SIZE, + deframer_.spdy_framer_error()) + << Http2DecoderAdapter::SpdyFramerErrorToString( + deframer_.spdy_framer_error()); + } else { + EXPECT_EQ(Http2DecoderAdapter::SPDY_READY_FOR_FRAME, deframer_.state()); + EXPECT_EQ(Http2DecoderAdapter::SPDY_NO_ERROR, + deframer_.spdy_framer_error()) + << Http2DecoderAdapter::SpdyFramerErrorToString( + deframer_.spdy_framer_error()); + } + deframer_.Reset(); + } while (++flags != 0); +} + +TEST_P(SpdyFramerTest, GoawayFrameFlags) { + uint8_t flags = 0; + do { + SCOPED_TRACE(testing::Message() + << "Flags " << std::hex << static_cast<int>(flags)); + + testing::StrictMock<test::MockSpdyFramerVisitor> visitor; + + deframer_.set_visitor(&visitor); + + SpdyGoAwayIR goaway_ir(/* last_good_stream_id = */ 97, ERROR_CODE_NO_ERROR, + "test"); + SpdySerializedFrame frame(framer_.SerializeGoAway(goaway_ir)); + if (use_output_) { + output_.Reset(); + ASSERT_TRUE(framer_.SerializeGoAway(goaway_ir, &output_)); + frame = SpdySerializedFrame(output_.Begin(), output_.Size(), false); + } + SetFrameFlags(&frame, flags); + + EXPECT_CALL(visitor, OnGoAway(97, ERROR_CODE_NO_ERROR)); + + deframer_.ProcessInput(frame.data(), frame.size()); + EXPECT_EQ(Http2DecoderAdapter::SPDY_READY_FOR_FRAME, deframer_.state()); + EXPECT_EQ(Http2DecoderAdapter::SPDY_NO_ERROR, deframer_.spdy_framer_error()) + << Http2DecoderAdapter::SpdyFramerErrorToString( + deframer_.spdy_framer_error()); + deframer_.Reset(); + } while (++flags != 0); +} + +TEST_P(SpdyFramerTest, HeadersFrameFlags) { + uint8_t flags = 0; + do { + SCOPED_TRACE(testing::Message() + << "Flags " << std::hex << static_cast<int>(flags)); + + testing::StrictMock<test::MockSpdyFramerVisitor> visitor; + SpdyFramer framer(SpdyFramer::ENABLE_COMPRESSION); + Http2DecoderAdapter deframer; + deframer.set_visitor(&visitor); + + SpdyHeadersIR headers_ir(/* stream_id = */ 57); + if (flags & HEADERS_FLAG_PRIORITY) { + headers_ir.set_weight(3); + headers_ir.set_has_priority(true); + headers_ir.set_parent_stream_id(5); + headers_ir.set_exclusive(true); + } + headers_ir.SetHeader("foo", "bar"); + SpdySerializedFrame frame(SpdyFramerPeer::SerializeHeaders( + &framer, headers_ir, use_output_ ? &output_ : nullptr)); + uint8_t set_flags = flags & ~HEADERS_FLAG_PADDED; + SetFrameFlags(&frame, set_flags); + + // Expected callback values + SpdyStreamId stream_id = 57; + bool has_priority = false; + int weight = 0; + SpdyStreamId parent_stream_id = 0; + bool exclusive = false; + bool fin = flags & CONTROL_FLAG_FIN; + bool end = flags & HEADERS_FLAG_END_HEADERS; + if (flags & HEADERS_FLAG_PRIORITY) { + has_priority = true; + weight = 3; + parent_stream_id = 5; + exclusive = true; + } + EXPECT_CALL(visitor, OnHeaders(stream_id, has_priority, weight, + parent_stream_id, exclusive, fin, end)); + EXPECT_CALL(visitor, OnHeaderFrameStart(57)).Times(1); + if (end) { + EXPECT_CALL(visitor, OnHeaderFrameEnd(57)).Times(1); + } + if (flags & DATA_FLAG_FIN && end) { + EXPECT_CALL(visitor, OnStreamEnd(_)); + } else { + // Do not close the stream if we are expecting a CONTINUATION frame. + EXPECT_CALL(visitor, OnStreamEnd(_)).Times(0); + } + + deframer.ProcessInput(frame.data(), frame.size()); + EXPECT_EQ(Http2DecoderAdapter::SPDY_READY_FOR_FRAME, deframer.state()); + EXPECT_EQ(Http2DecoderAdapter::SPDY_NO_ERROR, deframer.spdy_framer_error()) + << Http2DecoderAdapter::SpdyFramerErrorToString( + deframer.spdy_framer_error()); + deframer.Reset(); + } while (++flags != 0); +} + +TEST_P(SpdyFramerTest, PingFrameFlags) { + uint8_t flags = 0; + do { + SCOPED_TRACE(testing::Message() + << "Flags " << std::hex << static_cast<int>(flags)); + + testing::StrictMock<test::MockSpdyFramerVisitor> visitor; + deframer_.set_visitor(&visitor); + + SpdySerializedFrame frame(framer_.SerializePing(SpdyPingIR(42))); + SetFrameFlags(&frame, flags); + + EXPECT_CALL(visitor, OnPing(42, flags & PING_FLAG_ACK)); + + deframer_.ProcessInput(frame.data(), frame.size()); + EXPECT_EQ(Http2DecoderAdapter::SPDY_READY_FOR_FRAME, deframer_.state()); + EXPECT_EQ(Http2DecoderAdapter::SPDY_NO_ERROR, deframer_.spdy_framer_error()) + << Http2DecoderAdapter::SpdyFramerErrorToString( + deframer_.spdy_framer_error()); + deframer_.Reset(); + } while (++flags != 0); +} + +TEST_P(SpdyFramerTest, WindowUpdateFrameFlags) { + uint8_t flags = 0; + do { + SCOPED_TRACE(testing::Message() + << "Flags " << std::hex << static_cast<int>(flags)); + + testing::StrictMock<test::MockSpdyFramerVisitor> visitor; + + deframer_.set_visitor(&visitor); + + SpdySerializedFrame frame(framer_.SerializeWindowUpdate( + SpdyWindowUpdateIR(/* stream_id = */ 4, /* delta = */ 1024))); + SetFrameFlags(&frame, flags); + + EXPECT_CALL(visitor, OnWindowUpdate(4, 1024)); + + deframer_.ProcessInput(frame.data(), frame.size()); + EXPECT_EQ(Http2DecoderAdapter::SPDY_READY_FOR_FRAME, deframer_.state()); + EXPECT_EQ(Http2DecoderAdapter::SPDY_NO_ERROR, deframer_.spdy_framer_error()) + << Http2DecoderAdapter::SpdyFramerErrorToString( + deframer_.spdy_framer_error()); + deframer_.Reset(); + } while (++flags != 0); +} + +TEST_P(SpdyFramerTest, PushPromiseFrameFlags) { + const SpdyStreamId client_id = 123; // Must be odd. + const SpdyStreamId promised_id = 22; // Must be even. + uint8_t flags = 0; + do { + SCOPED_TRACE(testing::Message() + << "Flags " << std::hex << static_cast<int>(flags)); + + testing::StrictMock<test::MockSpdyFramerVisitor> visitor; + testing::StrictMock<test::MockDebugVisitor> debug_visitor; + SpdyFramer framer(SpdyFramer::ENABLE_COMPRESSION); + Http2DecoderAdapter deframer; + deframer.set_visitor(&visitor); + deframer.set_debug_visitor(&debug_visitor); + framer.set_debug_visitor(&debug_visitor); + + EXPECT_CALL( + debug_visitor, + OnSendCompressedFrame(client_id, SpdyFrameType::PUSH_PROMISE, _, _)); + + SpdyPushPromiseIR push_promise(client_id, promised_id); + push_promise.SetHeader("foo", "bar"); + SpdySerializedFrame frame(SpdyFramerPeer::SerializePushPromise( + &framer, push_promise, use_output_ ? &output_ : nullptr)); + // TODO(jgraettinger): Add padding to SpdyPushPromiseIR, + // and implement framing. + SetFrameFlags(&frame, flags & ~HEADERS_FLAG_PADDED); + + bool end = flags & PUSH_PROMISE_FLAG_END_PUSH_PROMISE; + EXPECT_CALL(debug_visitor, OnReceiveCompressedFrame( + client_id, SpdyFrameType::PUSH_PROMISE, _)); + EXPECT_CALL(visitor, OnPushPromise(client_id, promised_id, end)); + EXPECT_CALL(visitor, OnHeaderFrameStart(client_id)).Times(1); + if (end) { + EXPECT_CALL(visitor, OnHeaderFrameEnd(client_id)).Times(1); + } + + deframer.ProcessInput(frame.data(), frame.size()); + EXPECT_EQ(Http2DecoderAdapter::SPDY_READY_FOR_FRAME, deframer.state()); + EXPECT_EQ(Http2DecoderAdapter::SPDY_NO_ERROR, deframer.spdy_framer_error()) + << Http2DecoderAdapter::SpdyFramerErrorToString( + deframer.spdy_framer_error()); + } while (++flags != 0); +} + +TEST_P(SpdyFramerTest, ContinuationFrameFlags) { + uint8_t flags = 0; + do { + if (use_output_) { + output_.Reset(); + } + SCOPED_TRACE(testing::Message() + << "Flags " << std::hex << static_cast<int>(flags)); + + testing::StrictMock<test::MockSpdyFramerVisitor> visitor; + testing::StrictMock<test::MockDebugVisitor> debug_visitor; + SpdyFramer framer(SpdyFramer::ENABLE_COMPRESSION); + Http2DecoderAdapter deframer; + deframer.set_visitor(&visitor); + deframer.set_debug_visitor(&debug_visitor); + framer.set_debug_visitor(&debug_visitor); + + EXPECT_CALL(debug_visitor, + OnSendCompressedFrame(42, SpdyFrameType::HEADERS, _, _)); + EXPECT_CALL(debug_visitor, + OnReceiveCompressedFrame(42, SpdyFrameType::HEADERS, _)); + EXPECT_CALL(visitor, OnHeaders(42, false, 0, 0, false, false, false)); + EXPECT_CALL(visitor, OnHeaderFrameStart(42)).Times(1); + + SpdyHeadersIR headers_ir(/* stream_id = */ 42); + headers_ir.SetHeader("foo", "bar"); + SpdySerializedFrame frame0; + if (use_output_) { + EXPECT_TRUE(framer.SerializeHeaders(headers_ir, &output_)); + frame0 = SpdySerializedFrame(output_.Begin(), output_.Size(), false); + } else { + frame0 = framer.SerializeHeaders(headers_ir); + } + SetFrameFlags(&frame0, 0); + + SpdyContinuationIR continuation(/* stream_id = */ 42); + SpdySerializedFrame frame1; + if (use_output_) { + char* begin = output_.Begin() + output_.Size(); + ASSERT_TRUE(framer.SerializeContinuation(continuation, &output_)); + frame1 = + SpdySerializedFrame(begin, output_.Size() - frame0.size(), false); + } else { + frame1 = framer.SerializeContinuation(continuation); + } + SetFrameFlags(&frame1, flags); + + EXPECT_CALL(debug_visitor, + OnReceiveCompressedFrame(42, SpdyFrameType::CONTINUATION, _)); + EXPECT_CALL(visitor, OnContinuation(42, flags & HEADERS_FLAG_END_HEADERS)); + bool end = flags & HEADERS_FLAG_END_HEADERS; + if (end) { + EXPECT_CALL(visitor, OnHeaderFrameEnd(42)).Times(1); + } + + deframer.ProcessInput(frame0.data(), frame0.size()); + deframer.ProcessInput(frame1.data(), frame1.size()); + EXPECT_EQ(Http2DecoderAdapter::SPDY_READY_FOR_FRAME, deframer.state()); + EXPECT_EQ(Http2DecoderAdapter::SPDY_NO_ERROR, deframer.spdy_framer_error()) + << Http2DecoderAdapter::SpdyFramerErrorToString( + deframer.spdy_framer_error()); + } while (++flags != 0); +} + +// TODO(mlavan): Add TEST_P(SpdyFramerTest, AltSvcFrameFlags) + +// Test handling of a RST_STREAM with out-of-bounds status codes. +TEST_P(SpdyFramerTest, RstStreamStatusBounds) { + const unsigned char kH2RstStreamInvalid[] = { + 0x00, 0x00, 0x04, // Length: 4 + 0x03, // Type: RST_STREAM + 0x00, // Flags: none + 0x00, 0x00, 0x00, 0x01, // Stream: 1 + 0x00, 0x00, 0x00, 0x00, // Error: NO_ERROR + }; + const unsigned char kH2RstStreamNumStatusCodes[] = { + 0x00, 0x00, 0x04, // Length: 4 + 0x03, // Type: RST_STREAM + 0x00, // Flags: none + 0x00, 0x00, 0x00, 0x01, // Stream: 1 + 0x00, 0x00, 0x00, 0xff, // Error: 255 + }; + + testing::StrictMock<test::MockSpdyFramerVisitor> visitor; + deframer_.set_visitor(&visitor); + + EXPECT_CALL(visitor, OnRstStream(1, ERROR_CODE_NO_ERROR)); + deframer_.ProcessInput(reinterpret_cast<const char*>(kH2RstStreamInvalid), + SPDY_ARRAYSIZE(kH2RstStreamInvalid)); + EXPECT_EQ(Http2DecoderAdapter::SPDY_READY_FOR_FRAME, deframer_.state()); + EXPECT_EQ(Http2DecoderAdapter::SPDY_NO_ERROR, deframer_.spdy_framer_error()) + << Http2DecoderAdapter::SpdyFramerErrorToString( + deframer_.spdy_framer_error()); + deframer_.Reset(); + + EXPECT_CALL(visitor, OnRstStream(1, ERROR_CODE_INTERNAL_ERROR)); + deframer_.ProcessInput( + reinterpret_cast<const char*>(kH2RstStreamNumStatusCodes), + SPDY_ARRAYSIZE(kH2RstStreamNumStatusCodes)); + EXPECT_EQ(Http2DecoderAdapter::SPDY_READY_FOR_FRAME, deframer_.state()); + EXPECT_EQ(Http2DecoderAdapter::SPDY_NO_ERROR, deframer_.spdy_framer_error()) + << Http2DecoderAdapter::SpdyFramerErrorToString( + deframer_.spdy_framer_error()); +} + +// Test handling of GOAWAY frames with out-of-bounds status code. +TEST_P(SpdyFramerTest, GoAwayStatusBounds) { + const unsigned char kH2FrameData[] = { + 0x00, 0x00, 0x0a, // Length: 10 + 0x07, // Type: GOAWAY + 0x00, // Flags: none + 0x00, 0x00, 0x00, 0x00, // Stream: 0 + 0x00, 0x00, 0x00, 0x01, // Last: 1 + 0xff, 0xff, 0xff, 0xff, // Error: 0xffffffff + 0x47, 0x41, // Description + }; + testing::StrictMock<test::MockSpdyFramerVisitor> visitor; + deframer_.set_visitor(&visitor); + + EXPECT_CALL(visitor, OnGoAway(1, ERROR_CODE_INTERNAL_ERROR)); + deframer_.ProcessInput(reinterpret_cast<const char*>(kH2FrameData), + SPDY_ARRAYSIZE(kH2FrameData)); + EXPECT_EQ(Http2DecoderAdapter::SPDY_READY_FOR_FRAME, deframer_.state()); + EXPECT_EQ(Http2DecoderAdapter::SPDY_NO_ERROR, deframer_.spdy_framer_error()) + << Http2DecoderAdapter::SpdyFramerErrorToString( + deframer_.spdy_framer_error()); +} + +// Tests handling of a GOAWAY frame with out-of-bounds stream ID. +TEST_P(SpdyFramerTest, GoAwayStreamIdBounds) { + const unsigned char kH2FrameData[] = { + 0x00, 0x00, 0x08, // Length: 8 + 0x07, // Type: GOAWAY + 0x00, // Flags: none + 0x00, 0x00, 0x00, 0x00, // Stream: 0 + 0xff, 0xff, 0xff, 0xff, // Last: 0x7fffffff (R-bit set) + 0x00, 0x00, 0x00, 0x00, // Error: NO_ERROR + }; + + testing::StrictMock<test::MockSpdyFramerVisitor> visitor; + + deframer_.set_visitor(&visitor); + + EXPECT_CALL(visitor, OnGoAway(0x7fffffff, ERROR_CODE_NO_ERROR)); + deframer_.ProcessInput(reinterpret_cast<const char*>(kH2FrameData), + SPDY_ARRAYSIZE(kH2FrameData)); + EXPECT_EQ(Http2DecoderAdapter::SPDY_READY_FOR_FRAME, deframer_.state()); + EXPECT_EQ(Http2DecoderAdapter::SPDY_NO_ERROR, deframer_.spdy_framer_error()) + << Http2DecoderAdapter::SpdyFramerErrorToString( + deframer_.spdy_framer_error()); +} + +TEST_P(SpdyFramerTest, OnAltSvcWithOrigin) { + const SpdyStreamId kStreamId = 0; // Stream id must be zero if origin given. + + testing::StrictMock<test::MockSpdyFramerVisitor> visitor; + + deframer_.set_visitor(&visitor); + + SpdyAltSvcWireFormat::AlternativeService altsvc1( + "pid1", "host", 443, 5, SpdyAltSvcWireFormat::VersionVector()); + SpdyAltSvcWireFormat::AlternativeService altsvc2( + "p\"=i:d", "h_\\o\"st", 123, 42, SpdyAltSvcWireFormat::VersionVector{24}); + SpdyAltSvcWireFormat::AlternativeServiceVector altsvc_vector; + altsvc_vector.push_back(altsvc1); + altsvc_vector.push_back(altsvc2); + EXPECT_CALL(visitor, + OnAltSvc(kStreamId, SpdyStringPiece("o_r|g!n"), altsvc_vector)); + + SpdyAltSvcIR altsvc_ir(kStreamId); + altsvc_ir.set_origin("o_r|g!n"); + altsvc_ir.add_altsvc(altsvc1); + altsvc_ir.add_altsvc(altsvc2); + SpdySerializedFrame frame(framer_.SerializeFrame(altsvc_ir)); + if (use_output_) { + output_.Reset(); + EXPECT_EQ(framer_.SerializeFrame(altsvc_ir, &output_), frame.size()); + frame = SpdySerializedFrame(output_.Begin(), output_.Size(), false); + } + deframer_.ProcessInput(frame.data(), frame.size()); + + EXPECT_EQ(Http2DecoderAdapter::SPDY_READY_FOR_FRAME, deframer_.state()); + EXPECT_EQ(Http2DecoderAdapter::SPDY_NO_ERROR, deframer_.spdy_framer_error()) + << Http2DecoderAdapter::SpdyFramerErrorToString( + deframer_.spdy_framer_error()); +} + +TEST_P(SpdyFramerTest, OnAltSvcNoOrigin) { + const SpdyStreamId kStreamId = 1; + + testing::StrictMock<test::MockSpdyFramerVisitor> visitor; + + deframer_.set_visitor(&visitor); + + SpdyAltSvcWireFormat::AlternativeService altsvc1( + "pid1", "host", 443, 5, SpdyAltSvcWireFormat::VersionVector()); + SpdyAltSvcWireFormat::AlternativeService altsvc2( + "p\"=i:d", "h_\\o\"st", 123, 42, SpdyAltSvcWireFormat::VersionVector{24}); + SpdyAltSvcWireFormat::AlternativeServiceVector altsvc_vector; + altsvc_vector.push_back(altsvc1); + altsvc_vector.push_back(altsvc2); + EXPECT_CALL(visitor, OnAltSvc(kStreamId, SpdyStringPiece(""), altsvc_vector)); + + SpdyAltSvcIR altsvc_ir(kStreamId); + altsvc_ir.add_altsvc(altsvc1); + altsvc_ir.add_altsvc(altsvc2); + SpdySerializedFrame frame(framer_.SerializeFrame(altsvc_ir)); + deframer_.ProcessInput(frame.data(), frame.size()); + + EXPECT_EQ(Http2DecoderAdapter::SPDY_READY_FOR_FRAME, deframer_.state()); + EXPECT_EQ(Http2DecoderAdapter::SPDY_NO_ERROR, deframer_.spdy_framer_error()) + << Http2DecoderAdapter::SpdyFramerErrorToString( + deframer_.spdy_framer_error()); +} + +TEST_P(SpdyFramerTest, OnAltSvcEmptyProtocolId) { + const SpdyStreamId kStreamId = 0; // Stream id must be zero if origin given. + + testing::StrictMock<test::MockSpdyFramerVisitor> visitor; + + deframer_.set_visitor(&visitor); + + EXPECT_CALL(visitor, + OnError(Http2DecoderAdapter::SPDY_INVALID_CONTROL_FRAME)); + + SpdyAltSvcIR altsvc_ir(kStreamId); + altsvc_ir.set_origin("o1"); + altsvc_ir.add_altsvc(SpdyAltSvcWireFormat::AlternativeService( + "pid1", "host", 443, 5, SpdyAltSvcWireFormat::VersionVector())); + altsvc_ir.add_altsvc(SpdyAltSvcWireFormat::AlternativeService( + "", "h1", 443, 10, SpdyAltSvcWireFormat::VersionVector())); + SpdySerializedFrame frame(framer_.SerializeFrame(altsvc_ir)); + if (use_output_) { + output_.Reset(); + EXPECT_EQ(framer_.SerializeFrame(altsvc_ir, &output_), frame.size()); + frame = SpdySerializedFrame(output_.Begin(), output_.Size(), false); + } + deframer_.ProcessInput(frame.data(), frame.size()); + + EXPECT_EQ(Http2DecoderAdapter::SPDY_ERROR, deframer_.state()); + EXPECT_EQ(Http2DecoderAdapter::SPDY_INVALID_CONTROL_FRAME, + deframer_.spdy_framer_error()) + << Http2DecoderAdapter::SpdyFramerErrorToString( + deframer_.spdy_framer_error()); +} + +TEST_P(SpdyFramerTest, OnAltSvcBadLengths) { + const unsigned char kType = SerializeFrameType(SpdyFrameType::ALTSVC); + const unsigned char kFrameDataOriginLenLargerThanFrame[] = { + 0x00, 0x00, 0x05, kType, 0x00, 0x00, 0x00, + 0x00, 0x03, 0x42, 0x42, 'f', 'o', 'o', + }; + + TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION); + + deframer_.set_visitor(&visitor); + visitor.SimulateInFramer(kFrameDataOriginLenLargerThanFrame, + sizeof(kFrameDataOriginLenLargerThanFrame)); + + EXPECT_EQ(1, visitor.error_count_); + EXPECT_EQ(Http2DecoderAdapter::SPDY_INVALID_CONTROL_FRAME, + visitor.deframer_.spdy_framer_error()); +} + +// Tests handling of ALTSVC frames delivered in small chunks. +TEST_P(SpdyFramerTest, ReadChunkedAltSvcFrame) { + SpdyAltSvcIR altsvc_ir(/* stream_id = */ 1); + SpdyAltSvcWireFormat::AlternativeService altsvc1( + "pid1", "host", 443, 5, SpdyAltSvcWireFormat::VersionVector()); + SpdyAltSvcWireFormat::AlternativeService altsvc2( + "p\"=i:d", "h_\\o\"st", 123, 42, SpdyAltSvcWireFormat::VersionVector{24}); + altsvc_ir.add_altsvc(altsvc1); + altsvc_ir.add_altsvc(altsvc2); + + SpdySerializedFrame control_frame(framer_.SerializeAltSvc(altsvc_ir)); + TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION); + + // Read data in small chunks. + size_t framed_data = 0; + size_t unframed_data = control_frame.size(); + size_t kReadChunkSize = 5; // Read five bytes at a time. + while (unframed_data > 0) { + size_t to_read = std::min(kReadChunkSize, unframed_data); + visitor.SimulateInFramer( + reinterpret_cast<unsigned char*>(control_frame.data() + framed_data), + to_read); + unframed_data -= to_read; + framed_data += to_read; + } + EXPECT_EQ(0, visitor.error_count_); + EXPECT_EQ(1, visitor.altsvc_count_); + ASSERT_NE(nullptr, visitor.test_altsvc_ir_); + ASSERT_EQ(2u, visitor.test_altsvc_ir_->altsvc_vector().size()); + EXPECT_TRUE(visitor.test_altsvc_ir_->altsvc_vector()[0] == altsvc1); + EXPECT_TRUE(visitor.test_altsvc_ir_->altsvc_vector()[1] == altsvc2); +} + +// While RFC7838 Section 4 says that an ALTSVC frame on stream 0 with empty +// origin MUST be ignored, it is not implemented at the framer level: instead, +// such frames are passed on to the consumer. +TEST_P(SpdyFramerTest, ReadAltSvcFrame) { + constexpr struct { + uint32_t stream_id; + const char* origin; + } test_cases[] = {{0, ""}, + {1, ""}, + {0, "https://www.example.com"}, + {1, "https://www.example.com"}}; + for (const auto& test_case : test_cases) { + SpdyAltSvcIR altsvc_ir(test_case.stream_id); + SpdyAltSvcWireFormat::AlternativeService altsvc( + "pid1", "host", 443, 5, SpdyAltSvcWireFormat::VersionVector()); + altsvc_ir.add_altsvc(altsvc); + altsvc_ir.set_origin(test_case.origin); + SpdySerializedFrame frame(framer_.SerializeAltSvc(altsvc_ir)); + + TestSpdyVisitor visitor(SpdyFramer::ENABLE_COMPRESSION); + deframer_.set_visitor(&visitor); + deframer_.ProcessInput(frame.data(), frame.size()); + + EXPECT_EQ(0, visitor.error_count_); + EXPECT_EQ(1, visitor.altsvc_count_); + EXPECT_EQ(Http2DecoderAdapter::SPDY_READY_FOR_FRAME, deframer_.state()); + EXPECT_EQ(Http2DecoderAdapter::SPDY_NO_ERROR, deframer_.spdy_framer_error()) + << Http2DecoderAdapter::SpdyFramerErrorToString( + deframer_.spdy_framer_error()); + } +} + +// An ALTSVC frame with invalid Alt-Svc-Field-Value results in an error. +TEST_P(SpdyFramerTest, ErrorOnAltSvcFrameWithInvalidValue) { + // Alt-Svc-Field-Value must be "clear" or must contain an "=" character + // per RFC7838 Section 3. + const char kFrameData[] = { + 0x00, 0x00, 0x16, // Length: 22 + 0x0a, // Type: ALTSVC + 0x00, // Flags: none + 0x00, 0x00, 0x00, 0x01, // Stream: 1 + 0x00, 0x00, // Origin-Len: 0 + 0x74, 0x68, 0x69, 0x73, // thisisnotavalidvalue + 0x69, 0x73, 0x6e, 0x6f, 0x74, 0x61, 0x76, 0x61, + 0x6c, 0x69, 0x64, 0x76, 0x61, 0x6c, 0x75, 0x65, + }; + + TestSpdyVisitor visitor(SpdyFramer::ENABLE_COMPRESSION); + deframer_.set_visitor(&visitor); + deframer_.ProcessInput(kFrameData, sizeof(kFrameData)); + + EXPECT_EQ(1, visitor.error_count_); + EXPECT_EQ(0, visitor.altsvc_count_); + EXPECT_EQ(Http2DecoderAdapter::SPDY_ERROR, deframer_.state()); + EXPECT_EQ(Http2DecoderAdapter::SPDY_INVALID_CONTROL_FRAME, + deframer_.spdy_framer_error()) + << Http2DecoderAdapter::SpdyFramerErrorToString( + deframer_.spdy_framer_error()); +} + +// Tests handling of PRIORITY frames. +TEST_P(SpdyFramerTest, ReadPriority) { + SpdyPriorityIR priority(/* stream_id = */ 3, + /* parent_stream_id = */ 1, + /* weight = */ 256, + /* exclusive = */ false); + SpdySerializedFrame frame(framer_.SerializePriority(priority)); + if (use_output_) { + output_.Reset(); + ASSERT_TRUE(framer_.SerializePriority(priority, &output_)); + frame = SpdySerializedFrame(output_.Begin(), output_.Size(), false); + } + testing::StrictMock<test::MockSpdyFramerVisitor> visitor; + deframer_.set_visitor(&visitor); + EXPECT_CALL(visitor, OnPriority(3, 1, 256, false)); + deframer_.ProcessInput(frame.data(), frame.size()); + + EXPECT_EQ(Http2DecoderAdapter::SPDY_READY_FOR_FRAME, deframer_.state()); + EXPECT_EQ(Http2DecoderAdapter::SPDY_NO_ERROR, deframer_.spdy_framer_error()) + << Http2DecoderAdapter::SpdyFramerErrorToString( + deframer_.spdy_framer_error()); +} + +// Tests handling of PRIORITY frame with incorrect size. +TEST_P(SpdyFramerTest, ReadIncorrectlySizedPriority) { + // PRIORITY frame of size 4, which isn't correct. + const unsigned char kFrameData[] = { + 0x00, 0x00, 0x04, // Length: 4 + 0x02, // Type: PRIORITY + 0x00, // Flags: none + 0x00, 0x00, 0x00, 0x03, // Stream: 3 + 0x00, 0x00, 0x00, 0x01, // Priority (Truncated) + }; + + TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION); + visitor.SimulateInFramer(kFrameData, sizeof(kFrameData)); + + EXPECT_EQ(Http2DecoderAdapter::SPDY_ERROR, visitor.deframer_.state()); + EXPECT_EQ(Http2DecoderAdapter::SPDY_INVALID_CONTROL_FRAME_SIZE, + visitor.deframer_.spdy_framer_error()) + << Http2DecoderAdapter::SpdyFramerErrorToString( + visitor.deframer_.spdy_framer_error()); +} + +// Tests handling of PING frame with incorrect size. +TEST_P(SpdyFramerTest, ReadIncorrectlySizedPing) { + // PING frame of size 4, which isn't correct. + const unsigned char kFrameData[] = { + 0x00, 0x00, 0x04, // Length: 4 + 0x06, // Type: PING + 0x00, // Flags: none + 0x00, 0x00, 0x00, 0x00, // Stream: 0 + 0x00, 0x00, 0x00, 0x01, // Ping (Truncated) + }; + + TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION); + visitor.SimulateInFramer(kFrameData, sizeof(kFrameData)); + + EXPECT_EQ(Http2DecoderAdapter::SPDY_ERROR, visitor.deframer_.state()); + EXPECT_EQ(Http2DecoderAdapter::SPDY_INVALID_CONTROL_FRAME_SIZE, + visitor.deframer_.spdy_framer_error()) + << Http2DecoderAdapter::SpdyFramerErrorToString( + visitor.deframer_.spdy_framer_error()); +} + +// Tests handling of WINDOW_UPDATE frame with incorrect size. +TEST_P(SpdyFramerTest, ReadIncorrectlySizedWindowUpdate) { + // WINDOW_UPDATE frame of size 3, which isn't correct. + const unsigned char kFrameData[] = { + 0x00, 0x00, 0x03, // Length: 3 + 0x08, // Type: WINDOW_UPDATE + 0x00, // Flags: none + 0x00, 0x00, 0x00, 0x03, // Stream: 3 + 0x00, 0x00, 0x01, // WindowUpdate (Truncated) + }; + + TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION); + visitor.SimulateInFramer(kFrameData, sizeof(kFrameData)); + + EXPECT_EQ(Http2DecoderAdapter::SPDY_ERROR, visitor.deframer_.state()); + EXPECT_EQ(Http2DecoderAdapter::SPDY_INVALID_CONTROL_FRAME_SIZE, + visitor.deframer_.spdy_framer_error()) + << Http2DecoderAdapter::SpdyFramerErrorToString( + visitor.deframer_.spdy_framer_error()); +} + +// Tests handling of RST_STREAM frame with incorrect size. +TEST_P(SpdyFramerTest, ReadIncorrectlySizedRstStream) { + // RST_STREAM frame of size 3, which isn't correct. + const unsigned char kFrameData[] = { + 0x00, 0x00, 0x03, // Length: 3 + 0x03, // Type: RST_STREAM + 0x00, // Flags: none + 0x00, 0x00, 0x00, 0x03, // Stream: 3 + 0x00, 0x00, 0x01, // RstStream (Truncated) + }; + + TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION); + visitor.SimulateInFramer(kFrameData, sizeof(kFrameData)); + + EXPECT_EQ(Http2DecoderAdapter::SPDY_ERROR, visitor.deframer_.state()); + EXPECT_EQ(Http2DecoderAdapter::SPDY_INVALID_CONTROL_FRAME_SIZE, + visitor.deframer_.spdy_framer_error()) + << Http2DecoderAdapter::SpdyFramerErrorToString( + visitor.deframer_.spdy_framer_error()); +} + +// Regression test for https://crbug.com/548674: +// RST_STREAM with payload must not be accepted. +TEST_P(SpdyFramerTest, ReadInvalidRstStreamWithPayload) { + const unsigned char kFrameData[] = { + 0x00, 0x00, 0x07, // Length: 7 + 0x03, // Type: RST_STREAM + 0x00, // Flags: none + 0x00, 0x00, 0x00, 0x01, // Stream: 1 + 0x00, 0x00, 0x00, 0x00, // Error: NO_ERROR + 'f', 'o', 'o' // Payload: "foo" + }; + + TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION); + visitor.SimulateInFramer(kFrameData, sizeof(kFrameData)); + + EXPECT_EQ(Http2DecoderAdapter::SPDY_ERROR, visitor.deframer_.state()); + EXPECT_EQ(Http2DecoderAdapter::SPDY_INVALID_CONTROL_FRAME_SIZE, + visitor.deframer_.spdy_framer_error()) + << Http2DecoderAdapter::SpdyFramerErrorToString( + visitor.deframer_.spdy_framer_error()); +} + +// Test that SpdyFramer processes, by default, all passed input in one call +// to ProcessInput (i.e. will not be calling set_process_single_input_frame()). +TEST_P(SpdyFramerTest, ProcessAllInput) { + auto visitor = + SpdyMakeUnique<TestSpdyVisitor>(SpdyFramer::DISABLE_COMPRESSION); + deframer_.set_visitor(visitor.get()); + + // Create two input frames. + SpdyHeadersIR headers(/* stream_id = */ 1); + headers.SetHeader("alpha", "beta"); + headers.SetHeader("gamma", "charlie"); + headers.SetHeader("cookie", "key1=value1; key2=value2"); + SpdySerializedFrame headers_frame(SpdyFramerPeer::SerializeHeaders( + &framer_, headers, use_output_ ? &output_ : nullptr)); + + const char four_score[] = "Four score and seven years ago"; + SpdyDataIR four_score_ir(/* stream_id = */ 1, four_score); + SpdySerializedFrame four_score_frame(framer_.SerializeData(four_score_ir)); + + // Put them in a single buffer (new variables here to make it easy to + // change the order and type of frames). + SpdySerializedFrame frame1 = std::move(headers_frame); + SpdySerializedFrame frame2 = std::move(four_score_frame); + + const size_t frame1_size = frame1.size(); + const size_t frame2_size = frame2.size(); + + VLOG(1) << "frame1_size = " << frame1_size; + VLOG(1) << "frame2_size = " << frame2_size; + + SpdyString input_buffer; + input_buffer.append(frame1.data(), frame1_size); + input_buffer.append(frame2.data(), frame2_size); + + const char* buf = input_buffer.data(); + const size_t buf_size = input_buffer.size(); + + VLOG(1) << "buf_size = " << buf_size; + + size_t processed = deframer_.ProcessInput(buf, buf_size); + EXPECT_EQ(buf_size, processed); + EXPECT_EQ(Http2DecoderAdapter::SPDY_READY_FOR_FRAME, deframer_.state()); + EXPECT_EQ(1, visitor->headers_frame_count_); + EXPECT_EQ(1, visitor->data_frame_count_); + EXPECT_EQ(strlen(four_score), static_cast<unsigned>(visitor->data_bytes_)); +} + +// Test that SpdyFramer stops after processing a full frame if +// process_single_input_frame is set. Input to ProcessInput has two frames, but +// only processes the first when we give it the first frame split at any point, +// or give it more than one frame in the input buffer. +TEST_P(SpdyFramerTest, ProcessAtMostOneFrame) { + deframer_.set_process_single_input_frame(true); + + // Create two input frames. + const char four_score[] = "Four score and ..."; + SpdyDataIR four_score_ir(/* stream_id = */ 1, four_score); + SpdySerializedFrame four_score_frame(framer_.SerializeData(four_score_ir)); + + SpdyHeadersIR headers(/* stream_id = */ 2); + headers.SetHeader("alpha", "beta"); + headers.SetHeader("gamma", "charlie"); + headers.SetHeader("cookie", "key1=value1; key2=value2"); + SpdySerializedFrame headers_frame(SpdyFramerPeer::SerializeHeaders( + &framer_, headers, use_output_ ? &output_ : nullptr)); + + // Put them in a single buffer (new variables here to make it easy to + // change the order and type of frames). + SpdySerializedFrame frame1 = std::move(four_score_frame); + SpdySerializedFrame frame2 = std::move(headers_frame); + + const size_t frame1_size = frame1.size(); + const size_t frame2_size = frame2.size(); + + VLOG(1) << "frame1_size = " << frame1_size; + VLOG(1) << "frame2_size = " << frame2_size; + + SpdyString input_buffer; + input_buffer.append(frame1.data(), frame1_size); + input_buffer.append(frame2.data(), frame2_size); + + const char* buf = input_buffer.data(); + const size_t buf_size = input_buffer.size(); + + VLOG(1) << "buf_size = " << buf_size; + + for (size_t first_size = 0; first_size <= buf_size; ++first_size) { + VLOG(1) << "first_size = " << first_size; + auto visitor = + SpdyMakeUnique<TestSpdyVisitor>(SpdyFramer::DISABLE_COMPRESSION); + deframer_.set_visitor(visitor.get()); + + EXPECT_EQ(Http2DecoderAdapter::SPDY_READY_FOR_FRAME, deframer_.state()); + + size_t processed_first = deframer_.ProcessInput(buf, first_size); + if (first_size < frame1_size) { + EXPECT_EQ(first_size, processed_first); + + if (first_size == 0) { + EXPECT_EQ(Http2DecoderAdapter::SPDY_READY_FOR_FRAME, deframer_.state()); + } else { + EXPECT_NE(Http2DecoderAdapter::SPDY_READY_FOR_FRAME, deframer_.state()); + } + + const char* rest = buf + processed_first; + const size_t remaining = buf_size - processed_first; + VLOG(1) << "remaining = " << remaining; + + size_t processed_second = deframer_.ProcessInput(rest, remaining); + + // Redundant tests just to make it easier to think about. + EXPECT_EQ(frame1_size - processed_first, processed_second); + size_t processed_total = processed_first + processed_second; + EXPECT_EQ(frame1_size, processed_total); + } else { + EXPECT_EQ(frame1_size, processed_first); + } + + EXPECT_EQ(Http2DecoderAdapter::SPDY_READY_FOR_FRAME, deframer_.state()); + + // At this point should have processed the entirety of the first frame, + // and none of the second frame. + + EXPECT_EQ(1, visitor->data_frame_count_); + EXPECT_EQ(strlen(four_score), static_cast<unsigned>(visitor->data_bytes_)); + EXPECT_EQ(0, visitor->headers_frame_count_); + } +} + +namespace { +void CheckFrameAndIRSize(SpdyFrameIR* ir, + SpdyFramer* framer, + ArrayOutputBuffer* output_buffer) { + output_buffer->Reset(); + SpdyFrameType type = ir->frame_type(); + size_t ir_size = ir->size(); + framer->SerializeFrame(*ir, output_buffer); + if (type == SpdyFrameType::HEADERS || type == SpdyFrameType::PUSH_PROMISE) { + // For HEADERS and PUSH_PROMISE, the size is an estimate. + EXPECT_GE(ir_size, output_buffer->Size() * 9 / 10); + EXPECT_LT(ir_size, output_buffer->Size() * 11 / 10); + } else { + EXPECT_EQ(ir_size, output_buffer->Size()); + } +} +} // namespace + +TEST_P(SpdyFramerTest, SpdyFrameIRSize) { + SpdyFramer framer(SpdyFramer::DISABLE_COMPRESSION); + + const char bytes[] = "this is a very short data frame"; + SpdyDataIR data_ir(1, SpdyStringPiece(bytes, SPDY_ARRAYSIZE(bytes))); + CheckFrameAndIRSize(&data_ir, &framer, &output_); + + SpdyRstStreamIR rst_ir(/* stream_id = */ 1, ERROR_CODE_PROTOCOL_ERROR); + CheckFrameAndIRSize(&rst_ir, &framer, &output_); + + SpdySettingsIR settings_ir; + settings_ir.AddSetting(SETTINGS_HEADER_TABLE_SIZE, 5); + settings_ir.AddSetting(SETTINGS_ENABLE_PUSH, 6); + settings_ir.AddSetting(SETTINGS_MAX_CONCURRENT_STREAMS, 7); + CheckFrameAndIRSize(&settings_ir, &framer, &output_); + + SpdyPingIR ping_ir(42); + CheckFrameAndIRSize(&ping_ir, &framer, &output_); + + SpdyGoAwayIR goaway_ir(97, ERROR_CODE_NO_ERROR, "Goaway description"); + CheckFrameAndIRSize(&goaway_ir, &framer, &output_); + + SpdyHeadersIR headers_ir(1); + headers_ir.SetHeader("alpha", "beta"); + headers_ir.SetHeader("gamma", "charlie"); + headers_ir.SetHeader("cookie", "key1=value1; key2=value2"); + CheckFrameAndIRSize(&headers_ir, &framer, &output_); + + SpdyHeadersIR headers_ir_with_continuation(1); + headers_ir_with_continuation.SetHeader("alpha", SpdyString(100000, 'x')); + headers_ir_with_continuation.SetHeader("beta", SpdyString(100000, 'x')); + headers_ir_with_continuation.SetHeader("cookie", "key1=value1; key2=value2"); + CheckFrameAndIRSize(&headers_ir_with_continuation, &framer, &output_); + + SpdyWindowUpdateIR window_update_ir(4, 1024); + CheckFrameAndIRSize(&window_update_ir, &framer, &output_); + + SpdyPushPromiseIR push_promise_ir(3, 8); + push_promise_ir.SetHeader("alpha", SpdyString(100000, 'x')); + push_promise_ir.SetHeader("beta", SpdyString(100000, 'x')); + push_promise_ir.SetHeader("cookie", "key1=value1; key2=value2"); + CheckFrameAndIRSize(&push_promise_ir, &framer, &output_); + + SpdyAltSvcWireFormat::AlternativeService altsvc1( + "pid1", "host", 443, 5, SpdyAltSvcWireFormat::VersionVector()); + SpdyAltSvcWireFormat::AlternativeService altsvc2( + "p\"=i:d", "h_\\o\"st", 123, 42, SpdyAltSvcWireFormat::VersionVector{24}); + SpdyAltSvcWireFormat::AlternativeServiceVector altsvc_vector; + altsvc_vector.push_back(altsvc1); + altsvc_vector.push_back(altsvc2); + SpdyAltSvcIR altsvc_ir(0); + altsvc_ir.set_origin("o_r|g!n"); + altsvc_ir.add_altsvc(altsvc1); + altsvc_ir.add_altsvc(altsvc2); + CheckFrameAndIRSize(&altsvc_ir, &framer, &output_); + + SpdyPriorityIR priority_ir(3, 1, 256, false); + CheckFrameAndIRSize(&priority_ir, &framer, &output_); + + const char kDescription[] = "Unknown frame"; + const uint8_t kType = 0xaf; + const uint8_t kFlags = 0x11; + SpdyUnknownIR unknown_ir(2, kType, kFlags, kDescription); + CheckFrameAndIRSize(&unknown_ir, &framer, &output_); +} + +} // namespace test + +} // namespace spdy diff --git a/chromium/net/third_party/quiche/src/spdy/core/spdy_header_block.cc b/chromium/net/third_party/quiche/src/spdy/core/spdy_header_block.cc new file mode 100644 index 00000000000..dcf84b95b7f --- /dev/null +++ b/chromium/net/third_party/quiche/src/spdy/core/spdy_header_block.cc @@ -0,0 +1,401 @@ +// Copyright (c) 2012 The Chromium 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/spdy/core/spdy_header_block.h" + +#include <string.h> + +#include <algorithm> +#include <utility> + +#include "base/logging.h" +#include "base/macros.h" +#include "net/third_party/quiche/src/spdy/platform/api/spdy_estimate_memory_usage.h" +#include "net/third_party/quiche/src/spdy/platform/api/spdy_ptr_util.h" +#include "net/third_party/quiche/src/spdy/platform/api/spdy_string_utils.h" +#include "net/third_party/quiche/src/spdy/platform/api/spdy_unsafe_arena.h" + +namespace spdy { +namespace { + +// By default, linked_hash_map's internal map allocates space for 100 map +// buckets on construction, which is larger than necessary. Standard library +// unordered map implementations use a list of prime numbers to set the bucket +// count for a particular capacity. |kInitialMapBuckets| is chosen to reduce +// memory usage for small header blocks, at the cost of having to rehash for +// large header blocks. +const size_t kInitialMapBuckets = 11; + +// SpdyHeaderBlock::Storage allocates blocks of this size by default. +const size_t kDefaultStorageBlockSize = 2048; + +const char kCookieKey[] = "cookie"; +const char kNullSeparator = 0; + +SpdyStringPiece SeparatorForKey(SpdyStringPiece key) { + if (key == kCookieKey) { + static SpdyStringPiece cookie_separator = "; "; + return cookie_separator; + } else { + return SpdyStringPiece(&kNullSeparator, 1); + } +} + +} // namespace + +// This class provides a backing store for SpdyStringPieces. It previously used +// custom allocation logic, but now uses an UnsafeArena instead. It has the +// property that SpdyStringPieces that refer to data in Storage are never +// invalidated until the Storage is deleted or Clear() is called. +// +// Write operations always append to the last block. If there is not enough +// space to perform the write, a new block is allocated, and any unused space +// is wasted. +class SpdyHeaderBlock::Storage { + public: + Storage() : arena_(kDefaultStorageBlockSize) {} + Storage(const Storage&) = delete; + Storage& operator=(const Storage&) = delete; + + SpdyStringPiece Write(const SpdyStringPiece s) { + return SpdyStringPiece(arena_.Memdup(s.data(), s.size()), s.size()); + } + + // If |s| points to the most recent allocation from arena_, the arena will + // reclaim the memory. Otherwise, this method is a no-op. + void Rewind(const SpdyStringPiece s) { + arena_.Free(const_cast<char*>(s.data()), s.size()); + } + + void Clear() { arena_.Reset(); } + + // Given a list of fragments and a separator, writes the fragments joined by + // the separator to a contiguous region of memory. Returns a SpdyStringPiece + // pointing to the region of memory. + SpdyStringPiece WriteFragments(const std::vector<SpdyStringPiece>& fragments, + SpdyStringPiece separator) { + if (fragments.empty()) { + return SpdyStringPiece(); + } + size_t total_size = separator.size() * (fragments.size() - 1); + for (const auto fragment : fragments) { + total_size += fragment.size(); + } + char* dst = arena_.Alloc(total_size); + size_t written = Join(dst, fragments, separator); + DCHECK_EQ(written, total_size); + return SpdyStringPiece(dst, total_size); + } + + size_t bytes_allocated() const { return arena_.status().bytes_allocated(); } + + // TODO(xunjieli): https://crbug.com/669108. Merge this with bytes_allocated() + size_t EstimateMemoryUsage() const { + return arena_.status().bytes_allocated(); + } + + private: + SpdyUnsafeArena arena_; +}; + +SpdyHeaderBlock::HeaderValue::HeaderValue(Storage* storage, + SpdyStringPiece key, + SpdyStringPiece initial_value) + : storage_(storage), + fragments_({initial_value}), + pair_({key, {}}), + size_(initial_value.size()), + separator_size_(SeparatorForKey(key).size()) {} + +SpdyHeaderBlock::HeaderValue::HeaderValue(HeaderValue&& other) + : storage_(other.storage_), + fragments_(std::move(other.fragments_)), + pair_(std::move(other.pair_)), + size_(other.size_), + separator_size_(other.separator_size_) {} + +SpdyHeaderBlock::HeaderValue& SpdyHeaderBlock::HeaderValue::operator=( + HeaderValue&& other) { + storage_ = other.storage_; + fragments_ = std::move(other.fragments_); + pair_ = std::move(other.pair_); + size_ = other.size_; + separator_size_ = other.separator_size_; + return *this; +} + +SpdyHeaderBlock::HeaderValue::~HeaderValue() = default; + +SpdyStringPiece SpdyHeaderBlock::HeaderValue::ConsolidatedValue() const { + if (fragments_.empty()) { + return SpdyStringPiece(); + } + if (fragments_.size() > 1) { + fragments_ = { + storage_->WriteFragments(fragments_, SeparatorForKey(pair_.first))}; + } + return fragments_[0]; +} + +void SpdyHeaderBlock::HeaderValue::Append(SpdyStringPiece fragment) { + size_ += (fragment.size() + separator_size_); + fragments_.push_back(fragment); +} + +const std::pair<SpdyStringPiece, SpdyStringPiece>& +SpdyHeaderBlock::HeaderValue::as_pair() const { + pair_.second = ConsolidatedValue(); + return pair_; +} + +SpdyHeaderBlock::iterator::iterator(MapType::const_iterator it) : it_(it) {} + +SpdyHeaderBlock::iterator::iterator(const iterator& other) = default; + +SpdyHeaderBlock::iterator::~iterator() = default; + +SpdyHeaderBlock::ValueProxy::ValueProxy( + SpdyHeaderBlock::MapType* block, + SpdyHeaderBlock::Storage* storage, + SpdyHeaderBlock::MapType::iterator lookup_result, + const SpdyStringPiece key, + size_t* spdy_header_block_value_size) + : block_(block), + storage_(storage), + lookup_result_(lookup_result), + key_(key), + spdy_header_block_value_size_(spdy_header_block_value_size), + valid_(true) {} + +SpdyHeaderBlock::ValueProxy::ValueProxy(ValueProxy&& other) + : block_(other.block_), + storage_(other.storage_), + lookup_result_(other.lookup_result_), + key_(other.key_), + spdy_header_block_value_size_(other.spdy_header_block_value_size_), + valid_(true) { + other.valid_ = false; +} + +SpdyHeaderBlock::ValueProxy& SpdyHeaderBlock::ValueProxy::operator=( + SpdyHeaderBlock::ValueProxy&& other) { + block_ = other.block_; + storage_ = other.storage_; + lookup_result_ = other.lookup_result_; + key_ = other.key_; + valid_ = true; + other.valid_ = false; + spdy_header_block_value_size_ = other.spdy_header_block_value_size_; + return *this; +} + +SpdyHeaderBlock::ValueProxy::~ValueProxy() { + // If the ValueProxy is destroyed while lookup_result_ == block_->end(), + // the assignment operator was never used, and the block's Storage can + // reclaim the memory used by the key. This makes lookup-only access to + // SpdyHeaderBlock through operator[] memory-neutral. + if (valid_ && lookup_result_ == block_->end()) { + storage_->Rewind(key_); + } +} + +SpdyHeaderBlock::ValueProxy& SpdyHeaderBlock::ValueProxy::operator=( + const SpdyStringPiece value) { + *spdy_header_block_value_size_ += value.size(); + if (lookup_result_ == block_->end()) { + DVLOG(1) << "Inserting: (" << key_ << ", " << value << ")"; + lookup_result_ = + block_ + ->emplace(std::make_pair( + key_, HeaderValue(storage_, key_, storage_->Write(value)))) + .first; + } else { + DVLOG(1) << "Updating key: " << key_ << " with value: " << value; + *spdy_header_block_value_size_ -= lookup_result_->second.SizeEstimate(); + lookup_result_->second = + HeaderValue(storage_, key_, storage_->Write(value)); + } + return *this; +} + +SpdyString SpdyHeaderBlock::ValueProxy::as_string() const { + if (lookup_result_ == block_->end()) { + return ""; + } else { + return SpdyString(lookup_result_->second.value()); + } +} + +SpdyHeaderBlock::SpdyHeaderBlock() : block_(kInitialMapBuckets) {} + +SpdyHeaderBlock::SpdyHeaderBlock(SpdyHeaderBlock&& other) + : block_(kInitialMapBuckets) { + block_.swap(other.block_); + storage_.swap(other.storage_); + key_size_ = other.key_size_; + value_size_ = other.value_size_; +} + +SpdyHeaderBlock::~SpdyHeaderBlock() = default; + +SpdyHeaderBlock& SpdyHeaderBlock::operator=(SpdyHeaderBlock&& other) { + block_.swap(other.block_); + storage_.swap(other.storage_); + key_size_ = other.key_size_; + value_size_ = other.value_size_; + return *this; +} + +SpdyHeaderBlock SpdyHeaderBlock::Clone() const { + SpdyHeaderBlock copy; + for (const auto& p : *this) { + copy.AppendHeader(p.first, p.second); + } + return copy; +} + +bool SpdyHeaderBlock::operator==(const SpdyHeaderBlock& other) const { + return size() == other.size() && std::equal(begin(), end(), other.begin()); +} + +bool SpdyHeaderBlock::operator!=(const SpdyHeaderBlock& other) const { + return !(operator==(other)); +} + +SpdyString SpdyHeaderBlock::DebugString() const { + if (empty()) { + return "{}"; + } + + SpdyString output = "\n{\n"; + for (auto it = begin(); it != end(); ++it) { + SpdyStrAppend(&output, " ", it->first, " ", it->second, "\n"); + } + SpdyStrAppend(&output, "}\n"); + return output; +} + +void SpdyHeaderBlock::erase(SpdyStringPiece key) { + auto iter = block_.find(key); + if (iter != block_.end()) { + DVLOG(1) << "Erasing header with name: " << key; + key_size_ -= key.size(); + value_size_ -= iter->second.SizeEstimate(); + block_.erase(iter); + } +} + +void SpdyHeaderBlock::clear() { + key_size_ = 0; + value_size_ = 0; + block_.clear(); + storage_.reset(); +} + +void SpdyHeaderBlock::insert(const SpdyHeaderBlock::value_type& value) { + // TODO(birenroy): Write new value in place of old value, if it fits. + value_size_ += value.second.size(); + + auto iter = block_.find(value.first); + if (iter == block_.end()) { + DVLOG(1) << "Inserting: (" << value.first << ", " << value.second << ")"; + AppendHeader(value.first, value.second); + } else { + DVLOG(1) << "Updating key: " << iter->first + << " with value: " << value.second; + value_size_ -= iter->second.SizeEstimate(); + auto* storage = GetStorage(); + iter->second = + HeaderValue(storage, iter->first, storage->Write(value.second)); + } +} + +SpdyHeaderBlock::ValueProxy SpdyHeaderBlock::operator[]( + const SpdyStringPiece key) { + DVLOG(2) << "Operator[] saw key: " << key; + SpdyStringPiece out_key; + auto iter = block_.find(key); + if (iter == block_.end()) { + // We write the key first, to assure that the ValueProxy has a + // reference to a valid SpdyStringPiece in its operator=. + out_key = WriteKey(key); + DVLOG(2) << "Key written as: " << std::hex + << static_cast<const void*>(key.data()) << ", " << std::dec + << key.size(); + } else { + out_key = iter->first; + } + return ValueProxy(&block_, GetStorage(), iter, out_key, &value_size_); +} + +void SpdyHeaderBlock::AppendValueOrAddHeader(const SpdyStringPiece key, + const SpdyStringPiece value) { + value_size_ += value.size(); + + auto iter = block_.find(key); + if (iter == block_.end()) { + DVLOG(1) << "Inserting: (" << key << ", " << value << ")"; + + AppendHeader(key, value); + return; + } + DVLOG(1) << "Updating key: " << iter->first << "; appending value: " << value; + value_size_ += SeparatorForKey(key).size(); + iter->second.Append(GetStorage()->Write(value)); +} + +size_t SpdyHeaderBlock::EstimateMemoryUsage() const { + // TODO(xunjieli): https://crbug.com/669108. Also include |block_| when EMU() + // supports linked_hash_map. + return SpdyEstimateMemoryUsage(storage_); +} + +void SpdyHeaderBlock::AppendHeader(const SpdyStringPiece key, + const SpdyStringPiece value) { + auto backed_key = WriteKey(key); + auto* storage = GetStorage(); + block_.emplace(std::make_pair( + backed_key, HeaderValue(storage, backed_key, storage->Write(value)))); +} + +SpdyHeaderBlock::Storage* SpdyHeaderBlock::GetStorage() { + if (storage_ == nullptr) { + storage_ = SpdyMakeUnique<Storage>(); + } + return storage_.get(); +} + +SpdyStringPiece SpdyHeaderBlock::WriteKey(const SpdyStringPiece key) { + key_size_ += key.size(); + return GetStorage()->Write(key); +} + +size_t SpdyHeaderBlock::bytes_allocated() const { + if (storage_ == nullptr) { + return 0; + } else { + return storage_->bytes_allocated(); + } +} + +size_t Join(char* dst, + const std::vector<SpdyStringPiece>& fragments, + SpdyStringPiece separator) { + if (fragments.empty()) { + return 0; + } + auto* original_dst = dst; + auto it = fragments.begin(); + memcpy(dst, it->data(), it->size()); + dst += it->size(); + for (++it; it != fragments.end(); ++it) { + memcpy(dst, separator.data(), separator.size()); + dst += separator.size(); + memcpy(dst, it->data(), it->size()); + dst += it->size(); + } + return dst - original_dst; +} + +} // namespace spdy diff --git a/chromium/net/third_party/quiche/src/spdy/core/spdy_header_block.h b/chromium/net/third_party/quiche/src/spdy/core/spdy_header_block.h new file mode 100644 index 00000000000..f453246486d --- /dev/null +++ b/chromium/net/third_party/quiche/src/spdy/core/spdy_header_block.h @@ -0,0 +1,254 @@ +// Copyright (c) 2012 The Chromium 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_SPDY_CORE_SPDY_HEADER_BLOCK_H_ +#define QUICHE_SPDY_CORE_SPDY_HEADER_BLOCK_H_ + +#include <stddef.h> + +#include <list> +#include <memory> +#include <utility> +#include <vector> + +#include "base/macros.h" +#include "net/third_party/quiche/src/spdy/platform/api/spdy_containers.h" +#include "net/third_party/quiche/src/spdy/platform/api/spdy_export.h" +#include "net/third_party/quiche/src/spdy/platform/api/spdy_macros.h" +#include "net/third_party/quiche/src/spdy/platform/api/spdy_string.h" +#include "net/third_party/quiche/src/spdy/platform/api/spdy_string_piece.h" + +namespace spdy { + +namespace test { +class SpdyHeaderBlockPeer; +class ValueProxyPeer; +} // namespace test + +// This class provides a key-value map that can be used to store SPDY header +// names and values. This data structure preserves insertion order. +// +// Under the hood, this data structure uses large, contiguous blocks of memory +// to store names and values. Lookups may be performed with SpdyStringPiece +// keys, and values are returned as SpdyStringPieces (via ValueProxy, below). +// Value SpdyStringPieces are valid as long as the SpdyHeaderBlock exists; +// allocated memory is never freed until SpdyHeaderBlock's destruction. +// +// This implementation does not make much of an effort to minimize wasted space. +// It's expected that keys are rarely deleted from a SpdyHeaderBlock. +class SPDY_EXPORT_PRIVATE SpdyHeaderBlock { + private: + class Storage; + + // Stores a list of value fragments that can be joined later with a + // key-dependent separator. + class SPDY_EXPORT_PRIVATE HeaderValue { + public: + HeaderValue(Storage* storage, + SpdyStringPiece key, + SpdyStringPiece initial_value); + + // Moves are allowed. + HeaderValue(HeaderValue&& other); + HeaderValue& operator=(HeaderValue&& other); + + // Copies are not. + HeaderValue(const HeaderValue& other) = delete; + HeaderValue& operator=(const HeaderValue& other) = delete; + + ~HeaderValue(); + + // Consumes at most |fragment.size()| bytes of memory. + void Append(SpdyStringPiece fragment); + + SpdyStringPiece value() const { return as_pair().second; } + const std::pair<SpdyStringPiece, SpdyStringPiece>& as_pair() const; + + // Size estimate including separators. Used when keys are erased from + // SpdyHeaderBlock. + size_t SizeEstimate() const { return size_; } + + private: + // May allocate a large contiguous region of memory to hold the concatenated + // fragments and separators. + SpdyStringPiece ConsolidatedValue() const; + + mutable Storage* storage_; + mutable std::vector<SpdyStringPiece> fragments_; + // The first element is the key; the second is the consolidated value. + mutable std::pair<SpdyStringPiece, SpdyStringPiece> pair_; + size_t size_ = 0; + size_t separator_size_ = 0; + }; + + typedef SpdyLinkedHashMap<SpdyStringPiece, HeaderValue, SpdyStringPieceHash> + MapType; + + public: + typedef std::pair<SpdyStringPiece, SpdyStringPiece> value_type; + + // Provides iteration over a sequence of std::pair<SpdyStringPiece, + // SpdyStringPiece>, even though the underlying MapType::value_type is + // different. Dereferencing the iterator will result in memory allocation for + // multi-value headers. + class SPDY_EXPORT_PRIVATE iterator { + public: + // The following type definitions fulfill the requirements for iterator + // implementations. + typedef std::pair<SpdyStringPiece, SpdyStringPiece> value_type; + typedef value_type& reference; + typedef value_type* pointer; + typedef std::forward_iterator_tag iterator_category; + typedef MapType::iterator::difference_type difference_type; + + // In practice, this iterator only offers access to const value_type. + typedef const value_type& const_reference; + typedef const value_type* const_pointer; + + explicit iterator(MapType::const_iterator it); + iterator(const iterator& other); + ~iterator(); + + // This will result in memory allocation if the value consists of multiple + // fragments. + const_reference operator*() const { return it_->second.as_pair(); } + + const_pointer operator->() const { return &(this->operator*()); } + bool operator==(const iterator& it) const { return it_ == it.it_; } + bool operator!=(const iterator& it) const { return !(*this == it); } + + iterator& operator++() { + it_++; + return *this; + } + + iterator operator++(int) { + auto ret = *this; + this->operator++(); + return ret; + } + + private: + MapType::const_iterator it_; + }; + typedef iterator const_iterator; + + class ValueProxy; + + SpdyHeaderBlock(); + SpdyHeaderBlock(const SpdyHeaderBlock& other) = delete; + SpdyHeaderBlock(SpdyHeaderBlock&& other); + ~SpdyHeaderBlock(); + + SpdyHeaderBlock& operator=(const SpdyHeaderBlock& other) = delete; + SpdyHeaderBlock& operator=(SpdyHeaderBlock&& other); + SpdyHeaderBlock Clone() const; + + bool operator==(const SpdyHeaderBlock& other) const; + bool operator!=(const SpdyHeaderBlock& other) const; + + // Provides a human readable multi-line representation of the stored header + // keys and values. + SpdyString DebugString() const; + + iterator begin() { return iterator(block_.begin()); } + iterator end() { return iterator(block_.end()); } + const_iterator begin() const { return const_iterator(block_.begin()); } + const_iterator end() const { return const_iterator(block_.end()); } + bool empty() const { return block_.empty(); } + size_t size() const { return block_.size(); } + iterator find(SpdyStringPiece key) { return iterator(block_.find(key)); } + const_iterator find(SpdyStringPiece key) const { + return const_iterator(block_.find(key)); + } + void erase(SpdyStringPiece key); + + // Clears both our MapType member and the memory used to hold headers. + void clear(); + + // The next few methods copy data into our backing storage. + + // If key already exists in the block, replaces the value of that key. Else + // adds a new header to the end of the block. + void insert(const value_type& value); + + // If a header with the key is already present, then append the value to the + // existing header value, NUL ("\0") separated unless the key is cookie, in + // which case the separator is "; ". + // If there is no such key, a new header with the key and value is added. + void AppendValueOrAddHeader(const SpdyStringPiece key, + const SpdyStringPiece value); + + // Allows either lookup or mutation of the value associated with a key. + ValueProxy operator[](const SpdyStringPiece key) SPDY_MUST_USE_RESULT; + + // This object provides automatic conversions that allow SpdyHeaderBlock to be + // nearly a drop-in replacement for SpdyLinkedHashMap<SpdyString, SpdyString>. + // It reads data from or writes data to a SpdyHeaderBlock::Storage. + class SPDY_EXPORT_PRIVATE ValueProxy { + public: + ~ValueProxy(); + + // Moves are allowed. + ValueProxy(ValueProxy&& other); + ValueProxy& operator=(ValueProxy&& other); + + // Copies are not. + ValueProxy(const ValueProxy& other) = delete; + ValueProxy& operator=(const ValueProxy& other) = delete; + + // Assignment modifies the underlying SpdyHeaderBlock. + ValueProxy& operator=(const SpdyStringPiece other); + + SpdyString as_string() const; + + private: + friend class SpdyHeaderBlock; + friend class test::ValueProxyPeer; + + ValueProxy(SpdyHeaderBlock::MapType* block, + SpdyHeaderBlock::Storage* storage, + SpdyHeaderBlock::MapType::iterator lookup_result, + const SpdyStringPiece key, + size_t* spdy_header_block_value_size); + + SpdyHeaderBlock::MapType* block_; + SpdyHeaderBlock::Storage* storage_; + SpdyHeaderBlock::MapType::iterator lookup_result_; + SpdyStringPiece key_; + size_t* spdy_header_block_value_size_; + bool valid_; + }; + + // Returns the estimate of dynamically allocated memory in bytes. + size_t EstimateMemoryUsage() const; + + size_t TotalBytesUsed() const { return key_size_ + value_size_; } + + private: + friend class test::SpdyHeaderBlockPeer; + + void AppendHeader(const SpdyStringPiece key, const SpdyStringPiece value); + Storage* GetStorage(); + SpdyStringPiece WriteKey(const SpdyStringPiece key); + size_t bytes_allocated() const; + + // SpdyStringPieces held by |block_| point to memory owned by |*storage_|. + // |storage_| might be nullptr as long as |block_| is empty. + MapType block_; + std::unique_ptr<Storage> storage_; + + size_t key_size_ = 0; + size_t value_size_ = 0; +}; + +// Writes |fragments| to |dst|, joined by |separator|. |dst| must be large +// enough to hold the result. Returns the number of bytes written. +SPDY_EXPORT_PRIVATE size_t Join(char* dst, + const std::vector<SpdyStringPiece>& fragments, + SpdyStringPiece separator); + +} // namespace spdy + +#endif // QUICHE_SPDY_CORE_SPDY_HEADER_BLOCK_H_ diff --git a/chromium/net/third_party/quiche/src/spdy/core/spdy_header_block_test.cc b/chromium/net/third_party/quiche/src/spdy/core/spdy_header_block_test.cc new file mode 100644 index 00000000000..3511a213918 --- /dev/null +++ b/chromium/net/third_party/quiche/src/spdy/core/spdy_header_block_test.cc @@ -0,0 +1,252 @@ +// Copyright (c) 2012 The Chromium 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/spdy/core/spdy_header_block.h" + +#include <memory> +#include <utility> + +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "net/third_party/quiche/src/spdy/core/spdy_test_utils.h" + +using ::testing::ElementsAre; + +namespace spdy { +namespace test { + +class ValueProxyPeer { + public: + static SpdyStringPiece key(SpdyHeaderBlock::ValueProxy* p) { return p->key_; } +}; + +std::pair<SpdyStringPiece, SpdyStringPiece> Pair(SpdyStringPiece k, + SpdyStringPiece v) { + return std::make_pair(k, v); +} + +// This test verifies that SpdyHeaderBlock behaves correctly when empty. +TEST(SpdyHeaderBlockTest, EmptyBlock) { + SpdyHeaderBlock block; + EXPECT_TRUE(block.empty()); + EXPECT_EQ(0u, block.size()); + EXPECT_EQ(block.end(), block.find("foo")); + EXPECT_TRUE(block.end() == block.begin()); + + // Should have no effect. + block.erase("bar"); +} + +TEST(SpdyHeaderBlockTest, KeyMemoryReclaimedOnLookup) { + SpdyHeaderBlock block; + SpdyStringPiece copied_key1; + { + auto proxy1 = block["some key name"]; + copied_key1 = ValueProxyPeer::key(&proxy1); + } + SpdyStringPiece copied_key2; + { + auto proxy2 = block["some other key name"]; + copied_key2 = ValueProxyPeer::key(&proxy2); + } + // Because proxy1 was never used to modify the block, the memory used for the + // key could be reclaimed and used for the second call to operator[]. + // Therefore, we expect the pointers of the two SpdyStringPieces to be equal. + EXPECT_EQ(copied_key1.data(), copied_key2.data()); + + { + auto proxy1 = block["some key name"]; + block["some other key name"] = "some value"; + } + // Nothing should blow up when proxy1 is destructed, and we should be able to + // modify and access the SpdyHeaderBlock. + block["key"] = "value"; + EXPECT_EQ("value", block["key"]); + EXPECT_EQ("some value", block["some other key name"]); + EXPECT_TRUE(block.find("some key name") == block.end()); +} + +// This test verifies that headers can be set in a variety of ways. +TEST(SpdyHeaderBlockTest, AddHeaders) { + SpdyHeaderBlock block; + block["foo"] = SpdyString(300, 'x'); + block["bar"] = "baz"; + block["qux"] = "qux1"; + block["qux"] = "qux2"; + block.insert(std::make_pair("key", "value")); + + EXPECT_EQ(Pair("foo", SpdyString(300, 'x')), *block.find("foo")); + EXPECT_EQ("baz", block["bar"]); + SpdyString qux("qux"); + EXPECT_EQ("qux2", block[qux]); + ASSERT_NE(block.end(), block.find("key")); + EXPECT_EQ(Pair("key", "value"), *block.find("key")); + + block.erase("key"); + EXPECT_EQ(block.end(), block.find("key")); +} + +// This test verifies that SpdyHeaderBlock can be copied using Clone(). +TEST(SpdyHeaderBlockTest, CopyBlocks) { + SpdyHeaderBlock block1; + block1["foo"] = SpdyString(300, 'x'); + block1["bar"] = "baz"; + block1.insert(std::make_pair("qux", "qux1")); + + SpdyHeaderBlock block2 = block1.Clone(); + SpdyHeaderBlock block3(block1.Clone()); + + EXPECT_EQ(block1, block2); + EXPECT_EQ(block1, block3); +} + +TEST(SpdyHeaderBlockTest, Equality) { + // Test equality and inequality operators. + SpdyHeaderBlock block1; + block1["foo"] = "bar"; + + SpdyHeaderBlock block2; + block2["foo"] = "bar"; + + SpdyHeaderBlock block3; + block3["baz"] = "qux"; + + EXPECT_EQ(block1, block2); + EXPECT_NE(block1, block3); + + block2["baz"] = "qux"; + EXPECT_NE(block1, block2); +} + +// Test that certain methods do not crash on moved-from instances. +TEST(SpdyHeaderBlockTest, MovedFromIsValid) { + SpdyHeaderBlock block1; + block1["foo"] = "bar"; + + SpdyHeaderBlock block2(std::move(block1)); + EXPECT_THAT(block2, ElementsAre(Pair("foo", "bar"))); + + block1["baz"] = "qux"; // NOLINT testing post-move behavior + + SpdyHeaderBlock block3(std::move(block1)); + + block1["foo"] = "bar"; // NOLINT testing post-move behavior + + SpdyHeaderBlock block4(std::move(block1)); + + block1.clear(); // NOLINT testing post-move behavior + EXPECT_TRUE(block1.empty()); + + block1["foo"] = "bar"; + EXPECT_THAT(block1, ElementsAre(Pair("foo", "bar"))); +} + +// This test verifies that headers can be appended to no matter how they were +// added originally. +TEST(SpdyHeaderBlockTest, AppendHeaders) { + SpdyHeaderBlock block; + block["foo"] = "foo"; + block.AppendValueOrAddHeader("foo", "bar"); + EXPECT_EQ(Pair("foo", SpdyString("foo\0bar", 7)), *block.find("foo")); + + block.insert(std::make_pair("foo", "baz")); + EXPECT_EQ("baz", block["foo"]); + EXPECT_EQ(Pair("foo", "baz"), *block.find("foo")); + + // Try all four methods of adding an entry. + block["cookie"] = "key1=value1"; + block.AppendValueOrAddHeader("h1", "h1v1"); + block.insert(std::make_pair("h2", "h2v1")); + + block.AppendValueOrAddHeader("h3", "h3v2"); + block.AppendValueOrAddHeader("h2", "h2v2"); + block.AppendValueOrAddHeader("h1", "h1v2"); + block.AppendValueOrAddHeader("cookie", "key2=value2"); + + block.AppendValueOrAddHeader("cookie", "key3=value3"); + block.AppendValueOrAddHeader("h1", "h1v3"); + block.AppendValueOrAddHeader("h2", "h2v3"); + block.AppendValueOrAddHeader("h3", "h3v3"); + block.AppendValueOrAddHeader("h4", "singleton"); + + EXPECT_EQ("key1=value1; key2=value2; key3=value3", block["cookie"]); + EXPECT_EQ("baz", block["foo"]); + EXPECT_EQ(SpdyString("h1v1\0h1v2\0h1v3", 14), block["h1"]); + EXPECT_EQ(SpdyString("h2v1\0h2v2\0h2v3", 14), block["h2"]); + EXPECT_EQ(SpdyString("h3v2\0h3v3", 9), block["h3"]); + EXPECT_EQ("singleton", block["h4"]); +} + +TEST(JoinTest, JoinEmpty) { + std::vector<SpdyStringPiece> empty; + SpdyStringPiece separator = ", "; + char buf[10] = ""; + size_t written = Join(buf, empty, separator); + EXPECT_EQ(0u, written); +} + +TEST(JoinTest, JoinOne) { + std::vector<SpdyStringPiece> v = {"one"}; + SpdyStringPiece separator = ", "; + char buf[15]; + size_t written = Join(buf, v, separator); + EXPECT_EQ(3u, written); + EXPECT_EQ("one", SpdyStringPiece(buf, written)); +} + +TEST(JoinTest, JoinMultiple) { + std::vector<SpdyStringPiece> v = {"one", "two", "three"}; + SpdyStringPiece separator = ", "; + char buf[15]; + size_t written = Join(buf, v, separator); + EXPECT_EQ(15u, written); + EXPECT_EQ("one, two, three", SpdyStringPiece(buf, written)); +} + +namespace { +size_t SpdyHeaderBlockSize(const SpdyHeaderBlock& block) { + size_t size = 0; + for (const auto& pair : block) { + size += pair.first.size() + pair.second.size(); + } + return size; +} +} // namespace + +// Tests SpdyHeaderBlock SizeEstimate(). +TEST(SpdyHeaderBlockTest, TotalBytesUsed) { + SpdyHeaderBlock block; + const size_t value_size = 300; + block["foo"] = SpdyString(value_size, 'x'); + EXPECT_EQ(block.TotalBytesUsed(), SpdyHeaderBlockSize(block)); + block.insert(std::make_pair("key", SpdyString(value_size, 'x'))); + EXPECT_EQ(block.TotalBytesUsed(), SpdyHeaderBlockSize(block)); + block.AppendValueOrAddHeader("abc", SpdyString(value_size, 'x')); + EXPECT_EQ(block.TotalBytesUsed(), SpdyHeaderBlockSize(block)); + + // Replace value for existing key. + block["foo"] = SpdyString(value_size, 'x'); + EXPECT_EQ(block.TotalBytesUsed(), SpdyHeaderBlockSize(block)); + block.insert(std::make_pair("key", SpdyString(value_size, 'x'))); + EXPECT_EQ(block.TotalBytesUsed(), SpdyHeaderBlockSize(block)); + // Add value for existing key. + block.AppendValueOrAddHeader("abc", SpdyString(value_size, 'x')); + EXPECT_EQ(block.TotalBytesUsed(), SpdyHeaderBlockSize(block)); + + // Copies/clones SpdyHeaderBlock. + size_t block_size = block.TotalBytesUsed(); + SpdyHeaderBlock block_copy = std::move(block); + EXPECT_EQ(block_size, block_copy.TotalBytesUsed()); + + // Erases key. + block_copy.erase("foo"); + EXPECT_EQ(block_copy.TotalBytesUsed(), SpdyHeaderBlockSize(block_copy)); + block_copy.erase("key"); + EXPECT_EQ(block_copy.TotalBytesUsed(), SpdyHeaderBlockSize(block_copy)); + block_copy.erase("abc"); + EXPECT_EQ(block_copy.TotalBytesUsed(), SpdyHeaderBlockSize(block_copy)); +} + +} // namespace test +} // namespace spdy diff --git a/chromium/net/third_party/quiche/src/spdy/core/spdy_headers_handler_interface.h b/chromium/net/third_party/quiche/src/spdy/core/spdy_headers_handler_interface.h new file mode 100644 index 00000000000..ce6c1b64d7d --- /dev/null +++ b/chromium/net/third_party/quiche/src/spdy/core/spdy_headers_handler_interface.h @@ -0,0 +1,39 @@ +// Copyright 2015 The Chromium 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_SPDY_CORE_SPDY_HEADERS_HANDLER_INTERFACE_H_ +#define QUICHE_SPDY_CORE_SPDY_HEADERS_HANDLER_INTERFACE_H_ + +#include <stddef.h> + +#include "net/third_party/quiche/src/spdy/platform/api/spdy_export.h" +#include "net/third_party/quiche/src/spdy/platform/api/spdy_string_piece.h" + +namespace spdy { + +// This interface defines how an object that accepts header data should behave. +// It is used by both SpdyHeadersBlockParser and HpackDecoder. +class SPDY_EXPORT_PRIVATE SpdyHeadersHandlerInterface { + public: + virtual ~SpdyHeadersHandlerInterface() {} + + // A callback method which notifies when the parser starts handling a new + // header block. Will only be called once per block, even if it extends into + // CONTINUATION frames. + virtual void OnHeaderBlockStart() = 0; + + // A callback method which notifies on a header key value pair. Multiple + // values for a given key will be emitted as multiple calls to OnHeader. + virtual void OnHeader(SpdyStringPiece key, SpdyStringPiece value) = 0; + + // A callback method which notifies when the parser finishes handling a + // header block (i.e. the containing frame has the END_HEADERS flag set). + // Also indicates the total number of bytes in this block. + virtual void OnHeaderBlockEnd(size_t uncompressed_header_bytes, + size_t compressed_header_bytes) = 0; +}; + +} // namespace spdy + +#endif // QUICHE_SPDY_CORE_SPDY_HEADERS_HANDLER_INTERFACE_H_ diff --git a/chromium/net/third_party/quiche/src/spdy/core/spdy_no_op_visitor.cc b/chromium/net/third_party/quiche/src/spdy/core/spdy_no_op_visitor.cc new file mode 100644 index 00000000000..5dcc15c70fa --- /dev/null +++ b/chromium/net/third_party/quiche/src/spdy/core/spdy_no_op_visitor.cc @@ -0,0 +1,29 @@ +// Copyright (c) 2016 The Chromium 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/spdy/core/spdy_no_op_visitor.h" + +#include <type_traits> + +namespace spdy { +namespace test { + +SpdyNoOpVisitor::SpdyNoOpVisitor() { + static_assert(std::is_abstract<SpdyNoOpVisitor>::value == false, + "Need to update SpdyNoOpVisitor."); +} +SpdyNoOpVisitor::~SpdyNoOpVisitor() = default; + +SpdyHeadersHandlerInterface* SpdyNoOpVisitor::OnHeaderFrameStart( + SpdyStreamId stream_id) { + return this; +} + +bool SpdyNoOpVisitor::OnUnknownFrame(SpdyStreamId stream_id, + uint8_t frame_type) { + return true; +} + +} // namespace test +} // namespace spdy diff --git a/chromium/net/third_party/quiche/src/spdy/core/spdy_no_op_visitor.h b/chromium/net/third_party/quiche/src/spdy/core/spdy_no_op_visitor.h new file mode 100644 index 00000000000..80e1535514c --- /dev/null +++ b/chromium/net/third_party/quiche/src/spdy/core/spdy_no_op_visitor.h @@ -0,0 +1,89 @@ +// Copyright (c) 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// SpdyNoOpVisitor implements several of the visitor and handler interfaces +// to make it easier to write tests that need to provide instances. Other +// interfaces can be added as needed. + +#ifndef QUICHE_SPDY_CORE_SPDY_NO_OP_VISITOR_H_ +#define QUICHE_SPDY_CORE_SPDY_NO_OP_VISITOR_H_ + +#include <cstdint> + +#include "net/third_party/quiche/src/spdy/core/http2_frame_decoder_adapter.h" +#include "net/third_party/quiche/src/spdy/core/spdy_protocol.h" +#include "net/third_party/quiche/src/spdy/platform/api/spdy_string_piece.h" + +namespace spdy { +namespace test { + +class SpdyNoOpVisitor : public SpdyFramerVisitorInterface, + public SpdyFramerDebugVisitorInterface, + public SpdyHeadersHandlerInterface { + public: + SpdyNoOpVisitor(); + ~SpdyNoOpVisitor() override; + + // SpdyFramerVisitorInterface methods: + void OnError(http2::Http2DecoderAdapter::SpdyFramerError error) override {} + SpdyHeadersHandlerInterface* OnHeaderFrameStart( + SpdyStreamId stream_id) override; + void OnHeaderFrameEnd(SpdyStreamId stream_id) override {} + void OnDataFrameHeader(SpdyStreamId stream_id, + size_t length, + bool fin) override {} + void OnStreamFrameData(SpdyStreamId stream_id, + const char* data, + size_t len) override {} + void OnStreamEnd(SpdyStreamId stream_id) override {} + void OnStreamPadding(SpdyStreamId stream_id, size_t len) override {} + void OnRstStream(SpdyStreamId stream_id, SpdyErrorCode error_code) override {} + void OnSetting(SpdySettingsId id, uint32_t value) override {} + void OnPing(SpdyPingId unique_id, bool is_ack) override {} + void OnSettingsEnd() override {} + void OnSettingsAck() override {} + void OnGoAway(SpdyStreamId last_accepted_stream_id, + SpdyErrorCode error_code) override {} + void OnHeaders(SpdyStreamId stream_id, + bool has_priority, + int weight, + SpdyStreamId parent_stream_id, + bool exclusive, + bool fin, + bool end) override {} + void OnWindowUpdate(SpdyStreamId stream_id, int delta_window_size) override {} + void OnPushPromise(SpdyStreamId stream_id, + SpdyStreamId promised_stream_id, + bool end) override {} + void OnContinuation(SpdyStreamId stream_id, bool end) override {} + void OnAltSvc(SpdyStreamId stream_id, + SpdyStringPiece origin, + const SpdyAltSvcWireFormat::AlternativeServiceVector& + altsvc_vector) override {} + void OnPriority(SpdyStreamId stream_id, + SpdyStreamId parent_stream_id, + int weight, + bool exclusive) override {} + bool OnUnknownFrame(SpdyStreamId stream_id, uint8_t frame_type) override; + + // SpdyFramerDebugVisitorInterface methods: + void OnSendCompressedFrame(SpdyStreamId stream_id, + SpdyFrameType type, + size_t payload_len, + size_t frame_len) override {} + void OnReceiveCompressedFrame(SpdyStreamId stream_id, + SpdyFrameType type, + size_t frame_len) override {} + + // SpdyHeadersHandlerInterface methods: + void OnHeaderBlockStart() override {} + void OnHeader(SpdyStringPiece key, SpdyStringPiece value) override {} + void OnHeaderBlockEnd(size_t /* uncompressed_header_bytes */, + size_t /* compressed_header_bytes */) override {} +}; + +} // namespace test +} // namespace spdy + +#endif // QUICHE_SPDY_CORE_SPDY_NO_OP_VISITOR_H_ diff --git a/chromium/net/third_party/quiche/src/spdy/core/spdy_pinnable_buffer_piece.cc b/chromium/net/third_party/quiche/src/spdy/core/spdy_pinnable_buffer_piece.cc new file mode 100644 index 00000000000..8670962c56a --- /dev/null +++ b/chromium/net/third_party/quiche/src/spdy/core/spdy_pinnable_buffer_piece.cc @@ -0,0 +1,36 @@ +// Copyright 2014 The Chromium 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/spdy/core/spdy_pinnable_buffer_piece.h" + +#include <new> + +namespace spdy { + +SpdyPinnableBufferPiece::SpdyPinnableBufferPiece() + : buffer_(nullptr), length_(0) {} + +SpdyPinnableBufferPiece::~SpdyPinnableBufferPiece() = default; + +void SpdyPinnableBufferPiece::Pin() { + if (!storage_ && buffer_ != nullptr && length_ != 0) { + storage_.reset(new char[length_]); + std::copy(buffer_, buffer_ + length_, storage_.get()); + buffer_ = storage_.get(); + } +} + +void SpdyPinnableBufferPiece::Swap(SpdyPinnableBufferPiece* other) { + size_t length = length_; + length_ = other->length_; + other->length_ = length; + + const char* buffer = buffer_; + buffer_ = other->buffer_; + other->buffer_ = buffer; + + storage_.swap(other->storage_); +} + +} // namespace spdy diff --git a/chromium/net/third_party/quiche/src/spdy/core/spdy_pinnable_buffer_piece.h b/chromium/net/third_party/quiche/src/spdy/core/spdy_pinnable_buffer_piece.h new file mode 100644 index 00000000000..3032ad7ec00 --- /dev/null +++ b/chromium/net/third_party/quiche/src/spdy/core/spdy_pinnable_buffer_piece.h @@ -0,0 +1,53 @@ +// Copyright 2014 The Chromium 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_SPDY_CORE_SPDY_PINNABLE_BUFFER_PIECE_H_ +#define QUICHE_SPDY_CORE_SPDY_PINNABLE_BUFFER_PIECE_H_ + +#include <stddef.h> + +#include <memory> + +#include "net/third_party/quiche/src/spdy/platform/api/spdy_export.h" +#include "net/third_party/quiche/src/spdy/platform/api/spdy_string_piece.h" + +namespace spdy { + +class SpdyPrefixedBufferReader; + +// Helper class of SpdyPrefixedBufferReader. +// Represents a piece of consumed buffer which may (or may not) own its +// underlying storage. Users may "pin" the buffer at a later time to ensure +// a SpdyPinnableBufferPiece owns and retains storage of the buffer. +struct SPDY_EXPORT_PRIVATE SpdyPinnableBufferPiece { + public: + SpdyPinnableBufferPiece(); + ~SpdyPinnableBufferPiece(); + + const char* buffer() const { return buffer_; } + + explicit operator SpdyStringPiece() const { + return SpdyStringPiece(buffer_, length_); + } + + // Allocates and copies the buffer to internal storage. + void Pin(); + + bool IsPinned() const { return storage_ != nullptr; } + + // Swaps buffers, including internal storage, with |other|. + void Swap(SpdyPinnableBufferPiece* other); + + private: + friend class SpdyPrefixedBufferReader; + + const char* buffer_; + size_t length_; + // Null iff |buffer_| isn't pinned. + std::unique_ptr<char[]> storage_; +}; + +} // namespace spdy + +#endif // QUICHE_SPDY_CORE_SPDY_PINNABLE_BUFFER_PIECE_H_ diff --git a/chromium/net/third_party/quiche/src/spdy/core/spdy_pinnable_buffer_piece_test.cc b/chromium/net/third_party/quiche/src/spdy/core/spdy_pinnable_buffer_piece_test.cc new file mode 100644 index 00000000000..d5a6c33f6f5 --- /dev/null +++ b/chromium/net/third_party/quiche/src/spdy/core/spdy_pinnable_buffer_piece_test.cc @@ -0,0 +1,80 @@ +// Copyright 2014 The Chromium 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/spdy/core/spdy_pinnable_buffer_piece.h" + +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "net/third_party/quiche/src/spdy/core/spdy_prefixed_buffer_reader.h" +#include "net/third_party/quiche/src/spdy/platform/api/spdy_string.h" + +namespace spdy { + +namespace test { + +class SpdyPinnableBufferPieceTest : public ::testing::Test { + protected: + SpdyPrefixedBufferReader Build(const SpdyString& prefix, + const SpdyString& suffix) { + prefix_ = prefix; + suffix_ = suffix; + return SpdyPrefixedBufferReader(prefix_.data(), prefix_.length(), + suffix_.data(), suffix_.length()); + } + SpdyString prefix_, suffix_; +}; + +TEST_F(SpdyPinnableBufferPieceTest, Pin) { + SpdyPrefixedBufferReader reader = Build("foobar", ""); + SpdyPinnableBufferPiece piece; + EXPECT_TRUE(reader.ReadN(6, &piece)); + + // Piece points to underlying prefix storage. + EXPECT_EQ(SpdyStringPiece("foobar"), SpdyStringPiece(piece)); + EXPECT_FALSE(piece.IsPinned()); + EXPECT_EQ(prefix_.data(), piece.buffer()); + + piece.Pin(); + + // Piece now points to allocated storage. + EXPECT_EQ(SpdyStringPiece("foobar"), SpdyStringPiece(piece)); + EXPECT_TRUE(piece.IsPinned()); + EXPECT_NE(prefix_.data(), piece.buffer()); + + // Pinning again has no effect. + const char* buffer = piece.buffer(); + piece.Pin(); + EXPECT_EQ(buffer, piece.buffer()); +} + +TEST_F(SpdyPinnableBufferPieceTest, Swap) { + SpdyPrefixedBufferReader reader = Build("foobar", ""); + SpdyPinnableBufferPiece piece1, piece2; + EXPECT_TRUE(reader.ReadN(4, &piece1)); + EXPECT_TRUE(reader.ReadN(2, &piece2)); + + piece1.Pin(); + + EXPECT_EQ(SpdyStringPiece("foob"), SpdyStringPiece(piece1)); + EXPECT_TRUE(piece1.IsPinned()); + EXPECT_EQ(SpdyStringPiece("ar"), SpdyStringPiece(piece2)); + EXPECT_FALSE(piece2.IsPinned()); + + piece1.Swap(&piece2); + + EXPECT_EQ(SpdyStringPiece("ar"), SpdyStringPiece(piece1)); + EXPECT_FALSE(piece1.IsPinned()); + EXPECT_EQ(SpdyStringPiece("foob"), SpdyStringPiece(piece2)); + EXPECT_TRUE(piece2.IsPinned()); + + SpdyPinnableBufferPiece empty; + piece2.Swap(&empty); + + EXPECT_EQ(SpdyStringPiece(""), SpdyStringPiece(piece2)); + EXPECT_FALSE(piece2.IsPinned()); +} + +} // namespace test + +} // namespace spdy diff --git a/chromium/net/third_party/quiche/src/spdy/core/spdy_prefixed_buffer_reader.cc b/chromium/net/third_party/quiche/src/spdy/core/spdy_prefixed_buffer_reader.cc new file mode 100644 index 00000000000..9f1fa7fda7c --- /dev/null +++ b/chromium/net/third_party/quiche/src/spdy/core/spdy_prefixed_buffer_reader.cc @@ -0,0 +1,84 @@ +// Copyright 2014 The Chromium 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/spdy/core/spdy_prefixed_buffer_reader.h" + +#include <new> + +#include "base/logging.h" + +namespace spdy { + +SpdyPrefixedBufferReader::SpdyPrefixedBufferReader(const char* prefix, + size_t prefix_length, + const char* suffix, + size_t suffix_length) + : prefix_(prefix), + suffix_(suffix), + prefix_length_(prefix_length), + suffix_length_(suffix_length) {} + +size_t SpdyPrefixedBufferReader::Available() { + return prefix_length_ + suffix_length_; +} + +bool SpdyPrefixedBufferReader::ReadN(size_t count, char* out) { + if (Available() < count) { + return false; + } + + if (prefix_length_ >= count) { + // Read is fully satisfied by the prefix. + std::copy(prefix_, prefix_ + count, out); + prefix_ += count; + prefix_length_ -= count; + return true; + } else if (prefix_length_ != 0) { + // Read is partially satisfied by the prefix. + out = std::copy(prefix_, prefix_ + prefix_length_, out); + count -= prefix_length_; + prefix_length_ = 0; + // Fallthrough to suffix read. + } + DCHECK(suffix_length_ >= count); + // Read is satisfied by the suffix. + std::copy(suffix_, suffix_ + count, out); + suffix_ += count; + suffix_length_ -= count; + return true; +} + +bool SpdyPrefixedBufferReader::ReadN(size_t count, + SpdyPinnableBufferPiece* out) { + if (Available() < count) { + return false; + } + + out->storage_.reset(); + out->length_ = count; + + if (prefix_length_ >= count) { + // Read is fully satisfied by the prefix. + out->buffer_ = prefix_; + prefix_ += count; + prefix_length_ -= count; + return true; + } else if (prefix_length_ != 0) { + // Read is only partially satisfied by the prefix. We need to allocate + // contiguous storage as the read spans the prefix & suffix. + out->storage_.reset(new char[count]); + out->buffer_ = out->storage_.get(); + ReadN(count, out->storage_.get()); + return true; + } else { + DCHECK(suffix_length_ >= count); + // Read is fully satisfied by the suffix. + out->buffer_ = suffix_; + suffix_ += count; + suffix_length_ -= count; + return true; + } +} + +} // namespace spdy diff --git a/chromium/net/third_party/quiche/src/spdy/core/spdy_prefixed_buffer_reader.h b/chromium/net/third_party/quiche/src/spdy/core/spdy_prefixed_buffer_reader.h new file mode 100644 index 00000000000..2905698e648 --- /dev/null +++ b/chromium/net/third_party/quiche/src/spdy/core/spdy_prefixed_buffer_reader.h @@ -0,0 +1,45 @@ +// Copyright 2014 The Chromium 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_SPDY_CORE_SPDY_PREFIXED_BUFFER_READER_H_ +#define QUICHE_SPDY_CORE_SPDY_PREFIXED_BUFFER_READER_H_ + +#include <stddef.h> + +#include "net/third_party/quiche/src/spdy/core/spdy_pinnable_buffer_piece.h" +#include "net/third_party/quiche/src/spdy/platform/api/spdy_export.h" + +namespace spdy { + +// Reader class which simplifies reading contiguously from +// from a disjoint buffer prefix & suffix. +class SPDY_EXPORT_PRIVATE SpdyPrefixedBufferReader { + public: + SpdyPrefixedBufferReader(const char* prefix, + size_t prefix_length, + const char* suffix, + size_t suffix_length); + + // Returns number of bytes available to be read. + size_t Available(); + + // Reads |count| bytes, copying into |*out|. Returns true on success, + // false if not enough bytes were available. + bool ReadN(size_t count, char* out); + + // Reads |count| bytes, returned in |*out|. Returns true on success, + // false if not enough bytes were available. + bool ReadN(size_t count, SpdyPinnableBufferPiece* out); + + private: + const char* prefix_; + const char* suffix_; + + size_t prefix_length_; + size_t suffix_length_; +}; + +} // namespace spdy + +#endif // QUICHE_SPDY_CORE_SPDY_PREFIXED_BUFFER_READER_H_ diff --git a/chromium/net/third_party/quiche/src/spdy/core/spdy_prefixed_buffer_reader_test.cc b/chromium/net/third_party/quiche/src/spdy/core/spdy_prefixed_buffer_reader_test.cc new file mode 100644 index 00000000000..1d052c7f21c --- /dev/null +++ b/chromium/net/third_party/quiche/src/spdy/core/spdy_prefixed_buffer_reader_test.cc @@ -0,0 +1,131 @@ +// Copyright 2014 The Chromium 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/spdy/core/spdy_prefixed_buffer_reader.h" + +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "net/third_party/quiche/src/spdy/platform/api/spdy_string.h" +#include "net/third_party/quiche/src/spdy/platform/api/spdy_string_piece.h" + +namespace spdy { + +namespace test { + +using testing::ElementsAreArray; + +class SpdyPrefixedBufferReaderTest : public ::testing::Test { + protected: + SpdyPrefixedBufferReader Build(const SpdyString& prefix, + const SpdyString& suffix) { + prefix_ = prefix; + suffix_ = suffix; + return SpdyPrefixedBufferReader(prefix_.data(), prefix_.length(), + suffix_.data(), suffix_.length()); + } + SpdyString prefix_, suffix_; +}; + +TEST_F(SpdyPrefixedBufferReaderTest, ReadRawFromPrefix) { + SpdyPrefixedBufferReader reader = Build("foobar", ""); + EXPECT_EQ(6u, reader.Available()); + + char buffer[] = "123456"; + EXPECT_FALSE(reader.ReadN(10, buffer)); // Not enough buffer. + EXPECT_TRUE(reader.ReadN(6, buffer)); + EXPECT_THAT(buffer, ElementsAreArray("foobar")); + EXPECT_EQ(0u, reader.Available()); +} + +TEST_F(SpdyPrefixedBufferReaderTest, ReadPieceFromPrefix) { + SpdyPrefixedBufferReader reader = Build("foobar", ""); + EXPECT_EQ(6u, reader.Available()); + + SpdyPinnableBufferPiece piece; + EXPECT_FALSE(reader.ReadN(10, &piece)); // Not enough buffer. + EXPECT_TRUE(reader.ReadN(6, &piece)); + EXPECT_FALSE(piece.IsPinned()); + EXPECT_EQ(SpdyStringPiece("foobar"), SpdyStringPiece(piece)); + EXPECT_EQ(0u, reader.Available()); +} + +TEST_F(SpdyPrefixedBufferReaderTest, ReadRawFromSuffix) { + SpdyPrefixedBufferReader reader = Build("", "foobar"); + EXPECT_EQ(6u, reader.Available()); + + char buffer[] = "123456"; + EXPECT_FALSE(reader.ReadN(10, buffer)); // Not enough buffer. + EXPECT_TRUE(reader.ReadN(6, buffer)); + EXPECT_THAT(buffer, ElementsAreArray("foobar")); + EXPECT_EQ(0u, reader.Available()); +} + +TEST_F(SpdyPrefixedBufferReaderTest, ReadPieceFromSuffix) { + SpdyPrefixedBufferReader reader = Build("", "foobar"); + EXPECT_EQ(6u, reader.Available()); + + SpdyPinnableBufferPiece piece; + EXPECT_FALSE(reader.ReadN(10, &piece)); // Not enough buffer. + EXPECT_TRUE(reader.ReadN(6, &piece)); + EXPECT_FALSE(piece.IsPinned()); + EXPECT_EQ(SpdyStringPiece("foobar"), SpdyStringPiece(piece)); + EXPECT_EQ(0u, reader.Available()); +} + +TEST_F(SpdyPrefixedBufferReaderTest, ReadRawSpanning) { + SpdyPrefixedBufferReader reader = Build("foob", "ar"); + EXPECT_EQ(6u, reader.Available()); + + char buffer[] = "123456"; + EXPECT_FALSE(reader.ReadN(10, buffer)); // Not enough buffer. + EXPECT_TRUE(reader.ReadN(6, buffer)); + EXPECT_THAT(buffer, ElementsAreArray("foobar")); + EXPECT_EQ(0u, reader.Available()); +} + +TEST_F(SpdyPrefixedBufferReaderTest, ReadPieceSpanning) { + SpdyPrefixedBufferReader reader = Build("foob", "ar"); + EXPECT_EQ(6u, reader.Available()); + + SpdyPinnableBufferPiece piece; + EXPECT_FALSE(reader.ReadN(10, &piece)); // Not enough buffer. + EXPECT_TRUE(reader.ReadN(6, &piece)); + EXPECT_TRUE(piece.IsPinned()); + EXPECT_EQ(SpdyStringPiece("foobar"), SpdyStringPiece(piece)); + EXPECT_EQ(0u, reader.Available()); +} + +TEST_F(SpdyPrefixedBufferReaderTest, ReadMixed) { + SpdyPrefixedBufferReader reader = Build("abcdef", "hijkl"); + EXPECT_EQ(11u, reader.Available()); + + char buffer[] = "1234"; + SpdyPinnableBufferPiece piece; + + EXPECT_TRUE(reader.ReadN(3, buffer)); + EXPECT_THAT(buffer, ElementsAreArray("abc4")); + EXPECT_EQ(8u, reader.Available()); + + EXPECT_TRUE(reader.ReadN(2, buffer)); + EXPECT_THAT(buffer, ElementsAreArray("dec4")); + EXPECT_EQ(6u, reader.Available()); + + EXPECT_TRUE(reader.ReadN(3, &piece)); + EXPECT_EQ(SpdyStringPiece("fhi"), SpdyStringPiece(piece)); + EXPECT_TRUE(piece.IsPinned()); + EXPECT_EQ(3u, reader.Available()); + + EXPECT_TRUE(reader.ReadN(2, &piece)); + EXPECT_EQ(SpdyStringPiece("jk"), SpdyStringPiece(piece)); + EXPECT_FALSE(piece.IsPinned()); + EXPECT_EQ(1u, reader.Available()); + + EXPECT_TRUE(reader.ReadN(1, buffer)); + EXPECT_THAT(buffer, ElementsAreArray("lec4")); + EXPECT_EQ(0u, reader.Available()); +} + +} // namespace test + +} // namespace spdy diff --git a/chromium/net/third_party/quiche/src/spdy/core/spdy_protocol.cc b/chromium/net/third_party/quiche/src/spdy/core/spdy_protocol.cc new file mode 100644 index 00000000000..f51dc6e6dfc --- /dev/null +++ b/chromium/net/third_party/quiche/src/spdy/core/spdy_protocol.cc @@ -0,0 +1,571 @@ +// Copyright (c) 2012 The Chromium 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/spdy/core/spdy_protocol.h" + +#include <ostream> + +#include "net/third_party/quiche/src/spdy/platform/api/spdy_bug_tracker.h" +#include "net/third_party/quiche/src/spdy/platform/api/spdy_ptr_util.h" +#include "net/third_party/quiche/src/spdy/platform/api/spdy_string_utils.h" + +namespace spdy { + +const char* const kHttp2ConnectionHeaderPrefix = + "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n"; + +std::ostream& operator<<(std::ostream& out, SpdyKnownSettingsId id) { + return out << static_cast<SpdySettingsId>(id); +} + +std::ostream& operator<<(std::ostream& out, SpdyFrameType frame_type) { + return out << SerializeFrameType(frame_type); +} + +SpdyPriority ClampSpdy3Priority(SpdyPriority priority) { + if (priority < kV3HighestPriority) { + SPDY_BUG << "Invalid priority: " << static_cast<int>(priority); + return kV3HighestPriority; + } + if (priority > kV3LowestPriority) { + SPDY_BUG << "Invalid priority: " << static_cast<int>(priority); + return kV3LowestPriority; + } + return priority; +} + +int ClampHttp2Weight(int weight) { + if (weight < kHttp2MinStreamWeight) { + SPDY_BUG << "Invalid weight: " << weight; + return kHttp2MinStreamWeight; + } + if (weight > kHttp2MaxStreamWeight) { + SPDY_BUG << "Invalid weight: " << weight; + return kHttp2MaxStreamWeight; + } + return weight; +} + +int Spdy3PriorityToHttp2Weight(SpdyPriority priority) { + priority = ClampSpdy3Priority(priority); + const float kSteps = 255.9f / 7.f; + return static_cast<int>(kSteps * (7.f - priority)) + 1; +} + +SpdyPriority Http2WeightToSpdy3Priority(int weight) { + weight = ClampHttp2Weight(weight); + const float kSteps = 255.9f / 7.f; + return static_cast<SpdyPriority>(7.f - (weight - 1) / kSteps); +} + +bool IsDefinedFrameType(uint8_t frame_type_field) { + return frame_type_field <= SerializeFrameType(SpdyFrameType::MAX_FRAME_TYPE); +} + +SpdyFrameType ParseFrameType(uint8_t frame_type_field) { + SPDY_BUG_IF(!IsDefinedFrameType(frame_type_field)) + << "Frame type not defined: " << static_cast<int>(frame_type_field); + return static_cast<SpdyFrameType>(frame_type_field); +} + +uint8_t SerializeFrameType(SpdyFrameType frame_type) { + return static_cast<uint8_t>(frame_type); +} + +bool IsValidHTTP2FrameStreamId(SpdyStreamId current_frame_stream_id, + SpdyFrameType frame_type_field) { + if (current_frame_stream_id == 0) { + switch (frame_type_field) { + case SpdyFrameType::DATA: + case SpdyFrameType::HEADERS: + case SpdyFrameType::PRIORITY: + case SpdyFrameType::RST_STREAM: + case SpdyFrameType::CONTINUATION: + case SpdyFrameType::PUSH_PROMISE: + // These frame types must specify a stream + return false; + default: + return true; + } + } else { + switch (frame_type_field) { + case SpdyFrameType::GOAWAY: + case SpdyFrameType::SETTINGS: + case SpdyFrameType::PING: + // These frame types must not specify a stream + return false; + default: + return true; + } + } +} + +const char* FrameTypeToString(SpdyFrameType frame_type) { + switch (frame_type) { + case SpdyFrameType::DATA: + return "DATA"; + case SpdyFrameType::RST_STREAM: + return "RST_STREAM"; + case SpdyFrameType::SETTINGS: + return "SETTINGS"; + case SpdyFrameType::PING: + return "PING"; + case SpdyFrameType::GOAWAY: + return "GOAWAY"; + case SpdyFrameType::HEADERS: + return "HEADERS"; + case SpdyFrameType::WINDOW_UPDATE: + return "WINDOW_UPDATE"; + case SpdyFrameType::PUSH_PROMISE: + return "PUSH_PROMISE"; + case SpdyFrameType::CONTINUATION: + return "CONTINUATION"; + case SpdyFrameType::PRIORITY: + return "PRIORITY"; + case SpdyFrameType::ALTSVC: + return "ALTSVC"; + case SpdyFrameType::EXTENSION: + return "EXTENSION (unspecified)"; + } + return "UNKNOWN_FRAME_TYPE"; +} + +bool ParseSettingsId(SpdySettingsId wire_setting_id, + SpdyKnownSettingsId* setting_id) { + if (wire_setting_id != SETTINGS_EXPERIMENT_SCHEDULER && + (wire_setting_id < SETTINGS_MIN || wire_setting_id > SETTINGS_MAX)) { + return false; + } + + *setting_id = static_cast<SpdyKnownSettingsId>(wire_setting_id); + // This switch ensures that the casted value is valid. The default case is + // explicitly omitted to have compile-time guarantees that new additions to + // |SpdyKnownSettingsId| must also be handled here. + switch (*setting_id) { + case SETTINGS_HEADER_TABLE_SIZE: + case SETTINGS_ENABLE_PUSH: + case SETTINGS_MAX_CONCURRENT_STREAMS: + case SETTINGS_INITIAL_WINDOW_SIZE: + case SETTINGS_MAX_FRAME_SIZE: + case SETTINGS_MAX_HEADER_LIST_SIZE: + case SETTINGS_ENABLE_CONNECT_PROTOCOL: + case SETTINGS_EXPERIMENT_SCHEDULER: + // FALLTHROUGH_INTENDED + return true; + } + return false; +} + +SpdyString SettingsIdToString(SpdySettingsId id) { + SpdyKnownSettingsId known_id; + if (!ParseSettingsId(id, &known_id)) { + return SpdyStrCat("SETTINGS_UNKNOWN_", + SpdyHexEncodeUInt32AndTrim(uint32_t{id})); + } + + switch (known_id) { + case SETTINGS_HEADER_TABLE_SIZE: + return "SETTINGS_HEADER_TABLE_SIZE"; + case SETTINGS_ENABLE_PUSH: + return "SETTINGS_ENABLE_PUSH"; + case SETTINGS_MAX_CONCURRENT_STREAMS: + return "SETTINGS_MAX_CONCURRENT_STREAMS"; + case SETTINGS_INITIAL_WINDOW_SIZE: + return "SETTINGS_INITIAL_WINDOW_SIZE"; + case SETTINGS_MAX_FRAME_SIZE: + return "SETTINGS_MAX_FRAME_SIZE"; + case SETTINGS_MAX_HEADER_LIST_SIZE: + return "SETTINGS_MAX_HEADER_LIST_SIZE"; + case SETTINGS_ENABLE_CONNECT_PROTOCOL: + return "SETTINGS_ENABLE_CONNECT_PROTOCOL"; + case SETTINGS_EXPERIMENT_SCHEDULER: + return "SETTINGS_EXPERIMENT_SCHEDULER"; + } + + return SpdyStrCat("SETTINGS_UNKNOWN_", + SpdyHexEncodeUInt32AndTrim(uint32_t{id})); +} + +SpdyErrorCode ParseErrorCode(uint32_t wire_error_code) { + if (wire_error_code > ERROR_CODE_MAX) { + return ERROR_CODE_INTERNAL_ERROR; + } + + return static_cast<SpdyErrorCode>(wire_error_code); +} + +const char* ErrorCodeToString(SpdyErrorCode error_code) { + switch (error_code) { + case ERROR_CODE_NO_ERROR: + return "NO_ERROR"; + case ERROR_CODE_PROTOCOL_ERROR: + return "PROTOCOL_ERROR"; + case ERROR_CODE_INTERNAL_ERROR: + return "INTERNAL_ERROR"; + case ERROR_CODE_FLOW_CONTROL_ERROR: + return "FLOW_CONTROL_ERROR"; + case ERROR_CODE_SETTINGS_TIMEOUT: + return "SETTINGS_TIMEOUT"; + case ERROR_CODE_STREAM_CLOSED: + return "STREAM_CLOSED"; + case ERROR_CODE_FRAME_SIZE_ERROR: + return "FRAME_SIZE_ERROR"; + case ERROR_CODE_REFUSED_STREAM: + return "REFUSED_STREAM"; + case ERROR_CODE_CANCEL: + return "CANCEL"; + case ERROR_CODE_COMPRESSION_ERROR: + return "COMPRESSION_ERROR"; + case ERROR_CODE_CONNECT_ERROR: + return "CONNECT_ERROR"; + case ERROR_CODE_ENHANCE_YOUR_CALM: + return "ENHANCE_YOUR_CALM"; + case ERROR_CODE_INADEQUATE_SECURITY: + return "INADEQUATE_SECURITY"; + case ERROR_CODE_HTTP_1_1_REQUIRED: + return "HTTP_1_1_REQUIRED"; + } + return "UNKNOWN_ERROR_CODE"; +} + +size_t GetNumberRequiredContinuationFrames(size_t size) { + DCHECK_GT(size, kHttp2MaxControlFrameSendSize); + size_t overflow = size - kHttp2MaxControlFrameSendSize; + int payload_size = + kHttp2MaxControlFrameSendSize - kContinuationFrameMinimumSize; + // This is ceiling(overflow/payload_size) using integer arithmetics. + return (overflow - 1) / payload_size + 1; +} + +const char* const kHttp2Npn = "h2"; + +const char* const kHttp2AuthorityHeader = ":authority"; +const char* const kHttp2MethodHeader = ":method"; +const char* const kHttp2PathHeader = ":path"; +const char* const kHttp2SchemeHeader = ":scheme"; +const char* const kHttp2ProtocolHeader = ":protocol"; + +const char* const kHttp2StatusHeader = ":status"; + +bool SpdyFrameIR::fin() const { + return false; +} + +int SpdyFrameIR::flow_control_window_consumed() const { + return 0; +} + +bool SpdyFrameWithFinIR::fin() const { + return fin_; +} + +SpdyFrameWithHeaderBlockIR::SpdyFrameWithHeaderBlockIR( + SpdyStreamId stream_id, + SpdyHeaderBlock header_block) + : SpdyFrameWithFinIR(stream_id), header_block_(std::move(header_block)) {} + +SpdyFrameWithHeaderBlockIR::~SpdyFrameWithHeaderBlockIR() = default; + +SpdyDataIR::SpdyDataIR(SpdyStreamId stream_id, SpdyStringPiece data) + : SpdyFrameWithFinIR(stream_id), + data_(nullptr), + data_len_(0), + padded_(false), + padding_payload_len_(0) { + SetDataDeep(data); +} + +SpdyDataIR::SpdyDataIR(SpdyStreamId stream_id, const char* data) + : SpdyDataIR(stream_id, SpdyStringPiece(data)) {} + +SpdyDataIR::SpdyDataIR(SpdyStreamId stream_id, SpdyString data) + : SpdyFrameWithFinIR(stream_id), + data_store_(SpdyMakeUnique<SpdyString>(std::move(data))), + data_(data_store_->data()), + data_len_(data_store_->size()), + padded_(false), + padding_payload_len_(0) {} + +SpdyDataIR::SpdyDataIR(SpdyStreamId stream_id) + : SpdyFrameWithFinIR(stream_id), + data_(nullptr), + data_len_(0), + padded_(false), + padding_payload_len_(0) {} + +SpdyDataIR::~SpdyDataIR() = default; + +void SpdyDataIR::Visit(SpdyFrameVisitor* visitor) const { + return visitor->VisitData(*this); +} + +SpdyFrameType SpdyDataIR::frame_type() const { + return SpdyFrameType::DATA; +} + +int SpdyDataIR::flow_control_window_consumed() const { + return padded_ ? 1 + padding_payload_len_ + data_len_ : data_len_; +} + +size_t SpdyDataIR::size() const { + return kFrameHeaderSize + + (padded() ? 1 + padding_payload_len() + data_len() : data_len()); +} + +SpdyRstStreamIR::SpdyRstStreamIR(SpdyStreamId stream_id, + SpdyErrorCode error_code) + : SpdyFrameIR(stream_id) { + set_error_code(error_code); +} + +SpdyRstStreamIR::~SpdyRstStreamIR() = default; + +void SpdyRstStreamIR::Visit(SpdyFrameVisitor* visitor) const { + return visitor->VisitRstStream(*this); +} + +SpdyFrameType SpdyRstStreamIR::frame_type() const { + return SpdyFrameType::RST_STREAM; +} + +size_t SpdyRstStreamIR::size() const { + return kRstStreamFrameSize; +} + +SpdySettingsIR::SpdySettingsIR() : is_ack_(false) {} + +SpdySettingsIR::~SpdySettingsIR() = default; + +void SpdySettingsIR::Visit(SpdyFrameVisitor* visitor) const { + return visitor->VisitSettings(*this); +} + +SpdyFrameType SpdySettingsIR::frame_type() const { + return SpdyFrameType::SETTINGS; +} + +size_t SpdySettingsIR::size() const { + return kFrameHeaderSize + values_.size() * kSettingsOneSettingSize; +} + +void SpdyPingIR::Visit(SpdyFrameVisitor* visitor) const { + return visitor->VisitPing(*this); +} + +SpdyFrameType SpdyPingIR::frame_type() const { + return SpdyFrameType::PING; +} + +size_t SpdyPingIR::size() const { + return kPingFrameSize; +} + +SpdyGoAwayIR::SpdyGoAwayIR(SpdyStreamId last_good_stream_id, + SpdyErrorCode error_code, + SpdyStringPiece description) + : description_(description) { + set_last_good_stream_id(last_good_stream_id); + set_error_code(error_code); +} + +SpdyGoAwayIR::SpdyGoAwayIR(SpdyStreamId last_good_stream_id, + SpdyErrorCode error_code, + const char* description) + : SpdyGoAwayIR(last_good_stream_id, + error_code, + SpdyStringPiece(description)) {} + +SpdyGoAwayIR::SpdyGoAwayIR(SpdyStreamId last_good_stream_id, + SpdyErrorCode error_code, + SpdyString description) + : description_store_(std::move(description)), + description_(description_store_) { + set_last_good_stream_id(last_good_stream_id); + set_error_code(error_code); +} + +SpdyGoAwayIR::~SpdyGoAwayIR() = default; + +void SpdyGoAwayIR::Visit(SpdyFrameVisitor* visitor) const { + return visitor->VisitGoAway(*this); +} + +SpdyFrameType SpdyGoAwayIR::frame_type() const { + return SpdyFrameType::GOAWAY; +} + +size_t SpdyGoAwayIR::size() const { + return kGoawayFrameMinimumSize + description_.size(); +} + +SpdyContinuationIR::SpdyContinuationIR(SpdyStreamId stream_id) + : SpdyFrameIR(stream_id), end_headers_(false) { + encoding_ = SpdyMakeUnique<SpdyString>(); +} + +SpdyContinuationIR::~SpdyContinuationIR() = default; + +void SpdyContinuationIR::Visit(SpdyFrameVisitor* visitor) const { + return visitor->VisitContinuation(*this); +} + +SpdyFrameType SpdyContinuationIR::frame_type() const { + return SpdyFrameType::CONTINUATION; +} + +size_t SpdyContinuationIR::size() const { + // We don't need to get the size of CONTINUATION frame directly. It is + // calculated in HEADERS or PUSH_PROMISE frame. + DLOG(WARNING) << "Shouldn't not call size() for CONTINUATION frame."; + return 0; +} + +void SpdyHeadersIR::Visit(SpdyFrameVisitor* visitor) const { + return visitor->VisitHeaders(*this); +} + +SpdyFrameType SpdyHeadersIR::frame_type() const { + return SpdyFrameType::HEADERS; +} + +size_t SpdyHeadersIR::size() const { + size_t size = kHeadersFrameMinimumSize; + + if (padded_) { + // Padding field length. + size += 1; + size += padding_payload_len_; + } + + if (has_priority_) { + size += 5; + } + + // Assume no hpack encoding is applied. + size += header_block().TotalBytesUsed() + + header_block().size() * kPerHeaderHpackOverhead; + if (size > kHttp2MaxControlFrameSendSize) { + size += GetNumberRequiredContinuationFrames(size) * + kContinuationFrameMinimumSize; + } + return size; +} + +void SpdyWindowUpdateIR::Visit(SpdyFrameVisitor* visitor) const { + return visitor->VisitWindowUpdate(*this); +} + +SpdyFrameType SpdyWindowUpdateIR::frame_type() const { + return SpdyFrameType::WINDOW_UPDATE; +} + +size_t SpdyWindowUpdateIR::size() const { + return kWindowUpdateFrameSize; +} + +void SpdyPushPromiseIR::Visit(SpdyFrameVisitor* visitor) const { + return visitor->VisitPushPromise(*this); +} + +SpdyFrameType SpdyPushPromiseIR::frame_type() const { + return SpdyFrameType::PUSH_PROMISE; +} + +size_t SpdyPushPromiseIR::size() const { + size_t size = kPushPromiseFrameMinimumSize; + + if (padded_) { + // Padding length field. + size += 1; + size += padding_payload_len_; + } + + size += header_block().TotalBytesUsed(); + if (size > kHttp2MaxControlFrameSendSize) { + size += GetNumberRequiredContinuationFrames(size) * + kContinuationFrameMinimumSize; + } + return size; +} + +SpdyAltSvcIR::SpdyAltSvcIR(SpdyStreamId stream_id) : SpdyFrameIR(stream_id) {} + +SpdyAltSvcIR::~SpdyAltSvcIR() = default; + +void SpdyAltSvcIR::Visit(SpdyFrameVisitor* visitor) const { + return visitor->VisitAltSvc(*this); +} + +SpdyFrameType SpdyAltSvcIR::frame_type() const { + return SpdyFrameType::ALTSVC; +} + +size_t SpdyAltSvcIR::size() const { + size_t size = kGetAltSvcFrameMinimumSize; + size += origin_.length(); + // TODO(yasong): estimates the size without serializing the vector. + SpdyString str = + SpdyAltSvcWireFormat::SerializeHeaderFieldValue(altsvc_vector_); + size += str.size(); + return size; +} + +void SpdyPriorityIR::Visit(SpdyFrameVisitor* visitor) const { + return visitor->VisitPriority(*this); +} + +SpdyFrameType SpdyPriorityIR::frame_type() const { + return SpdyFrameType::PRIORITY; +} + +size_t SpdyPriorityIR::size() const { + return kPriorityFrameSize; +} + +void SpdyUnknownIR::Visit(SpdyFrameVisitor* visitor) const { + return visitor->VisitUnknown(*this); +} + +SpdyFrameType SpdyUnknownIR::frame_type() const { + return static_cast<SpdyFrameType>(type()); +} + +size_t SpdyUnknownIR::size() const { + return kFrameHeaderSize + payload_.size(); +} + +int SpdyUnknownIR::flow_control_window_consumed() const { + if (frame_type() == SpdyFrameType::DATA) { + return payload_.size(); + } else { + return 0; + } +} + +// Wire size of pad length field. +const size_t kPadLengthFieldSize = 1; + +size_t GetHeaderFrameSizeSansBlock(const SpdyHeadersIR& header_ir) { + size_t min_size = kFrameHeaderSize; + if (header_ir.padded()) { + min_size += kPadLengthFieldSize; + min_size += header_ir.padding_payload_len(); + } + if (header_ir.has_priority()) { + min_size += 5; + } + return min_size; +} + +size_t GetPushPromiseFrameSizeSansBlock( + const SpdyPushPromiseIR& push_promise_ir) { + size_t min_size = kPushPromiseFrameMinimumSize; + if (push_promise_ir.padded()) { + min_size += kPadLengthFieldSize; + min_size += push_promise_ir.padding_payload_len(); + } + return min_size; +} + +} // namespace spdy diff --git a/chromium/net/third_party/quiche/src/spdy/core/spdy_protocol.h b/chromium/net/third_party/quiche/src/spdy/core/spdy_protocol.h new file mode 100644 index 00000000000..d300d10bed2 --- /dev/null +++ b/chromium/net/third_party/quiche/src/spdy/core/spdy_protocol.h @@ -0,0 +1,1066 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file contains some protocol structures for use with SPDY 3 and HTTP 2 +// The SPDY 3 spec can be found at: +// http://dev.chromium.org/spdy/spdy-protocol/spdy-protocol-draft3 + +#ifndef QUICHE_SPDY_CORE_SPDY_PROTOCOL_H_ +#define QUICHE_SPDY_CORE_SPDY_PROTOCOL_H_ + +#include <cstddef> +#include <cstdint> +#include <iosfwd> +#include <limits> +#include <map> +#include <memory> +#include <new> +#include <utility> + +#include "base/logging.h" +#include "base/macros.h" +#include "net/third_party/quiche/src/spdy/core/spdy_alt_svc_wire_format.h" +#include "net/third_party/quiche/src/spdy/core/spdy_bitmasks.h" +#include "net/third_party/quiche/src/spdy/core/spdy_header_block.h" +#include "net/third_party/quiche/src/spdy/platform/api/spdy_bug_tracker.h" +#include "net/third_party/quiche/src/spdy/platform/api/spdy_export.h" +#include "net/third_party/quiche/src/spdy/platform/api/spdy_ptr_util.h" +#include "net/third_party/quiche/src/spdy/platform/api/spdy_string.h" +#include "net/third_party/quiche/src/spdy/platform/api/spdy_string_piece.h" + +namespace spdy { + +// A stream ID is a 31-bit entity. +using SpdyStreamId = uint32_t; + +// A SETTINGS ID is a 16-bit entity. +using SpdySettingsId = uint16_t; + +// Specifies the stream ID used to denote the current session (for +// flow control). +const SpdyStreamId kSessionFlowControlStreamId = 0; + +// 0 is not a valid stream ID for any other purpose than flow control. +const SpdyStreamId kInvalidStreamId = 0; + +// Max stream id. +const SpdyStreamId kMaxStreamId = 0x7fffffff; + +// The maximum possible frame payload size allowed by the spec. +const uint32_t kSpdyMaxFrameSizeLimit = (1 << 24) - 1; + +// The initial value for the maximum frame payload size as per the spec. This is +// the maximum control frame size we accept. +const uint32_t kHttp2DefaultFramePayloadLimit = 1 << 14; + +// The maximum size of the control frames that we send, including the size of +// the header. This limit is arbitrary. We can enforce it here or at the +// application layer. We chose the framing layer, but this can be changed (or +// removed) if necessary later down the line. +const size_t kHttp2MaxControlFrameSendSize = kHttp2DefaultFramePayloadLimit - 1; + +// Number of octets in the frame header. +const size_t kFrameHeaderSize = 9; + +// The initial value for the maximum frame payload size as per the spec. This is +// the maximum control frame size we accept. +const uint32_t kHttp2DefaultFrameSizeLimit = + kHttp2DefaultFramePayloadLimit + kFrameHeaderSize; + +// The initial value for the maximum size of the header list, "unlimited" (max +// unsigned 32-bit int) as per the spec. +const uint32_t kSpdyInitialHeaderListSizeLimit = 0xFFFFFFFF; + +// Maximum window size for a Spdy stream or session. +const int32_t kSpdyMaximumWindowSize = 0x7FFFFFFF; // Max signed 32bit int + +// Maximum padding size in octets for one DATA or HEADERS or PUSH_PROMISE frame. +const int32_t kPaddingSizePerFrame = 256; + +// The HTTP/2 connection preface, which must be the first bytes sent by the +// client upon starting an HTTP/2 connection, and which must be followed by a +// SETTINGS frame. Note that even though |kHttp2ConnectionHeaderPrefix| is +// defined as a string literal with a null terminator, the actual connection +// preface is only the first |kHttp2ConnectionHeaderPrefixSize| bytes, which +// excludes the null terminator. +SPDY_EXPORT_PRIVATE extern const char* const kHttp2ConnectionHeaderPrefix; +const int kHttp2ConnectionHeaderPrefixSize = 24; + +// Wire values for HTTP2 frame types. +enum class SpdyFrameType : uint8_t { + DATA = 0x00, + HEADERS = 0x01, + PRIORITY = 0x02, + RST_STREAM = 0x03, + SETTINGS = 0x04, + PUSH_PROMISE = 0x05, + PING = 0x06, + GOAWAY = 0x07, + WINDOW_UPDATE = 0x08, + CONTINUATION = 0x09, + // ALTSVC is a public extension. + ALTSVC = 0x0a, + MAX_FRAME_TYPE = ALTSVC, + // The specific value of EXTENSION is meaningless; it is a placeholder used + // within SpdyFramer's state machine when handling unknown frames via an + // extension API. + // TODO(birenroy): Remove the fake EXTENSION value from the SpdyFrameType + // enum. + EXTENSION = 0xff +}; + +// Flags on data packets. +enum SpdyDataFlags { + DATA_FLAG_NONE = 0x00, + DATA_FLAG_FIN = 0x01, + DATA_FLAG_PADDED = 0x08, +}; + +// Flags on control packets +enum SpdyControlFlags { + CONTROL_FLAG_NONE = 0x00, + CONTROL_FLAG_FIN = 0x01, +}; + +enum SpdyPingFlags { + PING_FLAG_ACK = 0x01, +}; + +// Used by HEADERS, PUSH_PROMISE, and CONTINUATION. +enum SpdyHeadersFlags { + HEADERS_FLAG_END_HEADERS = 0x04, + HEADERS_FLAG_PADDED = 0x08, + HEADERS_FLAG_PRIORITY = 0x20, +}; + +enum SpdyPushPromiseFlags { + PUSH_PROMISE_FLAG_END_PUSH_PROMISE = 0x04, + PUSH_PROMISE_FLAG_PADDED = 0x08, +}; + +enum Http2SettingsControlFlags { + SETTINGS_FLAG_ACK = 0x01, +}; + +// Wire values of HTTP/2 setting identifiers. +enum SpdyKnownSettingsId : SpdySettingsId { + // HPACK header table maximum size. + SETTINGS_HEADER_TABLE_SIZE = 0x1, + SETTINGS_MIN = SETTINGS_HEADER_TABLE_SIZE, + // Whether or not server push (PUSH_PROMISE) is enabled. + SETTINGS_ENABLE_PUSH = 0x2, + // The maximum number of simultaneous live streams in each direction. + SETTINGS_MAX_CONCURRENT_STREAMS = 0x3, + // Initial window size in bytes + SETTINGS_INITIAL_WINDOW_SIZE = 0x4, + // The size of the largest frame payload that a receiver is willing to accept. + SETTINGS_MAX_FRAME_SIZE = 0x5, + // The maximum size of header list that the sender is prepared to accept. + SETTINGS_MAX_HEADER_LIST_SIZE = 0x6, + // Enable Websockets over HTTP/2, see + // https://tools.ietf.org/html/draft-ietf-httpbis-h2-websockets-00. + SETTINGS_ENABLE_CONNECT_PROTOCOL = 0x8, + SETTINGS_MAX = SETTINGS_ENABLE_CONNECT_PROTOCOL, + // Experimental setting used to configure an alternative write scheduler. + SETTINGS_EXPERIMENT_SCHEDULER = 0xFF45, +}; + +// This explicit operator is needed, otherwise compiler finds +// overloaded operator to be ambiguous. +SPDY_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& out, + SpdyKnownSettingsId id); + +// This operator is needed, because SpdyFrameType is an enum class, +// therefore implicit conversion to underlying integer type is not allowed. +SPDY_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& out, + SpdyFrameType frame_type); + +using SettingsMap = std::map<SpdySettingsId, uint32_t>; + +// HTTP/2 error codes, RFC 7540 Section 7. +enum SpdyErrorCode : uint32_t { + ERROR_CODE_NO_ERROR = 0x0, + ERROR_CODE_PROTOCOL_ERROR = 0x1, + ERROR_CODE_INTERNAL_ERROR = 0x2, + ERROR_CODE_FLOW_CONTROL_ERROR = 0x3, + ERROR_CODE_SETTINGS_TIMEOUT = 0x4, + ERROR_CODE_STREAM_CLOSED = 0x5, + ERROR_CODE_FRAME_SIZE_ERROR = 0x6, + ERROR_CODE_REFUSED_STREAM = 0x7, + ERROR_CODE_CANCEL = 0x8, + ERROR_CODE_COMPRESSION_ERROR = 0x9, + ERROR_CODE_CONNECT_ERROR = 0xa, + ERROR_CODE_ENHANCE_YOUR_CALM = 0xb, + ERROR_CODE_INADEQUATE_SECURITY = 0xc, + ERROR_CODE_HTTP_1_1_REQUIRED = 0xd, + ERROR_CODE_MAX = ERROR_CODE_HTTP_1_1_REQUIRED +}; + +// A SPDY priority is a number between 0 and 7 (inclusive). +typedef uint8_t SpdyPriority; + +// Lowest and Highest here refer to SPDY priorities as described in +// https://www.chromium.org/spdy/spdy-protocol/spdy-protocol-draft3-1#TOC-2.3.3-Stream-priority +const SpdyPriority kV3HighestPriority = 0; +const SpdyPriority kV3LowestPriority = 7; + +// Returns SPDY 3.x priority value clamped to the valid range of [0, 7]. +SPDY_EXPORT_PRIVATE SpdyPriority ClampSpdy3Priority(SpdyPriority priority); + +// HTTP/2 stream weights are integers in range [1, 256], as specified in RFC +// 7540 section 5.3.2. Default stream weight is defined in section 5.3.5. +const int kHttp2MinStreamWeight = 1; +const int kHttp2MaxStreamWeight = 256; +const int kHttp2DefaultStreamWeight = 16; + +// Returns HTTP/2 weight clamped to the valid range of [1, 256]. +SPDY_EXPORT_PRIVATE int ClampHttp2Weight(int weight); + +// Maps SPDY 3.x priority value in range [0, 7] to HTTP/2 weight value in range +// [1, 256], where priority 0 (i.e. highest precedence) corresponds to maximum +// weight 256 and priority 7 (lowest precedence) corresponds to minimum weight +// 1. +SPDY_EXPORT_PRIVATE int Spdy3PriorityToHttp2Weight(SpdyPriority priority); + +// Maps HTTP/2 weight value in range [1, 256] to SPDY 3.x priority value in +// range [0, 7], where minimum weight 1 corresponds to priority 7 (lowest +// precedence) and maximum weight 256 corresponds to priority 0 (highest +// precedence). +SPDY_EXPORT_PRIVATE SpdyPriority Http2WeightToSpdy3Priority(int weight); + +// Reserved ID for root stream of HTTP/2 stream dependency tree, as specified +// in RFC 7540 section 5.3.1. +const unsigned int kHttp2RootStreamId = 0; + +typedef uint64_t SpdyPingId; + +// Returns true if a given on-the-wire enumeration of a frame type is defined +// in a standardized HTTP/2 specification, false otherwise. +SPDY_EXPORT_PRIVATE bool IsDefinedFrameType(uint8_t frame_type_field); + +// Parses a frame type from an on-the-wire enumeration. +// Behavior is undefined for invalid frame type fields; consumers should first +// use IsValidFrameType() to verify validity of frame type fields. +SPDY_EXPORT_PRIVATE SpdyFrameType ParseFrameType(uint8_t frame_type_field); + +// Serializes a frame type to the on-the-wire value. +SPDY_EXPORT_PRIVATE uint8_t SerializeFrameType(SpdyFrameType frame_type); + +// (HTTP/2) All standard frame types except WINDOW_UPDATE are +// (stream-specific xor connection-level). Returns false iff we know +// the given frame type does not align with the given streamID. +SPDY_EXPORT_PRIVATE bool IsValidHTTP2FrameStreamId( + SpdyStreamId current_frame_stream_id, + SpdyFrameType frame_type_field); + +// Serialize |frame_type| to string for logging/debugging. +const char* FrameTypeToString(SpdyFrameType frame_type); + +// If |wire_setting_id| is the on-the-wire representation of a defined SETTINGS +// parameter, parse it to |*setting_id| and return true. +SPDY_EXPORT_PRIVATE bool ParseSettingsId(SpdySettingsId wire_setting_id, + SpdyKnownSettingsId* setting_id); + +// Returns a string representation of the |id| for logging/debugging. Returns +// the |id| prefixed with "SETTINGS_UNKNOWN_" for unknown SETTINGS IDs. To parse +// the |id| into a SpdyKnownSettingsId (if applicable), use ParseSettingsId(). +SPDY_EXPORT_PRIVATE SpdyString SettingsIdToString(SpdySettingsId id); + +// Parse |wire_error_code| to a SpdyErrorCode. +// Treat unrecognized error codes as INTERNAL_ERROR +// as recommended by the HTTP/2 specification. +SPDY_EXPORT_PRIVATE SpdyErrorCode ParseErrorCode(uint32_t wire_error_code); + +// Serialize RST_STREAM or GOAWAY frame error code to string +// for logging/debugging. +const char* ErrorCodeToString(SpdyErrorCode error_code); + +// Minimum size of a frame, in octets. +const size_t kFrameMinimumSize = kFrameHeaderSize; + +// Minimum frame size for variable size frame types (includes mandatory fields), +// frame size for fixed size frames, in octets. + +const size_t kDataFrameMinimumSize = kFrameHeaderSize; +const size_t kHeadersFrameMinimumSize = kFrameHeaderSize; +// PRIORITY frame has stream_dependency (4 octets) and weight (1 octet) fields. +const size_t kPriorityFrameSize = kFrameHeaderSize + 5; +// RST_STREAM frame has error_code (4 octets) field. +const size_t kRstStreamFrameSize = kFrameHeaderSize + 4; +const size_t kSettingsFrameMinimumSize = kFrameHeaderSize; +const size_t kSettingsOneSettingSize = + sizeof(uint32_t) + sizeof(SpdySettingsId); +// PUSH_PROMISE frame has promised_stream_id (4 octet) field. +const size_t kPushPromiseFrameMinimumSize = kFrameHeaderSize + 4; +// PING frame has opaque_bytes (8 octet) field. +const size_t kPingFrameSize = kFrameHeaderSize + 8; +// GOAWAY frame has last_stream_id (4 octet) and error_code (4 octet) fields. +const size_t kGoawayFrameMinimumSize = kFrameHeaderSize + 8; +// WINDOW_UPDATE frame has window_size_increment (4 octet) field. +const size_t kWindowUpdateFrameSize = kFrameHeaderSize + 4; +const size_t kContinuationFrameMinimumSize = kFrameHeaderSize; +// ALTSVC frame has origin_len (2 octets) field. +const size_t kGetAltSvcFrameMinimumSize = kFrameHeaderSize + 2; + +// Maximum possible configurable size of a frame in octets. +const size_t kMaxFrameSizeLimit = kSpdyMaxFrameSizeLimit + kFrameHeaderSize; +// Size of a header block size field. +const size_t kSizeOfSizeField = sizeof(uint32_t); +// Per-header overhead for block size accounting in bytes. +const size_t kPerHeaderOverhead = 32; +// Initial window size for a stream in bytes. +const int32_t kInitialStreamWindowSize = 64 * 1024 - 1; +// Initial window size for a session in bytes. +const int32_t kInitialSessionWindowSize = 64 * 1024 - 1; +// The NPN string for HTTP2, "h2". +extern const char* const kHttp2Npn; +// An estimate size of the HPACK overhead for each header field. 1 bytes for +// indexed literal, 1 bytes for key literal and length encoding, and 2 bytes for +// value literal and length encoding. +const size_t kPerHeaderHpackOverhead = 4; + +// Names of pseudo-headers defined for HTTP/2 requests. +SPDY_EXPORT_PRIVATE extern const char* const kHttp2AuthorityHeader; +SPDY_EXPORT_PRIVATE extern const char* const kHttp2MethodHeader; +SPDY_EXPORT_PRIVATE extern const char* const kHttp2PathHeader; +SPDY_EXPORT_PRIVATE extern const char* const kHttp2SchemeHeader; +SPDY_EXPORT_PRIVATE extern const char* const kHttp2ProtocolHeader; + +// Name of pseudo-header defined for HTTP/2 responses. +SPDY_EXPORT_PRIVATE extern const char* const kHttp2StatusHeader; + +SPDY_EXPORT_PRIVATE size_t GetNumberRequiredContinuationFrames(size_t size); + +// Variant type (i.e. tagged union) that is either a SPDY 3.x priority value, +// or else an HTTP/2 stream dependency tuple {parent stream ID, weight, +// exclusive bit}. Templated to allow for use by QUIC code; SPDY and HTTP/2 +// code should use the concrete type instantiation SpdyStreamPrecedence. +template <typename StreamIdType> +class StreamPrecedence { + public: + // Constructs instance that is a SPDY 3.x priority. Clamps priority value to + // the valid range [0, 7]. + explicit StreamPrecedence(SpdyPriority priority) + : is_spdy3_priority_(true), + spdy3_priority_(ClampSpdy3Priority(priority)) {} + + // Constructs instance that is an HTTP/2 stream weight, parent stream ID, and + // exclusive bit. Clamps stream weight to the valid range [1, 256]. + StreamPrecedence(StreamIdType parent_id, int weight, bool is_exclusive) + : is_spdy3_priority_(false), + http2_stream_dependency_{parent_id, ClampHttp2Weight(weight), + is_exclusive} {} + + // Intentionally copyable, to support pass by value. + StreamPrecedence(const StreamPrecedence& other) = default; + StreamPrecedence& operator=(const StreamPrecedence& other) = default; + + // Returns true if this instance is a SPDY 3.x priority, or false if this + // instance is an HTTP/2 stream dependency. + bool is_spdy3_priority() const { return is_spdy3_priority_; } + + // Returns SPDY 3.x priority value. If |is_spdy3_priority()| is true, this is + // the value provided at construction, clamped to the legal priority + // range. Otherwise, it is the HTTP/2 stream weight mapped to a SPDY 3.x + // priority value, where minimum weight 1 corresponds to priority 7 (lowest + // precedence) and maximum weight 256 corresponds to priority 0 (highest + // precedence). + SpdyPriority spdy3_priority() const { + return is_spdy3_priority_ + ? spdy3_priority_ + : Http2WeightToSpdy3Priority(http2_stream_dependency_.weight); + } + + // Returns HTTP/2 parent stream ID. If |is_spdy3_priority()| is false, this is + // the value provided at construction, otherwise it is |kHttp2RootStreamId|. + StreamIdType parent_id() const { + return is_spdy3_priority_ ? kHttp2RootStreamId + : http2_stream_dependency_.parent_id; + } + + // Returns HTTP/2 stream weight. If |is_spdy3_priority()| is false, this is + // the value provided at construction, clamped to the legal weight + // range. Otherwise, it is the SPDY 3.x priority value mapped to an HTTP/2 + // stream weight, where priority 0 (i.e. highest precedence) corresponds to + // maximum weight 256 and priority 7 (lowest precedence) corresponds to + // minimum weight 1. + int weight() const { + return is_spdy3_priority_ ? Spdy3PriorityToHttp2Weight(spdy3_priority_) + : http2_stream_dependency_.weight; + } + + // Returns HTTP/2 parent stream exclusivity. If |is_spdy3_priority()| is + // false, this is the value provided at construction, otherwise it is false. + bool is_exclusive() const { + return !is_spdy3_priority_ && http2_stream_dependency_.is_exclusive; + } + + // Facilitates test assertions. + bool operator==(const StreamPrecedence& other) const { + if (is_spdy3_priority()) { + return other.is_spdy3_priority() && + (spdy3_priority() == other.spdy3_priority()); + } else { + return !other.is_spdy3_priority() && (parent_id() == other.parent_id()) && + (weight() == other.weight()) && + (is_exclusive() == other.is_exclusive()); + } + } + + bool operator!=(const StreamPrecedence& other) const { + return !(*this == other); + } + + private: + struct Http2StreamDependency { + StreamIdType parent_id; + int weight; + bool is_exclusive; + }; + + bool is_spdy3_priority_; + union { + SpdyPriority spdy3_priority_; + Http2StreamDependency http2_stream_dependency_; + }; +}; + +typedef StreamPrecedence<SpdyStreamId> SpdyStreamPrecedence; + +class SpdyFrameVisitor; + +// Intermediate representation for HTTP2 frames. +class SPDY_EXPORT_PRIVATE SpdyFrameIR { + public: + virtual ~SpdyFrameIR() {} + + virtual void Visit(SpdyFrameVisitor* visitor) const = 0; + virtual SpdyFrameType frame_type() const = 0; + SpdyStreamId stream_id() const { return stream_id_; } + virtual bool fin() const; + // Returns an estimate of the size of the serialized frame, without applying + // compression. May not be exact. + virtual size_t size() const = 0; + + // Returns the number of bytes of flow control window that would be consumed + // by this frame if written to the wire. + virtual int flow_control_window_consumed() const; + + protected: + SpdyFrameIR() : stream_id_(0) {} + explicit SpdyFrameIR(SpdyStreamId stream_id) : stream_id_(stream_id) {} + SpdyFrameIR(const SpdyFrameIR&) = delete; + SpdyFrameIR& operator=(const SpdyFrameIR&) = delete; + + private: + SpdyStreamId stream_id_; +}; + +// Abstract class intended to be inherited by IRs that have the option of a FIN +// flag. +class SPDY_EXPORT_PRIVATE SpdyFrameWithFinIR : public SpdyFrameIR { + public: + ~SpdyFrameWithFinIR() override {} + bool fin() const override; + void set_fin(bool fin) { fin_ = fin; } + + protected: + explicit SpdyFrameWithFinIR(SpdyStreamId stream_id) + : SpdyFrameIR(stream_id), fin_(false) {} + SpdyFrameWithFinIR(const SpdyFrameWithFinIR&) = delete; + SpdyFrameWithFinIR& operator=(const SpdyFrameWithFinIR&) = delete; + + private: + bool fin_; +}; + +// Abstract class intended to be inherited by IRs that contain a header +// block. Implies SpdyFrameWithFinIR. +class SPDY_EXPORT_PRIVATE SpdyFrameWithHeaderBlockIR + : public SpdyFrameWithFinIR { + public: + ~SpdyFrameWithHeaderBlockIR() override; + + const SpdyHeaderBlock& header_block() const { return header_block_; } + void set_header_block(SpdyHeaderBlock header_block) { + // Deep copy. + header_block_ = std::move(header_block); + } + void SetHeader(SpdyStringPiece name, SpdyStringPiece value) { + header_block_[name] = value; + } + + protected: + SpdyFrameWithHeaderBlockIR(SpdyStreamId stream_id, + SpdyHeaderBlock header_block); + SpdyFrameWithHeaderBlockIR(const SpdyFrameWithHeaderBlockIR&) = delete; + SpdyFrameWithHeaderBlockIR& operator=(const SpdyFrameWithHeaderBlockIR&) = + delete; + + private: + SpdyHeaderBlock header_block_; +}; + +class SPDY_EXPORT_PRIVATE SpdyDataIR : public SpdyFrameWithFinIR { + public: + // Performs a deep copy on data. + SpdyDataIR(SpdyStreamId stream_id, SpdyStringPiece data); + + // Performs a deep copy on data. + SpdyDataIR(SpdyStreamId stream_id, const char* data); + + // Moves data into data_store_. Makes a copy if passed a non-movable string. + SpdyDataIR(SpdyStreamId stream_id, SpdyString data); + + // Use in conjunction with SetDataShallow() for shallow-copy on data. + explicit SpdyDataIR(SpdyStreamId stream_id); + SpdyDataIR(const SpdyDataIR&) = delete; + SpdyDataIR& operator=(const SpdyDataIR&) = delete; + + ~SpdyDataIR() override; + + const char* data() const { return data_; } + size_t data_len() const { return data_len_; } + + bool padded() const { return padded_; } + + int padding_payload_len() const { return padding_payload_len_; } + + void set_padding_len(int padding_len) { + DCHECK_GT(padding_len, 0); + DCHECK_LE(padding_len, kPaddingSizePerFrame); + padded_ = true; + // The pad field takes one octet on the wire. + padding_payload_len_ = padding_len - 1; + } + + // Deep-copy of data (keep private copy). + void SetDataDeep(SpdyStringPiece data) { + data_store_ = SpdyMakeUnique<SpdyString>(data.data(), data.size()); + data_ = data_store_->data(); + data_len_ = data.size(); + } + + // Shallow-copy of data (do not keep private copy). + void SetDataShallow(SpdyStringPiece data) { + data_store_.reset(); + data_ = data.data(); + data_len_ = data.size(); + } + + // Use this method if we don't have a contiguous buffer and only + // need a length. + void SetDataShallow(size_t len) { + data_store_.reset(); + data_ = nullptr; + data_len_ = len; + } + + void Visit(SpdyFrameVisitor* visitor) const override; + + SpdyFrameType frame_type() const override; + + int flow_control_window_consumed() const override; + + size_t size() const override; + + private: + // Used to store data that this SpdyDataIR should own. + std::unique_ptr<SpdyString> data_store_; + const char* data_; + size_t data_len_; + + bool padded_; + // padding_payload_len_ = desired padding length - len(padding length field). + int padding_payload_len_; +}; + +class SPDY_EXPORT_PRIVATE SpdyRstStreamIR : public SpdyFrameIR { + public: + SpdyRstStreamIR(SpdyStreamId stream_id, SpdyErrorCode error_code); + SpdyRstStreamIR(const SpdyRstStreamIR&) = delete; + SpdyRstStreamIR& operator=(const SpdyRstStreamIR&) = delete; + + ~SpdyRstStreamIR() override; + + SpdyErrorCode error_code() const { return error_code_; } + void set_error_code(SpdyErrorCode error_code) { error_code_ = error_code; } + + void Visit(SpdyFrameVisitor* visitor) const override; + + SpdyFrameType frame_type() const override; + + size_t size() const override; + + private: + SpdyErrorCode error_code_; +}; + +class SPDY_EXPORT_PRIVATE SpdySettingsIR : public SpdyFrameIR { + public: + SpdySettingsIR(); + SpdySettingsIR(const SpdySettingsIR&) = delete; + SpdySettingsIR& operator=(const SpdySettingsIR&) = delete; + ~SpdySettingsIR() override; + + // Overwrites as appropriate. + const SettingsMap& values() const { return values_; } + void AddSetting(SpdySettingsId id, int32_t value) { values_[id] = value; } + + bool is_ack() const { return is_ack_; } + void set_is_ack(bool is_ack) { is_ack_ = is_ack; } + + void Visit(SpdyFrameVisitor* visitor) const override; + + SpdyFrameType frame_type() const override; + + size_t size() const override; + + private: + SettingsMap values_; + bool is_ack_; +}; + +class SPDY_EXPORT_PRIVATE SpdyPingIR : public SpdyFrameIR { + public: + explicit SpdyPingIR(SpdyPingId id) : id_(id), is_ack_(false) {} + SpdyPingIR(const SpdyPingIR&) = delete; + SpdyPingIR& operator=(const SpdyPingIR&) = delete; + SpdyPingId id() const { return id_; } + + bool is_ack() const { return is_ack_; } + void set_is_ack(bool is_ack) { is_ack_ = is_ack; } + + void Visit(SpdyFrameVisitor* visitor) const override; + + SpdyFrameType frame_type() const override; + + size_t size() const override; + + private: + SpdyPingId id_; + bool is_ack_; +}; + +class SPDY_EXPORT_PRIVATE SpdyGoAwayIR : public SpdyFrameIR { + public: + // References description, doesn't copy it, so description must outlast + // this SpdyGoAwayIR. + SpdyGoAwayIR(SpdyStreamId last_good_stream_id, + SpdyErrorCode error_code, + SpdyStringPiece description); + + // References description, doesn't copy it, so description must outlast + // this SpdyGoAwayIR. + SpdyGoAwayIR(SpdyStreamId last_good_stream_id, + SpdyErrorCode error_code, + const char* description); + + // Moves description into description_store_, so caller doesn't need to + // keep description live after constructing this SpdyGoAwayIR. + SpdyGoAwayIR(SpdyStreamId last_good_stream_id, + SpdyErrorCode error_code, + SpdyString description); + SpdyGoAwayIR(const SpdyGoAwayIR&) = delete; + SpdyGoAwayIR& operator=(const SpdyGoAwayIR&) = delete; + + ~SpdyGoAwayIR() override; + + SpdyStreamId last_good_stream_id() const { return last_good_stream_id_; } + void set_last_good_stream_id(SpdyStreamId last_good_stream_id) { + DCHECK_EQ(0u, last_good_stream_id & ~kStreamIdMask); + last_good_stream_id_ = last_good_stream_id; + } + SpdyErrorCode error_code() const { return error_code_; } + void set_error_code(SpdyErrorCode error_code) { + // TODO(hkhalil): Check valid ranges of error_code? + error_code_ = error_code; + } + + const SpdyStringPiece& description() const { return description_; } + + void Visit(SpdyFrameVisitor* visitor) const override; + + SpdyFrameType frame_type() const override; + + size_t size() const override; + + private: + SpdyStreamId last_good_stream_id_; + SpdyErrorCode error_code_; + const SpdyString description_store_; + const SpdyStringPiece description_; +}; + +class SPDY_EXPORT_PRIVATE SpdyHeadersIR : public SpdyFrameWithHeaderBlockIR { + public: + explicit SpdyHeadersIR(SpdyStreamId stream_id) + : SpdyHeadersIR(stream_id, SpdyHeaderBlock()) {} + SpdyHeadersIR(SpdyStreamId stream_id, SpdyHeaderBlock header_block) + : SpdyFrameWithHeaderBlockIR(stream_id, std::move(header_block)) {} + SpdyHeadersIR(const SpdyHeadersIR&) = delete; + SpdyHeadersIR& operator=(const SpdyHeadersIR&) = delete; + + void Visit(SpdyFrameVisitor* visitor) const override; + + SpdyFrameType frame_type() const override; + + size_t size() const override; + + bool has_priority() const { return has_priority_; } + void set_has_priority(bool has_priority) { has_priority_ = has_priority; } + int weight() const { return weight_; } + void set_weight(int weight) { weight_ = weight; } + SpdyStreamId parent_stream_id() const { return parent_stream_id_; } + void set_parent_stream_id(SpdyStreamId id) { parent_stream_id_ = id; } + bool exclusive() const { return exclusive_; } + void set_exclusive(bool exclusive) { exclusive_ = exclusive; } + bool padded() const { return padded_; } + int padding_payload_len() const { return padding_payload_len_; } + void set_padding_len(int padding_len) { + DCHECK_GT(padding_len, 0); + DCHECK_LE(padding_len, kPaddingSizePerFrame); + padded_ = true; + // The pad field takes one octet on the wire. + padding_payload_len_ = padding_len - 1; + } + + private: + bool has_priority_ = false; + int weight_ = kHttp2DefaultStreamWeight; + SpdyStreamId parent_stream_id_ = 0; + bool exclusive_ = false; + bool padded_ = false; + int padding_payload_len_ = 0; +}; + +class SPDY_EXPORT_PRIVATE SpdyWindowUpdateIR : public SpdyFrameIR { + public: + SpdyWindowUpdateIR(SpdyStreamId stream_id, int32_t delta) + : SpdyFrameIR(stream_id) { + set_delta(delta); + } + SpdyWindowUpdateIR(const SpdyWindowUpdateIR&) = delete; + SpdyWindowUpdateIR& operator=(const SpdyWindowUpdateIR&) = delete; + + int32_t delta() const { return delta_; } + void set_delta(int32_t delta) { + DCHECK_LE(0, delta); + DCHECK_LE(delta, kSpdyMaximumWindowSize); + delta_ = delta; + } + + void Visit(SpdyFrameVisitor* visitor) const override; + + SpdyFrameType frame_type() const override; + + size_t size() const override; + + private: + int32_t delta_; +}; + +class SPDY_EXPORT_PRIVATE SpdyPushPromiseIR + : public SpdyFrameWithHeaderBlockIR { + public: + SpdyPushPromiseIR(SpdyStreamId stream_id, SpdyStreamId promised_stream_id) + : SpdyPushPromiseIR(stream_id, promised_stream_id, SpdyHeaderBlock()) {} + SpdyPushPromiseIR(SpdyStreamId stream_id, + SpdyStreamId promised_stream_id, + SpdyHeaderBlock header_block) + : SpdyFrameWithHeaderBlockIR(stream_id, std::move(header_block)), + promised_stream_id_(promised_stream_id), + padded_(false), + padding_payload_len_(0) {} + SpdyPushPromiseIR(const SpdyPushPromiseIR&) = delete; + SpdyPushPromiseIR& operator=(const SpdyPushPromiseIR&) = delete; + SpdyStreamId promised_stream_id() const { return promised_stream_id_; } + + void Visit(SpdyFrameVisitor* visitor) const override; + + SpdyFrameType frame_type() const override; + + size_t size() const override; + + bool padded() const { return padded_; } + int padding_payload_len() const { return padding_payload_len_; } + void set_padding_len(int padding_len) { + DCHECK_GT(padding_len, 0); + DCHECK_LE(padding_len, kPaddingSizePerFrame); + padded_ = true; + // The pad field takes one octet on the wire. + padding_payload_len_ = padding_len - 1; + } + + private: + SpdyStreamId promised_stream_id_; + + bool padded_; + int padding_payload_len_; +}; + +class SPDY_EXPORT_PRIVATE SpdyContinuationIR : public SpdyFrameIR { + public: + explicit SpdyContinuationIR(SpdyStreamId stream_id); + SpdyContinuationIR(const SpdyContinuationIR&) = delete; + SpdyContinuationIR& operator=(const SpdyContinuationIR&) = delete; + ~SpdyContinuationIR() override; + + void Visit(SpdyFrameVisitor* visitor) const override; + + SpdyFrameType frame_type() const override; + + bool end_headers() const { return end_headers_; } + void set_end_headers(bool end_headers) { end_headers_ = end_headers; } + const SpdyString& encoding() const { return *encoding_; } + void take_encoding(std::unique_ptr<SpdyString> encoding) { + encoding_ = std::move(encoding); + } + size_t size() const override; + + private: + std::unique_ptr<SpdyString> encoding_; + bool end_headers_; +}; + +class SPDY_EXPORT_PRIVATE SpdyAltSvcIR : public SpdyFrameIR { + public: + explicit SpdyAltSvcIR(SpdyStreamId stream_id); + SpdyAltSvcIR(const SpdyAltSvcIR&) = delete; + SpdyAltSvcIR& operator=(const SpdyAltSvcIR&) = delete; + ~SpdyAltSvcIR() override; + + SpdyString origin() const { return origin_; } + const SpdyAltSvcWireFormat::AlternativeServiceVector& altsvc_vector() const { + return altsvc_vector_; + } + + void set_origin(SpdyString origin) { origin_ = std::move(origin); } + void add_altsvc(const SpdyAltSvcWireFormat::AlternativeService& altsvc) { + altsvc_vector_.push_back(altsvc); + } + + void Visit(SpdyFrameVisitor* visitor) const override; + + SpdyFrameType frame_type() const override; + + size_t size() const override; + + private: + SpdyString origin_; + SpdyAltSvcWireFormat::AlternativeServiceVector altsvc_vector_; +}; + +class SPDY_EXPORT_PRIVATE SpdyPriorityIR : public SpdyFrameIR { + public: + SpdyPriorityIR(SpdyStreamId stream_id, + SpdyStreamId parent_stream_id, + int weight, + bool exclusive) + : SpdyFrameIR(stream_id), + parent_stream_id_(parent_stream_id), + weight_(weight), + exclusive_(exclusive) {} + SpdyPriorityIR(const SpdyPriorityIR&) = delete; + SpdyPriorityIR& operator=(const SpdyPriorityIR&) = delete; + SpdyStreamId parent_stream_id() const { return parent_stream_id_; } + int weight() const { return weight_; } + bool exclusive() const { return exclusive_; } + + void Visit(SpdyFrameVisitor* visitor) const override; + + SpdyFrameType frame_type() const override; + + size_t size() const override; + + private: + SpdyStreamId parent_stream_id_; + int weight_; + bool exclusive_; +}; + +// Represents a frame of unrecognized type. +class SPDY_EXPORT_PRIVATE SpdyUnknownIR : public SpdyFrameIR { + public: + SpdyUnknownIR(SpdyStreamId stream_id, + uint8_t type, + uint8_t flags, + SpdyString payload) + : SpdyFrameIR(stream_id), + type_(type), + flags_(flags), + length_(payload.size()), + payload_(std::move(payload)) {} + SpdyUnknownIR(const SpdyUnknownIR&) = delete; + SpdyUnknownIR& operator=(const SpdyUnknownIR&) = delete; + uint8_t type() const { return type_; } + uint8_t flags() const { return flags_; } + size_t length() const { return length_; } + const SpdyString& payload() const { return payload_; } + + void Visit(SpdyFrameVisitor* visitor) const override; + + SpdyFrameType frame_type() const override; + + int flow_control_window_consumed() const override; + + size_t size() const override; + + protected: + // Allows subclasses to overwrite the default payload length. + void set_length(size_t length) { length_ = length; } + + private: + uint8_t type_; + uint8_t flags_; + size_t length_; + const SpdyString payload_; +}; + +class SPDY_EXPORT_PRIVATE SpdySerializedFrame { + public: + SpdySerializedFrame() + : frame_(const_cast<char*>("")), size_(0), owns_buffer_(false) {} + + // Create a valid SpdySerializedFrame using a pre-created buffer. + // If |owns_buffer| is true, this class takes ownership of the buffer and will + // delete it on cleanup. The buffer must have been created using new char[]. + // If |owns_buffer| is false, the caller retains ownership of the buffer and + // is responsible for making sure the buffer outlives this frame. In other + // words, this class does NOT create a copy of the buffer. + SpdySerializedFrame(char* data, size_t size, bool owns_buffer) + : frame_(data), size_(size), owns_buffer_(owns_buffer) {} + + SpdySerializedFrame(SpdySerializedFrame&& other) + : frame_(other.frame_), + size_(other.size_), + owns_buffer_(other.owns_buffer_) { + // |other| is no longer responsible for the buffer. + other.owns_buffer_ = false; + } + SpdySerializedFrame(const SpdySerializedFrame&) = delete; + SpdySerializedFrame& operator=(const SpdySerializedFrame&) = delete; + + SpdySerializedFrame& operator=(SpdySerializedFrame&& other) { + // Free buffer if necessary. + if (owns_buffer_) { + delete[] frame_; + } + // Take over |other|. + frame_ = other.frame_; + size_ = other.size_; + owns_buffer_ = other.owns_buffer_; + // |other| is no longer responsible for the buffer. + other.owns_buffer_ = false; + return *this; + } + + ~SpdySerializedFrame() { + if (owns_buffer_) { + delete[] frame_; + } + } + + // Provides access to the frame bytes, which is a buffer containing the frame + // packed as expected for sending over the wire. + char* data() const { return frame_; } + + // Returns the actual size of the underlying buffer. + size_t size() const { return size_; } + + // Returns a buffer containing the contents of the frame, of which the caller + // takes ownership, and clears this SpdySerializedFrame. + char* ReleaseBuffer() { + char* buffer; + if (owns_buffer_) { + // If the buffer is owned, relinquish ownership to the caller. + buffer = frame_; + owns_buffer_ = false; + } else { + // Otherwise, we need to make a copy to give to the caller. + buffer = new char[size_]; + memcpy(buffer, frame_, size_); + } + *this = SpdySerializedFrame(); + return buffer; + } + + // Returns the estimate of dynamically allocated memory in bytes. + size_t EstimateMemoryUsage() const { return owns_buffer_ ? size_ : 0; } + + protected: + char* frame_; + + private: + size_t size_; + bool owns_buffer_; +}; + +// This interface is for classes that want to process SpdyFrameIRs without +// having to know what type they are. An instance of this interface can be +// passed to a SpdyFrameIR's Visit method, and the appropriate type-specific +// method of this class will be called. +class SPDY_EXPORT_PRIVATE SpdyFrameVisitor { + public: + virtual void VisitRstStream(const SpdyRstStreamIR& rst_stream) = 0; + virtual void VisitSettings(const SpdySettingsIR& settings) = 0; + virtual void VisitPing(const SpdyPingIR& ping) = 0; + virtual void VisitGoAway(const SpdyGoAwayIR& goaway) = 0; + virtual void VisitHeaders(const SpdyHeadersIR& headers) = 0; + virtual void VisitWindowUpdate(const SpdyWindowUpdateIR& window_update) = 0; + virtual void VisitPushPromise(const SpdyPushPromiseIR& push_promise) = 0; + virtual void VisitContinuation(const SpdyContinuationIR& continuation) = 0; + virtual void VisitAltSvc(const SpdyAltSvcIR& altsvc) = 0; + virtual void VisitPriority(const SpdyPriorityIR& priority) = 0; + virtual void VisitData(const SpdyDataIR& data) = 0; + virtual void VisitUnknown(const SpdyUnknownIR& unknown) { + // TODO(birenroy): make abstract. + } + + protected: + SpdyFrameVisitor() {} + SpdyFrameVisitor(const SpdyFrameVisitor&) = delete; + SpdyFrameVisitor& operator=(const SpdyFrameVisitor&) = delete; + virtual ~SpdyFrameVisitor() {} +}; + +// Optionally, and in addition to SpdyFramerVisitorInterface, a class supporting +// SpdyFramerDebugVisitorInterface may be used in conjunction with SpdyFramer in +// order to extract debug/internal information about the SpdyFramer as it +// operates. +// +// Most HTTP2 implementations need not bother with this interface at all. +class SPDY_EXPORT_PRIVATE SpdyFramerDebugVisitorInterface { + public: + virtual ~SpdyFramerDebugVisitorInterface() {} + + // Called after compressing a frame with a payload of + // a list of name-value pairs. + // |payload_len| is the uncompressed payload size. + // |frame_len| is the compressed frame size. + virtual void OnSendCompressedFrame(SpdyStreamId stream_id, + SpdyFrameType type, + size_t payload_len, + size_t frame_len) {} + + // Called when a frame containing a compressed payload of + // name-value pairs is received. + // |frame_len| is the compressed frame size. + virtual void OnReceiveCompressedFrame(SpdyStreamId stream_id, + SpdyFrameType type, + size_t frame_len) {} +}; + +// Calculates the number of bytes required to serialize a SpdyHeadersIR, not +// including the bytes to be used for the encoded header set. +size_t GetHeaderFrameSizeSansBlock(const SpdyHeadersIR& header_ir); + +// Calculates the number of bytes required to serialize a SpdyPushPromiseIR, +// not including the bytes to be used for the encoded header set. +size_t GetPushPromiseFrameSizeSansBlock( + const SpdyPushPromiseIR& push_promise_ir); + +} // namespace spdy + +#endif // QUICHE_SPDY_CORE_SPDY_PROTOCOL_H_ diff --git a/chromium/net/third_party/quiche/src/spdy/core/spdy_protocol_test.cc b/chromium/net/third_party/quiche/src/spdy/core/spdy_protocol_test.cc new file mode 100644 index 00000000000..09a56cbe4f4 --- /dev/null +++ b/chromium/net/third_party/quiche/src/spdy/core/spdy_protocol_test.cc @@ -0,0 +1,270 @@ +// Copyright (c) 2012 The Chromium 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/spdy/core/spdy_protocol.h" + +#include <iostream> +#include <limits> +#include <memory> + +#include "testing/gtest/include/gtest/gtest.h" +#include "net/third_party/quiche/src/spdy/core/spdy_bitmasks.h" +#include "net/third_party/quiche/src/spdy/core/spdy_test_utils.h" + +namespace spdy { + +std::ostream& operator<<(std::ostream& os, + const SpdyStreamPrecedence precedence) { + if (precedence.is_spdy3_priority()) { + os << "SpdyStreamPrecedence[spdy3_priority=" << precedence.spdy3_priority() + << "]"; + } else { + os << "SpdyStreamPrecedence[parent_id=" << precedence.parent_id() + << ", weight=" << precedence.weight() + << ", is_exclusive=" << precedence.is_exclusive() << "]"; + } + return os; +} + +namespace test { + +TEST(SpdyProtocolTest, ClampSpdy3Priority) { + EXPECT_SPDY_BUG(EXPECT_EQ(7, ClampSpdy3Priority(8)), "Invalid priority: 8"); + EXPECT_EQ(kV3LowestPriority, ClampSpdy3Priority(kV3LowestPriority)); + EXPECT_EQ(kV3HighestPriority, ClampSpdy3Priority(kV3HighestPriority)); +} + +TEST(SpdyProtocolTest, ClampHttp2Weight) { + EXPECT_SPDY_BUG(EXPECT_EQ(kHttp2MinStreamWeight, ClampHttp2Weight(0)), + "Invalid weight: 0"); + EXPECT_SPDY_BUG(EXPECT_EQ(kHttp2MaxStreamWeight, ClampHttp2Weight(300)), + "Invalid weight: 300"); + EXPECT_EQ(kHttp2MinStreamWeight, ClampHttp2Weight(kHttp2MinStreamWeight)); + EXPECT_EQ(kHttp2MaxStreamWeight, ClampHttp2Weight(kHttp2MaxStreamWeight)); +} + +TEST(SpdyProtocolTest, Spdy3PriorityToHttp2Weight) { + EXPECT_EQ(256, Spdy3PriorityToHttp2Weight(0)); + EXPECT_EQ(220, Spdy3PriorityToHttp2Weight(1)); + EXPECT_EQ(183, Spdy3PriorityToHttp2Weight(2)); + EXPECT_EQ(147, Spdy3PriorityToHttp2Weight(3)); + EXPECT_EQ(110, Spdy3PriorityToHttp2Weight(4)); + EXPECT_EQ(74, Spdy3PriorityToHttp2Weight(5)); + EXPECT_EQ(37, Spdy3PriorityToHttp2Weight(6)); + EXPECT_EQ(1, Spdy3PriorityToHttp2Weight(7)); +} + +TEST(SpdyProtocolTest, Http2WeightToSpdy3Priority) { + EXPECT_EQ(0u, Http2WeightToSpdy3Priority(256)); + EXPECT_EQ(0u, Http2WeightToSpdy3Priority(221)); + EXPECT_EQ(1u, Http2WeightToSpdy3Priority(220)); + EXPECT_EQ(1u, Http2WeightToSpdy3Priority(184)); + EXPECT_EQ(2u, Http2WeightToSpdy3Priority(183)); + EXPECT_EQ(2u, Http2WeightToSpdy3Priority(148)); + EXPECT_EQ(3u, Http2WeightToSpdy3Priority(147)); + EXPECT_EQ(3u, Http2WeightToSpdy3Priority(111)); + EXPECT_EQ(4u, Http2WeightToSpdy3Priority(110)); + EXPECT_EQ(4u, Http2WeightToSpdy3Priority(75)); + EXPECT_EQ(5u, Http2WeightToSpdy3Priority(74)); + EXPECT_EQ(5u, Http2WeightToSpdy3Priority(38)); + EXPECT_EQ(6u, Http2WeightToSpdy3Priority(37)); + EXPECT_EQ(6u, Http2WeightToSpdy3Priority(2)); + EXPECT_EQ(7u, Http2WeightToSpdy3Priority(1)); +} + +TEST(SpdyProtocolTest, IsValidHTTP2FrameStreamId) { + // Stream-specific frames must have non-zero stream ids + EXPECT_TRUE(IsValidHTTP2FrameStreamId(1, SpdyFrameType::DATA)); + EXPECT_FALSE(IsValidHTTP2FrameStreamId(0, SpdyFrameType::DATA)); + EXPECT_TRUE(IsValidHTTP2FrameStreamId(1, SpdyFrameType::HEADERS)); + EXPECT_FALSE(IsValidHTTP2FrameStreamId(0, SpdyFrameType::HEADERS)); + EXPECT_TRUE(IsValidHTTP2FrameStreamId(1, SpdyFrameType::PRIORITY)); + EXPECT_FALSE(IsValidHTTP2FrameStreamId(0, SpdyFrameType::PRIORITY)); + EXPECT_TRUE(IsValidHTTP2FrameStreamId(1, SpdyFrameType::RST_STREAM)); + EXPECT_FALSE(IsValidHTTP2FrameStreamId(0, SpdyFrameType::RST_STREAM)); + EXPECT_TRUE(IsValidHTTP2FrameStreamId(1, SpdyFrameType::CONTINUATION)); + EXPECT_FALSE(IsValidHTTP2FrameStreamId(0, SpdyFrameType::CONTINUATION)); + EXPECT_TRUE(IsValidHTTP2FrameStreamId(1, SpdyFrameType::PUSH_PROMISE)); + EXPECT_FALSE(IsValidHTTP2FrameStreamId(0, SpdyFrameType::PUSH_PROMISE)); + + // Connection-level frames must have zero stream ids + EXPECT_FALSE(IsValidHTTP2FrameStreamId(1, SpdyFrameType::GOAWAY)); + EXPECT_TRUE(IsValidHTTP2FrameStreamId(0, SpdyFrameType::GOAWAY)); + EXPECT_FALSE(IsValidHTTP2FrameStreamId(1, SpdyFrameType::SETTINGS)); + EXPECT_TRUE(IsValidHTTP2FrameStreamId(0, SpdyFrameType::SETTINGS)); + EXPECT_FALSE(IsValidHTTP2FrameStreamId(1, SpdyFrameType::PING)); + EXPECT_TRUE(IsValidHTTP2FrameStreamId(0, SpdyFrameType::PING)); + + // Frames that are neither stream-specific nor connection-level + // should not have their stream id declared invalid + EXPECT_TRUE(IsValidHTTP2FrameStreamId(1, SpdyFrameType::WINDOW_UPDATE)); + EXPECT_TRUE(IsValidHTTP2FrameStreamId(0, SpdyFrameType::WINDOW_UPDATE)); +} + +TEST(SpdyProtocolTest, ParseSettingsId) { + SpdyKnownSettingsId setting_id; + EXPECT_FALSE(ParseSettingsId(0, &setting_id)); + EXPECT_TRUE(ParseSettingsId(1, &setting_id)); + EXPECT_EQ(SETTINGS_HEADER_TABLE_SIZE, setting_id); + EXPECT_TRUE(ParseSettingsId(2, &setting_id)); + EXPECT_EQ(SETTINGS_ENABLE_PUSH, setting_id); + EXPECT_TRUE(ParseSettingsId(3, &setting_id)); + EXPECT_EQ(SETTINGS_MAX_CONCURRENT_STREAMS, setting_id); + EXPECT_TRUE(ParseSettingsId(4, &setting_id)); + EXPECT_EQ(SETTINGS_INITIAL_WINDOW_SIZE, setting_id); + EXPECT_TRUE(ParseSettingsId(5, &setting_id)); + EXPECT_EQ(SETTINGS_MAX_FRAME_SIZE, setting_id); + EXPECT_TRUE(ParseSettingsId(6, &setting_id)); + EXPECT_EQ(SETTINGS_MAX_HEADER_LIST_SIZE, setting_id); + EXPECT_FALSE(ParseSettingsId(7, &setting_id)); + EXPECT_TRUE(ParseSettingsId(8, &setting_id)); + EXPECT_EQ(SETTINGS_ENABLE_CONNECT_PROTOCOL, setting_id); + EXPECT_FALSE(ParseSettingsId(9, &setting_id)); + EXPECT_FALSE(ParseSettingsId(0xFF44, &setting_id)); + EXPECT_TRUE(ParseSettingsId(0xFF45, &setting_id)); + EXPECT_EQ(SETTINGS_EXPERIMENT_SCHEDULER, setting_id); + EXPECT_FALSE(ParseSettingsId(0xFF46, &setting_id)); +} + +TEST(SpdyProtocolTest, SettingsIdToString) { + struct { + SpdySettingsId setting_id; + const SpdyString expected_string; + } test_cases[] = { + {0, "SETTINGS_UNKNOWN_0"}, + {SETTINGS_HEADER_TABLE_SIZE, "SETTINGS_HEADER_TABLE_SIZE"}, + {SETTINGS_ENABLE_PUSH, "SETTINGS_ENABLE_PUSH"}, + {SETTINGS_MAX_CONCURRENT_STREAMS, "SETTINGS_MAX_CONCURRENT_STREAMS"}, + {SETTINGS_INITIAL_WINDOW_SIZE, "SETTINGS_INITIAL_WINDOW_SIZE"}, + {SETTINGS_MAX_FRAME_SIZE, "SETTINGS_MAX_FRAME_SIZE"}, + {SETTINGS_MAX_HEADER_LIST_SIZE, "SETTINGS_MAX_HEADER_LIST_SIZE"}, + {7, "SETTINGS_UNKNOWN_7"}, + {SETTINGS_ENABLE_CONNECT_PROTOCOL, "SETTINGS_ENABLE_CONNECT_PROTOCOL"}, + {9, "SETTINGS_UNKNOWN_9"}, + {0xFF44, "SETTINGS_UNKNOWN_ff44"}, + {0xFF45, "SETTINGS_EXPERIMENT_SCHEDULER"}, + {0xFF46, "SETTINGS_UNKNOWN_ff46"}}; + for (auto test_case : test_cases) { + EXPECT_EQ(test_case.expected_string, + SettingsIdToString(test_case.setting_id)); + } +} + +TEST(SpdyStreamPrecedenceTest, Basic) { + SpdyStreamPrecedence spdy3_prec(2); + EXPECT_TRUE(spdy3_prec.is_spdy3_priority()); + EXPECT_EQ(2, spdy3_prec.spdy3_priority()); + EXPECT_EQ(kHttp2RootStreamId, spdy3_prec.parent_id()); + EXPECT_EQ(Spdy3PriorityToHttp2Weight(2), spdy3_prec.weight()); + EXPECT_FALSE(spdy3_prec.is_exclusive()); + + for (bool is_exclusive : {true, false}) { + SpdyStreamPrecedence h2_prec(7, 123, is_exclusive); + EXPECT_FALSE(h2_prec.is_spdy3_priority()); + EXPECT_EQ(Http2WeightToSpdy3Priority(123), h2_prec.spdy3_priority()); + EXPECT_EQ(7u, h2_prec.parent_id()); + EXPECT_EQ(123, h2_prec.weight()); + EXPECT_EQ(is_exclusive, h2_prec.is_exclusive()); + } +} + +TEST(SpdyStreamPrecedenceTest, Clamping) { + EXPECT_SPDY_BUG(EXPECT_EQ(7, SpdyStreamPrecedence(8).spdy3_priority()), + "Invalid priority: 8"); + EXPECT_SPDY_BUG(EXPECT_EQ(kHttp2MinStreamWeight, + SpdyStreamPrecedence(3, 0, false).weight()), + "Invalid weight: 0"); + EXPECT_SPDY_BUG(EXPECT_EQ(kHttp2MaxStreamWeight, + SpdyStreamPrecedence(3, 300, false).weight()), + "Invalid weight: 300"); +} + +TEST(SpdyStreamPrecedenceTest, Copying) { + SpdyStreamPrecedence prec1(3); + SpdyStreamPrecedence copy1(prec1); + EXPECT_TRUE(copy1.is_spdy3_priority()); + EXPECT_EQ(3, copy1.spdy3_priority()); + + SpdyStreamPrecedence prec2(4, 5, true); + SpdyStreamPrecedence copy2(prec2); + EXPECT_FALSE(copy2.is_spdy3_priority()); + EXPECT_EQ(4u, copy2.parent_id()); + EXPECT_EQ(5, copy2.weight()); + EXPECT_TRUE(copy2.is_exclusive()); + + copy1 = prec2; + EXPECT_FALSE(copy1.is_spdy3_priority()); + EXPECT_EQ(4u, copy1.parent_id()); + EXPECT_EQ(5, copy1.weight()); + EXPECT_TRUE(copy1.is_exclusive()); + + copy2 = prec1; + EXPECT_TRUE(copy2.is_spdy3_priority()); + EXPECT_EQ(3, copy2.spdy3_priority()); +} + +TEST(SpdyStreamPrecedenceTest, Equals) { + EXPECT_EQ(SpdyStreamPrecedence(3), SpdyStreamPrecedence(3)); + EXPECT_NE(SpdyStreamPrecedence(3), SpdyStreamPrecedence(4)); + + EXPECT_EQ(SpdyStreamPrecedence(1, 2, false), + SpdyStreamPrecedence(1, 2, false)); + EXPECT_NE(SpdyStreamPrecedence(1, 2, false), + SpdyStreamPrecedence(2, 2, false)); + EXPECT_NE(SpdyStreamPrecedence(1, 2, false), + SpdyStreamPrecedence(1, 3, false)); + EXPECT_NE(SpdyStreamPrecedence(1, 2, false), + SpdyStreamPrecedence(1, 2, true)); + + SpdyStreamPrecedence spdy3_prec(3); + SpdyStreamPrecedence h2_prec(spdy3_prec.parent_id(), spdy3_prec.weight(), + spdy3_prec.is_exclusive()); + EXPECT_NE(spdy3_prec, h2_prec); +} + +TEST(SpdyDataIRTest, Construct) { + // Confirm that it makes a string of zero length from a + // SpdyStringPiece(nullptr). + SpdyStringPiece s1; + SpdyDataIR d1(/* stream_id = */ 1, s1); + EXPECT_EQ(0u, d1.data_len()); + EXPECT_NE(nullptr, d1.data()); + + // Confirms makes a copy of char array. + const char s2[] = "something"; + SpdyDataIR d2(/* stream_id = */ 2, s2); + EXPECT_EQ(SpdyStringPiece(d2.data(), d2.data_len()), s2); + EXPECT_NE(SpdyStringPiece(d1.data(), d1.data_len()), s2); + EXPECT_EQ((int)d1.data_len(), d1.flow_control_window_consumed()); + + // Confirm copies a const string. + const SpdyString foo = "foo"; + SpdyDataIR d3(/* stream_id = */ 3, foo); + EXPECT_EQ(foo, d3.data()); + EXPECT_EQ((int)d3.data_len(), d3.flow_control_window_consumed()); + + // Confirm copies a non-const string. + SpdyString bar = "bar"; + SpdyDataIR d4(/* stream_id = */ 4, bar); + EXPECT_EQ("bar", bar); + EXPECT_EQ("bar", SpdyStringPiece(d4.data(), d4.data_len())); + + // Confirm moves an rvalue reference. Note that the test string "baz" is too + // short to trigger the move optimization, and instead a copy occurs. + SpdyString baz = "the quick brown fox"; + SpdyDataIR d5(/* stream_id = */ 5, std::move(baz)); + EXPECT_EQ("", baz); + EXPECT_EQ(SpdyStringPiece(d5.data(), d5.data_len()), "the quick brown fox"); + + // Confirms makes a copy of string literal. + SpdyDataIR d7(/* stream_id = */ 7, "something else"); + EXPECT_EQ(SpdyStringPiece(d7.data(), d7.data_len()), "something else"); + + SpdyDataIR d8(/* stream_id = */ 8, "shawarma"); + d8.set_padding_len(20); + EXPECT_EQ(28, d8.flow_control_window_consumed()); +} + +} // namespace test +} // namespace spdy diff --git a/chromium/net/third_party/quiche/src/spdy/core/spdy_protocol_test_utils.cc b/chromium/net/third_party/quiche/src/spdy/core/spdy_protocol_test_utils.cc new file mode 100644 index 00000000000..14d338502eb --- /dev/null +++ b/chromium/net/third_party/quiche/src/spdy/core/spdy_protocol_test_utils.cc @@ -0,0 +1,155 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/spdy/core/spdy_protocol_test_utils.h" + +#include <cstdint> + +#include "net/third_party/quiche/src/spdy/platform/api/spdy_string_piece.h" + +namespace spdy { +namespace test { + +// TODO(jamessynge): Where it makes sense in these functions, it would be nice +// to make use of the existing gMock matchers here, instead of rolling our own. + +::testing::AssertionResult VerifySpdyFrameWithHeaderBlockIREquals( + const SpdyFrameWithHeaderBlockIR& expected, + const SpdyFrameWithHeaderBlockIR& actual) { + VLOG(1) << "VerifySpdyFrameWithHeaderBlockIREquals"; + VERIFY_TRUE(actual.header_block() == expected.header_block()); + return ::testing::AssertionSuccess(); +} + +::testing::AssertionResult VerifySpdyFrameIREquals(const SpdyAltSvcIR& expected, + const SpdyAltSvcIR& actual) { + VERIFY_EQ(expected.stream_id(), actual.stream_id()); + VERIFY_EQ(expected.origin(), actual.origin()); + VERIFY_THAT(actual.altsvc_vector(), + ::testing::ContainerEq(expected.altsvc_vector())); + return ::testing::AssertionSuccess(); +} + +::testing::AssertionResult VerifySpdyFrameIREquals( + const SpdyContinuationIR& expected, + const SpdyContinuationIR& actual) { + return ::testing::AssertionFailure() + << "VerifySpdyFrameIREquals SpdyContinuationIR not yet implemented"; +} + +::testing::AssertionResult VerifySpdyFrameIREquals(const SpdyDataIR& expected, + const SpdyDataIR& actual) { + VLOG(1) << "VerifySpdyFrameIREquals SpdyDataIR"; + VERIFY_EQ(expected.stream_id(), actual.stream_id()); + VERIFY_EQ(expected.fin(), actual.fin()); + VERIFY_EQ(expected.data_len(), actual.data_len()); + if (expected.data() == nullptr) { + VERIFY_EQ(nullptr, actual.data()); + } else { + VERIFY_EQ(SpdyStringPiece(expected.data(), expected.data_len()), + SpdyStringPiece(actual.data(), actual.data_len())); + } + VERIFY_SUCCESS(VerifySpdyFrameWithPaddingIREquals(expected, actual)); + return ::testing::AssertionSuccess(); +} + +::testing::AssertionResult VerifySpdyFrameIREquals(const SpdyGoAwayIR& expected, + const SpdyGoAwayIR& actual) { + VLOG(1) << "VerifySpdyFrameIREquals SpdyGoAwayIR"; + VERIFY_EQ(expected.last_good_stream_id(), actual.last_good_stream_id()); + VERIFY_EQ(expected.error_code(), actual.error_code()); + VERIFY_EQ(expected.description(), actual.description()); + return ::testing::AssertionSuccess(); +} + +::testing::AssertionResult VerifySpdyFrameIREquals( + const SpdyHeadersIR& expected, + const SpdyHeadersIR& actual) { + VLOG(1) << "VerifySpdyFrameIREquals SpdyHeadersIR"; + VERIFY_EQ(expected.stream_id(), actual.stream_id()); + VERIFY_EQ(expected.fin(), actual.fin()); + VERIFY_SUCCESS(VerifySpdyFrameWithHeaderBlockIREquals(expected, actual)); + VERIFY_EQ(expected.has_priority(), actual.has_priority()); + if (expected.has_priority()) { + VERIFY_SUCCESS(VerifySpdyFrameWithPriorityIREquals(expected, actual)); + } + VERIFY_SUCCESS(VerifySpdyFrameWithPaddingIREquals(expected, actual)); + return ::testing::AssertionSuccess(); +} + +::testing::AssertionResult VerifySpdyFrameIREquals(const SpdyPingIR& expected, + const SpdyPingIR& actual) { + VLOG(1) << "VerifySpdyFrameIREquals SpdyPingIR"; + VERIFY_EQ(expected.id(), actual.id()); + VERIFY_EQ(expected.is_ack(), actual.is_ack()); + return ::testing::AssertionSuccess(); +} + +::testing::AssertionResult VerifySpdyFrameIREquals( + const SpdyPriorityIR& expected, + const SpdyPriorityIR& actual) { + VLOG(1) << "VerifySpdyFrameIREquals SpdyPriorityIR"; + VERIFY_EQ(expected.stream_id(), actual.stream_id()); + VERIFY_SUCCESS(VerifySpdyFrameWithPriorityIREquals(expected, actual)); + return ::testing::AssertionSuccess(); +} + +::testing::AssertionResult VerifySpdyFrameIREquals( + const SpdyPushPromiseIR& expected, + const SpdyPushPromiseIR& actual) { + VLOG(1) << "VerifySpdyFrameIREquals SpdyPushPromiseIR"; + VERIFY_EQ(expected.stream_id(), actual.stream_id()); + VERIFY_SUCCESS(VerifySpdyFrameWithPaddingIREquals(expected, actual)); + VERIFY_EQ(expected.promised_stream_id(), actual.promised_stream_id()); + VERIFY_SUCCESS(VerifySpdyFrameWithHeaderBlockIREquals(expected, actual)); + return ::testing::AssertionSuccess(); +} + +::testing::AssertionResult VerifySpdyFrameIREquals( + const SpdyRstStreamIR& expected, + const SpdyRstStreamIR& actual) { + VLOG(1) << "VerifySpdyFrameIREquals SpdyRstStreamIR"; + VERIFY_EQ(expected.stream_id(), actual.stream_id()); + VERIFY_EQ(expected.error_code(), actual.error_code()); + return ::testing::AssertionSuccess(); +} + +::testing::AssertionResult VerifySpdyFrameIREquals( + const SpdySettingsIR& expected, + const SpdySettingsIR& actual) { + VLOG(1) << "VerifySpdyFrameIREquals SpdySettingsIR"; + // Note, ignoring non-HTTP/2 fields such as clear_settings. + VERIFY_EQ(expected.is_ack(), actual.is_ack()); + + // Note, the following doesn't work because there isn't a comparator and + // formatter for SpdySettingsIR::Value. Fixable if we cared. + // + // VERIFY_THAT(actual.values(), ::testing::ContainerEq(actual.values())); + + VERIFY_EQ(expected.values().size(), actual.values().size()); + for (const auto& entry : expected.values()) { + const auto& param = entry.first; + auto actual_itr = actual.values().find(param); + VERIFY_TRUE(!(actual_itr == actual.values().end())) + << "actual doesn't contain param: " << param; + uint32_t expected_value = entry.second; + uint32_t actual_value = actual_itr->second; + VERIFY_EQ(expected_value, actual_value) + << "Values don't match for parameter: " << param; + } + + return ::testing::AssertionSuccess(); +} + +::testing::AssertionResult VerifySpdyFrameIREquals( + const SpdyWindowUpdateIR& expected, + const SpdyWindowUpdateIR& actual) { + VLOG(1) << "VerifySpdyFrameIREquals SpdyWindowUpdateIR"; + VERIFY_EQ(expected.stream_id(), actual.stream_id()); + VERIFY_EQ(expected.delta(), actual.delta()); + return ::testing::AssertionSuccess(); +} + +} // namespace test +} // namespace spdy diff --git a/chromium/net/third_party/quiche/src/spdy/core/spdy_protocol_test_utils.h b/chromium/net/third_party/quiche/src/spdy/core/spdy_protocol_test_utils.h new file mode 100644 index 00000000000..bbec5979337 --- /dev/null +++ b/chromium/net/third_party/quiche/src/spdy/core/spdy_protocol_test_utils.h @@ -0,0 +1,149 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef QUICHE_SPDY_CORE_SPDY_PROTOCOL_TEST_UTILS_H_ +#define QUICHE_SPDY_CORE_SPDY_PROTOCOL_TEST_UTILS_H_ + +// These functions support tests that need to compare two concrete SpdyFrameIR +// instances for equality. They return AssertionResult, so they may be used as +// follows: +// +// SomeSpdyFrameIRSubClass expected_ir(...); +// std::unique_ptr<SpdyFrameIR> collected_frame; +// ... some test code that may fill in collected_frame ... +// ASSERT_TRUE(VerifySpdyFrameIREquals(expected_ir, collected_frame.get())); +// +// TODO(jamessynge): Where it makes sense in these functions, it would be nice +// to make use of the existing gMock matchers here, instead of rolling our own. + +#include <typeinfo> + +#include "base/logging.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "net/third_party/quiche/src/spdy/core/spdy_protocol.h" +#include "net/third_party/quiche/src/spdy/core/spdy_test_utils.h" + +namespace spdy { +namespace test { + +// Verify the header entries in two SpdyFrameWithHeaderBlockIR instances +// are the same. +::testing::AssertionResult VerifySpdyFrameWithHeaderBlockIREquals( + const SpdyFrameWithHeaderBlockIR& expected, + const SpdyFrameWithHeaderBlockIR& actual); + +// Verify that the padding in two frames of type T is the same. +template <class T> +::testing::AssertionResult VerifySpdyFrameWithPaddingIREquals(const T& expected, + const T& actual) { + VLOG(1) << "VerifySpdyFrameWithPaddingIREquals"; + VERIFY_EQ(expected.padded(), actual.padded()); + if (expected.padded()) { + VERIFY_EQ(expected.padding_payload_len(), actual.padding_payload_len()); + } + + return ::testing::AssertionSuccess(); +} + +// Verify the priority fields in two frames of type T are the same. +template <class T> +::testing::AssertionResult VerifySpdyFrameWithPriorityIREquals( + const T& expected, + const T& actual) { + VLOG(1) << "VerifySpdyFrameWithPriorityIREquals"; + VERIFY_EQ(expected.parent_stream_id(), actual.parent_stream_id()); + VERIFY_EQ(expected.weight(), actual.weight()); + VERIFY_EQ(expected.exclusive(), actual.exclusive()); + return ::testing::AssertionSuccess(); +} + +// Verify that two SpdyAltSvcIR frames are the same. +::testing::AssertionResult VerifySpdyFrameIREquals(const SpdyAltSvcIR& expected, + const SpdyAltSvcIR& actual); + +// VerifySpdyFrameIREquals for SpdyContinuationIR frames isn't really needed +// because we don't really make use of SpdyContinuationIR, instead creating +// SpdyHeadersIR or SpdyPushPromiseIR with the pre-encoding form of the HPACK +// block (i.e. we don't yet have a CONTINUATION frame). +// +// ::testing::AssertionResult VerifySpdyFrameIREquals( +// const SpdyContinuationIR& expected, +// const SpdyContinuationIR& actual) { +// return ::testing::AssertionFailure() +// << "VerifySpdyFrameIREquals SpdyContinuationIR NYI"; +// } + +// Verify that two SpdyDataIR frames are the same. +::testing::AssertionResult VerifySpdyFrameIREquals(const SpdyDataIR& expected, + const SpdyDataIR& actual); + +// Verify that two SpdyGoAwayIR frames are the same. +::testing::AssertionResult VerifySpdyFrameIREquals(const SpdyGoAwayIR& expected, + const SpdyGoAwayIR& actual); + +// Verify that two SpdyHeadersIR frames are the same. +::testing::AssertionResult VerifySpdyFrameIREquals( + const SpdyHeadersIR& expected, + const SpdyHeadersIR& actual); + +// Verify that two SpdyPingIR frames are the same. +::testing::AssertionResult VerifySpdyFrameIREquals(const SpdyPingIR& expected, + const SpdyPingIR& actual); + +// Verify that two SpdyPriorityIR frames are the same. +::testing::AssertionResult VerifySpdyFrameIREquals( + const SpdyPriorityIR& expected, + const SpdyPriorityIR& actual); + +// Verify that two SpdyPushPromiseIR frames are the same. +::testing::AssertionResult VerifySpdyFrameIREquals( + const SpdyPushPromiseIR& expected, + const SpdyPushPromiseIR& actual); + +// Verify that two SpdyRstStreamIR frames are the same. +::testing::AssertionResult VerifySpdyFrameIREquals( + const SpdyRstStreamIR& expected, + const SpdyRstStreamIR& actual); + +// Verify that two SpdySettingsIR frames are the same. +::testing::AssertionResult VerifySpdyFrameIREquals( + const SpdySettingsIR& expected, + const SpdySettingsIR& actual); + +// Verify that two SpdyWindowUpdateIR frames are the same. +::testing::AssertionResult VerifySpdyFrameIREquals( + const SpdyWindowUpdateIR& expected, + const SpdyWindowUpdateIR& actual); + +// Verify that either expected and actual are both nullptr, or that both are not +// nullptr, and that actual is of type E, and that it matches expected. +template <class E> +::testing::AssertionResult VerifySpdyFrameIREquals(const E* expected, + const SpdyFrameIR* actual) { + if (expected == nullptr || actual == nullptr) { + VLOG(1) << "VerifySpdyFrameIREquals one null"; + VERIFY_EQ(expected, nullptr); + VERIFY_EQ(actual, nullptr); + return ::testing::AssertionSuccess(); + } + VLOG(1) << "VerifySpdyFrameIREquals not null"; + VERIFY_EQ(actual->frame_type(), expected->frame_type()); + const E* actual2 = static_cast<const E*>(actual); + return VerifySpdyFrameIREquals(*expected, *actual2); +} + +// Verify that actual is not nullptr, that it is of type E and that it +// matches expected. +template <class E> +::testing::AssertionResult VerifySpdyFrameIREquals(const E& expected, + const SpdyFrameIR* actual) { + VLOG(1) << "VerifySpdyFrameIREquals"; + return VerifySpdyFrameIREquals(&expected, actual); +} + +} // namespace test +} // namespace spdy + +#endif // QUICHE_SPDY_CORE_SPDY_PROTOCOL_TEST_UTILS_H_ diff --git a/chromium/net/third_party/quiche/src/spdy/core/spdy_test_utils.cc b/chromium/net/third_party/quiche/src/spdy/core/spdy_test_utils.cc new file mode 100644 index 00000000000..dd6c417abeb --- /dev/null +++ b/chromium/net/third_party/quiche/src/spdy/core/spdy_test_utils.cc @@ -0,0 +1,119 @@ +// Copyright (c) 2012 The Chromium 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/spdy/core/spdy_test_utils.h" + +#include <algorithm> +#include <cstring> +#include <memory> +#include <new> +#include <utility> +#include <vector> + +#include "base/logging.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "net/third_party/quiche/src/spdy/platform/api/spdy_endianness_util.h" + +namespace spdy { +namespace test { + +SpdyString HexDumpWithMarks(const unsigned char* data, + int length, + const bool* marks, + int mark_length) { + static const char kHexChars[] = "0123456789abcdef"; + static const int kColumns = 4; + + const int kSizeLimit = 1024; + if (length > kSizeLimit || mark_length > kSizeLimit) { + LOG(ERROR) << "Only dumping first " << kSizeLimit << " bytes."; + length = std::min(length, kSizeLimit); + mark_length = std::min(mark_length, kSizeLimit); + } + + SpdyString hex; + for (const unsigned char* row = data; length > 0; + row += kColumns, length -= kColumns) { + for (const unsigned char* p = row; p < row + 4; ++p) { + if (p < row + length) { + const bool mark = + (marks && (p - data) < mark_length && marks[p - data]); + hex += mark ? '*' : ' '; + hex += kHexChars[(*p & 0xf0) >> 4]; + hex += kHexChars[*p & 0x0f]; + hex += mark ? '*' : ' '; + } else { + hex += " "; + } + } + hex = hex + " "; + + for (const unsigned char* p = row; p < row + 4 && p < row + length; ++p) { + hex += (*p >= 0x20 && *p <= 0x7f) ? (*p) : '.'; + } + + hex = hex + '\n'; + } + return hex; +} + +void CompareCharArraysWithHexError(const SpdyString& description, + const unsigned char* actual, + const int actual_len, + const unsigned char* expected, + const int expected_len) { + const int min_len = std::min(actual_len, expected_len); + const int max_len = std::max(actual_len, expected_len); + std::unique_ptr<bool[]> marks(new bool[max_len]); + bool identical = (actual_len == expected_len); + for (int i = 0; i < min_len; ++i) { + if (actual[i] != expected[i]) { + marks[i] = true; + identical = false; + } else { + marks[i] = false; + } + } + for (int i = min_len; i < max_len; ++i) { + marks[i] = true; + } + if (identical) + return; + ADD_FAILURE() << "Description:\n" + << description << "\n\nExpected:\n" + << HexDumpWithMarks(expected, expected_len, marks.get(), + max_len) + << "\nActual:\n" + << HexDumpWithMarks(actual, actual_len, marks.get(), max_len); +} + +void SetFrameFlags(SpdySerializedFrame* frame, uint8_t flags) { + frame->data()[4] = flags; +} + +void SetFrameLength(SpdySerializedFrame* frame, size_t length) { + CHECK_GT(1u << 14, length); + { + int32_t wire_length = SpdyHostToNet32(length); + memcpy(frame->data(), reinterpret_cast<char*>(&wire_length) + 1, 3); + } +} + +void TestHeadersHandler::OnHeaderBlockStart() { + block_.clear(); +} + +void TestHeadersHandler::OnHeader(SpdyStringPiece name, SpdyStringPiece value) { + block_.AppendValueOrAddHeader(name, value); +} + +void TestHeadersHandler::OnHeaderBlockEnd( + size_t header_bytes_parsed, + size_t compressed_header_bytes_parsed) { + header_bytes_parsed_ = header_bytes_parsed; + compressed_header_bytes_parsed_ = compressed_header_bytes_parsed; +} + +} // namespace test +} // namespace spdy diff --git a/chromium/net/third_party/quiche/src/spdy/core/spdy_test_utils.h b/chromium/net/third_party/quiche/src/spdy/core/spdy_test_utils.h new file mode 100644 index 00000000000..bf62eeba930 --- /dev/null +++ b/chromium/net/third_party/quiche/src/spdy/core/spdy_test_utils.h @@ -0,0 +1,74 @@ +// Copyright (c) 2012 The Chromium 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_SPDY_CORE_SPDY_TEST_UTILS_H_ +#define QUICHE_SPDY_CORE_SPDY_TEST_UTILS_H_ + +#include <cstddef> +#include <cstdint> + +#include "net/third_party/quiche/src/http2/platform/api/http2_test_helpers.h" +#include "net/third_party/quiche/src/spdy/core/spdy_header_block.h" +#include "net/third_party/quiche/src/spdy/core/spdy_headers_handler_interface.h" +#include "net/third_party/quiche/src/spdy/core/spdy_protocol.h" +#include "net/third_party/quiche/src/spdy/platform/api/spdy_bug_tracker.h" +#include "net/third_party/quiche/src/spdy/platform/api/spdy_string.h" +#include "net/third_party/quiche/src/spdy/platform/api/spdy_string_piece.h" +#include "net/third_party/quiche/src/spdy/platform/api/spdy_test_helpers.h" + +namespace spdy { + +inline bool operator==(SpdyStringPiece x, + const SpdyHeaderBlock::ValueProxy& y) { + return x == y.as_string(); +} + +namespace test { + +SpdyString HexDumpWithMarks(const unsigned char* data, + int length, + const bool* marks, + int mark_length); + +void CompareCharArraysWithHexError(const SpdyString& description, + const unsigned char* actual, + const int actual_len, + const unsigned char* expected, + const int expected_len); + +void SetFrameFlags(SpdySerializedFrame* frame, uint8_t flags); + +void SetFrameLength(SpdySerializedFrame* frame, size_t length); + +// A test implementation of SpdyHeadersHandlerInterface that correctly +// reconstructs multiple header values for the same name. +class TestHeadersHandler : public SpdyHeadersHandlerInterface { + public: + TestHeadersHandler() {} + TestHeadersHandler(const TestHeadersHandler&) = delete; + TestHeadersHandler& operator=(const TestHeadersHandler&) = delete; + + void OnHeaderBlockStart() override; + + void OnHeader(SpdyStringPiece name, SpdyStringPiece value) override; + + void OnHeaderBlockEnd(size_t header_bytes_parsed, + size_t compressed_header_bytes_parsed) override; + + const SpdyHeaderBlock& decoded_block() const { return block_; } + size_t header_bytes_parsed() const { return header_bytes_parsed_; } + size_t compressed_header_bytes_parsed() const { + return compressed_header_bytes_parsed_; + } + + private: + SpdyHeaderBlock block_; + size_t header_bytes_parsed_ = 0; + size_t compressed_header_bytes_parsed_ = 0; +}; + +} // namespace test +} // namespace spdy + +#endif // QUICHE_SPDY_CORE_SPDY_TEST_UTILS_H_ diff --git a/chromium/net/third_party/quiche/src/spdy/core/write_scheduler.h b/chromium/net/third_party/quiche/src/spdy/core/write_scheduler.h new file mode 100644 index 00000000000..07e5fb32c60 --- /dev/null +++ b/chromium/net/third_party/quiche/src/spdy/core/write_scheduler.h @@ -0,0 +1,156 @@ +// Copyright (c) 2016 The Chromium 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_SPDY_CORE_WRITE_SCHEDULER_H_ +#define QUICHE_SPDY_CORE_WRITE_SCHEDULER_H_ + +#include <cstdint> +#include <tuple> +#include <vector> + +#include "net/third_party/quiche/src/spdy/core/spdy_protocol.h" +#include "net/third_party/quiche/src/spdy/platform/api/spdy_export.h" +#include "net/third_party/quiche/src/spdy/platform/api/spdy_string.h" + +namespace spdy { + +// Abstract superclass for classes that decide which SPDY or HTTP/2 stream to +// write next. Concrete subclasses implement various scheduling policies: +// +// PriorityWriteScheduler: implements SPDY priority-based stream scheduling, +// where (writable) higher-priority streams are always given precedence +// over lower-priority streams. +// +// Http2PriorityWriteScheduler: implements SPDY priority-based stream +// scheduling coupled with the HTTP/2 stream dependency model. This is only +// intended as a transitional step towards Http2WeightedWriteScheduler. +// +// Http2WeightedWriteScheduler (coming soon): implements the HTTP/2 stream +// dependency model with weighted stream scheduling, fully conforming to +// RFC 7540. +// +// The type used to represent stream IDs (StreamIdType) is templated in order +// to allow for use by both SPDY and QUIC codebases. It must be a POD that +// supports comparison (i.e., a numeric type). +// +// Each stream can be in one of two states: ready or not ready (for writing). +// Ready state is changed by calling the MarkStreamReady() and +// MarkStreamNotReady() methods. Only streams in the ready state can be +// returned by PopNextReadyStream(); when returned by that method, the stream's +// state changes to not ready. +template <typename StreamIdType> +class SPDY_EXPORT_PRIVATE WriteScheduler { + public: + typedef StreamPrecedence<StreamIdType> StreamPrecedenceType; + + virtual ~WriteScheduler() {} + + // Registers new stream |stream_id| with the scheduler, assigning it the + // given precedence. If the scheduler supports stream dependencies, the + // stream is inserted into the dependency tree under + // |precedence.parent_id()|. + // + // Preconditions: |stream_id| should be unregistered, and + // |precedence.parent_id()| should be registered or |kHttp2RootStreamId|. + virtual void RegisterStream(StreamIdType stream_id, + const StreamPrecedenceType& precedence) = 0; + + // Unregisters the given stream from the scheduler, which will no longer keep + // state for it. + // + // Preconditions: |stream_id| should be registered. + virtual void UnregisterStream(StreamIdType stream_id) = 0; + + // Returns true if the given stream is currently registered. + virtual bool StreamRegistered(StreamIdType stream_id) const = 0; + + // Returns the precedence of the specified stream. If the scheduler supports + // stream dependencies, calling |parent_id()| on the return value returns the + // stream's parent, and calling |exclusive()| returns true iff the specified + // stream is an only child of the parent stream. + // + // Preconditions: |stream_id| should be registered. + virtual StreamPrecedenceType GetStreamPrecedence( + StreamIdType stream_id) const = 0; + + // Updates the precedence of the given stream. If the scheduler supports + // stream dependencies, |stream_id|'s parent will be updated to be + // |precedence.parent_id()| if it is not already. + // + // Preconditions: |stream_id| should be unregistered, and + // |precedence.parent_id()| should be registered or |kHttp2RootStreamId|. + virtual void UpdateStreamPrecedence( + StreamIdType stream_id, + const StreamPrecedenceType& precedence) = 0; + + // Returns child streams of the given stream, if any. If the scheduler + // doesn't support stream dependencies, returns an empty vector. + // + // Preconditions: |stream_id| should be registered. + virtual std::vector<StreamIdType> GetStreamChildren( + StreamIdType stream_id) const = 0; + + // Records time (in microseconds) of a read/write event for the given + // stream. + // + // Preconditions: |stream_id| should be registered. + virtual void RecordStreamEventTime(StreamIdType stream_id, + int64_t now_in_usec) = 0; + + // Returns time (in microseconds) of the last read/write event for a stream + // with higher priority than the priority of the given stream, or 0 if there + // is no such event. + // + // Preconditions: |stream_id| should be registered. + virtual int64_t GetLatestEventWithPrecedence( + StreamIdType stream_id) const = 0; + + // If the scheduler has any ready streams, returns the next scheduled + // ready stream, in the process transitioning the stream from ready to not + // ready. + // + // Preconditions: |HasReadyStreams() == true| + virtual StreamIdType PopNextReadyStream() = 0; + + // If the scheduler has any ready streams, returns the next scheduled + // ready stream and its priority, in the process transitioning the stream from + // ready to not ready. + // + // Preconditions: |HasReadyStreams() == true| + virtual std::tuple<StreamIdType, StreamPrecedenceType> + PopNextReadyStreamAndPrecedence() = 0; + + // Returns true if there's another stream ahead of the given stream in the + // scheduling queue. This function can be called to see if the given stream + // should yield work to another stream. + // + // Preconditions: |stream_id| should be registered. + virtual bool ShouldYield(StreamIdType stream_id) const = 0; + + // Marks the stream as ready to write. If the stream was already ready, does + // nothing. If add_to_front is true, the stream is scheduled ahead of other + // streams of the same priority/weight, otherwise it is scheduled behind them. + // + // Preconditions: |stream_id| should be registered. + virtual void MarkStreamReady(StreamIdType stream_id, bool add_to_front) = 0; + + // Marks the stream as not ready to write. If the stream is not registered or + // not ready, does nothing. + // + // Preconditions: |stream_id| should be registered. + virtual void MarkStreamNotReady(StreamIdType stream_id) = 0; + + // Returns true iff the scheduler has any ready streams. + virtual bool HasReadyStreams() const = 0; + + // Returns the number of streams currently marked ready. + virtual size_t NumReadyStreams() const = 0; + + // Returns summary of internal state, for logging/debugging. + virtual SpdyString DebugString() const = 0; +}; + +} // namespace spdy + +#endif // QUICHE_SPDY_CORE_WRITE_SCHEDULER_H_ diff --git a/chromium/net/third_party/quiche/src/spdy/core/zero_copy_output_buffer.h b/chromium/net/third_party/quiche/src/spdy/core/zero_copy_output_buffer.h new file mode 100644 index 00000000000..3f35bab714d --- /dev/null +++ b/chromium/net/third_party/quiche/src/spdy/core/zero_copy_output_buffer.h @@ -0,0 +1,30 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef QUICHE_SPDY_CORE_ZERO_COPY_OUTPUT_BUFFER_H_ +#define QUICHE_SPDY_CORE_ZERO_COPY_OUTPUT_BUFFER_H_ + +#include <cstdint> + +namespace spdy { + +class ZeroCopyOutputBuffer { + public: + virtual ~ZeroCopyOutputBuffer() {} + + // Returns the next available segment of memory to write. Will always return + // the same segment until AdvanceWritePtr is called. + virtual void Next(char** data, int* size) = 0; + + // After writing to a buffer returned from Next(), the caller should call + // this method to indicate how many bytes were written. + virtual void AdvanceWritePtr(int64_t count) = 0; + + // Returns the available capacity of the buffer. + virtual uint64_t BytesFree() const = 0; +}; + +} // namespace spdy + +#endif // QUICHE_SPDY_CORE_ZERO_COPY_OUTPUT_BUFFER_H_ diff --git a/chromium/net/third_party/quiche/src/spdy/platform/api/spdy_arraysize.h b/chromium/net/third_party/quiche/src/spdy/platform/api/spdy_arraysize.h new file mode 100644 index 00000000000..356051e4700 --- /dev/null +++ b/chromium/net/third_party/quiche/src/spdy/platform/api/spdy_arraysize.h @@ -0,0 +1,12 @@ +// Copyright (c) 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef QUICHE_SPDY_PLATFORM_API_SPDY_ARRAYSIZE_H_ +#define QUICHE_SPDY_PLATFORM_API_SPDY_ARRAYSIZE_H_ + +#include "net/spdy/platform/impl/spdy_arraysize_impl.h" + +#define SPDY_ARRAYSIZE(x) SPDY_ARRAYSIZE_IMPL(x) + +#endif // QUICHE_SPDY_PLATFORM_API_SPDY_ARRAYSIZE_H_ diff --git a/chromium/net/third_party/quiche/src/spdy/platform/api/spdy_bug_tracker.h b/chromium/net/third_party/quiche/src/spdy/platform/api/spdy_bug_tracker.h new file mode 100644 index 00000000000..d750a21f976 --- /dev/null +++ b/chromium/net/third_party/quiche/src/spdy/platform/api/spdy_bug_tracker.h @@ -0,0 +1,15 @@ +// Copyright (c) 2019 The Chromium 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_SPDY_PLATFORM_API_SPDY_BUG_TRACKER_H_ +#define QUICHE_SPDY_PLATFORM_API_SPDY_BUG_TRACKER_H_ + +#include "net/spdy/platform/impl/spdy_bug_tracker_impl.h" + +#define SPDY_BUG SPDY_BUG_IMPL +#define SPDY_BUG_IF(condition) SPDY_BUG_IF_IMPL(condition) +#define FLAGS_spdy_always_log_bugs_for_tests \ + FLAGS_spdy_always_log_bugs_for_tests_impl + +#endif // QUICHE_SPDY_PLATFORM_API_SPDY_BUG_TRACKER_H_ diff --git a/chromium/net/third_party/quiche/src/spdy/platform/api/spdy_containers.h b/chromium/net/third_party/quiche/src/spdy/platform/api/spdy_containers.h new file mode 100644 index 00000000000..e4f4c494eb4 --- /dev/null +++ b/chromium/net/third_party/quiche/src/spdy/platform/api/spdy_containers.h @@ -0,0 +1,42 @@ +// Copyright (c) 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef QUICHE_SPDY_PLATFORM_API_SPDY_CONTAINERS_H_ +#define QUICHE_SPDY_PLATFORM_API_SPDY_CONTAINERS_H_ + +#include "net/third_party/quiche/src/spdy/platform/api/spdy_string_piece.h" +#include "net/spdy/platform/impl/spdy_containers_impl.h" + +namespace spdy { + +template <typename KeyType> +using SpdyHash = SpdyHashImpl<KeyType>; + +// SpdyHashMap does not guarantee pointer stability. +template <typename KeyType, + typename ValueType, + typename Hash = SpdyHash<KeyType>> +using SpdyHashMap = SpdyHashMapImpl<KeyType, ValueType, Hash>; + +// SpdyHashSet does not guarantee pointer stability. +template <typename ElementType, typename Hasher, typename Eq> +using SpdyHashSet = SpdyHashSetImpl<ElementType, Hasher, Eq>; + +// A map which offers insertion-ordered iteration. +template <typename Key, typename Value, typename Hash = SpdyHash<Key>> +using SpdyLinkedHashMap = SpdyLinkedHashMapImpl<Key, Value, Hash>; + +// A vector optimized for small sizes. Provides the same APIs as a std::vector. +template <typename T, size_t N, typename A = std::allocator<T>> +using SpdyInlinedVector = SpdyInlinedVectorImpl<T, N, A>; + +using SpdyStringPieceHash = SpdyStringPieceHashImpl; + +inline size_t SpdyHashStringPair(SpdyStringPiece a, SpdyStringPiece b) { + return SpdyHashStringPairImpl(a, b); +} + +} // namespace spdy + +#endif // QUICHE_SPDY_PLATFORM_API_SPDY_CONTAINERS_H_ diff --git a/chromium/net/third_party/quiche/src/spdy/platform/api/spdy_endianness_util.h b/chromium/net/third_party/quiche/src/spdy/platform/api/spdy_endianness_util.h new file mode 100644 index 00000000000..e4074d710ce --- /dev/null +++ b/chromium/net/third_party/quiche/src/spdy/platform/api/spdy_endianness_util.h @@ -0,0 +1,44 @@ +// Copyright (c) 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef QUICHE_SPDY_PLATFORM_API_SPDY_ENDIANNESS_UTIL_H_ +#define QUICHE_SPDY_PLATFORM_API_SPDY_ENDIANNESS_UTIL_H_ + +#include <stdint.h> + +#include "net/spdy/platform/impl/spdy_endianness_util_impl.h" + +namespace spdy { + +// Converts the bytes in |x| from network to host order (endianness), and +// returns the result. +inline uint16_t SpdyNetToHost16(uint16_t x) { + return SpdyNetToHost16Impl(x); +} + +inline uint32_t SpdyNetToHost32(uint32_t x) { + return SpdyNetToHost32Impl(x); +} + +inline uint64_t SpdyNetToHost64(uint64_t x) { + return SpdyNetToHost64Impl(x); +} + +// Converts the bytes in |x| from host to network order (endianness), and +// returns the result. +inline uint16_t SpdyHostToNet16(uint16_t x) { + return SpdyHostToNet16Impl(x); +} + +inline uint32_t SpdyHostToNet32(uint32_t x) { + return SpdyHostToNet32Impl(x); +} + +inline uint64_t SpdyHostToNet64(uint64_t x) { + return SpdyHostToNet64Impl(x); +} + +} // namespace spdy + +#endif // QUICHE_SPDY_PLATFORM_API_SPDY_ENDIANNESS_UTIL_H_ diff --git a/chromium/net/third_party/quiche/src/spdy/platform/api/spdy_estimate_memory_usage.h b/chromium/net/third_party/quiche/src/spdy/platform/api/spdy_estimate_memory_usage.h new file mode 100644 index 00000000000..6aa53c3e79b --- /dev/null +++ b/chromium/net/third_party/quiche/src/spdy/platform/api/spdy_estimate_memory_usage.h @@ -0,0 +1,21 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef QUICHE_SPDY_PLATFORM_API_SPDY_ESTIMATE_MEMORY_USAGE_H_ +#define QUICHE_SPDY_PLATFORM_API_SPDY_ESTIMATE_MEMORY_USAGE_H_ + +#include <cstddef> + +#include "net/spdy/platform/impl/spdy_estimate_memory_usage_impl.h" + +namespace spdy { + +template <class T> +size_t SpdyEstimateMemoryUsage(const T& object) { + return SpdyEstimateMemoryUsageImpl(object); +} + +} // namespace spdy + +#endif // QUICHE_SPDY_PLATFORM_API_SPDY_ESTIMATE_MEMORY_USAGE_H_ diff --git a/chromium/net/third_party/quiche/src/spdy/platform/api/spdy_export.h b/chromium/net/third_party/quiche/src/spdy/platform/api/spdy_export.h new file mode 100644 index 00000000000..ba92f791dee --- /dev/null +++ b/chromium/net/third_party/quiche/src/spdy/platform/api/spdy_export.h @@ -0,0 +1,10 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef QUICHE_SPDY_PLATFORM_API_SPDY_EXPORT_H_ +#define QUICHE_SPDY_PLATFORM_API_SPDY_EXPORT_H_ + +#include "net/spdy/platform/impl/spdy_export_impl.h" + +#endif // QUICHE_SPDY_PLATFORM_API_SPDY_EXPORT_H_ diff --git a/chromium/net/third_party/quiche/src/spdy/platform/api/spdy_flags.h b/chromium/net/third_party/quiche/src/spdy/platform/api/spdy_flags.h new file mode 100644 index 00000000000..f3446d98203 --- /dev/null +++ b/chromium/net/third_party/quiche/src/spdy/platform/api/spdy_flags.h @@ -0,0 +1,13 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef QUICHE_SPDY_PLATFORM_API_SPDY_FLAGS_H_ +#define QUICHE_SPDY_PLATFORM_API_SPDY_FLAGS_H_ + +#include "net/spdy/platform/impl/spdy_flags_impl.h" + +#define GetSpdyReloadableFlag(flag) GetSpdyReloadableFlagImpl(flag) +#define GetSpdyRestartFlag(flag) GetSpdyRestartFlagImpl(flag) + +#endif // QUICHE_SPDY_PLATFORM_API_SPDY_FLAGS_H_ diff --git a/chromium/net/third_party/quiche/src/spdy/platform/api/spdy_macros.h b/chromium/net/third_party/quiche/src/spdy/platform/api/spdy_macros.h new file mode 100644 index 00000000000..a75c9576fc8 --- /dev/null +++ b/chromium/net/third_party/quiche/src/spdy/platform/api/spdy_macros.h @@ -0,0 +1,15 @@ +// Copyright (c) 2019 The Chromium 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_SPDY_PLATFORM_API_SPDY_MACROS_H_ +#define QUICHE_SPDY_PLATFORM_API_SPDY_MACROS_H_ + +#include "net/spdy/platform/impl/spdy_macros_impl.h" + +#define SPDY_MUST_USE_RESULT SPDY_MUST_USE_RESULT_IMPL +#define SPDY_UNUSED SPDY_UNUSED_IMPL + +#define SPDY_DVLOG_IF SPDY_DVLOG_IF_IMPL + +#endif // QUICHE_SPDY_PLATFORM_API_SPDY_MACROS_H_ diff --git a/chromium/net/third_party/quiche/src/spdy/platform/api/spdy_mem_slice.h b/chromium/net/third_party/quiche/src/spdy/platform/api/spdy_mem_slice.h new file mode 100644 index 00000000000..0d67c000ee9 --- /dev/null +++ b/chromium/net/third_party/quiche/src/spdy/platform/api/spdy_mem_slice.h @@ -0,0 +1,54 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef QUICHE_SPDY_PLATFORM_API_SPDY_MEM_SLICE_H_ +#define QUICHE_SPDY_PLATFORM_API_SPDY_MEM_SLICE_H_ + +#include <utility> + +#include "net/third_party/quiche/src/spdy/platform/api/spdy_export.h" +#include "net/spdy/platform/impl/spdy_mem_slice_impl.h" + +namespace spdy { + +// SpdyMemSlice is an internally reference counted data buffer used as the +// source buffers for write operations. SpdyMemSlice implicitly maintains a +// reference count and will free the underlying data buffer when the reference +// count reaches zero. +class SPDY_EXPORT_PRIVATE SpdyMemSlice { + public: + // Constructs an empty SpdyMemSlice with no underlying data and 0 reference + // count. + SpdyMemSlice() = default; + + // Constructs a SpdyMemSlice with reference count 1 to a newly allocated data + // buffer of |length| bytes. + explicit SpdyMemSlice(size_t length) : impl_(length) {} + + // Constructs a SpdyMemSlice from |impl|. It takes the reference away from + // |impl|. + explicit SpdyMemSlice(SpdyMemSliceImpl impl) : impl_(std::move(impl)) {} + + SpdyMemSlice(const SpdyMemSlice& other) = delete; + SpdyMemSlice& operator=(const SpdyMemSlice& other) = delete; + + // Move constructors. |other| will not hold a reference to the data buffer + // after this call completes. + SpdyMemSlice(SpdyMemSlice&& other) = default; + SpdyMemSlice& operator=(SpdyMemSlice&& other) = default; + + ~SpdyMemSlice() = default; + + // Returns a char pointer to underlying data buffer. + const char* data() const { return impl_.data(); } + // Returns the length of underlying data buffer. + size_t length() const { return impl_.length(); } + + private: + SpdyMemSliceImpl impl_; +}; + +} // namespace spdy + +#endif // QUICHE_SPDY_PLATFORM_API_SPDY_MEM_SLICE_H_ diff --git a/chromium/net/third_party/quiche/src/spdy/platform/api/spdy_mem_slice_test.cc b/chromium/net/third_party/quiche/src/spdy/platform/api/spdy_mem_slice_test.cc new file mode 100644 index 00000000000..af323f6767d --- /dev/null +++ b/chromium/net/third_party/quiche/src/spdy/platform/api/spdy_mem_slice_test.cc @@ -0,0 +1,47 @@ +// 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/spdy/platform/api/spdy_mem_slice.h" + +#include <utility> + +#include "testing/gtest/include/gtest/gtest.h" + +namespace spdy { +namespace test { +namespace { + +class SpdyMemSliceTest : public ::testing::Test { + public: + SpdyMemSliceTest() { + slice_ = SpdyMemSlice(1024); + orig_data_ = slice_.data(); + orig_length_ = slice_.length(); + } + + SpdyMemSlice slice_; + const char* orig_data_; + size_t orig_length_; +}; + +TEST_F(SpdyMemSliceTest, MoveConstruct) { + SpdyMemSlice moved(std::move(slice_)); + EXPECT_EQ(moved.data(), orig_data_); + EXPECT_EQ(moved.length(), orig_length_); + EXPECT_EQ(nullptr, slice_.data()); + EXPECT_EQ(0u, slice_.length()); +} + +TEST_F(SpdyMemSliceTest, MoveAssign) { + SpdyMemSlice moved; + moved = std::move(slice_); + EXPECT_EQ(moved.data(), orig_data_); + EXPECT_EQ(moved.length(), orig_length_); + EXPECT_EQ(nullptr, slice_.data()); + EXPECT_EQ(0u, slice_.length()); +} + +} // namespace +} // namespace test +} // namespace spdy diff --git a/chromium/net/third_party/quiche/src/spdy/platform/api/spdy_ptr_util.h b/chromium/net/third_party/quiche/src/spdy/platform/api/spdy_ptr_util.h new file mode 100644 index 00000000000..32b8515f4ca --- /dev/null +++ b/chromium/net/third_party/quiche/src/spdy/platform/api/spdy_ptr_util.h @@ -0,0 +1,27 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef QUICHE_SPDY_PLATFORM_API_SPDY_PTR_UTIL_H_ +#define QUICHE_SPDY_PLATFORM_API_SPDY_PTR_UTIL_H_ + +#include <memory> +#include <utility> + +#include "net/spdy/platform/impl/spdy_ptr_util_impl.h" + +namespace spdy { + +template <typename T, typename... Args> +std::unique_ptr<T> SpdyMakeUnique(Args&&... args) { + return SpdyMakeUniqueImpl<T>(std::forward<Args>(args)...); +} + +template <typename T> +std::unique_ptr<T> SpdyWrapUnique(T* ptr) { + return SpdyWrapUniqueImpl<T>(ptr); +} + +} // namespace spdy + +#endif // QUICHE_SPDY_PLATFORM_API_SPDY_PTR_UTIL_H_ diff --git a/chromium/net/third_party/quiche/src/spdy/platform/api/spdy_string.h b/chromium/net/third_party/quiche/src/spdy/platform/api/spdy_string.h new file mode 100644 index 00000000000..16eb9e5569d --- /dev/null +++ b/chromium/net/third_party/quiche/src/spdy/platform/api/spdy_string.h @@ -0,0 +1,16 @@ +// Copyright (c) 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef QUICHE_SPDY_PLATFORM_API_SPDY_STRING_H_ +#define QUICHE_SPDY_PLATFORM_API_SPDY_STRING_H_ + +#include "net/spdy/platform/impl/spdy_string_impl.h" + +namespace spdy { + +using SpdyString = SpdyStringImpl; + +} // namespace spdy + +#endif // QUICHE_SPDY_PLATFORM_API_SPDY_STRING_H_ diff --git a/chromium/net/third_party/quiche/src/spdy/platform/api/spdy_string_piece.h b/chromium/net/third_party/quiche/src/spdy/platform/api/spdy_string_piece.h new file mode 100644 index 00000000000..37eea70f58a --- /dev/null +++ b/chromium/net/third_party/quiche/src/spdy/platform/api/spdy_string_piece.h @@ -0,0 +1,16 @@ +// Copyright (c) 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef QUICHE_SPDY_PLATFORM_API_SPDY_STRING_PIECE_H_ +#define QUICHE_SPDY_PLATFORM_API_SPDY_STRING_PIECE_H_ + +#include "net/spdy/platform/impl/spdy_string_piece_impl.h" + +namespace spdy { + +using SpdyStringPiece = SpdyStringPieceImpl; + +} // namespace spdy + +#endif // QUICHE_SPDY_PLATFORM_API_SPDY_STRING_PIECE_H_ diff --git a/chromium/net/third_party/quiche/src/spdy/platform/api/spdy_string_utils.h b/chromium/net/third_party/quiche/src/spdy/platform/api/spdy_string_utils.h new file mode 100644 index 00000000000..7554f796a97 --- /dev/null +++ b/chromium/net/third_party/quiche/src/spdy/platform/api/spdy_string_utils.h @@ -0,0 +1,58 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef QUICHE_SPDY_PLATFORM_API_SPDY_STRING_UTILS_H_ +#define QUICHE_SPDY_PLATFORM_API_SPDY_STRING_UTILS_H_ + +#include <utility> + +// The following header file has to be included from at least +// non-test file in order to avoid strange linking errors. +// TODO(bnc): Remove this include as soon as it is included elsewhere in +// non-test code. +#include "net/third_party/quiche/src/spdy/platform/api/spdy_mem_slice.h" + +#include "net/third_party/quiche/src/spdy/platform/api/spdy_string.h" +#include "net/third_party/quiche/src/spdy/platform/api/spdy_string_piece.h" +#include "net/spdy/platform/impl/spdy_string_utils_impl.h" + +namespace spdy { + +template <typename... Args> +inline SpdyString SpdyStrCat(const Args&... args) { + return SpdyStrCatImpl(std::forward<const Args&>(args)...); +} + +template <typename... Args> +inline void SpdyStrAppend(SpdyString* output, const Args&... args) { + SpdyStrAppendImpl(output, std::forward<const Args&>(args)...); +} + +inline char SpdyHexDigitToInt(char c) { + return SpdyHexDigitToIntImpl(c); +} + +inline SpdyString SpdyHexDecode(SpdyStringPiece data) { + return SpdyHexDecodeImpl(data); +} + +inline bool SpdyHexDecodeToUInt32(SpdyStringPiece data, uint32_t* out) { + return SpdyHexDecodeToUInt32Impl(data, out); +} + +inline SpdyString SpdyHexEncode(const char* bytes, size_t size) { + return SpdyHexEncodeImpl(bytes, size); +} + +inline SpdyString SpdyHexEncodeUInt32AndTrim(uint32_t data) { + return SpdyHexEncodeUInt32AndTrimImpl(data); +} + +inline SpdyString SpdyHexDump(SpdyStringPiece data) { + return SpdyHexDumpImpl(data); +} + +} // namespace spdy + +#endif // QUICHE_SPDY_PLATFORM_API_SPDY_STRING_UTILS_H_ diff --git a/chromium/net/third_party/quiche/src/spdy/platform/api/spdy_string_utils_test.cc b/chromium/net/third_party/quiche/src/spdy/platform/api/spdy_string_utils_test.cc new file mode 100644 index 00000000000..3f7e6a1882f --- /dev/null +++ b/chromium/net/third_party/quiche/src/spdy/platform/api/spdy_string_utils_test.cc @@ -0,0 +1,242 @@ +// 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/spdy/platform/api/spdy_string_utils.h" + +#include <cstdint> + +#include "testing/gtest/include/gtest/gtest.h" +#include "net/third_party/quiche/src/spdy/platform/api/spdy_string_piece.h" + +namespace spdy { +namespace test { +namespace { + +TEST(SpdyStringUtilsTest, SpdyStrCat) { + // No arguments. + EXPECT_EQ("", SpdyStrCat()); + + // Single string-like argument. + const char kFoo[] = "foo"; + const SpdyString string_foo(kFoo); + const SpdyStringPiece stringpiece_foo(string_foo); + EXPECT_EQ("foo", SpdyStrCat(kFoo)); + EXPECT_EQ("foo", SpdyStrCat(string_foo)); + EXPECT_EQ("foo", SpdyStrCat(stringpiece_foo)); + + // Two string-like arguments. + const char kBar[] = "bar"; + const SpdyStringPiece stringpiece_bar(kBar); + const SpdyString string_bar(kBar); + EXPECT_EQ("foobar", SpdyStrCat(kFoo, kBar)); + EXPECT_EQ("foobar", SpdyStrCat(kFoo, string_bar)); + EXPECT_EQ("foobar", SpdyStrCat(kFoo, stringpiece_bar)); + EXPECT_EQ("foobar", SpdyStrCat(string_foo, kBar)); + EXPECT_EQ("foobar", SpdyStrCat(string_foo, string_bar)); + EXPECT_EQ("foobar", SpdyStrCat(string_foo, stringpiece_bar)); + EXPECT_EQ("foobar", SpdyStrCat(stringpiece_foo, kBar)); + EXPECT_EQ("foobar", SpdyStrCat(stringpiece_foo, string_bar)); + EXPECT_EQ("foobar", SpdyStrCat(stringpiece_foo, stringpiece_bar)); + + // Many-many arguments. + EXPECT_EQ( + "foobarbazquxquuxquuzcorgegraultgarplywaldofredplughxyzzythud", + SpdyStrCat("foo", "bar", "baz", "qux", "quux", "quuz", "corge", "grault", + "garply", "waldo", "fred", "plugh", "xyzzy", "thud")); + + // Numerical arguments. + const int16_t i = 1; + const uint64_t u = 8; + const double d = 3.1415; + + EXPECT_EQ("1 8", SpdyStrCat(i, " ", u)); + EXPECT_EQ("3.14151181", SpdyStrCat(d, i, i, u, i)); + EXPECT_EQ("i: 1, u: 8, d: 3.1415", + SpdyStrCat("i: ", i, ", u: ", u, ", d: ", d)); + + // Boolean arguments. + const bool t = true; + const bool f = false; + + EXPECT_EQ("1", SpdyStrCat(t)); + EXPECT_EQ("0", SpdyStrCat(f)); + EXPECT_EQ("0110", SpdyStrCat(f, t, t, f)); + + // Mixed string-like, numerical, and Boolean arguments. + EXPECT_EQ("foo1foo081bar3.14151", + SpdyStrCat(kFoo, i, string_foo, f, u, t, stringpiece_bar, d, t)); + EXPECT_EQ("3.141511bar18bar13.14150", + SpdyStrCat(d, t, t, string_bar, i, u, kBar, t, d, f)); +} + +TEST(SpdyStringUtilsTest, SpdyStrAppend) { + // No arguments on empty string. + SpdyString output; + SpdyStrAppend(&output); + EXPECT_TRUE(output.empty()); + + // Single string-like argument. + const char kFoo[] = "foo"; + const SpdyString string_foo(kFoo); + const SpdyStringPiece stringpiece_foo(string_foo); + SpdyStrAppend(&output, kFoo); + EXPECT_EQ("foo", output); + SpdyStrAppend(&output, string_foo); + EXPECT_EQ("foofoo", output); + SpdyStrAppend(&output, stringpiece_foo); + EXPECT_EQ("foofoofoo", output); + + // No arguments on non-empty string. + SpdyStrAppend(&output); + EXPECT_EQ("foofoofoo", output); + + output.clear(); + + // Two string-like arguments. + const char kBar[] = "bar"; + const SpdyStringPiece stringpiece_bar(kBar); + const SpdyString string_bar(kBar); + SpdyStrAppend(&output, kFoo, kBar); + EXPECT_EQ("foobar", output); + SpdyStrAppend(&output, kFoo, string_bar); + EXPECT_EQ("foobarfoobar", output); + SpdyStrAppend(&output, kFoo, stringpiece_bar); + EXPECT_EQ("foobarfoobarfoobar", output); + SpdyStrAppend(&output, string_foo, kBar); + EXPECT_EQ("foobarfoobarfoobarfoobar", output); + + output.clear(); + + SpdyStrAppend(&output, string_foo, string_bar); + EXPECT_EQ("foobar", output); + SpdyStrAppend(&output, string_foo, stringpiece_bar); + EXPECT_EQ("foobarfoobar", output); + SpdyStrAppend(&output, stringpiece_foo, kBar); + EXPECT_EQ("foobarfoobarfoobar", output); + SpdyStrAppend(&output, stringpiece_foo, string_bar); + EXPECT_EQ("foobarfoobarfoobarfoobar", output); + + output.clear(); + + SpdyStrAppend(&output, stringpiece_foo, stringpiece_bar); + EXPECT_EQ("foobar", output); + + // Many-many arguments. + SpdyStrAppend(&output, "foo", "bar", "baz", "qux", "quux", "quuz", "corge", + "grault", "garply", "waldo", "fred", "plugh", "xyzzy", "thud"); + EXPECT_EQ( + "foobarfoobarbazquxquuxquuzcorgegraultgarplywaldofredplughxyzzythud", + output); + + output.clear(); + + // Numerical arguments. + const int16_t i = 1; + const uint64_t u = 8; + const double d = 3.1415; + + SpdyStrAppend(&output, i, " ", u); + EXPECT_EQ("1 8", output); + SpdyStrAppend(&output, d, i, i, u, i); + EXPECT_EQ("1 83.14151181", output); + SpdyStrAppend(&output, "i: ", i, ", u: ", u, ", d: ", d); + EXPECT_EQ("1 83.14151181i: 1, u: 8, d: 3.1415", output); + + output.clear(); + + // Boolean arguments. + const bool t = true; + const bool f = false; + + SpdyStrAppend(&output, t); + EXPECT_EQ("1", output); + SpdyStrAppend(&output, f); + EXPECT_EQ("10", output); + SpdyStrAppend(&output, f, t, t, f); + EXPECT_EQ("100110", output); + + output.clear(); + + // Mixed string-like, numerical, and Boolean arguments. + SpdyStrAppend(&output, kFoo, i, string_foo, f, u, t, stringpiece_bar, d, t); + EXPECT_EQ("foo1foo081bar3.14151", output); + SpdyStrAppend(&output, d, t, t, string_bar, i, u, kBar, t, d, f); + EXPECT_EQ("foo1foo081bar3.141513.141511bar18bar13.14150", output); +} + +TEST(SpdyStringUtilsTest, SpdyHexDigitToInt) { + EXPECT_EQ(0, SpdyHexDigitToInt('0')); + EXPECT_EQ(1, SpdyHexDigitToInt('1')); + EXPECT_EQ(2, SpdyHexDigitToInt('2')); + EXPECT_EQ(3, SpdyHexDigitToInt('3')); + EXPECT_EQ(4, SpdyHexDigitToInt('4')); + EXPECT_EQ(5, SpdyHexDigitToInt('5')); + EXPECT_EQ(6, SpdyHexDigitToInt('6')); + EXPECT_EQ(7, SpdyHexDigitToInt('7')); + EXPECT_EQ(8, SpdyHexDigitToInt('8')); + EXPECT_EQ(9, SpdyHexDigitToInt('9')); + + EXPECT_EQ(10, SpdyHexDigitToInt('a')); + EXPECT_EQ(11, SpdyHexDigitToInt('b')); + EXPECT_EQ(12, SpdyHexDigitToInt('c')); + EXPECT_EQ(13, SpdyHexDigitToInt('d')); + EXPECT_EQ(14, SpdyHexDigitToInt('e')); + EXPECT_EQ(15, SpdyHexDigitToInt('f')); + + EXPECT_EQ(10, SpdyHexDigitToInt('A')); + EXPECT_EQ(11, SpdyHexDigitToInt('B')); + EXPECT_EQ(12, SpdyHexDigitToInt('C')); + EXPECT_EQ(13, SpdyHexDigitToInt('D')); + EXPECT_EQ(14, SpdyHexDigitToInt('E')); + EXPECT_EQ(15, SpdyHexDigitToInt('F')); +} + +TEST(SpdyStringUtilsTest, SpdyHexDecodeToUInt32) { + uint32_t out; + EXPECT_TRUE(SpdyHexDecodeToUInt32("0", &out)); + EXPECT_EQ(0u, out); + EXPECT_TRUE(SpdyHexDecodeToUInt32("00", &out)); + EXPECT_EQ(0u, out); + EXPECT_TRUE(SpdyHexDecodeToUInt32("0000000", &out)); + EXPECT_EQ(0u, out); + EXPECT_TRUE(SpdyHexDecodeToUInt32("00000000", &out)); + EXPECT_EQ(0u, out); + EXPECT_TRUE(SpdyHexDecodeToUInt32("1", &out)); + EXPECT_EQ(1u, out); + EXPECT_TRUE(SpdyHexDecodeToUInt32("ffffFFF", &out)); + EXPECT_EQ(0xFFFFFFFu, out); + EXPECT_TRUE(SpdyHexDecodeToUInt32("fFfFffFf", &out)); + EXPECT_EQ(0xFFFFFFFFu, out); + EXPECT_TRUE(SpdyHexDecodeToUInt32("01AEF", &out)); + EXPECT_EQ(0x1AEFu, out); + EXPECT_TRUE(SpdyHexDecodeToUInt32("abcde", &out)); + EXPECT_EQ(0xABCDEu, out); + + EXPECT_FALSE(SpdyHexDecodeToUInt32("", &out)); + EXPECT_FALSE(SpdyHexDecodeToUInt32("111111111", &out)); + EXPECT_FALSE(SpdyHexDecodeToUInt32("1111111111", &out)); + EXPECT_FALSE(SpdyHexDecodeToUInt32("0x1111", &out)); +} + +TEST(SpdyStringUtilsTest, SpdyHexEncode) { + unsigned char bytes[] = {0x01, 0xff, 0x02, 0xfe, 0x03, 0x80, 0x81}; + EXPECT_EQ("01ff02fe038081", + SpdyHexEncode(reinterpret_cast<char*>(bytes), sizeof(bytes))); +} + +TEST(SpdyStringUtilsTest, SpdyHexEncodeUInt32AndTrim) { + EXPECT_EQ("0", SpdyHexEncodeUInt32AndTrim(0)); + EXPECT_EQ("1", SpdyHexEncodeUInt32AndTrim(1)); + EXPECT_EQ("a", SpdyHexEncodeUInt32AndTrim(0xA)); + EXPECT_EQ("f", SpdyHexEncodeUInt32AndTrim(0xF)); + EXPECT_EQ("a9", SpdyHexEncodeUInt32AndTrim(0xA9)); + EXPECT_EQ("9abcdef", SpdyHexEncodeUInt32AndTrim(0x9ABCDEF)); + EXPECT_EQ("12345678", SpdyHexEncodeUInt32AndTrim(0x12345678)); + EXPECT_EQ("ffffffff", SpdyHexEncodeUInt32AndTrim(0xFFFFFFFF)); + EXPECT_EQ("10000001", SpdyHexEncodeUInt32AndTrim(0x10000001)); +} + +} // namespace +} // namespace test +} // namespace spdy diff --git a/chromium/net/third_party/quiche/src/spdy/platform/api/spdy_test_helpers.h b/chromium/net/third_party/quiche/src/spdy/platform/api/spdy_test_helpers.h new file mode 100644 index 00000000000..367d330b6c9 --- /dev/null +++ b/chromium/net/third_party/quiche/src/spdy/platform/api/spdy_test_helpers.h @@ -0,0 +1,12 @@ +// Copyright (c) 2019 The Chromium 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_SPDY_PLATFORM_API_SPDY_TEST_HELPERS_H_ +#define QUICHE_SPDY_PLATFORM_API_SPDY_TEST_HELPERS_H_ + +#include "net/spdy/platform/impl/spdy_test_helpers_impl.h" + +#define EXPECT_SPDY_BUG EXPECT_SPDY_BUG_IMPL + +#endif // QUICHE_SPDY_PLATFORM_API_SPDY_TEST_HELPERS_H_ diff --git a/chromium/net/third_party/quiche/src/spdy/platform/api/spdy_test_utils_prod.h b/chromium/net/third_party/quiche/src/spdy/platform/api/spdy_test_utils_prod.h new file mode 100644 index 00000000000..14667701905 --- /dev/null +++ b/chromium/net/third_party/quiche/src/spdy/platform/api/spdy_test_utils_prod.h @@ -0,0 +1,12 @@ +// Copyright (c) 2019 The Chromium 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_SPDY_PLATFORM_API_SPDY_TEST_UTILS_PROD_H_ +#define QUICHE_SPDY_PLATFORM_API_SPDY_TEST_UTILS_PROD_H_ + +#include "net/spdy/platform/impl/spdy_test_utils_prod_impl.h" + +#define SPDY_FRIEND_TEST SPDY_FRIEND_TEST_IMPL + +#endif // QUICHE_SPDY_PLATFORM_API_SPDY_TEST_UTILS_PROD_H_ diff --git a/chromium/net/third_party/quiche/src/spdy/platform/api/spdy_unsafe_arena.h b/chromium/net/third_party/quiche/src/spdy/platform/api/spdy_unsafe_arena.h new file mode 100644 index 00000000000..77185e17598 --- /dev/null +++ b/chromium/net/third_party/quiche/src/spdy/platform/api/spdy_unsafe_arena.h @@ -0,0 +1,16 @@ +// Copyright (c) 2019 The Chromium 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_SPDY_PLATFORM_API_SPDY_UNSAFE_ARENA_H_ +#define QUICHE_SPDY_PLATFORM_API_SPDY_UNSAFE_ARENA_H_ + +#include "net/spdy/platform/impl/spdy_unsafe_arena_impl.h" + +namespace spdy { + +using SpdyUnsafeArena = SpdyUnsafeArenaImpl; + +} // namespace spdy + +#endif // QUICHE_SPDY_PLATFORM_API_SPDY_UNSAFE_ARENA_H_ |