summaryrefslogtreecommitdiff
path: root/chromium/net/third_party/quiche/src/quiche/spdy/core
diff options
context:
space:
mode:
authorAllan Sandfeld Jensen <allan.jensen@qt.io>2022-05-17 17:24:03 +0200
committerAllan Sandfeld Jensen <allan.jensen@qt.io>2022-06-22 07:51:41 +0000
commit774f54339e5db91f785733232d3950366db65d07 (patch)
tree068e1b47bd1af94d77094ed12b604a6b83d9c22a /chromium/net/third_party/quiche/src/quiche/spdy/core
parentf7eaed5286974984ba5f9e3189d8f49d03e99f81 (diff)
downloadqtwebengine-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')
-rw-r--r--chromium/net/third_party/quiche/src/quiche/spdy/core/array_output_buffer.cc23
-rw-r--r--chromium/net/third_party/quiche/src/quiche/spdy/core/array_output_buffer.h47
-rw-r--r--chromium/net/third_party/quiche/src/quiche/spdy/core/array_output_buffer_test.cc49
-rw-r--r--chromium/net/third_party/quiche/src/quiche/spdy/core/header_byte_listener_interface.h22
-rw-r--r--chromium/net/third_party/quiche/src/quiche/spdy/core/hpack/hpack_constants.cc374
-rw-r--r--chromium/net/third_party/quiche/src/quiche/spdy/core/hpack/hpack_constants.h90
-rw-r--r--chromium/net/third_party/quiche/src/quiche/spdy/core/hpack/hpack_decoder_adapter.cc164
-rw-r--r--chromium/net/third_party/quiche/src/quiche/spdy/core/hpack/hpack_decoder_adapter.h157
-rw-r--r--chromium/net/third_party/quiche/src/quiche/spdy/core/hpack/hpack_decoder_adapter_test.cc1119
-rw-r--r--chromium/net/third_party/quiche/src/quiche/spdy/core/hpack/hpack_encoder.cc374
-rw-r--r--chromium/net/third_party/quiche/src/quiche/spdy/core/hpack/hpack_encoder.h146
-rw-r--r--chromium/net/third_party/quiche/src/quiche/spdy/core/hpack/hpack_encoder_test.cc753
-rw-r--r--chromium/net/third_party/quiche/src/quiche/spdy/core/hpack/hpack_entry.cc24
-rw-r--r--chromium/net/third_party/quiche/src/quiche/spdy/core/hpack/hpack_entry.h81
-rw-r--r--chromium/net/third_party/quiche/src/quiche/spdy/core/hpack/hpack_entry_test.cc53
-rw-r--r--chromium/net/third_party/quiche/src/quiche/spdy/core/hpack/hpack_header_table.cc188
-rw-r--r--chromium/net/third_party/quiche/src/quiche/spdy/core/hpack/hpack_header_table.h153
-rw-r--r--chromium/net/third_party/quiche/src/quiche/spdy/core/hpack/hpack_header_table_test.cc392
-rw-r--r--chromium/net/third_party/quiche/src/quiche/spdy/core/hpack/hpack_output_stream.cc100
-rw-r--r--chromium/net/third_party/quiche/src/quiche/spdy/core/hpack/hpack_output_stream.h75
-rw-r--r--chromium/net/third_party/quiche/src/quiche/spdy/core/hpack/hpack_output_stream_test.cc284
-rw-r--r--chromium/net/third_party/quiche/src/quiche/spdy/core/hpack/hpack_round_trip_test.cc222
-rw-r--r--chromium/net/third_party/quiche/src/quiche/spdy/core/hpack/hpack_static_table.cc50
-rw-r--r--chromium/net/third_party/quiche/src/quiche/spdy/core/hpack/hpack_static_table.h56
-rw-r--r--chromium/net/third_party/quiche/src/quiche/spdy/core/hpack/hpack_static_table_test.cc63
-rw-r--r--chromium/net/third_party/quiche/src/quiche/spdy/core/http2_frame_decoder_adapter.cc1107
-rw-r--r--chromium/net/third_party/quiche/src/quiche/spdy/core/http2_frame_decoder_adapter.h547
-rw-r--r--chromium/net/third_party/quiche/src/quiche/spdy/core/http2_header_block_hpack_listener.h47
-rw-r--r--chromium/net/third_party/quiche/src/quiche/spdy/core/metadata_extension.cc195
-rw-r--r--chromium/net/third_party/quiche/src/quiche/spdy/core/metadata_extension.h116
-rw-r--r--chromium/net/third_party/quiche/src/quiche/spdy/core/metadata_extension_test.cc229
-rw-r--r--chromium/net/third_party/quiche/src/quiche/spdy/core/mock_spdy_framer_visitor.cc19
-rw-r--r--chromium/net/third_party/quiche/src/quiche/spdy/core/mock_spdy_framer_visitor.h150
-rw-r--r--chromium/net/third_party/quiche/src/quiche/spdy/core/no_op_headers_handler.h39
-rw-r--r--chromium/net/third_party/quiche/src/quiche/spdy/core/recording_headers_handler.cc38
-rw-r--r--chromium/net/third_party/quiche/src/quiche/spdy/core/recording_headers_handler.h51
-rw-r--r--chromium/net/third_party/quiche/src/quiche/spdy/core/spdy_alt_svc_wire_format.cc427
-rw-r--r--chromium/net/third_party/quiche/src/quiche/spdy/core/spdy_alt_svc_wire_format.h105
-rw-r--r--chromium/net/third_party/quiche/src/quiche/spdy/core/spdy_alt_svc_wire_format_test.cc638
-rw-r--r--chromium/net/third_party/quiche/src/quiche/spdy/core/spdy_bitmasks.h18
-rw-r--r--chromium/net/third_party/quiche/src/quiche/spdy/core/spdy_frame_builder.cc185
-rw-r--r--chromium/net/third_party/quiche/src/quiche/spdy/core/spdy_frame_builder.h146
-rw-r--r--chromium/net/third_party/quiche/src/quiche/spdy/core/spdy_frame_builder_test.cc87
-rw-r--r--chromium/net/third_party/quiche/src/quiche/spdy/core/spdy_framer.cc1380
-rw-r--r--chromium/net/third_party/quiche/src/quiche/spdy/core/spdy_framer.h383
-rw-r--r--chromium/net/third_party/quiche/src/quiche/spdy/core/spdy_framer_test.cc5085
-rw-r--r--chromium/net/third_party/quiche/src/quiche/spdy/core/spdy_header_block.cc317
-rw-r--r--chromium/net/third_party/quiche/src/quiche/spdy/core/spdy_header_block.h295
-rw-r--r--chromium/net/third_party/quiche/src/quiche/spdy/core/spdy_header_block_test.cc279
-rw-r--r--chromium/net/third_party/quiche/src/quiche/spdy/core/spdy_header_storage.cc60
-rw-r--r--chromium/net/third_party/quiche/src/quiche/spdy/core/spdy_header_storage.h59
-rw-r--r--chromium/net/third_party/quiche/src/quiche/spdy/core/spdy_header_storage_test.cc35
-rw-r--r--chromium/net/third_party/quiche/src/quiche/spdy/core/spdy_headers_handler_interface.h39
-rw-r--r--chromium/net/third_party/quiche/src/quiche/spdy/core/spdy_intrusive_list.h331
-rw-r--r--chromium/net/third_party/quiche/src/quiche/spdy/core/spdy_intrusive_list_test.cc427
-rw-r--r--chromium/net/third_party/quiche/src/quiche/spdy/core/spdy_no_op_visitor.cc27
-rw-r--r--chromium/net/third_party/quiche/src/quiche/spdy/core/spdy_no_op_visitor.h96
-rw-r--r--chromium/net/third_party/quiche/src/quiche/spdy/core/spdy_pinnable_buffer_piece.cc36
-rw-r--r--chromium/net/third_party/quiche/src/quiche/spdy/core/spdy_pinnable_buffer_piece.h53
-rw-r--r--chromium/net/third_party/quiche/src/quiche/spdy/core/spdy_pinnable_buffer_piece_test.cc80
-rw-r--r--chromium/net/third_party/quiche/src/quiche/spdy/core/spdy_prefixed_buffer_reader.cc84
-rw-r--r--chromium/net/third_party/quiche/src/quiche/spdy/core/spdy_prefixed_buffer_reader.h45
-rw-r--r--chromium/net/third_party/quiche/src/quiche/spdy/core/spdy_prefixed_buffer_reader_test.cc131
-rw-r--r--chromium/net/third_party/quiche/src/quiche/spdy/core/spdy_protocol.cc643
-rw-r--r--chromium/net/third_party/quiche/src/quiche/spdy/core/spdy_protocol.h1138
-rw-r--r--chromium/net/third_party/quiche/src/quiche/spdy/core/spdy_protocol_test.cc275
-rw-r--r--chromium/net/third_party/quiche/src/quiche/spdy/core/spdy_protocol_test_utils.cc155
-rw-r--r--chromium/net/third_party/quiche/src/quiche/spdy/core/spdy_protocol_test_utils.h149
-rw-r--r--chromium/net/third_party/quiche/src/quiche/spdy/core/spdy_simple_arena.cc106
-rw-r--r--chromium/net/third_party/quiche/src/quiche/spdy/core/spdy_simple_arena.h77
-rw-r--r--chromium/net/third_party/quiche/src/quiche/spdy/core/spdy_simple_arena_test.cc141
-rw-r--r--chromium/net/third_party/quiche/src/quiche/spdy/core/spdy_test_utils.cc104
-rw-r--r--chromium/net/third_party/quiche/src/quiche/spdy/core/spdy_test_utils.h43
-rw-r--r--chromium/net/third_party/quiche/src/quiche/spdy/core/zero_copy_output_buffer.h30
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, &regular_headers);
+ } else if (!header.first.empty() &&
+ header.first[0] == kPseudoHeaderPrefix) {
+ DecomposeRepresentation(header, &pseudo_headers);
+ } else {
+ DecomposeRepresentation(header, &regular_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, &regular_headers_);
+ } else if (!header.first.empty() &&
+ header.first[0] == kPseudoHeaderPrefix) {
+ DecomposeRepresentation(header, &pseudo_headers_);
+ } else {
+ DecomposeRepresentation(header, &regular_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, &regular_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_