diff options
author | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2022-05-17 17:24:03 +0200 |
---|---|---|
committer | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2022-06-22 07:51:41 +0000 |
commit | 774f54339e5db91f785733232d3950366db65d07 (patch) | |
tree | 068e1b47bd1af94d77094ed12b604a6b83d9c22a /chromium/net/third_party/quiche/src/quiche/spdy/core | |
parent | f7eaed5286974984ba5f9e3189d8f49d03e99f81 (diff) | |
download | qtwebengine-chromium-774f54339e5db91f785733232d3950366db65d07.tar.gz |
BASELINE: Update Chromium to 102.0.5005.57
Change-Id: I885f714bb40ee724c28f94ca6bd8dbdb39915158
Reviewed-by: Allan Sandfeld Jensen <allan.jensen@qt.io>
Diffstat (limited to 'chromium/net/third_party/quiche/src/quiche/spdy/core')
74 files changed, 21306 insertions, 0 deletions
diff --git a/chromium/net/third_party/quiche/src/quiche/spdy/core/array_output_buffer.cc b/chromium/net/third_party/quiche/src/quiche/spdy/core/array_output_buffer.cc new file mode 100644 index 00000000000..8cebea7c60c --- /dev/null +++ b/chromium/net/third_party/quiche/src/quiche/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 "quiche/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/quiche/spdy/core/array_output_buffer.h b/chromium/net/third_party/quiche/src/quiche/spdy/core/array_output_buffer.h new file mode 100644 index 00000000000..03d896a37d2 --- /dev/null +++ b/chromium/net/third_party/quiche/src/quiche/spdy/core/array_output_buffer.h @@ -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. + +#ifndef QUICHE_SPDY_CORE_ARRAY_OUTPUT_BUFFER_H_ +#define QUICHE_SPDY_CORE_ARRAY_OUTPUT_BUFFER_H_ + +#include <cstddef> + +#include "quiche/common/platform/api/quiche_export.h" +#include "quiche/spdy/core/zero_copy_output_buffer.h" + +namespace spdy { + +class QUICHE_NO_EXPORT 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/quiche/spdy/core/array_output_buffer_test.cc b/chromium/net/third_party/quiche/src/quiche/spdy/core/array_output_buffer_test.cc new file mode 100644 index 00000000000..0054a722819 --- /dev/null +++ b/chromium/net/third_party/quiche/src/quiche/spdy/core/array_output_buffer_test.cc @@ -0,0 +1,49 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "quiche/spdy/core/array_output_buffer.h" + +#include "quiche/common/platform/api/quiche_test.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/quiche/spdy/core/header_byte_listener_interface.h b/chromium/net/third_party/quiche/src/quiche/spdy/core/header_byte_listener_interface.h new file mode 100644 index 00000000000..7b738afed08 --- /dev/null +++ b/chromium/net/third_party/quiche/src/quiche/spdy/core/header_byte_listener_interface.h @@ -0,0 +1,22 @@ +#ifndef QUICHE_SPDY_CORE_HEADER_BYTE_LISTENER_INTERFACE_H_ +#define QUICHE_SPDY_CORE_HEADER_BYTE_LISTENER_INTERFACE_H_ + +#include <stddef.h> + +#include "quiche/common/platform/api/quiche_export.h" + +namespace spdy { + +// Listens for the receipt of uncompressed header bytes. +class QUICHE_EXPORT_PRIVATE HeaderByteListenerInterface { + public: + virtual ~HeaderByteListenerInterface() {} + + // Called when a header block has been parsed, with the number of uncompressed + // header bytes parsed from the header block. + virtual void OnHeaderBytesReceived(size_t uncompressed_header_bytes) = 0; +}; + +} // namespace spdy + +#endif // QUICHE_SPDY_CORE_HEADER_BYTE_LISTENER_INTERFACE_H_ diff --git a/chromium/net/third_party/quiche/src/quiche/spdy/core/hpack/hpack_constants.cc b/chromium/net/third_party/quiche/src/quiche/spdy/core/hpack/hpack_constants.cc new file mode 100644 index 00000000000..817216e648c --- /dev/null +++ b/chromium/net/third_party/quiche/src/quiche/spdy/core/hpack/hpack_constants.cc @@ -0,0 +1,374 @@ +// 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 "quiche/spdy/core/hpack/hpack_constants.h" + +#include <cstddef> +#include <memory> +#include <vector> + +#include "absl/base/macros.h" +#include "quiche/common/platform/api/quiche_logging.h" +#include "quiche/spdy/core/hpack/hpack_static_table.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, ABSL_ARRAYSIZE(name) - 1, value, ABSL_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 HpackStaticTable& ObtainHpackStaticTable() { + static const HpackStaticTable* const shared_static_table = []() { + auto* table = new HpackStaticTable(); + table->Initialize(HpackStaticTableVector().data(), + HpackStaticTableVector().size()); + QUICHE_CHECK(table->IsInitialized()); + return table; + }(); + return *shared_static_table; +} + +} // namespace spdy diff --git a/chromium/net/third_party/quiche/src/quiche/spdy/core/hpack/hpack_constants.h b/chromium/net/third_party/quiche/src/quiche/spdy/core/hpack/hpack_constants.h new file mode 100644 index 00000000000..6c91a47b944 --- /dev/null +++ b/chromium/net/third_party/quiche/src/quiche/spdy/core/hpack/hpack_constants.h @@ -0,0 +1,90 @@ +// 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 "quiche/common/platform/api/quiche_export.h" + +// All section references below are to +// https://httpwg.org/specs/rfc7540.html and +// https://httpwg.org/specs/rfc7541.html. + +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 HpackStaticTable; + +// RFC 7540, 6.5.2: Initial value for SETTINGS_HEADER_TABLE_SIZE. +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}; + +// RFC 7541, Appendix B: Huffman Code. +QUICHE_EXPORT_PRIVATE const std::vector<HpackHuffmanSymbol>& +HpackHuffmanCodeVector(); + +// RFC 7541, Appendix A: Static Table Definition. +QUICHE_EXPORT_PRIVATE const std::vector<HpackStaticEntry>& +HpackStaticTableVector(); + +// 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. +QUICHE_EXPORT_PRIVATE const HpackStaticTable& ObtainHpackStaticTable(); + +// RFC 7541, 8.1.2.1: Pseudo-headers start with a colon. +const char kPseudoHeaderPrefix = ':'; + +} // namespace spdy + +#endif // QUICHE_SPDY_CORE_HPACK_HPACK_CONSTANTS_H_ diff --git a/chromium/net/third_party/quiche/src/quiche/spdy/core/hpack/hpack_decoder_adapter.cc b/chromium/net/third_party/quiche/src/quiche/spdy/core/hpack/hpack_decoder_adapter.cc new file mode 100644 index 00000000000..874a7947d63 --- /dev/null +++ b/chromium/net/third_party/quiche/src/quiche/spdy/core/hpack/hpack_decoder_adapter.cc @@ -0,0 +1,164 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "quiche/spdy/core/hpack/hpack_decoder_adapter.h" + +#include "quiche/http2/decoder/decode_buffer.h" +#include "quiche/http2/decoder/decode_status.h" +#include "quiche/common/platform/api/quiche_logging.h" + +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), + max_header_block_bytes_(0), + header_block_started_(false), + error_(http2::HpackDecodingError::kOk) {} + +HpackDecoderAdapter::~HpackDecoderAdapter() = default; + +void HpackDecoderAdapter::ApplyHeaderTableSizeSetting(size_t size_setting) { + QUICHE_DVLOG(2) << "HpackDecoderAdapter::ApplyHeaderTableSizeSetting"; + hpack_decoder_.ApplyHeaderTableSizeSetting(size_setting); +} + +size_t HpackDecoderAdapter::GetCurrentHeaderTableSizeSetting() const { + return hpack_decoder_.GetCurrentHeaderTableSizeSetting(); +} + +void HpackDecoderAdapter::HandleControlFrameHeadersStart( + SpdyHeadersHandlerInterface* handler) { + QUICHE_DVLOG(2) << "HpackDecoderAdapter::HandleControlFrameHeadersStart"; + QUICHE_DCHECK(!header_block_started_); + listener_adapter_.set_handler(handler); +} + +bool HpackDecoderAdapter::HandleControlFrameHeadersData( + const char* headers_data, size_t headers_data_length) { + QUICHE_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; + error_ = hpack_decoder_.error(); + detailed_error_ = hpack_decoder_.detailed_error(); + 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) { + QUICHE_DCHECK_NE(headers_data, nullptr); + if (headers_data_length > max_decode_buffer_size_bytes_) { + QUICHE_DVLOG(1) << "max_decode_buffer_size_bytes_ < headers_data_length: " + << max_decode_buffer_size_bytes_ << " < " + << headers_data_length; + error_ = http2::HpackDecodingError::kFragmentTooLong; + detailed_error_ = ""; + return false; + } + listener_adapter_.AddToTotalHpackBytes(headers_data_length); + if (max_header_block_bytes_ != 0 && + listener_adapter_.total_hpack_bytes() > max_header_block_bytes_) { + error_ = http2::HpackDecodingError::kCompressedHeaderSizeExceedsLimit; + detailed_error_ = ""; + return false; + } + http2::DecodeBuffer db(headers_data, headers_data_length); + bool ok = hpack_decoder_.DecodeFragment(&db); + QUICHE_DCHECK(!ok || db.Empty()) << "Remaining=" << db.Remaining(); + if (!ok) { + error_ = hpack_decoder_.error(); + detailed_error_ = hpack_decoder_.detailed_error(); + } + return ok; + } + return true; +} + +bool HpackDecoderAdapter::HandleControlFrameHeadersComplete() { + QUICHE_DVLOG(2) << "HpackDecoderAdapter::HandleControlFrameHeadersComplete"; + if (!hpack_decoder_.EndDecodingBlock()) { + QUICHE_DVLOG(3) << "EndDecodingBlock returned false"; + error_ = hpack_decoder_.error(); + detailed_error_ = hpack_decoder_.detailed_error(); + return false; + } + header_block_started_ = false; + return true; +} + +const SpdyHeaderBlock& HpackDecoderAdapter::decoded_block() const { + return listener_adapter_.decoded_block(); +} + +void HpackDecoderAdapter::set_max_decode_buffer_size_bytes( + size_t max_decode_buffer_size_bytes) { + QUICHE_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); +} + +void HpackDecoderAdapter::set_max_header_block_bytes( + size_t max_header_block_bytes) { + max_header_block_bytes_ = max_header_block_bytes; +} + +HpackDecoderAdapter::ListenerAdapter::ListenerAdapter() : handler_(nullptr) {} +HpackDecoderAdapter::ListenerAdapter::~ListenerAdapter() = default; + +void HpackDecoderAdapter::ListenerAdapter::set_handler( + SpdyHeadersHandlerInterface* handler) { + handler_ = handler; +} + +void HpackDecoderAdapter::ListenerAdapter::OnHeaderListStart() { + QUICHE_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(const std::string& name, + const std::string& value) { + QUICHE_DVLOG(2) << "HpackDecoderAdapter::ListenerAdapter::OnHeader:\n name: " + << name << "\n value: " << value; + total_uncompressed_bytes_ += name.size() + value.size(); + if (handler_ == nullptr) { + QUICHE_DVLOG(3) << "Adding to decoded_block"; + decoded_block_.AppendValueOrAddHeader(name, value); + } else { + QUICHE_DVLOG(3) << "Passing to handler"; + handler_->OnHeader(name, value); + } +} + +void HpackDecoderAdapter::ListenerAdapter::OnHeaderListEnd() { + QUICHE_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( + absl::string_view error_message) { + QUICHE_VLOG(1) << error_message; +} + +} // namespace spdy diff --git a/chromium/net/third_party/quiche/src/quiche/spdy/core/hpack/hpack_decoder_adapter.h b/chromium/net/third_party/quiche/src/quiche/spdy/core/hpack/hpack_decoder_adapter.h new file mode 100644 index 00000000000..d1eefbd521f --- /dev/null +++ b/chromium/net/third_party/quiche/src/quiche/spdy/core/hpack/hpack_decoder_adapter.h @@ -0,0 +1,157 @@ +// 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 "absl/strings/string_view.h" +#include "quiche/http2/hpack/decoder/hpack_decoder.h" +#include "quiche/http2/hpack/decoder/hpack_decoder_listener.h" +#include "quiche/http2/hpack/decoder/hpack_decoder_tables.h" +#include "quiche/http2/hpack/http2_hpack_constants.h" +#include "quiche/common/platform/api/quiche_export.h" +#include "quiche/spdy/core/hpack/hpack_header_table.h" +#include "quiche/spdy/core/spdy_header_block.h" +#include "quiche/spdy/core/spdy_headers_handler_interface.h" + +namespace spdy { +namespace test { +class HpackDecoderAdapterPeer; +} // namespace test + +class QUICHE_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); + + // Returns the most recently applied value of SETTINGS_HEADER_TABLE_SIZE. + size_t GetCurrentHeaderTableSizeSetting() const; + + // 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. + bool HandleControlFrameHeadersComplete(); + + // 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; + + // Returns the current dynamic table size, including the 32 bytes per entry + // overhead mentioned in RFC 7541 section 4.1. + size_t GetDynamicTableSize() const { + return hpack_decoder_.GetDynamicTableSize(); + } + + // 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); + + // Specifies the maximum size of an on-the-wire header block that will be + // accepted. + void set_max_header_block_bytes(size_t max_header_block_bytes); + + // Error code if an error has occurred, Error::kOk otherwise. + http2::HpackDecodingError error() const { return error_; } + + std::string detailed_error() const { return detailed_error_; } + + private: + class QUICHE_EXPORT_PRIVATE ListenerAdapter + : public http2::HpackDecoderListener { + 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_; } + + // Override the HpackDecoderListener methods: + void OnHeaderListStart() override; + void OnHeader(const std::string& name, const std::string& value) override; + void OnHeaderListEnd() override; + void OnHeaderErrorDetected(absl::string_view error_message) override; + + 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_; + }; + + // 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_; + + // How much encoded data this decoder is willing to process. + size_t max_header_block_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_; + + // Error code if an error has occurred, Error::kOk otherwise. + http2::HpackDecodingError error_; + std::string detailed_error_; +}; + +} // namespace spdy + +#endif // QUICHE_SPDY_CORE_HPACK_HPACK_DECODER_ADAPTER_H_ diff --git a/chromium/net/third_party/quiche/src/quiche/spdy/core/hpack/hpack_decoder_adapter_test.cc b/chromium/net/third_party/quiche/src/quiche/spdy/core/hpack/hpack_decoder_adapter_test.cc new file mode 100644 index 00000000000..33cc4774f11 --- /dev/null +++ b/chromium/net/third_party/quiche/src/quiche/spdy/core/hpack/hpack_decoder_adapter_test.cc @@ -0,0 +1,1119 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "quiche/spdy/core/hpack/hpack_decoder_adapter.h" + +// Tests of HpackDecoderAdapter. + +#include <stdint.h> + +#include <string> +#include <tuple> +#include <utility> +#include <vector> + +#include "absl/base/macros.h" +#include "absl/strings/escaping.h" +#include "quiche/http2/hpack/decoder/hpack_decoder_state.h" +#include "quiche/http2/hpack/decoder/hpack_decoder_tables.h" +#include "quiche/http2/hpack/tools/hpack_block_builder.h" +#include "quiche/http2/test_tools/http2_random.h" +#include "quiche/common/platform/api/quiche_logging.h" +#include "quiche/common/platform/api/quiche_test.h" +#include "quiche/common/quiche_text_utils.h" +#include "quiche/spdy/core/hpack/hpack_constants.h" +#include "quiche/spdy/core/hpack/hpack_encoder.h" +#include "quiche/spdy/core/hpack/hpack_output_stream.h" +#include "quiche/spdy/core/recording_headers_handler.h" +#include "quiche/spdy/core/spdy_test_utils.h" + +using ::http2::HpackEntryType; +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(const std::string& name, + const std::string& value) { + decoder_->listener_adapter_.OnHeader(name, 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 QuicheTestWithParam<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(absl::string_view str) { + QUICHE_VLOG(3) << "HandleControlFrameHeadersData:\n" + << quiche::QuicheTextUtils::HexDump(str); + bytes_passed_in_ += str.size(); + return decoder_.HandleControlFrameHeadersData(str.data(), str.size()); + } + + bool HandleControlFrameHeadersComplete() { + bool rc = decoder_.HandleControlFrameHeadersComplete(); + return rc; + } + + bool DecodeHeaderBlock(absl::string_view 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; + } + if (start_choice_ == START_WITH_HANDLER) { + if (!HandleControlFrameHeadersComplete()) { + decode_has_failed_ = true; + return false; + } + EXPECT_EQ(handler_.compressed_header_bytes(), bytes_passed_in_); + } else { + if (!HandleControlFrameHeadersComplete()) { + decode_has_failed_ = true; + return false; + } + } + if (check_decoded_size && start_choice_ == START_WITH_HANDLER) { + EXPECT_EQ(handler_.uncompressed_header_bytes(), + 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(absl::string_view str) { + EXPECT_TRUE(DecodeHeaderBlock(str)); + return decoded_block(); + } + + void expectEntry(size_t index, size_t size, const std::string& name, + const std::string& 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<std::string, std::string>>& 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_; + RecordingHeadersHandler handler_; + StartChoice start_choice_; + bool randomly_split_input_buffer_; + bool decode_has_failed_ = false; + size_t bytes_passed_in_; +}; + +INSTANTIATE_TEST_SUITE_P( + NoHandler, HpackDecoderAdapterTest, + ::testing::Combine(::testing::Values(START_WITHOUT_HANDLER, NO_START), + ::testing::Bool())); + +INSTANTIATE_TEST_SUITE_P( + WithHandler, HpackDecoderAdapterTest, + ::testing::Combine(::testing::Values(START_WITH_HANDLER), + ::testing::Bool())); + +TEST_P(HpackDecoderAdapterTest, ApplyHeaderTableSizeSetting) { + EXPECT_EQ(4096u, decoder_.GetCurrentHeaderTableSizeSetting()); + decoder_.ApplyHeaderTableSizeSetting(12 * 1024); + EXPECT_EQ(12288u, decoder_.GetCurrentHeaderTableSizeSetting()); +} + +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 std::string a_value = std::string(49, 'x'); + decoder_.set_max_decode_buffer_size_bytes(kMaxBufferSizeBytes); + HpackBlockBuilder hbb; + hbb.AppendLiteralNameAndValue(HpackEntryType::kNeverIndexedLiteralHeader, + false, "a", false, a_value); + const std::string& 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 std::string name = std::string(2 * kMaxBufferSizeBytes, 'x'); + const std::string 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 std::string 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 std::string name = "some-key"; + const std::string 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 std::string fragment = hbb.buffer().substr(0, fragment_size); + + HandleControlFrameHeadersStart(); + EXPECT_FALSE(HandleControlFrameHeadersData(fragment)); +} + +// Verify that a header block that exceeds the maximum length is rejected. +TEST_P(HpackDecoderAdapterTest, HeaderBlockTooLong) { + const std::string name = "some-key"; + const std::string value = "some-value"; + const size_t kMaxBufferSizeBytes = 1024; + + HpackBlockBuilder hbb; + hbb.AppendLiteralNameAndValue(HpackEntryType::kIndexedLiteralHeader, false, + name, false, value); + while (hbb.size() < kMaxBufferSizeBytes) { + hbb.AppendLiteralNameAndValue(HpackEntryType::kIndexedLiteralHeader, false, + "", false, ""); + } + // With no limit on the maximum header block size, the decoder handles the + // entire block successfully. + HandleControlFrameHeadersStart(); + EXPECT_TRUE(HandleControlFrameHeadersData(hbb.buffer())); + EXPECT_TRUE(HandleControlFrameHeadersComplete()); + + // When a total byte limit is imposed, the decoder bails before the end of the + // block. + decoder_.set_max_header_block_bytes(kMaxBufferSizeBytes); + HandleControlFrameHeadersStart(); + EXPECT_FALSE(HandleControlFrameHeadersData(hbb.buffer())); +} + +// 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<std::string, std::string>> 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")); + + EXPECT_TRUE(HandleControlFrameHeadersComplete()); + + 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", + std::string("foo\0baz", 7)); + + // Other headers are joined on \0. Case matters. + decoder_peer_.HandleHeaderRepresentation("joined", "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(); + + // 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", absl::string_view("foo\0baz", 7)), + Pair("joined", absl::string_view("joined\0value 1\0value 2", 22)), + Pair("empty", ""), + Pair("empty-joined", absl::string_view("\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()); + std::string input; + { + // Maximum-size update with size 126. Succeeds. + HpackOutputStream output_stream; + output_stream.AppendPrefix(kHeaderTableSizeUpdateOpcode); + output_stream.AppendUint32(126); + + input = output_stream.TakeString(); + EXPECT_TRUE(DecodeHeaderBlock(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); + + input = output_stream.TakeString(); + EXPECT_TRUE(DecodeHeaderBlock(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); + + input = output_stream.TakeString(); + EXPECT_FALSE(DecodeHeaderBlock(input)); + EXPECT_EQ(kDefaultHeaderTableSizeSetting, + decoder_peer_.header_table_size_limit()); + } +} + +// Two HeaderTableSizeUpdates may appear at the beginning of the block +TEST_P(HpackDecoderAdapterTest, TwoTableSizeUpdates) { + std::string 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); + + input = output_stream.TakeString(); + EXPECT_TRUE(DecodeHeaderBlock(input)); + EXPECT_EQ(122u, decoder_peer_.header_table_size_limit()); + } +} + +// Three HeaderTableSizeUpdates should result in an error +TEST_P(HpackDecoderAdapterTest, ThreeTableSizeUpdatesError) { + std::string 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); + + input = output_stream.TakeString(); + + EXPECT_FALSE(DecodeHeaderBlock(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) { + std::string 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); + + input = output_stream.TakeString(); + + EXPECT_FALSE(DecodeHeaderBlock(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) { + std::string 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); + + input = output_stream.TakeString(); + + EXPECT_FALSE(DecodeHeaderBlock(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( + absl::string_view(input, ABSL_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( + absl::string_view(input, ABSL_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(absl::string_view("\x7d\x03ooo"))); + // Name is one beyond the last static index. Fails. + EXPECT_FALSE(DecodeHeaderBlock(absl::string_view("\x7e\x03ooo"))); +} + +TEST_P(HpackDecoderAdapterTest, LiteralHeaderNoIndexingInvalidNameIndex) { + // Name is the last static index. Works. + EXPECT_TRUE(DecodeHeaderBlock(absl::string_view("\x0f\x2e\x03ooo"))); + // Name is one beyond the last static index. Fails. + EXPECT_FALSE(DecodeHeaderBlock(absl::string_view("\x0f\x2f\x03ooo"))); +} + +TEST_P(HpackDecoderAdapterTest, LiteralHeaderNeverIndexedInvalidNameIndex) { + // Name is the last static index. Works. + EXPECT_TRUE(DecodeHeaderBlock(absl::string_view("\x1f\x2e\x03ooo"))); + // Name is one beyond the last static index. Fails. + EXPECT_FALSE(DecodeHeaderBlock(absl::string_view("\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 + + std::string first = absl::HexStringToBytes("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 + + std::string first = absl::HexStringToBytes("418cf1e3c2e5f23a6ba0ab90f4ff"); + EXPECT_TRUE(DecodeHeaderBlock(first)); + first = absl::HexStringToBytes("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; + + 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"; + + std::string encoded_header_set = + encoder.EncodeHeaderBlock(expected_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 + std::string first = + absl::HexStringToBytes("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 + + std::string second = absl::HexStringToBytes("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 + std::string third = absl::HexStringToBytes( + "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 + + std::string first = absl::HexStringToBytes( + "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 + std::string second = absl::HexStringToBytes("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 + std::string third = absl::HexStringToBytes( + "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 absl::string_view name("some-name"); + const absl::string_view value1("some-value"); + const absl::string_view value2("another-value"); + const absl::string_view 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. + std::string 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_.uncompressed_header_bytes(), + 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(absl::HexStringToBytes("608294e76003626172"))); + EXPECT_EQ(expected_header_set, decoded_block()); +} + +} // namespace +} // namespace test +} // namespace spdy diff --git a/chromium/net/third_party/quiche/src/quiche/spdy/core/hpack/hpack_encoder.cc b/chromium/net/third_party/quiche/src/quiche/spdy/core/hpack/hpack_encoder.cc new file mode 100644 index 00000000000..7bfb8a45cd7 --- /dev/null +++ b/chromium/net/third_party/quiche/src/quiche/spdy/core/hpack/hpack_encoder.cc @@ -0,0 +1,374 @@ +// 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 "quiche/spdy/core/hpack/hpack_encoder.h" + +#include <algorithm> +#include <limits> +#include <utility> + +#include "quiche/http2/hpack/huffman/hpack_huffman_encoder.h" +#include "quiche/common/platform/api/quiche_bug_tracker.h" +#include "quiche/common/platform/api/quiche_logging.h" +#include "quiche/spdy/core/hpack/hpack_constants.h" +#include "quiche/spdy/core/hpack/hpack_header_table.h" +#include "quiche/spdy/core/hpack/hpack_output_stream.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(absl::string_view /*name*/, absl::string_view /*value*/) {} + +// The default HPACK indexing policy. +bool DefaultPolicy(absl::string_view name, absl::string_view /* 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() + : output_stream_(), + 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; + +std::string HpackEncoder::EncodeHeaderBlock(const SpdyHeaderBlock& header_set) { + // 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); + return EncodeRepresentations(&iter); +} + +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; +} + +std::string HpackEncoder::EncodeRepresentations(RepresentationIterator* iter) { + MaybeEmitTableSize(); + while (iter->HasNext()) { + const auto header = iter->Next(); + listener_(header.first, header.second); + if (enable_compression_) { + size_t index = + header_table_.GetByNameAndValue(header.first, header.second); + if (index != kHpackEntryNotFound) { + EmitIndex(index); + } else if (should_index_(header.first, header.second)) { + EmitIndexedLiteral(header); + } else { + EmitNonIndexedLiteral(header, enable_compression_); + } + } else { + EmitNonIndexedLiteral(header, enable_compression_); + } + } + + return output_stream_.TakeString(); +} + +void HpackEncoder::EmitIndex(size_t index) { + QUICHE_DVLOG(2) << "Emitting index " << index; + output_stream_.AppendPrefix(kIndexedOpcode); + output_stream_.AppendUint32(index); +} + +void HpackEncoder::EmitIndexedLiteral(const Representation& representation) { + QUICHE_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, + bool enable_compression) { + QUICHE_DVLOG(2) << "Emitting nonindexed literal: (" << representation.first + << ", " << representation.second << ")"; + output_stream_.AppendPrefix(kLiteralNoIndexOpcode); + size_t name_index = header_table_.GetByName(representation.first); + if (enable_compression && name_index != kHpackEntryNotFound) { + output_stream_.AppendUint32(name_index); + } else { + output_stream_.AppendUint32(0); + EmitString(representation.first); + } + EmitString(representation.second); +} + +void HpackEncoder::EmitLiteral(const Representation& representation) { + size_t name_index = header_table_.GetByName(representation.first); + if (name_index != kHpackEntryNotFound) { + output_stream_.AppendUint32(name_index); + } else { + output_stream_.AppendUint32(0); + EmitString(representation.first); + } + EmitString(representation.second); +} + +void HpackEncoder::EmitString(absl::string_view str) { + size_t encoded_size = + enable_compression_ ? http2::HuffmanSize(str) : str.size(); + if (encoded_size < str.size()) { + QUICHE_DVLOG(2) << "Emitted Huffman-encoded string of length " + << encoded_size; + output_stream_.AppendPrefix(kStringLiteralHuffmanEncoded); + output_stream_.AppendUint32(encoded_size); + http2::HuffmanEncodeFast(str, encoded_size, output_stream_.MutableString()); + } else { + QUICHE_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(); + QUICHE_DVLOG(1) << "MaybeEmitTableSize current_size=" << current_size; + QUICHE_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. + absl::string_view cookie_value = cookie.second; + // Consume leading and trailing whitespace if present. + absl::string_view::size_type first = cookie_value.find_first_not_of(" \t"); + absl::string_view::size_type last = cookie_value.find_last_not_of(" \t"); + if (first == absl::string_view::npos) { + cookie_value = absl::string_view(); + } else { + cookie_value = cookie_value.substr(first, (last - first) + 1); + } + for (size_t pos = 0;;) { + size_t end = cookie_value.find(';', pos); + + if (end == absl::string_view::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 != absl::string_view::npos) { + end = header_field.second.find('\0', pos); + out->push_back(std::make_pair( + header_field.first, + header_field.second.substr( + pos, end == absl::string_view::npos ? end : end - pos))); + pos = end + 1; + } +} + +// Iteratively encodes a SpdyHeaderBlock. +class HpackEncoder::Encoderator : public ProgressiveEncoder { + public: + Encoderator(const SpdyHeaderBlock& header_set, HpackEncoder* encoder); + Encoderator(const Representations& representations, 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 and returns up to max_encoded_bytes of the current header block. + std::string Next(size_t max_encoded_bytes) 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. + 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_); + } + } + header_it_ = std::make_unique<RepresentationIterator>(pseudo_headers_, + regular_headers_); + + encoder_->MaybeEmitTableSize(); +} + +HpackEncoder::Encoderator::Encoderator(const Representations& representations, + HpackEncoder* encoder) + : encoder_(encoder), has_next_(true) { + for (const auto& header : representations) { + if (header.first == "cookie") { + CookieToCrumbs(header, ®ular_headers_); + } else if (!header.first.empty() && + header.first[0] == kPseudoHeaderPrefix) { + pseudo_headers_.push_back(header); + } else { + regular_headers_.push_back(header); + } + } + header_it_ = std::make_unique<RepresentationIterator>(pseudo_headers_, + regular_headers_); + + encoder_->MaybeEmitTableSize(); +} + +std::string HpackEncoder::Encoderator::Next(size_t max_encoded_bytes) { + QUICHE_BUG_IF(spdy_bug_61_1, !has_next_) + << "Encoderator::Next called with nothing left to encode."; + const bool enable_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 (enable_compression) { + size_t index = encoder_->header_table_.GetByNameAndValue(header.first, + header.second); + if (index != kHpackEntryNotFound) { + encoder_->EmitIndex(index); + } else if (encoder_->should_index_(header.first, header.second)) { + encoder_->EmitIndexedLiteral(header); + } else { + encoder_->EmitNonIndexedLiteral(header, enable_compression); + } + } else { + encoder_->EmitNonIndexedLiteral(header, enable_compression); + } + } + + has_next_ = encoder_->output_stream_.size() > max_encoded_bytes; + return encoder_->output_stream_.BoundedTakeString(max_encoded_bytes); +} + +std::unique_ptr<HpackEncoder::ProgressiveEncoder> HpackEncoder::EncodeHeaderSet( + const SpdyHeaderBlock& header_set) { + return std::make_unique<Encoderator>(header_set, this); +} + +std::unique_ptr<HpackEncoder::ProgressiveEncoder> +HpackEncoder::EncodeRepresentations(const Representations& representations) { + return std::make_unique<Encoderator>(representations, this); +} + +} // namespace spdy diff --git a/chromium/net/third_party/quiche/src/quiche/spdy/core/hpack/hpack_encoder.h b/chromium/net/third_party/quiche/src/quiche/spdy/core/hpack/hpack_encoder.h new file mode 100644 index 00000000000..4974e198ddf --- /dev/null +++ b/chromium/net/third_party/quiche/src/quiche/spdy/core/hpack/hpack_encoder.h @@ -0,0 +1,146 @@ +// 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 <string> +#include <utility> +#include <vector> + +#include "absl/strings/string_view.h" +#include "quiche/common/platform/api/quiche_export.h" +#include "quiche/spdy/core/hpack/hpack_header_table.h" +#include "quiche/spdy/core/hpack/hpack_output_stream.h" +#include "quiche/spdy/core/spdy_protocol.h" + +// An HpackEncoder encodes header sets as outlined in +// http://tools.ietf.org/html/rfc7541. + +namespace spdy { + +namespace test { +class HpackEncoderPeer; +} // namespace test + +class QUICHE_EXPORT_PRIVATE HpackEncoder { + public: + using Representation = std::pair<absl::string_view, absl::string_view>; + 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(absl::string_view, absl::string_view)>; + + // 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(absl::string_view, absl::string_view)>; + + HpackEncoder(); + HpackEncoder(const HpackEncoder&) = delete; + HpackEncoder& operator=(const HpackEncoder&) = delete; + ~HpackEncoder(); + + // Encodes and returns the given header set as a string. + std::string EncodeHeaderBlock(const SpdyHeaderBlock& header_set); + + class QUICHE_EXPORT_PRIVATE ProgressiveEncoder { + public: + virtual ~ProgressiveEncoder() {} + + // Returns true iff more remains to encode. + virtual bool HasNext() const = 0; + + // Encodes and returns up to max_encoded_bytes of the current header block. + virtual std::string Next(size_t max_encoded_bytes) = 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); + // Returns a ProgressiveEncoder which must be outlived by this HpackEncoder. + // The encoder will not attempt to split any \0-delimited values in + // |representations|. If such splitting is desired, it must be performed by + // the caller when constructing the list of representations. + std::unique_ptr<ProgressiveEncoder> EncodeRepresentations( + const Representations& representations); + + // 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); + + // TODO(birenroy): Rename this GetDynamicTableCapacity(). + 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 DisableCompression() { enable_compression_ = false; } + + // Returns the current dynamic table size, including the 32 bytes per entry + // overhead mentioned in RFC 7541 section 4.1. + size_t GetDynamicTableSize() const { return header_table_.size(); } + + private: + friend class test::HpackEncoderPeer; + + class RepresentationIterator; + class Encoderator; + + // Encodes a sequence of header name-value pairs as a single header block. + std::string EncodeRepresentations(RepresentationIterator* iter); + + // Emits a static/dynamic indexed representation (Section 7.1). + void EmitIndex(size_t index); + + // Emits a literal representation (Section 7.2). + void EmitIndexedLiteral(const Representation& representation); + void EmitNonIndexedLiteral(const Representation& representation, + bool enable_compression); + void EmitLiteral(const Representation& representation); + + // Emits a Huffman or identity string (whichever is smaller). + void EmitString(absl::string_view 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); + + HpackHeaderTable header_table_; + HpackOutputStream output_stream_; + + 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/quiche/spdy/core/hpack/hpack_encoder_test.cc b/chromium/net/third_party/quiche/src/quiche/spdy/core/hpack/hpack_encoder_test.cc new file mode 100644 index 00000000000..886b9ed748f --- /dev/null +++ b/chromium/net/third_party/quiche/src/quiche/spdy/core/hpack/hpack_encoder_test.cc @@ -0,0 +1,753 @@ +// 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 "quiche/spdy/core/hpack/hpack_encoder.h" + +#include <cstdint> +#include <utility> + +#include "quiche/http2/hpack/huffman/hpack_huffman_encoder.h" +#include "quiche/http2/test_tools/http2_random.h" +#include "quiche/common/platform/api/quiche_test.h" +#include "quiche/spdy/core/hpack/hpack_static_table.h" +#include "quiche/spdy/core/spdy_simple_arena.h" + +namespace spdy { + +namespace test { + +class HpackHeaderTablePeer { + public: + explicit HpackHeaderTablePeer(HpackHeaderTable* table) : table_(table) {} + + const HpackEntry* GetFirstStaticEntry() const { + return &table_->static_entries_.front(); + } + + HpackHeaderTable::DynamicEntryTable* 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()); } + void EmitString(absl::string_view str) { encoder_->EmitString(str); } + void TakeString(std::string* out) { + *out = encoder_->output_stream_.TakeString(); + } + static void CookieToCrumbs(absl::string_view cookie, + std::vector<absl::string_view>* 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(absl::string_view value, + std::vector<absl::string_view>* 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 std::string EncodeHeaderBlock(HpackEncoder* encoder, + const SpdyHeaderBlock& header_set) { + return encoder->EncodeHeaderBlock(header_set); + } + + static bool EncodeIncremental(HpackEncoder* encoder, + const SpdyHeaderBlock& header_set, + std::string* output) { + std::unique_ptr<HpackEncoder::ProgressiveEncoder> encoderator = + encoder->EncodeHeaderSet(header_set); + http2::test::Http2Random random; + std::string output_buffer = encoderator->Next(random.UniformInRange(0, 16)); + while (encoderator->HasNext()) { + std::string second_buffer = + encoderator->Next(random.UniformInRange(0, 16)); + output_buffer.append(second_buffer); + } + *output = std::move(output_buffer); + return true; + } + + static bool EncodeRepresentations(HpackEncoder* encoder, + const Representations& representations, + std::string* output) { + std::unique_ptr<HpackEncoder::ProgressiveEncoder> encoderator = + encoder->EncodeRepresentations(representations); + http2::test::Http2Random random; + std::string output_buffer = encoderator->Next(random.UniformInRange(0, 16)); + while (encoderator->HasNext()) { + std::string second_buffer = + encoderator->Next(random.UniformInRange(0, 16)); + output_buffer.append(second_buffer); + } + *output = std::move(output_buffer); + return true; + } + + private: + HpackEncoder* encoder_; +}; + +} // namespace test + +namespace { + +using testing::ElementsAre; +using testing::Pair; + +const size_t kStaticEntryIndex = 1; + +enum EncodeStrategy { + kDefault, + kIncremental, + kRepresentations, +}; + +class HpackEncoderTest : public QuicheTestWithParam<EncodeStrategy> { + protected: + typedef test::HpackEncoderPeer::Representations Representations; + + HpackEncoderTest() + : peer_(&encoder_), + static_(peer_.table_peer().GetFirstStaticEntry()), + dynamic_table_insertions_(0), + headers_storage_(1024 /* block size */), + strategy_(GetParam()) {} + + void SetUp() override { + // 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_1_index_ = dynamic_table_insertions_++; + key_2_ = peer_.table()->TryAddEntry("key2", "value2"); + key_2_index_ = dynamic_table_insertions_++; + cookie_a_ = peer_.table()->TryAddEntry("cookie", "a=bb"); + cookie_a_index_ = dynamic_table_insertions_++; + cookie_c_ = peer_.table()->TryAddEntry("cookie", "c=dd"); + cookie_c_index_ = dynamic_table_insertions_++; + + // No further insertions may occur without evictions. + peer_.table()->SetMaxSize(peer_.table()->size()); + QUICHE_CHECK_EQ(kInitialDynamicTableSize, peer_.table()->size()); + } + + void SaveHeaders(absl::string_view name, absl::string_view value) { + absl::string_view n(headers_storage_.Memdup(name.data(), name.size()), + name.size()); + absl::string_view 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(size_t key_index, absl::string_view value) { + expected_.AppendPrefix(kLiteralIncrementalIndexOpcode); + expected_.AppendUint32(key_index); + ExpectString(&expected_, value); + } + void ExpectIndexedLiteral(absl::string_view name, absl::string_view value) { + expected_.AppendPrefix(kLiteralIncrementalIndexOpcode); + expected_.AppendUint32(0); + ExpectString(&expected_, name); + ExpectString(&expected_, value); + } + void ExpectNonIndexedLiteral(absl::string_view name, + absl::string_view value) { + expected_.AppendPrefix(kLiteralNoIndexOpcode); + expected_.AppendUint32(0); + ExpectString(&expected_, name); + ExpectString(&expected_, value); + } + void ExpectNonIndexedLiteralWithNameIndex(size_t key_index, + absl::string_view value) { + expected_.AppendPrefix(kLiteralNoIndexOpcode); + expected_.AppendUint32(key_index); + ExpectString(&expected_, value); + } + void ExpectString(HpackOutputStream* stream, absl::string_view str) { + size_t encoded_size = + peer_.compression_enabled() ? http2::HuffmanSize(str) : str.size(); + if (encoded_size < str.size()) { + expected_.AppendPrefix(kStringLiteralHuffmanEncoded); + expected_.AppendUint32(encoded_size); + http2::HuffmanEncodeFast(str, encoded_size, stream->MutableString()); + } else { + expected_.AppendPrefix(kStringLiteralIdentityEncoded); + expected_.AppendUint32(str.size()); + expected_.AppendBytes(str); + } + } + void ExpectHeaderTableSizeUpdate(uint32_t size) { + expected_.AppendPrefix(kHeaderTableSizeUpdateOpcode); + expected_.AppendUint32(size); + } + Representations MakeRepresentations(const SpdyHeaderBlock& header_set) { + Representations r; + for (const auto& header : header_set) { + r.push_back(header); + } + return r; + } + void CompareWithExpectedEncoding(const SpdyHeaderBlock& header_set) { + std::string actual_out; + std::string expected_out = expected_.TakeString(); + switch (strategy_) { + case kDefault: + actual_out = + test::HpackEncoderPeer::EncodeHeaderBlock(&encoder_, header_set); + break; + case kIncremental: + EXPECT_TRUE(test::HpackEncoderPeer::EncodeIncremental( + &encoder_, header_set, &actual_out)); + break; + case kRepresentations: + EXPECT_TRUE(test::HpackEncoderPeer::EncodeRepresentations( + &encoder_, MakeRepresentations(header_set), &actual_out)); + break; + } + EXPECT_EQ(expected_out, actual_out); + } + void CompareWithExpectedEncoding(const Representations& representations) { + std::string actual_out; + std::string expected_out = expected_.TakeString(); + EXPECT_TRUE(test::HpackEncoderPeer::EncodeRepresentations( + &encoder_, representations, &actual_out)); + EXPECT_EQ(expected_out, actual_out); + } + // Converts the index of a dynamic table entry to the HPACK index. + // In these test, dynamic table entries are indexed sequentially, starting + // with 0. The HPACK indexing scheme is defined at + // https://httpwg.org/specs/rfc7541.html#index.address.space. + size_t DynamicIndexToWireIndex(size_t index) { + return dynamic_table_insertions_ - index + kStaticTableSize; + } + + HpackEncoder encoder_; + test::HpackEncoderPeer peer_; + + // Calculated based on the names and values inserted in SetUp(), above. + const size_t kInitialDynamicTableSize = 4 * (10 + 32); + + const HpackEntry* static_; + const HpackEntry* key_1_; + const HpackEntry* key_2_; + const HpackEntry* cookie_a_; + const HpackEntry* cookie_c_; + size_t key_1_index_; + size_t key_2_index_; + size_t cookie_a_index_; + size_t cookie_c_index_; + size_t dynamic_table_insertions_; + + SpdySimpleArena headers_storage_; + std::vector<std::pair<absl::string_view, absl::string_view>> + headers_observed_; + + HpackOutputStream expected_; + const EncodeStrategy strategy_; +}; + +using HpackEncoderTestWithDefaultStrategy = HpackEncoderTest; + +INSTANTIATE_TEST_SUITE_P(HpackEncoderTests, HpackEncoderTestWithDefaultStrategy, + ::testing::Values(kDefault)); + +TEST_P(HpackEncoderTestWithDefaultStrategy, EncodeRepresentations) { + EXPECT_EQ(kInitialDynamicTableSize, encoder_.GetDynamicTableSize()); + encoder_.SetHeaderListener( + [this](absl::string_view name, absl::string_view value) { + this->SaveHeaders(name, value); + }); + const std::vector<std::pair<absl::string_view, absl::string_view>> + header_list = {{"cookie", "val1; val2;val3"}, + {":path", "/home"}, + {"accept", "text/html, text/plain,application/xml"}, + {"cookie", "val4"}, + {"withnul", absl::string_view("one\0two", 7)}}; + ExpectNonIndexedLiteralWithNameIndex(peer_.table()->GetByName(":path"), + "/home"); + ExpectIndexedLiteral(peer_.table()->GetByName("cookie"), "val1"); + ExpectIndexedLiteral(peer_.table()->GetByName("cookie"), "val2"); + ExpectIndexedLiteral(peer_.table()->GetByName("cookie"), "val3"); + ExpectIndexedLiteral(peer_.table()->GetByName("accept"), + "text/html, text/plain,application/xml"); + ExpectIndexedLiteral(peer_.table()->GetByName("cookie"), "val4"); + ExpectIndexedLiteral("withnul", absl::string_view("one\0two", 7)); + + CompareWithExpectedEncoding(header_list); + EXPECT_THAT( + headers_observed_, + ElementsAre(Pair(":path", "/home"), Pair("cookie", "val1"), + Pair("cookie", "val2"), Pair("cookie", "val3"), + Pair("accept", "text/html, text/plain,application/xml"), + Pair("cookie", "val4"), + Pair("withnul", absl::string_view("one\0two", 7)))); + // Insertions and evictions have happened over the course of the test. + EXPECT_GE(kInitialDynamicTableSize, encoder_.GetDynamicTableSize()); +} + +TEST_P(HpackEncoderTestWithDefaultStrategy, DynamicTableGrows) { + EXPECT_EQ(kInitialDynamicTableSize, encoder_.GetDynamicTableSize()); + peer_.table()->SetMaxSize(4096); + encoder_.SetHeaderListener( + [this](absl::string_view name, absl::string_view value) { + this->SaveHeaders(name, value); + }); + const std::vector<std::pair<absl::string_view, absl::string_view>> + header_list = {{"cookie", "val1; val2;val3"}, + {":path", "/home"}, + {"accept", "text/html, text/plain,application/xml"}, + {"cookie", "val4"}, + {"withnul", absl::string_view("one\0two", 7)}}; + std::string out; + EXPECT_TRUE(test::HpackEncoderPeer::EncodeRepresentations(&encoder_, + header_list, &out)); + + EXPECT_FALSE(out.empty()); + // Insertions have happened over the course of the test. + EXPECT_GT(encoder_.GetDynamicTableSize(), kInitialDynamicTableSize); +} + +INSTANTIATE_TEST_SUITE_P(HpackEncoderTests, HpackEncoderTest, + ::testing::Values(kDefault, kIncremental, + kRepresentations)); + +TEST_P(HpackEncoderTest, SingleDynamicIndex) { + encoder_.SetHeaderListener( + [this](absl::string_view name, absl::string_view value) { + this->SaveHeaders(name, value); + }); + + ExpectIndex(DynamicIndexToWireIndex(key_2_index_)); + + 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(kStaticEntryIndex); + + SpdyHeaderBlock headers; + headers[static_->name()] = static_->value(); + CompareWithExpectedEncoding(headers); +} + +TEST_P(HpackEncoderTest, SingleStaticIndexTooLarge) { + peer_.table()->SetMaxSize(1); // Also evicts all fixtures. + ExpectIndex(kStaticEntryIndex); + + SpdyHeaderBlock headers; + headers[static_->name()] = static_->value(); + CompareWithExpectedEncoding(headers); + + EXPECT_EQ(0u, peer_.table_peer().dynamic_entries()->size()); +} + +TEST_P(HpackEncoderTest, SingleLiteralWithIndexName) { + ExpectIndexedLiteral(DynamicIndexToWireIndex(key_2_index_), "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().get(); + 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().get(); + 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(DynamicIndexToWireIndex(key_1_index_)); + ExpectIndexedLiteral("key3", "value3"); + + SpdyHeaderBlock headers; + headers[key_1_->name()] = key_1_->value(); + headers["key3"] = "value3"; + CompareWithExpectedEncoding(headers); +} + +TEST_P(HpackEncoderTest, CookieHeaderIsCrumbled) { + ExpectIndex(DynamicIndexToWireIndex(cookie_a_index_)); + ExpectIndex(DynamicIndexToWireIndex(cookie_c_index_)); + ExpectIndexedLiteral(peer_.table()->GetByName("cookie"), "e=ff"); + + SpdyHeaderBlock headers; + headers["cookie"] = "a=bb; c=dd; e=ff"; + CompareWithExpectedEncoding(headers); +} + +TEST_P(HpackEncoderTest, MultiValuedHeadersNotCrumbled) { + ExpectIndexedLiteral("foo", "bar, baz"); + SpdyHeaderBlock headers; + headers["foo"] = "bar, baz"; + 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("@@@@@@"); + + std::string actual_out; + std::string expected_out = expected_.TakeString(); + peer_.TakeString(&actual_out); + EXPECT_EQ(expected_out, actual_out); +} + +TEST_P(HpackEncoderTest, EncodingWithoutCompression) { + encoder_.SetHeaderListener( + [this](absl::string_view name, absl::string_view value) { + this->SaveHeaders(name, value); + }); + encoder_.DisableCompression(); + + ExpectNonIndexedLiteral(":path", "/index.html"); + ExpectNonIndexedLiteral("cookie", "foo=bar"); + ExpectNonIndexedLiteral("cookie", "baz=bing"); + if (strategy_ == kRepresentations) { + ExpectNonIndexedLiteral("hello", std::string("goodbye\0aloha", 13)); + } else { + ExpectNonIndexedLiteral("hello", "goodbye"); + ExpectNonIndexedLiteral("hello", "aloha"); + } + ExpectNonIndexedLiteral("multivalue", "value1, value2"); + + SpdyHeaderBlock headers; + headers[":path"] = "/index.html"; + headers["cookie"] = "foo=bar; baz=bing"; + headers["hello"] = "goodbye"; + headers.AppendValueOrAddHeader("hello", "aloha"); + headers["multivalue"] = "value1, value2"; + + CompareWithExpectedEncoding(headers); + + if (strategy_ == kRepresentations) { + EXPECT_THAT( + headers_observed_, + ElementsAre(Pair(":path", "/index.html"), Pair("cookie", "foo=bar"), + Pair("cookie", "baz=bing"), + Pair("hello", absl::string_view("goodbye\0aloha", 13)), + Pair("multivalue", "value1, value2"))); + } else { + EXPECT_THAT( + headers_observed_, + ElementsAre(Pair(":path", "/index.html"), Pair("cookie", "foo=bar"), + Pair("cookie", "baz=bing"), Pair("hello", "goodbye"), + Pair("hello", "aloha"), + Pair("multivalue", "value1, value2"))); + } + EXPECT_EQ(kInitialDynamicTableSize, encoder_.GetDynamicTableSize()); +} + +TEST_P(HpackEncoderTest, MultipleEncodingPasses) { + encoder_.SetHeaderListener( + [this](absl::string_view name, absl::string_view value) { + this->SaveHeaders(name, value); + }); + + // Pass 1. + { + SpdyHeaderBlock headers; + headers["key1"] = "value1"; + headers["cookie"] = "a=bb"; + + ExpectIndex(DynamicIndexToWireIndex(key_1_index_)); + ExpectIndex(DynamicIndexToWireIndex(cookie_a_index_)); + 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(DynamicIndexToWireIndex(key_2_index_)); + // "cookie: c=dd" + ExpectIndex(DynamicIndexToWireIndex(cookie_c_index_)); + // This cookie evicts |key1| from the dynamic table. + ExpectIndexedLiteral(peer_.table()->GetByName("cookie"), "e=ff"); + dynamic_table_insertions_++; + + 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" + EXPECT_EQ(65u, DynamicIndexToWireIndex(key_2_index_)); + ExpectIndex(DynamicIndexToWireIndex(key_2_index_)); + // "cookie: a=bb" + EXPECT_EQ(64u, DynamicIndexToWireIndex(cookie_a_index_)); + ExpectIndex(DynamicIndexToWireIndex(cookie_a_index_)); + // This cookie evicts |key2| from the dynamic table. + ExpectIndexedLiteral(peer_.table()->GetByName("cookie"), "b=cc"); + dynamic_table_insertions_++; + // "cookie: c=dd" + ExpectIndex(DynamicIndexToWireIndex(cookie_c_index_)); + + 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. + ExpectNonIndexedLiteralWithNameIndex(peer_.table()->GetByName(":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<absl::string_view> 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<absl::string_view> out; + + peer.DecomposeRepresentation("", &out); + EXPECT_THAT(out, ElementsAre("")); + + peer.DecomposeRepresentation("foobar", &out); + EXPECT_THAT(out, ElementsAre("foobar")); + + peer.DecomposeRepresentation(absl::string_view("foo\0bar", 7), &out); + EXPECT_THAT(out, ElementsAre("foo", "bar")); + + peer.DecomposeRepresentation(absl::string_view("\0foo\0bar", 8), &out); + EXPECT_THAT(out, ElementsAre("", "foo", "bar")); + + peer.DecomposeRepresentation(absl::string_view("foo\0bar\0", 8), &out); + EXPECT_THAT(out, ElementsAre("foo", "bar", "")); + + peer.DecomposeRepresentation(absl::string_view("\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) { + if (strategy_ == kRepresentations) { + // When HpackEncoder is asked to encode a list of Representations, the + // caller must crumble null-delimited values. + return; + } + SpdyHeaderBlock headers; + // A header field to be crumbled: "spam: foo\0bar". + headers["spam"] = std::string("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().get(); + 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().get(); + 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().get(); + 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().get(); + EXPECT_EQ(new_entry->name(), "key3"); + EXPECT_EQ(new_entry->value(), "value3"); +} + +} // namespace + +} // namespace spdy diff --git a/chromium/net/third_party/quiche/src/quiche/spdy/core/hpack/hpack_entry.cc b/chromium/net/third_party/quiche/src/quiche/spdy/core/hpack/hpack_entry.cc new file mode 100644 index 00000000000..437b5d04f16 --- /dev/null +++ b/chromium/net/third_party/quiche/src/quiche/spdy/core/hpack/hpack_entry.cc @@ -0,0 +1,24 @@ +// 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 "quiche/spdy/core/hpack/hpack_entry.h" + +#include "absl/strings/str_cat.h" + +namespace spdy { + +HpackEntry::HpackEntry(std::string name, std::string value) + : name_(std::move(name)), value_(std::move(value)) {} + +// static +size_t HpackEntry::Size(absl::string_view name, absl::string_view value) { + return name.size() + value.size() + kHpackEntrySizeOverhead; +} +size_t HpackEntry::Size() const { return Size(name(), value()); } + +std::string HpackEntry::GetDebugString() const { + return absl::StrCat("{ name: \"", name_, "\", value: \"", value_, "\" }"); +} + +} // namespace spdy diff --git a/chromium/net/third_party/quiche/src/quiche/spdy/core/hpack/hpack_entry.h b/chromium/net/third_party/quiche/src/quiche/spdy/core/hpack/hpack_entry.h new file mode 100644 index 00000000000..26eeb6af088 --- /dev/null +++ b/chromium/net/third_party/quiche/src/quiche/spdy/core/hpack/hpack_entry.h @@ -0,0 +1,81 @@ +// 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 <string> +#include <utility> + +#include "absl/strings/string_view.h" +#include "quiche/common/platform/api/quiche_export.h" + +// All section references below are to +// http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-08 + +namespace spdy { + +// The constant amount added to name().size() and value().size() to +// get the size of an HpackEntry as defined in 5.1. +constexpr size_t kHpackEntrySizeOverhead = 32; + +// A structure for looking up entries in the static and dynamic tables. +struct QUICHE_EXPORT_PRIVATE HpackLookupEntry { + absl::string_view name; + absl::string_view value; + + bool operator==(const HpackLookupEntry& other) const { + return name == other.name && value == other.value; + } + + // Abseil hashing framework extension according to absl/hash/hash.h: + template <typename H> + friend H AbslHashValue(H h, const HpackLookupEntry& entry) { + return H::combine(std::move(h), entry.name, entry.value); + } +}; + +// A structure for an entry in the static table (3.3.1) +// and the header table (3.3.2). +class QUICHE_EXPORT_PRIVATE HpackEntry { + public: + HpackEntry(std::string name, std::string value); + + // Make HpackEntry non-copyable to make sure it is always moved. + HpackEntry(const HpackEntry&) = delete; + HpackEntry& operator=(const HpackEntry&) = delete; + + HpackEntry(HpackEntry&&) = default; + HpackEntry& operator=(HpackEntry&&) = default; + + // Getters for std::string members traditionally return const std::string&. + // However, HpackHeaderTable uses string_view as keys in the maps + // static_name_index_ and dynamic_name_index_. If HpackEntry::name() returned + // const std::string&, then + // dynamic_name_index_.insert(std::make_pair(entry.name(), index)); + // would silently create a dangling reference: make_pair infers type from the + // return type of entry.name() and silently creates a temporary string copy. + // Insert creates a string_view that points to this copy, which then + // immediately goes out of scope and gets destroyed. While this is quite easy + // to avoid, for example, by explicitly specifying type as a template + // parameter to make_pair, returning string_view here is less error-prone. + absl::string_view name() const { return name_; } + absl::string_view value() const { return value_; } + + // Returns the size of an entry as defined in 5.1. + static size_t Size(absl::string_view name, absl::string_view value); + size_t Size() const; + + std::string GetDebugString() const; + + private: + std::string name_; + std::string value_; +}; + +} // namespace spdy + +#endif // QUICHE_SPDY_CORE_HPACK_HPACK_ENTRY_H_ diff --git a/chromium/net/third_party/quiche/src/quiche/spdy/core/hpack/hpack_entry_test.cc b/chromium/net/third_party/quiche/src/quiche/spdy/core/hpack/hpack_entry_test.cc new file mode 100644 index 00000000000..faf77862bfd --- /dev/null +++ b/chromium/net/third_party/quiche/src/quiche/spdy/core/hpack/hpack_entry_test.cc @@ -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. + +#include "quiche/spdy/core/hpack/hpack_entry.h" + +#include "absl/hash/hash.h" +#include "quiche/common/platform/api/quiche_test.h" + +namespace spdy { + +namespace { + +TEST(HpackLookupEntryTest, EntryNamesDiffer) { + HpackLookupEntry entry1{"header", "value"}; + HpackLookupEntry entry2{"HEADER", "value"}; + + EXPECT_FALSE(entry1 == entry2); + EXPECT_NE(absl::Hash<HpackLookupEntry>()(entry1), + absl::Hash<HpackLookupEntry>()(entry2)); +} + +TEST(HpackLookupEntryTest, EntryValuesDiffer) { + HpackLookupEntry entry1{"header", "value"}; + HpackLookupEntry entry2{"header", "VALUE"}; + + EXPECT_FALSE(entry1 == entry2); + EXPECT_NE(absl::Hash<HpackLookupEntry>()(entry1), + absl::Hash<HpackLookupEntry>()(entry2)); +} + +TEST(HpackLookupEntryTest, EntriesEqual) { + HpackLookupEntry entry1{"name", "value"}; + HpackLookupEntry entry2{"name", "value"}; + + EXPECT_TRUE(entry1 == entry2); + EXPECT_EQ(absl::Hash<HpackLookupEntry>()(entry1), + absl::Hash<HpackLookupEntry>()(entry2)); +} + +TEST(HpackEntryTest, BasicEntry) { + HpackEntry entry("header-name", "header value"); + + EXPECT_EQ("header-name", entry.name()); + EXPECT_EQ("header value", entry.value()); + + EXPECT_EQ(55u, entry.Size()); + EXPECT_EQ(55u, HpackEntry::Size("header-name", "header value")); +} + +} // namespace + +} // namespace spdy diff --git a/chromium/net/third_party/quiche/src/quiche/spdy/core/hpack/hpack_header_table.cc b/chromium/net/third_party/quiche/src/quiche/spdy/core/hpack/hpack_header_table.cc new file mode 100644 index 00000000000..e9bc9fc3b00 --- /dev/null +++ b/chromium/net/third_party/quiche/src/quiche/spdy/core/hpack/hpack_header_table.cc @@ -0,0 +1,188 @@ +// 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 "quiche/spdy/core/hpack/hpack_header_table.h" + +#include <algorithm> + +#include "quiche/common/platform/api/quiche_logging.h" +#include "quiche/spdy/core/hpack/hpack_constants.h" +#include "quiche/spdy/core/hpack/hpack_static_table.h" + +namespace spdy { + +HpackHeaderTable::HpackHeaderTable() + : static_entries_(ObtainHpackStaticTable().GetStaticEntries()), + static_index_(ObtainHpackStaticTable().GetStaticIndex()), + static_name_index_(ObtainHpackStaticTable().GetStaticNameIndex()), + settings_size_bound_(kDefaultHeaderTableSizeSetting), + size_(0), + max_size_(kDefaultHeaderTableSizeSetting), + dynamic_table_insertions_(0) {} + +HpackHeaderTable::~HpackHeaderTable() = default; + +size_t HpackHeaderTable::GetByName(absl::string_view name) { + { + auto it = static_name_index_.find(name); + if (it != static_name_index_.end()) { + return 1 + it->second; + } + } + { + NameToEntryMap::const_iterator it = dynamic_name_index_.find(name); + if (it != dynamic_name_index_.end()) { + return dynamic_table_insertions_ - it->second + kStaticTableSize; + } + } + return kHpackEntryNotFound; +} + +size_t HpackHeaderTable::GetByNameAndValue(absl::string_view name, + absl::string_view value) { + HpackLookupEntry query{name, value}; + { + auto it = static_index_.find(query); + if (it != static_index_.end()) { + return 1 + it->second; + } + } + { + auto it = dynamic_index_.find(query); + if (it != dynamic_index_.end()) { + return dynamic_table_insertions_ - it->second + kStaticTableSize; + } + } + return kHpackEntryNotFound; +} + +void HpackHeaderTable::SetMaxSize(size_t max_size) { + QUICHE_CHECK_LE(max_size, settings_size_bound_); + + max_size_ = max_size; + if (size_ > max_size_) { + Evict(EvictionCountToReclaim(size_ - max_size_)); + QUICHE_CHECK_LE(size_, max_size_); + } +} + +void HpackHeaderTable::SetSettingsHeaderTableSize(size_t settings_size) { + settings_size_bound_ = settings_size; + SetMaxSize(settings_size_bound_); +} + +void HpackHeaderTable::EvictionSet(absl::string_view name, + absl::string_view value, + DynamicEntryTable::iterator* begin_out, + DynamicEntryTable::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(absl::string_view name, + absl::string_view 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) { + QUICHE_CHECK(!dynamic_entries_.empty()); + + HpackEntry* entry = dynamic_entries_.back().get(); + const size_t index = dynamic_table_insertions_ - dynamic_entries_.size(); + + size_ -= entry->Size(); + auto it = dynamic_index_.find({entry->name(), entry->value()}); + QUICHE_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->second == index) { + dynamic_index_.erase(it); + } + auto name_it = dynamic_name_index_.find(entry->name()); + QUICHE_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 == index) { + dynamic_name_index_.erase(name_it); + } + dynamic_entries_.pop_back(); + } +} + +const HpackEntry* HpackHeaderTable::TryAddEntry(absl::string_view name, + absl::string_view value) { + // Since |dynamic_entries_| has iterator stability, |name| and |value| are + // valid even after evicting other entries and push_front() making room for + // the new one. + 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. + QUICHE_DCHECK(dynamic_entries_.empty()); + QUICHE_DCHECK_EQ(0u, size_); + return nullptr; + } + + const size_t index = dynamic_table_insertions_; + dynamic_entries_.push_front( + std::make_unique<HpackEntry>(std::string(name), std::string(value))); + HpackEntry* new_entry = dynamic_entries_.front().get(); + auto index_result = dynamic_index_.insert(std::make_pair( + HpackLookupEntry{new_entry->name(), new_entry->value()}, index)); + 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. + QUICHE_DVLOG(1) << "Found existing entry at: " << index_result.first->second + << " replacing with: " << new_entry->GetDebugString() + << " at: " << index; + QUICHE_DCHECK_GT(index, index_result.first->second); + dynamic_index_.erase(index_result.first); + auto insert_result = dynamic_index_.insert(std::make_pair( + HpackLookupEntry{new_entry->name(), new_entry->value()}, index)); + QUICHE_CHECK(insert_result.second); + } + + auto name_result = + dynamic_name_index_.insert(std::make_pair(new_entry->name(), index)); + 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. + QUICHE_DVLOG(1) << "Found existing entry at: " << name_result.first->second + << " replacing with: " << new_entry->GetDebugString() + << " at: " << index; + QUICHE_DCHECK_GT(index, name_result.first->second); + dynamic_name_index_.erase(name_result.first); + auto insert_result = + dynamic_name_index_.insert(std::make_pair(new_entry->name(), index)); + QUICHE_CHECK(insert_result.second); + } + + size_ += entry_size; + ++dynamic_table_insertions_; + + return dynamic_entries_.front().get(); +} + +} // namespace spdy diff --git a/chromium/net/third_party/quiche/src/quiche/spdy/core/hpack/hpack_header_table.h b/chromium/net/third_party/quiche/src/quiche/spdy/core/hpack/hpack_header_table.h new file mode 100644 index 00000000000..7886dde02e6 --- /dev/null +++ b/chromium/net/third_party/quiche/src/quiche/spdy/core/hpack/hpack_header_table.h @@ -0,0 +1,153 @@ +// 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 "absl/base/attributes.h" +#include "absl/container/flat_hash_map.h" +#include "absl/container/flat_hash_set.h" +#include "absl/hash/hash.h" +#include "absl/strings/string_view.h" +#include "quiche/common/platform/api/quiche_export.h" +#include "quiche/common/quiche_circular_deque.h" +#include "quiche/spdy/core/hpack/hpack_entry.h" + +// All section references below are to http://tools.ietf.org/html/rfc7541. + +namespace spdy { + +namespace test { +class HpackHeaderTablePeer; +} // namespace test + +// Return value of GetByName() and GetByNameAndValue() if matching entry is not +// found. This value is never used in HPACK for indexing entries, see +// https://httpwg.org/specs/rfc7541.html#index.address.space. +constexpr size_t kHpackEntryNotFound = 0; + +// A data structure for the static table (2.3.1) and the dynamic table (2.3.2). +class QUICHE_EXPORT_PRIVATE HpackHeaderTable { + public: + friend class test::HpackHeaderTablePeer; + + // Use a lightweight, memory efficient container for the static table, which + // is initialized once and never changed after. + using StaticEntryTable = std::vector<HpackEntry>; + + // HpackHeaderTable takes advantage of the deque property that references + // remain valid, so long as insertions & deletions are at the head & tail. + using DynamicEntryTable = + quiche::QuicheCircularDeque<std::unique_ptr<HpackEntry>>; + + using NameValueToEntryMap = absl::flat_hash_map<HpackLookupEntry, size_t>; + using NameToEntryMap = absl::flat_hash_map<absl::string_view, size_t>; + + 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_; } + + // The HPACK indexing scheme used by GetByName() and GetByNameAndValue() is + // defined at https://httpwg.org/specs/rfc7541.html#index.address.space. + + // Returns the index of the lowest-index entry matching |name|, + // or kHpackEntryNotFound if no matching entry is found. + size_t GetByName(absl::string_view name); + + // Returns the index of the lowest-index entry matching |name| and |value|, + // or kHpackEntryNotFound if no matching entry is found. + size_t GetByNameAndValue(absl::string_view name, absl::string_view value); + + // 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(absl::string_view name, absl::string_view value, + DynamicEntryTable::iterator* begin_out, + DynamicEntryTable::iterator* end_out); + + // Adds an entry for the representation, evicting entries as needed. |name| + // and |value| must not point to an entry in |dynamic_entries_| which is about + // to be evicted, but they may point to an entry which is not. + // 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(absl::string_view name, + absl::string_view value); + + private: + // Returns number of evictions required to enter |name| & |value|. + size_t EvictionCountForEntry(absl::string_view name, + absl::string_view 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. + + // Stores HpackEntries. + const StaticEntryTable& static_entries_; + DynamicEntryTable dynamic_entries_; + + // Tracks the index of the unique HpackEntry for a given header name and + // value. Keys consist of string_views that point to strings stored in + // |static_entries_|. + const NameValueToEntryMap& static_index_; + + // Tracks the index of the first static entry for each name in the static + // table. Each key is a string_view that points to a name string stored in + // |static_entries_|. + const NameToEntryMap& static_name_index_; + + // Tracks the index of the most recently inserted HpackEntry for a given + // header name and value. Keys consist of string_views that point to strings + // stored in |dynamic_entries_|. + NameValueToEntryMap dynamic_index_; + + // Tracks the index of the most recently inserted HpackEntry for a given + // header name. Each key is a string_view that points to a name string stored + // in |dynamic_entries_|. + 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 dynamic table insertions so far + // (including entries that have been evicted). + size_t dynamic_table_insertions_; +}; + +} // namespace spdy + +#endif // QUICHE_SPDY_CORE_HPACK_HPACK_HEADER_TABLE_H_ diff --git a/chromium/net/third_party/quiche/src/quiche/spdy/core/hpack/hpack_header_table_test.cc b/chromium/net/third_party/quiche/src/quiche/spdy/core/hpack/hpack_header_table_test.cc new file mode 100644 index 00000000000..51bd7ed054b --- /dev/null +++ b/chromium/net/third_party/quiche/src/quiche/spdy/core/hpack/hpack_header_table_test.cc @@ -0,0 +1,392 @@ +// 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 "quiche/spdy/core/hpack/hpack_header_table.h" + +#include <algorithm> +#include <cstdint> +#include <set> +#include <string> +#include <vector> + +#include "quiche/common/platform/api/quiche_test.h" +#include "quiche/spdy/core/hpack/hpack_constants.h" +#include "quiche/spdy/core/hpack/hpack_entry.h" +#include "quiche/spdy/core/hpack/hpack_static_table.h" + +namespace spdy { + +using std::distance; + +namespace test { + +class HpackHeaderTablePeer { + public: + explicit HpackHeaderTablePeer(HpackHeaderTable* table) : table_(table) {} + + const HpackHeaderTable::DynamicEntryTable& dynamic_entries() { + return table_->dynamic_entries_; + } + const HpackHeaderTable::StaticEntryTable& static_entries() { + return table_->static_entries_; + } + const HpackEntry* GetFirstStaticEntry() { + return &table_->static_entries_.front(); + } + const HpackEntry* GetLastStaticEntry() { + return &table_->static_entries_.back(); + } + std::vector<HpackEntry*> EvictionSet(absl::string_view name, + absl::string_view value) { + HpackHeaderTable::DynamicEntryTable::iterator begin, end; + table_->EvictionSet(name, value, &begin, &end); + std::vector<HpackEntry*> result; + for (; begin != end; ++begin) { + result.push_back(begin->get()); + } + return result; + } + size_t dynamic_table_insertions() { + return table_->dynamic_table_insertions_; + } + size_t EvictionCountForEntry(absl::string_view name, + absl::string_view 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); } + + private: + HpackHeaderTable* table_; +}; + +} // namespace test + +namespace { + +class HpackHeaderTableTest : public QuicheTest { + 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, kHpackEntrySizeOverhead); + std::string name((size - kHpackEntrySizeOverhead) / 2, 'n'); + std::string value(size - kHpackEntrySizeOverhead - name.size(), 'v'); + HpackEntry entry(name, value); + 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, kHpackEntrySizeOverhead); + uint32_t entry_size = kHpackEntrySizeOverhead; + 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::DynamicEntryTable::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)); + } + } + + 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().size()); + EXPECT_EQ(0u, peer_.dynamic_table_insertions()); + + // Static entries have been populated and inserted into the table & index. + const HpackHeaderTable::StaticEntryTable& static_entries = + peer_.static_entries(); + EXPECT_EQ(kStaticTableSize, static_entries.size()); + // HPACK indexing scheme is 1-based. + size_t index = 1; + for (const HpackEntry& entry : static_entries) { + EXPECT_EQ(index, table_.GetByNameAndValue(entry.name(), entry.value())); + index++; + } +} + +TEST_F(HpackHeaderTableTest, BasicDynamicEntryInsertionAndEviction) { + EXPECT_EQ(kStaticTableSize, peer_.static_entries().size()); + + const HpackEntry* first_static_entry = peer_.GetFirstStaticEntry(); + const HpackEntry* last_static_entry = peer_.GetLastStaticEntry(); + + const HpackEntry* entry = table_.TryAddEntry("header-key", "Header Value"); + EXPECT_EQ("header-key", entry->name()); + EXPECT_EQ("Header Value", entry->value()); + + // Table counts were updated appropriately. + EXPECT_EQ(entry->Size(), table_.size()); + EXPECT_EQ(1u, peer_.dynamic_entries().size()); + EXPECT_EQ(kStaticTableSize, peer_.static_entries().size()); + + EXPECT_EQ(62u, table_.GetByNameAndValue("header-key", "Header Value")); + + // Index of static entries does not change. + EXPECT_EQ(first_static_entry, peer_.GetFirstStaticEntry()); + EXPECT_EQ(last_static_entry, peer_.GetLastStaticEntry()); + + // Evict |entry|. Table counts are again updated appropriately. + peer_.Evict(1); + EXPECT_EQ(0u, table_.size()); + EXPECT_EQ(0u, peer_.dynamic_entries().size()); + EXPECT_EQ(kStaticTableSize, peer_.static_entries().size()); + + // Index of static entries does not change. + EXPECT_EQ(first_static_entry, peer_.GetFirstStaticEntry()); + EXPECT_EQ(last_static_entry, peer_.GetLastStaticEntry()); +} + +TEST_F(HpackHeaderTableTest, EntryIndexing) { + const HpackEntry* first_static_entry = peer_.GetFirstStaticEntry(); + const HpackEntry* last_static_entry = peer_.GetLastStaticEntry(); + + // Static entries are queryable by name & value. + EXPECT_EQ(1u, table_.GetByName(first_static_entry->name())); + EXPECT_EQ(1u, 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. + table_.TryAddEntry(first_static_entry->name(), first_static_entry->value()); + table_.TryAddEntry(first_static_entry->name(), "Value Four"); + table_.TryAddEntry("key-1", "Value One"); + table_.TryAddEntry("key-2", "Value Three"); + table_.TryAddEntry("key-1", "Value Two"); + table_.TryAddEntry("key-2", "Value Three"); + table_.TryAddEntry("key-2", "Value Four"); + + // The following entry is identical to the one at index 68. The smaller index + // is returned by GetByNameAndValue(). + EXPECT_EQ(1u, table_.GetByNameAndValue(first_static_entry->name(), + first_static_entry->value())); + EXPECT_EQ(67u, + table_.GetByNameAndValue(first_static_entry->name(), "Value Four")); + EXPECT_EQ(66u, table_.GetByNameAndValue("key-1", "Value One")); + EXPECT_EQ(64u, table_.GetByNameAndValue("key-1", "Value Two")); + // The following entry is identical to the one at index 65. The smaller index + // is returned by GetByNameAndValue(). + EXPECT_EQ(63u, table_.GetByNameAndValue("key-2", "Value Three")); + EXPECT_EQ(62u, table_.GetByNameAndValue("key-2", "Value Four")); + + // Index of static entries does not change. + EXPECT_EQ(first_static_entry, peer_.GetFirstStaticEntry()); + EXPECT_EQ(last_static_entry, peer_.GetLastStaticEntry()); + + // Querying by name returns the most recently added matching entry. + EXPECT_EQ(64u, table_.GetByName("key-1")); + EXPECT_EQ(62u, table_.GetByName("key-2")); + EXPECT_EQ(1u, table_.GetByName(first_static_entry->name())); + EXPECT_EQ(kHpackEntryNotFound, 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(66u, table_.GetByNameAndValue("key-1", "Value One")); + EXPECT_EQ(64u, table_.GetByNameAndValue("key-1", "Value Two")); + EXPECT_EQ(63u, table_.GetByNameAndValue("key-2", "Value Three")); + EXPECT_EQ(62u, table_.GetByNameAndValue("key-2", "Value Four")); + EXPECT_EQ(1u, table_.GetByNameAndValue(first_static_entry->name(), + first_static_entry->value())); + EXPECT_EQ(67u, + table_.GetByNameAndValue(first_static_entry->name(), "Value Four")); + EXPECT_EQ(kHpackEntryNotFound, + table_.GetByNameAndValue("key-1", "Not Present")); + EXPECT_EQ(kHpackEntryNotFound, + 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(1u, table_.GetByNameAndValue(first_static_entry->name(), + first_static_entry->value())); + EXPECT_EQ(67u, + table_.GetByNameAndValue(first_static_entry->name(), "Value Four")); + + // Evict |entry2|. Queries by its name & value are not found. + peer_.Evict(1); + EXPECT_EQ(kHpackEntryNotFound, + table_.GetByNameAndValue(first_static_entry->name(), "Value Four")); + + // Index of static entries does not change. + EXPECT_EQ(first_static_entry, peer_.GetFirstStaticEntry()); + EXPECT_EQ(last_static_entry, peer_.GetLastStaticEntry()); +} + +TEST_F(HpackHeaderTableTest, SetSizes) { + std::string 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) { + std::string 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) { + std::string 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); + + // The first entry in the dynamic table. + const HpackEntry* survivor_entry = peer_.dynamic_entries().front().get(); + + 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()); + + table_.TryAddEntry(long_entry.name(), long_entry.value()); + EXPECT_EQ(2u, peer_.dynamic_entries().size()); + EXPECT_EQ(63u, table_.GetByNameAndValue(survivor_entry->name(), + survivor_entry->value())); + EXPECT_EQ(62u, + table_.GetByNameAndValue(long_entry.name(), long_entry.value())); +} + +// 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()); +} + +} // namespace + +} // namespace spdy diff --git a/chromium/net/third_party/quiche/src/quiche/spdy/core/hpack/hpack_output_stream.cc b/chromium/net/third_party/quiche/src/quiche/spdy/core/hpack/hpack_output_stream.cc new file mode 100644 index 00000000000..8a0c8d67037 --- /dev/null +++ b/chromium/net/third_party/quiche/src/quiche/spdy/core/hpack/hpack_output_stream.cc @@ -0,0 +1,100 @@ +// 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 "quiche/spdy/core/hpack/hpack_output_stream.h" + +#include <utility> + +#include "quiche/common/platform/api/quiche_logging.h" + +namespace spdy { + +HpackOutputStream::HpackOutputStream() : bit_offset_(0) {} + +HpackOutputStream::~HpackOutputStream() = default; + +void HpackOutputStream::AppendBits(uint8_t bits, size_t bit_size) { + QUICHE_DCHECK_GT(bit_size, 0u); + QUICHE_DCHECK_LE(bit_size, 8u); + QUICHE_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. + QUICHE_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(absl::string_view buffer) { + QUICHE_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); + } + QUICHE_DCHECK_EQ(bit_offset_, 0u); +} + +std::string* HpackOutputStream::MutableString() { + QUICHE_DCHECK_EQ(bit_offset_, 0u); + return &buffer_; +} + +std::string HpackOutputStream::TakeString() { + // This must hold, since all public functions cause the buffer to + // end on a byte boundary. + QUICHE_DCHECK_EQ(bit_offset_, 0u); + std::string out = std::move(buffer_); + buffer_ = {}; + bit_offset_ = 0; + return out; +} + +std::string HpackOutputStream::BoundedTakeString(size_t max_size) { + if (buffer_.size() > max_size) { + // Save off overflow bytes to temporary string (causes a copy). + std::string overflow = buffer_.substr(max_size); + + // Resize buffer down to the given limit. + buffer_.resize(max_size); + + // Give buffer to output string. + std::string out = std::move(buffer_); + + // Reset to contain overflow. + buffer_ = std::move(overflow); + return out; + } else { + return TakeString(); + } +} + +} // namespace spdy diff --git a/chromium/net/third_party/quiche/src/quiche/spdy/core/hpack/hpack_output_stream.h b/chromium/net/third_party/quiche/src/quiche/spdy/core/hpack/hpack_output_stream.h new file mode 100644 index 00000000000..cd8ce4687ba --- /dev/null +++ b/chromium/net/third_party/quiche/src/quiche/spdy/core/hpack/hpack_output_stream.h @@ -0,0 +1,75 @@ +// 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 <string> + +#include "absl/strings/string_view.h" +#include "quiche/common/platform/api/quiche_export.h" +#include "quiche/spdy/core/hpack/hpack_constants.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 QUICHE_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(absl::string_view 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); + + // Return pointer to internal buffer. |bit_offset_| needs to be zero. + std::string* MutableString(); + + // Returns the internal buffer as a string, then resets state. + std::string TakeString(); + + // Returns up to |max_size| bytes of the internal buffer. Resets + // internal state with the overflow. + std::string BoundedTakeString(size_t max_size); + + // Size in bytes of stream's internal buffer. + size_t size() const { return buffer_.size(); } + + private: + // The internal bit buffer. + std::string 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/quiche/spdy/core/hpack/hpack_output_stream_test.cc b/chromium/net/third_party/quiche/src/quiche/spdy/core/hpack/hpack_output_stream_test.cc new file mode 100644 index 00000000000..ea1c2656fd8 --- /dev/null +++ b/chromium/net/third_party/quiche/src/quiche/spdy/core/hpack/hpack_output_stream_test.cc @@ -0,0 +1,284 @@ +// 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 "quiche/spdy/core/hpack/hpack_output_stream.h" + +#include <cstddef> + +#include "quiche/common/platform/api/quiche_test.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; + std::string 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); + + std::string str = output_stream.TakeString(); + EXPECT_EQ(expected_str, str); +} + +// Utility function to return I as a string encoded with an N-bit +// prefix. +std::string EncodeUint32(uint8_t N, uint32_t I) { + HpackOutputStream output_stream; + if (N < 8) { + output_stream.AppendBits(0x00, 8 - N); + } + output_stream.AppendUint32(I); + std::string str = output_stream.TakeString(); + 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(std::string("\x00", 1), EncodeUint32(8, 0x00)); + EXPECT_EQ("\x7f", EncodeUint32(8, 0x7f)); + // Maximum. + EXPECT_EQ("\xfe", EncodeUint32(8, 0xfe)); +} + +TEST(HpackOutputStreamTest, TwoByteIntegersEightBitPrefix) { + // Minimum. + EXPECT_EQ(std::string("\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(std::string("\x00", 1), EncodeUint32(7, 0x00)); + EXPECT_EQ(std::string("\x00", 1), EncodeUint32(6, 0x00)); + EXPECT_EQ(std::string("\x00", 1), EncodeUint32(5, 0x00)); + EXPECT_EQ(std::string("\x00", 1), EncodeUint32(4, 0x00)); + EXPECT_EQ(std::string("\x00", 1), EncodeUint32(3, 0x00)); + EXPECT_EQ(std::string("\x00", 1), EncodeUint32(2, 0x00)); + EXPECT_EQ(std::string("\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(std::string("\x00", 1), EncodeUint32(1, 0x00)); +} + +TEST(HpackOutputStreamTest, TwoByteIntegersOneToSevenBitPrefixes) { + // Minimums. + EXPECT_EQ(std::string("\x7f\x00", 2), EncodeUint32(7, 0x7f)); + EXPECT_EQ(std::string("\x3f\x00", 2), EncodeUint32(6, 0x3f)); + EXPECT_EQ(std::string("\x1f\x00", 2), EncodeUint32(5, 0x1f)); + EXPECT_EQ(std::string("\x0f\x00", 2), EncodeUint32(4, 0x0f)); + EXPECT_EQ(std::string("\x07\x00", 2), EncodeUint32(3, 0x07)); + EXPECT_EQ(std::string("\x03\x00", 2), EncodeUint32(2, 0x03)); + EXPECT_EQ(std::string("\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); + std::string str = output_stream.TakeString(); + EXPECT_EQ(std::string("\xff\x00", 2), str); +} + +TEST(HpackOutputStreamTest, AppendBytes) { + HpackOutputStream output_stream; + + output_stream.AppendBytes("buffer1"); + output_stream.AppendBytes("buffer2"); + + std::string str = output_stream.TakeString(); + EXPECT_EQ("buffer1buffer2", str); +} + +TEST(HpackOutputStreamTest, BoundedTakeString) { + HpackOutputStream output_stream; + + output_stream.AppendBytes("buffer12"); + output_stream.AppendBytes("buffer456"); + + std::string str = output_stream.BoundedTakeString(9); + EXPECT_EQ("buffer12b", str); + + output_stream.AppendBits(0x7f, 7); + output_stream.AppendUint32(0x11); + str = output_stream.BoundedTakeString(9); + EXPECT_EQ("uffer456\xff", str); + + str = output_stream.BoundedTakeString(9); + EXPECT_EQ("\x10", str); +} + +TEST(HpackOutputStreamTest, MutableString) { + HpackOutputStream output_stream; + + output_stream.AppendBytes("1"); + output_stream.MutableString()->append("2"); + + output_stream.AppendBytes("foo"); + output_stream.MutableString()->append("bar"); + + std::string str = output_stream.TakeString(); + EXPECT_EQ("12foobar", str); +} + +} // namespace + +} // namespace spdy diff --git a/chromium/net/third_party/quiche/src/quiche/spdy/core/hpack/hpack_round_trip_test.cc b/chromium/net/third_party/quiche/src/quiche/spdy/core/hpack/hpack_round_trip_test.cc new file mode 100644 index 00000000000..30a2e77cc99 --- /dev/null +++ b/chromium/net/third_party/quiche/src/quiche/spdy/core/hpack/hpack_round_trip_test.cc @@ -0,0 +1,222 @@ +// 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 "quiche/http2/test_tools/http2_random.h" +#include "quiche/common/platform/api/quiche_test.h" +#include "quiche/spdy/core/hpack/hpack_constants.h" +#include "quiche/spdy/core/hpack/hpack_decoder_adapter.h" +#include "quiche/spdy/core/hpack/hpack_encoder.h" +#include "quiche/spdy/core/spdy_test_utils.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 QuicheTestWithParam<InputSizeParam> { + protected: + void SetUp() override { + // Use a small table size to tickle eviction handling. + encoder_.ApplyHeaderTableSizeSetting(256); + decoder_.ApplyHeaderTableSizeSetting(256); + } + + bool RoundTrip(const SpdyHeaderBlock& header_set) { + std::string encoded = encoder_.EncodeHeaderBlock(header_set); + + 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(); + } + + 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_SUITE_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"] = std::string("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"] = std::string("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<std::string> 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<std::string> 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) { + std::string 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()) { + std::string 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/quiche/spdy/core/hpack/hpack_static_table.cc b/chromium/net/third_party/quiche/src/quiche/spdy/core/hpack/hpack_static_table.cc new file mode 100644 index 00000000000..a443283091a --- /dev/null +++ b/chromium/net/third_party/quiche/src/quiche/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 "quiche/spdy/core/hpack/hpack_static_table.h" + +#include "absl/strings/string_view.h" +#include "quiche/common/platform/api/quiche_logging.h" +#include "quiche/spdy/core/hpack/hpack_constants.h" +#include "quiche/spdy/core/hpack/hpack_entry.h" + +namespace spdy { + +HpackStaticTable::HpackStaticTable() = default; + +HpackStaticTable::~HpackStaticTable() = default; + +void HpackStaticTable::Initialize(const HpackStaticEntry* static_entry_table, + size_t static_entry_count) { + QUICHE_CHECK(!IsInitialized()); + + static_entries_.reserve(static_entry_count); + + for (const HpackStaticEntry* it = static_entry_table; + it != static_entry_table + static_entry_count; ++it) { + std::string name(it->name, it->name_len); + std::string value(it->value, it->value_len); + static_entries_.push_back(HpackEntry(std::move(name), std::move(value))); + } + + // |static_entries_| will not be mutated any more. Therefore its entries will + // remain stable even if the container does not have iterator stability. + int insertion_count = 0; + for (const auto& entry : static_entries_) { + auto result = static_index_.insert(std::make_pair( + HpackLookupEntry{entry.name(), entry.value()}, insertion_count)); + QUICHE_CHECK(result.second); + + // Multiple static entries may have the same name, so inserts may fail. + static_name_index_.insert(std::make_pair(entry.name(), insertion_count)); + + ++insertion_count; + } +} + +bool HpackStaticTable::IsInitialized() const { + return !static_entries_.empty(); +} + +} // namespace spdy diff --git a/chromium/net/third_party/quiche/src/quiche/spdy/core/hpack/hpack_static_table.h b/chromium/net/third_party/quiche/src/quiche/spdy/core/hpack/hpack_static_table.h new file mode 100644 index 00000000000..fda34d12a06 --- /dev/null +++ b/chromium/net/third_party/quiche/src/quiche/spdy/core/hpack/hpack_static_table.h @@ -0,0 +1,56 @@ +// 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 "quiche/common/platform/api/quiche_export.h" +#include "quiche/spdy/core/hpack/hpack_header_table.h" + +namespace spdy { + +struct HpackStaticEntry; + +// Number of entries in the HPACK static table. +constexpr size_t kStaticTableSize = 61; + +// 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 QUICHE_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::StaticEntryTable& GetStaticEntries() const { + return static_entries_; + } + const HpackHeaderTable::NameValueToEntryMap& GetStaticIndex() const { + return static_index_; + } + const HpackHeaderTable::NameToEntryMap& GetStaticNameIndex() const { + return static_name_index_; + } + + private: + HpackHeaderTable::StaticEntryTable static_entries_; + // The following two members have string_views that point to strings stored in + // |static_entries_|. + HpackHeaderTable::NameValueToEntryMap 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/quiche/spdy/core/hpack/hpack_static_table_test.cc b/chromium/net/third_party/quiche/src/quiche/spdy/core/hpack/hpack_static_table_test.cc new file mode 100644 index 00000000000..e0577a0df61 --- /dev/null +++ b/chromium/net/third_party/quiche/src/quiche/spdy/core/hpack/hpack_static_table_test.cc @@ -0,0 +1,63 @@ +// 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 "quiche/spdy/core/hpack/hpack_static_table.h" + +#include <set> +#include <vector> + +#include "absl/strings/string_view.h" +#include "quiche/common/platform/api/quiche_test.h" +#include "quiche/spdy/core/hpack/hpack_constants.h" + +namespace spdy { + +namespace test { + +namespace { + +class HpackStaticTableTest : public QuicheTest { + 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()); + + const HpackHeaderTable::StaticEntryTable& static_entries = + table_.GetStaticEntries(); + EXPECT_EQ(kStaticTableSize, static_entries.size()); + + const HpackHeaderTable::NameValueToEntryMap& static_index = + table_.GetStaticIndex(); + EXPECT_EQ(kStaticTableSize, static_index.size()); + + const HpackHeaderTable::NameToEntryMap& static_name_index = + table_.GetStaticNameIndex(); + // Count distinct names in static table. + std::set<absl::string_view> names; + for (const auto& entry : static_entries) { + 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/quiche/spdy/core/http2_frame_decoder_adapter.cc b/chromium/net/third_party/quiche/src/quiche/spdy/core/http2_frame_decoder_adapter.cc new file mode 100644 index 00000000000..d5ebcabe38c --- /dev/null +++ b/chromium/net/third_party/quiche/src/quiche/spdy/core/http2_frame_decoder_adapter.cc @@ -0,0 +1,1107 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "quiche/spdy/core/http2_frame_decoder_adapter.h" + +// Logging policy: If an error in the input is detected, QUICHE_VLOG(n) is used +// so that the option exists to debug the situation. Otherwise, this code mostly +// uses QUICHE_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 "quiche/http2/decoder/decode_buffer.h" +#include "quiche/http2/decoder/decode_status.h" +#include "quiche/http2/decoder/http2_frame_decoder.h" +#include "quiche/http2/decoder/http2_frame_decoder_listener.h" +#include "quiche/http2/http2_constants.h" +#include "quiche/http2/http2_structures.h" +#include "quiche/common/platform/api/quiche_bug_tracker.h" +#include "quiche/common/platform/api/quiche_logging.h" +#include "quiche/common/quiche_endian.h" +#include "quiche/spdy/core/hpack/hpack_decoder_adapter.h" +#include "quiche/spdy/core/hpack/hpack_header_table.h" +#include "quiche/spdy/core/spdy_alt_svc_wire_format.h" +#include "quiche/spdy/core/spdy_header_block.h" +#include "quiche/spdy/core/spdy_headers_handler_interface.h" +#include "quiche/spdy/core/spdy_protocol.h" + +using ::spdy::ExtensionVisitorInterface; +using ::spdy::HpackDecoderAdapter; +using ::spdy::HpackHeaderTable; +using ::spdy::ParseErrorCode; +using ::spdy::ParseFrameType; +using ::spdy::SpdyAltSvcWireFormat; +using ::spdy::SpdyErrorCode; +using ::spdy::SpdyFramerDebugVisitorInterface; +using ::spdy::SpdyFramerVisitorInterface; +using ::spdy::SpdyFrameType; +using ::spdy::SpdyHeadersHandlerInterface; +using ::spdy::SpdyKnownSettingsId; +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 quiche::QuicheEndian::NetToHost64(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. +#ifndef NDEBUG +void CorruptFrameHeader(Http2FrameHeader* header) { + // Beyond a valid payload length, which is 2^24 - 1. + header->payload_length = 0x1010dead; + // An unsupported frame type. + header->type = Http2FrameType(0x80); + QUICHE_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; +} +#else +void CorruptFrameHeader(Http2FrameHeader* /*header*/) {} +#endif + +Http2DecoderAdapter::SpdyFramerError HpackDecodingErrorToSpdyFramerError( + HpackDecodingError error) { + switch (error) { + case HpackDecodingError::kOk: + return Http2DecoderAdapter::SpdyFramerError::SPDY_NO_ERROR; + case HpackDecodingError::kIndexVarintError: + return Http2DecoderAdapter::SpdyFramerError:: + SPDY_HPACK_INDEX_VARINT_ERROR; + case HpackDecodingError::kNameLengthVarintError: + return Http2DecoderAdapter::SpdyFramerError:: + SPDY_HPACK_NAME_LENGTH_VARINT_ERROR; + case HpackDecodingError::kValueLengthVarintError: + return Http2DecoderAdapter::SpdyFramerError:: + SPDY_HPACK_VALUE_LENGTH_VARINT_ERROR; + case HpackDecodingError::kNameTooLong: + return Http2DecoderAdapter::SpdyFramerError::SPDY_HPACK_NAME_TOO_LONG; + case HpackDecodingError::kValueTooLong: + return Http2DecoderAdapter::SpdyFramerError::SPDY_HPACK_VALUE_TOO_LONG; + case HpackDecodingError::kNameHuffmanError: + return Http2DecoderAdapter::SpdyFramerError:: + SPDY_HPACK_NAME_HUFFMAN_ERROR; + case HpackDecodingError::kValueHuffmanError: + return Http2DecoderAdapter::SpdyFramerError:: + SPDY_HPACK_VALUE_HUFFMAN_ERROR; + case HpackDecodingError::kMissingDynamicTableSizeUpdate: + return Http2DecoderAdapter::SpdyFramerError:: + SPDY_HPACK_MISSING_DYNAMIC_TABLE_SIZE_UPDATE; + case HpackDecodingError::kInvalidIndex: + return Http2DecoderAdapter::SpdyFramerError::SPDY_HPACK_INVALID_INDEX; + case HpackDecodingError::kInvalidNameIndex: + return Http2DecoderAdapter::SpdyFramerError:: + SPDY_HPACK_INVALID_NAME_INDEX; + case HpackDecodingError::kDynamicTableSizeUpdateNotAllowed: + return Http2DecoderAdapter::SpdyFramerError:: + SPDY_HPACK_DYNAMIC_TABLE_SIZE_UPDATE_NOT_ALLOWED; + case HpackDecodingError::kInitialDynamicTableSizeUpdateIsAboveLowWaterMark: + return Http2DecoderAdapter::SpdyFramerError:: + SPDY_HPACK_INITIAL_DYNAMIC_TABLE_SIZE_UPDATE_IS_ABOVE_LOW_WATER_MARK; + case HpackDecodingError::kDynamicTableSizeUpdateIsAboveAcknowledgedSetting: + return Http2DecoderAdapter::SpdyFramerError:: + SPDY_HPACK_DYNAMIC_TABLE_SIZE_UPDATE_IS_ABOVE_ACKNOWLEDGED_SETTING; + case HpackDecodingError::kTruncatedBlock: + return Http2DecoderAdapter::SpdyFramerError::SPDY_HPACK_TRUNCATED_BLOCK; + case HpackDecodingError::kFragmentTooLong: + return Http2DecoderAdapter::SpdyFramerError::SPDY_HPACK_FRAGMENT_TOO_LONG; + case HpackDecodingError::kCompressedHeaderSizeExceedsLimit: + return Http2DecoderAdapter::SpdyFramerError:: + SPDY_HPACK_COMPRESSED_HEADER_SIZE_EXCEEDS_LIMIT; + } + + return Http2DecoderAdapter::SpdyFramerError::SPDY_DECOMPRESS_FAILURE; +} + +} // 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_DECOMPRESS_FAILURE: + return "DECOMPRESS_FAILURE"; + case SPDY_INVALID_PADDING: + return "INVALID_PADDING"; + case SPDY_INVALID_DATA_FRAME_FLAGS: + return "INVALID_DATA_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 SPDY_HPACK_INDEX_VARINT_ERROR: + return "HPACK_INDEX_VARINT_ERROR"; + case SPDY_HPACK_NAME_LENGTH_VARINT_ERROR: + return "HPACK_NAME_LENGTH_VARINT_ERROR"; + case SPDY_HPACK_VALUE_LENGTH_VARINT_ERROR: + return "HPACK_VALUE_LENGTH_VARINT_ERROR"; + case SPDY_HPACK_NAME_TOO_LONG: + return "HPACK_NAME_TOO_LONG"; + case SPDY_HPACK_VALUE_TOO_LONG: + return "HPACK_VALUE_TOO_LONG"; + case SPDY_HPACK_NAME_HUFFMAN_ERROR: + return "HPACK_NAME_HUFFMAN_ERROR"; + case SPDY_HPACK_VALUE_HUFFMAN_ERROR: + return "HPACK_VALUE_HUFFMAN_ERROR"; + case SPDY_HPACK_MISSING_DYNAMIC_TABLE_SIZE_UPDATE: + return "HPACK_MISSING_DYNAMIC_TABLE_SIZE_UPDATE"; + case SPDY_HPACK_INVALID_INDEX: + return "HPACK_INVALID_INDEX"; + case SPDY_HPACK_INVALID_NAME_INDEX: + return "HPACK_INVALID_NAME_INDEX"; + case SPDY_HPACK_DYNAMIC_TABLE_SIZE_UPDATE_NOT_ALLOWED: + return "HPACK_DYNAMIC_TABLE_SIZE_UPDATE_NOT_ALLOWED"; + case SPDY_HPACK_INITIAL_DYNAMIC_TABLE_SIZE_UPDATE_IS_ABOVE_LOW_WATER_MARK: + return "HPACK_INITIAL_DYNAMIC_TABLE_SIZE_UPDATE_IS_ABOVE_LOW_WATER_MARK"; + case SPDY_HPACK_DYNAMIC_TABLE_SIZE_UPDATE_IS_ABOVE_ACKNOWLEDGED_SETTING: + return "HPACK_DYNAMIC_TABLE_SIZE_UPDATE_IS_ABOVE_ACKNOWLEDGED_SETTING"; + case SPDY_HPACK_TRUNCATED_BLOCK: + return "HPACK_TRUNCATED_BLOCK"; + case SPDY_HPACK_FRAGMENT_TOO_LONG: + return "HPACK_FRAGMENT_TOO_LONG"; + case SPDY_HPACK_COMPRESSED_HEADER_SIZE_EXCEEDS_LIMIT: + return "HPACK_COMPRESSED_HEADER_SIZE_EXCEEDS_LIMIT"; + case SPDY_STOP_PROCESSING: + return "STOP_PROCESSING"; + case LAST_ERROR: + return "UNKNOWN_ERROR"; + } + return "UNKNOWN_ERROR"; +} + +Http2DecoderAdapter::Http2DecoderAdapter() : frame_decoder_(this) { + QUICHE_DVLOG(1) << "Http2DecoderAdapter ctor"; + + CorruptFrameHeader(&frame_header_); + CorruptFrameHeader(&hpack_first_frame_header_); +} + +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_extension_visitor( + ExtensionVisitorInterface* visitor) { + extension_ = visitor; +} + +size_t Http2DecoderAdapter::ProcessInput(const char* data, size_t len) { + 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. + QUICHE_DCHECK(processed > 0) + << "processed=" << processed << " spdy_state_=" << spdy_state_ + << " spdy_framer_error_=" << spdy_framer_error_; + + data += processed; + len -= processed; + total_processed += processed; + if (processed == 0) { + break; + } + } + return total_processed; +} + +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_; +} + +void Http2DecoderAdapter::StopProcessing() { + SetSpdyErrorAndNotify(SpdyFramerError::SPDY_STOP_PROCESSING, + "Ignoring further events on this connection."); +} + +void Http2DecoderAdapter::SetMaxFrameSize(size_t max_frame_size) { + max_frame_size_ = max_frame_size; + frame_decoder_.set_maximum_payload_size(max_frame_size); +} + +// =========================================================================== +// 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) { + QUICHE_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. + QUICHE_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. + QUICHE_VLOG(1) << "Unknown control frame type " << header.type + << " received on invalid stream " << header.stream_id; + SetSpdyErrorAndNotify(SpdyFramerError::SPDY_INVALID_CONTROL_FRAME, ""); + return false; + } else { + QUICHE_DVLOG(1) << "Ignoring unknown frame type " << header.type; + return true; + } + } + + SpdyFrameType frame_type = ToSpdyFrameType(header.type); + if (!IsValidHTTP2FrameStreamId(header.stream_id, frame_type)) { + QUICHE_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_) { + QUICHE_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) { + QUICHE_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) { + QUICHE_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) { + QUICHE_DVLOG(1) << "OnDataPayload: len=" << len; + QUICHE_DCHECK(has_frame_header_); + QUICHE_DCHECK_EQ(frame_header_.type, Http2FrameType::DATA); + visitor()->OnStreamFrameData(frame_header().stream_id, data, len); +} + +void Http2DecoderAdapter::OnDataEnd() { + QUICHE_DVLOG(1) << "OnDataEnd"; + QUICHE_DCHECK(has_frame_header_); + QUICHE_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) { + QUICHE_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) { + QUICHE_DVLOG(1) << "OnHeadersPriority: " << priority; + QUICHE_DCHECK(has_frame_header_); + QUICHE_DCHECK_EQ(frame_type(), Http2FrameType::HEADERS) << frame_header_; + QUICHE_DCHECK(frame_header_.HasPriority()); + QUICHE_DCHECK(!on_headers_called_); + on_headers_called_ = true; + ReportReceiveCompressedFrame(frame_header_); + if (!visitor()) { + QUICHE_BUG(spdy_bug_1_1) + << "Visitor is nullptr, handling priority in headers failed." + << " priority:" << priority << " frame_header:" << frame_header_; + return; + } + 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) { + QUICHE_DVLOG(1) << "OnHpackFragment: len=" << len; + on_hpack_fragment_called_ = true; + auto* decoder = GetHpackDecoder(); + if (!decoder->HandleControlFrameHeadersData(data, len)) { + SetSpdyErrorAndNotify(HpackDecodingErrorToSpdyFramerError(decoder->error()), + decoder->detailed_error()); + return; + } +} + +void Http2DecoderAdapter::OnHeadersEnd() { + QUICHE_DVLOG(1) << "OnHeadersEnd"; + CommonHpackFragmentEnd(); + opt_pad_length_.reset(); +} + +void Http2DecoderAdapter::OnPriorityFrame(const Http2FrameHeader& header, + const Http2PriorityFields& priority) { + QUICHE_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) { + QUICHE_DVLOG(1) << "OnContinuationStart: " << header; + if (IsOkToStartFrame(header) && HasRequiredStreamId(header)) { + QUICHE_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() { + QUICHE_DVLOG(1) << "OnContinuationEnd"; + CommonHpackFragmentEnd(); +} + +void Http2DecoderAdapter::OnPadLength(size_t trailing_length) { + QUICHE_DVLOG(1) << "OnPadLength: " << trailing_length; + opt_pad_length_ = trailing_length; + QUICHE_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) { + QUICHE_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) { + QUICHE_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) { + QUICHE_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) { + QUICHE_DVLOG(1) << "OnSetting: " << setting_fields; + const auto parameter = static_cast<SpdySettingsId>(setting_fields.parameter); + visitor()->OnSetting(parameter, setting_fields.value); + SpdyKnownSettingsId known_id; + if (extension_ != nullptr && !spdy::ParseSettingsId(parameter, &known_id)) { + extension_->OnSetting(parameter, setting_fields.value); + } +} + +void Http2DecoderAdapter::OnSettingsEnd() { + QUICHE_DVLOG(1) << "OnSettingsEnd"; + visitor()->OnSettingsEnd(); +} + +void Http2DecoderAdapter::OnSettingsAck(const Http2FrameHeader& header) { + QUICHE_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) { + QUICHE_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() { + QUICHE_DVLOG(1) << "OnPushPromiseEnd"; + CommonHpackFragmentEnd(); + opt_pad_length_.reset(); +} + +void Http2DecoderAdapter::OnPing(const Http2FrameHeader& header, + const Http2PingFields& ping) { + QUICHE_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) { + QUICHE_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) { + QUICHE_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) { + QUICHE_DVLOG(1) << "OnGoAwayOpaqueData: len=" << len; + visitor()->OnGoAwayFrameData(data, len); +} + +void Http2DecoderAdapter::OnGoAwayEnd() { + QUICHE_DVLOG(1) << "OnGoAwayEnd"; + visitor()->OnGoAwayFrameData(nullptr, 0); +} + +void Http2DecoderAdapter::OnWindowUpdate(const Http2FrameHeader& header, + uint32_t increment) { + QUICHE_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) { + QUICHE_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) { + QUICHE_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) { + QUICHE_DVLOG(1) << "OnAltSvcValueData: len=" << len; + alt_svc_value_.append(data, len); +} + +void Http2DecoderAdapter::OnAltSvcEnd() { + QUICHE_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)) { + QUICHE_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(); +} + +void Http2DecoderAdapter::OnPriorityUpdateStart( + const Http2FrameHeader& header, + const Http2PriorityUpdateFields& priority_update) { + QUICHE_DVLOG(1) << "OnPriorityUpdateStart: " << header + << "; prioritized_stream_id: " + << priority_update.prioritized_stream_id; + if (IsOkToStartFrame(header) && HasRequiredStreamIdZero(header) && + HasRequiredStreamId(priority_update.prioritized_stream_id)) { + frame_header_ = header; + has_frame_header_ = true; + prioritized_stream_id_ = priority_update.prioritized_stream_id; + } +} + +void Http2DecoderAdapter::OnPriorityUpdatePayload(const char* data, + size_t len) { + QUICHE_DVLOG(1) << "OnPriorityUpdatePayload: len=" << len; + priority_field_value_.append(data, len); +} + +void Http2DecoderAdapter::OnPriorityUpdateEnd() { + QUICHE_DVLOG(1) << "OnPriorityUpdateEnd: priority_field_value.size(): " + << priority_field_value_.size(); + visitor()->OnPriorityUpdate(prioritized_stream_id_, priority_field_value_); + priority_field_value_.clear(); +} + +// Except for BLOCKED frames, all other unknown frames are either dropped or +// passed to a registered extension. +void Http2DecoderAdapter::OnUnknownStart(const Http2FrameHeader& header) { + QUICHE_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 { + QUICHE_DVLOG(1) << "OnUnknownPayload: len=" << len; + } +} + +void Http2DecoderAdapter::OnUnknownEnd() { + QUICHE_DVLOG(1) << "OnUnknownEnd"; + handling_extension_payload_ = false; +} + +void Http2DecoderAdapter::OnPaddingTooLong(const Http2FrameHeader& header, + size_t missing_length) { + QUICHE_DVLOG(1) << "OnPaddingTooLong: " << header + << "; missing_length: " << missing_length; + if (header.type == Http2FrameType::DATA) { + if (header.payload_length == 0) { + QUICHE_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) { + QUICHE_DVLOG(1) << "OnFrameSizeError: " << header; + if (header.payload_length > max_frame_size_) { + if (header.type == Http2FrameType::DATA) { + SetSpdyErrorAndNotify(SpdyFramerError::SPDY_OVERSIZED_PAYLOAD, ""); + } else { + 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) { + QUICHE_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 { + QUICHE_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); + QUICHE_VLOG(1) << "Skipping past " << avail << " bytes, of " << total + << " total remaining in the frame's payload."; + db.AdvanceCursor(avail); + } else { + QUICHE_BUG(spdy_bug_1_2) + << "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) { + QUICHE_DCHECK_EQ(spdy_framer_error_, SPDY_NO_ERROR); + QUICHE_DCHECK(!HasError()) << spdy_framer_error_; + switch (status) { + case DecodeStatus::kDecodeDone: + QUICHE_DVLOG(1) << "ProcessInputFrame -> DecodeStatus::kDecodeDone"; + ResetBetweenFrames(); + break; + case DecodeStatus::kDecodeInProgress: + QUICHE_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: + QUICHE_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 decode_status = frame_decoder_.DecodeFrame(&tmp); + if (decode_status != DecodeStatus::kDecodeDone) { + QUICHE_BUG(spdy_bug_1_3) + << "Expected to be done decoding the frame, not " + << decode_status; + SetSpdyErrorAndNotify(SPDY_INTERNAL_FRAMER_ERROR, ""); + } else if (spdy_framer_error_ != SPDY_NO_ERROR) { + QUICHE_BUG(spdy_bug_1_4) + << "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); +} + +void Http2DecoderAdapter::set_spdy_state(SpdyState v) { + QUICHE_DVLOG(2) << "set_spdy_state(" << StateToString(v) << ")"; + spdy_state_ = v; +} + +void Http2DecoderAdapter::SetSpdyErrorAndNotify(SpdyFramerError error, + std::string detailed_error) { + if (HasError()) { + QUICHE_DCHECK_EQ(spdy_state_, SpdyState::SPDY_ERROR); + } else { + QUICHE_VLOG(2) << "SetSpdyErrorAndNotify(" << SpdyFramerErrorToString(error) + << ")"; + QUICHE_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, detailed_error); + } +} + +bool Http2DecoderAdapter::HasError() const { + if (spdy_state_ == SpdyState::SPDY_ERROR) { + QUICHE_DCHECK_NE(spdy_framer_error(), SpdyFramerError::SPDY_NO_ERROR); + return true; + } else { + QUICHE_DCHECK_EQ(spdy_framer_error(), SpdyFramerError::SPDY_NO_ERROR); + return false; + } +} + +const Http2FrameHeader& Http2DecoderAdapter::frame_header() const { + QUICHE_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 { + QUICHE_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_; + QUICHE_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; + QUICHE_DVLOG(2) << "Http2DecoderAdapter::IsSkippingPadding: " << result; + return result; +} +bool Http2DecoderAdapter::IsDiscardingPayload() { + bool result = decoded_frame_header_ && frame_decoder_.IsDiscardingPayload(); + QUICHE_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) { + QUICHE_DVLOG(3) << "IsOkToStartFrame"; + if (HasError()) { + QUICHE_VLOG(2) << "HasError()"; + return false; + } + QUICHE_DCHECK(!has_frame_header_); + if (has_expected_frame_type_ && header.type != expected_frame_type_) { + QUICHE_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) { + QUICHE_DVLOG(3) << "HasRequiredStreamId: " << stream_id; + if (HasError()) { + QUICHE_VLOG(2) << "HasError()"; + return false; + } + if (stream_id != 0) { + return true; + } + QUICHE_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) { + QUICHE_DVLOG(3) << "HasRequiredStreamIdZero: " << stream_id; + if (HasError()) { + QUICHE_VLOG(2) << "HasError()"; + return false; + } + if (stream_id == 0) { + return true; + } + QUICHE_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_ = std::make_unique<HpackDecoderAdapter>(); + } + return hpack_decoder_.get(); +} + +void Http2DecoderAdapter::CommonStartHpackBlock() { + QUICHE_DVLOG(1) << "CommonStartHpackBlock"; + QUICHE_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) { + QUICHE_BUG(spdy_bug_1_5) << "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); + QUICHE_DCHECK(on_hpack_fragment_called_); + } +} + +void Http2DecoderAdapter::CommonHpackFragmentEnd() { + QUICHE_DVLOG(1) << "CommonHpackFragmentEnd: stream_id=" << stream_id(); + if (HasError()) { + QUICHE_VLOG(1) << "HasError(), returning"; + return; + } + QUICHE_DCHECK(has_frame_header_); + MaybeAnnounceEmptyFirstHpackFragment(); + if (frame_header_.IsEndHeaders()) { + QUICHE_DCHECK_EQ(has_hpack_first_frame_header_, + frame_type() == Http2FrameType::CONTINUATION) + << frame_header(); + has_expected_frame_type_ = false; + auto* decoder = GetHpackDecoder(); + if (decoder->HandleControlFrameHeadersComplete()) { + visitor()->OnHeaderFrameEnd(stream_id()); + } else { + SetSpdyErrorAndNotify( + HpackDecodingErrorToSpdyFramerError(decoder->error()), ""); + 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 { + QUICHE_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/quiche/spdy/core/http2_frame_decoder_adapter.h b/chromium/net/third_party/quiche/src/quiche/spdy/core/http2_frame_decoder_adapter.h new file mode 100644 index 00000000000..1798ae00620 --- /dev/null +++ b/chromium/net/third_party/quiche/src/quiche/spdy/core/http2_frame_decoder_adapter.h @@ -0,0 +1,547 @@ +// Copyright 2016 The Chromium 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 <string> + +#include "absl/strings/string_view.h" +#include "absl/types/optional.h" +#include "quiche/http2/decoder/http2_frame_decoder.h" +#include "quiche/common/platform/api/quiche_export.h" +#include "quiche/spdy/core/hpack/hpack_decoder_adapter.h" +#include "quiche/spdy/core/hpack/hpack_header_table.h" +#include "quiche/spdy/core/spdy_alt_svc_wire_format.h" +#include "quiche/spdy/core/spdy_headers_handler_interface.h" +#include "quiche/spdy/core/spdy_protocol.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 QUICHE_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_DECOMPRESS_FAILURE, // There was an error decompressing. + SPDY_INVALID_PADDING, // HEADERS or DATA frame padding invalid + SPDY_INVALID_DATA_FRAME_FLAGS, // Data 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 + + // HttpDecoder or HttpDecoderAdapter error. + // See HpackDecodingError for description of each error code. + SPDY_HPACK_INDEX_VARINT_ERROR, + SPDY_HPACK_NAME_LENGTH_VARINT_ERROR, + SPDY_HPACK_VALUE_LENGTH_VARINT_ERROR, + SPDY_HPACK_NAME_TOO_LONG, + SPDY_HPACK_VALUE_TOO_LONG, + SPDY_HPACK_NAME_HUFFMAN_ERROR, + SPDY_HPACK_VALUE_HUFFMAN_ERROR, + SPDY_HPACK_MISSING_DYNAMIC_TABLE_SIZE_UPDATE, + SPDY_HPACK_INVALID_INDEX, + SPDY_HPACK_INVALID_NAME_INDEX, + SPDY_HPACK_DYNAMIC_TABLE_SIZE_UPDATE_NOT_ALLOWED, + SPDY_HPACK_INITIAL_DYNAMIC_TABLE_SIZE_UPDATE_IS_ABOVE_LOW_WATER_MARK, + SPDY_HPACK_DYNAMIC_TABLE_SIZE_UPDATE_IS_ABOVE_ACKNOWLEDGED_SETTING, + SPDY_HPACK_TRUNCATED_BLOCK, + SPDY_HPACK_FRAGMENT_TOO_LONG, + SPDY_HPACK_COMPRESSED_HEADER_SIZE_EXCEEDS_LIMIT, + + // Set if the visitor no longer wishes to receive events for this + // connection. + SPDY_STOP_PROCESSING, + + 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; + + Http2DecoderAdapter(const Http2DecoderAdapter&) = delete; + Http2DecoderAdapter& operator=(const Http2DecoderAdapter&) = delete; + + // 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); + spdy::ExtensionVisitorInterface* extension_visitor() const { + return extension_; + } + + // 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_; + } + + // 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. + // + // If the input contains the entirety of a DATA frame payload, GOAWAY frame + // Additional Debug Data field, or unknown frame payload, then the + // corresponding SpdyFramerVisitorInterface::OnStreamFrameData(), + // OnGoAwayFrameData(), or ExtensionVisitorInterface::OnFramePayload() method + // is guaranteed to be called exactly once, with the entire payload or field. + size_t ProcessInput(const char* data, size_t len); + + // 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; + + spdy::HpackDecoderAdapter* GetHpackDecoder(); + const spdy::HpackDecoderAdapter* GetHpackDecoder() const { + return hpack_decoder_.get(); + } + + bool HasError() const; + + // A visitor may call this method to indicate it no longer wishes to receive + // events for this connection. + void StopProcessing(); + + // Sets the limit on the size of received HTTP/2 frame payloads. Corresponds + // to SETTINGS_MAX_FRAME_SIZE as advertised to the peer. + void SetMaxFrameSize(size_t max_frame_size); + + 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 OnPriorityUpdateStart( + const Http2FrameHeader& header, + const Http2PriorityUpdateFields& priority_update) override; + void OnPriorityUpdatePayload(const char* data, size_t len) override; + void OnPriorityUpdateEnd() override; + void OnUnknownStart(const Http2FrameHeader& header) override; + void OnUnknownPayload(const char* data, size_t len) override; + void OnUnknownEnd() override; + void OnPaddingTooLong(const Http2FrameHeader& header, + size_t missing_length) override; + void OnFrameSizeError(const Http2FrameHeader& header) override; + + size_t ProcessInputFrame(const char* data, size_t len); + + void DetermineSpdyState(DecodeStatus status); + void ResetBetweenFrames(); + + void set_spdy_state(SpdyState v); + + void SetSpdyErrorAndNotify(SpdyFramerError error, std::string detailed_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. + absl::optional<size_t> opt_pad_length_; + + // Temporary buffers for the AltSvc fields. + std::string alt_svc_origin_; + std::string alt_svc_value_; + + // Temporary buffers for PRIORITY_UPDATE fields. + uint32_t prioritized_stream_id_ = 0; + std::string priority_field_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_; + + // The HTTP/2 frame decoder. + 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(jamessynge): 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_ = SpdyState::SPDY_READY_FOR_FRAME; + SpdyFramerError spdy_framer_error_ = SpdyFramerError::SPDY_NO_ERROR; + + // The limit on the size of received HTTP/2 payloads as specified in the + // SETTINGS_MAX_FRAME_SIZE advertised to peer. + size_t max_frame_size_ = 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_ = false; + + // 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_ = false; + + // 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; +}; + +} // 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 QUICHE_EXPORT_PRIVATE SpdyFramerVisitorInterface { + public: + virtual ~SpdyFramerVisitorInterface() {} + + // Called if an error is detected in the SpdyFrame protocol. + virtual void OnError(http2::Http2DecoderAdapter::SpdyFramerError error, + std::string detailed_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*/, + absl::string_view /*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 PRIORITY_UPDATE frame is received on stream 0. + // |prioritized_stream_id| is the Prioritized Stream ID and + // |priority_field_value| is the Priority Field Value + // parsed from the frame payload. + virtual void OnPriorityUpdate(SpdyStreamId prioritized_stream_id, + absl::string_view priority_field_value) = 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 QUICHE_EXPORT_PRIVATE ExtensionVisitorInterface { + public: + virtual ~ExtensionVisitorInterface() {} + + // Called when non-standard SETTINGS are received. + 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/quiche/spdy/core/http2_header_block_hpack_listener.h b/chromium/net/third_party/quiche/src/quiche/spdy/core/http2_header_block_hpack_listener.h new file mode 100644 index 00000000000..795543ad23e --- /dev/null +++ b/chromium/net/third_party/quiche/src/quiche/spdy/core/http2_header_block_hpack_listener.h @@ -0,0 +1,47 @@ +#ifndef QUICHE_SPDY_CORE_HTTP2_HEADER_BLOCK_HPACK_LISTENER_H_ +#define QUICHE_SPDY_CORE_HTTP2_HEADER_BLOCK_HPACK_LISTENER_H_ + +#include "absl/strings/string_view.h" +#include "quiche/http2/hpack/decoder/hpack_decoder_listener.h" +#include "quiche/common/platform/api/quiche_logging.h" +#include "quiche/spdy/core/spdy_header_block.h" + +namespace spdy { + +// This class simply gathers the key-value pairs emitted by an HpackDecoder in +// a SpdyHeaderBlock. +class Http2HeaderBlockHpackListener : public http2::HpackDecoderListener { + public: + Http2HeaderBlockHpackListener() {} + + void OnHeaderListStart() override { + header_block_.clear(); + hpack_error_ = false; + } + + void OnHeader(const std::string& name, const std::string& value) override { + header_block_.AppendValueOrAddHeader(name, value); + } + + void OnHeaderListEnd() override {} + + void OnHeaderErrorDetected(absl::string_view error_message) override { + QUICHE_VLOG(1) << error_message; + hpack_error_ = true; + } + + SpdyHeaderBlock release_header_block() { + SpdyHeaderBlock block = std::move(header_block_); + header_block_ = {}; + return block; + } + bool hpack_error() const { return hpack_error_; } + + private: + SpdyHeaderBlock header_block_; + bool hpack_error_ = false; +}; + +} // namespace spdy + +#endif // QUICHE_SPDY_CORE_HTTP2_HEADER_BLOCK_HPACK_LISTENER_H_ diff --git a/chromium/net/third_party/quiche/src/quiche/spdy/core/metadata_extension.cc b/chromium/net/third_party/quiche/src/quiche/spdy/core/metadata_extension.cc new file mode 100644 index 00000000000..6eb10af93b9 --- /dev/null +++ b/chromium/net/third_party/quiche/src/quiche/spdy/core/metadata_extension.cc @@ -0,0 +1,195 @@ +#include "quiche/spdy/core/metadata_extension.h" + +#include <list> +#include <string> + +#include "absl/memory/memory.h" +#include "absl/strings/str_cat.h" +#include "quiche/http2/decoder/decode_buffer.h" +#include "quiche/http2/hpack/decoder/hpack_decoder.h" +#include "quiche/common/platform/api/quiche_bug_tracker.h" +#include "quiche/common/platform/api/quiche_logging.h" +#include "quiche/spdy/core/hpack/hpack_encoder.h" +#include "quiche/spdy/core/http2_header_block_hpack_listener.h" + +namespace spdy { + +// Non-standard constants related to METADATA frames. +const SpdySettingsId MetadataVisitor::kMetadataExtensionId = 0x4d44; +const uint8_t MetadataVisitor::kMetadataFrameType = 0x4d; +const uint8_t MetadataVisitor::kEndMetadataFlag = 0x4; + +namespace { + +const size_t kMaxMetadataBlockSize = 1 << 20; // 1 MB + +// This class uses an HpackEncoder to serialize a METADATA block as a series of +// METADATA frames. +class MetadataFrameSequence : public MetadataSerializer::FrameSequence { + public: + MetadataFrameSequence(SpdyStreamId stream_id, spdy::SpdyHeaderBlock payload) + : stream_id_(stream_id), payload_(std::move(payload)) { + // Metadata should not use HPACK compression. + encoder_.DisableCompression(); + HpackEncoder::Representations r; + for (const auto& kv_pair : payload_) { + r.push_back(kv_pair); + } + progressive_encoder_ = encoder_.EncodeRepresentations(r); + } + + // Copies are not allowed. + MetadataFrameSequence(const MetadataFrameSequence& other) = delete; + MetadataFrameSequence& operator=(const MetadataFrameSequence& other) = delete; + + std::unique_ptr<spdy::SpdyFrameIR> Next() override; + + private: + SpdyStreamId stream_id_; + SpdyHeaderBlock payload_; + HpackEncoder encoder_; + std::unique_ptr<HpackEncoder::ProgressiveEncoder> progressive_encoder_; +}; + +std::unique_ptr<spdy::SpdyFrameIR> MetadataFrameSequence::Next() { + if (!progressive_encoder_->HasNext()) { + return nullptr; + } + // METADATA frames obey the HTTP/2 maximum frame size. + std::string payload = + progressive_encoder_->Next(spdy::kHttp2DefaultFramePayloadLimit); + const bool end_metadata = (!progressive_encoder_->HasNext()); + const uint8_t flags = end_metadata ? MetadataVisitor::kEndMetadataFlag : 0; + return absl::make_unique<spdy::SpdyUnknownIR>( + stream_id_, MetadataVisitor::kMetadataFrameType, flags, + std::move(payload)); +} + +} // anonymous namespace + +struct MetadataVisitor::MetadataPayloadState { + MetadataPayloadState(size_t remaining, bool end) + : bytes_remaining(remaining), end_metadata(end) {} + std::list<std::string> buffer; + size_t bytes_remaining; + bool end_metadata; +}; + +MetadataVisitor::MetadataVisitor(OnCompletePayload on_payload, + OnMetadataSupport on_support) + : on_payload_(std::move(on_payload)), + on_support_(std::move(on_support)), + peer_supports_metadata_(MetadataSupportState::UNSPECIFIED) {} + +MetadataVisitor::~MetadataVisitor() {} + +void MetadataVisitor::OnSetting(SpdySettingsId id, uint32_t value) { + QUICHE_VLOG(1) << "MetadataVisitor::OnSetting(" << id << ", " << value << ")"; + if (id == kMetadataExtensionId) { + if (value == 0) { + const MetadataSupportState previous_state = peer_supports_metadata_; + peer_supports_metadata_ = MetadataSupportState::NOT_SUPPORTED; + if (previous_state == MetadataSupportState::UNSPECIFIED || + previous_state == MetadataSupportState::SUPPORTED) { + on_support_(false); + } + } else if (value == 1) { + const MetadataSupportState previous_state = peer_supports_metadata_; + peer_supports_metadata_ = MetadataSupportState::SUPPORTED; + if (previous_state == MetadataSupportState::UNSPECIFIED || + previous_state == MetadataSupportState::NOT_SUPPORTED) { + on_support_(true); + } + } else { + LOG_EVERY_N_SEC(WARNING, 1) + << "Unrecognized value for setting " << id << ": " << value; + } + } +} + +bool MetadataVisitor::OnFrameHeader(SpdyStreamId stream_id, size_t length, + uint8_t type, uint8_t flags) { + QUICHE_VLOG(1) << "OnFrameHeader(stream_id=" << stream_id + << ", length=" << length << ", type=" << static_cast<int>(type) + << ", flags=" << static_cast<int>(flags); + // TODO(birenroy): Consider disabling METADATA handling until our setting + // advertising METADATA support has been acked. + if (type != kMetadataFrameType) { + return false; + } + auto it = metadata_map_.find(stream_id); + if (it == metadata_map_.end()) { + auto state = absl::make_unique<MetadataPayloadState>( + length, flags & kEndMetadataFlag); + auto result = metadata_map_.insert(std::make_pair(stream_id, + std::move(state))); + QUICHE_BUG_IF(bug_if_2781_1, !result.second) << "Map insertion failed."; + it = result.first; + } else { + QUICHE_BUG_IF(bug_22051_1, it->second->end_metadata) + << "Inconsistent metadata payload state!"; + QUICHE_BUG_IF(bug_if_2781_2, it->second->bytes_remaining > 0) + << "Incomplete metadata block!"; + } + + if (it->second == nullptr) { + QUICHE_BUG(bug_2781_3) << "Null metadata payload state!"; + return false; + } + current_stream_ = stream_id; + it->second->bytes_remaining = length; + it->second->end_metadata = (flags & kEndMetadataFlag); + return true; +} + +void MetadataVisitor::OnFramePayload(const char* data, size_t len) { + QUICHE_VLOG(1) << "OnFramePayload(stream_id=" << current_stream_ + << ", len=" << len << ")"; + auto it = metadata_map_.find(current_stream_); + if (it == metadata_map_.end() || it->second == nullptr) { + QUICHE_BUG(bug_2781_4) << "Invalid order of operations on MetadataVisitor."; + } else { + MetadataPayloadState* state = it->second.get(); // For readability. + state->buffer.push_back(std::string(data, len)); + if (len < state->bytes_remaining) { + state->bytes_remaining -= len; + } else { + QUICHE_BUG_IF(bug_22051_2, len > state->bytes_remaining) + << "Metadata payload overflow! len: " << len + << " bytes_remaining: " << state->bytes_remaining; + state->bytes_remaining = 0; + if (state->end_metadata) { + // The whole process of decoding the HPACK-encoded metadata block, + // below, is more cumbersome than it ought to be. + spdy::Http2HeaderBlockHpackListener listener; + http2::HpackDecoder decoder(&listener, kMaxMetadataBlockSize); + + // If any operations fail, the decode process should be aborted. + bool success = decoder.StartDecodingBlock(); + for (const std::string& slice : state->buffer) { + if (!success) { + break; + } + http2::DecodeBuffer buffer(slice.data(), slice.size()); + success = success && decoder.DecodeFragment(&buffer); + } + success = + success && decoder.EndDecodingBlock() && !listener.hpack_error(); + if (success) { + on_payload_(current_stream_, listener.release_header_block()); + } + // TODO(birenroy): add varz counting metadata decode successes/failures. + metadata_map_.erase(it); + } + } + } +} + +std::unique_ptr<MetadataSerializer::FrameSequence> +MetadataSerializer::FrameSequenceForPayload(SpdyStreamId stream_id, + MetadataPayload payload) { + return absl::make_unique<MetadataFrameSequence>(stream_id, + std::move(payload)); +} + +} // namespace spdy diff --git a/chromium/net/third_party/quiche/src/quiche/spdy/core/metadata_extension.h b/chromium/net/third_party/quiche/src/quiche/spdy/core/metadata_extension.h new file mode 100644 index 00000000000..a27ad165caa --- /dev/null +++ b/chromium/net/third_party/quiche/src/quiche/spdy/core/metadata_extension.h @@ -0,0 +1,116 @@ +#ifndef QUICHE_SPDY_CORE_METADATA_EXTENSION_H_ +#define QUICHE_SPDY_CORE_METADATA_EXTENSION_H_ + +#include <memory> +#include <string> +#include <vector> + +#include "absl/container/flat_hash_map.h" +#include "quiche/spdy/core/http2_frame_decoder_adapter.h" +#include "quiche/spdy/core/spdy_header_block.h" +#include "quiche/spdy/core/spdy_protocol.h" +#include "quiche/spdy/core/zero_copy_output_buffer.h" + +namespace spdy { + +// An implementation of the ExtensionVisitorInterface that can parse +// METADATA frames. METADATA is a non-standard HTTP/2 extension developed and +// used internally at Google. A peer advertises support for METADATA by sending +// a setting with a setting ID of kMetadataExtensionId and a value of 1. +// +// Metadata is represented as a HPACK header block with literal encoding. +class MetadataVisitor : public spdy::ExtensionVisitorInterface { + public: + using MetadataPayload = spdy::SpdyHeaderBlock; + + static_assert(!std::is_copy_constructible<MetadataPayload>::value, + "MetadataPayload should be a move-only type!"); + + using OnMetadataSupport = std::function<void(bool)>; + using OnCompletePayload = + std::function<void(spdy::SpdyStreamId, MetadataPayload)>; + + // The HTTP/2 SETTINGS ID that is used to indicate support for METADATA + // frames. + static const spdy::SpdySettingsId kMetadataExtensionId; + + // The 8-bit frame type code for a METADATA frame. + static const uint8_t kMetadataFrameType; + + // The flag that indicates the end of a logical metadata block. Due to frame + // size limits, a single metadata block may be emitted as several HTTP/2 + // frames. + static const uint8_t kEndMetadataFlag; + + // |on_payload| is invoked whenever a complete metadata payload is received. + // |on_support| is invoked whenever the peer's advertised support for metadata + // changes. + MetadataVisitor(OnCompletePayload on_payload, OnMetadataSupport on_support); + ~MetadataVisitor() override; + + MetadataVisitor(const MetadataVisitor&) = delete; + MetadataVisitor& operator=(const MetadataVisitor&) = delete; + + // Interprets the non-standard setting indicating support for METADATA. + void OnSetting(spdy::SpdySettingsId id, uint32_t value) override; + + // Returns true iff |type| indicates a METADATA frame. + bool OnFrameHeader(spdy::SpdyStreamId stream_id, size_t length, uint8_t type, + uint8_t flags) override; + + // Consumes a METADATA frame payload. Invokes the registered callback when a + // complete payload has been received. + void OnFramePayload(const char* data, size_t len) override; + + // Returns true if the peer has advertised support for METADATA via the + // appropriate setting. + bool PeerSupportsMetadata() const { + return peer_supports_metadata_ == MetadataSupportState::SUPPORTED; + } + + private: + enum class MetadataSupportState : uint8_t { + UNSPECIFIED, + SUPPORTED, + NOT_SUPPORTED, + }; + + struct MetadataPayloadState; + + using StreamMetadataMap = + absl::flat_hash_map<spdy::SpdyStreamId, + std::unique_ptr<MetadataPayloadState>>; + + OnCompletePayload on_payload_; + OnMetadataSupport on_support_; + StreamMetadataMap metadata_map_; + spdy::SpdyStreamId current_stream_; + MetadataSupportState peer_supports_metadata_; +}; + +// A class that serializes metadata blocks as sequences of frames. +class MetadataSerializer { + public: + using MetadataPayload = spdy::SpdyHeaderBlock; + + class FrameSequence { + public: + virtual ~FrameSequence() {} + + // Returns nullptr once the sequence has been exhausted. + virtual std::unique_ptr<spdy::SpdyFrameIR> Next() = 0; + }; + + MetadataSerializer() {} + + MetadataSerializer(const MetadataSerializer&) = delete; + MetadataSerializer& operator=(const MetadataSerializer&) = delete; + + // Returns nullptr on failure. + std::unique_ptr<FrameSequence> FrameSequenceForPayload( + spdy::SpdyStreamId stream_id, MetadataPayload payload); +}; + +} // namespace spdy + +#endif // QUICHE_SPDY_CORE_METADATA_EXTENSION_H_ diff --git a/chromium/net/third_party/quiche/src/quiche/spdy/core/metadata_extension_test.cc b/chromium/net/third_party/quiche/src/quiche/spdy/core/metadata_extension_test.cc new file mode 100644 index 00000000000..baf4a88c454 --- /dev/null +++ b/chromium/net/third_party/quiche/src/quiche/spdy/core/metadata_extension_test.cc @@ -0,0 +1,229 @@ +#include "quiche/spdy/core/metadata_extension.h" + +#include "absl/container/flat_hash_map.h" +#include "absl/functional/bind_front.h" +#include "absl/strings/str_cat.h" +#include "absl/strings/string_view.h" +#include "quiche/common/platform/api/quiche_test.h" +#include "quiche/spdy/core/array_output_buffer.h" +#include "quiche/spdy/core/mock_spdy_framer_visitor.h" +#include "quiche/spdy/core/spdy_framer.h" +#include "quiche/spdy/core/spdy_header_block.h" +#include "quiche/spdy/core/spdy_no_op_visitor.h" +#include "quiche/spdy/core/spdy_protocol.h" + +namespace spdy { +namespace test { +namespace { + +using ::absl::bind_front; +using ::spdy::SpdyFramer; +using ::spdy::SpdyHeaderBlock; +using ::spdy::test::MockSpdyFramerVisitor; +using ::testing::_; +using ::testing::ElementsAre; +using ::testing::IsEmpty; + +const size_t kBufferSize = 64 * 1024; +char kBuffer[kBufferSize]; + +class MetadataExtensionTest : public QuicheTest { + protected: + MetadataExtensionTest() : test_buffer_(kBuffer, kBufferSize) {} + + void SetUp() override { + extension_ = absl::make_unique<MetadataVisitor>( + bind_front(&MetadataExtensionTest::OnCompletePayload, this), + bind_front(&MetadataExtensionTest::OnMetadataSupport, this)); + } + + void OnCompletePayload(spdy::SpdyStreamId stream_id, + MetadataVisitor::MetadataPayload payload) { + ++received_count_; + received_payload_map_.insert(std::make_pair(stream_id, std::move(payload))); + } + + void OnMetadataSupport(bool peer_supports_metadata) { + EXPECT_EQ(peer_supports_metadata, extension_->PeerSupportsMetadata()); + received_metadata_support_.push_back(peer_supports_metadata); + } + + MetadataSerializer::MetadataPayload PayloadForData(absl::string_view data) { + SpdyHeaderBlock block; + block["example-payload"] = data; + return block; + } + + std::unique_ptr<MetadataVisitor> extension_; + absl::flat_hash_map<spdy::SpdyStreamId, SpdyHeaderBlock> + received_payload_map_; + std::vector<bool> received_metadata_support_; + size_t received_count_ = 0; + spdy::ArrayOutputBuffer test_buffer_; +}; + +// This test verifies that the MetadataVisitor is initialized to a state where +// it believes the peer does not support metadata. +TEST_F(MetadataExtensionTest, MetadataNotSupported) { + EXPECT_FALSE(extension_->PeerSupportsMetadata()); + EXPECT_THAT(received_metadata_support_, IsEmpty()); +} + +// This test verifies that upon receiving a specific setting, the extension +// realizes that the peer supports metadata. +TEST_F(MetadataExtensionTest, MetadataSupported) { + EXPECT_FALSE(extension_->PeerSupportsMetadata()); + // 3 is not an appropriate value for the metadata extension key. + extension_->OnSetting(MetadataVisitor::kMetadataExtensionId, 3); + EXPECT_FALSE(extension_->PeerSupportsMetadata()); + extension_->OnSetting(MetadataVisitor::kMetadataExtensionId, 1); + ASSERT_TRUE(extension_->PeerSupportsMetadata()); + extension_->OnSetting(MetadataVisitor::kMetadataExtensionId, 0); + EXPECT_FALSE(extension_->PeerSupportsMetadata()); + EXPECT_THAT(received_metadata_support_, ElementsAre(true, false)); +} + +TEST_F(MetadataExtensionTest, MetadataIgnoredWithoutExtension) { + const char kData[] = "some payload"; + SpdyHeaderBlock payload = PayloadForData(kData); + + extension_->OnSetting(MetadataVisitor::kMetadataExtensionId, 1); + ASSERT_TRUE(extension_->PeerSupportsMetadata()); + + MetadataSerializer serializer; + auto sequence = serializer.FrameSequenceForPayload(3, std::move(payload)); + ASSERT_TRUE(sequence != nullptr); + + http2::Http2DecoderAdapter deframer; + ::testing::StrictMock<MockSpdyFramerVisitor> visitor; + deframer.set_visitor(&visitor); + + EXPECT_CALL(visitor, + OnCommonHeader(3, _, MetadataVisitor::kMetadataFrameType, _)); + // The Return(true) should not be necessary. http://b/36023792 + EXPECT_CALL(visitor, OnUnknownFrame(3, MetadataVisitor::kMetadataFrameType)) + .WillOnce(::testing::Return(true)); + + SpdyFramer framer(SpdyFramer::ENABLE_COMPRESSION); + auto frame = sequence->Next(); + ASSERT_TRUE(frame != nullptr); + while (frame != nullptr) { + const size_t frame_size = framer.SerializeFrame(*frame, &test_buffer_); + ASSERT_GT(frame_size, 0u); + ASSERT_FALSE(deframer.HasError()); + ASSERT_EQ(frame_size, test_buffer_.Size()); + EXPECT_EQ(frame_size, deframer.ProcessInput(kBuffer, frame_size)); + test_buffer_.Reset(); + frame = sequence->Next(); + } + EXPECT_FALSE(deframer.HasError()); + EXPECT_THAT(received_metadata_support_, ElementsAre(true)); +} + +// This test verifies that the METADATA frame emitted by a MetadataExtension +// can be parsed by another SpdyFramer with a MetadataVisitor. +TEST_F(MetadataExtensionTest, MetadataPayloadEndToEnd) { + SpdyHeaderBlock block1; + block1["foo"] = "Some metadata value."; + SpdyHeaderBlock block2; + block2["bar"] = + "The color taupe truly represents a triumph of the human spirit over " + "adversity."; + block2["baz"] = + "Or perhaps it represents abject surrender to the implacable and " + "incomprehensible forces of the universe."; + const absl::string_view binary_payload{"binary\0payload", 14}; + block2["qux"] = binary_payload; + EXPECT_EQ(binary_payload, block2["qux"]); + for (const SpdyHeaderBlock& payload_block : + {std::move(block1), std::move(block2)}) { + extension_->OnSetting(MetadataVisitor::kMetadataExtensionId, 1); + ASSERT_TRUE(extension_->PeerSupportsMetadata()); + + MetadataSerializer serializer; + auto sequence = + serializer.FrameSequenceForPayload(3, payload_block.Clone()); + ASSERT_TRUE(sequence != nullptr); + + http2::Http2DecoderAdapter deframer; + ::spdy::SpdyNoOpVisitor visitor; + deframer.set_visitor(&visitor); + deframer.set_extension_visitor(extension_.get()); + SpdyFramer framer(SpdyFramer::ENABLE_COMPRESSION); + auto frame = sequence->Next(); + ASSERT_TRUE(frame != nullptr); + while (frame != nullptr) { + const size_t frame_size = framer.SerializeFrame(*frame, &test_buffer_); + ASSERT_GT(frame_size, 0u); + ASSERT_FALSE(deframer.HasError()); + ASSERT_EQ(frame_size, test_buffer_.Size()); + EXPECT_EQ(frame_size, deframer.ProcessInput(kBuffer, frame_size)); + test_buffer_.Reset(); + frame = sequence->Next(); + } + EXPECT_EQ(1u, received_count_); + auto it = received_payload_map_.find(3); + ASSERT_TRUE(it != received_payload_map_.end()); + EXPECT_EQ(payload_block, it->second); + + received_count_ = 0; + received_payload_map_.clear(); + } +} + +// This test verifies that METADATA frames for two different streams can be +// interleaved and still successfully parsed by another SpdyFramer with a +// MetadataVisitor. +TEST_F(MetadataExtensionTest, MetadataPayloadInterleaved) { + const std::string kData1 = std::string(65 * 1024, 'a'); + const std::string kData2 = std::string(65 * 1024, 'b'); + const SpdyHeaderBlock payload1 = PayloadForData(kData1); + const SpdyHeaderBlock payload2 = PayloadForData(kData2); + + extension_->OnSetting(MetadataVisitor::kMetadataExtensionId, 1); + ASSERT_TRUE(extension_->PeerSupportsMetadata()); + + MetadataSerializer serializer; + auto sequence1 = serializer.FrameSequenceForPayload(3, payload1.Clone()); + ASSERT_TRUE(sequence1 != nullptr); + + auto sequence2 = serializer.FrameSequenceForPayload(5, payload2.Clone()); + ASSERT_TRUE(sequence2 != nullptr); + + http2::Http2DecoderAdapter deframer; + ::spdy::SpdyNoOpVisitor visitor; + deframer.set_visitor(&visitor); + deframer.set_extension_visitor(extension_.get()); + + SpdyFramer framer(SpdyFramer::ENABLE_COMPRESSION); + auto frame1 = sequence1->Next(); + ASSERT_TRUE(frame1 != nullptr); + auto frame2 = sequence2->Next(); + ASSERT_TRUE(frame2 != nullptr); + while (frame1 != nullptr || frame2 != nullptr) { + for (auto frame : {frame1.get(), frame2.get()}) { + if (frame != nullptr) { + const size_t frame_size = framer.SerializeFrame(*frame, &test_buffer_); + ASSERT_GT(frame_size, 0u); + ASSERT_FALSE(deframer.HasError()); + ASSERT_EQ(frame_size, test_buffer_.Size()); + EXPECT_EQ(frame_size, deframer.ProcessInput(kBuffer, frame_size)); + test_buffer_.Reset(); + } + } + frame1 = sequence1->Next(); + frame2 = sequence2->Next(); + } + EXPECT_EQ(2u, received_count_); + auto it = received_payload_map_.find(3); + ASSERT_TRUE(it != received_payload_map_.end()); + EXPECT_EQ(payload1, it->second); + + it = received_payload_map_.find(5); + ASSERT_TRUE(it != received_payload_map_.end()); + EXPECT_EQ(payload2, it->second); +} + +} // anonymous namespace +} // namespace test +} // namespace spdy diff --git a/chromium/net/third_party/quiche/src/quiche/spdy/core/mock_spdy_framer_visitor.cc b/chromium/net/third_party/quiche/src/quiche/spdy/core/mock_spdy_framer_visitor.cc new file mode 100644 index 00000000000..cdc89069dff --- /dev/null +++ b/chromium/net/third_party/quiche/src/quiche/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 "quiche/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/quiche/spdy/core/mock_spdy_framer_visitor.h b/chromium/net/third_party/quiche/src/quiche/spdy/core/mock_spdy_framer_visitor.h new file mode 100644 index 00000000000..8aa79fd4d65 --- /dev/null +++ b/chromium/net/third_party/quiche/src/quiche/spdy/core/mock_spdy_framer_visitor.h @@ -0,0 +1,150 @@ +// 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 <utility> + +#include "absl/strings/string_view.h" +#include "quiche/common/platform/api/quiche_test.h" +#include "quiche/spdy/core/http2_frame_decoder_adapter.h" +#include "quiche/spdy/core/recording_headers_handler.h" +#include "quiche/spdy/core/spdy_test_utils.h" + +namespace spdy { + +namespace test { + +class QUICHE_NO_EXPORT MockSpdyFramerVisitor + : public SpdyFramerVisitorInterface { + public: + MockSpdyFramerVisitor(); + ~MockSpdyFramerVisitor() override; + + MOCK_METHOD(void, + OnError, + (http2::Http2DecoderAdapter::SpdyFramerError error, + std::string detailed_error), + (override)); + MOCK_METHOD(void, OnCommonHeader, + (SpdyStreamId stream_id, size_t length, uint8_t type, + uint8_t flags), + (override)); + MOCK_METHOD(void, + OnDataFrameHeader, + (SpdyStreamId stream_id, size_t length, bool fin), + (override)); + MOCK_METHOD(void, + OnStreamFrameData, + (SpdyStreamId stream_id, const char* data, size_t len), + (override)); + MOCK_METHOD(void, OnStreamEnd, (SpdyStreamId stream_id), (override)); + MOCK_METHOD(void, + OnStreamPadLength, + (SpdyStreamId stream_id, size_t value), + (override)); + MOCK_METHOD(void, + OnStreamPadding, + (SpdyStreamId stream_id, size_t len), + (override)); + MOCK_METHOD(SpdyHeadersHandlerInterface*, + OnHeaderFrameStart, + (SpdyStreamId stream_id), + (override)); + MOCK_METHOD(void, OnHeaderFrameEnd, (SpdyStreamId stream_id), (override)); + MOCK_METHOD(void, + OnRstStream, + (SpdyStreamId stream_id, SpdyErrorCode error_code), + (override)); + MOCK_METHOD(void, OnSettings, (), (override)); + MOCK_METHOD(void, OnSetting, (SpdySettingsId id, uint32_t value), (override)); + MOCK_METHOD(void, OnPing, (SpdyPingId unique_id, bool is_ack), (override)); + MOCK_METHOD(void, OnSettingsEnd, (), (override)); + MOCK_METHOD(void, OnSettingsAck, (), (override)); + MOCK_METHOD(void, + OnGoAway, + (SpdyStreamId last_accepted_stream_id, SpdyErrorCode error_code), + (override)); + MOCK_METHOD(bool, OnGoAwayFrameData, (const char* goaway_data, size_t len), + (override)); + MOCK_METHOD(void, + OnHeaders, + (SpdyStreamId stream_id, + bool has_priority, + int weight, + SpdyStreamId parent_stream_id, + bool exclusive, + bool fin, + bool end), + (override)); + MOCK_METHOD(void, + OnWindowUpdate, + (SpdyStreamId stream_id, int delta_window_size), + (override)); + MOCK_METHOD(void, + OnPushPromise, + (SpdyStreamId stream_id, + SpdyStreamId promised_stream_id, + bool end), + (override)); + MOCK_METHOD(void, + OnContinuation, + (SpdyStreamId stream_id, bool end), + (override)); + MOCK_METHOD( + void, + OnAltSvc, + (SpdyStreamId stream_id, + absl::string_view origin, + const SpdyAltSvcWireFormat::AlternativeServiceVector& altsvc_vector), + (override)); + MOCK_METHOD(void, + OnPriority, + (SpdyStreamId stream_id, + SpdyStreamId parent_stream_id, + int weight, + bool exclusive), + (override)); + MOCK_METHOD(void, + OnPriorityUpdate, + (SpdyStreamId prioritized_stream_id, + absl::string_view priority_field_value), + (override)); + MOCK_METHOD(bool, + OnUnknownFrame, + (SpdyStreamId stream_id, uint8_t frame_type), + (override)); + + 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_ = std::make_unique<RecordingHeadersHandler>(); + } + 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/quiche/spdy/core/no_op_headers_handler.h b/chromium/net/third_party/quiche/src/quiche/spdy/core/no_op_headers_handler.h new file mode 100644 index 00000000000..9db36d2d133 --- /dev/null +++ b/chromium/net/third_party/quiche/src/quiche/spdy/core/no_op_headers_handler.h @@ -0,0 +1,39 @@ +#ifndef QUICHE_SPDY_CORE_NO_OP_HEADERS_HANDLER_H_ +#define QUICHE_SPDY_CORE_NO_OP_HEADERS_HANDLER_H_ + +#include "quiche/common/platform/api/quiche_export.h" +#include "quiche/spdy/core/header_byte_listener_interface.h" +#include "quiche/spdy/core/spdy_headers_handler_interface.h" + +namespace spdy { + +// Drops all header data, but passes information about header bytes parsed to +// a listener. +class QUICHE_EXPORT_PRIVATE NoOpHeadersHandler + : public SpdyHeadersHandlerInterface { + public: + // Does not take ownership of listener. + explicit NoOpHeadersHandler(HeaderByteListenerInterface* listener) + : listener_(listener) {} + NoOpHeadersHandler(const NoOpHeadersHandler&) = delete; + NoOpHeadersHandler& operator=(const NoOpHeadersHandler&) = delete; + ~NoOpHeadersHandler() override {} + + // From SpdyHeadersHandlerInterface + void OnHeaderBlockStart() override {} + void OnHeader(absl::string_view /*key*/, + absl::string_view /*value*/) override {} + void OnHeaderBlockEnd(size_t uncompressed_header_bytes, + size_t /* compressed_header_bytes */) override { + if (listener_ != nullptr) { + listener_->OnHeaderBytesReceived(uncompressed_header_bytes); + } + } + + private: + HeaderByteListenerInterface* listener_; +}; + +} // namespace spdy + +#endif // QUICHE_SPDY_CORE_NO_OP_HEADERS_HANDLER_H_ diff --git a/chromium/net/third_party/quiche/src/quiche/spdy/core/recording_headers_handler.cc b/chromium/net/third_party/quiche/src/quiche/spdy/core/recording_headers_handler.cc new file mode 100644 index 00000000000..7808e7f6944 --- /dev/null +++ b/chromium/net/third_party/quiche/src/quiche/spdy/core/recording_headers_handler.cc @@ -0,0 +1,38 @@ +// Copyright (c) 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "quiche/spdy/core/recording_headers_handler.h" + +namespace spdy { + +RecordingHeadersHandler::RecordingHeadersHandler( + SpdyHeadersHandlerInterface* wrapped) + : wrapped_(wrapped) {} + +void RecordingHeadersHandler::OnHeaderBlockStart() { + block_.clear(); + if (wrapped_ != nullptr) { + wrapped_->OnHeaderBlockStart(); + } +} + +void RecordingHeadersHandler::OnHeader(absl::string_view key, + absl::string_view value) { + block_.AppendValueOrAddHeader(key, value); + if (wrapped_ != nullptr) { + wrapped_->OnHeader(key, value); + } +} + +void RecordingHeadersHandler::OnHeaderBlockEnd(size_t uncompressed_header_bytes, + size_t compressed_header_bytes) { + uncompressed_header_bytes_ = uncompressed_header_bytes; + compressed_header_bytes_ = compressed_header_bytes; + if (wrapped_ != nullptr) { + wrapped_->OnHeaderBlockEnd(uncompressed_header_bytes, + compressed_header_bytes); + } +} + +} // namespace spdy diff --git a/chromium/net/third_party/quiche/src/quiche/spdy/core/recording_headers_handler.h b/chromium/net/third_party/quiche/src/quiche/spdy/core/recording_headers_handler.h new file mode 100644 index 00000000000..a2c06ecaace --- /dev/null +++ b/chromium/net/third_party/quiche/src/quiche/spdy/core/recording_headers_handler.h @@ -0,0 +1,51 @@ +// Copyright (c) 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef QUICHE_SPDY_CORE_RECORDING_HEADERS_HANDLER_H_ +#define QUICHE_SPDY_CORE_RECORDING_HEADERS_HANDLER_H_ + +#include <cstddef> +#include <cstdint> +#include <string> + +#include "absl/strings/string_view.h" +#include "quiche/common/platform/api/quiche_export.h" +#include "quiche/spdy/core/spdy_header_block.h" +#include "quiche/spdy/core/spdy_headers_handler_interface.h" + +namespace spdy { + +// RecordingHeadersHandler copies the headers emitted from the deframer, and +// when needed can forward events to another wrapped handler. +class QUICHE_EXPORT_PRIVATE RecordingHeadersHandler + : public SpdyHeadersHandlerInterface { + public: + explicit RecordingHeadersHandler( + SpdyHeadersHandlerInterface* wrapped = nullptr); + RecordingHeadersHandler(const RecordingHeadersHandler&) = delete; + RecordingHeadersHandler& operator=(const RecordingHeadersHandler&) = delete; + + void OnHeaderBlockStart() override; + + void OnHeader(absl::string_view key, absl::string_view value) override; + + void OnHeaderBlockEnd(size_t uncompressed_header_bytes, + size_t compressed_header_bytes) override; + + const Http2HeaderBlock& decoded_block() const { return block_; } + size_t uncompressed_header_bytes() const { + return uncompressed_header_bytes_; + } + size_t compressed_header_bytes() const { return compressed_header_bytes_; } + + private: + SpdyHeadersHandlerInterface* wrapped_ = nullptr; + Http2HeaderBlock block_; + size_t uncompressed_header_bytes_ = 0; + size_t compressed_header_bytes_ = 0; +}; + +} // namespace spdy + +#endif // QUICHE_SPDY_CORE_RECORDING_HEADERS_HANDLER_H_ diff --git a/chromium/net/third_party/quiche/src/quiche/spdy/core/spdy_alt_svc_wire_format.cc b/chromium/net/third_party/quiche/src/quiche/spdy/core/spdy_alt_svc_wire_format.cc new file mode 100644 index 00000000000..08c301e5773 --- /dev/null +++ b/chromium/net/third_party/quiche/src/quiche/spdy/core/spdy_alt_svc_wire_format.cc @@ -0,0 +1,427 @@ +// 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 "quiche/spdy/core/spdy_alt_svc_wire_format.h" + +#include <algorithm> +#include <cctype> +#include <limits> + +#include "absl/strings/str_cat.h" + +#include "quiche/common/platform/api/quiche_logging.h" + +namespace spdy { + +namespace { + +template <class T> +bool ParsePositiveIntegerImpl(absl::string_view::const_iterator c, + absl::string_view::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 std::string& protocol_id, const std::string& host, uint16_t port, + uint32_t max_age_seconds, VersionVector version) + : protocol_id(protocol_id), + host(host), + port(port), + max_age_seconds(max_age_seconds), + version(std::move(version)) {} + +SpdyAltSvcWireFormat::AlternativeService::~AlternativeService() = default; + +SpdyAltSvcWireFormat::AlternativeService::AlternativeService( + const AlternativeService& other) = default; + +// static +bool SpdyAltSvcWireFormat::ParseHeaderFieldValue( + absl::string_view value, + AlternativeServiceVector* altsvc_vector) { + // Empty value is invalid according to the specification. + if (value.empty()) { + return false; + } + altsvc_vector->clear(); + if (value == absl::string_view("clear")) { + return true; + } + absl::string_view::const_iterator c = value.begin(); + while (c != value.end()) { + // Parse protocol-id. + absl::string_view::const_iterator percent_encoded_protocol_id_end = + std::find(c, value.end(), '='); + std::string 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. + QUICHE_DCHECK_EQ('=', *c); + ++c; + if (c == value.end() || *c != '"') { + return false; + } + ++c; + absl::string_view::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; + } + QUICHE_DCHECK_EQ('"', *c); + std::string host; + uint16_t port; + if (!ParseAltAuthority(alt_authority_begin, c, &host, &port)) { + return false; + } + ++c; + // Parse parameters. + uint32_t max_age_seconds = 86400; + VersionVector version; + absl::string_view::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; + } + std::string 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); + absl::string_view::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_seconds)) { + 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(), ','); + absl::string_view::const_iterator v_begin = parameter_value_begin + 1; + while (v_begin < c) { + absl::string_view::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 (!HexDecodeToUInt32(absl::string_view(&*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_seconds, + version); + for (; c != value.end() && (*c == ' ' || *c == '\t' || *c == ','); ++c) { + } + } + return true; +} + +// static +std::string SpdyAltSvcWireFormat::SerializeHeaderFieldValue( + const AlternativeServiceVector& altsvc_vector) { + if (altsvc_vector.empty()) { + return std::string("clear"); + } + const char kNibbleToHex[] = "0123456789ABCDEF"; + std::string 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); + } + absl::StrAppend(&value, ":", altsvc.port, "\""); + if (altsvc.max_age_seconds != 86400) { + absl::StrAppend(&value, "; ma=", altsvc.max_age_seconds); + } + if (!altsvc.version.empty()) { + if (is_ietf_format_quic) { + for (uint32_t quic_version : altsvc.version) { + absl::StrAppend(&value, "; quic=", absl::Hex(quic_version)); + } + } else { + value.append("; v=\""); + for (auto it = altsvc.version.begin(); it != altsvc.version.end(); + ++it) { + if (it != altsvc.version.begin()) { + value.append(","); + } + absl::StrAppend(&value, *it); + } + value.append("\""); + } + } + } + return value; +} + +// static +void SpdyAltSvcWireFormat::SkipWhiteSpace( + absl::string_view::const_iterator* c, + absl::string_view::const_iterator end) { + for (; *c != end && (**c == ' ' || **c == '\t'); ++*c) { + } +} + +// static +bool SpdyAltSvcWireFormat::PercentDecode(absl::string_view::const_iterator c, + absl::string_view::const_iterator end, + std::string* output) { + output->clear(); + for (; c != end; ++c) { + if (*c != '%') { + output->push_back(*c); + continue; + } + QUICHE_DCHECK_EQ('%', *c); + ++c; + if (c == end || !std::isxdigit(*c)) { + return false; + } + // Network byte order is big-endian. + char decoded = HexDigitToInt(*c) << 4; + ++c; + if (c == end || !std::isxdigit(*c)) { + return false; + } + decoded += HexDigitToInt(*c); + output->push_back(decoded); + } + return true; +} + +// static +bool SpdyAltSvcWireFormat::ParseAltAuthority( + absl::string_view::const_iterator c, + absl::string_view::const_iterator end, + std::string* 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; + } + QUICHE_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; + } + QUICHE_DCHECK_EQ(':', *c); + ++c; + return ParsePositiveInteger16(c, end, port); +} + +// static +bool SpdyAltSvcWireFormat::ParsePositiveInteger16( + absl::string_view::const_iterator c, + absl::string_view::const_iterator end, + uint16_t* value) { + return ParsePositiveIntegerImpl<uint16_t>(c, end, value); +} + +// static +bool SpdyAltSvcWireFormat::ParsePositiveInteger32( + absl::string_view::const_iterator c, + absl::string_view::const_iterator end, + uint32_t* value) { + return ParsePositiveIntegerImpl<uint32_t>(c, end, value); +} + +// static +char SpdyAltSvcWireFormat::HexDigitToInt(char c) { + QUICHE_DCHECK(std::isxdigit(c)); + + if (std::isdigit(c)) { + return c - '0'; + } + if (c >= 'A' && c <= 'F') { + return c - 'A' + 10; + } + if (c >= 'a' && c <= 'f') { + return c - 'a' + 10; + } + + return 0; +} + +// static +bool SpdyAltSvcWireFormat::HexDecodeToUInt32(absl::string_view data, + uint32_t* value) { + if (data.empty() || data.length() > 8u) { + return false; + } + + *value = 0; + for (char c : data) { + if (!std::isxdigit(c)) { + return false; + } + + *value <<= 4; + *value += HexDigitToInt(c); + } + + return true; +} + +} // namespace spdy diff --git a/chromium/net/third_party/quiche/src/quiche/spdy/core/spdy_alt_svc_wire_format.h b/chromium/net/third_party/quiche/src/quiche/spdy/core/spdy_alt_svc_wire_format.h new file mode 100644 index 00000000000..b81a97953b1 --- /dev/null +++ b/chromium/net/third_party/quiche/src/quiche/spdy/core/spdy_alt_svc_wire_format.h @@ -0,0 +1,105 @@ +// 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 <string> +#include <vector> + +#include "absl/container/inlined_vector.h" +#include "absl/strings/string_view.h" +#include "quiche/common/platform/api/quiche_export.h" + +namespace spdy { + +namespace test { +class SpdyAltSvcWireFormatPeer; +} // namespace test + +class QUICHE_EXPORT_PRIVATE SpdyAltSvcWireFormat { + public: + using VersionVector = absl::InlinedVector<uint32_t, 8>; + + struct QUICHE_EXPORT_PRIVATE AlternativeService { + std::string protocol_id; + std::string host; + + // Default is 0: invalid port. + uint16_t port = 0; + // Default is one day. + uint32_t max_age_seconds = 86400; + // Default is empty: unspecified version. + VersionVector version; + + AlternativeService(); + AlternativeService(const std::string& protocol_id, const std::string& host, + uint16_t port, uint32_t max_age_seconds, + 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_seconds == other.max_age_seconds; + } + }; + // 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(absl::string_view value, + AlternativeServiceVector* altsvc_vector); + static std::string SerializeHeaderFieldValue( + const AlternativeServiceVector& altsvc_vector); + + private: + // Forward |*c| over space and tab or until |end| is reached. + static void SkipWhiteSpace(absl::string_view::const_iterator* c, + absl::string_view::const_iterator end); + // Decode percent-decoded string between |c| and |end| into |*output|. + // Return true on success, false if input is invalid. + static bool PercentDecode(absl::string_view::const_iterator c, + absl::string_view::const_iterator end, + std::string* output); + // Parse the authority part of Alt-Svc between |c| and |end| into |*host| and + // |*port|. Return true on success, false if input is invalid. + static bool ParseAltAuthority(absl::string_view::const_iterator c, + absl::string_view::const_iterator end, + std::string* host, + uint16_t* port); + // Parse a positive integer between |c| and |end| into |*value|. + // Return true on success, false if input is not a positive integer or it + // cannot be represented on uint16_t. + static bool ParsePositiveInteger16(absl::string_view::const_iterator c, + absl::string_view::const_iterator end, + uint16_t* value); + // Parse a positive integer between |c| and |end| into |*value|. + // Return true on success, false if input is not a positive integer or it + // cannot be represented on uint32_t. + static bool ParsePositiveInteger32(absl::string_view::const_iterator c, + absl::string_view::const_iterator end, + uint32_t* value); + // Parse |c| as hexadecimal digit, case insensitive. |c| must be [0-9a-fA-F]. + // Output is between 0 and 15. + static char HexDigitToInt(char c); + // Parse |data| as hexadecimal number into |*value|. |data| must only contain + // hexadecimal digits, no "0x" prefix. + // Return true on success, false if input is empty, not valid hexadecimal + // number, or cannot be represented on uint32_t. + static bool HexDecodeToUInt32(absl::string_view data, uint32_t* value); +}; + +} // namespace spdy + +#endif // QUICHE_SPDY_CORE_SPDY_ALT_SVC_WIRE_FORMAT_H_ diff --git a/chromium/net/third_party/quiche/src/quiche/spdy/core/spdy_alt_svc_wire_format_test.cc b/chromium/net/third_party/quiche/src/quiche/spdy/core/spdy_alt_svc_wire_format_test.cc new file mode 100644 index 00000000000..e9cbc67fcb1 --- /dev/null +++ b/chromium/net/third_party/quiche/src/quiche/spdy/core/spdy_alt_svc_wire_format_test.cc @@ -0,0 +1,638 @@ +// 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 "quiche/spdy/core/spdy_alt_svc_wire_format.h" + +#include "quiche/common/platform/api/quiche_test.h" + +namespace spdy { + +namespace test { + +// Expose all private methods of class SpdyAltSvcWireFormat. +class SpdyAltSvcWireFormatPeer { + public: + static void SkipWhiteSpace(absl::string_view::const_iterator* c, + absl::string_view::const_iterator end) { + SpdyAltSvcWireFormat::SkipWhiteSpace(c, end); + } + static bool PercentDecode(absl::string_view::const_iterator c, + absl::string_view::const_iterator end, + std::string* output) { + return SpdyAltSvcWireFormat::PercentDecode(c, end, output); + } + static bool ParseAltAuthority(absl::string_view::const_iterator c, + absl::string_view::const_iterator end, + std::string* host, + uint16_t* port) { + return SpdyAltSvcWireFormat::ParseAltAuthority(c, end, host, port); + } + static bool ParsePositiveInteger16(absl::string_view::const_iterator c, + absl::string_view::const_iterator end, + uint16_t* max_age_seconds) { + return SpdyAltSvcWireFormat::ParsePositiveInteger16(c, end, + max_age_seconds); + } + static bool ParsePositiveInteger32(absl::string_view::const_iterator c, + absl::string_view::const_iterator end, + uint32_t* max_age_seconds) { + return SpdyAltSvcWireFormat::ParsePositiveInteger32(c, end, + max_age_seconds); + } + static char HexDigitToInt(char c) { + return SpdyAltSvcWireFormat::HexDigitToInt(c); + } + static bool HexDecodeToUInt32(absl::string_view data, uint32_t* value) { + return SpdyAltSvcWireFormat::HexDecodeToUInt32(data, value); + } +}; + +namespace { + +// Generate header field values, possibly with multiply defined parameters and +// random case, and corresponding AlternativeService entries. +void FuzzHeaderFieldValue( + int i, + std::string* 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_seconds = 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_seconds = 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, + std::string* 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_seconds = 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_seconds); + 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) { + std::string 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_seconds, + altsvc_vector[0].max_age_seconds); + EXPECT_EQ(expected_altsvc.version, altsvc_vector[0].version); + + // Roundtrip test starting with |altsvc_vector|. + std::string 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_seconds, + roundtrip_altsvc_vector[0].max_age_seconds); + 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;) { + std::string 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_seconds, + altsvc_vector[j].max_age_seconds); + EXPECT_EQ(expected_altsvc_vector[j].version, altsvc_vector[j].version); + } + + // Roundtrip test starting with |altsvc_vector|. + std::string 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_seconds, + roundtrip_altsvc_vector[j].max_age_seconds); + 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; + std::string 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_seconds, parsed_altsvc_vector[0].max_age_seconds); + 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; + std::string 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_seconds, parsed_it->max_age_seconds); + 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 std::string& 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) { + absl::string_view input("a \tb "); + absl::string_view::const_iterator c = input.begin(); + SpdyAltSvcWireFormatPeer::SkipWhiteSpace(&c, input.end()); + ASSERT_EQ(input.begin(), c); + ++c; + SpdyAltSvcWireFormatPeer::SkipWhiteSpace(&c, input.end()); + ASSERT_EQ(input.begin() + 3, c); + ++c; + SpdyAltSvcWireFormatPeer::SkipWhiteSpace(&c, input.end()); + ASSERT_EQ(input.end(), c); +} + +// Test PercentDecode() on valid input. +TEST(SpdyAltSvcWireFormatTest, PercentDecodeValid) { + absl::string_view input(""); + std::string output; + ASSERT_TRUE(SpdyAltSvcWireFormatPeer::PercentDecode(input.begin(), + input.end(), &output)); + EXPECT_EQ("", output); + + input = absl::string_view("foo"); + output.clear(); + ASSERT_TRUE(SpdyAltSvcWireFormatPeer::PercentDecode(input.begin(), + input.end(), &output)); + EXPECT_EQ("foo", output); + + input = absl::string_view("%2ca%5Cb"); + output.clear(); + ASSERT_TRUE(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) { + absl::string_view input(invalid_input); + std::string output; + EXPECT_FALSE(SpdyAltSvcWireFormatPeer::PercentDecode(input.begin(), + input.end(), &output)) + << input; + } +} + +// Test ParseAltAuthority() on valid input. +TEST(SpdyAltSvcWireFormatTest, ParseAltAuthorityValid) { + absl::string_view input(":42"); + std::string host; + uint16_t port; + ASSERT_TRUE(SpdyAltSvcWireFormatPeer::ParseAltAuthority( + input.begin(), input.end(), &host, &port)); + EXPECT_TRUE(host.empty()); + EXPECT_EQ(42, port); + + input = absl::string_view("foo:137"); + ASSERT_TRUE(SpdyAltSvcWireFormatPeer::ParseAltAuthority( + input.begin(), input.end(), &host, &port)); + EXPECT_EQ("foo", host); + EXPECT_EQ(137, port); + + input = absl::string_view("[2003:8:0:16::509d:9615]:443"); + ASSERT_TRUE(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) { + absl::string_view input(invalid_input); + std::string host; + uint16_t port; + EXPECT_FALSE(SpdyAltSvcWireFormatPeer::ParseAltAuthority( + input.begin(), input.end(), &host, &port)) + << input; + } +} + +// Test ParseInteger() on valid input. +TEST(SpdyAltSvcWireFormatTest, ParseIntegerValid) { + absl::string_view input("3"); + uint16_t value; + ASSERT_TRUE(SpdyAltSvcWireFormatPeer::ParsePositiveInteger16( + input.begin(), input.end(), &value)); + EXPECT_EQ(3, value); + + input = absl::string_view("1337"); + ASSERT_TRUE(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) { + absl::string_view input(invalid_input); + uint16_t value; + EXPECT_FALSE(SpdyAltSvcWireFormatPeer::ParsePositiveInteger16( + input.begin(), input.end(), &value)) + << input; + } +} + +// Test ParseIntegerValid() around overflow limit. +TEST(SpdyAltSvcWireFormatTest, ParseIntegerOverflow) { + // Largest possible uint16_t value. + absl::string_view input("65535"); + uint16_t value16; + ASSERT_TRUE(SpdyAltSvcWireFormatPeer::ParsePositiveInteger16( + input.begin(), input.end(), &value16)); + EXPECT_EQ(65535, value16); + + // Overflow uint16_t, ParsePositiveInteger16() should return false. + input = absl::string_view("65536"); + ASSERT_FALSE(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 = absl::string_view("65537"); + ASSERT_FALSE(SpdyAltSvcWireFormatPeer::ParsePositiveInteger16( + input.begin(), input.end(), &value16)); + + // Largest possible uint32_t value. + input = absl::string_view("4294967295"); + uint32_t value32; + ASSERT_TRUE(SpdyAltSvcWireFormatPeer::ParsePositiveInteger32( + input.begin(), input.end(), &value32)); + EXPECT_EQ(4294967295, value32); + + // Overflow uint32_t, ParsePositiveInteger32() should return false. + input = absl::string_view("4294967296"); + ASSERT_FALSE(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 = absl::string_view("4294967297"); + ASSERT_FALSE(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_seconds); + EXPECT_THAT(altsvc_vector[0].version, ::testing::ElementsAre(36, 35)); +} + +TEST(SpdyAltSvcWireFormatTest, HexDigitToInt) { + EXPECT_EQ(0, SpdyAltSvcWireFormatPeer::HexDigitToInt('0')); + EXPECT_EQ(1, SpdyAltSvcWireFormatPeer::HexDigitToInt('1')); + EXPECT_EQ(2, SpdyAltSvcWireFormatPeer::HexDigitToInt('2')); + EXPECT_EQ(3, SpdyAltSvcWireFormatPeer::HexDigitToInt('3')); + EXPECT_EQ(4, SpdyAltSvcWireFormatPeer::HexDigitToInt('4')); + EXPECT_EQ(5, SpdyAltSvcWireFormatPeer::HexDigitToInt('5')); + EXPECT_EQ(6, SpdyAltSvcWireFormatPeer::HexDigitToInt('6')); + EXPECT_EQ(7, SpdyAltSvcWireFormatPeer::HexDigitToInt('7')); + EXPECT_EQ(8, SpdyAltSvcWireFormatPeer::HexDigitToInt('8')); + EXPECT_EQ(9, SpdyAltSvcWireFormatPeer::HexDigitToInt('9')); + + EXPECT_EQ(10, SpdyAltSvcWireFormatPeer::HexDigitToInt('a')); + EXPECT_EQ(11, SpdyAltSvcWireFormatPeer::HexDigitToInt('b')); + EXPECT_EQ(12, SpdyAltSvcWireFormatPeer::HexDigitToInt('c')); + EXPECT_EQ(13, SpdyAltSvcWireFormatPeer::HexDigitToInt('d')); + EXPECT_EQ(14, SpdyAltSvcWireFormatPeer::HexDigitToInt('e')); + EXPECT_EQ(15, SpdyAltSvcWireFormatPeer::HexDigitToInt('f')); + + EXPECT_EQ(10, SpdyAltSvcWireFormatPeer::HexDigitToInt('A')); + EXPECT_EQ(11, SpdyAltSvcWireFormatPeer::HexDigitToInt('B')); + EXPECT_EQ(12, SpdyAltSvcWireFormatPeer::HexDigitToInt('C')); + EXPECT_EQ(13, SpdyAltSvcWireFormatPeer::HexDigitToInt('D')); + EXPECT_EQ(14, SpdyAltSvcWireFormatPeer::HexDigitToInt('E')); + EXPECT_EQ(15, SpdyAltSvcWireFormatPeer::HexDigitToInt('F')); +} + +TEST(SpdyAltSvcWireFormatTest, HexDecodeToUInt32) { + uint32_t out; + EXPECT_TRUE(SpdyAltSvcWireFormatPeer::HexDecodeToUInt32("0", &out)); + EXPECT_EQ(0u, out); + EXPECT_TRUE(SpdyAltSvcWireFormatPeer::HexDecodeToUInt32("00", &out)); + EXPECT_EQ(0u, out); + EXPECT_TRUE(SpdyAltSvcWireFormatPeer::HexDecodeToUInt32("0000000", &out)); + EXPECT_EQ(0u, out); + EXPECT_TRUE(SpdyAltSvcWireFormatPeer::HexDecodeToUInt32("00000000", &out)); + EXPECT_EQ(0u, out); + EXPECT_TRUE(SpdyAltSvcWireFormatPeer::HexDecodeToUInt32("1", &out)); + EXPECT_EQ(1u, out); + EXPECT_TRUE(SpdyAltSvcWireFormatPeer::HexDecodeToUInt32("ffffFFF", &out)); + EXPECT_EQ(0xFFFFFFFu, out); + EXPECT_TRUE(SpdyAltSvcWireFormatPeer::HexDecodeToUInt32("fFfFffFf", &out)); + EXPECT_EQ(0xFFFFFFFFu, out); + EXPECT_TRUE(SpdyAltSvcWireFormatPeer::HexDecodeToUInt32("01AEF", &out)); + EXPECT_EQ(0x1AEFu, out); + EXPECT_TRUE(SpdyAltSvcWireFormatPeer::HexDecodeToUInt32("abcde", &out)); + EXPECT_EQ(0xABCDEu, out); + EXPECT_TRUE(SpdyAltSvcWireFormatPeer::HexDecodeToUInt32("1234abcd", &out)); + EXPECT_EQ(0x1234ABCDu, out); + + EXPECT_FALSE(SpdyAltSvcWireFormatPeer::HexDecodeToUInt32("", &out)); + EXPECT_FALSE(SpdyAltSvcWireFormatPeer::HexDecodeToUInt32("111111111", &out)); + EXPECT_FALSE(SpdyAltSvcWireFormatPeer::HexDecodeToUInt32("1111111111", &out)); + EXPECT_FALSE(SpdyAltSvcWireFormatPeer::HexDecodeToUInt32("0x1111", &out)); +} + +} // namespace + +} // namespace test + +} // namespace spdy diff --git a/chromium/net/third_party/quiche/src/quiche/spdy/core/spdy_bitmasks.h b/chromium/net/third_party/quiche/src/quiche/spdy/core/spdy_bitmasks.h new file mode 100644 index 00000000000..657bd1761e9 --- /dev/null +++ b/chromium/net/third_party/quiche/src/quiche/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/quiche/spdy/core/spdy_frame_builder.cc b/chromium/net/third_party/quiche/src/quiche/spdy/core/spdy_frame_builder.cc new file mode 100644 index 00000000000..a9a8923cf8c --- /dev/null +++ b/chromium/net/third_party/quiche/src/quiche/spdy/core/spdy_frame_builder.cc @@ -0,0 +1,185 @@ +// 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 "quiche/spdy/core/spdy_frame_builder.h" + +#include <algorithm> +#include <cstdint> +#include <limits> +#include <new> + +#include "quiche/common/platform/api/quiche_bug_tracker.h" +#include "quiche/common/platform/api/quiche_logging.h" +#include "quiche/spdy/core/spdy_protocol.h" +#include "quiche/spdy/core/zero_copy_output_buffer.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); + QUICHE_DCHECK(IsDefinedFrameType(raw_frame_type)); + QUICHE_DCHECK_EQ(0u, stream_id & ~kStreamIdMask); + bool success = true; + if (length_ > 0) { + QUICHE_BUG(spdy_bug_73_1) + << "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); + QUICHE_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); + QUICHE_DCHECK(IsDefinedFrameType(raw_frame_type)); + QUICHE_DCHECK_EQ(0u, stream_id & ~kStreamIdMask); + QUICHE_BUG_IF(spdy_bug_73_2, 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) { + QUICHE_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); + QUICHE_DCHECK_EQ(kDataFrameMinimumSize, length_); + return success; +} + +bool SpdyFrameBuilder::WriteStringPiece32(const absl::string_view 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) { + QUICHE_DCHECK(false); + return false; + } + + if (output_ == nullptr) { + if (offset_ + length_ + length > capacity_) { + QUICHE_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/quiche/spdy/core/spdy_frame_builder.h b/chromium/net/third_party/quiche/src/quiche/spdy/core/spdy_frame_builder.h new file mode 100644 index 00000000000..129a7843a63 --- /dev/null +++ b/chromium/net/third_party/quiche/src/quiche/spdy/core/spdy_frame_builder.h @@ -0,0 +1,146 @@ +// 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 "absl/strings/string_view.h" +#include "quiche/common/platform/api/quiche_bug_tracker.h" +#include "quiche/common/platform/api/quiche_export.h" +#include "quiche/common/quiche_endian.h" +#include "quiche/spdy/core/spdy_protocol.h" +#include "quiche/spdy/core/zero_copy_output_buffer.h" + +namespace spdy { + +namespace test { +class SpdyFrameBuilderPeer; +} // namespace test + +// 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 QUICHE_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() { + QUICHE_BUG_IF(spdy_bug_39_1, output_ != nullptr) + << "ZeroCopyOutputBuffer is used to build " + << "frames. take() shouldn't be called"; + QUICHE_BUG_IF(spdy_bug_39_2, 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 = quiche::QuicheEndian::HostToNet16(value); + return WriteBytes(&value, sizeof(value)); + } + bool WriteUInt24(uint32_t value) { + value = quiche::QuicheEndian::HostToNet32(value); + return WriteBytes(reinterpret_cast<char*>(&value) + 1, sizeof(value) - 1); + } + bool WriteUInt32(uint32_t value) { + value = quiche::QuicheEndian::HostToNet32(value); + return WriteBytes(&value, sizeof(value)); + } + bool WriteUInt64(uint64_t value) { + uint32_t upper = + quiche::QuicheEndian::HostToNet32(static_cast<uint32_t>(value >> 32)); + uint32_t lower = + quiche::QuicheEndian::HostToNet32(static_cast<uint32_t>(value)); + return (WriteBytes(&upper, sizeof(upper)) && + WriteBytes(&lower, sizeof(lower))); + } + bool WriteStringPiece32(const absl::string_view value); + bool WriteBytes(const void* data, uint32_t data_len); + + private: + friend class test::SpdyFrameBuilderPeer; + + // 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/quiche/spdy/core/spdy_frame_builder_test.cc b/chromium/net/third_party/quiche/src/quiche/spdy/core/spdy_frame_builder_test.cc new file mode 100644 index 00000000000..66656e34883 --- /dev/null +++ b/chromium/net/third_party/quiche/src/quiche/spdy/core/spdy_frame_builder_test.cc @@ -0,0 +1,87 @@ +// 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 "quiche/spdy/core/spdy_frame_builder.h" + +#include <memory> + +#include "quiche/common/platform/api/quiche_export.h" +#include "quiche/common/platform/api/quiche_test.h" +#include "quiche/spdy/core/array_output_buffer.h" +#include "quiche/spdy/core/spdy_framer.h" +#include "quiche/spdy/core/spdy_protocol.h" + +namespace spdy { + +namespace test { + +class QUICHE_EXPORT_PRIVATE SpdyFrameBuilderPeer { + public: + static char* GetWritableBuffer(SpdyFrameBuilder* builder, size_t length) { + return builder->GetWritableBuffer(length); + } + + static char* GetWritableOutput(SpdyFrameBuilder* builder, + size_t desired_length, + size_t* actual_length) { + return builder->GetWritableOutput(desired_length, actual_length); + } +}; + +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 = + SpdyFrameBuilderPeer::GetWritableBuffer(&builder, kBuilderSize); + memset(writable_buffer, ~1, kBuilderSize); + EXPECT_TRUE(builder.Seek(kBuilderSize)); + SpdySerializedFrame frame(builder.take()); + char expected[kBuilderSize]; + memset(expected, ~1, kBuilderSize); + EXPECT_EQ(absl::string_view(expected, kBuilderSize), + absl::string_view(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 = SpdyFrameBuilderPeer::GetWritableOutput( + &builder, 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(absl::string_view(expected, kBuilderSize), + absl::string_view(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 = SpdyFrameBuilderPeer::GetWritableOutput( + &builder, kBuilderSize, &actual_size); + EXPECT_EQ(0u, actual_size); + EXPECT_EQ(nullptr, writable_buffer); +} + +} // namespace test +} // namespace spdy diff --git a/chromium/net/third_party/quiche/src/quiche/spdy/core/spdy_framer.cc b/chromium/net/third_party/quiche/src/quiche/spdy/core/spdy_framer.cc new file mode 100644 index 00000000000..f568a0528ad --- /dev/null +++ b/chromium/net/third_party/quiche/src/quiche/spdy/core/spdy_framer.cc @@ -0,0 +1,1380 @@ +// 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 "quiche/spdy/core/spdy_framer.h" + +#include <algorithm> +#include <cstdint> +#include <iterator> +#include <list> +#include <new> +#include <utility> + +#include "absl/base/macros.h" +#include "absl/memory/memory.h" +#include "quiche/common/platform/api/quiche_bug_tracker.h" +#include "quiche/common/platform/api/quiche_logging.h" +#include "quiche/spdy/core/spdy_bitmasks.h" +#include "quiche/spdy/core/spdy_frame_builder.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 Http2HeaderBlock& 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 std::string& 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); + QUICHE_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) { + std::string padding(headers.padding_payload_len(), 0); + ret &= builder.WriteBytes(padding.data(), padding.length()); + } + + if (!ret) { + QUICHE_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 std::string& 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) { + std::string padding(push_promise.padding_payload_len(), 0); + ok = builder.WriteBytes(padding.data(), padding.length()); + } + + QUICHE_DLOG_IF(ERROR, !ok) + << "Failed to write PUSH_PROMISE encoding, not enough " + << "space in output"; + return ok; +} + +bool WritePayloadWithContinuation(SpdyFrameBuilder* builder, + const std::string& 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 { + QUICHE_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) { + std::string padding = std::string(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, + std::string* 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_) { + QUICHE_BUG(spdy_bug_75_1) + << "SpdyFramer::SpdyFrameIterator::NextFrame called without " + << "a next frame."; + return false; + } + + const size_t size_without_block = + is_first_frame_ ? GetFrameSizeSansBlock() : kContinuationFrameMinimumSize; + std::string encoding = + encoder_->Next(kHttp2MaxControlFrameSendSize - size_without_block); + 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_; +} + +size_t SpdyFramer::SpdyHeaderFrameIterator::GetFrameSizeSansBlock() const { + return GetHeaderFrameSizeSansBlock(*headers_ir_); +} + +bool SpdyFramer::SpdyHeaderFrameIterator::SerializeGivenEncoding( + const std::string& 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_; +} + +size_t SpdyFramer::SpdyPushPromiseFrameIterator::GetFrameSizeSansBlock() const { + return GetPushPromiseFrameSizeSansBlock(*push_promise_ir_); +} + +bool SpdyFramer::SpdyPushPromiseFrameIterator::SerializeGivenEncoding( + const std::string& 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_; +} + +std::unique_ptr<SpdyFrameSequence> SpdyFramer::CreateIterator( + SpdyFramer* framer, + std::unique_ptr<const SpdyFrameIR> frame_ir) { + switch (frame_ir->frame_type()) { + case SpdyFrameType::HEADERS: { + return std::make_unique<SpdyHeaderFrameIterator>( + framer, absl::WrapUnique( + static_cast<const SpdyHeadersIR*>(frame_ir.release()))); + } + case SpdyFrameType::PUSH_PROMISE: { + return std::make_unique<SpdyPushPromiseFrameIterator>( + framer, absl::WrapUnique(static_cast<const SpdyPushPromiseIR*>( + frame_ir.release()))); + } + case SpdyFrameType::DATA: { + QUICHE_DVLOG(1) << "Serialize a stream end DATA frame for VTL"; + ABSL_FALLTHROUGH_INTENDED; + } + default: { + return std::make_unique<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) { + std::string padding(data_ir.padding_payload_len(), 0); + builder.WriteBytes(padding.data(), padding.length()); + } + QUICHE_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); + } + QUICHE_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()); + + QUICHE_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(); + } + + QUICHE_DCHECK_EQ(kSettingsFrameMinimumSize, builder.length()); + for (auto it = values->begin(); it != values->end(); ++it) { + int setting_id = it->first; + QUICHE_DCHECK_GE(setting_id, 0); + builder.WriteUInt16(static_cast<SpdySettingsId>(setting_id)); + builder.WriteUInt32(it->second); + } + QUICHE_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()); + QUICHE_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()); + } + + QUICHE_DCHECK_EQ(expected_length, builder.length()); + return builder.take(); +} + +void SpdyFramer::SerializeHeadersBuilderHelper(const SpdyHeadersIR& headers, + uint8_t* flags, + size_t* size, + std::string* 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; + } + + *hpack_encoding = + GetHpackEncoder()->EncodeHeaderBlock(headers.header_block()); + *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; + std::string 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); + + QUICHE_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()); + QUICHE_DCHECK_EQ(kWindowUpdateFrameSize, builder.length()); + return builder.take(); +} + +void SpdyFramer::SerializePushPromiseBuilderHelper( + const SpdyPushPromiseIR& push_promise, + uint8_t* flags, + std::string* 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(); + } + + *hpack_encoding = + GetHpackEncoder()->EncodeHeaderBlock(push_promise.header_block()); + *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; + std::string 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()); + QUICHE_DCHECK_EQ(kPushPromiseFrameMinimumSize + kPadLengthFieldSize, + builder.length()); + + padding_payload_len = push_promise.padding_payload_len(); + } else { + builder.WriteUInt32(push_promise.promised_stream_id()); + QUICHE_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 std::string& 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()); + QUICHE_DCHECK_EQ(kFrameHeaderSize, builder.length()); + + builder.WriteBytes(encoding.data(), encoding.size()); + return builder.take(); +} + +SpdySerializedFrame SpdyFramer::SerializeAltSvc(const SpdyAltSvcIR& altsvc_ir) { + std::string 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()); + QUICHE_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); + QUICHE_DCHECK_EQ(kPriorityFrameSize, builder.length()); + return builder.take(); +} + +SpdySerializedFrame SpdyFramer::SerializePriorityUpdate( + const SpdyPriorityUpdateIR& priority_update) const { + const size_t total_size = kPriorityUpdateFrameMinimumSize + + priority_update.priority_field_value().size(); + SpdyFrameBuilder builder(total_size); + builder.BeginNewFrame(SpdyFrameType::PRIORITY_UPDATE, kNoFlags, + priority_update.stream_id()); + + builder.WriteUInt32(priority_update.prioritized_stream_id()); + builder.WriteBytes(priority_update.priority_field_value().data(), + priority_update.priority_field_value().size()); + QUICHE_DCHECK_EQ(total_size, builder.length()); + return builder.take(); +} + +SpdySerializedFrame SpdyFramer::SerializeAcceptCh( + const SpdyAcceptChIR& accept_ch) const { + const size_t total_size = accept_ch.size(); + SpdyFrameBuilder builder(total_size); + builder.BeginNewFrame(SpdyFrameType::ACCEPT_CH, kNoFlags, + accept_ch.stream_id()); + + for (const AcceptChOriginValuePair& entry : accept_ch.entries()) { + builder.WriteUInt16(entry.origin.size()); + builder.WriteBytes(entry.origin.data(), entry.origin.size()); + builder.WriteUInt16(entry.value.size()); + builder.WriteBytes(entry.value.data(), entry.value.size()); + } + + QUICHE_DCHECK_EQ(total_size, 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 VisitPriorityUpdate( + const SpdyPriorityUpdateIR& priority_update) override { + frame_ = framer_->SerializePriorityUpdate(priority_update); + } + void VisitAcceptCh(const SpdyAcceptChIR& accept_ch) override { + frame_ = framer_->SerializeAcceptCh(accept_ch); + } + 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; + } + + void VisitPriorityUpdate( + const SpdyPriorityUpdateIR& /*priority_update*/) override { + flags_ = kNoFlags; + } + + void VisitAcceptCh(const SpdyAcceptChIR& /*accept_ch*/) 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) { + std::string padding; + padding = std::string(data_ir.padding_payload_len(), 0); + ok = ok && builder.WriteBytes(padding.data(), padding.length()); + } + QUICHE_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); + } + QUICHE_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()); + + QUICHE_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; + } + + QUICHE_DCHECK_EQ(kSettingsFrameMinimumSize, builder.length()); + for (auto it = values->begin(); it != values->end(); ++it) { + int setting_id = it->first; + QUICHE_DCHECK_GE(setting_id, 0); + ok = ok && builder.WriteUInt16(static_cast<SpdySettingsId>(setting_id)) && + builder.WriteUInt32(it->second); + } + QUICHE_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()); + QUICHE_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()); + } + + QUICHE_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; + std::string 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); + QUICHE_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()); + QUICHE_DCHECK_EQ(kWindowUpdateFrameSize, builder.length()); + return ok; +} + +bool SpdyFramer::SerializePushPromise(const SpdyPushPromiseIR& push_promise, + ZeroCopyOutputBuffer* output) { + uint8_t flags = 0; + size_t size = 0; + std::string 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()); + QUICHE_DCHECK_EQ(kPushPromiseFrameMinimumSize + kPadLengthFieldSize, + builder.length()); + + padding_payload_len = push_promise.padding_payload_len(); + } else { + ok = ok && builder.WriteUInt32(push_promise.promised_stream_id()); + QUICHE_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 std::string& 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); + QUICHE_DCHECK_EQ(kFrameHeaderSize, builder.length()); + + ok = ok && builder.WriteBytes(encoding.data(), encoding.size()); + return ok; +} + +bool SpdyFramer::SerializeAltSvc(const SpdyAltSvcIR& altsvc_ir, + ZeroCopyOutputBuffer* output) { + std::string 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()); + QUICHE_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); + QUICHE_DCHECK_EQ(kPriorityFrameSize, builder.length()); + return ok; +} + +bool SpdyFramer::SerializePriorityUpdate( + const SpdyPriorityUpdateIR& priority_update, + ZeroCopyOutputBuffer* output) const { + const size_t total_size = kPriorityUpdateFrameMinimumSize + + priority_update.priority_field_value().size(); + SpdyFrameBuilder builder(total_size, output); + bool ok = builder.BeginNewFrame(SpdyFrameType::PRIORITY_UPDATE, kNoFlags, + priority_update.stream_id()); + + ok = ok && builder.WriteUInt32(priority_update.prioritized_stream_id()); + ok = ok && builder.WriteBytes(priority_update.priority_field_value().data(), + priority_update.priority_field_value().size()); + QUICHE_DCHECK_EQ(total_size, builder.length()); + return ok; +} + +bool SpdyFramer::SerializeAcceptCh(const SpdyAcceptChIR& accept_ch, + ZeroCopyOutputBuffer* output) const { + const size_t total_size = accept_ch.size(); + SpdyFrameBuilder builder(total_size, output); + bool ok = builder.BeginNewFrame(SpdyFrameType::ACCEPT_CH, kNoFlags, + accept_ch.stream_id()); + + for (const AcceptChOriginValuePair& entry : accept_ch.entries()) { + ok = ok && builder.WriteUInt16(entry.origin.size()); + ok = ok && builder.WriteBytes(entry.origin.data(), entry.origin.size()); + ok = ok && builder.WriteUInt16(entry.value.size()); + ok = ok && builder.WriteBytes(entry.value.data(), entry.value.size()); + } + + QUICHE_DCHECK_EQ(total_size, 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 VisitPriorityUpdate( + const SpdyPriorityUpdateIR& priority_update) override { + result_ = framer_->SerializePriorityUpdate(priority_update, output_); + } + void VisitAcceptCh(const SpdyAcceptChIR& accept_ch) override { + result_ = framer_->SerializeAcceptCh(accept_ch, 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_ = std::make_unique<HpackEncoder>(); + 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(); + } +} + +} // namespace spdy diff --git a/chromium/net/third_party/quiche/src/quiche/spdy/core/spdy_framer.h b/chromium/net/third_party/quiche/src/quiche/spdy/core/spdy_framer.h new file mode 100644 index 00000000000..dcf2e1d1f1b --- /dev/null +++ b/chromium/net/third_party/quiche/src/quiche/spdy/core/spdy_framer.h @@ -0,0 +1,383 @@ +// 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 <string> +#include <utility> + +#include "absl/strings/string_view.h" +#include "quiche/common/platform/api/quiche_export.h" +#include "quiche/spdy/core/hpack/hpack_encoder.h" +#include "quiche/spdy/core/spdy_alt_svc_wire_format.h" +#include "quiche/spdy/core/spdy_header_block.h" +#include "quiche/spdy/core/spdy_headers_handler_interface.h" +#include "quiche/spdy/core/spdy_protocol.h" +#include "quiche/spdy/core/zero_copy_output_buffer.h" + +namespace spdy { + +namespace test { + +class SpdyFramerPeer; +class SpdyFramerTest_MultipleContinuationFramesWithIterator_Test; +class SpdyFramerTest_PushPromiseFramesWithIterator_Test; + +} // namespace test + +class QUICHE_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 QUICHE_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 a PRIORITY_UPDATE frame. + // See https://httpwg.org/http-extensions/draft-ietf-httpbis-priority.html. + SpdySerializedFrame SerializePriorityUpdate( + const SpdyPriorityUpdateIR& priority_update) const; + + // Serializes an ACCEPT_CH frame. See + // https://tools.ietf.org/html/draft-davidben-http-client-hint-reliability-02. + SpdySerializedFrame SerializeAcceptCh(const SpdyAcceptChIR& accept_ch) 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 a PRIORITY_UPDATE frame. + // See https://httpwg.org/http-extensions/draft-ietf-httpbis-priority.html. + bool SerializePriorityUpdate(const SpdyPriorityUpdateIR& priority_update, + ZeroCopyOutputBuffer* output) const; + + // Serializes an ACCEPT_CH frame. See + // https://tools.ietf.org/html/draft-davidben-http-client-hint-reliability-02. + bool SerializeAcceptCh(const SpdyAcceptChIR& accept_ch, + 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; + + // Get (and lazily initialize) the HPACK encoder state. + HpackEncoder* GetHpackEncoder(); + + // Gets the HPACK encoder state. Returns nullptr if the encoder has not been + // initialized. + const HpackEncoder* GetHpackEncoder() const { return hpack_encoder_.get(); } + + 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 QUICHE_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 std::string& 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 QUICHE_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 std::string& 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 QUICHE_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 std::string& 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 QUICHE_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, + std::string* hpack_encoding, + int* weight, + size_t* length_field); + void SerializePushPromiseBuilderHelper(const SpdyPushPromiseIR& push_promise, + uint8_t* flags, + std::string* 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/quiche/spdy/core/spdy_framer_test.cc b/chromium/net/third_party/quiche/src/quiche/spdy/core/spdy_framer_test.cc new file mode 100644 index 00000000000..a99a08de804 --- /dev/null +++ b/chromium/net/third_party/quiche/src/quiche/spdy/core/spdy_framer_test.cc @@ -0,0 +1,5085 @@ +// 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 "quiche/spdy/core/spdy_framer.h" + +#include <stdlib.h> + +#include <algorithm> +#include <cstdint> +#include <limits> +#include <tuple> +#include <utility> +#include <vector> + +#include "absl/base/macros.h" +#include "quiche/common/platform/api/quiche_logging.h" +#include "quiche/common/platform/api/quiche_test.h" +#include "quiche/common/quiche_text_utils.h" +#include "quiche/spdy/core/array_output_buffer.h" +#include "quiche/spdy/core/http2_frame_decoder_adapter.h" +#include "quiche/spdy/core/mock_spdy_framer_visitor.h" +#include "quiche/spdy/core/recording_headers_handler.h" +#include "quiche/spdy/core/spdy_bitmasks.h" +#include "quiche/spdy/core/spdy_frame_builder.h" +#include "quiche/spdy/core/spdy_protocol.h" +#include "quiche/spdy/core/spdy_test_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()) { + QUICHE_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 = std::make_unique<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 = std::make_unique<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 constexpr 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, + std::string /*detailed_error*/) override { + QUICHE_VLOG(1) << "SpdyFramer Error: " + << Http2DecoderAdapter::SpdyFramerErrorToString(error); + ++error_count_; + } + + void OnDataFrameHeader(SpdyStreamId stream_id, + size_t length, + bool fin) override { + QUICHE_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 { + QUICHE_VLOG(1) << "OnStreamFrameData(" << stream_id << ", data, " << len + << ", " + << ") data:\n" + << quiche::QuicheTextUtils::HexDump( + absl::string_view(data, len)); + EXPECT_EQ(header_stream_id_, stream_id); + + data_bytes_ += len; + } + + void OnStreamEnd(SpdyStreamId stream_id) override { + QUICHE_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 { + QUICHE_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 { + QUICHE_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_ = std::make_unique<RecordingHeadersHandler>(); + } + return headers_handler_.get(); + } + + void OnHeaderFrameEnd(SpdyStreamId /*stream_id*/) override { + QUICHE_CHECK(headers_handler_ != nullptr); + headers_ = headers_handler_->decoded_block().Clone(); + header_bytes_received_ = headers_handler_->uncompressed_header_bytes(); + headers_handler_.reset(); + } + + void OnRstStream(SpdyStreamId stream_id, SpdyErrorCode error_code) override { + QUICHE_VLOG(1) << "OnRstStream(" << stream_id << ", " << error_code << ")"; + ++fin_frame_count_; + } + + void OnSetting(SpdySettingsId id, uint32_t value) override { + QUICHE_VLOG(1) << "OnSetting(" << id << ", " << std::hex << value << ")"; + ++setting_count_; + } + + void OnSettingsAck() override { + QUICHE_VLOG(1) << "OnSettingsAck"; + ++settings_ack_received_; + } + + void OnSettingsEnd() override { + QUICHE_VLOG(1) << "OnSettingsEnd"; + ++settings_ack_sent_; + } + + void OnPing(SpdyPingId unique_id, bool is_ack) override { + QUICHE_LOG(DFATAL) << "OnPing(" << unique_id << ", " << (is_ack ? 1 : 0) + << ")"; + } + + void OnGoAway(SpdyStreamId last_accepted_stream_id, + SpdyErrorCode error_code) override { + QUICHE_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 { + QUICHE_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 { + QUICHE_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 { + QUICHE_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 { + QUICHE_VLOG(1) << "OnContinuation(" << stream_id << ", " << end << ")"; + ++continuation_count_; + } + + void OnAltSvc(SpdyStreamId stream_id, + absl::string_view origin, + const SpdyAltSvcWireFormat::AlternativeServiceVector& + altsvc_vector) override { + QUICHE_VLOG(1) << "OnAltSvc(" << stream_id << ", \"" << origin + << "\", altsvc_vector)"; + test_altsvc_ir_ = std::make_unique<SpdyAltSvcIR>(stream_id); + if (origin.length() > 0) { + test_altsvc_ir_->set_origin(std::string(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 { + QUICHE_VLOG(1) << "OnPriority(" << stream_id << ", " << parent_stream_id + << ", " << weight << ", " << (exclusive ? 1 : 0) << ")"; + ++priority_count_; + } + + void OnPriorityUpdate(SpdyStreamId prioritized_stream_id, + absl::string_view priority_field_value) override { + QUICHE_VLOG(1) << "OnPriorityUpdate(" << prioritized_stream_id << ", " + << priority_field_value << ")"; + } + + bool OnUnknownFrame(SpdyStreamId stream_id, uint8_t frame_type) override { + QUICHE_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 { + QUICHE_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 { + QUICHE_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; // NOLINT + 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))) { + QUICHE_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<RecordingHeadersHandler> headers_handler_; + Http2HeaderBlock 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; + std::string 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 QuicheTestWithParam<Output> { + public: + SpdyFramerTest() + : output_(output_buffer, kSize), + framer_(SpdyFramer::ENABLE_COMPRESSION), + deframer_(absl::make_unique<Http2DecoderAdapter>()) {} + + 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 std::string& 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_; + std::unique_ptr<Http2DecoderAdapter> deframer_; +}; + +INSTANTIATE_TEST_SUITE_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 a payload length field at the default +// 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: DATA + 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, OnCommonHeader(1, 16384, 0x0, 0x0)); + 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 a payload length larger than the default +// 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: DATA + 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, OnCommonHeader(1, 16385, 0x0, 0x0)); + 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 set a larger max frame size and then receive a frame with a +// payload length at that larger size, we do not set an error in ProcessInput. +TEST_P(SpdyFramerTest, AcceptLargerMaxFrameSizeSetting) { + testing::StrictMock<test::MockSpdyFramerVisitor> visitor; + deframer_->set_visitor(&visitor); + + const size_t big_frame_size = (1 << 14) + 1; + deframer_->SetMaxFrameSize(big_frame_size); + + // DATA frame with larger-than-default but acceptable payload length. + unsigned char kH2FrameData[] = { + 0x00, 0x40, 0x01, // Length: 2^14 + 1 + 0x00, // Type: DATA + 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, OnCommonHeader(1, big_frame_size, 0x0, 0x0)); + EXPECT_CALL(visitor, OnDataFrameHeader(1, big_frame_size, false)); + EXPECT_CALL(visitor, OnStreamFrameData(1, _, 4)); + deframer_->ProcessInput(frame.data(), frame.size()); + EXPECT_FALSE(deframer_->HasError()); +} + +// 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, OnCommonHeader(1, 5, 0x0, 0x9)); + 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, OnCommonHeader(1, 5, 0x0, 0x8)); + 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, OnCommonHeader(1, 5, 0x1, 0x8)); + 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, OnCommonHeader(1, 5, 0x1, 0x8)); + 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, OnCommonHeader(0, _, 0x0, _)); + 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, OnCommonHeader(0, _, 0x1, _)); + 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, OnCommonHeader(0, _, 0x2, _)); + 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, OnCommonHeader(0, _, 0x3, _)); + 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, OnCommonHeader(1, 6, 0x4, 0x0)); + 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, OnCommonHeader(1, 10, 0x7, 0x0)); + 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); + std::string some_nonsense_encoding = "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, OnCommonHeader(0, _, 0x9, _)); + 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, OnCommonHeader(0, _, 0x5, _)); + 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, OnCommonHeader(3, _, 0x5, _)); + 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); + std::string value("value1\0value2", 13); + // TODO(jgraettinger): If this pattern appears again, move to test class. + Http2HeaderBlock header_set; + header_set["name"] = value; + HpackEncoder encoder; + encoder.DisableCompression(); + std::string buffer = encoder.EncodeHeaderBlock(header_set); + // 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", absl::string_view(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_); +} + +// Verifies that the decoder stops delivering events after a user error. +TEST_P(SpdyFramerTest, BasicWithError) { + // Send HEADERS frames with PRIORITY and END_HEADERS set. + // frame-format off + const unsigned char kH2Input[] = { + 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, 0x06, // Length: 6 + 0x01, // Type: HEADERS + 0x24, // Flags: END_HEADERS|PRIORITY + 0x00, 0x00, 0x00, 0x03, // Stream: 3 + 0x00, 0x00, 0x00, 0x00, // Parent: 0 + 0x82, // Weight: 131 + 0x8c, // :status: 200 + + 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 + + testing::StrictMock<test::MockSpdyFramerVisitor> visitor; + + deframer_->set_visitor(&visitor); + + testing::InSequence s; + EXPECT_CALL(visitor, OnCommonHeader(1, 1, 0x1, 0x4)); + EXPECT_CALL(visitor, OnHeaders(1, false, 0, 0, false, false, true)); + EXPECT_CALL(visitor, OnHeaderFrameStart(1)); + EXPECT_CALL(visitor, OnHeaderFrameEnd(1)); + EXPECT_CALL(visitor, OnCommonHeader(1, 12, 0x0, 0x0)); + EXPECT_CALL(visitor, OnDataFrameHeader(1, 12, false)); + EXPECT_CALL(visitor, OnStreamFrameData(1, _, 12)); + EXPECT_CALL(visitor, OnCommonHeader(3, 6, 0x1, 0x24)); + EXPECT_CALL(visitor, OnHeaders(3, true, 131, 0, false, false, true)); + EXPECT_CALL(visitor, OnHeaderFrameStart(3)); + EXPECT_CALL(visitor, OnHeaderFrameEnd(3)); + EXPECT_CALL(visitor, OnCommonHeader(3, 8, 0x0, 0x0)); + EXPECT_CALL(visitor, OnDataFrameHeader(3, 8, false)) + .WillOnce(testing::InvokeWithoutArgs( + [this]() { deframer_->StopProcessing(); })); + // Remaining frames are not processed due to the error. + EXPECT_CALL( + visitor, + OnError(http2::Http2DecoderAdapter::SpdyFramerError::SPDY_STOP_PROCESSING, + "Ignoring further events on this connection.")); + + size_t processed = deframer_->ProcessInput( + reinterpret_cast<const char*>(kH2Input), sizeof(kH2Input)); + EXPECT_LT(processed, sizeof(kH2Input)); +} + +// 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, + absl::string_view(bytes, ABSL_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(ABSL_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, ABSL_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, + ABSL_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, + ABSL_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, + ABSL_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, + ABSL_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, + ABSL_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, + ABSL_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, + ABSL_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, + ABSL_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, + ABSL_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, + ABSL_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, + ABSL_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, + ABSL_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, + ABSL_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, + ABSL_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, + ABSL_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, + ABSL_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, + ABSL_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, + ABSL_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, + ABSL_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, + ABSL_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, + ABSL_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, + ABSL_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, + ABSL_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, + ABSL_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, + ABSL_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, + ABSL_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, + ABSL_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, + ABSL_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, ABSL_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, ABSL_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, ABSL_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 + + Http2HeaderBlock header_block; + header_block["bar"] = "foo"; + header_block["foo"] = "bar"; + HpackEncoder encoder; + encoder.DisableCompression(); + std::string buffer = encoder.EncodeHeaderBlock(header_block); + + 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, ABSL_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, OnCommonHeader(42, 18, 0x9, 0x4)); + 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); + std::string 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, + ABSL_ARRAYSIZE(kPartialPushPromiseFrameData), + kPartialPushPromiseFrameData, + ABSL_ARRAYSIZE(kPartialPushPromiseFrameData)); + + // Compare the CONTINUATION frame against the template. + frame_data += kHttp2MaxControlFrameSendSize; + CompareCharArraysWithHexError( + kDescription, frame_data, ABSL_ARRAYSIZE(kContinuationFrameData), + kContinuationFrameData, ABSL_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, ABSL_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, ABSL_ARRAYSIZE(kFrameData)); +} + +TEST_P(SpdyFramerTest, CreatePriorityUpdate) { + const char kDescription[] = "PRIORITY_UPDATE frame"; + const unsigned char kType = + SerializeFrameType(SpdyFrameType::PRIORITY_UPDATE); + const unsigned char kFrameData[] = { + 0x00, 0x00, 0x07, // frame length + kType, // frame type + 0x00, // flags + 0x00, 0x00, 0x00, 0x00, // stream ID, must be 0 for PRIORITY_UPDATE + 0x00, 0x00, 0x00, 0x03, // prioritized stream ID + 'u', '=', '0'}; // priority field value + SpdyPriorityUpdateIR priority_update_ir(/* stream_id = */ 0, + /* prioritized_stream_id = */ 3, + /* priority_field_value = */ "u=0"); + SpdySerializedFrame frame(framer_.SerializeFrame(priority_update_ir)); + if (use_output_) { + EXPECT_EQ(framer_.SerializeFrame(priority_update_ir, &output_), + frame.size()); + frame = SpdySerializedFrame(output_.Begin(), output_.Size(), false); + } + CompareFrame(kDescription, frame, kFrameData, ABSL_ARRAYSIZE(kFrameData)); +} + +TEST_P(SpdyFramerTest, CreateAcceptCh) { + const char kDescription[] = "ACCEPT_CH frame"; + const unsigned char kType = SerializeFrameType(SpdyFrameType::ACCEPT_CH); + const unsigned char kFrameData[] = { + 0x00, 0x00, 0x2d, // frame length + kType, // frame type + 0x00, // flags + 0x00, 0x00, 0x00, 0x00, // stream ID, must be 0 for ACCEPT_CH + 0x00, 0x0f, // origin length + 'w', 'w', 'w', '.', 'e', 'x', // origin + 'a', 'm', 'p', 'l', 'e', '.', // + 'c', 'o', 'm', // + 0x00, 0x03, // value length + 'f', 'o', 'o', // value + 0x00, 0x10, // origin length + 'm', 'a', 'i', 'l', '.', 'e', // + 'x', 'a', 'm', 'p', 'l', 'e', // + '.', 'c', 'o', 'm', // + 0x00, 0x03, // value length + 'b', 'a', 'r'}; // value + SpdyAcceptChIR accept_ch_ir( + {{"www.example.com", "foo"}, {"mail.example.com", "bar"}}); + SpdySerializedFrame frame(framer_.SerializeFrame(accept_ch_ir)); + if (use_output_) { + EXPECT_EQ(framer_.SerializeFrame(accept_ch_ir, &output_), frame.size()); + frame = SpdySerializedFrame(output_.Begin(), output_.Size(), false); + } + CompareFrame(kDescription, frame, kFrameData, ABSL_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, ABSL_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, ABSL_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; + std::string 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 = std::make_unique<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; + std::string big_valuex(kBigValueSize, 'x'); + headers->SetHeader("aa", big_valuex); + std::string 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 = + std::make_unique<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; + std::string big_valuex(kBigValueSize, 'x'); + push_promise->SetHeader("aa", big_valuex); + std::string 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 quiche::test::QuicheTest { + 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 = std::make_unique<SpdyRstStreamIR>(0, ERROR_CODE_PROTOCOL_ERROR); + RunTest(std::move(ir)); +} + +TEST_F(SpdyControlFrameIteratorTest, SettingsFrameWithIterator) { + auto ir = std::make_unique<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 = std::make_unique<SpdyPingIR>(kPingId); + RunTest(std::move(ir)); +} + +TEST_F(SpdyControlFrameIteratorTest, GoAwayFrameWithIterator) { + auto ir = std::make_unique<SpdyGoAwayIR>(0, ERROR_CODE_NO_ERROR, "GA"); + RunTest(std::move(ir)); +} + +TEST_F(SpdyControlFrameIteratorTest, WindowUpdateFrameWithIterator) { + auto ir = std::make_unique<SpdyWindowUpdateIR>(1, 1); + RunTest(std::move(ir)); +} + +TEST_F(SpdyControlFrameIteratorTest, AtlSvcFrameWithIterator) { + auto ir = std::make_unique<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 = std::make_unique<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; + std::string 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; + std::string 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); + std::string 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, 0x18, // Length: 24 + 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 + 0x00, 0x08, // Param: ENABLE_CONNECT_PROTOCOL + 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(4, visitor.setting_count_); + EXPECT_EQ(0, visitor.error_count_); + + // The extension receives only the non-standard SETTINGS. + EXPECT_THAT( + extension.settings_received_, + testing::ElementsAre(testing::Pair(16, 2), testing::Pair(95, 65538))); +} + +// 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, + OnCommonHeader(1, kPaddingLen + strlen(data_payload), 0x0, 0x8)); + EXPECT_CALL(visitor, + OnDataFrameHeader(1, kPaddingLen + strlen(data_payload), false)); + QUICHE_CHECK_EQ(kDataFrameMinimumSize, + deframer_->ProcessInput(frame.data(), kDataFrameMinimumSize)); + QUICHE_CHECK_EQ(deframer_->state(), + Http2DecoderAdapter::SPDY_READ_DATA_FRAME_PADDING_LENGTH); + QUICHE_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)); + QUICHE_CHECK_EQ(1u, + deframer_->ProcessInput(frame.data() + bytes_consumed, 1)); + QUICHE_CHECK_EQ(deframer_->state(), + Http2DecoderAdapter::SPDY_FORWARD_STREAM_FRAME); + QUICHE_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)); + QUICHE_CHECK_EQ(2u, + deframer_->ProcessInput(frame.data() + bytes_consumed, 2)); + QUICHE_CHECK_EQ(deframer_->state(), + Http2DecoderAdapter::SPDY_FORWARD_STREAM_FRAME); + QUICHE_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)); + QUICHE_CHECK_EQ(3u, + deframer_->ProcessInput(frame.data() + bytes_consumed, 3)); + QUICHE_CHECK_EQ(deframer_->state(), + Http2DecoderAdapter::SPDY_CONSUME_PADDING); + QUICHE_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)); + QUICHE_CHECK_EQ(100u, + deframer_->ProcessInput(frame.data() + bytes_consumed, 100)); + QUICHE_CHECK_EQ(deframer_->state(), + Http2DecoderAdapter::SPDY_CONSUME_PADDING); + QUICHE_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)); + QUICHE_CHECK_EQ(18u, + deframer_->ProcessInput(frame.data() + bytes_consumed, 18)); + QUICHE_CHECK_EQ(deframer_->state(), + Http2DecoderAdapter::SPDY_READY_FOR_FRAME); + QUICHE_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, ABSL_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, ABSL_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(std::string(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, ABSL_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, ABSL_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("DECOMPRESS_FAILURE", + Http2DecoderAdapter::SpdyFramerErrorToString( + Http2DecoderAdapter::SPDY_DECOMPRESS_FAILURE)); + 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("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); + + EXPECT_CALL(visitor, OnCommonHeader(1, 5, 0x0, 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_ = absl::make_unique<Http2DecoderAdapter>(); + } 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, OnCommonHeader(13, 4, 0x3, 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_ = absl::make_unique<Http2DecoderAdapter>(); + } 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); + + EXPECT_CALL(visitor, OnCommonHeader(0, 6, 0x4, 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_ = absl::make_unique<Http2DecoderAdapter>(); + } 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, OnCommonHeader(0, _, 0x7, flags)); + EXPECT_CALL(visitor, OnGoAway(97, ERROR_CODE_NO_ERROR)); + EXPECT_CALL(visitor, OnGoAwayFrameData) + .WillRepeatedly(testing::Return(true)); + + 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_ = absl::make_unique<Http2DecoderAdapter>(); + } 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, OnCommonHeader(stream_id, _, 0x1, set_flags)); + 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()); + } 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, OnCommonHeader(0, 8, 0x6, 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_ = absl::make_unique<Http2DecoderAdapter>(); + } 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, OnCommonHeader(4, 4, 0x8, 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_ = absl::make_unique<Http2DecoderAdapter>(); + } 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, OnCommonHeader(client_id, _, 0x5, + flags & ~HEADERS_FLAG_PADDED)); + 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, OnCommonHeader(42, _, 0x1, 0)); + 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, OnCommonHeader(42, _, 0x9, flags)); + 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, OnCommonHeader(1, 4, 0x3, 0x0)); + EXPECT_CALL(visitor, OnRstStream(1, ERROR_CODE_NO_ERROR)); + deframer_->ProcessInput(reinterpret_cast<const char*>(kH2RstStreamInvalid), + ABSL_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_ = absl::make_unique<Http2DecoderAdapter>(); + deframer_->set_visitor(&visitor); + + EXPECT_CALL(visitor, OnCommonHeader(1, 4, 0x3, 0x0)); + EXPECT_CALL(visitor, OnRstStream(1, ERROR_CODE_INTERNAL_ERROR)); + deframer_->ProcessInput( + reinterpret_cast<const char*>(kH2RstStreamNumStatusCodes), + ABSL_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, OnCommonHeader(0, 10, 0x7, 0x0)); + EXPECT_CALL(visitor, OnGoAway(1, ERROR_CODE_INTERNAL_ERROR)); + EXPECT_CALL(visitor, OnGoAwayFrameData).WillRepeatedly(testing::Return(true)); + deframer_->ProcessInput(reinterpret_cast<const char*>(kH2FrameData), + ABSL_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, OnCommonHeader(0, 8, 0x7, 0x0)); + EXPECT_CALL(visitor, OnGoAway(0x7fffffff, ERROR_CODE_NO_ERROR)); + EXPECT_CALL(visitor, OnGoAwayFrameData).WillRepeatedly(testing::Return(true)); + deframer_->ProcessInput(reinterpret_cast<const char*>(kH2FrameData), + ABSL_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, OnCommonHeader(kStreamId, _, 0x0A, 0x0)); + EXPECT_CALL(visitor, + OnAltSvc(kStreamId, absl::string_view("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, OnCommonHeader(kStreamId, _, 0x0A, 0x0)); + EXPECT_CALL(visitor, + OnAltSvc(kStreamId, absl::string_view(""), 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, OnCommonHeader(kStreamId, _, 0x0A, 0x0)); + 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()); +} + +TEST_P(SpdyFramerTest, ReadPriorityUpdateFrame) { + const char kFrameData[] = { + 0x00, 0x00, 0x07, // payload length + 0x10, // frame type PRIORITY_UPDATE + 0x00, // flags + 0x00, 0x00, 0x00, 0x00, // stream ID, must be 0 + 0x00, 0x00, 0x00, 0x03, // prioritized stream ID, must not be zero + 'f', 'o', 'o' // priority field value + }; + + testing::StrictMock<test::MockSpdyFramerVisitor> visitor; + deframer_->set_visitor(&visitor); + + EXPECT_CALL(visitor, OnCommonHeader(0, 7, 0x10, 0x0)); + EXPECT_CALL(visitor, OnPriorityUpdate(3, "foo")); + deframer_->ProcessInput(kFrameData, sizeof(kFrameData)); + EXPECT_FALSE(deframer_->HasError()); +} + +TEST_P(SpdyFramerTest, ReadPriorityUpdateFrameWithEmptyPriorityFieldValue) { + const char kFrameData[] = { + 0x00, 0x00, 0x04, // payload length + 0x10, // frame type PRIORITY_UPDATE + 0x00, // flags + 0x00, 0x00, 0x00, 0x00, // stream ID, must be 0 + 0x00, 0x00, 0x00, 0x03 // prioritized stream ID, must not be zero + }; + + testing::StrictMock<test::MockSpdyFramerVisitor> visitor; + deframer_->set_visitor(&visitor); + + EXPECT_CALL(visitor, OnCommonHeader(0, 4, 0x10, 0x0)); + EXPECT_CALL(visitor, OnPriorityUpdate(3, "")); + deframer_->ProcessInput(kFrameData, sizeof(kFrameData)); + EXPECT_FALSE(deframer_->HasError()); +} + +TEST_P(SpdyFramerTest, PriorityUpdateFrameWithEmptyPayload) { + const char kFrameData[] = { + 0x00, 0x00, 0x00, // payload length + 0x10, // frame type PRIORITY_UPDATE + 0x00, // flags + 0x00, 0x00, 0x00, 0x00, // stream ID, must be 0 + }; + + testing::StrictMock<test::MockSpdyFramerVisitor> visitor; + deframer_->set_visitor(&visitor); + + EXPECT_CALL(visitor, OnCommonHeader(0, 0, 0x10, 0x0)); + EXPECT_CALL(visitor, + OnError(Http2DecoderAdapter::SPDY_INVALID_CONTROL_FRAME_SIZE, _)); + deframer_->ProcessInput(kFrameData, sizeof(kFrameData)); + EXPECT_TRUE(deframer_->HasError()); +} + +TEST_P(SpdyFramerTest, PriorityUpdateFrameWithShortPayload) { + const char kFrameData[] = { + 0x00, 0x00, 0x02, // payload length + 0x10, // frame type PRIORITY_UPDATE + 0x00, // flags + 0x00, 0x00, 0x00, 0x00, // stream ID, must be 0 + 0x00, 0x01 // payload not long enough to hold 32 bits of prioritized + // stream ID + }; + + testing::StrictMock<test::MockSpdyFramerVisitor> visitor; + deframer_->set_visitor(&visitor); + + EXPECT_CALL(visitor, OnCommonHeader(0, 2, 0x10, 0x0)); + EXPECT_CALL(visitor, + OnError(Http2DecoderAdapter::SPDY_INVALID_CONTROL_FRAME_SIZE, _)); + deframer_->ProcessInput(kFrameData, sizeof(kFrameData)); + EXPECT_TRUE(deframer_->HasError()); +} + +TEST_P(SpdyFramerTest, PriorityUpdateFrameOnIncorrectStream) { + const char kFrameData[] = { + 0x00, 0x00, 0x04, // payload length + 0x10, // frame type PRIORITY_UPDATE + 0x00, // flags + 0x00, 0x00, 0x00, 0x01, // invalid stream ID, must be 0 + 0x00, 0x00, 0x00, 0x01, // prioritized stream ID, must not be zero + }; + + testing::StrictMock<test::MockSpdyFramerVisitor> visitor; + deframer_->set_visitor(&visitor); + + EXPECT_CALL(visitor, OnCommonHeader(1, 4, 0x10, 0x0)); + EXPECT_CALL(visitor, OnError(Http2DecoderAdapter::SPDY_INVALID_STREAM_ID, _)); + deframer_->ProcessInput(kFrameData, sizeof(kFrameData)); + EXPECT_TRUE(deframer_->HasError()); +} + +TEST_P(SpdyFramerTest, PriorityUpdateFramePrioritizingIncorrectStream) { + const char kFrameData[] = { + 0x00, 0x00, 0x04, // payload length + 0x10, // frame type PRIORITY_UPDATE + 0x00, // flags + 0x00, 0x00, 0x00, 0x00, // stream ID, must be 0 + 0x00, 0x00, 0x00, 0x00, // prioritized stream ID, must not be zero + }; + + testing::StrictMock<test::MockSpdyFramerVisitor> visitor; + deframer_->set_visitor(&visitor); + + EXPECT_CALL(visitor, OnCommonHeader(0, 4, 0x10, 0x0)); + EXPECT_CALL(visitor, OnError(Http2DecoderAdapter::SPDY_INVALID_STREAM_ID, _)); + deframer_->ProcessInput(kFrameData, sizeof(kFrameData)); + EXPECT_TRUE(deframer_->HasError()); +} + +// 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, OnCommonHeader(3, 5, 0x2, 0x0)); + 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 all passed input in one call to ProcessInput. +TEST_P(SpdyFramerTest, ProcessAllInput) { + auto visitor = + std::make_unique<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(); + + QUICHE_VLOG(1) << "frame1_size = " << frame1_size; + QUICHE_VLOG(1) << "frame2_size = " << frame2_size; + + std::string 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(); + + QUICHE_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_)); +} + +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, absl::string_view(bytes, ABSL_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", std::string(100000, 'x')); + headers_ir_with_continuation.SetHeader("beta", std::string(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", std::string(100000, 'x')); + push_promise_ir.SetHeader("beta", std::string(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/quiche/spdy/core/spdy_header_block.cc b/chromium/net/third_party/quiche/src/quiche/spdy/core/spdy_header_block.cc new file mode 100644 index 00000000000..3a59c33b217 --- /dev/null +++ b/chromium/net/third_party/quiche/src/quiche/spdy/core/spdy_header_block.cc @@ -0,0 +1,317 @@ +// 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 "quiche/spdy/core/spdy_header_block.h" + +#include <string.h> + +#include <algorithm> +#include <utility> + +#include "absl/strings/str_cat.h" +#include "quiche/common/platform/api/quiche_logging.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; + +const char kCookieKey[] = "cookie"; +const char kNullSeparator = 0; + +absl::string_view SeparatorForKey(absl::string_view key) { + if (key == kCookieKey) { + static absl::string_view cookie_separator = "; "; + return cookie_separator; + } else { + return absl::string_view(&kNullSeparator, 1); + } +} + +} // namespace + +Http2HeaderBlock::HeaderValue::HeaderValue(SpdyHeaderStorage* storage, + absl::string_view key, + absl::string_view initial_value) + : storage_(storage), + fragments_({initial_value}), + pair_({key, {}}), + size_(initial_value.size()), + separator_size_(SeparatorForKey(key).size()) {} + +Http2HeaderBlock::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_) {} + +Http2HeaderBlock::HeaderValue& Http2HeaderBlock::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; +} + +void Http2HeaderBlock::HeaderValue::set_storage(SpdyHeaderStorage* storage) { + storage_ = storage; +} + +Http2HeaderBlock::HeaderValue::~HeaderValue() = default; + +absl::string_view Http2HeaderBlock::HeaderValue::ConsolidatedValue() const { + if (fragments_.empty()) { + return absl::string_view(); + } + if (fragments_.size() > 1) { + fragments_ = { + storage_->WriteFragments(fragments_, SeparatorForKey(pair_.first))}; + } + return fragments_[0]; +} + +void Http2HeaderBlock::HeaderValue::Append(absl::string_view fragment) { + size_ += (fragment.size() + separator_size_); + fragments_.push_back(fragment); +} + +const std::pair<absl::string_view, absl::string_view>& +Http2HeaderBlock::HeaderValue::as_pair() const { + pair_.second = ConsolidatedValue(); + return pair_; +} + +Http2HeaderBlock::iterator::iterator(MapType::const_iterator it) : it_(it) {} + +Http2HeaderBlock::iterator::iterator(const iterator& other) = default; + +Http2HeaderBlock::iterator::~iterator() = default; + +Http2HeaderBlock::ValueProxy::ValueProxy( + Http2HeaderBlock* block, + Http2HeaderBlock::MapType::iterator lookup_result, + const absl::string_view key, + size_t* spdy_header_block_value_size) + : block_(block), + lookup_result_(lookup_result), + key_(key), + spdy_header_block_value_size_(spdy_header_block_value_size), + valid_(true) {} + +Http2HeaderBlock::ValueProxy::ValueProxy(ValueProxy&& other) + : block_(other.block_), + lookup_result_(other.lookup_result_), + key_(other.key_), + spdy_header_block_value_size_(other.spdy_header_block_value_size_), + valid_(true) { + other.valid_ = false; +} + +Http2HeaderBlock::ValueProxy& Http2HeaderBlock::ValueProxy::operator=( + Http2HeaderBlock::ValueProxy&& other) { + block_ = other.block_; + 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; +} + +Http2HeaderBlock::ValueProxy::~ValueProxy() { + // If the ValueProxy is destroyed while lookup_result_ == block_->end(), + // the assignment operator was never used, and the block's SpdyHeaderStorage + // can reclaim the memory used by the key. This makes lookup-only access to + // Http2HeaderBlock through operator[] memory-neutral. + if (valid_ && lookup_result_ == block_->map_.end()) { + block_->storage_.Rewind(key_); + } +} + +Http2HeaderBlock::ValueProxy& Http2HeaderBlock::ValueProxy::operator=( + absl::string_view value) { + *spdy_header_block_value_size_ += value.size(); + SpdyHeaderStorage* storage = &block_->storage_; + if (lookup_result_ == block_->map_.end()) { + QUICHE_DVLOG(1) << "Inserting: (" << key_ << ", " << value << ")"; + lookup_result_ = + block_->map_ + .emplace(std::make_pair( + key_, HeaderValue(storage, key_, storage->Write(value)))) + .first; + } else { + QUICHE_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; +} + +bool Http2HeaderBlock::ValueProxy::operator==(absl::string_view value) const { + if (lookup_result_ == block_->map_.end()) { + return false; + } else { + return value == lookup_result_->second.value(); + } +} + +std::string Http2HeaderBlock::ValueProxy::as_string() const { + if (lookup_result_ == block_->map_.end()) { + return ""; + } else { + return std::string(lookup_result_->second.value()); + } +} + +Http2HeaderBlock::Http2HeaderBlock() : map_(kInitialMapBuckets) {} + +Http2HeaderBlock::Http2HeaderBlock(Http2HeaderBlock&& other) + : map_(kInitialMapBuckets) { + map_.swap(other.map_); + storage_ = std::move(other.storage_); + for (auto& p : map_) { + p.second.set_storage(&storage_); + } + key_size_ = other.key_size_; + value_size_ = other.value_size_; +} + +Http2HeaderBlock::~Http2HeaderBlock() = default; + +Http2HeaderBlock& Http2HeaderBlock::operator=(Http2HeaderBlock&& other) { + map_.swap(other.map_); + storage_ = std::move(other.storage_); + for (auto& p : map_) { + p.second.set_storage(&storage_); + } + key_size_ = other.key_size_; + value_size_ = other.value_size_; + return *this; +} + +Http2HeaderBlock Http2HeaderBlock::Clone() const { + Http2HeaderBlock copy; + for (const auto& p : *this) { + copy.AppendHeader(p.first, p.second); + } + return copy; +} + +bool Http2HeaderBlock::operator==(const Http2HeaderBlock& other) const { + return size() == other.size() && std::equal(begin(), end(), other.begin()); +} + +bool Http2HeaderBlock::operator!=(const Http2HeaderBlock& other) const { + return !(operator==(other)); +} + +std::string Http2HeaderBlock::DebugString() const { + if (empty()) { + return "{}"; + } + + std::string output = "\n{\n"; + for (auto it = begin(); it != end(); ++it) { + absl::StrAppend(&output, " ", it->first, " ", it->second, "\n"); + } + absl::StrAppend(&output, "}\n"); + return output; +} + +void Http2HeaderBlock::erase(absl::string_view key) { + auto iter = map_.find(key); + if (iter != map_.end()) { + QUICHE_DVLOG(1) << "Erasing header with name: " << key; + key_size_ -= key.size(); + value_size_ -= iter->second.SizeEstimate(); + map_.erase(iter); + } +} + +void Http2HeaderBlock::clear() { + key_size_ = 0; + value_size_ = 0; + map_.clear(); + storage_.Clear(); +} + +void Http2HeaderBlock::insert(const Http2HeaderBlock::value_type& value) { + // TODO(birenroy): Write new value in place of old value, if it fits. + value_size_ += value.second.size(); + + auto iter = map_.find(value.first); + if (iter == map_.end()) { + QUICHE_DVLOG(1) << "Inserting: (" << value.first << ", " << value.second + << ")"; + AppendHeader(value.first, value.second); + } else { + QUICHE_DVLOG(1) << "Updating key: " << iter->first + << " with value: " << value.second; + value_size_ -= iter->second.SizeEstimate(); + iter->second = + HeaderValue(&storage_, iter->first, storage_.Write(value.second)); + } +} + +Http2HeaderBlock::ValueProxy Http2HeaderBlock::operator[]( + const absl::string_view key) { + QUICHE_DVLOG(2) << "Operator[] saw key: " << key; + absl::string_view out_key; + auto iter = map_.find(key); + if (iter == map_.end()) { + // We write the key first, to assure that the ValueProxy has a + // reference to a valid absl::string_view in its operator=. + out_key = WriteKey(key); + QUICHE_DVLOG(2) << "Key written as: " << std::hex + << static_cast<const void*>(key.data()) << ", " << std::dec + << key.size(); + } else { + out_key = iter->first; + } + return ValueProxy(this, iter, out_key, &value_size_); +} + +void Http2HeaderBlock::AppendValueOrAddHeader(const absl::string_view key, + const absl::string_view value) { + value_size_ += value.size(); + + auto iter = map_.find(key); + if (iter == map_.end()) { + QUICHE_DVLOG(1) << "Inserting: (" << key << ", " << value << ")"; + + AppendHeader(key, value); + return; + } + QUICHE_DVLOG(1) << "Updating key: " << iter->first + << "; appending value: " << value; + value_size_ += SeparatorForKey(key).size(); + iter->second.Append(storage_.Write(value)); +} + +void Http2HeaderBlock::AppendHeader(const absl::string_view key, + const absl::string_view value) { + auto backed_key = WriteKey(key); + map_.emplace(std::make_pair( + backed_key, HeaderValue(&storage_, backed_key, storage_.Write(value)))); +} + +absl::string_view Http2HeaderBlock::WriteKey(const absl::string_view key) { + key_size_ += key.size(); + return storage_.Write(key); +} + +size_t Http2HeaderBlock::bytes_allocated() const { + return storage_.bytes_allocated(); +} + +} // namespace spdy diff --git a/chromium/net/third_party/quiche/src/quiche/spdy/core/spdy_header_block.h b/chromium/net/third_party/quiche/src/quiche/spdy/core/spdy_header_block.h new file mode 100644 index 00000000000..795d3501765 --- /dev/null +++ b/chromium/net/third_party/quiche/src/quiche/spdy/core/spdy_header_block.h @@ -0,0 +1,295 @@ +// 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 <functional> +#include <list> +#include <string> +#include <utility> +#include <vector> + +#include "absl/base/attributes.h" +#include "quiche/common/platform/api/quiche_export.h" +#include "quiche/common/platform/api/quiche_logging.h" +#include "quiche/common/quiche_linked_hash_map.h" +#include "quiche/common/quiche_text_utils.h" +#include "quiche/spdy/core/spdy_header_storage.h" + +namespace spdy { + +namespace test { +class Http2HeaderBlockPeer; +class ValueProxyPeer; +} // namespace test + +#ifndef SPDY_HEADER_DEBUG +#if !defined(NDEBUG) || defined(ADDRESS_SANITIZER) +#define SPDY_HEADER_DEBUG 1 +#else // !defined(NDEBUG) || defined(ADDRESS_SANITIZER) +#define SPDY_HEADER_DEBUG 0 +#endif // !defined(NDEBUG) || defined(ADDRESS_SANITIZER) +#endif // SPDY_HEADER_DEBUG + +// 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 absl::string_view +// keys, and values are returned as absl::string_views (via ValueProxy, below). +// Value absl::string_views are valid as long as the Http2HeaderBlock exists; +// allocated memory is never freed until Http2HeaderBlock'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 Http2HeaderBlock. +class QUICHE_EXPORT_PRIVATE Http2HeaderBlock { + private: + // Stores a list of value fragments that can be joined later with a + // key-dependent separator. + class QUICHE_EXPORT_PRIVATE HeaderValue { + public: + HeaderValue(SpdyHeaderStorage* storage, + absl::string_view key, + absl::string_view initial_value); + + // Moves are allowed. + HeaderValue(HeaderValue&& other); + HeaderValue& operator=(HeaderValue&& other); + + void set_storage(SpdyHeaderStorage* storage); + + // 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(absl::string_view fragment); + + absl::string_view value() const { return as_pair().second; } + const std::pair<absl::string_view, absl::string_view>& as_pair() const; + + // Size estimate including separators. Used when keys are erased from + // Http2HeaderBlock. + size_t SizeEstimate() const { return size_; } + + private: + // May allocate a large contiguous region of memory to hold the concatenated + // fragments and separators. + absl::string_view ConsolidatedValue() const; + + mutable SpdyHeaderStorage* storage_; + mutable std::vector<absl::string_view> fragments_; + // The first element is the key; the second is the consolidated value. + mutable std::pair<absl::string_view, absl::string_view> pair_; + size_t size_ = 0; + size_t separator_size_ = 0; + }; + + typedef quiche::QuicheLinkedHashMap<absl::string_view, HeaderValue, + quiche::StringPieceCaseHash, + quiche::StringPieceCaseEqual> + MapType; + + public: + typedef std::pair<absl::string_view, absl::string_view> value_type; + + // Provides iteration over a sequence of std::pair<absl::string_view, + // absl::string_view>, even though the underlying MapType::value_type is + // different. Dereferencing the iterator will result in memory allocation for + // multi-value headers. + class QUICHE_EXPORT_PRIVATE iterator { + public: + // The following type definitions fulfill the requirements for iterator + // implementations. + typedef std::pair<absl::string_view, absl::string_view> 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 { +#if SPDY_HEADER_DEBUG + QUICHE_CHECK(!dereference_forbidden_); +#endif // SPDY_HEADER_DEBUG + 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; + } + +#if SPDY_HEADER_DEBUG + void forbid_dereference() { dereference_forbidden_ = true; } +#endif // SPDY_HEADER_DEBUG + + private: + MapType::const_iterator it_; +#if SPDY_HEADER_DEBUG + bool dereference_forbidden_ = false; +#endif // SPDY_HEADER_DEBUG + }; + typedef iterator const_iterator; + + Http2HeaderBlock(); + Http2HeaderBlock(const Http2HeaderBlock& other) = delete; + Http2HeaderBlock(Http2HeaderBlock&& other); + ~Http2HeaderBlock(); + + Http2HeaderBlock& operator=(const Http2HeaderBlock& other) = delete; + Http2HeaderBlock& operator=(Http2HeaderBlock&& other); + Http2HeaderBlock Clone() const; + + bool operator==(const Http2HeaderBlock& other) const; + bool operator!=(const Http2HeaderBlock& other) const; + + // Provides a human readable multi-line representation of the stored header + // keys and values. + std::string DebugString() const; + + iterator begin() { return wrap_iterator(map_.begin()); } + iterator end() { return wrap_iterator(map_.end()); } + const_iterator begin() const { return wrap_const_iterator(map_.begin()); } + const_iterator end() const { return wrap_const_iterator(map_.end()); } + bool empty() const { return map_.empty(); } + size_t size() const { return map_.size(); } + iterator find(absl::string_view key) { return wrap_iterator(map_.find(key)); } + const_iterator find(absl::string_view key) const { + return wrap_const_iterator(map_.find(key)); + } + bool contains(absl::string_view key) const { return find(key) != end(); } + void erase(absl::string_view 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 absl::string_view key, + const absl::string_view value); + + // This object provides automatic conversions that allow Http2HeaderBlock to + // be nearly a drop-in replacement for + // SpdyLinkedHashMap<std::string, std::string>. + // It reads data from or writes data to a SpdyHeaderStorage. + class QUICHE_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 Http2HeaderBlock. + ValueProxy& operator=(absl::string_view value); + + // Provides easy comparison against absl::string_view. + bool operator==(absl::string_view value) const; + + std::string as_string() const; + + private: + friend class Http2HeaderBlock; + friend class test::ValueProxyPeer; + + ValueProxy(Http2HeaderBlock* block, + Http2HeaderBlock::MapType::iterator lookup_result, + const absl::string_view key, + size_t* spdy_header_block_value_size); + + Http2HeaderBlock* block_; + Http2HeaderBlock::MapType::iterator lookup_result_; + absl::string_view key_; + size_t* spdy_header_block_value_size_; + bool valid_; + }; + + // Allows either lookup or mutation of the value associated with a key. + ABSL_MUST_USE_RESULT ValueProxy operator[](const absl::string_view key); + + size_t TotalBytesUsed() const { return key_size_ + value_size_; } + + private: + friend class test::Http2HeaderBlockPeer; + + inline iterator wrap_iterator(MapType::const_iterator inner_iterator) const { +#if SPDY_HEADER_DEBUG + iterator outer_iterator(inner_iterator); + if (inner_iterator == map_.end()) { + outer_iterator.forbid_dereference(); + } + return outer_iterator; +#else // SPDY_HEADER_DEBUG + return iterator(inner_iterator); +#endif // SPDY_HEADER_DEBUG + } + + inline const_iterator wrap_const_iterator( + MapType::const_iterator inner_iterator) const { +#if SPDY_HEADER_DEBUG + const_iterator outer_iterator(inner_iterator); + if (inner_iterator == map_.end()) { + outer_iterator.forbid_dereference(); + } + return outer_iterator; +#else // SPDY_HEADER_DEBUG + return iterator(inner_iterator); +#endif // SPDY_HEADER_DEBUG + } + + void AppendHeader(const absl::string_view key, const absl::string_view value); + absl::string_view WriteKey(const absl::string_view key); + size_t bytes_allocated() const; + + // absl::string_views held by |map_| point to memory owned by |storage_|. + MapType map_; + SpdyHeaderStorage storage_; + + size_t key_size_ = 0; + size_t value_size_ = 0; +}; + +// TODO(b/156770486): Remove this alias when the rename is complete. +using SpdyHeaderBlock = Http2HeaderBlock; + +} // namespace spdy + +#endif // QUICHE_SPDY_CORE_SPDY_HEADER_BLOCK_H_ diff --git a/chromium/net/third_party/quiche/src/quiche/spdy/core/spdy_header_block_test.cc b/chromium/net/third_party/quiche/src/quiche/spdy/core/spdy_header_block_test.cc new file mode 100644 index 00000000000..7b266c67dc1 --- /dev/null +++ b/chromium/net/third_party/quiche/src/quiche/spdy/core/spdy_header_block_test.cc @@ -0,0 +1,279 @@ +// 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 "quiche/spdy/core/spdy_header_block.h" + +#include <memory> +#include <utility> + +#include "quiche/common/platform/api/quiche_test.h" +#include "quiche/spdy/core/spdy_test_utils.h" + +using ::testing::ElementsAre; + +namespace spdy { +namespace test { + +class ValueProxyPeer { + public: + static absl::string_view key(Http2HeaderBlock::ValueProxy* p) { + return p->key_; + } +}; + +std::pair<absl::string_view, absl::string_view> Pair(absl::string_view k, + absl::string_view v) { + return std::make_pair(k, v); +} + +// This test verifies that Http2HeaderBlock behaves correctly when empty. +TEST(Http2HeaderBlockTest, EmptyBlock) { + Http2HeaderBlock block; + EXPECT_TRUE(block.empty()); + EXPECT_EQ(0u, block.size()); + EXPECT_EQ(block.end(), block.find("foo")); + EXPECT_FALSE(block.contains("foo")); + EXPECT_TRUE(block.end() == block.begin()); + + // Should have no effect. + block.erase("bar"); +} + +TEST(Http2HeaderBlockTest, KeyMemoryReclaimedOnLookup) { + Http2HeaderBlock block; + absl::string_view copied_key1; + { + auto proxy1 = block["some key name"]; + copied_key1 = ValueProxyPeer::key(&proxy1); + } + absl::string_view 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 absl::string_views 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 Http2HeaderBlock. + 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(Http2HeaderBlockTest, AddHeaders) { + Http2HeaderBlock block; + block["foo"] = std::string(300, 'x'); + block["bar"] = "baz"; + block["qux"] = "qux1"; + block["qux"] = "qux2"; + block.insert(std::make_pair("key", "value")); + + EXPECT_EQ(Pair("foo", std::string(300, 'x')), *block.find("foo")); + EXPECT_EQ("baz", block["bar"]); + std::string qux("qux"); + EXPECT_EQ("qux2", block[qux]); + ASSERT_NE(block.end(), block.find("key")); + ASSERT_TRUE(block.contains("key")); + EXPECT_EQ(Pair("key", "value"), *block.find("key")); + + block.erase("key"); + EXPECT_EQ(block.end(), block.find("key")); +} + +// This test verifies that Http2HeaderBlock can be copied using Clone(). +TEST(Http2HeaderBlockTest, CopyBlocks) { + Http2HeaderBlock block1; + block1["foo"] = std::string(300, 'x'); + block1["bar"] = "baz"; + block1.insert(std::make_pair("qux", "qux1")); + + Http2HeaderBlock block2 = block1.Clone(); + Http2HeaderBlock block3(block1.Clone()); + + EXPECT_EQ(block1, block2); + EXPECT_EQ(block1, block3); +} + +TEST(Http2HeaderBlockTest, Equality) { + // Test equality and inequality operators. + Http2HeaderBlock block1; + block1["foo"] = "bar"; + + Http2HeaderBlock block2; + block2["foo"] = "bar"; + + Http2HeaderBlock block3; + block3["baz"] = "qux"; + + EXPECT_EQ(block1, block2); + EXPECT_NE(block1, block3); + + block2["baz"] = "qux"; + EXPECT_NE(block1, block2); +} + +Http2HeaderBlock ReturnTestHeaderBlock() { + Http2HeaderBlock block; + block["foo"] = "bar"; + block.insert(std::make_pair("foo2", "baz")); + return block; +} + +// Test that certain methods do not crash on moved-from instances. +TEST(Http2HeaderBlockTest, MovedFromIsValid) { + Http2HeaderBlock block1; + block1["foo"] = "bar"; + + Http2HeaderBlock block2(std::move(block1)); + EXPECT_THAT(block2, ElementsAre(Pair("foo", "bar"))); + + block1["baz"] = "qux"; // NOLINT testing post-move behavior + + Http2HeaderBlock block3(std::move(block1)); + + block1["foo"] = "bar"; // NOLINT testing post-move behavior + + Http2HeaderBlock 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"))); + + Http2HeaderBlock block5 = ReturnTestHeaderBlock(); + block5.AppendValueOrAddHeader("foo", "bar2"); + EXPECT_THAT(block5, ElementsAre(Pair("foo", std::string("bar\0bar2", 8)), + Pair("foo2", "baz"))); +} + +// This test verifies that headers can be appended to no matter how they were +// added originally. +TEST(Http2HeaderBlockTest, AppendHeaders) { + Http2HeaderBlock block; + block["foo"] = "foo"; + block.AppendValueOrAddHeader("foo", "bar"); + EXPECT_EQ(Pair("foo", std::string("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(std::string("h1v1\0h1v2\0h1v3", 14), block["h1"]); + EXPECT_EQ(std::string("h2v1\0h2v2\0h2v3", 14), block["h2"]); + EXPECT_EQ(std::string("h3v2\0h3v3", 9), block["h3"]); + EXPECT_EQ("singleton", block["h4"]); +} + +TEST(Http2HeaderBlockTest, CompareValueToStringPiece) { + Http2HeaderBlock block; + block["foo"] = "foo"; + block.AppendValueOrAddHeader("foo", "bar"); + const auto& val = block["foo"]; + const char expected[] = "foo\0bar"; + EXPECT_TRUE(absl::string_view(expected, 7) == val); + EXPECT_TRUE(val == absl::string_view(expected, 7)); + EXPECT_FALSE(absl::string_view(expected, 3) == val); + EXPECT_FALSE(val == absl::string_view(expected, 3)); + const char not_expected[] = "foo\0barextra"; + EXPECT_FALSE(absl::string_view(not_expected, 12) == val); + EXPECT_FALSE(val == absl::string_view(not_expected, 12)); + + const auto& val2 = block["foo2"]; + EXPECT_FALSE(absl::string_view(expected, 7) == val2); + EXPECT_FALSE(val2 == absl::string_view(expected, 7)); + EXPECT_FALSE(absl::string_view("") == val2); + EXPECT_FALSE(val2 == absl::string_view("")); +} + +// This test demonstrates that the Http2HeaderBlock data structure does not +// place any limitations on the characters present in the header names. +TEST(Http2HeaderBlockTest, UpperCaseNames) { + Http2HeaderBlock block; + block["Foo"] = "foo"; + block.AppendValueOrAddHeader("Foo", "bar"); + EXPECT_NE(block.end(), block.find("foo")); + EXPECT_EQ(Pair("Foo", std::string("foo\0bar", 7)), *block.find("Foo")); + + // The map is case insensitive, so updating "foo" modifies the entry + // previously added. + block.AppendValueOrAddHeader("foo", "baz"); + EXPECT_THAT(block, + ElementsAre(Pair("Foo", std::string("foo\0bar\0baz", 11)))); +} + +namespace { +size_t Http2HeaderBlockSize(const Http2HeaderBlock& block) { + size_t size = 0; + for (const auto& pair : block) { + size += pair.first.size() + pair.second.size(); + } + return size; +} +} // namespace + +// Tests Http2HeaderBlock SizeEstimate(). +TEST(Http2HeaderBlockTest, TotalBytesUsed) { + Http2HeaderBlock block; + const size_t value_size = 300; + block["foo"] = std::string(value_size, 'x'); + EXPECT_EQ(block.TotalBytesUsed(), Http2HeaderBlockSize(block)); + block.insert(std::make_pair("key", std::string(value_size, 'x'))); + EXPECT_EQ(block.TotalBytesUsed(), Http2HeaderBlockSize(block)); + block.AppendValueOrAddHeader("abc", std::string(value_size, 'x')); + EXPECT_EQ(block.TotalBytesUsed(), Http2HeaderBlockSize(block)); + + // Replace value for existing key. + block["foo"] = std::string(value_size, 'x'); + EXPECT_EQ(block.TotalBytesUsed(), Http2HeaderBlockSize(block)); + block.insert(std::make_pair("key", std::string(value_size, 'x'))); + EXPECT_EQ(block.TotalBytesUsed(), Http2HeaderBlockSize(block)); + // Add value for existing key. + block.AppendValueOrAddHeader("abc", std::string(value_size, 'x')); + EXPECT_EQ(block.TotalBytesUsed(), Http2HeaderBlockSize(block)); + + // Copies/clones Http2HeaderBlock. + size_t block_size = block.TotalBytesUsed(); + Http2HeaderBlock block_copy = std::move(block); + EXPECT_EQ(block_size, block_copy.TotalBytesUsed()); + + // Erases key. + block_copy.erase("foo"); + EXPECT_EQ(block_copy.TotalBytesUsed(), Http2HeaderBlockSize(block_copy)); + block_copy.erase("key"); + EXPECT_EQ(block_copy.TotalBytesUsed(), Http2HeaderBlockSize(block_copy)); + block_copy.erase("abc"); + EXPECT_EQ(block_copy.TotalBytesUsed(), Http2HeaderBlockSize(block_copy)); +} + +} // namespace test +} // namespace spdy diff --git a/chromium/net/third_party/quiche/src/quiche/spdy/core/spdy_header_storage.cc b/chromium/net/third_party/quiche/src/quiche/spdy/core/spdy_header_storage.cc new file mode 100644 index 00000000000..8fab19d9d3c --- /dev/null +++ b/chromium/net/third_party/quiche/src/quiche/spdy/core/spdy_header_storage.cc @@ -0,0 +1,60 @@ +#include "quiche/spdy/core/spdy_header_storage.h" + +#include <cstring> + +#include "quiche/common/platform/api/quiche_logging.h" + +namespace spdy { +namespace { + +// SpdyHeaderStorage allocates blocks of this size by default. +const size_t kDefaultStorageBlockSize = 2048; + +} // namespace + +SpdyHeaderStorage::SpdyHeaderStorage() : arena_(kDefaultStorageBlockSize) {} + +absl::string_view SpdyHeaderStorage::Write(const absl::string_view s) { + return absl::string_view(arena_.Memdup(s.data(), s.size()), s.size()); +} + +void SpdyHeaderStorage::Rewind(const absl::string_view s) { + arena_.Free(const_cast<char*>(s.data()), s.size()); +} + +absl::string_view SpdyHeaderStorage::WriteFragments( + const std::vector<absl::string_view>& fragments, + absl::string_view separator) { + if (fragments.empty()) { + return absl::string_view(); + } + size_t total_size = separator.size() * (fragments.size() - 1); + for (const absl::string_view& fragment : fragments) { + total_size += fragment.size(); + } + char* dst = arena_.Alloc(total_size); + size_t written = Join(dst, fragments, separator); + QUICHE_DCHECK_EQ(written, total_size); + return absl::string_view(dst, total_size); +} + +size_t Join(char* dst, + const std::vector<absl::string_view>& fragments, + absl::string_view 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/quiche/spdy/core/spdy_header_storage.h b/chromium/net/third_party/quiche/src/quiche/spdy/core/spdy_header_storage.h new file mode 100644 index 00000000000..42b1b9f5b0c --- /dev/null +++ b/chromium/net/third_party/quiche/src/quiche/spdy/core/spdy_header_storage.h @@ -0,0 +1,59 @@ +#ifndef QUICHE_SPDY_CORE_SPDY_HEADER_STORAGE_H_ +#define QUICHE_SPDY_CORE_SPDY_HEADER_STORAGE_H_ + +#include "absl/strings/string_view.h" +#include "quiche/common/platform/api/quiche_export.h" +#include "quiche/spdy/core/spdy_simple_arena.h" + +namespace spdy { + +// This class provides a backing store for absl::string_views. It previously +// used custom allocation logic, but now uses an UnsafeArena instead. It has the +// property that absl::string_views that refer to data in SpdyHeaderStorage are +// never invalidated until the SpdyHeaderStorage 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 QUICHE_EXPORT_PRIVATE SpdyHeaderStorage { + public: + SpdyHeaderStorage(); + + SpdyHeaderStorage(const SpdyHeaderStorage&) = delete; + SpdyHeaderStorage& operator=(const SpdyHeaderStorage&) = delete; + + SpdyHeaderStorage(SpdyHeaderStorage&& other) = default; + SpdyHeaderStorage& operator=(SpdyHeaderStorage&& other) = default; + + absl::string_view Write(absl::string_view s); + + // 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(absl::string_view s); + + 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 absl::string_view + // pointing to the region of memory. + absl::string_view WriteFragments( + const std::vector<absl::string_view>& fragments, + absl::string_view separator); + + size_t bytes_allocated() const { return arena_.status().bytes_allocated(); } + + private: + SpdySimpleArena arena_; +}; + +// Writes |fragments| to |dst|, joined by |separator|. |dst| must be large +// enough to hold the result. Returns the number of bytes written. +QUICHE_EXPORT_PRIVATE size_t +Join(char* dst, + const std::vector<absl::string_view>& fragments, + absl::string_view separator); + +} // namespace spdy + +#endif // QUICHE_SPDY_CORE_SPDY_HEADER_STORAGE_H_ diff --git a/chromium/net/third_party/quiche/src/quiche/spdy/core/spdy_header_storage_test.cc b/chromium/net/third_party/quiche/src/quiche/spdy/core/spdy_header_storage_test.cc new file mode 100644 index 00000000000..474b01e4fed --- /dev/null +++ b/chromium/net/third_party/quiche/src/quiche/spdy/core/spdy_header_storage_test.cc @@ -0,0 +1,35 @@ +#include "quiche/spdy/core/spdy_header_storage.h" + +#include "quiche/common/platform/api/quiche_test.h" + +namespace spdy { +namespace test { + +TEST(JoinTest, JoinEmpty) { + std::vector<absl::string_view> empty; + absl::string_view separator = ", "; + char buf[10] = ""; + size_t written = Join(buf, empty, separator); + EXPECT_EQ(0u, written); +} + +TEST(JoinTest, JoinOne) { + std::vector<absl::string_view> v = {"one"}; + absl::string_view separator = ", "; + char buf[15]; + size_t written = Join(buf, v, separator); + EXPECT_EQ(3u, written); + EXPECT_EQ("one", absl::string_view(buf, written)); +} + +TEST(JoinTest, JoinMultiple) { + std::vector<absl::string_view> v = {"one", "two", "three"}; + absl::string_view separator = ", "; + char buf[15]; + size_t written = Join(buf, v, separator); + EXPECT_EQ(15u, written); + EXPECT_EQ("one, two, three", absl::string_view(buf, written)); +} + +} // namespace test +} // namespace spdy diff --git a/chromium/net/third_party/quiche/src/quiche/spdy/core/spdy_headers_handler_interface.h b/chromium/net/third_party/quiche/src/quiche/spdy/core/spdy_headers_handler_interface.h new file mode 100644 index 00000000000..00f95da14fe --- /dev/null +++ b/chromium/net/third_party/quiche/src/quiche/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 "absl/strings/string_view.h" +#include "quiche/common/platform/api/quiche_export.h" + +namespace spdy { + +// This interface defines how an object that accepts header data should behave. +// It is used by both SpdyHeadersBlockParser and HpackDecoder. +class QUICHE_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(absl::string_view key, absl::string_view 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/quiche/spdy/core/spdy_intrusive_list.h b/chromium/net/third_party/quiche/src/quiche/spdy/core/spdy_intrusive_list.h new file mode 100644 index 00000000000..405ab2832fa --- /dev/null +++ b/chromium/net/third_party/quiche/src/quiche/spdy/core/spdy_intrusive_list.h @@ -0,0 +1,331 @@ +// 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_CORE_SPDY_INTRUSIVE_LIST_H_ +#define QUICHE_SPDY_CORE_SPDY_INTRUSIVE_LIST_H_ + +// A SpdyIntrusiveList<> is a doubly-linked list where the link pointers are +// embedded in the elements. They are circularly linked making insertion and +// removal into a known position constant time and branch-free operations. +// +// Usage is similar to an STL list<> where feasible, but there are important +// differences. First and foremost, the elements must derive from the +// SpdyIntrusiveLink<> base class: +// +// struct Foo : public SpdyIntrusiveLink<Foo> { +// // ... +// } +// +// SpdyIntrusiveList<Foo> l; +// l.push_back(new Foo); +// l.push_front(new Foo); +// l.erase(&l.front()); +// l.erase(&l.back()); +// +// Intrusive lists are primarily useful when you would have considered embedding +// link pointers in your class directly for space or performance reasons. An +// SpdyIntrusiveLink<> is the size of 2 pointers, usually 16 bytes on 64-bit +// systems. Intrusive lists do not perform memory allocation (unlike the STL +// list<> class) and thus may use less memory than list<>. In particular, if the +// list elements are pointers to objects, using a list<> would perform an extra +// memory allocation for each list node structure, while an SpdyIntrusiveList<> +// would not. +// +// Note that SpdyIntrusiveLink is exempt from the C++ style guide's limitations +// on multiple inheritance, so it's fine to inherit from both SpdyIntrusiveLink +// and a base class, even if the base class is not a pure interface. +// +// Because the list pointers are embedded in the objects stored in an +// SpdyIntrusiveList<>, erasing an item from a list is constant time. Consider +// the following: +// +// map<string,Foo> foo_map; +// list<Foo*> foo_list; +// +// foo_list.push_back(&foo_map["bar"]); +// foo_list.erase(&foo_map["bar"]); // Compile error! +// +// The problem here is that a Foo* doesn't know where on foo_list it resides, +// so removal requires iteration over the list. Various tricks can be performed +// to overcome this. For example, a foo_list::iterator can be stored inside of +// the Foo object. But at that point you'd be better off using an +// SpdyIntrusiveList<>: +// +// map<string,Foo> foo_map; +// SpdyIntrusiveList<Foo> foo_list; +// +// foo_list.push_back(&foo_map["bar"]); +// foo_list.erase(&foo_map["bar"]); // Yeah! +// +// Note that SpdyIntrusiveLists come with a few limitations. The primary +// limitation is that the SpdyIntrusiveLink<> base class is not copyable or +// assignable. The result is that STL algorithms which mutate the order of +// iterators, such as reverse() and unique(), will not work by default with +// SpdyIntrusiveLists. In order to allow these algorithms to work you'll need to +// define swap() and/or operator= for your class. +// +// Another limitation is that the SpdyIntrusiveList<> structure itself is not +// copyable or assignable since an item/link combination can only exist on one +// SpdyIntrusiveList<> at a time. This limitation is a result of the link +// pointers for an item being intrusive in the item itself. For example, the +// following will not compile: +// +// FooList a; +// FooList b(a); // no copy constructor +// b = a; // no assignment operator +// +// The similar STL code does work since the link pointers are external to the +// item: +// +// list<int*> a; +// a.push_back(new int); +// list<int*> b(a); +// QUICHE_CHECK(a.front() == b.front()); +// +// Note that SpdyIntrusiveList::size() runs in O(N) time. + +#include <stddef.h> + +#include <iterator> + +#include "quiche/common/platform/api/quiche_export.h" + +namespace spdy { + +template <typename T, typename ListID> class SpdyIntrusiveList; + +template <typename T, typename ListID = void> +class QUICHE_EXPORT_PRIVATE SpdyIntrusiveLink { + protected: + // We declare the constructor protected so that only derived types and the + // befriended list can construct this. + SpdyIntrusiveLink() : next_(nullptr), prev_(nullptr) {} + +#ifndef SWIG + SpdyIntrusiveLink(const SpdyIntrusiveLink&) = delete; + SpdyIntrusiveLink& operator=(const SpdyIntrusiveLink&) = delete; +#endif // SWIG + + private: + // We befriend the matching list type so that it can manipulate the links + // while they are kept private from others. + friend class SpdyIntrusiveList<T, ListID>; + + // Encapsulates the logic to convert from a link to its derived type. + T* cast_to_derived() { return static_cast<T*>(this); } + const T* cast_to_derived() const { return static_cast<const T*>(this); } + + SpdyIntrusiveLink* next_; + SpdyIntrusiveLink* prev_; +}; + +template <typename T, typename ListID = void> +class QUICHE_EXPORT_PRIVATE SpdyIntrusiveList { + template <typename QualifiedT, typename QualifiedLinkT> class iterator_impl; + + public: + typedef T value_type; + typedef value_type *pointer; + typedef const value_type *const_pointer; + typedef value_type &reference; + typedef const value_type &const_reference; + typedef size_t size_type; + typedef ptrdiff_t difference_type; + + typedef SpdyIntrusiveLink<T, ListID> link_type; + typedef iterator_impl<T, link_type> iterator; + typedef iterator_impl<const T, const link_type> const_iterator; + typedef std::reverse_iterator<const_iterator> const_reverse_iterator; + typedef std::reverse_iterator<iterator> reverse_iterator; + + SpdyIntrusiveList() { clear(); } + // After the move constructor the moved-from list will be empty. + // + // NOTE: There is no move assign operator (for now). + // The reason is that at the moment 'clear()' does not unlink the nodes. + // It makes is_linked() return true when it should return false. + // If such node is removed from the list (e.g. from its destructor), or is + // added to another list - a memory corruption will occur. + // Admitedly the destructor does not unlink the nodes either, but move-assign + // will likely make the problem more prominent. +#ifndef SWIG + SpdyIntrusiveList(SpdyIntrusiveList&& src) noexcept { + clear(); + if (src.empty()) return; + sentinel_link_.next_ = src.sentinel_link_.next_; + sentinel_link_.prev_ = src.sentinel_link_.prev_; + // Fix head and tail nodes of the list. + sentinel_link_.prev_->next_ = &sentinel_link_; + sentinel_link_.next_->prev_ = &sentinel_link_; + src.clear(); + } +#endif // SWIG + + iterator begin() { return iterator(sentinel_link_.next_); } + const_iterator begin() const { return const_iterator(sentinel_link_.next_); } + iterator end() { return iterator(&sentinel_link_); } + const_iterator end() const { return const_iterator(&sentinel_link_); } + reverse_iterator rbegin() { return reverse_iterator(end()); } + const_reverse_iterator rbegin() const { + return const_reverse_iterator(end()); + } + reverse_iterator rend() { return reverse_iterator(begin()); } + const_reverse_iterator rend() const { + return const_reverse_iterator(begin()); + } + + bool empty() const { return (sentinel_link_.next_ == &sentinel_link_); } + // This runs in O(N) time. + size_type size() const { return std::distance(begin(), end()); } + size_type max_size() const { return size_type(-1); } + + reference front() { return *begin(); } + const_reference front() const { return *begin(); } + reference back() { return *(--end()); } + const_reference back() const { return *(--end()); } + + static iterator insert(iterator position, T *obj) { + return insert_link(position.link(), obj); + } + void push_front(T* obj) { insert(begin(), obj); } + void push_back(T* obj) { insert(end(), obj); } + + static iterator erase(T* obj) { + link_type* obj_link = obj; + // Fix up the next and previous links for the previous and next objects. + obj_link->next_->prev_ = obj_link->prev_; + obj_link->prev_->next_ = obj_link->next_; + // Zero out the next and previous links for the removed item. This will + // cause any future attempt to remove the item from the list to cause a + // crash instead of possibly corrupting the list structure. + link_type* next_link = obj_link->next_; + obj_link->next_ = nullptr; + obj_link->prev_ = nullptr; + return iterator(next_link); + } + + static iterator erase(iterator position) { + return erase(position.operator->()); + } + void pop_front() { erase(begin()); } + void pop_back() { erase(--end()); } + + // Check whether the given element is linked into some list. Note that this + // does *not* check whether it is linked into a particular list. + // Also, if clear() is used to clear the containing list, is_linked() will + // still return true even though obj is no longer in any list. + static bool is_linked(const T* obj) { + return obj->link_type::next_ != nullptr; + } + + void clear() { + sentinel_link_.next_ = sentinel_link_.prev_ = &sentinel_link_; + } + void swap(SpdyIntrusiveList& x) { + SpdyIntrusiveList tmp; + tmp.splice(tmp.begin(), *this); + this->splice(this->begin(), x); + x.splice(x.begin(), tmp); + } + + void splice(iterator pos, SpdyIntrusiveList& src) { + splice(pos, src.begin(), src.end()); + } + + void splice(iterator pos, iterator i) { splice(pos, i, std::next(i)); } + + void splice(iterator pos, iterator first, iterator last) { + if (first == last) return; + + link_type* const last_prev = last.link()->prev_; + + // Remove from the source. + first.link()->prev_->next_ = last.operator->(); + last.link()->prev_ = first.link()->prev_; + + // Attach to the destination. + first.link()->prev_ = pos.link()->prev_; + pos.link()->prev_->next_ = first.operator->(); + last_prev->next_ = pos.operator->(); + pos.link()->prev_ = last_prev; + } + + private: + static iterator insert_link(link_type* next_link, T* obj) { + link_type* obj_link = obj; + obj_link->next_ = next_link; + link_type* const initial_next_prev = next_link->prev_; + obj_link->prev_ = initial_next_prev; + initial_next_prev->next_ = obj_link; + next_link->prev_ = obj_link; + return iterator(obj_link); + } + + // The iterator implementation is parameterized on a potentially qualified + // variant of T and the matching qualified link type. Essentially, QualifiedT + // will either be 'T' or 'const T', the latter for a const_iterator. + template <typename QualifiedT, typename QualifiedLinkT> + class QUICHE_EXPORT_PRIVATE iterator_impl + : public std::iterator<std::bidirectional_iterator_tag, QualifiedT> { + public: + typedef std::iterator<std::bidirectional_iterator_tag, QualifiedT> base; + + iterator_impl() = default; + iterator_impl(QualifiedLinkT* link) : link_(link) {} + iterator_impl(const iterator_impl& x) = default; + iterator_impl& operator=(const iterator_impl& x) = default; + + // Allow converting and comparing across iterators where the pointer + // assignment and comparisons (respectively) are allowed. + template <typename U, typename V> + iterator_impl(const iterator_impl<U, V>& x) : link_(x.link_) {} + template <typename U, typename V> + bool operator==(const iterator_impl<U, V>& x) const { + return link_ == x.link_; + } + template <typename U, typename V> + bool operator!=(const iterator_impl<U, V>& x) const { + return link_ != x.link_; + } + + typename base::reference operator*() const { return *operator->(); } + typename base::pointer operator->() const { + return link_->cast_to_derived(); + } + + QualifiedLinkT *link() const { return link_; } + +#ifndef SWIG // SWIG can't wrap these operator overloads. + iterator_impl& operator++() { link_ = link_->next_; return *this; } + iterator_impl operator++(int /*unused*/) { + iterator_impl tmp = *this; + ++*this; + return tmp; + } + iterator_impl& operator--() { link_ = link_->prev_; return *this; } + iterator_impl operator--(int /*unused*/) { + iterator_impl tmp = *this; + --*this; + return tmp; + } +#endif // SWIG + + private: + // Ensure iterators can access other iterators node directly. + template <typename U, typename V> friend class iterator_impl; + + QualifiedLinkT* link_ = nullptr; + }; + + // This bare link acts as the sentinel node. + link_type sentinel_link_; + + // These are private and undefined to prevent copying and assigning. + SpdyIntrusiveList(const SpdyIntrusiveList&); + void operator=(const SpdyIntrusiveList&); +}; + +} // namespace spdy + +#endif // QUICHE_SPDY_CORE_SPDY_INTRUSIVE_LIST_H_ diff --git a/chromium/net/third_party/quiche/src/quiche/spdy/core/spdy_intrusive_list_test.cc b/chromium/net/third_party/quiche/src/quiche/spdy/core/spdy_intrusive_list_test.cc new file mode 100644 index 00000000000..f52313f3e5f --- /dev/null +++ b/chromium/net/third_party/quiche/src/quiche/spdy/core/spdy_intrusive_list_test.cc @@ -0,0 +1,427 @@ +// 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. + +#include "quiche/spdy/core/spdy_intrusive_list.h" + +#include <algorithm> +#include <cstddef> +#include <list> +#include <string> +#include <utility> + +#include "quiche/common/platform/api/quiche_test.h" + +namespace spdy { +namespace test { + +struct ListId2 {}; + +struct TestItem : public SpdyIntrusiveLink<TestItem>, + public SpdyIntrusiveLink<TestItem, ListId2> { + int n; +}; +typedef SpdyIntrusiveList<TestItem> TestList; +typedef std::list<TestItem*> CanonicalList; + +void swap(TestItem &a, TestItem &b) { + using std::swap; + swap(a.n, b.n); +} + +class IntrusiveListTest : public QuicheTest { + protected: + void CheckLists() { + CheckLists(l1, ll1); + if (QuicheTest::HasFailure()) + return; + CheckLists(l2, ll2); + } + + void CheckLists(const TestList &list_a, const CanonicalList &list_b) { + ASSERT_EQ(list_a.size(), list_b.size()); + TestList::const_iterator it_a = list_a.begin(); + CanonicalList::const_iterator it_b = list_b.begin(); + while (it_a != list_a.end()) { + EXPECT_EQ(&*it_a++, *it_b++); + } + EXPECT_EQ(list_a.end(), it_a); + EXPECT_EQ(list_b.end(), it_b); + } + + void PrepareLists(int num_elems_1, int num_elems_2 = 0) { + FillLists(&l1, &ll1, e, num_elems_1); + FillLists(&l2, &ll2, e + num_elems_1, num_elems_2); + } + + void FillLists(TestList *list_a, CanonicalList *list_b, TestItem *elems, + int num_elems) { + list_a->clear(); + list_b->clear(); + for (int i = 0; i < num_elems; ++i) { + list_a->push_back(elems + i); + list_b->push_back(elems + i); + } + CheckLists(*list_a, *list_b); + } + + TestItem e[10]; + TestList l1, l2; + CanonicalList ll1, ll2; +}; + +TEST(NewIntrusiveListTest, Basic) { + TestList list1; + + EXPECT_EQ(sizeof(SpdyIntrusiveLink<TestItem>), sizeof(void*) * 2); + + for (int i = 0; i < 10; ++i) { + TestItem *e = new TestItem; + e->n = i; + list1.push_front(e); + } + EXPECT_EQ(list1.size(), 10u); + + // Verify we can reverse a list because we defined swap for TestItem. + std::reverse(list1.begin(), list1.end()); + EXPECT_EQ(list1.size(), 10u); + + // Check both const and non-const forward iteration. + const TestList& clist1 = list1; + int i = 0; + TestList::iterator iter = list1.begin(); + for (; + iter != list1.end(); + ++iter, ++i) { + EXPECT_EQ(iter->n, i); + } + EXPECT_EQ(iter, clist1.end()); + EXPECT_NE(iter, clist1.begin()); + i = 0; + iter = list1.begin(); + for (; + iter != list1.end(); + ++iter, ++i) { + EXPECT_EQ(iter->n, i); + } + EXPECT_EQ(iter, clist1.end()); + EXPECT_NE(iter, clist1.begin()); + + EXPECT_EQ(list1.front().n, 0); + EXPECT_EQ(list1.back().n, 9); + + // Verify we can swap 2 lists. + TestList list2; + list2.swap(list1); + EXPECT_EQ(list1.size(), 0u); + EXPECT_EQ(list2.size(), 10u); + + // Check both const and non-const reverse iteration. + const TestList& clist2 = list2; + TestList::reverse_iterator riter = list2.rbegin(); + i = 9; + for (; + riter != list2.rend(); + ++riter, --i) { + EXPECT_EQ(riter->n, i); + } + EXPECT_EQ(riter, clist2.rend()); + EXPECT_NE(riter, clist2.rbegin()); + + riter = list2.rbegin(); + i = 9; + for (; + riter != list2.rend(); + ++riter, --i) { + EXPECT_EQ(riter->n, i); + } + EXPECT_EQ(riter, clist2.rend()); + EXPECT_NE(riter, clist2.rbegin()); + + while (!list2.empty()) { + TestItem *e = &list2.front(); + list2.pop_front(); + delete e; + } +} + +TEST(NewIntrusiveListTest, Erase) { + TestList l; + TestItem *e[10]; + + // Create a list with 10 items. + for (int i = 0; i < 10; ++i) { + e[i] = new TestItem; + l.push_front(e[i]); + } + + // Test that erase works. + for (int i = 0; i < 10; ++i) { + EXPECT_EQ(l.size(), (10u - i)); + + TestList::iterator iter = l.erase(e[i]); + EXPECT_NE(iter, TestList::iterator(e[i])); + + EXPECT_EQ(l.size(), (10u - i - 1)); + delete e[i]; + } +} + +TEST(NewIntrusiveListTest, Insert) { + TestList l; + TestList::iterator iter = l.end(); + TestItem *e[10]; + + // Create a list with 10 items. + for (int i = 9; i >= 0; --i) { + e[i] = new TestItem; + iter = l.insert(iter, e[i]); + EXPECT_EQ(&(*iter), e[i]); + } + + EXPECT_EQ(l.size(), 10u); + + // Verify insertion order. + iter = l.begin(); + for (TestItem *item : e) { + EXPECT_EQ(&(*iter), item); + iter = l.erase(item); + delete item; + } +} + +TEST(NewIntrusiveListTest, Move) { + // Move contructible. + + { // Move-construct from an empty list. + TestList src; + TestList dest(std::move(src)); + EXPECT_TRUE(dest.empty()); + } + + { // Move-construct from a single item list. + TestItem e; + TestList src; + src.push_front(&e); + + TestList dest(std::move(src)); + EXPECT_TRUE(src.empty()); // NOLINT bugprone-use-after-move + ASSERT_THAT(dest.size(), 1); + EXPECT_THAT(&dest.front(), &e); + EXPECT_THAT(&dest.back(), &e); + } + + { // Move-construct from a list with multiple items. + TestItem items[10]; + TestList src; + for (TestItem &e : items) src.push_back(&e); + + TestList dest(std::move(src)); + EXPECT_TRUE(src.empty()); // NOLINT bugprone-use-after-move + // Verify the items on the destination list. + ASSERT_THAT(dest.size(), 10); + int i = 0; + for (TestItem &e : dest) { + EXPECT_THAT(&e, &items[i++]) << " for index " << i; + } + } +} + +TEST(NewIntrusiveListTest, StaticInsertErase) { + TestList l; + TestItem e[2]; + TestList::iterator i = l.begin(); + TestList::insert(i, &e[0]); + TestList::insert(&e[0], &e[1]); + TestList::erase(&e[0]); + TestList::erase(TestList::iterator(&e[1])); + EXPECT_TRUE(l.empty()); +} + +TEST_F(IntrusiveListTest, Splice) { + // We verify that the contents of this secondary list aren't affected by any + // of the splices. + SpdyIntrusiveList<TestItem, ListId2> secondary_list; + for (int i = 0; i < 3; ++i) { + secondary_list.push_back(&e[i]); + } + + // Test the basic cases: + // - The lists range from 0 to 2 elements. + // - The insertion point ranges from begin() to end() + // - The transfered range has multiple sizes and locations in the source. + for (int l1_count = 0; l1_count < 3; ++l1_count) { + for (int l2_count = 0; l2_count < 3; ++l2_count) { + for (int pos = 0; pos <= l1_count; ++pos) { + for (int first = 0; first <= l2_count; ++first) { + for (int last = first; last <= l2_count; ++last) { + PrepareLists(l1_count, l2_count); + + l1.splice(std::next(l1.begin(), pos), std::next(l2.begin(), first), + std::next(l2.begin(), last)); + ll1.splice(std::next(ll1.begin(), pos), ll2, + std::next(ll2.begin(), first), + std::next(ll2.begin(), last)); + + CheckLists(); + + ASSERT_EQ(3u, secondary_list.size()); + for (int i = 0; i < 3; ++i) { + EXPECT_EQ(&e[i], &*std::next(secondary_list.begin(), i)); + } + } + } + } + } + } +} + +// Build up a set of classes which form "challenging" type hierarchies to use +// with an SpdyIntrusiveList. +struct BaseLinkId {}; +struct DerivedLinkId {}; + +struct AbstractBase : public SpdyIntrusiveLink<AbstractBase, BaseLinkId> { + virtual ~AbstractBase() = 0; + virtual std::string name() { return "AbstractBase"; } +}; +AbstractBase::~AbstractBase() {} +struct DerivedClass : public SpdyIntrusiveLink<DerivedClass, DerivedLinkId>, + public AbstractBase { + ~DerivedClass() override {} + std::string name() override { return "DerivedClass"; } +}; +struct VirtuallyDerivedBaseClass : public virtual AbstractBase { + ~VirtuallyDerivedBaseClass() override = 0; + std::string name() override { return "VirtuallyDerivedBaseClass"; } +}; +VirtuallyDerivedBaseClass::~VirtuallyDerivedBaseClass() {} +struct VirtuallyDerivedClassA + : public SpdyIntrusiveLink<VirtuallyDerivedClassA, DerivedLinkId>, + public virtual VirtuallyDerivedBaseClass { + ~VirtuallyDerivedClassA() override {} + std::string name() override { return "VirtuallyDerivedClassA"; } +}; +struct NonceClass { + virtual ~NonceClass() {} + int data_; +}; +struct VirtuallyDerivedClassB + : public SpdyIntrusiveLink<VirtuallyDerivedClassB, DerivedLinkId>, + public virtual NonceClass, + public virtual VirtuallyDerivedBaseClass { + ~VirtuallyDerivedClassB() override {} + std::string name() override { return "VirtuallyDerivedClassB"; } +}; +struct VirtuallyDerivedClassC + : public SpdyIntrusiveLink<VirtuallyDerivedClassC, DerivedLinkId>, + public virtual AbstractBase, + public virtual NonceClass, + public virtual VirtuallyDerivedBaseClass { + ~VirtuallyDerivedClassC() override {} + std::string name() override { return "VirtuallyDerivedClassC"; } +}; + +// Test for multiple layers between the element type and the link. +namespace templated_base_link { +template <typename T> struct AbstractBase : public SpdyIntrusiveLink<T> { + virtual ~AbstractBase() = 0; +}; +template <typename T> AbstractBase<T>::~AbstractBase() {} +struct DerivedClass : public AbstractBase<DerivedClass> { + int n; +}; +} + +TEST(NewIntrusiveListTest, HandleInheritanceHierarchies) { + { + SpdyIntrusiveList<DerivedClass, DerivedLinkId> list; + DerivedClass elements[2]; + EXPECT_TRUE(list.empty()); + list.push_back(&elements[0]); + EXPECT_EQ(1u, list.size()); + list.push_back(&elements[1]); + EXPECT_EQ(2u, list.size()); + list.pop_back(); + EXPECT_EQ(1u, list.size()); + list.pop_back(); + EXPECT_TRUE(list.empty()); + } + { + SpdyIntrusiveList<VirtuallyDerivedClassA, DerivedLinkId> list; + VirtuallyDerivedClassA elements[2]; + EXPECT_TRUE(list.empty()); + list.push_back(&elements[0]); + EXPECT_EQ(1u, list.size()); + list.push_back(&elements[1]); + EXPECT_EQ(2u, list.size()); + list.pop_back(); + EXPECT_EQ(1u, list.size()); + list.pop_back(); + EXPECT_TRUE(list.empty()); + } + { + SpdyIntrusiveList<VirtuallyDerivedClassC, DerivedLinkId> list; + VirtuallyDerivedClassC elements[2]; + EXPECT_TRUE(list.empty()); + list.push_back(&elements[0]); + EXPECT_EQ(1u, list.size()); + list.push_back(&elements[1]); + EXPECT_EQ(2u, list.size()); + list.pop_back(); + EXPECT_EQ(1u, list.size()); + list.pop_back(); + EXPECT_TRUE(list.empty()); + } + { + SpdyIntrusiveList<AbstractBase, BaseLinkId> list; + DerivedClass d1; + VirtuallyDerivedClassA d2; + VirtuallyDerivedClassB d3; + VirtuallyDerivedClassC d4; + EXPECT_TRUE(list.empty()); + list.push_back(&d1); + EXPECT_EQ(1u, list.size()); + list.push_back(&d2); + EXPECT_EQ(2u, list.size()); + list.push_back(&d3); + EXPECT_EQ(3u, list.size()); + list.push_back(&d4); + EXPECT_EQ(4u, list.size()); + SpdyIntrusiveList<AbstractBase, BaseLinkId>::iterator it = list.begin(); + EXPECT_EQ("DerivedClass", (it++)->name()); + EXPECT_EQ("VirtuallyDerivedClassA", (it++)->name()); + EXPECT_EQ("VirtuallyDerivedClassB", (it++)->name()); + EXPECT_EQ("VirtuallyDerivedClassC", (it++)->name()); + } + { + SpdyIntrusiveList<templated_base_link::DerivedClass> list; + templated_base_link::DerivedClass elements[2]; + EXPECT_TRUE(list.empty()); + list.push_back(&elements[0]); + EXPECT_EQ(1u, list.size()); + list.push_back(&elements[1]); + EXPECT_EQ(2u, list.size()); + list.pop_back(); + EXPECT_EQ(1u, list.size()); + list.pop_back(); + EXPECT_TRUE(list.empty()); + } +} + +class IntrusiveListTagTypeTest : public QuicheTest { + protected: + struct Tag {}; + class Element : public SpdyIntrusiveLink<Element, Tag> {}; +}; + +TEST_F(IntrusiveListTagTypeTest, TagTypeListID) { + SpdyIntrusiveList<Element, Tag> list; + { + Element e; + list.push_back(&e); + } +} + +} // namespace test +} // namespace spdy diff --git a/chromium/net/third_party/quiche/src/quiche/spdy/core/spdy_no_op_visitor.cc b/chromium/net/third_party/quiche/src/quiche/spdy/core/spdy_no_op_visitor.cc new file mode 100644 index 00000000000..d1ab89b9b66 --- /dev/null +++ b/chromium/net/third_party/quiche/src/quiche/spdy/core/spdy_no_op_visitor.cc @@ -0,0 +1,27 @@ +// 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 "quiche/spdy/core/spdy_no_op_visitor.h" + +#include <type_traits> + +namespace spdy { + +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 spdy diff --git a/chromium/net/third_party/quiche/src/quiche/spdy/core/spdy_no_op_visitor.h b/chromium/net/third_party/quiche/src/quiche/spdy/core/spdy_no_op_visitor.h new file mode 100644 index 00000000000..65ab1428c00 --- /dev/null +++ b/chromium/net/third_party/quiche/src/quiche/spdy/core/spdy_no_op_visitor.h @@ -0,0 +1,96 @@ +// 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 "absl/strings/string_view.h" +#include "quiche/common/platform/api/quiche_export.h" +#include "quiche/spdy/core/http2_frame_decoder_adapter.h" +#include "quiche/spdy/core/spdy_protocol.h" + +namespace spdy { + +class QUICHE_EXPORT_PRIVATE SpdyNoOpVisitor + : public SpdyFramerVisitorInterface, + public SpdyFramerDebugVisitorInterface, + public SpdyHeadersHandlerInterface { + public: + SpdyNoOpVisitor(); + ~SpdyNoOpVisitor() override; + + // SpdyFramerVisitorInterface methods: + void OnError(http2::Http2DecoderAdapter::SpdyFramerError /*error*/, + std::string /*detailed_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*/, + absl::string_view /*origin*/, + const SpdyAltSvcWireFormat::AlternativeServiceVector& + /*altsvc_vector*/) override {} + void OnPriority(SpdyStreamId /*stream_id*/, + SpdyStreamId /*parent_stream_id*/, + int /*weight*/, + bool /*exclusive*/) override {} + void OnPriorityUpdate(SpdyStreamId /*prioritized_stream_id*/, + absl::string_view /*priority_field_value*/) 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(absl::string_view /*key*/, + absl::string_view /*value*/) override {} + void OnHeaderBlockEnd(size_t /* uncompressed_header_bytes */, + size_t /* compressed_header_bytes */) override {} +}; + +} // namespace spdy + +#endif // QUICHE_SPDY_CORE_SPDY_NO_OP_VISITOR_H_ diff --git a/chromium/net/third_party/quiche/src/quiche/spdy/core/spdy_pinnable_buffer_piece.cc b/chromium/net/third_party/quiche/src/quiche/spdy/core/spdy_pinnable_buffer_piece.cc new file mode 100644 index 00000000000..4448d909d1f --- /dev/null +++ b/chromium/net/third_party/quiche/src/quiche/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 "quiche/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/quiche/spdy/core/spdy_pinnable_buffer_piece.h b/chromium/net/third_party/quiche/src/quiche/spdy/core/spdy_pinnable_buffer_piece.h new file mode 100644 index 00000000000..1b1bd32d2f8 --- /dev/null +++ b/chromium/net/third_party/quiche/src/quiche/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 "absl/strings/string_view.h" +#include "quiche/common/platform/api/quiche_export.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 QUICHE_EXPORT_PRIVATE SpdyPinnableBufferPiece { + public: + SpdyPinnableBufferPiece(); + ~SpdyPinnableBufferPiece(); + + const char* buffer() const { return buffer_; } + + explicit operator absl::string_view() const { + return absl::string_view(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/quiche/spdy/core/spdy_pinnable_buffer_piece_test.cc b/chromium/net/third_party/quiche/src/quiche/spdy/core/spdy_pinnable_buffer_piece_test.cc new file mode 100644 index 00000000000..f35f9b06989 --- /dev/null +++ b/chromium/net/third_party/quiche/src/quiche/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 "quiche/spdy/core/spdy_pinnable_buffer_piece.h" + +#include <string> + +#include "quiche/common/platform/api/quiche_test.h" +#include "quiche/spdy/core/spdy_prefixed_buffer_reader.h" + +namespace spdy { + +namespace test { + +class SpdyPinnableBufferPieceTest : public QuicheTest { + protected: + SpdyPrefixedBufferReader Build(const std::string& prefix, + const std::string& suffix) { + prefix_ = prefix; + suffix_ = suffix; + return SpdyPrefixedBufferReader(prefix_.data(), prefix_.length(), + suffix_.data(), suffix_.length()); + } + std::string 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(absl::string_view("foobar"), absl::string_view(piece)); + EXPECT_FALSE(piece.IsPinned()); + EXPECT_EQ(prefix_.data(), piece.buffer()); + + piece.Pin(); + + // Piece now points to allocated storage. + EXPECT_EQ(absl::string_view("foobar"), absl::string_view(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(absl::string_view("foob"), absl::string_view(piece1)); + EXPECT_TRUE(piece1.IsPinned()); + EXPECT_EQ(absl::string_view("ar"), absl::string_view(piece2)); + EXPECT_FALSE(piece2.IsPinned()); + + piece1.Swap(&piece2); + + EXPECT_EQ(absl::string_view("ar"), absl::string_view(piece1)); + EXPECT_FALSE(piece1.IsPinned()); + EXPECT_EQ(absl::string_view("foob"), absl::string_view(piece2)); + EXPECT_TRUE(piece2.IsPinned()); + + SpdyPinnableBufferPiece empty; + piece2.Swap(&empty); + + EXPECT_EQ(absl::string_view(""), absl::string_view(piece2)); + EXPECT_FALSE(piece2.IsPinned()); +} + +} // namespace test + +} // namespace spdy diff --git a/chromium/net/third_party/quiche/src/quiche/spdy/core/spdy_prefixed_buffer_reader.cc b/chromium/net/third_party/quiche/src/quiche/spdy/core/spdy_prefixed_buffer_reader.cc new file mode 100644 index 00000000000..8b5a252fae8 --- /dev/null +++ b/chromium/net/third_party/quiche/src/quiche/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 "quiche/spdy/core/spdy_prefixed_buffer_reader.h" + +#include <new> + +#include "quiche/common/platform/api/quiche_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. + } + QUICHE_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 { + QUICHE_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/quiche/spdy/core/spdy_prefixed_buffer_reader.h b/chromium/net/third_party/quiche/src/quiche/spdy/core/spdy_prefixed_buffer_reader.h new file mode 100644 index 00000000000..8f51f865fe4 --- /dev/null +++ b/chromium/net/third_party/quiche/src/quiche/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 "quiche/common/platform/api/quiche_export.h" +#include "quiche/spdy/core/spdy_pinnable_buffer_piece.h" + +namespace spdy { + +// Reader class which simplifies reading contiguously from +// from a disjoint buffer prefix & suffix. +class QUICHE_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/quiche/spdy/core/spdy_prefixed_buffer_reader_test.cc b/chromium/net/third_party/quiche/src/quiche/spdy/core/spdy_prefixed_buffer_reader_test.cc new file mode 100644 index 00000000000..c2bf25c36b6 --- /dev/null +++ b/chromium/net/third_party/quiche/src/quiche/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 "quiche/spdy/core/spdy_prefixed_buffer_reader.h" + +#include <string> + +#include "absl/strings/string_view.h" +#include "quiche/common/platform/api/quiche_test.h" + +namespace spdy { + +namespace test { + +using testing::ElementsAreArray; + +class SpdyPrefixedBufferReaderTest : public QuicheTest { + protected: + SpdyPrefixedBufferReader Build(const std::string& prefix, + const std::string& suffix) { + prefix_ = prefix; + suffix_ = suffix; + return SpdyPrefixedBufferReader(prefix_.data(), prefix_.length(), + suffix_.data(), suffix_.length()); + } + std::string 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(absl::string_view("foobar"), absl::string_view(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(absl::string_view("foobar"), absl::string_view(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(absl::string_view("foobar"), absl::string_view(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(absl::string_view("fhi"), absl::string_view(piece)); + EXPECT_TRUE(piece.IsPinned()); + EXPECT_EQ(3u, reader.Available()); + + EXPECT_TRUE(reader.ReadN(2, &piece)); + EXPECT_EQ(absl::string_view("jk"), absl::string_view(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/quiche/spdy/core/spdy_protocol.cc b/chromium/net/third_party/quiche/src/quiche/spdy/core/spdy_protocol.cc new file mode 100644 index 00000000000..75888068e36 --- /dev/null +++ b/chromium/net/third_party/quiche/src/quiche/spdy/core/spdy_protocol.cc @@ -0,0 +1,643 @@ +// 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 "quiche/spdy/core/spdy_protocol.h" + +#include <limits> +#include <ostream> + +#include "absl/strings/str_cat.h" +#include "quiche/common/platform/api/quiche_bug_tracker.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) { + static_assert(std::numeric_limits<SpdyPriority>::min() == kV3HighestPriority, + "The value of given priority shouldn't be smaller than highest " + "priority. Check this invariant explicitly."); + if (priority > kV3LowestPriority) { + QUICHE_BUG(spdy_bug_22_1) + << "Invalid priority: " << static_cast<int>(priority); + return kV3LowestPriority; + } + return priority; +} + +int ClampHttp2Weight(int weight) { + if (weight < kHttp2MinStreamWeight) { + QUICHE_BUG(spdy_bug_22_2) << "Invalid weight: " << weight; + return kHttp2MinStreamWeight; + } + if (weight > kHttp2MaxStreamWeight) { + QUICHE_BUG(spdy_bug_22_3) << "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) { + switch (static_cast<SpdyFrameType>(frame_type_field)) { + case SpdyFrameType::DATA: + return true; + case SpdyFrameType::HEADERS: + return true; + case SpdyFrameType::PRIORITY: + return true; + case SpdyFrameType::RST_STREAM: + return true; + case SpdyFrameType::SETTINGS: + return true; + case SpdyFrameType::PUSH_PROMISE: + return true; + case SpdyFrameType::PING: + return true; + case SpdyFrameType::GOAWAY: + return true; + case SpdyFrameType::WINDOW_UPDATE: + return true; + case SpdyFrameType::CONTINUATION: + return true; + case SpdyFrameType::ALTSVC: + return true; + case SpdyFrameType::PRIORITY_UPDATE: + return true; + case SpdyFrameType::ACCEPT_CH: + return true; + } + return false; +} + +SpdyFrameType ParseFrameType(uint8_t frame_type_field) { + QUICHE_BUG_IF(spdy_bug_22_4, !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::PRIORITY_UPDATE: + return "PRIORITY_UPDATE"; + case SpdyFrameType::ACCEPT_CH: + return "ACCEPT_CH"; + } + 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_DEPRECATE_HTTP2_PRIORITIES: + case SETTINGS_EXPERIMENT_SCHEDULER: + return true; + } + return false; +} + +std::string SettingsIdToString(SpdySettingsId id) { + SpdyKnownSettingsId known_id; + if (!ParseSettingsId(id, &known_id)) { + return absl::StrCat("SETTINGS_UNKNOWN_", absl::Hex(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_DEPRECATE_HTTP2_PRIORITIES: + return "SETTINGS_DEPRECATE_HTTP2_PRIORITIES"; + case SETTINGS_EXPERIMENT_SCHEDULER: + return "SETTINGS_EXPERIMENT_SCHEDULER"; + } + + return absl::StrCat("SETTINGS_UNKNOWN_", absl::Hex(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"; +} + +const char* WriteSchedulerTypeToString(WriteSchedulerType type) { + switch (type) { + case WriteSchedulerType::LIFO: + return "LIFO"; + case WriteSchedulerType::SPDY: + return "SPDY"; + case WriteSchedulerType::HTTP2: + return "HTTP2"; + case WriteSchedulerType::FIFO: + return "FIFO"; + } + return "UNKNOWN"; +} + +size_t GetNumberRequiredContinuationFrames(size_t size) { + QUICHE_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, + Http2HeaderBlock header_block) + : SpdyFrameWithFinIR(stream_id), header_block_(std::move(header_block)) {} + +SpdyFrameWithHeaderBlockIR::~SpdyFrameWithHeaderBlockIR() = default; + +SpdyDataIR::SpdyDataIR(SpdyStreamId stream_id, absl::string_view 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, absl::string_view(data)) {} + +SpdyDataIR::SpdyDataIR(SpdyStreamId stream_id, std::string data) + : SpdyFrameWithFinIR(stream_id), + data_store_(std::make_unique<std::string>(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, + absl::string_view 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, + absl::string_view(description)) {} + +SpdyGoAwayIR::SpdyGoAwayIR(SpdyStreamId last_good_stream_id, + SpdyErrorCode error_code, + std::string 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) { +} + +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. + QUICHE_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. + std::string 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 SpdyPriorityUpdateIR::Visit(SpdyFrameVisitor* visitor) const { + return visitor->VisitPriorityUpdate(*this); +} + +SpdyFrameType SpdyPriorityUpdateIR::frame_type() const { + return SpdyFrameType::PRIORITY_UPDATE; +} + +size_t SpdyPriorityUpdateIR::size() const { + return kPriorityUpdateFrameMinimumSize + priority_field_value_.size(); +} + +void SpdyAcceptChIR::Visit(SpdyFrameVisitor* visitor) const { + return visitor->VisitAcceptCh(*this); +} + +SpdyFrameType SpdyAcceptChIR::frame_type() const { + return SpdyFrameType::ACCEPT_CH; +} + +size_t SpdyAcceptChIR::size() const { + size_t total_size = kAcceptChFrameMinimumSize; + for (const AcceptChOriginValuePair& entry : entries_) { + total_size += entry.origin.size() + entry.value.size() + + kAcceptChFramePerEntryOverhead; + } + return total_size; +} + +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/quiche/spdy/core/spdy_protocol.h b/chromium/net/third_party/quiche/src/quiche/spdy/core/spdy_protocol.h new file mode 100644 index 00000000000..7f462a52bc5 --- /dev/null +++ b/chromium/net/third_party/quiche/src/quiche/spdy/core/spdy_protocol.h @@ -0,0 +1,1138 @@ +// 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 <string> +#include <utility> + +#include "absl/strings/string_view.h" +#include "quiche/common/platform/api/quiche_export.h" +#include "quiche/common/platform/api/quiche_logging.h" +#include "quiche/spdy/core/spdy_alt_svc_wire_format.h" +#include "quiche/spdy/core/spdy_bitmasks.h" +#include "quiche/spdy/core/spdy_header_block.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. +QUICHE_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, + PRIORITY_UPDATE = 0x10, + ACCEPT_CH = 0x89, +}; + +// 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://httpwg.org/specs/rfc8441.html + SETTINGS_ENABLE_CONNECT_PROTOCOL = 0x8, + // Disable HTTP/2 priorities, see + // https://tools.ietf.org/html/draft-ietf-httpbis-priority-02. + SETTINGS_DEPRECATE_HTTP2_PRIORITIES = 0x9, + SETTINGS_MAX = SETTINGS_DEPRECATE_HTTP2_PRIORITIES, + // 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. +QUICHE_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. +QUICHE_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 +}; + +// Type of priority write scheduler. +enum class WriteSchedulerType { + LIFO, // Last added stream has the highest priority. + SPDY, // Uses SPDY priorities described in + // https://www.chromium.org/spdy/spdy-protocol/spdy-protocol-draft3-1#TOC-2.3.3-Stream-priority. + HTTP2, // Uses HTTP2 (tree-style) priority described in + // https://tools.ietf.org/html/rfc7540#section-5.3. + FIFO, // Stream with the smallest stream ID has the highest priority. +}; + +// 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]. +QUICHE_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]. +QUICHE_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. +QUICHE_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). +QUICHE_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. +QUICHE_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. +QUICHE_EXPORT_PRIVATE SpdyFrameType ParseFrameType(uint8_t frame_type_field); + +// Serializes a frame type to the on-the-wire value. +QUICHE_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. +QUICHE_EXPORT_PRIVATE bool IsValidHTTP2FrameStreamId( + SpdyStreamId current_frame_stream_id, + SpdyFrameType frame_type_field); + +// Serialize |frame_type| to string for logging/debugging. +QUICHE_EXPORT_PRIVATE 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. +QUICHE_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(). +QUICHE_EXPORT_PRIVATE std::string SettingsIdToString(SpdySettingsId id); + +// Parse |wire_error_code| to a SpdyErrorCode. +// Treat unrecognized error codes as INTERNAL_ERROR +// as recommended by the HTTP/2 specification. +QUICHE_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); + +// Serialize |type| to string for logging/debugging. +QUICHE_EXPORT_PRIVATE const char* WriteSchedulerTypeToString( + WriteSchedulerType type); + +// 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; +// PRIORITY_UPDATE frame has prioritized_stream_id (4 octets) field. +const size_t kPriorityUpdateFrameMinimumSize = kFrameHeaderSize + 4; +// ACCEPT_CH frame may have empty payload. +const size_t kAcceptChFrameMinimumSize = kFrameHeaderSize; +// Each ACCEPT_CH frame entry has a 16-bit origin length and a 16-bit value +// length. +const size_t kAcceptChFramePerEntryOverhead = 4; + +// 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); +// 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". +QUICHE_EXPORT_PRIVATE 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. +QUICHE_EXPORT_PRIVATE extern const char* const kHttp2AuthorityHeader; +QUICHE_EXPORT_PRIVATE extern const char* const kHttp2MethodHeader; +QUICHE_EXPORT_PRIVATE extern const char* const kHttp2PathHeader; +QUICHE_EXPORT_PRIVATE extern const char* const kHttp2SchemeHeader; +QUICHE_EXPORT_PRIVATE extern const char* const kHttp2ProtocolHeader; + +// Name of pseudo-header defined for HTTP/2 responses. +QUICHE_EXPORT_PRIVATE extern const char* const kHttp2StatusHeader; + +QUICHE_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 QUICHE_EXPORT_PRIVATE 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 QUICHE_EXPORT_PRIVATE 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 QUICHE_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 QUICHE_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 QUICHE_EXPORT_PRIVATE SpdyFrameWithHeaderBlockIR + : public SpdyFrameWithFinIR { + public: + ~SpdyFrameWithHeaderBlockIR() override; + + const Http2HeaderBlock& header_block() const { return header_block_; } + void set_header_block(Http2HeaderBlock header_block) { + // Deep copy. + header_block_ = std::move(header_block); + } + void SetHeader(absl::string_view name, absl::string_view value) { + header_block_[name] = value; + } + + protected: + SpdyFrameWithHeaderBlockIR(SpdyStreamId stream_id, + Http2HeaderBlock header_block); + SpdyFrameWithHeaderBlockIR(const SpdyFrameWithHeaderBlockIR&) = delete; + SpdyFrameWithHeaderBlockIR& operator=(const SpdyFrameWithHeaderBlockIR&) = + delete; + + private: + Http2HeaderBlock header_block_; +}; + +class QUICHE_EXPORT_PRIVATE SpdyDataIR : public SpdyFrameWithFinIR { + public: + // Performs a deep copy on data. + SpdyDataIR(SpdyStreamId stream_id, absl::string_view 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, std::string 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) { + QUICHE_DCHECK_GT(padding_len, 0); + QUICHE_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(absl::string_view data) { + data_store_ = std::make_unique<std::string>(data.data(), data.size()); + data_ = data_store_->data(); + data_len_ = data.size(); + } + + // Shallow-copy of data (do not keep private copy). + void SetDataShallow(absl::string_view 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<std::string> 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 QUICHE_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 QUICHE_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 QUICHE_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 QUICHE_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, + absl::string_view 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, + std::string 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) { + QUICHE_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 absl::string_view& 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 std::string description_store_; + const absl::string_view description_; +}; + +class QUICHE_EXPORT_PRIVATE SpdyHeadersIR : public SpdyFrameWithHeaderBlockIR { + public: + explicit SpdyHeadersIR(SpdyStreamId stream_id) + : SpdyHeadersIR(stream_id, Http2HeaderBlock()) {} + SpdyHeadersIR(SpdyStreamId stream_id, Http2HeaderBlock 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) { + QUICHE_DCHECK_GT(padding_len, 0); + QUICHE_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 QUICHE_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) { + QUICHE_DCHECK_LE(0, delta); + QUICHE_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 QUICHE_EXPORT_PRIVATE SpdyPushPromiseIR + : public SpdyFrameWithHeaderBlockIR { + public: + SpdyPushPromiseIR(SpdyStreamId stream_id, SpdyStreamId promised_stream_id) + : SpdyPushPromiseIR(stream_id, promised_stream_id, Http2HeaderBlock()) {} + SpdyPushPromiseIR(SpdyStreamId stream_id, + SpdyStreamId promised_stream_id, + Http2HeaderBlock 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) { + QUICHE_DCHECK_GT(padding_len, 0); + QUICHE_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 QUICHE_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 std::string& encoding() const { return encoding_; } + void take_encoding(std::string encoding) { encoding_ = std::move(encoding); } + size_t size() const override; + + private: + std::string encoding_; + bool end_headers_; +}; + +class QUICHE_EXPORT_PRIVATE SpdyAltSvcIR : public SpdyFrameIR { + public: + explicit SpdyAltSvcIR(SpdyStreamId stream_id); + SpdyAltSvcIR(const SpdyAltSvcIR&) = delete; + SpdyAltSvcIR& operator=(const SpdyAltSvcIR&) = delete; + ~SpdyAltSvcIR() override; + + std::string origin() const { return origin_; } + const SpdyAltSvcWireFormat::AlternativeServiceVector& altsvc_vector() const { + return altsvc_vector_; + } + + void set_origin(std::string 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: + std::string origin_; + SpdyAltSvcWireFormat::AlternativeServiceVector altsvc_vector_; +}; + +class QUICHE_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_; +}; + +class QUICHE_EXPORT_PRIVATE SpdyPriorityUpdateIR : public SpdyFrameIR { + public: + SpdyPriorityUpdateIR(SpdyStreamId stream_id, + SpdyStreamId prioritized_stream_id, + std::string priority_field_value) + : SpdyFrameIR(stream_id), + prioritized_stream_id_(prioritized_stream_id), + priority_field_value_(std::move(priority_field_value)) {} + SpdyPriorityUpdateIR(const SpdyPriorityUpdateIR&) = delete; + SpdyPriorityUpdateIR& operator=(const SpdyPriorityUpdateIR&) = delete; + SpdyStreamId prioritized_stream_id() const { return prioritized_stream_id_; } + const std::string& priority_field_value() const { + return priority_field_value_; + } + + void Visit(SpdyFrameVisitor* visitor) const override; + + SpdyFrameType frame_type() const override; + + size_t size() const override; + + private: + SpdyStreamId prioritized_stream_id_; + std::string priority_field_value_; +}; + +struct QUICHE_EXPORT_PRIVATE AcceptChOriginValuePair { + std::string origin; + std::string value; + bool operator==(const AcceptChOriginValuePair& rhs) const { + return origin == rhs.origin && value == rhs.value; + } +}; + +class QUICHE_EXPORT_PRIVATE SpdyAcceptChIR : public SpdyFrameIR { + public: + SpdyAcceptChIR(std::vector<AcceptChOriginValuePair> entries) + : entries_(std::move(entries)) {} + SpdyAcceptChIR(const SpdyAcceptChIR&) = delete; + SpdyAcceptChIR& operator=(const SpdyAcceptChIR&) = delete; + + void Visit(SpdyFrameVisitor* visitor) const override; + + SpdyFrameType frame_type() const override; + + size_t size() const override; + + const std::vector<AcceptChOriginValuePair>& entries() const { + return entries_; + } + + private: + std::vector<AcceptChOriginValuePair> entries_; +}; + +// Represents a frame of unrecognized type. +class QUICHE_EXPORT_PRIVATE SpdyUnknownIR : public SpdyFrameIR { + public: + SpdyUnknownIR(SpdyStreamId stream_id, + uint8_t type, + uint8_t flags, + std::string 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 std::string& 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 std::string payload_; +}; + +class QUICHE_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_; } + + operator absl::string_view() const { + return absl::string_view{frame_, size_}; + } + + operator std::string() const { return std::string{frame_, 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; + } + + 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 QUICHE_EXPORT_PRIVATE SpdyFrameVisitor { + public: + SpdyFrameVisitor() {} + SpdyFrameVisitor(const SpdyFrameVisitor&) = delete; + SpdyFrameVisitor& operator=(const SpdyFrameVisitor&) = delete; + virtual ~SpdyFrameVisitor() {} + + 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 VisitPriorityUpdate( + const SpdyPriorityUpdateIR& priority_update) = 0; + virtual void VisitAcceptCh(const SpdyAcceptChIR& accept_ch) = 0; + virtual void VisitUnknown(const SpdyUnknownIR& /*unknown*/) { + // TODO(birenroy): make abstract. + } +}; + +// 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 QUICHE_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/quiche/spdy/core/spdy_protocol_test.cc b/chromium/net/third_party/quiche/src/quiche/spdy/core/spdy_protocol_test.cc new file mode 100644 index 00000000000..3084907d503 --- /dev/null +++ b/chromium/net/third_party/quiche/src/quiche/spdy/core/spdy_protocol_test.cc @@ -0,0 +1,275 @@ +// 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 "quiche/spdy/core/spdy_protocol.h" + +#include <iostream> +#include <limits> +#include <memory> + +#include "quiche/common/platform/api/quiche_test.h" +#include "quiche/common/platform/api/quiche_test_helpers.h" +#include "quiche/spdy/core/spdy_bitmasks.h" +#include "quiche/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_QUICHE_BUG(EXPECT_EQ(7, ClampSpdy3Priority(8)), "Invalid priority: 8"); + EXPECT_EQ(kV3LowestPriority, ClampSpdy3Priority(kV3LowestPriority)); + EXPECT_EQ(kV3HighestPriority, ClampSpdy3Priority(kV3HighestPriority)); +} + +TEST(SpdyProtocolTest, ClampHttp2Weight) { + EXPECT_QUICHE_BUG(EXPECT_EQ(kHttp2MinStreamWeight, ClampHttp2Weight(0)), + "Invalid weight: 0"); + EXPECT_QUICHE_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_TRUE(ParseSettingsId(9, &setting_id)); + EXPECT_EQ(SETTINGS_DEPRECATE_HTTP2_PRIORITIES, setting_id); + EXPECT_FALSE(ParseSettingsId(10, &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 std::string 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"}, + {SETTINGS_DEPRECATE_HTTP2_PRIORITIES, + "SETTINGS_DEPRECATE_HTTP2_PRIORITIES"}, + {0xa, "SETTINGS_UNKNOWN_a"}, + {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_QUICHE_BUG(EXPECT_EQ(7, SpdyStreamPrecedence(8).spdy3_priority()), + "Invalid priority: 8"); + EXPECT_QUICHE_BUG(EXPECT_EQ(kHttp2MinStreamWeight, + SpdyStreamPrecedence(3, 0, false).weight()), + "Invalid weight: 0"); + EXPECT_QUICHE_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 + // absl::string_view(nullptr). + absl::string_view 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(absl::string_view(d2.data(), d2.data_len()), s2); + EXPECT_NE(absl::string_view(d1.data(), d1.data_len()), s2); + EXPECT_EQ((int)d1.data_len(), d1.flow_control_window_consumed()); + + // Confirm copies a const string. + const std::string 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. + std::string bar = "bar"; + SpdyDataIR d4(/* stream_id = */ 4, bar); + EXPECT_EQ("bar", bar); + EXPECT_EQ("bar", absl::string_view(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. + std::string baz = "the quick brown fox"; + SpdyDataIR d5(/* stream_id = */ 5, std::move(baz)); + EXPECT_EQ("", baz); + EXPECT_EQ(absl::string_view(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(absl::string_view(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/quiche/spdy/core/spdy_protocol_test_utils.cc b/chromium/net/third_party/quiche/src/quiche/spdy/core/spdy_protocol_test_utils.cc new file mode 100644 index 00000000000..9bd3a06180a --- /dev/null +++ b/chromium/net/third_party/quiche/src/quiche/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 "quiche/spdy/core/spdy_protocol_test_utils.h" + +#include <cstdint> + +#include "absl/strings/string_view.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) { + QUICHE_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) { + QUICHE_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(absl::string_view(expected.data(), expected.data_len()), + absl::string_view(actual.data(), actual.data_len())); + } + VERIFY_SUCCESS(VerifySpdyFrameWithPaddingIREquals(expected, actual)); + return ::testing::AssertionSuccess(); +} + +::testing::AssertionResult VerifySpdyFrameIREquals(const SpdyGoAwayIR& expected, + const SpdyGoAwayIR& actual) { + QUICHE_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) { + QUICHE_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) { + QUICHE_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) { + QUICHE_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) { + QUICHE_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) { + QUICHE_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) { + QUICHE_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) { + QUICHE_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/quiche/spdy/core/spdy_protocol_test_utils.h b/chromium/net/third_party/quiche/src/quiche/spdy/core/spdy_protocol_test_utils.h new file mode 100644 index 00000000000..515a903474f --- /dev/null +++ b/chromium/net/third_party/quiche/src/quiche/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 "quiche/common/platform/api/quiche_logging.h" +#include "quiche/common/platform/api/quiche_test.h" +#include "quiche/common/platform/api/quiche_test_helpers.h" +#include "quiche/spdy/core/spdy_protocol.h" +#include "quiche/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) { + QUICHE_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) { + QUICHE_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) { + QUICHE_VLOG(1) << "VerifySpdyFrameIREquals one null"; + VERIFY_EQ(expected, nullptr); + VERIFY_EQ(actual, nullptr); + return ::testing::AssertionSuccess(); + } + QUICHE_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) { + QUICHE_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/quiche/spdy/core/spdy_simple_arena.cc b/chromium/net/third_party/quiche/src/quiche/spdy/core/spdy_simple_arena.cc new file mode 100644 index 00000000000..0b3b7985214 --- /dev/null +++ b/chromium/net/third_party/quiche/src/quiche/spdy/core/spdy_simple_arena.cc @@ -0,0 +1,106 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "quiche/spdy/core/spdy_simple_arena.h" + +#include <algorithm> +#include <cstring> + +#include "quiche/common/platform/api/quiche_logging.h" + +namespace spdy { + +SpdySimpleArena::SpdySimpleArena(size_t block_size) : block_size_(block_size) {} + +SpdySimpleArena::~SpdySimpleArena() = default; + +SpdySimpleArena::SpdySimpleArena(SpdySimpleArena&& other) = default; +SpdySimpleArena& SpdySimpleArena::operator=(SpdySimpleArena&& other) = default; + +char* SpdySimpleArena::Alloc(size_t size) { + Reserve(size); + Block& b = blocks_.back(); + QUICHE_DCHECK_GE(b.size, b.used + size); + char* out = b.data.get() + b.used; + b.used += size; + return out; +} + +char* SpdySimpleArena::Realloc(char* original, size_t oldsize, size_t newsize) { + QUICHE_DCHECK(!blocks_.empty()); + Block& last = blocks_.back(); + if (last.data.get() <= original && original < last.data.get() + last.size) { + // (original, oldsize) is in the last Block. + QUICHE_DCHECK_GE(last.data.get() + last.used, original + oldsize); + if (original + oldsize == last.data.get() + last.used) { + // (original, oldsize) was the most recent allocation, + if (original + newsize < last.data.get() + last.size) { + // (original, newsize) fits in the same Block. + last.used += newsize - oldsize; + return original; + } + } + } + char* out = Alloc(newsize); + memcpy(out, original, oldsize); + return out; +} + +char* SpdySimpleArena::Memdup(const char* data, size_t size) { + char* out = Alloc(size); + memcpy(out, data, size); + return out; +} + +void SpdySimpleArena::Free(char* data, size_t size) { + if (blocks_.empty()) { + return; + } + Block& b = blocks_.back(); + if (size <= b.used && data + size == b.data.get() + b.used) { + // The memory region passed by the caller was the most recent allocation + // from the final block in this arena. + b.used -= size; + } +} + +void SpdySimpleArena::Reset() { + blocks_.clear(); + status_.bytes_allocated_ = 0; +} + +void SpdySimpleArena::Reserve(size_t additional_space) { + if (blocks_.empty()) { + AllocBlock(std::max(additional_space, block_size_)); + } else { + const Block& last = blocks_.back(); + if (last.size < last.used + additional_space) { + AllocBlock(std::max(additional_space, block_size_)); + } + } +} + +void SpdySimpleArena::AllocBlock(size_t size) { + blocks_.push_back(Block(size)); + status_.bytes_allocated_ += size; +} + +SpdySimpleArena::Block::Block(size_t s) : data(new char[s]), size(s), used(0) {} + +SpdySimpleArena::Block::~Block() = default; + +SpdySimpleArena::Block::Block(SpdySimpleArena::Block&& other) + : size(other.size), used(other.used) { + data = std::move(other.data); +} + +SpdySimpleArena::Block& SpdySimpleArena::Block::operator=( + SpdySimpleArena::Block&& other) { + size = other.size; + used = other.used; + data = std::move(other.data); + return *this; +} + +} // namespace spdy diff --git a/chromium/net/third_party/quiche/src/quiche/spdy/core/spdy_simple_arena.h b/chromium/net/third_party/quiche/src/quiche/spdy/core/spdy_simple_arena.h new file mode 100644 index 00000000000..99a6f743192 --- /dev/null +++ b/chromium/net/third_party/quiche/src/quiche/spdy/core/spdy_simple_arena.h @@ -0,0 +1,77 @@ +// Copyright 2016 The Chromium 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_SIMPLE_ARENA_H_ +#define QUICHE_SPDY_CORE_SPDY_SIMPLE_ARENA_H_ + +#include <memory> +#include <vector> + +#include "quiche/common/platform/api/quiche_export.h" + +namespace spdy { + +// Allocates large blocks of memory, and doles them out in smaller chunks. +// Not thread-safe. +class QUICHE_EXPORT_PRIVATE SpdySimpleArena { + public: + class Status { + private: + friend class SpdySimpleArena; + size_t bytes_allocated_; + + public: + Status() : bytes_allocated_(0) {} + size_t bytes_allocated() const { return bytes_allocated_; } + }; + + // Blocks allocated by this arena will be at least |block_size| bytes. + explicit SpdySimpleArena(size_t block_size); + ~SpdySimpleArena(); + + // Copy and assign are not allowed. + SpdySimpleArena() = delete; + SpdySimpleArena(const SpdySimpleArena&) = delete; + SpdySimpleArena& operator=(const SpdySimpleArena&) = delete; + + // Move is allowed. + SpdySimpleArena(SpdySimpleArena&& other); + SpdySimpleArena& operator=(SpdySimpleArena&& other); + + char* Alloc(size_t size); + char* Realloc(char* original, size_t oldsize, size_t newsize); + char* Memdup(const char* data, size_t size); + + // If |data| and |size| describe the most recent allocation made from this + // arena, the memory is reclaimed. Otherwise, this method is a no-op. + void Free(char* data, size_t size); + + void Reset(); + + Status status() const { return status_; } + + private: + struct Block { + std::unique_ptr<char[]> data; + size_t size = 0; + size_t used = 0; + + explicit Block(size_t s); + ~Block(); + + Block(Block&& other); + Block& operator=(Block&& other); + }; + + void Reserve(size_t additional_space); + void AllocBlock(size_t size); + + size_t block_size_; + std::vector<Block> blocks_; + Status status_; +}; + +} // namespace spdy + +#endif // QUICHE_SPDY_CORE_SPDY_SIMPLE_ARENA_H_ diff --git a/chromium/net/third_party/quiche/src/quiche/spdy/core/spdy_simple_arena_test.cc b/chromium/net/third_party/quiche/src/quiche/spdy/core/spdy_simple_arena_test.cc new file mode 100644 index 00000000000..9708375374b --- /dev/null +++ b/chromium/net/third_party/quiche/src/quiche/spdy/core/spdy_simple_arena_test.cc @@ -0,0 +1,141 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "quiche/spdy/core/spdy_simple_arena.h" + +#include <string> +#include <vector> + +#include "absl/strings/string_view.h" +#include "quiche/common/platform/api/quiche_test.h" + +namespace spdy { +namespace { + +size_t kDefaultBlockSize = 2048; +const char kTestString[] = "This is a decently long test string."; + +TEST(SpdySimpleArenaTest, NoAllocationOnConstruction) { + SpdySimpleArena arena(kDefaultBlockSize); + EXPECT_EQ(0u, arena.status().bytes_allocated()); +} + +TEST(SpdySimpleArenaTest, Memdup) { + SpdySimpleArena arena(kDefaultBlockSize); + const size_t length = strlen(kTestString); + char* c = arena.Memdup(kTestString, length); + EXPECT_NE(nullptr, c); + EXPECT_NE(c, kTestString); + EXPECT_EQ(absl::string_view(c, length), kTestString); +} + +TEST(SpdySimpleArenaTest, MemdupLargeString) { + SpdySimpleArena arena(10 /* block size */); + const size_t length = strlen(kTestString); + char* c = arena.Memdup(kTestString, length); + EXPECT_NE(nullptr, c); + EXPECT_NE(c, kTestString); + EXPECT_EQ(absl::string_view(c, length), kTestString); +} + +TEST(SpdySimpleArenaTest, MultipleBlocks) { + SpdySimpleArena arena(40 /* block size */); + std::vector<std::string> strings = { + "One decently long string.", "Another string.", + "A third string that will surely go in a different block."}; + std::vector<absl::string_view> copies; + for (const std::string& s : strings) { + absl::string_view sp(arena.Memdup(s.data(), s.size()), s.size()); + copies.push_back(sp); + } + EXPECT_EQ(strings.size(), copies.size()); + for (size_t i = 0; i < strings.size(); ++i) { + EXPECT_EQ(copies[i], strings[i]); + } +} + +TEST(SpdySimpleArenaTest, UseAfterReset) { + SpdySimpleArena arena(kDefaultBlockSize); + const size_t length = strlen(kTestString); + char* c = arena.Memdup(kTestString, length); + arena.Reset(); + c = arena.Memdup(kTestString, length); + EXPECT_NE(nullptr, c); + EXPECT_NE(c, kTestString); + EXPECT_EQ(absl::string_view(c, length), kTestString); +} + +TEST(SpdySimpleArenaTest, Free) { + SpdySimpleArena arena(kDefaultBlockSize); + const size_t length = strlen(kTestString); + // Freeing memory not owned by the arena should be a no-op, and freeing + // before any allocations from the arena should be a no-op. + arena.Free(const_cast<char*>(kTestString), length); + char* c1 = arena.Memdup("Foo", 3); + char* c2 = arena.Memdup(kTestString, length); + arena.Free(const_cast<char*>(kTestString), length); + char* c3 = arena.Memdup("Bar", 3); + char* c4 = arena.Memdup(kTestString, length); + EXPECT_NE(c1, c2); + EXPECT_NE(c1, c3); + EXPECT_NE(c1, c4); + EXPECT_NE(c2, c3); + EXPECT_NE(c2, c4); + EXPECT_NE(c3, c4); + // Freeing c4 should succeed, since it was the most recent allocation. + arena.Free(c4, length); + // Freeing c2 should be a no-op. + arena.Free(c2, length); + // c5 should reuse memory that was previously used by c4. + char* c5 = arena.Memdup("Baz", 3); + EXPECT_EQ(c4, c5); +} + +TEST(SpdySimpleArenaTest, Alloc) { + SpdySimpleArena arena(kDefaultBlockSize); + const size_t length = strlen(kTestString); + char* c1 = arena.Alloc(length); + char* c2 = arena.Alloc(2 * length); + char* c3 = arena.Alloc(3 * length); + char* c4 = arena.Memdup(kTestString, length); + EXPECT_EQ(c1 + length, c2); + EXPECT_EQ(c2 + 2 * length, c3); + EXPECT_EQ(c3 + 3 * length, c4); + EXPECT_EQ(absl::string_view(c4, length), kTestString); +} + +TEST(SpdySimpleArenaTest, Realloc) { + SpdySimpleArena arena(kDefaultBlockSize); + const size_t length = strlen(kTestString); + // Simple realloc that fits in the block. + char* c1 = arena.Memdup(kTestString, length); + char* c2 = arena.Realloc(c1, length, 2 * length); + EXPECT_TRUE(c1); + EXPECT_EQ(c1, c2); + EXPECT_EQ(absl::string_view(c1, length), kTestString); + // Multiple reallocs. + char* c3 = arena.Memdup(kTestString, length); + EXPECT_EQ(c2 + 2 * length, c3); + EXPECT_EQ(absl::string_view(c3, length), kTestString); + char* c4 = arena.Realloc(c3, length, 2 * length); + EXPECT_EQ(c3, c4); + EXPECT_EQ(absl::string_view(c4, length), kTestString); + char* c5 = arena.Realloc(c4, 2 * length, 3 * length); + EXPECT_EQ(c4, c5); + EXPECT_EQ(absl::string_view(c5, length), kTestString); + char* c6 = arena.Memdup(kTestString, length); + EXPECT_EQ(c5 + 3 * length, c6); + EXPECT_EQ(absl::string_view(c6, length), kTestString); + // Realloc that does not fit in the remainder of the first block. + char* c7 = arena.Realloc(c6, length, kDefaultBlockSize); + EXPECT_EQ(absl::string_view(c7, length), kTestString); + arena.Free(c7, kDefaultBlockSize); + char* c8 = arena.Memdup(kTestString, length); + EXPECT_NE(c6, c7); + EXPECT_EQ(c7, c8); + EXPECT_EQ(absl::string_view(c8, length), kTestString); +} + +} // namespace +} // namespace spdy diff --git a/chromium/net/third_party/quiche/src/quiche/spdy/core/spdy_test_utils.cc b/chromium/net/third_party/quiche/src/quiche/spdy/core/spdy_test_utils.cc new file mode 100644 index 00000000000..b896d931be3 --- /dev/null +++ b/chromium/net/third_party/quiche/src/quiche/spdy/core/spdy_test_utils.cc @@ -0,0 +1,104 @@ +// 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 "quiche/spdy/core/spdy_test_utils.h" + +#include <algorithm> +#include <cstring> +#include <memory> +#include <new> +#include <utility> +#include <vector> + +#include "quiche/common/platform/api/quiche_logging.h" +#include "quiche/common/platform/api/quiche_test.h" +#include "quiche/common/quiche_endian.h" + +namespace spdy { +namespace test { + +std::string 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) { + QUICHE_LOG(ERROR) << "Only dumping first " << kSizeLimit << " bytes."; + length = std::min(length, kSizeLimit); + mark_length = std::min(mark_length, kSizeLimit); + } + + std::string 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 std::string& 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) { + QUICHE_CHECK_GT(1u << 14, length); + { + int32_t wire_length = quiche::QuicheEndian::HostToNet32(length); + memcpy(frame->data(), reinterpret_cast<char*>(&wire_length) + 1, 3); + } +} + +} // namespace test +} // namespace spdy diff --git a/chromium/net/third_party/quiche/src/quiche/spdy/core/spdy_test_utils.h b/chromium/net/third_party/quiche/src/quiche/spdy/core/spdy_test_utils.h new file mode 100644 index 00000000000..27f42e93ac3 --- /dev/null +++ b/chromium/net/third_party/quiche/src/quiche/spdy/core/spdy_test_utils.h @@ -0,0 +1,43 @@ +// 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 <string> + +#include "absl/strings/string_view.h" +#include "quiche/spdy/core/spdy_header_block.h" +#include "quiche/spdy/core/spdy_protocol.h" + +namespace spdy { + +inline bool operator==(absl::string_view x, + const Http2HeaderBlock::ValueProxy& y) { + return y.operator==(x); +} + +namespace test { + +std::string HexDumpWithMarks(const unsigned char* data, + int length, + const bool* marks, + int mark_length); + +void CompareCharArraysWithHexError(const std::string& 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); + +} // namespace test +} // namespace spdy + +#endif // QUICHE_SPDY_CORE_SPDY_TEST_UTILS_H_ diff --git a/chromium/net/third_party/quiche/src/quiche/spdy/core/zero_copy_output_buffer.h b/chromium/net/third_party/quiche/src/quiche/spdy/core/zero_copy_output_buffer.h new file mode 100644 index 00000000000..3f35bab714d --- /dev/null +++ b/chromium/net/third_party/quiche/src/quiche/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_ |