summaryrefslogtreecommitdiff
path: root/chromium/net/third_party/quiche
diff options
context:
space:
mode:
authorAllan Sandfeld Jensen <allan.jensen@qt.io>2019-02-13 16:23:34 +0100
committerAllan Sandfeld Jensen <allan.jensen@qt.io>2019-02-14 10:37:21 +0000
commit38a9a29f4f9436cace7f0e7abf9c586057df8a4e (patch)
treec4e8c458dc595bc0ddb435708fa2229edfd00bd4 /chromium/net/third_party/quiche
parente684a3455bcc29a6e3e66a004e352dea4e1141e7 (diff)
downloadqtwebengine-chromium-38a9a29f4f9436cace7f0e7abf9c586057df8a4e.tar.gz
BASELINE: Update Chromium to 73.0.3683.37
Change-Id: I08c9af2948b645f671e5d933aca1f7a90ea372f2 Reviewed-by: Michael BrĂ¼ning <michael.bruning@qt.io>
Diffstat (limited to 'chromium/net/third_party/quiche')
-rw-r--r--chromium/net/third_party/quiche/src/CONTRIBUTING.md28
-rw-r--r--chromium/net/third_party/quiche/src/LICENSE27
-rw-r--r--chromium/net/third_party/quiche/src/README.md9
-rw-r--r--chromium/net/third_party/quiche/src/http2/decoder/decode_buffer.cc93
-rw-r--r--chromium/net/third_party/quiche/src/http2/decoder/decode_buffer.h166
-rw-r--r--chromium/net/third_party/quiche/src/http2/decoder/decode_buffer_test.cc203
-rw-r--r--chromium/net/third_party/quiche/src/http2/decoder/decode_http2_structures.cc111
-rw-r--r--chromium/net/third_party/quiche/src/http2/decoder/decode_http2_structures.h34
-rw-r--r--chromium/net/third_party/quiche/src/http2/decoder/decode_http2_structures_test.cc461
-rw-r--r--chromium/net/third_party/quiche/src/http2/decoder/decode_status.cc28
-rw-r--r--chromium/net/third_party/quiche/src/http2/decoder/decode_status.h33
-rw-r--r--chromium/net/third_party/quiche/src/http2/decoder/frame_decoder_state.cc81
-rw-r--r--chromium/net/third_party/quiche/src/http2/decoder/frame_decoder_state.h250
-rw-r--r--chromium/net/third_party/quiche/src/http2/decoder/frame_decoder_state_test_util.cc34
-rw-r--r--chromium/net/third_party/quiche/src/http2/decoder/frame_decoder_state_test_util.h36
-rw-r--r--chromium/net/third_party/quiche/src/http2/decoder/http2_frame_decoder.cc433
-rw-r--r--chromium/net/third_party/quiche/src/http2/decoder/http2_frame_decoder.h205
-rw-r--r--chromium/net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener.cc14
-rw-r--r--chromium/net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener.h357
-rw-r--r--chromium/net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener_test_util.cc488
-rw-r--r--chromium/net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener_test_util.h143
-rw-r--r--chromium/net/third_party/quiche/src/http2/decoder/http2_frame_decoder_test.cc929
-rw-r--r--chromium/net/third_party/quiche/src/http2/decoder/http2_structure_decoder.cc90
-rw-r--r--chromium/net/third_party/quiche/src/http2/decoder/http2_structure_decoder.h131
-rw-r--r--chromium/net/third_party/quiche/src/http2/decoder/http2_structure_decoder_test.cc541
-rw-r--r--chromium/net/third_party/quiche/src/http2/decoder/http2_structure_decoder_test_util.cc22
-rw-r--r--chromium/net/third_party/quiche/src/http2/decoder/http2_structure_decoder_test_util.h24
-rw-r--r--chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/altsvc_payload_decoder.cc148
-rw-r--r--chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/altsvc_payload_decoder.h64
-rw-r--r--chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/altsvc_payload_decoder_test.cc121
-rw-r--r--chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/continuation_payload_decoder.cc58
-rw-r--r--chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/continuation_payload_decoder.h31
-rw-r--r--chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/continuation_payload_decoder_test.cc85
-rw-r--r--chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/data_payload_decoder.cc127
-rw-r--r--chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/data_payload_decoder.h54
-rw-r--r--chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/data_payload_decoder_test.cc113
-rw-r--r--chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/goaway_payload_decoder.cc121
-rw-r--r--chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/goaway_payload_decoder.h66
-rw-r--r--chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/goaway_payload_decoder_test.cc107
-rw-r--r--chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/headers_payload_decoder.cc175
-rw-r--r--chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/headers_payload_decoder.h67
-rw-r--r--chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/headers_payload_decoder_test.cc158
-rw-r--r--chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/payload_decoder_base_test_util.cc97
-rw-r--r--chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/payload_decoder_base_test_util.h455
-rw-r--r--chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/ping_payload_decoder.cc89
-rw-r--r--chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/ping_payload_decoder.h43
-rw-r--r--chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/ping_payload_decoder_test.cc110
-rw-r--r--chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/priority_payload_decoder.cc64
-rw-r--r--chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/priority_payload_decoder.h44
-rw-r--r--chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/priority_payload_decoder_test.cc90
-rw-r--r--chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/push_promise_payload_decoder.cc172
-rw-r--r--chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/push_promise_payload_decoder.h66
-rw-r--r--chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/push_promise_payload_decoder_test.cc137
-rw-r--r--chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/rst_stream_payload_decoder.cc66
-rw-r--r--chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/rst_stream_payload_decoder.h42
-rw-r--r--chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/rst_stream_payload_decoder_test.cc92
-rw-r--r--chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/settings_payload_decoder.cc97
-rw-r--r--chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/settings_payload_decoder.h54
-rw-r--r--chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/settings_payload_decoder_test.cc160
-rw-r--r--chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/unknown_payload_decoder.cc55
-rw-r--r--chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/unknown_payload_decoder.h33
-rw-r--r--chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/unknown_payload_decoder_test.cc100
-rw-r--r--chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/window_update_payload_decoder.cc82
-rw-r--r--chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/window_update_payload_decoder.h42
-rw-r--r--chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/window_update_payload_decoder_test.cc95
-rw-r--r--chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_block_collector.cc151
-rw-r--r--chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_block_collector.h129
-rw-r--r--chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_block_decoder.cc62
-rw-r--r--chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_block_decoder.h64
-rw-r--r--chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_block_decoder_test.cc294
-rw-r--r--chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder.cc122
-rw-r--r--chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder.h123
-rw-r--r--chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_listener.cc30
-rw-r--r--chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_listener.h66
-rw-r--r--chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_state.cc218
-rw-r--r--chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_state.h129
-rw-r--r--chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_state_test.cc539
-rw-r--r--chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_string_buffer.cc235
-rw-r--r--chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_string_buffer.h102
-rw-r--r--chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_string_buffer_test.cc251
-rw-r--r--chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_tables.cc153
-rw-r--r--chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_tables.h197
-rw-r--r--chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_tables_test.cc266
-rw-r--r--chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_test.cc1219
-rw-r--r--chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_collector.cc301
-rw-r--r--chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_collector.h155
-rw-r--r--chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_decoder.cc267
-rw-r--r--chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_decoder.h85
-rw-r--r--chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_decoder_listener.cc81
-rw-r--r--chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_decoder_listener.h110
-rw-r--r--chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_decoder_test.cc211
-rw-r--r--chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_type_decoder.cc358
-rw-r--r--chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_type_decoder.h57
-rw-r--r--chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_type_decoder_test.cc87
-rw-r--r--chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_string_collector.cc123
-rw-r--r--chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_string_collector.h63
-rw-r--r--chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_string_decoder.cc35
-rw-r--r--chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_string_decoder.h209
-rw-r--r--chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_string_decoder_listener.cc36
-rw-r--r--chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_string_decoder_listener.h62
-rw-r--r--chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_string_decoder_test.cc155
-rw-r--r--chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_whole_entry_buffer.cc139
-rw-r--r--chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_whole_entry_buffer.h104
-rw-r--r--chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_whole_entry_buffer_test.cc206
-rw-r--r--chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_whole_entry_listener.cc33
-rw-r--r--chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_whole_entry_listener.h80
-rw-r--r--chromium/net/third_party/quiche/src/http2/hpack/hpack_static_table_entries.inc65
-rw-r--r--chromium/net/third_party/quiche/src/http2/hpack/hpack_string.cc72
-rw-r--r--chromium/net/third_party/quiche/src/http2/hpack/hpack_string.h75
-rw-r--r--chromium/net/third_party/quiche/src/http2/hpack/hpack_string_test.cc149
-rw-r--r--chromium/net/third_party/quiche/src/http2/hpack/http2_hpack_constants.cc31
-rw-r--r--chromium/net/third_party/quiche/src/http2/hpack/http2_hpack_constants.h63
-rw-r--r--chromium/net/third_party/quiche/src/http2/hpack/http2_hpack_constants_test.cc72
-rw-r--r--chromium/net/third_party/quiche/src/http2/hpack/huffman/hpack_huffman_decoder.cc487
-rw-r--r--chromium/net/third_party/quiche/src/http2/hpack/huffman/hpack_huffman_decoder.h134
-rw-r--r--chromium/net/third_party/quiche/src/http2/hpack/huffman/hpack_huffman_decoder_test.cc245
-rw-r--r--chromium/net/third_party/quiche/src/http2/hpack/huffman/hpack_huffman_encoder.cc110
-rw-r--r--chromium/net/third_party/quiche/src/http2/hpack/huffman/hpack_huffman_encoder.h40
-rw-r--r--chromium/net/third_party/quiche/src/http2/hpack/huffman/hpack_huffman_encoder_test.cc97
-rw-r--r--chromium/net/third_party/quiche/src/http2/hpack/huffman/hpack_huffman_transcoder_test.cc184
-rw-r--r--chromium/net/third_party/quiche/src/http2/hpack/huffman/huffman_spec_tables.cc578
-rw-r--r--chromium/net/third_party/quiche/src/http2/hpack/huffman/huffman_spec_tables.h25
-rw-r--r--chromium/net/third_party/quiche/src/http2/hpack/tools/hpack_block_builder.cc80
-rw-r--r--chromium/net/third_party/quiche/src/http2/hpack/tools/hpack_block_builder.h96
-rw-r--r--chromium/net/third_party/quiche/src/http2/hpack/tools/hpack_block_builder_test.cc169
-rw-r--r--chromium/net/third_party/quiche/src/http2/hpack/tools/hpack_example.cc58
-rw-r--r--chromium/net/third_party/quiche/src/http2/hpack/tools/hpack_example.h31
-rw-r--r--chromium/net/third_party/quiche/src/http2/hpack/varint/hpack_varint_decoder.cc172
-rw-r--r--chromium/net/third_party/quiche/src/http2/hpack/varint/hpack_varint_decoder.h144
-rw-r--r--chromium/net/third_party/quiche/src/http2/hpack/varint/hpack_varint_decoder_test.cc486
-rw-r--r--chromium/net/third_party/quiche/src/http2/hpack/varint/hpack_varint_encoder.cc65
-rw-r--r--chromium/net/third_party/quiche/src/http2/hpack/varint/hpack_varint_encoder.h51
-rw-r--r--chromium/net/third_party/quiche/src/http2/hpack/varint/hpack_varint_encoder_test.cc178
-rw-r--r--chromium/net/third_party/quiche/src/http2/hpack/varint/hpack_varint_round_trip_test.cc513
-rw-r--r--chromium/net/third_party/quiche/src/http2/http2_constants.cc150
-rw-r--r--chromium/net/third_party/quiche/src/http2/http2_constants.h261
-rw-r--r--chromium/net/third_party/quiche/src/http2/http2_constants_test.cc271
-rw-r--r--chromium/net/third_party/quiche/src/http2/http2_constants_test_util.cc84
-rw-r--r--chromium/net/third_party/quiche/src/http2/http2_constants_test_util.h34
-rw-r--r--chromium/net/third_party/quiche/src/http2/http2_structures.cc132
-rw-r--r--chromium/net/third_party/quiche/src/http2/http2_structures.h325
-rw-r--r--chromium/net/third_party/quiche/src/http2/http2_structures_test.cc537
-rw-r--r--chromium/net/third_party/quiche/src/http2/http2_structures_test_util.cc109
-rw-r--r--chromium/net/third_party/quiche/src/http2/http2_structures_test_util.h59
-rw-r--r--chromium/net/third_party/quiche/src/http2/platform/api/http2_arraysize.h12
-rw-r--r--chromium/net/third_party/quiche/src/http2/platform/api/http2_bug_tracker.h15
-rw-r--r--chromium/net/third_party/quiche/src/http2/platform/api/http2_containers.h17
-rw-r--r--chromium/net/third_party/quiche/src/http2/platform/api/http2_estimate_memory_usage.h21
-rw-r--r--chromium/net/third_party/quiche/src/http2/platform/api/http2_export.h10
-rw-r--r--chromium/net/third_party/quiche/src/http2/platform/api/http2_flag_utils.h12
-rw-r--r--chromium/net/third_party/quiche/src/http2/platform/api/http2_flags.h14
-rw-r--r--chromium/net/third_party/quiche/src/http2/platform/api/http2_macros.h10
-rw-r--r--chromium/net/third_party/quiche/src/http2/platform/api/http2_mock_log.h18
-rw-r--r--chromium/net/third_party/quiche/src/http2/platform/api/http2_optional.h19
-rw-r--r--chromium/net/third_party/quiche/src/http2/platform/api/http2_ptr_util.h22
-rw-r--r--chromium/net/third_party/quiche/src/http2/platform/api/http2_reconstruct_object.h34
-rw-r--r--chromium/net/third_party/quiche/src/http2/platform/api/http2_string.h16
-rw-r--r--chromium/net/third_party/quiche/src/http2/platform/api/http2_string_piece.h16
-rw-r--r--chromium/net/third_party/quiche/src/http2/platform/api/http2_string_utils.h56
-rw-r--r--chromium/net/third_party/quiche/src/http2/platform/api/http2_string_utils_test.cc178
-rw-r--r--chromium/net/third_party/quiche/src/http2/platform/api/http2_test_helpers.h16
-rw-r--r--chromium/net/third_party/quiche/src/http2/test_tools/frame_parts.cc526
-rw-r--r--chromium/net/third_party/quiche/src/http2/test_tools/frame_parts.h249
-rw-r--r--chromium/net/third_party/quiche/src/http2/test_tools/frame_parts_collector.cc113
-rw-r--r--chromium/net/third_party/quiche/src/http2/test_tools/frame_parts_collector.h111
-rw-r--r--chromium/net/third_party/quiche/src/http2/test_tools/frame_parts_collector_listener.cc230
-rw-r--r--chromium/net/third_party/quiche/src/http2/test_tools/frame_parts_collector_listener.h85
-rw-r--r--chromium/net/third_party/quiche/src/http2/test_tools/http2_random.cc72
-rw-r--r--chromium/net/third_party/quiche/src/http2/test_tools/http2_random.h88
-rw-r--r--chromium/net/third_party/quiche/src/http2/test_tools/http2_random_test.cc94
-rw-r--r--chromium/net/third_party/quiche/src/http2/tools/http2_frame_builder.cc181
-rw-r--r--chromium/net/third_party/quiche/src/http2/tools/http2_frame_builder.h101
-rw-r--r--chromium/net/third_party/quiche/src/http2/tools/random_decoder_test.cc167
-rw-r--r--chromium/net/third_party/quiche/src/http2/tools/random_decoder_test.h258
-rw-r--r--chromium/net/third_party/quiche/src/http2/tools/random_util.cc39
-rw-r--r--chromium/net/third_party/quiche/src/http2/tools/random_util.h29
-rw-r--r--chromium/net/third_party/quiche/src/spdy/core/array_output_buffer.cc23
-rw-r--r--chromium/net/third_party/quiche/src/spdy/core/array_output_buffer.h45
-rw-r--r--chromium/net/third_party/quiche/src/spdy/core/array_output_buffer_test.cc50
-rw-r--r--chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_constants.cc386
-rw-r--r--chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_constants.h95
-rw-r--r--chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_decoder_adapter.cc201
-rw-r--r--chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_decoder_adapter.h159
-rw-r--r--chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_decoder_adapter_test.cc1097
-rw-r--r--chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_encoder.cc365
-rw-r--r--chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_encoder.h152
-rw-r--r--chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_encoder_test.cc586
-rw-r--r--chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_entry.cc89
-rw-r--r--chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_entry.h110
-rw-r--r--chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_entry_test.cc122
-rw-r--r--chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_header_table.cc274
-rw-r--r--chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_header_table.h180
-rw-r--r--chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_header_table_test.cc446
-rw-r--r--chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_huffman_table.cc151
-rw-r--r--chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_huffman_table.h76
-rw-r--r--chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_huffman_table_test.cc309
-rw-r--r--chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_output_stream.cc97
-rw-r--r--chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_output_stream.h76
-rw-r--r--chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_output_stream_test.cc276
-rw-r--r--chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_round_trip_test.cc227
-rw-r--r--chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_static_table.cc50
-rw-r--r--chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_static_table.h54
-rw-r--r--chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_static_table_test.cc60
-rw-r--r--chromium/net/third_party/quiche/src/spdy/core/http2_frame_decoder_adapter.cc1022
-rw-r--r--chromium/net/third_party/quiche/src/spdy/core/http2_frame_decoder_adapter.h514
-rw-r--r--chromium/net/third_party/quiche/src/spdy/core/mock_spdy_framer_visitor.cc19
-rw-r--r--chromium/net/third_party/quiche/src/spdy/core/mock_spdy_framer_visitor.h103
-rw-r--r--chromium/net/third_party/quiche/src/spdy/core/priority_write_scheduler.h322
-rw-r--r--chromium/net/third_party/quiche/src/spdy/core/priority_write_scheduler_test.cc371
-rw-r--r--chromium/net/third_party/quiche/src/spdy/core/spdy_alt_svc_wire_format.cc390
-rw-r--r--chromium/net/third_party/quiche/src/spdy/core/spdy_alt_svc_wire_format.h88
-rw-r--r--chromium/net/third_party/quiche/src/spdy/core/spdy_alt_svc_wire_format_test.cc573
-rw-r--r--chromium/net/third_party/quiche/src/spdy/core/spdy_bitmasks.h18
-rw-r--r--chromium/net/third_party/quiche/src/spdy/core/spdy_deframer_visitor.cc1028
-rw-r--r--chromium/net/third_party/quiche/src/spdy/core/spdy_deframer_visitor.h250
-rw-r--r--chromium/net/third_party/quiche/src/spdy/core/spdy_deframer_visitor_test.cc247
-rw-r--r--chromium/net/third_party/quiche/src/spdy/core/spdy_frame_builder.cc183
-rw-r--r--chromium/net/third_party/quiche/src/spdy/core/spdy_frame_builder.h142
-rw-r--r--chromium/net/third_party/quiche/src/spdy/core/spdy_frame_builder_test.cc68
-rw-r--r--chromium/net/third_party/quiche/src/spdy/core/spdy_frame_reader.cc200
-rw-r--r--chromium/net/third_party/quiche/src/spdy/core/spdy_frame_reader.h129
-rw-r--r--chromium/net/third_party/quiche/src/spdy/core/spdy_frame_reader_test.cc247
-rw-r--r--chromium/net/third_party/quiche/src/spdy/core/spdy_framer.cc1295
-rw-r--r--chromium/net/third_party/quiche/src/spdy/core/spdy_framer.h365
-rw-r--r--chromium/net/third_party/quiche/src/spdy/core/spdy_framer_test.cc4833
-rw-r--r--chromium/net/third_party/quiche/src/spdy/core/spdy_header_block.cc401
-rw-r--r--chromium/net/third_party/quiche/src/spdy/core/spdy_header_block.h254
-rw-r--r--chromium/net/third_party/quiche/src/spdy/core/spdy_header_block_test.cc252
-rw-r--r--chromium/net/third_party/quiche/src/spdy/core/spdy_headers_handler_interface.h39
-rw-r--r--chromium/net/third_party/quiche/src/spdy/core/spdy_no_op_visitor.cc29
-rw-r--r--chromium/net/third_party/quiche/src/spdy/core/spdy_no_op_visitor.h89
-rw-r--r--chromium/net/third_party/quiche/src/spdy/core/spdy_pinnable_buffer_piece.cc36
-rw-r--r--chromium/net/third_party/quiche/src/spdy/core/spdy_pinnable_buffer_piece.h53
-rw-r--r--chromium/net/third_party/quiche/src/spdy/core/spdy_pinnable_buffer_piece_test.cc80
-rw-r--r--chromium/net/third_party/quiche/src/spdy/core/spdy_prefixed_buffer_reader.cc84
-rw-r--r--chromium/net/third_party/quiche/src/spdy/core/spdy_prefixed_buffer_reader.h45
-rw-r--r--chromium/net/third_party/quiche/src/spdy/core/spdy_prefixed_buffer_reader_test.cc131
-rw-r--r--chromium/net/third_party/quiche/src/spdy/core/spdy_protocol.cc571
-rw-r--r--chromium/net/third_party/quiche/src/spdy/core/spdy_protocol.h1066
-rw-r--r--chromium/net/third_party/quiche/src/spdy/core/spdy_protocol_test.cc270
-rw-r--r--chromium/net/third_party/quiche/src/spdy/core/spdy_protocol_test_utils.cc155
-rw-r--r--chromium/net/third_party/quiche/src/spdy/core/spdy_protocol_test_utils.h149
-rw-r--r--chromium/net/third_party/quiche/src/spdy/core/spdy_test_utils.cc119
-rw-r--r--chromium/net/third_party/quiche/src/spdy/core/spdy_test_utils.h74
-rw-r--r--chromium/net/third_party/quiche/src/spdy/core/write_scheduler.h156
-rw-r--r--chromium/net/third_party/quiche/src/spdy/core/zero_copy_output_buffer.h30
-rw-r--r--chromium/net/third_party/quiche/src/spdy/platform/api/spdy_arraysize.h12
-rw-r--r--chromium/net/third_party/quiche/src/spdy/platform/api/spdy_bug_tracker.h15
-rw-r--r--chromium/net/third_party/quiche/src/spdy/platform/api/spdy_containers.h42
-rw-r--r--chromium/net/third_party/quiche/src/spdy/platform/api/spdy_endianness_util.h44
-rw-r--r--chromium/net/third_party/quiche/src/spdy/platform/api/spdy_estimate_memory_usage.h21
-rw-r--r--chromium/net/third_party/quiche/src/spdy/platform/api/spdy_export.h10
-rw-r--r--chromium/net/third_party/quiche/src/spdy/platform/api/spdy_flags.h13
-rw-r--r--chromium/net/third_party/quiche/src/spdy/platform/api/spdy_macros.h15
-rw-r--r--chromium/net/third_party/quiche/src/spdy/platform/api/spdy_mem_slice.h54
-rw-r--r--chromium/net/third_party/quiche/src/spdy/platform/api/spdy_mem_slice_test.cc47
-rw-r--r--chromium/net/third_party/quiche/src/spdy/platform/api/spdy_ptr_util.h27
-rw-r--r--chromium/net/third_party/quiche/src/spdy/platform/api/spdy_string.h16
-rw-r--r--chromium/net/third_party/quiche/src/spdy/platform/api/spdy_string_piece.h16
-rw-r--r--chromium/net/third_party/quiche/src/spdy/platform/api/spdy_string_utils.h58
-rw-r--r--chromium/net/third_party/quiche/src/spdy/platform/api/spdy_string_utils_test.cc242
-rw-r--r--chromium/net/third_party/quiche/src/spdy/platform/api/spdy_test_helpers.h12
-rw-r--r--chromium/net/third_party/quiche/src/spdy/platform/api/spdy_test_utils_prod.h12
-rw-r--r--chromium/net/third_party/quiche/src/spdy/platform/api/spdy_unsafe_arena.h16
264 files changed, 48237 insertions, 0 deletions
diff --git a/chromium/net/third_party/quiche/src/CONTRIBUTING.md b/chromium/net/third_party/quiche/src/CONTRIBUTING.md
new file mode 100644
index 00000000000..3a93363a0f9
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/CONTRIBUTING.md
@@ -0,0 +1,28 @@
+# How to Contribute
+
+We'd love to accept your patches and contributions to this project. There are
+just a few small guidelines you need to follow.
+
+## Contributor License Agreement
+
+Contributions to this project must be accompanied by a Contributor License
+Agreement. You (or your employer) retain the copyright to your contribution;
+this simply gives us permission to use and redistribute your contributions as
+part of the project. Head over to <https://cla.developers.google.com/> to see
+your current agreements on file or to sign a new one.
+
+You generally only need to submit a CLA once, so if you've already submitted one
+(even if it was for a different project), you probably don't need to do it
+again.
+
+## Code reviews
+
+All submissions, including submissions by project members, require review. We
+use Gerrit pull requests for this purpose.
+
+TODO: write up the contributing guidelines.
+
+## Community Guidelines
+
+This project follows [Google's Open Source Community
+Guidelines](https://opensource.google.com/conduct/).
diff --git a/chromium/net/third_party/quiche/src/LICENSE b/chromium/net/third_party/quiche/src/LICENSE
new file mode 100644
index 00000000000..a32e00ce6be
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/LICENSE
@@ -0,0 +1,27 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/chromium/net/third_party/quiche/src/README.md b/chromium/net/third_party/quiche/src/README.md
new file mode 100644
index 00000000000..4e2037083a1
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/README.md
@@ -0,0 +1,9 @@
+# QUICHE
+
+QUICHE (QUIC, Http/2, Etc) is Google's implementation of QUIC and related
+protocols. It powers Chromium as well as Google's QUIC servers and some other
+projects.
+
+The code is currently in process of being moved from
+https://cs.chromium.org/chromium/src/net/third_party/ into this repository.
+Please excuse our appearance while we're under construction.
diff --git a/chromium/net/third_party/quiche/src/http2/decoder/decode_buffer.cc b/chromium/net/third_party/quiche/src/http2/decoder/decode_buffer.cc
new file mode 100644
index 00000000000..b24420a1b0e
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/decoder/decode_buffer.cc
@@ -0,0 +1,93 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h"
+
+namespace http2 {
+
+uint8_t DecodeBuffer::DecodeUInt8() {
+ return static_cast<uint8_t>(DecodeChar());
+}
+
+uint16_t DecodeBuffer::DecodeUInt16() {
+ DCHECK_LE(2u, Remaining());
+ const uint8_t b1 = DecodeUInt8();
+ const uint8_t b2 = DecodeUInt8();
+ // Note that chars are automatically promoted to ints during arithmetic,
+ // so the b1 << 8 doesn't end up as zero before being or-ed with b2.
+ // And the left-shift operator has higher precedence than the or operator.
+ return b1 << 8 | b2;
+}
+
+uint32_t DecodeBuffer::DecodeUInt24() {
+ DCHECK_LE(3u, Remaining());
+ const uint8_t b1 = DecodeUInt8();
+ const uint8_t b2 = DecodeUInt8();
+ const uint8_t b3 = DecodeUInt8();
+ return b1 << 16 | b2 << 8 | b3;
+}
+
+uint32_t DecodeBuffer::DecodeUInt31() {
+ DCHECK_LE(4u, Remaining());
+ const uint8_t b1 = DecodeUInt8() & 0x7f; // Mask out the high order bit.
+ const uint8_t b2 = DecodeUInt8();
+ const uint8_t b3 = DecodeUInt8();
+ const uint8_t b4 = DecodeUInt8();
+ return b1 << 24 | b2 << 16 | b3 << 8 | b4;
+}
+
+uint32_t DecodeBuffer::DecodeUInt32() {
+ DCHECK_LE(4u, Remaining());
+ const uint8_t b1 = DecodeUInt8();
+ const uint8_t b2 = DecodeUInt8();
+ const uint8_t b3 = DecodeUInt8();
+ const uint8_t b4 = DecodeUInt8();
+ return b1 << 24 | b2 << 16 | b3 << 8 | b4;
+}
+
+#ifndef NDEBUG
+void DecodeBuffer::set_subset_of_base(DecodeBuffer* base,
+ const DecodeBufferSubset* subset) {
+ DCHECK_EQ(this, subset);
+ base->set_subset(subset);
+}
+void DecodeBuffer::clear_subset_of_base(DecodeBuffer* base,
+ const DecodeBufferSubset* subset) {
+ DCHECK_EQ(this, subset);
+ base->clear_subset(subset);
+}
+void DecodeBuffer::set_subset(const DecodeBufferSubset* subset) {
+ DCHECK(subset != nullptr);
+ DCHECK_EQ(subset_, nullptr) << "There is already a subset";
+ subset_ = subset;
+}
+void DecodeBuffer::clear_subset(const DecodeBufferSubset* subset) {
+ DCHECK(subset != nullptr);
+ DCHECK_EQ(subset_, subset);
+ subset_ = nullptr;
+}
+void DecodeBufferSubset::DebugSetup() {
+ start_base_offset_ = base_buffer_->Offset();
+ max_base_offset_ = start_base_offset_ + FullSize();
+ DCHECK_LE(max_base_offset_, base_buffer_->FullSize());
+
+ // Ensure that there is only one DecodeBufferSubset at a time for a base.
+ set_subset_of_base(base_buffer_, this);
+}
+void DecodeBufferSubset::DebugTearDown() {
+ // Ensure that the base hasn't been modified.
+ DCHECK_EQ(start_base_offset_, base_buffer_->Offset())
+ << "The base buffer was modified";
+
+ // Ensure that we haven't gone beyond the maximum allowed offset.
+ size_t offset = Offset();
+ DCHECK_LE(offset, FullSize());
+ DCHECK_LE(start_base_offset_ + offset, max_base_offset_);
+ DCHECK_LE(max_base_offset_, base_buffer_->FullSize());
+
+ clear_subset_of_base(base_buffer_, this);
+}
+#endif
+
+} // namespace http2
diff --git a/chromium/net/third_party/quiche/src/http2/decoder/decode_buffer.h b/chromium/net/third_party/quiche/src/http2/decoder/decode_buffer.h
new file mode 100644
index 00000000000..3b213c2a861
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/decoder/decode_buffer.h
@@ -0,0 +1,166 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_HTTP2_DECODER_DECODE_BUFFER_H_
+#define QUICHE_HTTP2_DECODER_DECODE_BUFFER_H_
+
+// DecodeBuffer provides primitives for decoding various integer types found in
+// HTTP/2 frames. It wraps a byte array from which we can read and decode
+// serialized HTTP/2 frames, or parts thereof. DecodeBuffer is intended only for
+// stack allocation, where the caller is typically going to use the DecodeBuffer
+// instance as part of decoding the entire buffer before returning to its own
+// caller.
+
+#include <stddef.h>
+
+#include <algorithm>
+#include <cstdint>
+
+#include "base/logging.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_export.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string_piece.h"
+
+namespace http2 {
+class DecodeBufferSubset;
+
+class HTTP2_EXPORT_PRIVATE DecodeBuffer {
+ public:
+ DecodeBuffer(const char* buffer, size_t len)
+ : buffer_(buffer), cursor_(buffer), beyond_(buffer + len) {
+ DCHECK(buffer != nullptr);
+ // We assume the decode buffers will typically be modest in size (i.e. often
+ // a few KB, perhaps as high as 100KB). Let's make sure during testing that
+ // we don't go very high, with 32MB selected rather arbitrarily.
+ const size_t kMaxDecodeBufferLength = 1 << 25;
+ DCHECK_LE(len, kMaxDecodeBufferLength);
+ }
+ explicit DecodeBuffer(Http2StringPiece s)
+ : DecodeBuffer(s.data(), s.size()) {}
+ // Constructor for character arrays, typically in tests. For example:
+ // const char input[] = { 0x11 };
+ // DecodeBuffer b(input);
+ template <size_t N>
+ explicit DecodeBuffer(const char (&buf)[N]) : DecodeBuffer(buf, N) {}
+
+ DecodeBuffer(const DecodeBuffer&) = delete;
+ DecodeBuffer operator=(const DecodeBuffer&) = delete;
+
+ bool Empty() const { return cursor_ >= beyond_; }
+ bool HasData() const { return cursor_ < beyond_; }
+ size_t Remaining() const {
+ DCHECK_LE(cursor_, beyond_);
+ return beyond_ - cursor_;
+ }
+ size_t Offset() const { return cursor_ - buffer_; }
+ size_t FullSize() const { return beyond_ - buffer_; }
+
+ // Returns the minimum of the number of bytes remaining in this DecodeBuffer
+ // and |length|, in support of determining how much of some structure/payload
+ // is in this DecodeBuffer.
+ size_t MinLengthRemaining(size_t length) const {
+ return std::min(length, Remaining());
+ }
+
+ // For string decoding, returns a pointer to the next byte/char to be decoded.
+ const char* cursor() const { return cursor_; }
+ // Advances the cursor (pointer to the next byte/char to be decoded).
+ void AdvanceCursor(size_t amount) {
+ DCHECK_LE(amount, Remaining()); // Need at least that much remaining.
+ DCHECK_EQ(subset_, nullptr) << "Access via subset only when present.";
+ cursor_ += amount;
+ }
+
+ // Only call methods starting "Decode" when there is enough input remaining.
+ char DecodeChar() {
+ DCHECK_LE(1u, Remaining()); // Need at least one byte remaining.
+ DCHECK_EQ(subset_, nullptr) << "Access via subset only when present.";
+ return *cursor_++;
+ }
+
+ uint8_t DecodeUInt8();
+ uint16_t DecodeUInt16();
+ uint32_t DecodeUInt24();
+
+ // For 31-bit unsigned integers, where the 32nd bit is reserved for future
+ // use (i.e. the high-bit of the first byte of the encoding); examples:
+ // the Stream Id in a frame header or the Window Size Increment in a
+ // WINDOW_UPDATE frame.
+ uint32_t DecodeUInt31();
+
+ uint32_t DecodeUInt32();
+
+ protected:
+#ifndef NDEBUG
+ // These are part of validating during tests that there is at most one
+ // DecodeBufferSubset instance at a time for any DecodeBuffer instance.
+ void set_subset_of_base(DecodeBuffer* base, const DecodeBufferSubset* subset);
+ void clear_subset_of_base(DecodeBuffer* base,
+ const DecodeBufferSubset* subset);
+#endif
+
+ private:
+#ifndef NDEBUG
+ void set_subset(const DecodeBufferSubset* subset);
+ void clear_subset(const DecodeBufferSubset* subset);
+#endif
+
+ // Prevent heap allocation of DecodeBuffer.
+ static void* operator new(size_t s);
+ static void* operator new[](size_t s);
+ static void operator delete(void* p);
+ static void operator delete[](void* p);
+
+ const char* const buffer_;
+ const char* cursor_;
+ const char* const beyond_;
+ const DecodeBufferSubset* subset_ = nullptr; // Used for DCHECKs.
+};
+
+// DecodeBufferSubset is used when decoding a known sized chunk of data, which
+// starts at base->cursor(), and continues for subset_len, which may be
+// entirely in |base|, or may extend beyond it (hence the MinLengthRemaining
+// in the constructor).
+// There are two benefits to using DecodeBufferSubset: it ensures that the
+// cursor of |base| is advanced when the subset's destructor runs, and it
+// ensures that the consumer of the subset can't go beyond the subset which
+// it is intended to decode.
+// There must be only a single DecodeBufferSubset at a time for a base
+// DecodeBuffer, though they can be nested (i.e. a DecodeBufferSubset's
+// base may itself be a DecodeBufferSubset). This avoids the AdvanceCursor
+// being called erroneously.
+class HTTP2_EXPORT_PRIVATE DecodeBufferSubset : public DecodeBuffer {
+ public:
+ DecodeBufferSubset(DecodeBuffer* base, size_t subset_len)
+ : DecodeBuffer(base->cursor(), base->MinLengthRemaining(subset_len)),
+ base_buffer_(base) {
+#ifndef NDEBUG
+ DebugSetup();
+#endif
+ }
+
+ DecodeBufferSubset(const DecodeBufferSubset&) = delete;
+ DecodeBufferSubset operator=(const DecodeBufferSubset&) = delete;
+
+ ~DecodeBufferSubset() {
+ size_t offset = Offset();
+#ifndef NDEBUG
+ DebugTearDown();
+#endif
+ base_buffer_->AdvanceCursor(offset);
+ }
+
+ private:
+ DecodeBuffer* const base_buffer_;
+#ifndef NDEBUG
+ size_t start_base_offset_; // Used for DCHECKs.
+ size_t max_base_offset_; // Used for DCHECKs.
+
+ void DebugSetup();
+ void DebugTearDown();
+#endif
+};
+
+} // namespace http2
+
+#endif // QUICHE_HTTP2_DECODER_DECODE_BUFFER_H_
diff --git a/chromium/net/third_party/quiche/src/http2/decoder/decode_buffer_test.cc b/chromium/net/third_party/quiche/src/http2/decoder/decode_buffer_test.cc
new file mode 100644
index 00000000000..e8e3d3bf176
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/decoder/decode_buffer_test.cc
@@ -0,0 +1,203 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h"
+
+#include <functional>
+
+#include "base/logging.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/http2/test_tools/http2_random.h"
+
+namespace http2 {
+namespace test {
+namespace {
+
+enum class TestEnumClass32 {
+ kValue1 = 1,
+ kValue99 = 99,
+ kValue1M = 1000000,
+};
+
+enum class TestEnumClass8 {
+ kValue1 = 1,
+ kValue2 = 1,
+ kValue99 = 99,
+ kValue255 = 255,
+};
+
+enum TestEnum8 {
+ kMaskLo = 0x01,
+ kMaskHi = 0x80,
+};
+
+struct TestStruct {
+ uint8_t f1;
+ uint16_t f2;
+ uint32_t f3; // Decoded as a uint24
+ uint32_t f4;
+ uint32_t f5; // Decoded as if uint31
+ TestEnumClass32 f6;
+ TestEnumClass8 f7;
+ TestEnum8 f8;
+};
+
+class DecodeBufferTest : public ::testing::Test {
+ protected:
+ Http2Random random_;
+ uint32_t decode_offset_;
+};
+
+TEST_F(DecodeBufferTest, DecodesFixedInts) {
+ const char data[] = "\x01\x12\x23\x34\x45\x56\x67\x78\x89\x9a";
+ DecodeBuffer b1(data, strlen(data));
+ EXPECT_EQ(1, b1.DecodeUInt8());
+ EXPECT_EQ(0x1223u, b1.DecodeUInt16());
+ EXPECT_EQ(0x344556u, b1.DecodeUInt24());
+ EXPECT_EQ(0x6778899Au, b1.DecodeUInt32());
+}
+
+// Make sure that DecodeBuffer is not copying input, just pointing into
+// provided input buffer.
+TEST_F(DecodeBufferTest, HasNotCopiedInput) {
+ const char data[] = "ab";
+ DecodeBuffer b1(data, 2);
+
+ EXPECT_EQ(2u, b1.Remaining());
+ EXPECT_EQ(0u, b1.Offset());
+ EXPECT_FALSE(b1.Empty());
+ EXPECT_EQ(data, b1.cursor()); // cursor points to input buffer
+ EXPECT_TRUE(b1.HasData());
+
+ b1.AdvanceCursor(1);
+
+ EXPECT_EQ(1u, b1.Remaining());
+ EXPECT_EQ(1u, b1.Offset());
+ EXPECT_FALSE(b1.Empty());
+ EXPECT_EQ(&data[1], b1.cursor());
+ EXPECT_TRUE(b1.HasData());
+
+ b1.AdvanceCursor(1);
+
+ EXPECT_EQ(0u, b1.Remaining());
+ EXPECT_EQ(2u, b1.Offset());
+ EXPECT_TRUE(b1.Empty());
+ EXPECT_EQ(&data[2], b1.cursor());
+ EXPECT_FALSE(b1.HasData());
+
+ DecodeBuffer b2(data, 0);
+
+ EXPECT_EQ(0u, b2.Remaining());
+ EXPECT_EQ(0u, b2.Offset());
+ EXPECT_TRUE(b2.Empty());
+ EXPECT_EQ(data, b2.cursor());
+ EXPECT_FALSE(b2.HasData());
+}
+
+// DecodeBufferSubset can't go beyond the end of the base buffer.
+TEST_F(DecodeBufferTest, DecodeBufferSubsetLimited) {
+ const char data[] = "abc";
+ DecodeBuffer base(data, 3);
+ base.AdvanceCursor(1);
+ DecodeBufferSubset subset(&base, 100);
+ EXPECT_EQ(2u, subset.FullSize());
+}
+
+// DecodeBufferSubset advances the cursor of its base upon destruction.
+TEST_F(DecodeBufferTest, DecodeBufferSubsetAdvancesCursor) {
+ const char data[] = "abc";
+ const size_t size = sizeof(data) - 1;
+ EXPECT_EQ(3u, size);
+ DecodeBuffer base(data, size);
+ {
+ // First no change to the cursor.
+ DecodeBufferSubset subset(&base, size + 100);
+ EXPECT_EQ(size, subset.FullSize());
+ EXPECT_EQ(base.FullSize(), subset.FullSize());
+ EXPECT_EQ(0u, subset.Offset());
+ }
+ EXPECT_EQ(0u, base.Offset());
+ EXPECT_EQ(size, base.Remaining());
+}
+
+// Make sure that DecodeBuffer ctor complains about bad args.
+#if GTEST_HAS_DEATH_TEST && !defined(NDEBUG)
+TEST(DecodeBufferDeathTest, NonNullBufferRequired) {
+ EXPECT_DEBUG_DEATH({ DecodeBuffer b(nullptr, 3); }, "nullptr");
+}
+
+// Make sure that DecodeBuffer ctor complains about bad args.
+TEST(DecodeBufferDeathTest, ModestBufferSizeRequired) {
+ EXPECT_DEBUG_DEATH(
+ {
+ const char data[] = "abc";
+ size_t len = 0;
+ DecodeBuffer b(data, ~len);
+ },
+ "Max.*Length");
+}
+
+// Make sure that DecodeBuffer detects advance beyond end, in debug mode.
+TEST(DecodeBufferDeathTest, LimitedAdvance) {
+ {
+ // Advance right up to end is OK.
+ const char data[] = "abc";
+ DecodeBuffer b(data, 3);
+ b.AdvanceCursor(3); // OK
+ EXPECT_TRUE(b.Empty());
+ }
+ EXPECT_DEBUG_DEATH(
+ {
+ // Going beyond is not OK.
+ const char data[] = "abc";
+ DecodeBuffer b(data, 3);
+ b.AdvanceCursor(4);
+ },
+ "4 vs. 3");
+}
+
+// Make sure that DecodeBuffer detects decode beyond end, in debug mode.
+TEST(DecodeBufferDeathTest, DecodeUInt8PastEnd) {
+ const char data[] = {0x12, 0x23};
+ DecodeBuffer b(data, sizeof data);
+ EXPECT_EQ(2u, b.FullSize());
+ EXPECT_EQ(0x1223, b.DecodeUInt16());
+ EXPECT_DEBUG_DEATH({ b.DecodeUInt8(); }, "1 vs. 0");
+}
+
+// Make sure that DecodeBuffer detects decode beyond end, in debug mode.
+TEST(DecodeBufferDeathTest, DecodeUInt16OverEnd) {
+ const char data[] = {0x12, 0x23, 0x34};
+ DecodeBuffer b(data, sizeof data);
+ EXPECT_EQ(3u, b.FullSize());
+ EXPECT_EQ(0x1223, b.DecodeUInt16());
+ EXPECT_DEBUG_DEATH({ b.DecodeUInt16(); }, "2 vs. 1");
+}
+
+// Make sure that DecodeBuffer doesn't agree with having two subsets.
+TEST(DecodeBufferSubsetDeathTest, TwoSubsets) {
+ const char data[] = "abc";
+ DecodeBuffer base(data, 3);
+ DecodeBufferSubset subset1(&base, 1);
+ EXPECT_DEBUG_DEATH({ DecodeBufferSubset subset2(&base, 1); },
+ "There is already a subset");
+}
+
+// Make sure that DecodeBufferSubset notices when the base's cursor has moved.
+TEST(DecodeBufferSubsetDeathTest, BaseCursorAdvanced) {
+ const char data[] = "abc";
+ DecodeBuffer base(data, 3);
+ base.AdvanceCursor(1);
+ EXPECT_DEBUG_DEATH(
+ {
+ DecodeBufferSubset subset1(&base, 2);
+ base.AdvanceCursor(1);
+ },
+ "Access via subset only when present");
+}
+#endif // GTEST_HAS_DEATH_TEST && !defined(NDEBUG)
+
+} // namespace
+} // namespace test
+} // namespace http2
diff --git a/chromium/net/third_party/quiche/src/http2/decoder/decode_http2_structures.cc b/chromium/net/third_party/quiche/src/http2/decoder/decode_http2_structures.cc
new file mode 100644
index 00000000000..71f6cce57bf
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/decoder/decode_http2_structures.cc
@@ -0,0 +1,111 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/http2/decoder/decode_http2_structures.h"
+
+#include <cstdint>
+
+#include "base/logging.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h"
+#include "net/third_party/quiche/src/http2/http2_constants.h"
+
+namespace http2 {
+
+// Http2FrameHeader decoding:
+
+void DoDecode(Http2FrameHeader* out, DecodeBuffer* b) {
+ DCHECK_NE(nullptr, out);
+ DCHECK_NE(nullptr, b);
+ DCHECK_LE(Http2FrameHeader::EncodedSize(), b->Remaining());
+ out->payload_length = b->DecodeUInt24();
+ out->type = static_cast<Http2FrameType>(b->DecodeUInt8());
+ out->flags = static_cast<Http2FrameFlag>(b->DecodeUInt8());
+ out->stream_id = b->DecodeUInt31();
+}
+
+// Http2PriorityFields decoding:
+
+void DoDecode(Http2PriorityFields* out, DecodeBuffer* b) {
+ DCHECK_NE(nullptr, out);
+ DCHECK_NE(nullptr, b);
+ DCHECK_LE(Http2PriorityFields::EncodedSize(), b->Remaining());
+ uint32_t stream_id_and_flag = b->DecodeUInt32();
+ out->stream_dependency = stream_id_and_flag & StreamIdMask();
+ if (out->stream_dependency == stream_id_and_flag) {
+ out->is_exclusive = false;
+ } else {
+ out->is_exclusive = true;
+ }
+ // Note that chars are automatically promoted to ints during arithmetic,
+ // so 255 + 1 doesn't end up as zero.
+ out->weight = b->DecodeUInt8() + 1;
+}
+
+// Http2RstStreamFields decoding:
+
+void DoDecode(Http2RstStreamFields* out, DecodeBuffer* b) {
+ DCHECK_NE(nullptr, out);
+ DCHECK_NE(nullptr, b);
+ DCHECK_LE(Http2RstStreamFields::EncodedSize(), b->Remaining());
+ out->error_code = static_cast<Http2ErrorCode>(b->DecodeUInt32());
+}
+
+// Http2SettingFields decoding:
+
+void DoDecode(Http2SettingFields* out, DecodeBuffer* b) {
+ DCHECK_NE(nullptr, out);
+ DCHECK_NE(nullptr, b);
+ DCHECK_LE(Http2SettingFields::EncodedSize(), b->Remaining());
+ out->parameter = static_cast<Http2SettingsParameter>(b->DecodeUInt16());
+ out->value = b->DecodeUInt32();
+}
+
+// Http2PushPromiseFields decoding:
+
+void DoDecode(Http2PushPromiseFields* out, DecodeBuffer* b) {
+ DCHECK_NE(nullptr, out);
+ DCHECK_NE(nullptr, b);
+ DCHECK_LE(Http2PushPromiseFields::EncodedSize(), b->Remaining());
+ out->promised_stream_id = b->DecodeUInt31();
+}
+
+// Http2PingFields decoding:
+
+void DoDecode(Http2PingFields* out, DecodeBuffer* b) {
+ DCHECK_NE(nullptr, out);
+ DCHECK_NE(nullptr, b);
+ DCHECK_LE(Http2PingFields::EncodedSize(), b->Remaining());
+ memcpy(out->opaque_bytes, b->cursor(), Http2PingFields::EncodedSize());
+ b->AdvanceCursor(Http2PingFields::EncodedSize());
+}
+
+// Http2GoAwayFields decoding:
+
+void DoDecode(Http2GoAwayFields* out, DecodeBuffer* b) {
+ DCHECK_NE(nullptr, out);
+ DCHECK_NE(nullptr, b);
+ DCHECK_LE(Http2GoAwayFields::EncodedSize(), b->Remaining());
+ out->last_stream_id = b->DecodeUInt31();
+ out->error_code = static_cast<Http2ErrorCode>(b->DecodeUInt32());
+}
+
+// Http2WindowUpdateFields decoding:
+
+void DoDecode(Http2WindowUpdateFields* out, DecodeBuffer* b) {
+ DCHECK_NE(nullptr, out);
+ DCHECK_NE(nullptr, b);
+ DCHECK_LE(Http2WindowUpdateFields::EncodedSize(), b->Remaining());
+ out->window_size_increment = b->DecodeUInt31();
+}
+
+// Http2AltSvcFields decoding:
+
+void DoDecode(Http2AltSvcFields* out, DecodeBuffer* b) {
+ DCHECK_NE(nullptr, out);
+ DCHECK_NE(nullptr, b);
+ DCHECK_LE(Http2AltSvcFields::EncodedSize(), b->Remaining());
+ out->origin_length = b->DecodeUInt16();
+}
+
+} // namespace http2
diff --git a/chromium/net/third_party/quiche/src/http2/decoder/decode_http2_structures.h b/chromium/net/third_party/quiche/src/http2/decoder/decode_http2_structures.h
new file mode 100644
index 00000000000..3f6a317231e
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/decoder/decode_http2_structures.h
@@ -0,0 +1,34 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_HTTP2_DECODER_DECODE_HTTP2_STRUCTURES_H_
+#define QUICHE_HTTP2_DECODER_DECODE_HTTP2_STRUCTURES_H_
+
+// Provides functions for decoding the fixed size structures in the HTTP/2 spec.
+
+#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h"
+#include "net/third_party/quiche/src/http2/http2_structures.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_export.h"
+
+namespace http2 {
+
+// DoDecode(STRUCTURE* out, DecodeBuffer* b) decodes the structure from start
+// to end, advancing the cursor by STRUCTURE::EncodedSize(). The decode buffer
+// must be large enough (i.e. b->Remaining() >= STRUCTURE::EncodedSize()).
+
+HTTP2_EXPORT_PRIVATE void DoDecode(Http2FrameHeader* out, DecodeBuffer* b);
+HTTP2_EXPORT_PRIVATE void DoDecode(Http2PriorityFields* out, DecodeBuffer* b);
+HTTP2_EXPORT_PRIVATE void DoDecode(Http2RstStreamFields* out, DecodeBuffer* b);
+HTTP2_EXPORT_PRIVATE void DoDecode(Http2SettingFields* out, DecodeBuffer* b);
+HTTP2_EXPORT_PRIVATE void DoDecode(Http2PushPromiseFields* out,
+ DecodeBuffer* b);
+HTTP2_EXPORT_PRIVATE void DoDecode(Http2PingFields* out, DecodeBuffer* b);
+HTTP2_EXPORT_PRIVATE void DoDecode(Http2GoAwayFields* out, DecodeBuffer* b);
+HTTP2_EXPORT_PRIVATE void DoDecode(Http2WindowUpdateFields* out,
+ DecodeBuffer* b);
+HTTP2_EXPORT_PRIVATE void DoDecode(Http2AltSvcFields* out, DecodeBuffer* b);
+
+} // namespace http2
+
+#endif // QUICHE_HTTP2_DECODER_DECODE_HTTP2_STRUCTURES_H_
diff --git a/chromium/net/third_party/quiche/src/http2/decoder/decode_http2_structures_test.cc b/chromium/net/third_party/quiche/src/http2/decoder/decode_http2_structures_test.cc
new file mode 100644
index 00000000000..43bd7e8d040
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/decoder/decode_http2_structures_test.cc
@@ -0,0 +1,461 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/http2/decoder/decode_http2_structures.h"
+
+// Tests decoding all of the fixed size HTTP/2 structures (i.e. those defined
+// in net/third_party/quiche/src/http2/http2_structures.h).
+
+#include <stddef.h>
+
+#include "base/logging.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_status.h"
+#include "net/third_party/quiche/src/http2/http2_constants.h"
+#include "net/third_party/quiche/src/http2/http2_structures_test_util.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string_piece.h"
+#include "net/third_party/quiche/src/http2/test_tools/http2_random.h"
+#include "net/third_party/quiche/src/http2/tools/http2_frame_builder.h"
+
+using ::testing::AssertionResult;
+
+namespace http2 {
+namespace test {
+namespace {
+
+template <typename T, size_t N>
+Http2StringPiece ToStringPiece(T (&data)[N]) {
+ return Http2StringPiece(reinterpret_cast<const char*>(data), N * sizeof(T));
+}
+
+template <class S>
+Http2String SerializeStructure(const S& s) {
+ Http2FrameBuilder fb;
+ fb.Append(s);
+ EXPECT_EQ(S::EncodedSize(), fb.size());
+ return fb.buffer();
+}
+
+template <class S>
+class StructureDecoderTest : public ::testing::Test {
+ protected:
+ typedef S Structure;
+
+ StructureDecoderTest() : random_(), random_decode_count_(100) {}
+
+ // Set the fields of |*p| to random values.
+ void Randomize(S* p) { ::http2::test::Randomize(p, &random_); }
+
+ // Fully decodes the Structure at the start of data, and confirms it matches
+ // *expected (if provided).
+ void DecodeLeadingStructure(const S* expected, Http2StringPiece data) {
+ ASSERT_LE(S::EncodedSize(), data.size());
+ DecodeBuffer db(data);
+ Randomize(&structure_);
+ DoDecode(&structure_, &db);
+ EXPECT_EQ(db.Offset(), S::EncodedSize());
+ if (expected != nullptr) {
+ EXPECT_EQ(structure_, *expected);
+ }
+ }
+
+ template <size_t N>
+ void DecodeLeadingStructure(const char (&data)[N]) {
+ DecodeLeadingStructure(nullptr, Http2StringPiece(data, N));
+ }
+
+ // Encode the structure |in_s| into bytes, then decode the bytes
+ // and validate that the decoder produced the same field values.
+ void EncodeThenDecode(const S& in_s) {
+ Http2String bytes = SerializeStructure(in_s);
+ EXPECT_EQ(S::EncodedSize(), bytes.size());
+ DecodeLeadingStructure(&in_s, bytes);
+ }
+
+ // Generate
+ void TestDecodingRandomizedStructures(size_t count) {
+ for (size_t i = 0; i < count && !HasFailure(); ++i) {
+ Structure input;
+ Randomize(&input);
+ EncodeThenDecode(input);
+ }
+ }
+
+ void TestDecodingRandomizedStructures() {
+ TestDecodingRandomizedStructures(random_decode_count_);
+ }
+
+ Http2Random random_;
+ const size_t random_decode_count_;
+ uint32_t decode_offset_ = 0;
+ S structure_;
+ size_t fast_decode_count_ = 0;
+ size_t slow_decode_count_ = 0;
+};
+
+class FrameHeaderDecoderTest : public StructureDecoderTest<Http2FrameHeader> {};
+
+TEST_F(FrameHeaderDecoderTest, DecodesLiteral) {
+ {
+ // Realistic input.
+ const char kData[] = {
+ '\x00', '\x00', '\x05', // Payload length: 5
+ '\x01', // Frame type: HEADERS
+ '\x08', // Flags: PADDED
+ '\x00', '\x00', '\x00', '\x01', // Stream ID: 1
+ '\x04', // Padding length: 4
+ '\x00', '\x00', '\x00', '\x00', // Padding bytes
+ };
+ DecodeLeadingStructure(kData);
+ if (!HasFailure()) {
+ EXPECT_EQ(5u, structure_.payload_length);
+ EXPECT_EQ(Http2FrameType::HEADERS, structure_.type);
+ EXPECT_EQ(Http2FrameFlag::PADDED, structure_.flags);
+ EXPECT_EQ(1u, structure_.stream_id);
+ }
+ }
+ {
+ // Unlikely input.
+ const char kData[] = {
+ '\xff', '\xff', '\xff', // Payload length: uint24 max
+ '\xff', // Frame type: Unknown
+ '\xff', // Flags: Unknown/All
+ '\xff', '\xff', '\xff', '\xff', // Stream ID: uint31 max, plus R-bit
+ };
+ DecodeLeadingStructure(kData);
+ if (!HasFailure()) {
+ EXPECT_EQ((1u << 24) - 1, structure_.payload_length);
+ EXPECT_EQ(static_cast<Http2FrameType>(255), structure_.type);
+ EXPECT_EQ(255, structure_.flags);
+ EXPECT_EQ(0x7FFFFFFFu, structure_.stream_id);
+ }
+ }
+}
+
+TEST_F(FrameHeaderDecoderTest, DecodesRandomized) {
+ TestDecodingRandomizedStructures();
+}
+
+//------------------------------------------------------------------------------
+
+class PriorityFieldsDecoderTest
+ : public StructureDecoderTest<Http2PriorityFields> {};
+
+TEST_F(PriorityFieldsDecoderTest, DecodesLiteral) {
+ {
+ const char kData[] = {
+ '\x80', '\x00', '\x00', '\x05', // Exclusive (yes) and Dependency (5)
+ '\xff', // Weight: 256 (after adding 1)
+ };
+ DecodeLeadingStructure(kData);
+ if (!HasFailure()) {
+ EXPECT_EQ(5u, structure_.stream_dependency);
+ EXPECT_EQ(256u, structure_.weight);
+ EXPECT_EQ(true, structure_.is_exclusive);
+ }
+ }
+ {
+ const char kData[] = {
+ '\x7f', '\xff',
+ '\xff', '\xff', // Exclusive (no) and Dependency (0x7fffffff)
+ '\x00', // Weight: 1 (after adding 1)
+ };
+ DecodeLeadingStructure(kData);
+ if (!HasFailure()) {
+ EXPECT_EQ(StreamIdMask(), structure_.stream_dependency);
+ EXPECT_EQ(1u, structure_.weight);
+ EXPECT_FALSE(structure_.is_exclusive);
+ }
+ }
+}
+
+TEST_F(PriorityFieldsDecoderTest, DecodesRandomized) {
+ TestDecodingRandomizedStructures();
+}
+
+//------------------------------------------------------------------------------
+
+class RstStreamFieldsDecoderTest
+ : public StructureDecoderTest<Http2RstStreamFields> {};
+
+TEST_F(RstStreamFieldsDecoderTest, DecodesLiteral) {
+ {
+ const char kData[] = {
+ '\x00', '\x00', '\x00', '\x01', // Error: PROTOCOL_ERROR
+ };
+ DecodeLeadingStructure(kData);
+ if (!HasFailure()) {
+ EXPECT_TRUE(structure_.IsSupportedErrorCode());
+ EXPECT_EQ(Http2ErrorCode::PROTOCOL_ERROR, structure_.error_code);
+ }
+ }
+ {
+ const char kData[] = {
+ '\xff', '\xff', '\xff',
+ '\xff', // Error: max uint32 (Unknown error code)
+ };
+ DecodeLeadingStructure(kData);
+ if (!HasFailure()) {
+ EXPECT_FALSE(structure_.IsSupportedErrorCode());
+ EXPECT_EQ(static_cast<Http2ErrorCode>(0xffffffff), structure_.error_code);
+ }
+ }
+}
+
+TEST_F(RstStreamFieldsDecoderTest, DecodesRandomized) {
+ TestDecodingRandomizedStructures();
+}
+
+//------------------------------------------------------------------------------
+
+class SettingFieldsDecoderTest
+ : public StructureDecoderTest<Http2SettingFields> {};
+
+TEST_F(SettingFieldsDecoderTest, DecodesLiteral) {
+ {
+ const char kData[] = {
+ '\x00', '\x01', // Setting: HEADER_TABLE_SIZE
+ '\x00', '\x00', '\x40', '\x00', // Value: 16K
+ };
+ DecodeLeadingStructure(kData);
+ if (!HasFailure()) {
+ EXPECT_TRUE(structure_.IsSupportedParameter());
+ EXPECT_EQ(Http2SettingsParameter::HEADER_TABLE_SIZE,
+ structure_.parameter);
+ EXPECT_EQ(1u << 14, structure_.value);
+ }
+ }
+ {
+ const char kData[] = {
+ '\x00', '\x00', // Setting: Unknown (0)
+ '\xff', '\xff', '\xff', '\xff', // Value: max uint32
+ };
+ DecodeLeadingStructure(kData);
+ if (!HasFailure()) {
+ EXPECT_FALSE(structure_.IsSupportedParameter());
+ EXPECT_EQ(static_cast<Http2SettingsParameter>(0), structure_.parameter);
+ }
+ }
+}
+
+TEST_F(SettingFieldsDecoderTest, DecodesRandomized) {
+ TestDecodingRandomizedStructures();
+}
+
+//------------------------------------------------------------------------------
+
+class PushPromiseFieldsDecoderTest
+ : public StructureDecoderTest<Http2PushPromiseFields> {};
+
+TEST_F(PushPromiseFieldsDecoderTest, DecodesLiteral) {
+ {
+ const char kData[] = {
+ '\x00', '\x01', '\x8a', '\x92', // Promised Stream ID: 101010
+ };
+ DecodeLeadingStructure(kData);
+ if (!HasFailure()) {
+ EXPECT_EQ(101010u, structure_.promised_stream_id);
+ }
+ }
+ {
+ // Promised stream id has R-bit (reserved for future use) set, which
+ // should be cleared by the decoder.
+ const char kData[] = {
+ '\xff', '\xff', '\xff',
+ '\xff', // Promised Stream ID: max uint31 and R-bit
+ };
+ DecodeLeadingStructure(kData);
+ if (!HasFailure()) {
+ EXPECT_EQ(StreamIdMask(), structure_.promised_stream_id);
+ }
+ }
+}
+
+TEST_F(PushPromiseFieldsDecoderTest, DecodesRandomized) {
+ TestDecodingRandomizedStructures();
+}
+
+//------------------------------------------------------------------------------
+
+class PingFieldsDecoderTest : public StructureDecoderTest<Http2PingFields> {};
+
+TEST_F(PingFieldsDecoderTest, DecodesLiteral) {
+ {
+ // Each byte is different, so can detect if order changed.
+ const char kData[] = {
+ '\x00', '\x01', '\x02', '\x03', '\x04', '\x05', '\x06', '\x07',
+ };
+ DecodeLeadingStructure(kData);
+ if (!HasFailure()) {
+ EXPECT_EQ(Http2StringPiece(kData, 8),
+ ToStringPiece(structure_.opaque_bytes));
+ }
+ }
+ {
+ // All zeros, detect problems handling NULs.
+ const char kData[] = {
+ '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00',
+ };
+ DecodeLeadingStructure(kData);
+ if (!HasFailure()) {
+ EXPECT_EQ(Http2StringPiece(kData, 8),
+ ToStringPiece(structure_.opaque_bytes));
+ }
+ }
+ {
+ const char kData[] = {
+ '\xff', '\xff', '\xff', '\xff', '\xff', '\xff', '\xff', '\xff',
+ };
+ DecodeLeadingStructure(kData);
+ if (!HasFailure()) {
+ EXPECT_EQ(Http2StringPiece(kData, 8),
+ ToStringPiece(structure_.opaque_bytes));
+ }
+ }
+}
+
+TEST_F(PingFieldsDecoderTest, DecodesRandomized) {
+ TestDecodingRandomizedStructures();
+}
+
+//------------------------------------------------------------------------------
+
+class GoAwayFieldsDecoderTest : public StructureDecoderTest<Http2GoAwayFields> {
+};
+
+TEST_F(GoAwayFieldsDecoderTest, DecodesLiteral) {
+ {
+ const char kData[] = {
+ '\x00', '\x00', '\x00', '\x00', // Last Stream ID: 0
+ '\x00', '\x00', '\x00', '\x00', // Error: NO_ERROR (0)
+ };
+ DecodeLeadingStructure(kData);
+ if (!HasFailure()) {
+ EXPECT_EQ(0u, structure_.last_stream_id);
+ EXPECT_TRUE(structure_.IsSupportedErrorCode());
+ EXPECT_EQ(Http2ErrorCode::HTTP2_NO_ERROR, structure_.error_code);
+ }
+ }
+ {
+ const char kData[] = {
+ '\x00', '\x00', '\x00', '\x01', // Last Stream ID: 1
+ '\x00', '\x00', '\x00', '\x0d', // Error: HTTP_1_1_REQUIRED
+ };
+ DecodeLeadingStructure(kData);
+ if (!HasFailure()) {
+ EXPECT_EQ(1u, structure_.last_stream_id);
+ EXPECT_TRUE(structure_.IsSupportedErrorCode());
+ EXPECT_EQ(Http2ErrorCode::HTTP_1_1_REQUIRED, structure_.error_code);
+ }
+ }
+ {
+ const char kData[] = {
+ '\xff', '\xff',
+ '\xff', '\xff', // Last Stream ID: max uint31 and R-bit
+ '\xff', '\xff',
+ '\xff', '\xff', // Error: max uint32 (Unknown error code)
+ };
+ DecodeLeadingStructure(kData);
+ if (!HasFailure()) {
+ EXPECT_EQ(StreamIdMask(), structure_.last_stream_id); // No high-bit.
+ EXPECT_FALSE(structure_.IsSupportedErrorCode());
+ EXPECT_EQ(static_cast<Http2ErrorCode>(0xffffffff), structure_.error_code);
+ }
+ }
+}
+
+TEST_F(GoAwayFieldsDecoderTest, DecodesRandomized) {
+ TestDecodingRandomizedStructures();
+}
+
+//------------------------------------------------------------------------------
+
+class WindowUpdateFieldsDecoderTest
+ : public StructureDecoderTest<Http2WindowUpdateFields> {};
+
+TEST_F(WindowUpdateFieldsDecoderTest, DecodesLiteral) {
+ {
+ const char kData[] = {
+ '\x00', '\x01', '\x00', '\x00', // Window Size Increment: 2 ^ 16
+ };
+ DecodeLeadingStructure(kData);
+ if (!HasFailure()) {
+ EXPECT_EQ(1u << 16, structure_.window_size_increment);
+ }
+ }
+ {
+ // Increment must be non-zero, but we need to be able to decode the invalid
+ // zero to detect it.
+ const char kData[] = {
+ '\x00', '\x00', '\x00', '\x00', // Window Size Increment: 0
+ };
+ DecodeLeadingStructure(kData);
+ if (!HasFailure()) {
+ EXPECT_EQ(0u, structure_.window_size_increment);
+ }
+ }
+ {
+ // Increment has R-bit (reserved for future use) set, which
+ // should be cleared by the decoder.
+ // clang-format off
+ const char kData[] = {
+ // Window Size Increment: max uint31 and R-bit
+ '\xff', '\xff', '\xff', '\xff',
+ };
+ // clang-format on
+ DecodeLeadingStructure(kData);
+ if (!HasFailure()) {
+ EXPECT_EQ(StreamIdMask(), structure_.window_size_increment);
+ }
+ }
+}
+
+TEST_F(WindowUpdateFieldsDecoderTest, DecodesRandomized) {
+ TestDecodingRandomizedStructures();
+}
+
+//------------------------------------------------------------------------------
+
+class AltSvcFieldsDecoderTest : public StructureDecoderTest<Http2AltSvcFields> {
+};
+
+TEST_F(AltSvcFieldsDecoderTest, DecodesLiteral) {
+ {
+ const char kData[] = {
+ '\x00', '\x00', // Origin Length: 0
+ };
+ DecodeLeadingStructure(kData);
+ if (!HasFailure()) {
+ EXPECT_EQ(0, structure_.origin_length);
+ }
+ }
+ {
+ const char kData[] = {
+ '\x00', '\x14', // Origin Length: 20
+ };
+ DecodeLeadingStructure(kData);
+ if (!HasFailure()) {
+ EXPECT_EQ(20, structure_.origin_length);
+ }
+ }
+ {
+ const char kData[] = {
+ '\xff', '\xff', // Origin Length: uint16 max
+ };
+ DecodeLeadingStructure(kData);
+ if (!HasFailure()) {
+ EXPECT_EQ(65535, structure_.origin_length);
+ }
+ }
+}
+
+TEST_F(AltSvcFieldsDecoderTest, DecodesRandomized) {
+ TestDecodingRandomizedStructures();
+}
+
+} // namespace
+} // namespace test
+} // namespace http2
diff --git a/chromium/net/third_party/quiche/src/http2/decoder/decode_status.cc b/chromium/net/third_party/quiche/src/http2/decoder/decode_status.cc
new file mode 100644
index 00000000000..6a913f05e50
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/decoder/decode_status.cc
@@ -0,0 +1,28 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/http2/decoder/decode_status.h"
+
+#include "base/logging.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_bug_tracker.h"
+
+namespace http2 {
+
+std::ostream& operator<<(std::ostream& out, DecodeStatus v) {
+ switch (v) {
+ case DecodeStatus::kDecodeDone:
+ return out << "DecodeDone";
+ case DecodeStatus::kDecodeInProgress:
+ return out << "DecodeInProgress";
+ case DecodeStatus::kDecodeError:
+ return out << "DecodeError";
+ }
+ // Since the value doesn't come over the wire, only a programming bug should
+ // result in reaching this point.
+ int unknown = static_cast<int>(v);
+ HTTP2_BUG << "Unknown DecodeStatus " << unknown;
+ return out << "DecodeStatus(" << unknown << ")";
+}
+
+} // namespace http2
diff --git a/chromium/net/third_party/quiche/src/http2/decoder/decode_status.h b/chromium/net/third_party/quiche/src/http2/decoder/decode_status.h
new file mode 100644
index 00000000000..fdecd224e38
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/decoder/decode_status.h
@@ -0,0 +1,33 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_HTTP2_DECODER_DECODE_STATUS_H_
+#define QUICHE_HTTP2_DECODER_DECODE_STATUS_H_
+
+// Enum DecodeStatus is used to report the status of decoding of many
+// types of HTTP/2 and HPACK objects.
+
+#include <ostream>
+
+#include "net/third_party/quiche/src/http2/platform/api/http2_export.h"
+
+namespace http2 {
+
+enum class DecodeStatus {
+ // Decoding is done.
+ kDecodeDone,
+
+ // Decoder needs more input to be able to make progress.
+ kDecodeInProgress,
+
+ // Decoding failed (e.g. HPACK variable length integer is too large, or
+ // an HTTP/2 frame has padding declared to be larger than the payload).
+ kDecodeError,
+};
+HTTP2_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& out,
+ DecodeStatus v);
+
+} // namespace http2
+
+#endif // QUICHE_HTTP2_DECODER_DECODE_STATUS_H_
diff --git a/chromium/net/third_party/quiche/src/http2/decoder/frame_decoder_state.cc b/chromium/net/third_party/quiche/src/http2/decoder/frame_decoder_state.cc
new file mode 100644
index 00000000000..946a6ee8d31
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/decoder/frame_decoder_state.cc
@@ -0,0 +1,81 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/http2/decoder/frame_decoder_state.h"
+
+namespace http2 {
+
+DecodeStatus FrameDecoderState::ReadPadLength(DecodeBuffer* db,
+ bool report_pad_length) {
+ DVLOG(2) << "ReadPadLength db->Remaining=" << db->Remaining()
+ << "; payload_length=" << frame_header().payload_length;
+ DCHECK(IsPaddable());
+ DCHECK(frame_header().IsPadded());
+
+ // Pad Length is always at the start of the frame, so remaining_payload_
+ // should equal payload_length at this point.
+ const uint32_t total_payload = frame_header().payload_length;
+ DCHECK_EQ(total_payload, remaining_payload_);
+ DCHECK_EQ(0u, remaining_padding_);
+
+ if (db->HasData()) {
+ const uint32_t pad_length = db->DecodeUInt8();
+ const uint32_t total_padding = pad_length + 1;
+ if (total_padding <= total_payload) {
+ remaining_padding_ = pad_length;
+ remaining_payload_ = total_payload - total_padding;
+ if (report_pad_length) {
+ listener()->OnPadLength(pad_length);
+ }
+ return DecodeStatus::kDecodeDone;
+ }
+ const uint32_t missing_length = total_padding - total_payload;
+ // To allow for the possibility of recovery, record the number of
+ // remaining bytes of the frame's payload (invalid though it is)
+ // in remaining_payload_.
+ remaining_payload_ = total_payload - 1; // 1 for sizeof(Pad Length).
+ remaining_padding_ = 0;
+ listener()->OnPaddingTooLong(frame_header(), missing_length);
+ return DecodeStatus::kDecodeError;
+ }
+
+ if (total_payload == 0) {
+ remaining_payload_ = 0;
+ remaining_padding_ = 0;
+ listener()->OnPaddingTooLong(frame_header(), 1);
+ return DecodeStatus::kDecodeError;
+ }
+ // Need to wait for another buffer.
+ return DecodeStatus::kDecodeInProgress;
+}
+
+bool FrameDecoderState::SkipPadding(DecodeBuffer* db) {
+ DVLOG(2) << "SkipPadding remaining_padding_=" << remaining_padding_
+ << ", db->Remaining=" << db->Remaining()
+ << ", header: " << frame_header();
+ DCHECK_EQ(remaining_payload_, 0u);
+ DCHECK(IsPaddable()) << "header: " << frame_header();
+ DCHECK_GE(remaining_padding_, 0u);
+ DCHECK(remaining_padding_ == 0 || frame_header().IsPadded())
+ << "remaining_padding_=" << remaining_padding_
+ << ", header: " << frame_header();
+ const size_t avail = AvailablePadding(db);
+ if (avail > 0) {
+ listener()->OnPadding(db->cursor(), avail);
+ db->AdvanceCursor(avail);
+ remaining_padding_ -= avail;
+ }
+ return remaining_padding_ == 0;
+}
+
+DecodeStatus FrameDecoderState::ReportFrameSizeError() {
+ DVLOG(2) << "FrameDecoderState::ReportFrameSizeError: "
+ << " remaining_payload_=" << remaining_payload_
+ << "; remaining_padding_=" << remaining_padding_
+ << ", header: " << frame_header();
+ listener()->OnFrameSizeError(frame_header());
+ return DecodeStatus::kDecodeError;
+}
+
+} // namespace http2
diff --git a/chromium/net/third_party/quiche/src/http2/decoder/frame_decoder_state.h b/chromium/net/third_party/quiche/src/http2/decoder/frame_decoder_state.h
new file mode 100644
index 00000000000..1581752f086
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/decoder/frame_decoder_state.h
@@ -0,0 +1,250 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_HTTP2_DECODER_FRAME_DECODER_STATE_H_
+#define QUICHE_HTTP2_DECODER_FRAME_DECODER_STATE_H_
+
+// FrameDecoderState provides state and behaviors in support of decoding
+// the common frame header and the payload of all frame types.
+// It is an input to all of the payload decoders.
+
+// TODO(jamessynge): Since FrameDecoderState has far more than state in it,
+// rename to FrameDecoderHelper, or similar.
+
+#include <stddef.h>
+
+#include <cstdint>
+
+#include "base/logging.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_status.h"
+#include "net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener.h"
+#include "net/third_party/quiche/src/http2/decoder/http2_structure_decoder.h"
+#include "net/third_party/quiche/src/http2/http2_constants.h"
+#include "net/third_party/quiche/src/http2/http2_structures.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_export.h"
+
+namespace http2 {
+namespace test {
+class FrameDecoderStatePeer;
+} // namespace test
+
+class HTTP2_EXPORT_PRIVATE FrameDecoderState {
+ public:
+ FrameDecoderState() {}
+
+ // Sets the listener which the decoders should call as they decode HTTP/2
+ // frames. The listener can be changed at any time, which allows for replacing
+ // it with a no-op listener when an error is detected, either by the payload
+ // decoder (OnPaddingTooLong or OnFrameSizeError) or by the "real" listener.
+ // That in turn allows us to define Http2FrameDecoderListener such that all
+ // methods have return type void, with no direct way to indicate whether the
+ // decoder should stop, and to eliminate from the decoder all checks of the
+ // return value. Instead the listener/caller can simply replace the current
+ // listener with a no-op listener implementation.
+ // TODO(jamessynge): Make set_listener private as only Http2FrameDecoder
+ // and tests need to set it, so it doesn't need to be public.
+ void set_listener(Http2FrameDecoderListener* listener) {
+ listener_ = listener;
+ }
+ Http2FrameDecoderListener* listener() const { return listener_; }
+
+ // The most recently decoded frame header.
+ const Http2FrameHeader& frame_header() const { return frame_header_; }
+
+ // Decode a structure in the payload, adjusting remaining_payload_ to account
+ // for the consumed portion of the payload. Returns kDecodeDone when fully
+ // decoded, kDecodeError if it ran out of payload before decoding completed,
+ // and kDecodeInProgress if the decode buffer didn't have enough of the
+ // remaining payload.
+ template <class S>
+ DecodeStatus StartDecodingStructureInPayload(S* out, DecodeBuffer* db) {
+ DVLOG(2) << __func__ << "\n\tdb->Remaining=" << db->Remaining()
+ << "\n\tremaining_payload_=" << remaining_payload_
+ << "\n\tneed=" << S::EncodedSize();
+ DecodeStatus status =
+ structure_decoder_.Start(out, db, &remaining_payload_);
+ if (status != DecodeStatus::kDecodeError) {
+ return status;
+ }
+ DVLOG(2) << "StartDecodingStructureInPayload: detected frame size error";
+ return ReportFrameSizeError();
+ }
+
+ // Resume decoding of a structure that has been split across buffers,
+ // adjusting remaining_payload_ to account for the consumed portion of
+ // the payload. Returns values are as for StartDecodingStructureInPayload.
+ template <class S>
+ DecodeStatus ResumeDecodingStructureInPayload(S* out, DecodeBuffer* db) {
+ DVLOG(2) << __func__ << "\n\tdb->Remaining=" << db->Remaining()
+ << "\n\tremaining_payload_=" << remaining_payload_;
+ if (structure_decoder_.Resume(out, db, &remaining_payload_)) {
+ return DecodeStatus::kDecodeDone;
+ } else if (remaining_payload_ > 0) {
+ return DecodeStatus::kDecodeInProgress;
+ } else {
+ DVLOG(2) << "ResumeDecodingStructureInPayload: detected frame size error";
+ return ReportFrameSizeError();
+ }
+ }
+
+ // Initializes the two remaining* fields, which is needed if the frame's
+ // payload is split across buffers, or the decoder calls ReadPadLength or
+ // StartDecodingStructureInPayload, and of course the methods below which
+ // read those fields, as their names imply.
+ void InitializeRemainders() {
+ remaining_payload_ = frame_header().payload_length;
+ // Note that remaining_total_payload() relies on remaining_padding_ being
+ // zero for frames that have no padding.
+ remaining_padding_ = 0;
+ }
+
+ // Returns the number of bytes of the frame's payload that remain to be
+ // decoded, including any trailing padding. This method must only be called
+ // after the variables have been initialized, which in practice means once a
+ // payload decoder has called InitializeRemainders and/or ReadPadLength.
+ size_t remaining_total_payload() const {
+ DCHECK(IsPaddable() || remaining_padding_ == 0) << frame_header();
+ return remaining_payload_ + remaining_padding_;
+ }
+
+ // Returns the number of bytes of the frame's payload that remain to be
+ // decoded, excluding any trailing padding. This method must only be called
+ // after the variable has been initialized, which in practice means once a
+ // payload decoder has called InitializeRemainders; ReadPadLength will deduct
+ // the total number of padding bytes from remaining_payload_, including the
+ // size of the Pad Length field itself (1 byte).
+ size_t remaining_payload() const { return remaining_payload_; }
+
+ // Returns the number of bytes of the frame's payload that remain to be
+ // decoded, including any trailing padding. This method must only be called if
+ // the frame type allows padding, and after the variable has been initialized,
+ // which in practice means once a payload decoder has called
+ // InitializeRemainders and/or ReadPadLength.
+ size_t remaining_payload_and_padding() const {
+ DCHECK(IsPaddable()) << frame_header();
+ return remaining_payload_ + remaining_padding_;
+ }
+
+ // Returns the number of bytes of trailing padding after the payload that
+ // remain to be decoded. This method must only be called if the frame type
+ // allows padding, and after the variable has been initialized, which in
+ // practice means once a payload decoder has called InitializeRemainders,
+ // and isn't set to a non-zero value until ReadPadLength has been called.
+ uint32_t remaining_padding() const {
+ DCHECK(IsPaddable()) << frame_header();
+ return remaining_padding_;
+ }
+
+ // How many bytes of the remaining payload are in db?
+ size_t AvailablePayload(DecodeBuffer* db) const {
+ return db->MinLengthRemaining(remaining_payload_);
+ }
+
+ // How many bytes of the remaining payload and padding are in db?
+ // Call only for frames whose type is paddable.
+ size_t AvailablePayloadAndPadding(DecodeBuffer* db) const {
+ DCHECK(IsPaddable()) << frame_header();
+ return db->MinLengthRemaining(remaining_payload_ + remaining_padding_);
+ }
+
+ // How many bytes of the padding that have not yet been skipped are in db?
+ // Call only after remaining_padding_ has been set (for padded frames), or
+ // been cleared (for unpadded frames); and after all of the non-padding
+ // payload has been decoded.
+ size_t AvailablePadding(DecodeBuffer* db) const {
+ DCHECK(IsPaddable()) << frame_header();
+ DCHECK_EQ(remaining_payload_, 0u);
+ return db->MinLengthRemaining(remaining_padding_);
+ }
+
+ // Reduces remaining_payload_ by amount. To be called by a payload decoder
+ // after it has passed a variable length portion of the payload to the
+ // listener; remaining_payload_ will be automatically reduced when fixed
+ // size structures and padding, including the Pad Length field, are decoded.
+ void ConsumePayload(size_t amount) {
+ DCHECK_LE(amount, remaining_payload_);
+ remaining_payload_ -= amount;
+ }
+
+ // Reads the Pad Length field into remaining_padding_, and appropriately sets
+ // remaining_payload_. When present, the Pad Length field is always the first
+ // field in the payload, which this method relies on so that the caller need
+ // not set remaining_payload_ before calling this method.
+ // If report_pad_length is true, calls the listener's OnPadLength method when
+ // it decodes the Pad Length field.
+ // Returns kDecodeDone if the decode buffer was not empty (i.e. because the
+ // field is only a single byte long, it can always be decoded if the buffer is
+ // not empty).
+ // Returns kDecodeError if the buffer is empty because the frame has no
+ // payload (i.e. payload_length() == 0).
+ // Returns kDecodeInProgress if the buffer is empty but the frame has a
+ // payload.
+ DecodeStatus ReadPadLength(DecodeBuffer* db, bool report_pad_length);
+
+ // Skip the trailing padding bytes; only call once remaining_payload_==0.
+ // Returns true when the padding has been skipped.
+ // Does NOT check that the padding is all zeroes.
+ bool SkipPadding(DecodeBuffer* db);
+
+ // Calls the listener's OnFrameSizeError method and returns kDecodeError.
+ DecodeStatus ReportFrameSizeError();
+
+ private:
+ friend class Http2FrameDecoder;
+ friend class test::FrameDecoderStatePeer;
+
+ // Starts the decoding of a common frame header. Returns true if completed the
+ // decoding, false if the decode buffer didn't have enough data in it, in
+ // which case the decode buffer will have been drained and the caller should
+ // call ResumeDecodingFrameHeader when more data is available. This is called
+ // from Http2FrameDecoder, a friend class.
+ bool StartDecodingFrameHeader(DecodeBuffer* db) {
+ return structure_decoder_.Start(&frame_header_, db);
+ }
+
+ // Resumes decoding the common frame header after the preceding call to
+ // StartDecodingFrameHeader returned false, as did any subsequent calls to
+ // ResumeDecodingFrameHeader. This is called from Http2FrameDecoder,
+ // a friend class.
+ bool ResumeDecodingFrameHeader(DecodeBuffer* db) {
+ return structure_decoder_.Resume(&frame_header_, db);
+ }
+
+ // Clear any of the flags in the frame header that aren't set in valid_flags.
+ void RetainFlags(uint8_t valid_flags) {
+ frame_header_.RetainFlags(valid_flags);
+ }
+
+ // Clear all of the flags in the frame header; for use with frame types that
+ // don't define any flags, such as WINDOW_UPDATE.
+ void ClearFlags() { frame_header_.flags = Http2FrameFlag(); }
+
+ // Returns true if the type of frame being decoded can have padding.
+ bool IsPaddable() const {
+ return frame_header().type == Http2FrameType::DATA ||
+ frame_header().type == Http2FrameType::HEADERS ||
+ frame_header().type == Http2FrameType::PUSH_PROMISE;
+ }
+
+ Http2FrameDecoderListener* listener_ = nullptr;
+ Http2FrameHeader frame_header_;
+
+ // Number of bytes remaining to be decoded, if set; does not include the
+ // trailing padding once the length of padding has been determined.
+ // See ReadPadLength.
+ uint32_t remaining_payload_;
+
+ // The amount of trailing padding after the payload that remains to be
+ // decoded. See ReadPadLength.
+ uint32_t remaining_padding_;
+
+ // Generic decoder of structures, which takes care of buffering the needed
+ // bytes if the encoded structure is split across decode buffers.
+ Http2StructureDecoder structure_decoder_;
+};
+
+} // namespace http2
+
+#endif // QUICHE_HTTP2_DECODER_FRAME_DECODER_STATE_H_
diff --git a/chromium/net/third_party/quiche/src/http2/decoder/frame_decoder_state_test_util.cc b/chromium/net/third_party/quiche/src/http2/decoder/frame_decoder_state_test_util.cc
new file mode 100644
index 00000000000..7c13ef00ada
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/decoder/frame_decoder_state_test_util.cc
@@ -0,0 +1,34 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/http2/decoder/frame_decoder_state_test_util.h"
+
+#include "base/logging.h"
+#include "net/third_party/quiche/src/http2/decoder/http2_structure_decoder_test_util.h"
+#include "net/third_party/quiche/src/http2/http2_structures.h"
+#include "net/third_party/quiche/src/http2/http2_structures_test_util.h"
+#include "net/third_party/quiche/src/http2/test_tools/http2_random.h"
+#include "net/third_party/quiche/src/http2/tools/random_decoder_test.h"
+
+namespace http2 {
+namespace test {
+
+// static
+void FrameDecoderStatePeer::Randomize(FrameDecoderState* p, Http2Random* rng) {
+ VLOG(1) << "FrameDecoderStatePeer::Randomize";
+ ::http2::test::Randomize(&p->frame_header_, rng);
+ p->remaining_payload_ = rng->Rand32();
+ p->remaining_padding_ = rng->Rand32();
+ Http2StructureDecoderPeer::Randomize(&p->structure_decoder_, rng);
+}
+
+// static
+void FrameDecoderStatePeer::set_frame_header(const Http2FrameHeader& header,
+ FrameDecoderState* p) {
+ VLOG(1) << "FrameDecoderStatePeer::set_frame_header " << header;
+ p->frame_header_ = header;
+}
+
+} // namespace test
+} // namespace http2
diff --git a/chromium/net/third_party/quiche/src/http2/decoder/frame_decoder_state_test_util.h b/chromium/net/third_party/quiche/src/http2/decoder/frame_decoder_state_test_util.h
new file mode 100644
index 00000000000..fc81a4ad90a
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/decoder/frame_decoder_state_test_util.h
@@ -0,0 +1,36 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_HTTP2_DECODER_FRAME_DECODER_STATE_TEST_UTIL_H_
+#define QUICHE_HTTP2_DECODER_FRAME_DECODER_STATE_TEST_UTIL_H_
+
+#include "net/third_party/quiche/src/http2/decoder/frame_decoder_state.h"
+#include "net/third_party/quiche/src/http2/http2_structures.h"
+#include "net/third_party/quiche/src/http2/tools/random_decoder_test.h"
+
+namespace http2 {
+namespace test {
+
+class FrameDecoderStatePeer {
+ public:
+ // Randomizes (i.e. corrupts) the fields of the FrameDecoderState.
+ // PayloadDecoderBaseTest::StartDecoding calls this before passing the first
+ // decode buffer to the payload decoder, which increases the likelihood of
+ // detecting any use of prior states of the decoder on the decoding of
+ // future payloads.
+ static void Randomize(FrameDecoderState* p, Http2Random* rng);
+
+ // Inject a frame header into the FrameDecoderState.
+ // PayloadDecoderBaseTest::StartDecoding calls this just after calling
+ // Randomize (above), to simulate a full frame decoder having just finished
+ // decoding the common frame header and then calling the appropriate payload
+ // decoder based on the frame type in that frame header.
+ static void set_frame_header(const Http2FrameHeader& header,
+ FrameDecoderState* p);
+};
+
+} // namespace test
+} // namespace http2
+
+#endif // QUICHE_HTTP2_DECODER_FRAME_DECODER_STATE_TEST_UTIL_H_
diff --git a/chromium/net/third_party/quiche/src/http2/decoder/http2_frame_decoder.cc b/chromium/net/third_party/quiche/src/http2/decoder/http2_frame_decoder.cc
new file mode 100644
index 00000000000..314dfbfa19d
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/decoder/http2_frame_decoder.cc
@@ -0,0 +1,433 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/http2/decoder/http2_frame_decoder.h"
+
+#include "net/third_party/quiche/src/http2/decoder/decode_status.h"
+#include "net/third_party/quiche/src/http2/hpack/varint/hpack_varint_decoder.h"
+#include "net/third_party/quiche/src/http2/http2_constants.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_bug_tracker.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_macros.h"
+
+namespace http2 {
+
+std::ostream& operator<<(std::ostream& out, Http2FrameDecoder::State v) {
+ switch (v) {
+ case Http2FrameDecoder::State::kStartDecodingHeader:
+ return out << "kStartDecodingHeader";
+ case Http2FrameDecoder::State::kResumeDecodingHeader:
+ return out << "kResumeDecodingHeader";
+ case Http2FrameDecoder::State::kResumeDecodingPayload:
+ return out << "kResumeDecodingPayload";
+ case Http2FrameDecoder::State::kDiscardPayload:
+ return out << "kDiscardPayload";
+ }
+ // Since the value doesn't come over the wire, only a programming bug should
+ // result in reaching this point.
+ int unknown = static_cast<int>(v);
+ HTTP2_BUG << "Http2FrameDecoder::State " << unknown;
+ return out << "Http2FrameDecoder::State(" << unknown << ")";
+}
+
+Http2FrameDecoder::Http2FrameDecoder(Http2FrameDecoderListener* listener)
+ : state_(State::kStartDecodingHeader),
+ maximum_payload_size_(Http2SettingsInfo::DefaultMaxFrameSize()) {
+ set_listener(listener);
+}
+
+void Http2FrameDecoder::set_listener(Http2FrameDecoderListener* listener) {
+ if (listener == nullptr) {
+ listener = &no_op_listener_;
+ }
+ frame_decoder_state_.set_listener(listener);
+}
+
+Http2FrameDecoderListener* Http2FrameDecoder::listener() const {
+ return frame_decoder_state_.listener();
+}
+
+DecodeStatus Http2FrameDecoder::DecodeFrame(DecodeBuffer* db) {
+ DVLOG(2) << "Http2FrameDecoder::DecodeFrame state=" << state_;
+ switch (state_) {
+ case State::kStartDecodingHeader:
+ if (frame_decoder_state_.StartDecodingFrameHeader(db)) {
+ return StartDecodingPayload(db);
+ }
+ state_ = State::kResumeDecodingHeader;
+ return DecodeStatus::kDecodeInProgress;
+
+ case State::kResumeDecodingHeader:
+ if (frame_decoder_state_.ResumeDecodingFrameHeader(db)) {
+ return StartDecodingPayload(db);
+ }
+ return DecodeStatus::kDecodeInProgress;
+
+ case State::kResumeDecodingPayload:
+ return ResumeDecodingPayload(db);
+
+ case State::kDiscardPayload:
+ return DiscardPayload(db);
+ }
+
+ HTTP2_UNREACHABLE();
+ return DecodeStatus::kDecodeError;
+}
+
+size_t Http2FrameDecoder::remaining_payload() const {
+ return frame_decoder_state_.remaining_payload();
+}
+
+uint32_t Http2FrameDecoder::remaining_padding() const {
+ return frame_decoder_state_.remaining_padding();
+}
+
+DecodeStatus Http2FrameDecoder::StartDecodingPayload(DecodeBuffer* db) {
+ const Http2FrameHeader& header = frame_header();
+
+ // TODO(jamessynge): Remove OnFrameHeader once done with supporting
+ // SpdyFramer's exact states.
+ if (!listener()->OnFrameHeader(header)) {
+ DVLOG(2) << "OnFrameHeader rejected the frame, will discard; header: "
+ << header;
+ state_ = State::kDiscardPayload;
+ frame_decoder_state_.InitializeRemainders();
+ return DecodeStatus::kDecodeError;
+ }
+
+ if (header.payload_length > maximum_payload_size_) {
+ DVLOG(2) << "Payload length is greater than allowed: "
+ << header.payload_length << " > " << maximum_payload_size_
+ << "\n header: " << header;
+ state_ = State::kDiscardPayload;
+ frame_decoder_state_.InitializeRemainders();
+ listener()->OnFrameSizeError(header);
+ return DecodeStatus::kDecodeError;
+ }
+
+ // The decode buffer can extend across many frames. Make sure that the
+ // buffer we pass to the start method that is specific to the frame type
+ // does not exend beyond this frame.
+ DecodeBufferSubset subset(db, header.payload_length);
+ DecodeStatus status;
+ switch (header.type) {
+ case Http2FrameType::DATA:
+ status = StartDecodingDataPayload(&subset);
+ break;
+
+ case Http2FrameType::HEADERS:
+ status = StartDecodingHeadersPayload(&subset);
+ break;
+
+ case Http2FrameType::PRIORITY:
+ status = StartDecodingPriorityPayload(&subset);
+ break;
+
+ case Http2FrameType::RST_STREAM:
+ status = StartDecodingRstStreamPayload(&subset);
+ break;
+
+ case Http2FrameType::SETTINGS:
+ status = StartDecodingSettingsPayload(&subset);
+ break;
+
+ case Http2FrameType::PUSH_PROMISE:
+ status = StartDecodingPushPromisePayload(&subset);
+ break;
+
+ case Http2FrameType::PING:
+ status = StartDecodingPingPayload(&subset);
+ break;
+
+ case Http2FrameType::GOAWAY:
+ status = StartDecodingGoAwayPayload(&subset);
+ break;
+
+ case Http2FrameType::WINDOW_UPDATE:
+ status = StartDecodingWindowUpdatePayload(&subset);
+ break;
+
+ case Http2FrameType::CONTINUATION:
+ status = StartDecodingContinuationPayload(&subset);
+ break;
+
+ case Http2FrameType::ALTSVC:
+ status = StartDecodingAltSvcPayload(&subset);
+ break;
+
+ default:
+ status = StartDecodingUnknownPayload(&subset);
+ break;
+ }
+
+ if (status == DecodeStatus::kDecodeDone) {
+ state_ = State::kStartDecodingHeader;
+ return status;
+ } else if (status == DecodeStatus::kDecodeInProgress) {
+ state_ = State::kResumeDecodingPayload;
+ return status;
+ } else {
+ state_ = State::kDiscardPayload;
+ return status;
+ }
+}
+
+DecodeStatus Http2FrameDecoder::ResumeDecodingPayload(DecodeBuffer* db) {
+ // The decode buffer can extend across many frames. Make sure that the
+ // buffer we pass to the start method that is specific to the frame type
+ // does not exend beyond this frame.
+ size_t remaining = frame_decoder_state_.remaining_total_payload();
+ DCHECK_LE(remaining, frame_header().payload_length);
+ DecodeBufferSubset subset(db, remaining);
+ DecodeStatus status;
+ switch (frame_header().type) {
+ case Http2FrameType::DATA:
+ status = ResumeDecodingDataPayload(&subset);
+ break;
+
+ case Http2FrameType::HEADERS:
+ status = ResumeDecodingHeadersPayload(&subset);
+ break;
+
+ case Http2FrameType::PRIORITY:
+ status = ResumeDecodingPriorityPayload(&subset);
+ break;
+
+ case Http2FrameType::RST_STREAM:
+ status = ResumeDecodingRstStreamPayload(&subset);
+ break;
+
+ case Http2FrameType::SETTINGS:
+ status = ResumeDecodingSettingsPayload(&subset);
+ break;
+
+ case Http2FrameType::PUSH_PROMISE:
+ status = ResumeDecodingPushPromisePayload(&subset);
+ break;
+
+ case Http2FrameType::PING:
+ status = ResumeDecodingPingPayload(&subset);
+ break;
+
+ case Http2FrameType::GOAWAY:
+ status = ResumeDecodingGoAwayPayload(&subset);
+ break;
+
+ case Http2FrameType::WINDOW_UPDATE:
+ status = ResumeDecodingWindowUpdatePayload(&subset);
+ break;
+
+ case Http2FrameType::CONTINUATION:
+ status = ResumeDecodingContinuationPayload(&subset);
+ break;
+
+ case Http2FrameType::ALTSVC:
+ status = ResumeDecodingAltSvcPayload(&subset);
+ break;
+
+ default:
+ status = ResumeDecodingUnknownPayload(&subset);
+ break;
+ }
+
+ if (status == DecodeStatus::kDecodeDone) {
+ state_ = State::kStartDecodingHeader;
+ return status;
+ } else if (status == DecodeStatus::kDecodeInProgress) {
+ return status;
+ } else {
+ state_ = State::kDiscardPayload;
+ return status;
+ }
+}
+
+// Clear any of the flags in the frame header that aren't set in valid_flags.
+void Http2FrameDecoder::RetainFlags(uint8_t valid_flags) {
+ frame_decoder_state_.RetainFlags(valid_flags);
+}
+
+// Clear all of the flags in the frame header; for use with frame types that
+// don't define any flags, such as WINDOW_UPDATE.
+void Http2FrameDecoder::ClearFlags() {
+ frame_decoder_state_.ClearFlags();
+}
+
+DecodeStatus Http2FrameDecoder::StartDecodingAltSvcPayload(DecodeBuffer* db) {
+ ClearFlags();
+ return altsvc_payload_decoder_.StartDecodingPayload(&frame_decoder_state_,
+ db);
+}
+DecodeStatus Http2FrameDecoder::ResumeDecodingAltSvcPayload(DecodeBuffer* db) {
+ // The frame is not paddable.
+ DCHECK_EQ(frame_decoder_state_.remaining_total_payload(),
+ frame_decoder_state_.remaining_payload());
+ return altsvc_payload_decoder_.ResumeDecodingPayload(&frame_decoder_state_,
+ db);
+}
+
+DecodeStatus Http2FrameDecoder::StartDecodingContinuationPayload(
+ DecodeBuffer* db) {
+ RetainFlags(Http2FrameFlag::END_HEADERS);
+ return continuation_payload_decoder_.StartDecodingPayload(
+ &frame_decoder_state_, db);
+}
+DecodeStatus Http2FrameDecoder::ResumeDecodingContinuationPayload(
+ DecodeBuffer* db) {
+ // The frame is not paddable.
+ DCHECK_EQ(frame_decoder_state_.remaining_total_payload(),
+ frame_decoder_state_.remaining_payload());
+ return continuation_payload_decoder_.ResumeDecodingPayload(
+ &frame_decoder_state_, db);
+}
+
+DecodeStatus Http2FrameDecoder::StartDecodingDataPayload(DecodeBuffer* db) {
+ RetainFlags(Http2FrameFlag::END_STREAM | Http2FrameFlag::PADDED);
+ return data_payload_decoder_.StartDecodingPayload(&frame_decoder_state_, db);
+}
+DecodeStatus Http2FrameDecoder::ResumeDecodingDataPayload(DecodeBuffer* db) {
+ return data_payload_decoder_.ResumeDecodingPayload(&frame_decoder_state_, db);
+}
+
+DecodeStatus Http2FrameDecoder::StartDecodingGoAwayPayload(DecodeBuffer* db) {
+ ClearFlags();
+ return goaway_payload_decoder_.StartDecodingPayload(&frame_decoder_state_,
+ db);
+}
+DecodeStatus Http2FrameDecoder::ResumeDecodingGoAwayPayload(DecodeBuffer* db) {
+ // The frame is not paddable.
+ DCHECK_EQ(frame_decoder_state_.remaining_total_payload(),
+ frame_decoder_state_.remaining_payload());
+ return goaway_payload_decoder_.ResumeDecodingPayload(&frame_decoder_state_,
+ db);
+}
+
+DecodeStatus Http2FrameDecoder::StartDecodingHeadersPayload(DecodeBuffer* db) {
+ RetainFlags(Http2FrameFlag::END_STREAM | Http2FrameFlag::END_HEADERS |
+ Http2FrameFlag::PADDED | Http2FrameFlag::PRIORITY);
+ return headers_payload_decoder_.StartDecodingPayload(&frame_decoder_state_,
+ db);
+}
+DecodeStatus Http2FrameDecoder::ResumeDecodingHeadersPayload(DecodeBuffer* db) {
+ DCHECK_LE(frame_decoder_state_.remaining_payload_and_padding(),
+ frame_header().payload_length);
+ return headers_payload_decoder_.ResumeDecodingPayload(&frame_decoder_state_,
+ db);
+}
+
+DecodeStatus Http2FrameDecoder::StartDecodingPingPayload(DecodeBuffer* db) {
+ RetainFlags(Http2FrameFlag::ACK);
+ return ping_payload_decoder_.StartDecodingPayload(&frame_decoder_state_, db);
+}
+DecodeStatus Http2FrameDecoder::ResumeDecodingPingPayload(DecodeBuffer* db) {
+ // The frame is not paddable.
+ DCHECK_EQ(frame_decoder_state_.remaining_total_payload(),
+ frame_decoder_state_.remaining_payload());
+ return ping_payload_decoder_.ResumeDecodingPayload(&frame_decoder_state_, db);
+}
+
+DecodeStatus Http2FrameDecoder::StartDecodingPriorityPayload(DecodeBuffer* db) {
+ ClearFlags();
+ return priority_payload_decoder_.StartDecodingPayload(&frame_decoder_state_,
+ db);
+}
+DecodeStatus Http2FrameDecoder::ResumeDecodingPriorityPayload(
+ DecodeBuffer* db) {
+ // The frame is not paddable.
+ DCHECK_EQ(frame_decoder_state_.remaining_total_payload(),
+ frame_decoder_state_.remaining_payload());
+ return priority_payload_decoder_.ResumeDecodingPayload(&frame_decoder_state_,
+ db);
+}
+
+DecodeStatus Http2FrameDecoder::StartDecodingPushPromisePayload(
+ DecodeBuffer* db) {
+ RetainFlags(Http2FrameFlag::END_HEADERS | Http2FrameFlag::PADDED);
+ return push_promise_payload_decoder_.StartDecodingPayload(
+ &frame_decoder_state_, db);
+}
+DecodeStatus Http2FrameDecoder::ResumeDecodingPushPromisePayload(
+ DecodeBuffer* db) {
+ DCHECK_LE(frame_decoder_state_.remaining_payload_and_padding(),
+ frame_header().payload_length);
+ return push_promise_payload_decoder_.ResumeDecodingPayload(
+ &frame_decoder_state_, db);
+}
+
+DecodeStatus Http2FrameDecoder::StartDecodingRstStreamPayload(
+ DecodeBuffer* db) {
+ ClearFlags();
+ return rst_stream_payload_decoder_.StartDecodingPayload(&frame_decoder_state_,
+ db);
+}
+DecodeStatus Http2FrameDecoder::ResumeDecodingRstStreamPayload(
+ DecodeBuffer* db) {
+ // The frame is not paddable.
+ DCHECK_EQ(frame_decoder_state_.remaining_total_payload(),
+ frame_decoder_state_.remaining_payload());
+ return rst_stream_payload_decoder_.ResumeDecodingPayload(
+ &frame_decoder_state_, db);
+}
+
+DecodeStatus Http2FrameDecoder::StartDecodingSettingsPayload(DecodeBuffer* db) {
+ RetainFlags(Http2FrameFlag::ACK);
+ return settings_payload_decoder_.StartDecodingPayload(&frame_decoder_state_,
+ db);
+}
+DecodeStatus Http2FrameDecoder::ResumeDecodingSettingsPayload(
+ DecodeBuffer* db) {
+ // The frame is not paddable.
+ DCHECK_EQ(frame_decoder_state_.remaining_total_payload(),
+ frame_decoder_state_.remaining_payload());
+ return settings_payload_decoder_.ResumeDecodingPayload(&frame_decoder_state_,
+ db);
+}
+
+DecodeStatus Http2FrameDecoder::StartDecodingUnknownPayload(DecodeBuffer* db) {
+ // We don't known what type of frame this is, so we don't know which flags
+ // are valid, so we don't touch them.
+ return unknown_payload_decoder_.StartDecodingPayload(&frame_decoder_state_,
+ db);
+}
+DecodeStatus Http2FrameDecoder::ResumeDecodingUnknownPayload(DecodeBuffer* db) {
+ // We don't known what type of frame this is, so we treat it as not paddable.
+ DCHECK_EQ(frame_decoder_state_.remaining_total_payload(),
+ frame_decoder_state_.remaining_payload());
+ return unknown_payload_decoder_.ResumeDecodingPayload(&frame_decoder_state_,
+ db);
+}
+
+DecodeStatus Http2FrameDecoder::StartDecodingWindowUpdatePayload(
+ DecodeBuffer* db) {
+ ClearFlags();
+ return window_update_payload_decoder_.StartDecodingPayload(
+ &frame_decoder_state_, db);
+}
+DecodeStatus Http2FrameDecoder::ResumeDecodingWindowUpdatePayload(
+ DecodeBuffer* db) {
+ // The frame is not paddable.
+ DCHECK_EQ(frame_decoder_state_.remaining_total_payload(),
+ frame_decoder_state_.remaining_payload());
+ return window_update_payload_decoder_.ResumeDecodingPayload(
+ &frame_decoder_state_, db);
+}
+
+DecodeStatus Http2FrameDecoder::DiscardPayload(DecodeBuffer* db) {
+ DVLOG(2) << "remaining_payload=" << frame_decoder_state_.remaining_payload_
+ << "; remaining_padding=" << frame_decoder_state_.remaining_padding_;
+ frame_decoder_state_.remaining_payload_ +=
+ frame_decoder_state_.remaining_padding_;
+ frame_decoder_state_.remaining_padding_ = 0;
+ const size_t avail = frame_decoder_state_.AvailablePayload(db);
+ DVLOG(2) << "avail=" << avail;
+ if (avail > 0) {
+ frame_decoder_state_.ConsumePayload(avail);
+ db->AdvanceCursor(avail);
+ }
+ if (frame_decoder_state_.remaining_payload_ == 0) {
+ state_ = State::kStartDecodingHeader;
+ return DecodeStatus::kDecodeDone;
+ }
+ return DecodeStatus::kDecodeInProgress;
+}
+
+} // namespace http2
diff --git a/chromium/net/third_party/quiche/src/http2/decoder/http2_frame_decoder.h b/chromium/net/third_party/quiche/src/http2/decoder/http2_frame_decoder.h
new file mode 100644
index 00000000000..6257bbeb825
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/decoder/http2_frame_decoder.h
@@ -0,0 +1,205 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_HTTP2_DECODER_HTTP2_FRAME_DECODER_H_
+#define QUICHE_HTTP2_DECODER_HTTP2_FRAME_DECODER_H_
+
+// Http2FrameDecoder decodes the available input until it reaches the end of
+// the input or it reaches the end of the first frame in the input.
+// Note that Http2FrameDecoder does only minimal validation; for example,
+// stream ids are not checked, nor is the sequence of frames such as
+// CONTINUATION frame placement.
+//
+// Http2FrameDecoder enters state kError once it has called the listener's
+// OnFrameSizeError or OnPaddingTooLong methods, and at this time has no
+// provision for leaving that state. While the HTTP/2 spec (RFC7540) allows
+// for some such errors to be considered as just stream errors in some cases,
+// this implementation treats them all as connection errors.
+
+#include <stddef.h>
+
+#include <cstdint>
+
+#include "base/logging.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_status.h"
+#include "net/third_party/quiche/src/http2/decoder/frame_decoder_state.h"
+#include "net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener.h"
+#include "net/third_party/quiche/src/http2/decoder/payload_decoders/altsvc_payload_decoder.h"
+#include "net/third_party/quiche/src/http2/decoder/payload_decoders/continuation_payload_decoder.h"
+#include "net/third_party/quiche/src/http2/decoder/payload_decoders/data_payload_decoder.h"
+#include "net/third_party/quiche/src/http2/decoder/payload_decoders/goaway_payload_decoder.h"
+#include "net/third_party/quiche/src/http2/decoder/payload_decoders/headers_payload_decoder.h"
+#include "net/third_party/quiche/src/http2/decoder/payload_decoders/ping_payload_decoder.h"
+#include "net/third_party/quiche/src/http2/decoder/payload_decoders/priority_payload_decoder.h"
+#include "net/third_party/quiche/src/http2/decoder/payload_decoders/push_promise_payload_decoder.h"
+#include "net/third_party/quiche/src/http2/decoder/payload_decoders/rst_stream_payload_decoder.h"
+#include "net/third_party/quiche/src/http2/decoder/payload_decoders/settings_payload_decoder.h"
+#include "net/third_party/quiche/src/http2/decoder/payload_decoders/unknown_payload_decoder.h"
+#include "net/third_party/quiche/src/http2/decoder/payload_decoders/window_update_payload_decoder.h"
+#include "net/third_party/quiche/src/http2/http2_structures.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_export.h"
+
+namespace http2 {
+namespace test {
+class Http2FrameDecoderPeer;
+} // namespace test
+
+class HTTP2_EXPORT_PRIVATE Http2FrameDecoder {
+ public:
+ explicit Http2FrameDecoder(Http2FrameDecoderListener* listener);
+ Http2FrameDecoder() : Http2FrameDecoder(nullptr) {}
+
+ Http2FrameDecoder(const Http2FrameDecoder&) = delete;
+ Http2FrameDecoder& operator=(const Http2FrameDecoder&) = delete;
+
+ // The decoder will call the listener's methods as it decodes a frame.
+ void set_listener(Http2FrameDecoderListener* listener);
+ Http2FrameDecoderListener* listener() const;
+
+ // The decoder will reject frame's whose payload
+ // length field exceeds the maximum payload size.
+ void set_maximum_payload_size(size_t v) { maximum_payload_size_ = v; }
+ size_t maximum_payload_size() const { return maximum_payload_size_; }
+
+ // Decodes the input up to the next frame boundary (i.e. at most one frame).
+ //
+ // Returns kDecodeDone if it decodes the final byte of a frame, OR if there
+ // is no input and it is awaiting the start of a new frame (e.g. if this
+ // is the first call to DecodeFrame, or if the previous call returned
+ // kDecodeDone).
+ //
+ // Returns kDecodeInProgress if it decodes all of the decode buffer, but has
+ // not reached the end of the frame.
+ //
+ // Returns kDecodeError if the frame's padding or length wasn't valid (i.e. if
+ // the decoder called either the listener's OnPaddingTooLong or
+ // OnFrameSizeError method).
+ DecodeStatus DecodeFrame(DecodeBuffer* db);
+
+ //////////////////////////////////////////////////////////////////////////////
+ // Methods that support Http2FrameDecoderAdapter.
+
+ // Is the remainder of the frame's payload being discarded?
+ bool IsDiscardingPayload() const { return state_ == State::kDiscardPayload; }
+
+ // Returns the number of bytes of the frame's payload that remain to be
+ // decoded, excluding any trailing padding. This method must only be called
+ // after the frame header has been decoded AND DecodeFrame has returned
+ // kDecodeInProgress.
+ size_t remaining_payload() const;
+
+ // Returns the number of bytes of trailing padding after the payload that
+ // remain to be decoded. This method must only be called if the frame type
+ // allows padding, and after the frame header has been decoded AND
+ // DecodeFrame has returned. Will return 0 if the Pad Length field has not
+ // yet been decoded.
+ uint32_t remaining_padding() const;
+
+ private:
+ enum class State {
+ // Ready to start decoding a new frame's header.
+ kStartDecodingHeader,
+ // Was in state kStartDecodingHeader, but unable to read the entire frame
+ // header, so needs more input to complete decoding the header.
+ kResumeDecodingHeader,
+
+ // Have decoded the frame header, and started decoding the available bytes
+ // of the frame's payload, but need more bytes to finish the job.
+ kResumeDecodingPayload,
+
+ // Decoding of the most recently started frame resulted in an error:
+ // OnPaddingTooLong or OnFrameSizeError was called to indicate that the
+ // decoder detected a problem, or OnFrameHeader returned false, indicating
+ // that the listener detected a problem. Regardless of which, the decoder
+ // will stay in state kDiscardPayload until it has been passed the rest
+ // of the bytes of the frame's payload that it hasn't yet seen, after
+ // which it will be ready to decode another frame.
+ kDiscardPayload,
+ };
+
+ friend class test::Http2FrameDecoderPeer;
+ friend std::ostream& operator<<(std::ostream& out, State v);
+
+ DecodeStatus StartDecodingPayload(DecodeBuffer* db);
+ DecodeStatus ResumeDecodingPayload(DecodeBuffer* db);
+ DecodeStatus DiscardPayload(DecodeBuffer* db);
+
+ const Http2FrameHeader& frame_header() const {
+ return frame_decoder_state_.frame_header();
+ }
+
+ // Clear any of the flags in the frame header that aren't set in valid_flags.
+ void RetainFlags(uint8_t valid_flags);
+
+ // Clear all of the flags in the frame header; for use with frame types that
+ // don't define any flags, such as WINDOW_UPDATE.
+ void ClearFlags();
+
+ // These methods call the StartDecodingPayload() method of the frame type's
+ // payload decoder, after first clearing invalid flags in the header. The
+ // caller must ensure that the decode buffer does not extend beyond the
+ // end of the payload (handled by Http2FrameDecoder::StartDecodingPayload).
+ DecodeStatus StartDecodingAltSvcPayload(DecodeBuffer* db);
+ DecodeStatus StartDecodingContinuationPayload(DecodeBuffer* db);
+ DecodeStatus StartDecodingDataPayload(DecodeBuffer* db);
+ DecodeStatus StartDecodingGoAwayPayload(DecodeBuffer* db);
+ DecodeStatus StartDecodingHeadersPayload(DecodeBuffer* db);
+ DecodeStatus StartDecodingPingPayload(DecodeBuffer* db);
+ DecodeStatus StartDecodingPriorityPayload(DecodeBuffer* db);
+ DecodeStatus StartDecodingPushPromisePayload(DecodeBuffer* db);
+ DecodeStatus StartDecodingRstStreamPayload(DecodeBuffer* db);
+ DecodeStatus StartDecodingSettingsPayload(DecodeBuffer* db);
+ DecodeStatus StartDecodingUnknownPayload(DecodeBuffer* db);
+ DecodeStatus StartDecodingWindowUpdatePayload(DecodeBuffer* db);
+
+ // These methods call the ResumeDecodingPayload() method of the frame type's
+ // payload decoder; they are called only if the preceding call to the
+ // corresponding Start method (above) returned kDecodeInProgress, as did any
+ // subsequent calls to the resume method.
+ // Unlike the Start methods, the decode buffer may extend beyond the
+ // end of the payload, so the method will create a DecodeBufferSubset
+ // before calling the ResumeDecodingPayload method of the frame type's
+ // payload decoder.
+ DecodeStatus ResumeDecodingAltSvcPayload(DecodeBuffer* db);
+ DecodeStatus ResumeDecodingContinuationPayload(DecodeBuffer* db);
+ DecodeStatus ResumeDecodingDataPayload(DecodeBuffer* db);
+ DecodeStatus ResumeDecodingGoAwayPayload(DecodeBuffer* db);
+ DecodeStatus ResumeDecodingHeadersPayload(DecodeBuffer* db);
+ DecodeStatus ResumeDecodingPingPayload(DecodeBuffer* db);
+ DecodeStatus ResumeDecodingPriorityPayload(DecodeBuffer* db);
+ DecodeStatus ResumeDecodingPushPromisePayload(DecodeBuffer* db);
+ DecodeStatus ResumeDecodingRstStreamPayload(DecodeBuffer* db);
+ DecodeStatus ResumeDecodingSettingsPayload(DecodeBuffer* db);
+ DecodeStatus ResumeDecodingUnknownPayload(DecodeBuffer* db);
+ DecodeStatus ResumeDecodingWindowUpdatePayload(DecodeBuffer* db);
+
+ FrameDecoderState frame_decoder_state_;
+
+ // We only need one payload decoder at a time, so they share the same storage.
+ union {
+ AltSvcPayloadDecoder altsvc_payload_decoder_;
+ ContinuationPayloadDecoder continuation_payload_decoder_;
+ DataPayloadDecoder data_payload_decoder_;
+ GoAwayPayloadDecoder goaway_payload_decoder_;
+ HeadersPayloadDecoder headers_payload_decoder_;
+ PingPayloadDecoder ping_payload_decoder_;
+ PriorityPayloadDecoder priority_payload_decoder_;
+ PushPromisePayloadDecoder push_promise_payload_decoder_;
+ RstStreamPayloadDecoder rst_stream_payload_decoder_;
+ SettingsPayloadDecoder settings_payload_decoder_;
+ UnknownPayloadDecoder unknown_payload_decoder_;
+ WindowUpdatePayloadDecoder window_update_payload_decoder_;
+ };
+
+ State state_;
+ size_t maximum_payload_size_;
+
+ // Listener used whenever caller passes nullptr to set_listener.
+ Http2FrameDecoderNoOpListener no_op_listener_;
+};
+
+} // namespace http2
+
+#endif // QUICHE_HTTP2_DECODER_HTTP2_FRAME_DECODER_H_
diff --git a/chromium/net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener.cc b/chromium/net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener.cc
new file mode 100644
index 00000000000..4cbce66d6f1
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener.cc
@@ -0,0 +1,14 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener.h"
+
+namespace http2 {
+
+bool Http2FrameDecoderNoOpListener::OnFrameHeader(
+ const Http2FrameHeader& header) {
+ return true;
+}
+
+} // namespace http2
diff --git a/chromium/net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener.h b/chromium/net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener.h
new file mode 100644
index 00000000000..b87512257a4
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener.h
@@ -0,0 +1,357 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_HTTP2_DECODER_HTTP2_FRAME_DECODER_LISTENER_H_
+#define QUICHE_HTTP2_DECODER_HTTP2_FRAME_DECODER_LISTENER_H_
+
+// Http2FrameDecoderListener is the interface which the HTTP/2 decoder uses
+// to report the decoded frames to a listener.
+//
+// The general design is to assume that the listener will copy the data it needs
+// (e.g. frame headers) and will keep track of the implicit state of the
+// decoding process (i.e. the decoder maintains just the information it needs in
+// order to perform the decoding). Therefore, the parameters are just those with
+// (potentially) new data, not previously provided info about the current frame.
+//
+// The calls are described as if they are made in quick succession, i.e. one
+// after another, but of course the decoder needs input to decode, and the
+// decoder will only call the listener once the necessary input has been
+// provided. For example: OnDataStart can only be called once the 9 bytes of
+// of an HTTP/2 common frame header have been received. The decoder will call
+// the listener methods as soon as possible to avoid almost all buffering.
+//
+// The listener interface is designed so that it is possible to exactly
+// reconstruct the serialized frames, with the exception of reserved bits,
+// including in the frame header's flags and stream_id fields, which will have
+// been cleared before the methods below are called.
+
+#include <stddef.h>
+
+#include <cstdint>
+#include <type_traits>
+
+#include "net/third_party/quiche/src/http2/http2_constants.h"
+#include "net/third_party/quiche/src/http2/http2_structures.h"
+
+namespace http2 {
+
+// TODO(jamessynge): Consider sorting the methods by frequency of call, if that
+// helps at all.
+class Http2FrameDecoderListener {
+ public:
+ Http2FrameDecoderListener() {}
+ virtual ~Http2FrameDecoderListener() {}
+
+ // Called once the common frame header has been decoded for any frame, and
+ // before any of the methods below, which will also be called. This method is
+ // included in this interface only for the purpose of supporting SpdyFramer
+ // semantics via an adapter. This is the only method that has a non-void
+ // return type, and this is just so that Http2FrameDecoderAdapter (called
+ // from SpdyFramer) can more readily pass existing tests that expect decoding
+ // to stop if the headers alone indicate an error. Return false to stop
+ // decoding just after decoding the header, else return true to continue
+ // decoding.
+ // TODO(jamessynge): Remove OnFrameHeader once done with supporting
+ // SpdyFramer's exact states.
+ virtual bool OnFrameHeader(const Http2FrameHeader& header) = 0;
+
+ //////////////////////////////////////////////////////////////////////////////
+
+ // Called once the common frame header has been decoded for a DATA frame,
+ // before examining the frame's payload, after which:
+ // OnPadLength will be called if header.IsPadded() is true, i.e. if the
+ // PADDED flag is set;
+ // OnDataPayload will be called as the non-padding portion of the payload
+ // is available until all of it has been provided;
+ // OnPadding will be called if the frame is padded AND the Pad Length field
+ // is greater than zero;
+ // OnDataEnd will be called last. If the frame is unpadded and has no
+ // payload, then this will be called immediately after OnDataStart.
+ virtual void OnDataStart(const Http2FrameHeader& header) = 0;
+
+ // Called when the next non-padding portion of a DATA frame's payload is
+ // received.
+ // |data| The start of |len| bytes of data.
+ // |len| The length of the data buffer. Maybe zero in some cases, which does
+ // not mean anything special.
+ virtual void OnDataPayload(const char* data, size_t len) = 0;
+
+ // Called after an entire DATA frame has been received.
+ // If header.IsEndStream() == true, this is the last data for the stream.
+ virtual void OnDataEnd() = 0;
+
+ // Called once the common frame header has been decoded for a HEADERS frame,
+ // before examining the frame's payload, after which:
+ // OnPadLength will be called if header.IsPadded() is true, i.e. if the
+ // PADDED flag is set;
+ // OnHeadersPriority will be called if header.HasPriority() is true, i.e. if
+ // the frame has the PRIORITY flag;
+ // OnHpackFragment as the remainder of the non-padding payload is available
+ // until all if has been provided;
+ // OnPadding will be called if the frame is padded AND the Pad Length field
+ // is greater than zero;
+ // OnHeadersEnd will be called last; If the frame is unpadded and has no
+ // payload, then this will be called immediately after OnHeadersStart;
+ // OnHeadersEnd indicates the end of the HPACK block only if the frame
+ // header had the END_HEADERS flag set, else the END_HEADERS should be
+ // looked for on a subsequent CONTINUATION frame.
+ virtual void OnHeadersStart(const Http2FrameHeader& header) = 0;
+
+ // Called when a HEADERS frame is received with the PRIORITY flag set and
+ // the priority fields have been decoded.
+ virtual void OnHeadersPriority(
+ const Http2PriorityFields& priority_fields) = 0;
+
+ // Called when a fragment (i.e. some or all of an HPACK Block) is received;
+ // this may be part of a HEADERS, PUSH_PROMISE or CONTINUATION frame.
+ // |data| The start of |len| bytes of data.
+ // |len| The length of the data buffer. Maybe zero in some cases, which does
+ // not mean anything special, except that it simplified the decoder.
+ virtual void OnHpackFragment(const char* data, size_t len) = 0;
+
+ // Called after an entire HEADERS frame has been received. The frame is the
+ // end of the HEADERS if the END_HEADERS flag is set; else there should be
+ // CONTINUATION frames after this frame.
+ virtual void OnHeadersEnd() = 0;
+
+ // Called when an entire PRIORITY frame has been decoded.
+ virtual void OnPriorityFrame(const Http2FrameHeader& header,
+ const Http2PriorityFields& priority_fields) = 0;
+
+ // Called once the common frame header has been decoded for a CONTINUATION
+ // frame, before examining the frame's payload, after which:
+ // OnHpackFragment as the frame's payload is available until all of it
+ // has been provided;
+ // OnContinuationEnd will be called last; If the frame has no payload,
+ // then this will be called immediately after OnContinuationStart;
+ // the HPACK block is at an end if and only if the frame header passed
+ // to OnContinuationStart had the END_HEADERS flag set.
+ virtual void OnContinuationStart(const Http2FrameHeader& header) = 0;
+
+ // Called after an entire CONTINUATION frame has been received. The frame is
+ // the end of the HEADERS if the END_HEADERS flag is set.
+ virtual void OnContinuationEnd() = 0;
+
+ // Called when Pad Length field has been read. Applies to DATA and HEADERS
+ // frames. For PUSH_PROMISE frames, the Pad Length + 1 is provided in the
+ // OnPushPromiseStart call as total_padding_length.
+ virtual void OnPadLength(size_t pad_length) = 0;
+
+ // Called when padding is skipped over.
+ virtual void OnPadding(const char* padding, size_t skipped_length) = 0;
+
+ // Called when an entire RST_STREAM frame has been decoded.
+ // This is the only callback for RST_STREAM frames.
+ virtual void OnRstStream(const Http2FrameHeader& header,
+ Http2ErrorCode error_code) = 0;
+
+ // Called once the common frame header has been decoded for a SETTINGS frame
+ // without the ACK flag, before examining the frame's payload, after which:
+ // OnSetting will be called in turn for each pair of settings parameter and
+ // value found in the payload;
+ // OnSettingsEnd will be called last; If the frame has no payload,
+ // then this will be called immediately after OnSettingsStart.
+ // The frame header is passed so that the caller can check the stream_id,
+ // which should be zero, but that hasn't been checked by the decoder.
+ virtual void OnSettingsStart(const Http2FrameHeader& header) = 0;
+
+ // Called for each setting parameter and value within a SETTINGS frame.
+ virtual void OnSetting(const Http2SettingFields& setting_fields) = 0;
+
+ // Called after parsing the complete payload of SETTINGS frame (non-ACK).
+ virtual void OnSettingsEnd() = 0;
+
+ // Called when an entire SETTINGS frame, with the ACK flag, has been decoded.
+ virtual void OnSettingsAck(const Http2FrameHeader& header) = 0;
+
+ // Called just before starting to process the HPACK block of a PUSH_PROMISE
+ // frame. The Pad Length field has already been decoded at this point, so
+ // OnPadLength will not be called; note that total_padding_length is Pad
+ // Length + 1. After OnPushPromiseStart:
+ // OnHpackFragment as the remainder of the non-padding payload is available
+ // until all if has been provided;
+ // OnPadding will be called if the frame is padded AND the Pad Length field
+ // is greater than zero (i.e. total_padding_length > 1);
+ // OnPushPromiseEnd will be called last; If the frame is unpadded and has no
+ // payload, then this will be called immediately after OnPushPromiseStart.
+ virtual void OnPushPromiseStart(const Http2FrameHeader& header,
+ const Http2PushPromiseFields& promise,
+ size_t total_padding_length) = 0;
+
+ // Called after all of the HPACK block fragment and padding of a PUSH_PROMISE
+ // has been decoded and delivered to the listener. This call indicates the end
+ // of the HPACK block if and only if the frame header had the END_HEADERS flag
+ // set (i.e. header.IsEndHeaders() is true); otherwise the next block must be
+ // a CONTINUATION frame with the same stream id (not the same promised stream
+ // id).
+ virtual void OnPushPromiseEnd() = 0;
+
+ // Called when an entire PING frame, without the ACK flag, has been decoded.
+ virtual void OnPing(const Http2FrameHeader& header,
+ const Http2PingFields& ping) = 0;
+
+ // Called when an entire PING frame, with the ACK flag, has been decoded.
+ virtual void OnPingAck(const Http2FrameHeader& header,
+ const Http2PingFields& ping) = 0;
+
+ // Called after parsing a GOAWAY frame's header and fixed size fields, after
+ // which:
+ // OnGoAwayOpaqueData will be called as opaque data of the payload becomes
+ // available to the decoder, until all of it has been provided to the
+ // listener;
+ // OnGoAwayEnd will be called last, after all the opaque data has been
+ // provided to the listener; if there is no opaque data, then OnGoAwayEnd
+ // will be called immediately after OnGoAwayStart.
+ virtual void OnGoAwayStart(const Http2FrameHeader& header,
+ const Http2GoAwayFields& goaway) = 0;
+
+ // Called when the next portion of a GOAWAY frame's payload is received.
+ // |data| The start of |len| bytes of opaque data.
+ // |len| The length of the opaque data buffer. Maybe zero in some cases,
+ // which does not mean anything special.
+ virtual void OnGoAwayOpaqueData(const char* data, size_t len) = 0;
+
+ // Called after finishing decoding all of a GOAWAY frame.
+ virtual void OnGoAwayEnd() = 0;
+
+ // Called when an entire WINDOW_UPDATE frame has been decoded. The
+ // window_size_increment is required to be non-zero, but that has not been
+ // checked. If header.stream_id==0, the connection's flow control window is
+ // being increased, else the specified stream's flow control is being
+ // increased.
+ virtual void OnWindowUpdate(const Http2FrameHeader& header,
+ uint32_t window_size_increment) = 0;
+
+ // Called when an ALTSVC frame header and origin length have been parsed.
+ // Either or both lengths may be zero. After OnAltSvcStart:
+ // OnAltSvcOriginData will be called until all of the (optional) Origin
+ // has been provided;
+ // OnAltSvcValueData will be called until all of the Alt-Svc-Field-Value
+ // has been provided;
+ // OnAltSvcEnd will called last, after all of the origin and
+ // Alt-Svc-Field-Value have been delivered to the listener.
+ virtual void OnAltSvcStart(const Http2FrameHeader& header,
+ size_t origin_length,
+ size_t value_length) = 0;
+
+ // Called when decoding the (optional) origin of an ALTSVC;
+ // the field is uninterpreted.
+ virtual void OnAltSvcOriginData(const char* data, size_t len) = 0;
+
+ // Called when decoding the Alt-Svc-Field-Value of an ALTSVC;
+ // the field is uninterpreted.
+ virtual void OnAltSvcValueData(const char* data, size_t len) = 0;
+
+ // Called after decoding all of a ALTSVC frame and providing to the listener
+ // via the above methods.
+ virtual void OnAltSvcEnd() = 0;
+
+ // Called when the common frame header has been decoded, but the frame type
+ // is unknown, after which:
+ // OnUnknownPayload is called as the payload of the frame is provided to the
+ // decoder, until all of the payload has been decoded;
+ // OnUnknownEnd will called last, after the entire frame of the unknown type
+ // has been decoded and provided to the listener.
+ virtual void OnUnknownStart(const Http2FrameHeader& header) = 0;
+
+ // Called when the payload of an unknown frame type is received.
+ // |data| A buffer containing the data received.
+ // |len| The length of the data buffer.
+ virtual void OnUnknownPayload(const char* data, size_t len) = 0;
+
+ // Called after decoding all of the payload of an unknown frame type.
+ virtual void OnUnknownEnd() = 0;
+
+ //////////////////////////////////////////////////////////////////////////////
+ // Below here are events indicating a problem has been detected during
+ // decoding (i.e. the received frames are malformed in some way).
+
+ // Padding field (uint8) has a value that is too large (i.e. the amount of
+ // padding is greater than the remainder of the payload that isn't required).
+ // From RFC Section 6.1, DATA:
+ // If the length of the padding is the length of the frame payload or
+ // greater, the recipient MUST treat this as a connection error
+ // (Section 5.4.1) of type PROTOCOL_ERROR.
+ // The same is true for HEADERS and PUSH_PROMISE.
+ virtual void OnPaddingTooLong(const Http2FrameHeader& header,
+ size_t missing_length) = 0;
+
+ // Frame size error. Depending upon the effected frame, this may or may not
+ // require terminating the connection, though that is probably the best thing
+ // to do.
+ // From RFC Section 4.2, Frame Size:
+ // An endpoint MUST send an error code of FRAME_SIZE_ERROR if a frame
+ // exceeds the size defined in SETTINGS_MAX_FRAME_SIZE, exceeds any limit
+ // defined for the frame type, or is too small to contain mandatory frame
+ // data. A frame size error in a frame that could alter the state of the
+ // the entire connection MUST be treated as a connection error
+ // (Section 5.4.1); this includes any frame carrying a header block
+ // (Section 4.3) (that is, HEADERS, PUSH_PROMISE, and CONTINUATION),
+ // SETTINGS, and any frame with a stream identifier of 0.
+ virtual void OnFrameSizeError(const Http2FrameHeader& header) = 0;
+};
+
+// Do nothing for each call. Useful for ignoring a frame that is invalid.
+class Http2FrameDecoderNoOpListener : public Http2FrameDecoderListener {
+ public:
+ Http2FrameDecoderNoOpListener() {}
+ ~Http2FrameDecoderNoOpListener() override {}
+
+ // TODO(jamessynge): Remove OnFrameHeader once done with supporting
+ // SpdyFramer's exact states.
+ bool OnFrameHeader(const Http2FrameHeader& header) override;
+
+ void OnDataStart(const Http2FrameHeader& header) override {}
+ void OnDataPayload(const char* data, size_t len) override {}
+ void OnDataEnd() override {}
+ void OnHeadersStart(const Http2FrameHeader& header) override {}
+ void OnHeadersPriority(const Http2PriorityFields& priority) override {}
+ void OnHpackFragment(const char* data, size_t len) override {}
+ void OnHeadersEnd() override {}
+ void OnPriorityFrame(const Http2FrameHeader& header,
+ const Http2PriorityFields& priority) override {}
+ void OnContinuationStart(const Http2FrameHeader& header) override {}
+ void OnContinuationEnd() override {}
+ void OnPadLength(size_t trailing_length) override {}
+ void OnPadding(const char* padding, size_t skipped_length) override {}
+ void OnRstStream(const Http2FrameHeader& header,
+ Http2ErrorCode error_code) override {}
+ void OnSettingsStart(const Http2FrameHeader& header) override {}
+ void OnSetting(const Http2SettingFields& setting_fields) override {}
+ void OnSettingsEnd() override {}
+ void OnSettingsAck(const Http2FrameHeader& header) override {}
+ void OnPushPromiseStart(const Http2FrameHeader& header,
+ const Http2PushPromiseFields& promise,
+ size_t total_padding_length) override {}
+ void OnPushPromiseEnd() override {}
+ void OnPing(const Http2FrameHeader& header,
+ const Http2PingFields& ping) override {}
+ void OnPingAck(const Http2FrameHeader& header,
+ const Http2PingFields& ping) override {}
+ void OnGoAwayStart(const Http2FrameHeader& header,
+ const Http2GoAwayFields& goaway) override {}
+ void OnGoAwayOpaqueData(const char* data, size_t len) override {}
+ void OnGoAwayEnd() override {}
+ void OnWindowUpdate(const Http2FrameHeader& header,
+ uint32_t increment) override {}
+ void OnAltSvcStart(const Http2FrameHeader& header,
+ size_t origin_length,
+ size_t value_length) override {}
+ void OnAltSvcOriginData(const char* data, size_t len) override {}
+ void OnAltSvcValueData(const char* data, size_t len) override {}
+ void OnAltSvcEnd() override {}
+ void OnUnknownStart(const Http2FrameHeader& header) override {}
+ void OnUnknownPayload(const char* data, size_t len) override {}
+ void OnUnknownEnd() override {}
+ void OnPaddingTooLong(const Http2FrameHeader& header,
+ size_t missing_length) override {}
+ void OnFrameSizeError(const Http2FrameHeader& header) override {}
+};
+
+static_assert(!std::is_abstract<Http2FrameDecoderNoOpListener>(),
+ "Http2FrameDecoderNoOpListener ought to be concrete.");
+
+} // namespace http2
+
+#endif // QUICHE_HTTP2_DECODER_HTTP2_FRAME_DECODER_LISTENER_H_
diff --git a/chromium/net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener_test_util.cc b/chromium/net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener_test_util.cc
new file mode 100644
index 00000000000..bc8cf1d5f72
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener_test_util.cc
@@ -0,0 +1,488 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener_test_util.h"
+
+#include "base/logging.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener.h"
+#include "net/third_party/quiche/src/http2/http2_constants.h"
+#include "net/third_party/quiche/src/http2/http2_structures.h"
+
+namespace http2 {
+
+FailingHttp2FrameDecoderListener::FailingHttp2FrameDecoderListener() = default;
+FailingHttp2FrameDecoderListener::~FailingHttp2FrameDecoderListener() = default;
+
+bool FailingHttp2FrameDecoderListener::OnFrameHeader(
+ const Http2FrameHeader& header) {
+ ADD_FAILURE() << "OnFrameHeader: " << header;
+ return false;
+}
+
+void FailingHttp2FrameDecoderListener::OnDataStart(
+ const Http2FrameHeader& header) {
+ FAIL() << "OnDataStart: " << header;
+}
+
+void FailingHttp2FrameDecoderListener::OnDataPayload(const char* data,
+ size_t len) {
+ FAIL() << "OnDataPayload: len=" << len;
+}
+
+void FailingHttp2FrameDecoderListener::OnDataEnd() {
+ FAIL() << "OnDataEnd";
+}
+
+void FailingHttp2FrameDecoderListener::OnHeadersStart(
+ const Http2FrameHeader& header) {
+ FAIL() << "OnHeadersStart: " << header;
+}
+
+void FailingHttp2FrameDecoderListener::OnHeadersPriority(
+ const Http2PriorityFields& priority) {
+ FAIL() << "OnHeadersPriority: " << priority;
+}
+
+void FailingHttp2FrameDecoderListener::OnHpackFragment(const char* data,
+ size_t len) {
+ FAIL() << "OnHpackFragment: len=" << len;
+}
+
+void FailingHttp2FrameDecoderListener::OnHeadersEnd() {
+ FAIL() << "OnHeadersEnd";
+}
+
+void FailingHttp2FrameDecoderListener::OnPriorityFrame(
+ const Http2FrameHeader& header,
+ const Http2PriorityFields& priority) {
+ FAIL() << "OnPriorityFrame: " << header << "; priority: " << priority;
+}
+
+void FailingHttp2FrameDecoderListener::OnContinuationStart(
+ const Http2FrameHeader& header) {
+ FAIL() << "OnContinuationStart: " << header;
+}
+
+void FailingHttp2FrameDecoderListener::OnContinuationEnd() {
+ FAIL() << "OnContinuationEnd";
+}
+
+void FailingHttp2FrameDecoderListener::OnPadLength(size_t trailing_length) {
+ FAIL() << "OnPadLength: trailing_length=" << trailing_length;
+}
+
+void FailingHttp2FrameDecoderListener::OnPadding(const char* padding,
+ size_t skipped_length) {
+ FAIL() << "OnPadding: skipped_length=" << skipped_length;
+}
+
+void FailingHttp2FrameDecoderListener::OnRstStream(
+ const Http2FrameHeader& header,
+ Http2ErrorCode error_code) {
+ FAIL() << "OnRstStream: " << header << "; code=" << error_code;
+}
+
+void FailingHttp2FrameDecoderListener::OnSettingsStart(
+ const Http2FrameHeader& header) {
+ FAIL() << "OnSettingsStart: " << header;
+}
+
+void FailingHttp2FrameDecoderListener::OnSetting(
+ const Http2SettingFields& setting_fields) {
+ FAIL() << "OnSetting: " << setting_fields;
+}
+
+void FailingHttp2FrameDecoderListener::OnSettingsEnd() {
+ FAIL() << "OnSettingsEnd";
+}
+
+void FailingHttp2FrameDecoderListener::OnSettingsAck(
+ const Http2FrameHeader& header) {
+ FAIL() << "OnSettingsAck: " << header;
+}
+
+void FailingHttp2FrameDecoderListener::OnPushPromiseStart(
+ const Http2FrameHeader& header,
+ const Http2PushPromiseFields& promise,
+ size_t total_padding_length) {
+ FAIL() << "OnPushPromiseStart: " << header << "; promise: " << promise
+ << "; total_padding_length: " << total_padding_length;
+}
+
+void FailingHttp2FrameDecoderListener::OnPushPromiseEnd() {
+ FAIL() << "OnPushPromiseEnd";
+}
+
+void FailingHttp2FrameDecoderListener::OnPing(const Http2FrameHeader& header,
+ const Http2PingFields& ping) {
+ FAIL() << "OnPing: " << header << "; ping: " << ping;
+}
+
+void FailingHttp2FrameDecoderListener::OnPingAck(const Http2FrameHeader& header,
+ const Http2PingFields& ping) {
+ FAIL() << "OnPingAck: " << header << "; ping: " << ping;
+}
+
+void FailingHttp2FrameDecoderListener::OnGoAwayStart(
+ const Http2FrameHeader& header,
+ const Http2GoAwayFields& goaway) {
+ FAIL() << "OnGoAwayStart: " << header << "; goaway: " << goaway;
+}
+
+void FailingHttp2FrameDecoderListener::OnGoAwayOpaqueData(const char* data,
+ size_t len) {
+ FAIL() << "OnGoAwayOpaqueData: len=" << len;
+}
+
+void FailingHttp2FrameDecoderListener::OnGoAwayEnd() {
+ FAIL() << "OnGoAwayEnd";
+}
+
+void FailingHttp2FrameDecoderListener::OnWindowUpdate(
+ const Http2FrameHeader& header,
+ uint32_t increment) {
+ FAIL() << "OnWindowUpdate: " << header << "; increment=" << increment;
+}
+
+void FailingHttp2FrameDecoderListener::OnAltSvcStart(
+ const Http2FrameHeader& header,
+ size_t origin_length,
+ size_t value_length) {
+ FAIL() << "OnAltSvcStart: " << header << "; origin_length: " << origin_length
+ << "; value_length: " << value_length;
+}
+
+void FailingHttp2FrameDecoderListener::OnAltSvcOriginData(const char* data,
+ size_t len) {
+ FAIL() << "OnAltSvcOriginData: len=" << len;
+}
+
+void FailingHttp2FrameDecoderListener::OnAltSvcValueData(const char* data,
+ size_t len) {
+ FAIL() << "OnAltSvcValueData: len=" << len;
+}
+
+void FailingHttp2FrameDecoderListener::OnAltSvcEnd() {
+ FAIL() << "OnAltSvcEnd";
+}
+
+void FailingHttp2FrameDecoderListener::OnUnknownStart(
+ const Http2FrameHeader& header) {
+ FAIL() << "OnUnknownStart: " << header;
+}
+
+void FailingHttp2FrameDecoderListener::OnUnknownPayload(const char* data,
+ size_t len) {
+ FAIL() << "OnUnknownPayload: len=" << len;
+}
+
+void FailingHttp2FrameDecoderListener::OnUnknownEnd() {
+ FAIL() << "OnUnknownEnd";
+}
+
+void FailingHttp2FrameDecoderListener::OnPaddingTooLong(
+ const Http2FrameHeader& header,
+ size_t missing_length) {
+ FAIL() << "OnPaddingTooLong: " << header
+ << "; missing_length: " << missing_length;
+}
+
+void FailingHttp2FrameDecoderListener::OnFrameSizeError(
+ const Http2FrameHeader& header) {
+ FAIL() << "OnFrameSizeError: " << header;
+}
+
+LoggingHttp2FrameDecoderListener::LoggingHttp2FrameDecoderListener()
+ : wrapped_(nullptr) {}
+LoggingHttp2FrameDecoderListener::LoggingHttp2FrameDecoderListener(
+ Http2FrameDecoderListener* wrapped)
+ : wrapped_(wrapped) {}
+LoggingHttp2FrameDecoderListener::~LoggingHttp2FrameDecoderListener() = default;
+
+bool LoggingHttp2FrameDecoderListener::OnFrameHeader(
+ const Http2FrameHeader& header) {
+ VLOG(1) << "OnFrameHeader: " << header;
+ if (wrapped_ != nullptr) {
+ return wrapped_->OnFrameHeader(header);
+ }
+ return true;
+}
+
+void LoggingHttp2FrameDecoderListener::OnDataStart(
+ const Http2FrameHeader& header) {
+ VLOG(1) << "OnDataStart: " << header;
+ if (wrapped_ != nullptr) {
+ wrapped_->OnDataStart(header);
+ }
+}
+
+void LoggingHttp2FrameDecoderListener::OnDataPayload(const char* data,
+ size_t len) {
+ VLOG(1) << "OnDataPayload: len=" << len;
+ if (wrapped_ != nullptr) {
+ wrapped_->OnDataPayload(data, len);
+ }
+}
+
+void LoggingHttp2FrameDecoderListener::OnDataEnd() {
+ VLOG(1) << "OnDataEnd";
+ if (wrapped_ != nullptr) {
+ wrapped_->OnDataEnd();
+ }
+}
+
+void LoggingHttp2FrameDecoderListener::OnHeadersStart(
+ const Http2FrameHeader& header) {
+ VLOG(1) << "OnHeadersStart: " << header;
+ if (wrapped_ != nullptr) {
+ wrapped_->OnHeadersStart(header);
+ }
+}
+
+void LoggingHttp2FrameDecoderListener::OnHeadersPriority(
+ const Http2PriorityFields& priority) {
+ VLOG(1) << "OnHeadersPriority: " << priority;
+ if (wrapped_ != nullptr) {
+ wrapped_->OnHeadersPriority(priority);
+ }
+}
+
+void LoggingHttp2FrameDecoderListener::OnHpackFragment(const char* data,
+ size_t len) {
+ VLOG(1) << "OnHpackFragment: len=" << len;
+ if (wrapped_ != nullptr) {
+ wrapped_->OnHpackFragment(data, len);
+ }
+}
+
+void LoggingHttp2FrameDecoderListener::OnHeadersEnd() {
+ VLOG(1) << "OnHeadersEnd";
+ if (wrapped_ != nullptr) {
+ wrapped_->OnHeadersEnd();
+ }
+}
+
+void LoggingHttp2FrameDecoderListener::OnPriorityFrame(
+ const Http2FrameHeader& header,
+ const Http2PriorityFields& priority) {
+ VLOG(1) << "OnPriorityFrame: " << header << "; priority: " << priority;
+ if (wrapped_ != nullptr) {
+ wrapped_->OnPriorityFrame(header, priority);
+ }
+}
+
+void LoggingHttp2FrameDecoderListener::OnContinuationStart(
+ const Http2FrameHeader& header) {
+ VLOG(1) << "OnContinuationStart: " << header;
+ if (wrapped_ != nullptr) {
+ wrapped_->OnContinuationStart(header);
+ }
+}
+
+void LoggingHttp2FrameDecoderListener::OnContinuationEnd() {
+ VLOG(1) << "OnContinuationEnd";
+ if (wrapped_ != nullptr) {
+ wrapped_->OnContinuationEnd();
+ }
+}
+
+void LoggingHttp2FrameDecoderListener::OnPadLength(size_t trailing_length) {
+ VLOG(1) << "OnPadLength: trailing_length=" << trailing_length;
+ if (wrapped_ != nullptr) {
+ wrapped_->OnPadLength(trailing_length);
+ }
+}
+
+void LoggingHttp2FrameDecoderListener::OnPadding(const char* padding,
+ size_t skipped_length) {
+ VLOG(1) << "OnPadding: skipped_length=" << skipped_length;
+ if (wrapped_ != nullptr) {
+ wrapped_->OnPadding(padding, skipped_length);
+ }
+}
+
+void LoggingHttp2FrameDecoderListener::OnRstStream(
+ const Http2FrameHeader& header,
+ Http2ErrorCode error_code) {
+ VLOG(1) << "OnRstStream: " << header << "; code=" << error_code;
+ if (wrapped_ != nullptr) {
+ wrapped_->OnRstStream(header, error_code);
+ }
+}
+
+void LoggingHttp2FrameDecoderListener::OnSettingsStart(
+ const Http2FrameHeader& header) {
+ VLOG(1) << "OnSettingsStart: " << header;
+ if (wrapped_ != nullptr) {
+ wrapped_->OnSettingsStart(header);
+ }
+}
+
+void LoggingHttp2FrameDecoderListener::OnSetting(
+ const Http2SettingFields& setting_fields) {
+ VLOG(1) << "OnSetting: " << setting_fields;
+ if (wrapped_ != nullptr) {
+ wrapped_->OnSetting(setting_fields);
+ }
+}
+
+void LoggingHttp2FrameDecoderListener::OnSettingsEnd() {
+ VLOG(1) << "OnSettingsEnd";
+ if (wrapped_ != nullptr) {
+ wrapped_->OnSettingsEnd();
+ }
+}
+
+void LoggingHttp2FrameDecoderListener::OnSettingsAck(
+ const Http2FrameHeader& header) {
+ VLOG(1) << "OnSettingsAck: " << header;
+ if (wrapped_ != nullptr) {
+ wrapped_->OnSettingsAck(header);
+ }
+}
+
+void LoggingHttp2FrameDecoderListener::OnPushPromiseStart(
+ const Http2FrameHeader& header,
+ const Http2PushPromiseFields& promise,
+ size_t total_padding_length) {
+ VLOG(1) << "OnPushPromiseStart: " << header << "; promise: " << promise
+ << "; total_padding_length: " << total_padding_length;
+ if (wrapped_ != nullptr) {
+ wrapped_->OnPushPromiseStart(header, promise, total_padding_length);
+ }
+}
+
+void LoggingHttp2FrameDecoderListener::OnPushPromiseEnd() {
+ VLOG(1) << "OnPushPromiseEnd";
+ if (wrapped_ != nullptr) {
+ wrapped_->OnPushPromiseEnd();
+ }
+}
+
+void LoggingHttp2FrameDecoderListener::OnPing(const Http2FrameHeader& header,
+ const Http2PingFields& ping) {
+ VLOG(1) << "OnPing: " << header << "; ping: " << ping;
+ if (wrapped_ != nullptr) {
+ wrapped_->OnPing(header, ping);
+ }
+}
+
+void LoggingHttp2FrameDecoderListener::OnPingAck(const Http2FrameHeader& header,
+ const Http2PingFields& ping) {
+ VLOG(1) << "OnPingAck: " << header << "; ping: " << ping;
+ if (wrapped_ != nullptr) {
+ wrapped_->OnPingAck(header, ping);
+ }
+}
+
+void LoggingHttp2FrameDecoderListener::OnGoAwayStart(
+ const Http2FrameHeader& header,
+ const Http2GoAwayFields& goaway) {
+ VLOG(1) << "OnGoAwayStart: " << header << "; goaway: " << goaway;
+ if (wrapped_ != nullptr) {
+ wrapped_->OnGoAwayStart(header, goaway);
+ }
+}
+
+void LoggingHttp2FrameDecoderListener::OnGoAwayOpaqueData(const char* data,
+ size_t len) {
+ VLOG(1) << "OnGoAwayOpaqueData: len=" << len;
+ if (wrapped_ != nullptr) {
+ wrapped_->OnGoAwayOpaqueData(data, len);
+ }
+}
+
+void LoggingHttp2FrameDecoderListener::OnGoAwayEnd() {
+ VLOG(1) << "OnGoAwayEnd";
+ if (wrapped_ != nullptr) {
+ wrapped_->OnGoAwayEnd();
+ }
+}
+
+void LoggingHttp2FrameDecoderListener::OnWindowUpdate(
+ const Http2FrameHeader& header,
+ uint32_t increment) {
+ VLOG(1) << "OnWindowUpdate: " << header << "; increment=" << increment;
+ if (wrapped_ != nullptr) {
+ wrapped_->OnWindowUpdate(header, increment);
+ }
+}
+
+void LoggingHttp2FrameDecoderListener::OnAltSvcStart(
+ const Http2FrameHeader& header,
+ size_t origin_length,
+ size_t value_length) {
+ VLOG(1) << "OnAltSvcStart: " << header << "; origin_length: " << origin_length
+ << "; value_length: " << value_length;
+ if (wrapped_ != nullptr) {
+ wrapped_->OnAltSvcStart(header, origin_length, value_length);
+ }
+}
+
+void LoggingHttp2FrameDecoderListener::OnAltSvcOriginData(const char* data,
+ size_t len) {
+ VLOG(1) << "OnAltSvcOriginData: len=" << len;
+ if (wrapped_ != nullptr) {
+ wrapped_->OnAltSvcOriginData(data, len);
+ }
+}
+
+void LoggingHttp2FrameDecoderListener::OnAltSvcValueData(const char* data,
+ size_t len) {
+ VLOG(1) << "OnAltSvcValueData: len=" << len;
+ if (wrapped_ != nullptr) {
+ wrapped_->OnAltSvcValueData(data, len);
+ }
+}
+
+void LoggingHttp2FrameDecoderListener::OnAltSvcEnd() {
+ VLOG(1) << "OnAltSvcEnd";
+ if (wrapped_ != nullptr) {
+ wrapped_->OnAltSvcEnd();
+ }
+}
+
+void LoggingHttp2FrameDecoderListener::OnUnknownStart(
+ const Http2FrameHeader& header) {
+ VLOG(1) << "OnUnknownStart: " << header;
+ if (wrapped_ != nullptr) {
+ wrapped_->OnUnknownStart(header);
+ }
+}
+
+void LoggingHttp2FrameDecoderListener::OnUnknownPayload(const char* data,
+ size_t len) {
+ VLOG(1) << "OnUnknownPayload: len=" << len;
+ if (wrapped_ != nullptr) {
+ wrapped_->OnUnknownPayload(data, len);
+ }
+}
+
+void LoggingHttp2FrameDecoderListener::OnUnknownEnd() {
+ VLOG(1) << "OnUnknownEnd";
+ if (wrapped_ != nullptr) {
+ wrapped_->OnUnknownEnd();
+ }
+}
+
+void LoggingHttp2FrameDecoderListener::OnPaddingTooLong(
+ const Http2FrameHeader& header,
+ size_t missing_length) {
+ VLOG(1) << "OnPaddingTooLong: " << header
+ << "; missing_length: " << missing_length;
+ if (wrapped_ != nullptr) {
+ wrapped_->OnPaddingTooLong(header, missing_length);
+ }
+}
+
+void LoggingHttp2FrameDecoderListener::OnFrameSizeError(
+ const Http2FrameHeader& header) {
+ VLOG(1) << "OnFrameSizeError: " << header;
+ if (wrapped_ != nullptr) {
+ wrapped_->OnFrameSizeError(header);
+ }
+}
+
+} // namespace http2
diff --git a/chromium/net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener_test_util.h b/chromium/net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener_test_util.h
new file mode 100644
index 00000000000..d6e84efe414
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener_test_util.h
@@ -0,0 +1,143 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_HTTP2_DECODER_HTTP2_FRAME_DECODER_LISTENER_TEST_UTIL_H_
+#define QUICHE_HTTP2_DECODER_HTTP2_FRAME_DECODER_LISTENER_TEST_UTIL_H_
+
+#include <stddef.h>
+
+#include <cstdint>
+
+#include "net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener.h"
+#include "net/third_party/quiche/src/http2/http2_constants.h"
+#include "net/third_party/quiche/src/http2/http2_structures.h"
+
+namespace http2 {
+
+// Fail if any of the methods are called. Allows a test to override only the
+// expected calls.
+class FailingHttp2FrameDecoderListener : public Http2FrameDecoderListener {
+ public:
+ FailingHttp2FrameDecoderListener();
+ ~FailingHttp2FrameDecoderListener() override;
+
+ // TODO(jamessynge): Remove OnFrameHeader once done with supporting
+ // SpdyFramer's exact states.
+ bool OnFrameHeader(const Http2FrameHeader& header) override;
+ void OnDataStart(const Http2FrameHeader& header) override;
+ void OnDataPayload(const char* data, size_t len) override;
+ void OnDataEnd() override;
+ void OnHeadersStart(const Http2FrameHeader& header) override;
+ void OnHeadersPriority(const Http2PriorityFields& priority) override;
+ void OnHpackFragment(const char* data, size_t len) override;
+ void OnHeadersEnd() override;
+ void OnPriorityFrame(const Http2FrameHeader& header,
+ const Http2PriorityFields& priority) override;
+ void OnContinuationStart(const Http2FrameHeader& header) override;
+ void OnContinuationEnd() override;
+ void OnPadLength(size_t trailing_length) override;
+ void OnPadding(const char* padding, size_t skipped_length) override;
+ void OnRstStream(const Http2FrameHeader& header,
+ Http2ErrorCode error_code) override;
+ void OnSettingsStart(const Http2FrameHeader& header) override;
+ void OnSetting(const Http2SettingFields& setting_fields) override;
+ void OnSettingsEnd() override;
+ void OnSettingsAck(const Http2FrameHeader& header) override;
+ void OnPushPromiseStart(const Http2FrameHeader& header,
+ const Http2PushPromiseFields& promise,
+ size_t total_padding_length) override;
+ void OnPushPromiseEnd() override;
+ void OnPing(const Http2FrameHeader& header,
+ const Http2PingFields& ping) override;
+ void OnPingAck(const Http2FrameHeader& header,
+ const Http2PingFields& ping) override;
+ void OnGoAwayStart(const Http2FrameHeader& header,
+ const Http2GoAwayFields& goaway) override;
+ void OnGoAwayOpaqueData(const char* data, size_t len) override;
+ void OnGoAwayEnd() override;
+ void OnWindowUpdate(const Http2FrameHeader& header,
+ uint32_t increment) override;
+ void OnAltSvcStart(const Http2FrameHeader& header,
+ size_t origin_length,
+ size_t value_length) override;
+ void OnAltSvcOriginData(const char* data, size_t len) override;
+ void OnAltSvcValueData(const char* data, size_t len) override;
+ void OnAltSvcEnd() override;
+ void OnUnknownStart(const Http2FrameHeader& header) override;
+ void OnUnknownPayload(const char* data, size_t len) override;
+ void OnUnknownEnd() override;
+ void OnPaddingTooLong(const Http2FrameHeader& header,
+ size_t missing_length) override;
+ void OnFrameSizeError(const Http2FrameHeader& header) override;
+
+ private:
+ void EnsureNotAbstract() { FailingHttp2FrameDecoderListener instance; }
+};
+
+// VLOG's all the calls it receives, and forwards those calls to an optional
+// listener.
+class LoggingHttp2FrameDecoderListener : public Http2FrameDecoderListener {
+ public:
+ LoggingHttp2FrameDecoderListener();
+ explicit LoggingHttp2FrameDecoderListener(Http2FrameDecoderListener* wrapped);
+ ~LoggingHttp2FrameDecoderListener() override;
+
+ // TODO(jamessynge): Remove OnFrameHeader once done with supporting
+ // SpdyFramer's exact states.
+ bool OnFrameHeader(const Http2FrameHeader& header) override;
+ void OnDataStart(const Http2FrameHeader& header) override;
+ void OnDataPayload(const char* data, size_t len) override;
+ void OnDataEnd() override;
+ void OnHeadersStart(const Http2FrameHeader& header) override;
+ void OnHeadersPriority(const Http2PriorityFields& priority) override;
+ void OnHpackFragment(const char* data, size_t len) override;
+ void OnHeadersEnd() override;
+ void OnPriorityFrame(const Http2FrameHeader& header,
+ const Http2PriorityFields& priority) override;
+ void OnContinuationStart(const Http2FrameHeader& header) override;
+ void OnContinuationEnd() override;
+ void OnPadLength(size_t trailing_length) override;
+ void OnPadding(const char* padding, size_t skipped_length) override;
+ void OnRstStream(const Http2FrameHeader& header,
+ Http2ErrorCode error_code) override;
+ void OnSettingsStart(const Http2FrameHeader& header) override;
+ void OnSetting(const Http2SettingFields& setting_fields) override;
+ void OnSettingsEnd() override;
+ void OnSettingsAck(const Http2FrameHeader& header) override;
+ void OnPushPromiseStart(const Http2FrameHeader& header,
+ const Http2PushPromiseFields& promise,
+ size_t total_padding_length) override;
+ void OnPushPromiseEnd() override;
+ void OnPing(const Http2FrameHeader& header,
+ const Http2PingFields& ping) override;
+ void OnPingAck(const Http2FrameHeader& header,
+ const Http2PingFields& ping) override;
+ void OnGoAwayStart(const Http2FrameHeader& header,
+ const Http2GoAwayFields& goaway) override;
+ void OnGoAwayOpaqueData(const char* data, size_t len) override;
+ void OnGoAwayEnd() override;
+ void OnWindowUpdate(const Http2FrameHeader& header,
+ uint32_t increment) override;
+ void OnAltSvcStart(const Http2FrameHeader& header,
+ size_t origin_length,
+ size_t value_length) override;
+ void OnAltSvcOriginData(const char* data, size_t len) override;
+ void OnAltSvcValueData(const char* data, size_t len) override;
+ void OnAltSvcEnd() override;
+ void OnUnknownStart(const Http2FrameHeader& header) override;
+ void OnUnknownPayload(const char* data, size_t len) override;
+ void OnUnknownEnd() override;
+ void OnPaddingTooLong(const Http2FrameHeader& header,
+ size_t missing_length) override;
+ void OnFrameSizeError(const Http2FrameHeader& header) override;
+
+ private:
+ void EnsureNotAbstract() { LoggingHttp2FrameDecoderListener instance; }
+
+ Http2FrameDecoderListener* wrapped_;
+};
+
+} // namespace http2
+
+#endif // QUICHE_HTTP2_DECODER_HTTP2_FRAME_DECODER_LISTENER_TEST_UTIL_H_
diff --git a/chromium/net/third_party/quiche/src/http2/decoder/http2_frame_decoder_test.cc b/chromium/net/third_party/quiche/src/http2/decoder/http2_frame_decoder_test.cc
new file mode 100644
index 00000000000..172bd62ef01
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/decoder/http2_frame_decoder_test.cc
@@ -0,0 +1,929 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/http2/decoder/http2_frame_decoder.h"
+
+// Tests of Http2FrameDecoder.
+
+#include <vector>
+
+#include "base/logging.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/http2/http2_constants.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_reconstruct_object.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string_piece.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_test_helpers.h"
+#include "net/third_party/quiche/src/http2/test_tools/frame_parts.h"
+#include "net/third_party/quiche/src/http2/test_tools/frame_parts_collector_listener.h"
+#include "net/third_party/quiche/src/http2/test_tools/http2_random.h"
+#include "net/third_party/quiche/src/http2/tools/random_decoder_test.h"
+
+using ::testing::AssertionResult;
+using ::testing::AssertionSuccess;
+
+namespace http2 {
+namespace test {
+class Http2FrameDecoderPeer {
+ public:
+ static size_t remaining_total_payload(Http2FrameDecoder* decoder) {
+ return decoder->frame_decoder_state_.remaining_total_payload();
+ }
+};
+
+namespace {
+
+class Http2FrameDecoderTest : public RandomDecoderTest {
+ protected:
+ void SetUp() override {
+ // On any one run of this suite, we'll always choose the same value for
+ // use_default_reconstruct_ because the random seed is the same for each
+ // test case, but across runs the random seed changes.
+ use_default_reconstruct_ = Random().OneIn(2);
+ }
+
+ DecodeStatus StartDecoding(DecodeBuffer* db) override {
+ DVLOG(2) << "StartDecoding, db->Remaining=" << db->Remaining();
+ collector_.Reset();
+ PrepareDecoder();
+
+ DecodeStatus status = decoder_.DecodeFrame(db);
+ if (status != DecodeStatus::kDecodeInProgress) {
+ // Keep track of this so that a concrete test can verify that both fast
+ // and slow decoding paths have been tested.
+ ++fast_decode_count_;
+ if (status == DecodeStatus::kDecodeError) {
+ ConfirmDiscardsRemainingPayload();
+ }
+ }
+ return status;
+ }
+
+ DecodeStatus ResumeDecoding(DecodeBuffer* db) override {
+ DVLOG(2) << "ResumeDecoding, db->Remaining=" << db->Remaining();
+ DecodeStatus status = decoder_.DecodeFrame(db);
+ if (status != DecodeStatus::kDecodeInProgress) {
+ // Keep track of this so that a concrete test can verify that both fast
+ // and slow decoding paths have been tested.
+ ++slow_decode_count_;
+ if (status == DecodeStatus::kDecodeError) {
+ ConfirmDiscardsRemainingPayload();
+ }
+ }
+ return status;
+ }
+
+ // When an error is returned, the decoder is in state kDiscardPayload, and
+ // stays there until the remaining bytes of the frame's payload have been
+ // skipped over. There are no callbacks for this situation.
+ void ConfirmDiscardsRemainingPayload() {
+ ASSERT_TRUE(decoder_.IsDiscardingPayload());
+ size_t remaining =
+ Http2FrameDecoderPeer::remaining_total_payload(&decoder_);
+ // The decoder will discard the remaining bytes, but not go beyond that,
+ // which these conditions verify.
+ size_t extra = 10;
+ Http2String junk(remaining + extra, '0');
+ DecodeBuffer tmp(junk);
+ EXPECT_EQ(DecodeStatus::kDecodeDone, decoder_.DecodeFrame(&tmp));
+ EXPECT_EQ(remaining, tmp.Offset());
+ EXPECT_EQ(extra, tmp.Remaining());
+ EXPECT_FALSE(decoder_.IsDiscardingPayload());
+ }
+
+ void PrepareDecoder() {
+ // Save and restore the maximum_payload_size when reconstructing
+ // the decoder.
+ size_t maximum_payload_size = decoder_.maximum_payload_size();
+
+ // Alternate which constructor is used.
+ if (use_default_reconstruct_) {
+ Http2DefaultReconstructObject(&decoder_, RandomPtr());
+ decoder_.set_listener(&collector_);
+ } else {
+ Http2ReconstructObject(&decoder_, RandomPtr(), &collector_);
+ }
+ decoder_.set_maximum_payload_size(maximum_payload_size);
+
+ use_default_reconstruct_ = !use_default_reconstruct_;
+ }
+
+ void ResetDecodeSpeedCounters() {
+ fast_decode_count_ = 0;
+ slow_decode_count_ = 0;
+ }
+
+ AssertionResult VerifyCollected(const FrameParts& expected) {
+ VERIFY_FALSE(collector_.IsInProgress());
+ VERIFY_EQ(1u, collector_.size());
+ VERIFY_AND_RETURN_SUCCESS(expected.VerifyEquals(*collector_.frame(0)));
+ }
+
+ AssertionResult DecodePayloadAndValidateSeveralWays(Http2StringPiece payload,
+ Validator validator) {
+ DecodeBuffer db(payload);
+ bool start_decoding_requires_non_empty = false;
+ return DecodeAndValidateSeveralWays(&db, start_decoding_requires_non_empty,
+ validator);
+ }
+
+ // Decode one frame's payload and confirm that the listener recorded the
+ // expected FrameParts instance, and only one FrameParts instance. The
+ // payload will be decoded several times with different partitionings
+ // of the payload, and after each the validator will be called.
+ AssertionResult DecodePayloadAndValidateSeveralWays(
+ Http2StringPiece payload,
+ const FrameParts& expected) {
+ auto validator = [&expected, this](const DecodeBuffer& input,
+ DecodeStatus status) -> AssertionResult {
+ VERIFY_EQ(status, DecodeStatus::kDecodeDone);
+ VERIFY_AND_RETURN_SUCCESS(VerifyCollected(expected));
+ };
+ ResetDecodeSpeedCounters();
+ VERIFY_SUCCESS(DecodePayloadAndValidateSeveralWays(
+ payload, ValidateDoneAndEmpty(validator)));
+ VERIFY_GT(fast_decode_count_, 0u);
+ VERIFY_GT(slow_decode_count_, 0u);
+
+ // Repeat with more input; it should stop without reading that input.
+ Http2String next_frame = Random().RandString(10);
+ Http2String input(payload.data(), payload.size());
+ input += next_frame;
+
+ ResetDecodeSpeedCounters();
+ VERIFY_SUCCESS(DecodePayloadAndValidateSeveralWays(
+ payload, ValidateDoneAndOffset(payload.size(), validator)));
+ VERIFY_GT(fast_decode_count_, 0u);
+ VERIFY_GT(slow_decode_count_, 0u);
+
+ return AssertionSuccess();
+ }
+
+ template <size_t N>
+ AssertionResult DecodePayloadAndValidateSeveralWays(
+ const char (&buf)[N],
+ const FrameParts& expected) {
+ return DecodePayloadAndValidateSeveralWays(Http2StringPiece(buf, N),
+ expected);
+ }
+
+ template <size_t N>
+ AssertionResult DecodePayloadAndValidateSeveralWays(
+ const char (&buf)[N],
+ const Http2FrameHeader& header) {
+ return DecodePayloadAndValidateSeveralWays(Http2StringPiece(buf, N),
+ FrameParts(header));
+ }
+
+ template <size_t N>
+ AssertionResult DecodePayloadExpectingError(const char (&buf)[N],
+ const FrameParts& expected) {
+ auto validator = [&expected, this](const DecodeBuffer& input,
+ DecodeStatus status) -> AssertionResult {
+ VERIFY_EQ(status, DecodeStatus::kDecodeError);
+ VERIFY_AND_RETURN_SUCCESS(VerifyCollected(expected));
+ };
+ ResetDecodeSpeedCounters();
+ EXPECT_TRUE(
+ DecodePayloadAndValidateSeveralWays(ToStringPiece(buf), validator));
+ EXPECT_GT(fast_decode_count_, 0u);
+ EXPECT_GT(slow_decode_count_, 0u);
+ return AssertionSuccess();
+ }
+
+ template <size_t N>
+ AssertionResult DecodePayloadExpectingFrameSizeError(const char (&buf)[N],
+ FrameParts expected) {
+ expected.SetHasFrameSizeError(true);
+ VERIFY_AND_RETURN_SUCCESS(DecodePayloadExpectingError(buf, expected));
+ }
+
+ template <size_t N>
+ AssertionResult DecodePayloadExpectingFrameSizeError(
+ const char (&buf)[N],
+ const Http2FrameHeader& header) {
+ return DecodePayloadExpectingFrameSizeError(buf, FrameParts(header));
+ }
+
+ // Count of payloads that are fully decoded by StartDecodingPayload or for
+ // which an error was detected by StartDecodingPayload.
+ size_t fast_decode_count_ = 0;
+
+ // Count of payloads that required calling ResumeDecodingPayload in order to
+ // decode completely, or for which an error was detected by
+ // ResumeDecodingPayload.
+ size_t slow_decode_count_ = 0;
+
+ FramePartsCollectorListener collector_;
+ Http2FrameDecoder decoder_;
+ bool use_default_reconstruct_;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+// Tests that pass the minimum allowed size for the frame type, which is often
+// empty. The tests are in order by frame type value (i.e. 0 for DATA frames).
+
+TEST_F(Http2FrameDecoderTest, DataEmpty) {
+ const char kFrameData[] = {
+ '\x00', '\x00', '\x00', // Payload length: 0
+ '\x00', // DATA
+ '\x00', // Flags: none
+ '\x00', '\x00', '\x00',
+ '\x00', // Stream ID: 0 (invalid but unchecked here)
+ };
+ Http2FrameHeader header(0, Http2FrameType::DATA, 0, 0);
+ FrameParts expected(header, "");
+ EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(kFrameData, expected));
+}
+
+TEST_F(Http2FrameDecoderTest, HeadersEmpty) {
+ const char kFrameData[] = {
+ '\x00', '\x00', '\x00', // Payload length: 0
+ '\x01', // HEADERS
+ '\x00', // Flags: none
+ '\x00', '\x00', '\x00', '\x01', // Stream ID: 0 (REQUIRES ID)
+ };
+ Http2FrameHeader header(0, Http2FrameType::HEADERS, 0, 1);
+ FrameParts expected(header, "");
+ EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(kFrameData, expected));
+}
+
+TEST_F(Http2FrameDecoderTest, Priority) {
+ const char kFrameData[] = {
+ '\x00', '\x00', '\x05', // Length: 5
+ '\x02', // Type: PRIORITY
+ '\x00', // Flags: none
+ '\x00', '\x00', '\x00', '\x02', // Stream: 2
+ '\x80', '\x00', '\x00', '\x01', // Parent: 1 (Exclusive)
+ '\x10', // Weight: 17
+ };
+ Http2FrameHeader header(5, Http2FrameType::PRIORITY, 0, 2);
+ FrameParts expected(header);
+ expected.SetOptPriority(Http2PriorityFields(1, 17, true));
+ EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(kFrameData, expected));
+}
+
+TEST_F(Http2FrameDecoderTest, RstStream) {
+ const char kFrameData[] = {
+ '\x00', '\x00', '\x04', // Length: 4
+ '\x03', // Type: RST_STREAM
+ '\x00', // Flags: none
+ '\x00', '\x00', '\x00', '\x01', // Stream: 1
+ '\x00', '\x00', '\x00', '\x01', // Error: PROTOCOL_ERROR
+ };
+ Http2FrameHeader header(4, Http2FrameType::RST_STREAM, 0, 1);
+ FrameParts expected(header);
+ expected.SetOptRstStreamErrorCode(Http2ErrorCode::PROTOCOL_ERROR);
+ EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(kFrameData, expected));
+}
+
+TEST_F(Http2FrameDecoderTest, SettingsEmpty) {
+ const char kFrameData[] = {
+ '\x00', '\x00', '\x00', // Length: 0
+ '\x04', // Type: SETTINGS
+ '\x00', // Flags: none
+ '\x00', '\x00', '\x00', '\x01', // Stream: 1 (invalid but unchecked here)
+ };
+ Http2FrameHeader header(0, Http2FrameType::SETTINGS, 0, 1);
+ EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(kFrameData, header));
+}
+
+TEST_F(Http2FrameDecoderTest, SettingsAck) {
+ const char kFrameData[] = {
+ '\x00', '\x00', '\x00', // Length: 6
+ '\x04', // Type: SETTINGS
+ '\x01', // Flags: ACK
+ '\x00', '\x00', '\x00', '\x00', // Stream: 0
+ };
+ Http2FrameHeader header(0, Http2FrameType::SETTINGS, Http2FrameFlag::ACK, 0);
+ FrameParts expected(header);
+ EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(kFrameData, expected));
+}
+
+TEST_F(Http2FrameDecoderTest, PushPromiseMinimal) {
+ const char kFrameData[] = {
+ '\x00', '\x00', '\x04', // Payload length: 4
+ '\x05', // PUSH_PROMISE
+ '\x04', // Flags: END_HEADERS
+ '\x00', '\x00', '\x00',
+ '\x02', // Stream: 2 (invalid but unchecked here)
+ '\x00', '\x00', '\x00',
+ '\x01', // Promised: 1 (invalid but unchecked here)
+ };
+ Http2FrameHeader header(4, Http2FrameType::PUSH_PROMISE,
+ Http2FrameFlag::END_HEADERS, 2);
+ FrameParts expected(header, "");
+ expected.SetOptPushPromise(Http2PushPromiseFields{1});
+ EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(kFrameData, expected));
+}
+
+TEST_F(Http2FrameDecoderTest, Ping) {
+ const char kFrameData[] = {
+ '\x00', '\x00', '\x08', // Length: 8
+ '\x06', // Type: PING
+ '\xfe', // Flags: no valid flags
+ '\x00', '\x00', '\x00', '\x00', // Stream: 0
+ 's', 'o', 'm', 'e', // "some"
+ 'd', 'a', 't', 'a', // "data"
+ };
+ Http2FrameHeader header(8, Http2FrameType::PING, 0, 0);
+ FrameParts expected(header);
+ expected.SetOptPing(
+ Http2PingFields{{'s', 'o', 'm', 'e', 'd', 'a', 't', 'a'}});
+ EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(kFrameData, expected));
+}
+
+TEST_F(Http2FrameDecoderTest, PingAck) {
+ const char kFrameData[] = {
+ '\x00', '\x00', '\x08', // Length: 8
+ '\x06', // Type: PING
+ '\xff', // Flags: ACK (plus all invalid flags)
+ '\x00', '\x00', '\x00', '\x00', // Stream: 0
+ 's', 'o', 'm', 'e', // "some"
+ 'd', 'a', 't', 'a', // "data"
+ };
+ Http2FrameHeader header(8, Http2FrameType::PING, Http2FrameFlag::ACK, 0);
+ FrameParts expected(header);
+ expected.SetOptPing(
+ Http2PingFields{{'s', 'o', 'm', 'e', 'd', 'a', 't', 'a'}});
+ EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(kFrameData, expected));
+}
+
+TEST_F(Http2FrameDecoderTest, GoAwayMinimal) {
+ const char kFrameData[] = {
+ '\x00', '\x00', '\x08', // Length: 8 (no opaque data)
+ '\x07', // Type: GOAWAY
+ '\xff', // Flags: 0xff (no valid flags)
+ '\x00', '\x00', '\x00', '\x01', // Stream: 1 (invalid but unchecked here)
+ '\x80', '\x00', '\x00', '\xff', // Last: 255 (plus R bit)
+ '\x00', '\x00', '\x00', '\x09', // Error: COMPRESSION_ERROR
+ };
+ Http2FrameHeader header(8, Http2FrameType::GOAWAY, 0, 1);
+ FrameParts expected(header);
+ expected.SetOptGoaway(
+ Http2GoAwayFields(255, Http2ErrorCode::COMPRESSION_ERROR));
+ EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(kFrameData, expected));
+}
+
+TEST_F(Http2FrameDecoderTest, WindowUpdate) {
+ const char kFrameData[] = {
+ '\x00', '\x00', '\x04', // Length: 4
+ '\x08', // Type: WINDOW_UPDATE
+ '\x0f', // Flags: 0xff (no valid flags)
+ '\x00', '\x00', '\x00', '\x01', // Stream: 1
+ '\x80', '\x00', '\x04', '\x00', // Incr: 1024 (plus R bit)
+ };
+ Http2FrameHeader header(4, Http2FrameType::WINDOW_UPDATE, 0, 1);
+ FrameParts expected(header);
+ expected.SetOptWindowUpdateIncrement(1024);
+ EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(kFrameData, expected));
+}
+
+TEST_F(Http2FrameDecoderTest, ContinuationEmpty) {
+ const char kFrameData[] = {
+ '\x00', '\x00', '\x00', // Payload length: 0
+ '\x09', // CONTINUATION
+ '\x00', // Flags: none
+ '\x00', '\x00', '\x00',
+ '\x00', // Stream ID: 0 (invalid but unchecked here)
+ };
+ Http2FrameHeader header(0, Http2FrameType::CONTINUATION, 0, 0);
+ FrameParts expected(header);
+ EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(kFrameData, expected));
+}
+
+TEST_F(Http2FrameDecoderTest, AltSvcMinimal) {
+ const char kFrameData[] = {
+ '\x00', '\x00', '\x02', // Payload length: 2
+ '\x0a', // ALTSVC
+ '\xff', // Flags: none (plus 0xff)
+ '\x00', '\x00', '\x00',
+ '\x00', // Stream ID: 0 (invalid but unchecked here)
+ '\x00', '\x00', // Origin Length: 0
+ };
+ Http2FrameHeader header(2, Http2FrameType::ALTSVC, 0, 0);
+ FrameParts expected(header);
+ expected.SetOptAltsvcOriginLength(0);
+ expected.SetOptAltsvcValueLength(0);
+ EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(kFrameData, expected));
+}
+
+TEST_F(Http2FrameDecoderTest, UnknownEmpty) {
+ const char kFrameData[] = {
+ '\x00', '\x00', '\x00', // Payload length: 0
+ '\x20', // 32 (unknown)
+ '\xff', // Flags: all
+ '\x00', '\x00', '\x00', '\x00', // Stream ID: 0
+ };
+ Http2FrameHeader header(0, static_cast<Http2FrameType>(32), 0xff, 0);
+ FrameParts expected(header);
+ EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(kFrameData, expected));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Tests of longer payloads, for those frame types that allow longer payloads.
+
+TEST_F(Http2FrameDecoderTest, DataPayload) {
+ const char kFrameData[] = {
+ '\x00', '\x00', '\x03', // Payload length: 7
+ '\x00', // DATA
+ '\x80', // Flags: 0x80
+ '\x00', '\x00', '\x02', '\x02', // Stream ID: 514
+ 'a', 'b', 'c', // Data
+ };
+ Http2FrameHeader header(3, Http2FrameType::DATA, 0, 514);
+ FrameParts expected(header, "abc");
+ EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(kFrameData, expected));
+}
+
+TEST_F(Http2FrameDecoderTest, HeadersPayload) {
+ const char kFrameData[] = {
+ '\x00', '\x00', '\x03', // Payload length: 3
+ '\x01', // HEADERS
+ '\x05', // Flags: END_STREAM | END_HEADERS
+ '\x00', '\x00', '\x00', '\x02', // Stream ID: 0 (REQUIRES ID)
+ 'a', 'b', 'c', // HPACK fragment (doesn't have to be valid)
+ };
+ Http2FrameHeader header(
+ 3, Http2FrameType::HEADERS,
+ Http2FrameFlag::END_STREAM | Http2FrameFlag::END_HEADERS, 2);
+ FrameParts expected(header, "abc");
+ EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(kFrameData, expected));
+}
+
+TEST_F(Http2FrameDecoderTest, HeadersPriority) {
+ const char kFrameData[] = {
+ '\x00', '\x00', '\x05', // Payload length: 5
+ '\x01', // HEADERS
+ '\x20', // Flags: PRIORITY
+ '\x00', '\x00', '\x00', '\x02', // Stream ID: 0 (REQUIRES ID)
+ '\x00', '\x00', '\x00', '\x01', // Parent: 1 (Not Exclusive)
+ '\xff', // Weight: 256
+ };
+ Http2FrameHeader header(5, Http2FrameType::HEADERS, Http2FrameFlag::PRIORITY,
+ 2);
+ FrameParts expected(header);
+ expected.SetOptPriority(Http2PriorityFields(1, 256, false));
+ EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(kFrameData, expected));
+}
+
+TEST_F(Http2FrameDecoderTest, Settings) {
+ const char kFrameData[] = {
+ '\x00', '\x00', '\x0c', // Length: 12
+ '\x04', // Type: SETTINGS
+ '\x00', // Flags: none
+ '\x00', '\x00', '\x00', '\x00', // Stream: 0
+ '\x00', '\x04', // Param: INITIAL_WINDOW_SIZE
+ '\x0a', '\x0b', '\x0c', '\x0d', // Value: 168496141
+ '\x00', '\x02', // Param: ENABLE_PUSH
+ '\x00', '\x00', '\x00', '\x03', // Value: 3 (invalid but unchecked here)
+ };
+ Http2FrameHeader header(12, Http2FrameType::SETTINGS, 0, 0);
+ FrameParts expected(header);
+ expected.AppendSetting(Http2SettingFields(
+ Http2SettingsParameter::INITIAL_WINDOW_SIZE, 168496141));
+ expected.AppendSetting(
+ Http2SettingFields(Http2SettingsParameter::ENABLE_PUSH, 3));
+ EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(kFrameData, expected));
+}
+
+TEST_F(Http2FrameDecoderTest, PushPromisePayload) {
+ const char kFrameData[] = {
+ '\x00', '\x00', 7, // Payload length: 7
+ '\x05', // PUSH_PROMISE
+ '\x04', // Flags: END_HEADERS
+ '\x00', '\x00', '\x00', '\xff', // Stream ID: 255
+ '\x00', '\x00', '\x01', '\x00', // Promised: 256
+ 'a', 'b', 'c', // HPACK fragment (doesn't have to be valid)
+ };
+ Http2FrameHeader header(7, Http2FrameType::PUSH_PROMISE,
+ Http2FrameFlag::END_HEADERS, 255);
+ FrameParts expected(header, "abc");
+ expected.SetOptPushPromise(Http2PushPromiseFields{256});
+ EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(kFrameData, expected));
+}
+
+TEST_F(Http2FrameDecoderTest, GoAwayOpaqueData) {
+ const char kFrameData[] = {
+ '\x00', '\x00', '\x0e', // Length: 14
+ '\x07', // Type: GOAWAY
+ '\xff', // Flags: 0xff (no valid flags)
+ '\x80', '\x00', '\x00', '\x00', // Stream: 0 (plus R bit)
+ '\x00', '\x00', '\x01', '\x00', // Last: 256
+ '\x00', '\x00', '\x00', '\x03', // Error: FLOW_CONTROL_ERROR
+ 'o', 'p', 'a', 'q', 'u', 'e',
+ };
+ Http2FrameHeader header(14, Http2FrameType::GOAWAY, 0, 0);
+ FrameParts expected(header, "opaque");
+ expected.SetOptGoaway(
+ Http2GoAwayFields(256, Http2ErrorCode::FLOW_CONTROL_ERROR));
+ EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(kFrameData, expected));
+}
+
+TEST_F(Http2FrameDecoderTest, ContinuationPayload) {
+ const char kFrameData[] = {
+ '\x00', '\x00', '\x03', // Payload length: 3
+ '\x09', // CONTINUATION
+ '\xff', // Flags: END_HEADERS | 0xfb
+ '\x00', '\x00', '\x00', '\x02', // Stream ID: 2
+ 'a', 'b', 'c', // Data
+ };
+ Http2FrameHeader header(3, Http2FrameType::CONTINUATION,
+ Http2FrameFlag::END_HEADERS, 2);
+ FrameParts expected(header, "abc");
+ EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(kFrameData, expected));
+}
+
+TEST_F(Http2FrameDecoderTest, AltSvcPayload) {
+ const char kFrameData[] = {
+ '\x00', '\x00', '\x08', // Payload length: 3
+ '\x0a', // ALTSVC
+ '\x00', // Flags: none
+ '\x00', '\x00', '\x00', '\x02', // Stream ID: 2
+ '\x00', '\x03', // Origin Length: 0
+ 'a', 'b', 'c', // Origin
+ 'd', 'e', 'f', // Value
+ };
+ Http2FrameHeader header(8, Http2FrameType::ALTSVC, 0, 2);
+ FrameParts expected(header);
+ expected.SetAltSvcExpected("abc", "def");
+ EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(kFrameData, expected));
+}
+
+TEST_F(Http2FrameDecoderTest, UnknownPayload) {
+ const char kFrameData[] = {
+ '\x00', '\x00', '\x03', // Payload length: 3
+ '\x30', // 48 (unknown)
+ '\x00', // Flags: none
+ '\x00', '\x00', '\x00', '\x02', // Stream ID: 2
+ 'a', 'b', 'c', // Payload
+ };
+ Http2FrameHeader header(3, static_cast<Http2FrameType>(48), 0, 2);
+ FrameParts expected(header, "abc");
+ EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(kFrameData, expected));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Tests of padded payloads, for those frame types that allow padding.
+
+TEST_F(Http2FrameDecoderTest, DataPayloadAndPadding) {
+ const char kFrameData[] = {
+ '\x00', '\x00', '\x07', // Payload length: 7
+ '\x00', // DATA
+ '\x09', // Flags: END_STREAM | PADDED
+ '\x00', '\x00', '\x00', '\x02', // Stream ID: 0 (REQUIRES ID)
+ '\x03', // Pad Len
+ 'a', 'b', 'c', // Data
+ '\x00', '\x00', '\x00', // Padding
+ };
+ Http2FrameHeader header(7, Http2FrameType::DATA,
+ Http2FrameFlag::END_STREAM | Http2FrameFlag::PADDED,
+ 2);
+ size_t total_pad_length = 4; // Including the Pad Length field.
+ FrameParts expected(header, "abc", total_pad_length);
+ EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(kFrameData, expected));
+}
+
+TEST_F(Http2FrameDecoderTest, HeadersPayloadAndPadding) {
+ const char kFrameData[] = {
+ '\x00', '\x00', '\x07', // Payload length: 7
+ '\x01', // HEADERS
+ '\x08', // Flags: PADDED
+ '\x00', '\x00', '\x00', '\x02', // Stream ID: 0 (REQUIRES ID)
+ '\x03', // Pad Len
+ 'a', 'b', 'c', // HPACK fragment (doesn't have to be valid)
+ '\x00', '\x00', '\x00', // Padding
+ };
+ Http2FrameHeader header(7, Http2FrameType::HEADERS, Http2FrameFlag::PADDED,
+ 2);
+ size_t total_pad_length = 4; // Including the Pad Length field.
+ FrameParts expected(header, "abc", total_pad_length);
+ EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(kFrameData, expected));
+}
+
+TEST_F(Http2FrameDecoderTest, HeadersPayloadPriorityAndPadding) {
+ const char kFrameData[] = {
+ '\x00', '\x00', '\x0c', // Payload length: 12
+ '\x01', // HEADERS
+ '\xff', // Flags: all, including undefined
+ '\x00', '\x00', '\x00', '\x02', // Stream ID: 0 (REQUIRES ID)
+ '\x03', // Pad Len
+ '\x80', '\x00', '\x00', '\x01', // Parent: 1 (Exclusive)
+ '\x10', // Weight: 17
+ 'a', 'b', 'c', // HPACK fragment (doesn't have to be valid)
+ '\x00', '\x00', '\x00', // Padding
+ };
+ Http2FrameHeader header(12, Http2FrameType::HEADERS,
+ Http2FrameFlag::END_STREAM |
+ Http2FrameFlag::END_HEADERS |
+ Http2FrameFlag::PADDED | Http2FrameFlag::PRIORITY,
+ 2);
+ size_t total_pad_length = 4; // Including the Pad Length field.
+ FrameParts expected(header, "abc", total_pad_length);
+ expected.SetOptPriority(Http2PriorityFields(1, 17, true));
+ EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(kFrameData, expected));
+}
+
+TEST_F(Http2FrameDecoderTest, PushPromisePayloadAndPadding) {
+ const char kFrameData[] = {
+ '\x00', '\x00', 11, // Payload length: 11
+ '\x05', // PUSH_PROMISE
+ '\xff', // Flags: END_HEADERS | PADDED | 0xf3
+ '\x00', '\x00', '\x00', '\x01', // Stream ID: 1
+ '\x03', // Pad Len
+ '\x00', '\x00', '\x00', '\x02', // Promised: 2
+ 'a', 'b', 'c', // HPACK fragment (doesn't have to be valid)
+ '\x00', '\x00', '\x00', // Padding
+ };
+ Http2FrameHeader header(11, Http2FrameType::PUSH_PROMISE,
+ Http2FrameFlag::END_HEADERS | Http2FrameFlag::PADDED,
+ 1);
+ size_t total_pad_length = 4; // Including the Pad Length field.
+ FrameParts expected(header, "abc", total_pad_length);
+ expected.SetOptPushPromise(Http2PushPromiseFields{2});
+ EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(kFrameData, expected));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Payload too short errors.
+
+TEST_F(Http2FrameDecoderTest, DataMissingPadLengthField) {
+ const char kFrameData[] = {
+ '\x00', '\x00', '\x00', // Payload length: 0
+ '\x00', // DATA
+ '\x08', // Flags: PADDED
+ '\x00', '\x00', '\x00', '\x01', // Stream ID: 1
+ };
+ Http2FrameHeader header(0, Http2FrameType::DATA, Http2FrameFlag::PADDED, 1);
+ FrameParts expected(header);
+ expected.SetOptMissingLength(1);
+ EXPECT_TRUE(DecodePayloadExpectingError(kFrameData, expected));
+}
+
+TEST_F(Http2FrameDecoderTest, HeaderPaddingTooLong) {
+ const char kFrameData[] = {
+ '\x00', '\x00', '\x02', // Payload length: 0
+ '\x01', // HEADERS
+ '\x08', // Flags: PADDED
+ '\x00', '\x01', '\x00', '\x00', // Stream ID: 65536
+ '\xff', // Pad Len: 255
+ '\x00', // Only one byte of padding
+ };
+ Http2FrameHeader header(2, Http2FrameType::HEADERS, Http2FrameFlag::PADDED,
+ 65536);
+ FrameParts expected(header);
+ expected.SetOptMissingLength(254);
+ EXPECT_TRUE(DecodePayloadExpectingError(kFrameData, expected));
+}
+
+TEST_F(Http2FrameDecoderTest, HeaderMissingPriority) {
+ const char kFrameData[] = {
+ '\x00', '\x00', '\x04', // Payload length: 0
+ '\x01', // HEADERS
+ '\x20', // Flags: PRIORITY
+ '\x00', '\x01', '\x00', '\x00', // Stream ID: 65536
+ '\x00', '\x00', '\x00', '\x00', // Priority (truncated)
+ };
+ Http2FrameHeader header(4, Http2FrameType::HEADERS, Http2FrameFlag::PRIORITY,
+ 65536);
+ EXPECT_TRUE(DecodePayloadExpectingFrameSizeError(kFrameData, header));
+}
+
+TEST_F(Http2FrameDecoderTest, PriorityTooShort) {
+ const char kFrameData[] = {
+ '\x00', '\x00', '\x04', // Length: 5
+ '\x02', // Type: PRIORITY
+ '\x00', // Flags: none
+ '\x00', '\x00', '\x00', '\x02', // Stream: 2
+ '\x80', '\x00', '\x00', '\x01', // Parent: 1 (Exclusive)
+ };
+ Http2FrameHeader header(4, Http2FrameType::PRIORITY, 0, 2);
+ EXPECT_TRUE(DecodePayloadExpectingFrameSizeError(kFrameData, header));
+}
+
+TEST_F(Http2FrameDecoderTest, RstStreamTooShort) {
+ const char kFrameData[] = {
+ '\x00', '\x00', '\x03', // Length: 4
+ '\x03', // Type: RST_STREAM
+ '\x00', // Flags: none
+ '\x00', '\x00', '\x00', '\x01', // Stream: 1
+ '\x00', '\x00', '\x00', // Truncated
+ };
+ Http2FrameHeader header(3, Http2FrameType::RST_STREAM, 0, 1);
+ EXPECT_TRUE(DecodePayloadExpectingFrameSizeError(kFrameData, header));
+}
+
+// SETTINGS frames must a multiple of 6 bytes long, so an 9 byte payload is
+// invalid.
+TEST_F(Http2FrameDecoderTest, SettingsWrongSize) {
+ const char kFrameData[] = {
+ '\x00', '\x00', '\x09', // Length: 2
+ '\x04', // Type: SETTINGS
+ '\x00', // Flags: none
+ '\x00', '\x00', '\x00', '\x00', // Stream: 0
+ '\x00', '\x02', // Param: ENABLE_PUSH
+ '\x00', '\x00', '\x00', '\x03', // Value: 1
+ '\x00', '\x04', // Param: INITIAL_WINDOW_SIZE
+ '\x00', // Value: Truncated
+ };
+ Http2FrameHeader header(9, Http2FrameType::SETTINGS, 0, 0);
+ FrameParts expected(header);
+ expected.AppendSetting(
+ Http2SettingFields(Http2SettingsParameter::ENABLE_PUSH, 3));
+ EXPECT_TRUE(DecodePayloadExpectingFrameSizeError(kFrameData, expected));
+}
+
+TEST_F(Http2FrameDecoderTest, PushPromiseTooShort) {
+ const char kFrameData[] = {
+ '\x00', '\x00', 3, // Payload length: 3
+ '\x05', // PUSH_PROMISE
+ '\x00', // Flags: none
+ '\x00', '\x00', '\x00', '\x01', // Stream ID: 1
+ '\x00', '\x00', '\x00', // Truncated promise id
+ };
+ Http2FrameHeader header(3, Http2FrameType::PUSH_PROMISE, 0, 1);
+ EXPECT_TRUE(DecodePayloadExpectingFrameSizeError(kFrameData, header));
+}
+
+TEST_F(Http2FrameDecoderTest, PushPromisePaddedTruncatedPromise) {
+ const char kFrameData[] = {
+ '\x00', '\x00', 4, // Payload length: 4
+ '\x05', // PUSH_PROMISE
+ '\x08', // Flags: PADDED
+ '\x00', '\x00', '\x00', '\x01', // Stream ID: 1
+ '\x00', // Pad Len
+ '\x00', '\x00', '\x00', // Truncated promise id
+ };
+ Http2FrameHeader header(4, Http2FrameType::PUSH_PROMISE,
+ Http2FrameFlag::PADDED, 1);
+ EXPECT_TRUE(DecodePayloadExpectingFrameSizeError(kFrameData, header));
+}
+
+TEST_F(Http2FrameDecoderTest, PingTooShort) {
+ const char kFrameData[] = {
+ '\x00', '\x00', '\x07', // Length: 8
+ '\x06', // Type: PING
+ '\xfe', // Flags: no valid flags
+ '\x00', '\x00', '\x00', '\x00', // Stream: 0
+ 's', 'o', 'm', 'e', // "some"
+ 'd', 'a', 't', // Too little
+ };
+ Http2FrameHeader header(7, Http2FrameType::PING, 0, 0);
+ EXPECT_TRUE(DecodePayloadExpectingFrameSizeError(kFrameData, header));
+}
+
+TEST_F(Http2FrameDecoderTest, GoAwayTooShort) {
+ const char kFrameData[] = {
+ '\x00', '\x00', '\x00', // Length: 0
+ '\x07', // Type: GOAWAY
+ '\xff', // Flags: 0xff (no valid flags)
+ '\x00', '\x00', '\x00', '\x00', // Stream: 0
+ };
+ Http2FrameHeader header(0, Http2FrameType::GOAWAY, 0, 0);
+ EXPECT_TRUE(DecodePayloadExpectingFrameSizeError(kFrameData, header));
+}
+
+TEST_F(Http2FrameDecoderTest, WindowUpdateTooShort) {
+ const char kFrameData[] = {
+ '\x00', '\x00', '\x03', // Length: 3
+ '\x08', // Type: WINDOW_UPDATE
+ '\x0f', // Flags: 0xff (no valid flags)
+ '\x00', '\x00', '\x00', '\x01', // Stream: 1
+ '\x80', '\x00', '\x04', // Truncated
+ };
+ Http2FrameHeader header(3, Http2FrameType::WINDOW_UPDATE, 0, 1);
+ EXPECT_TRUE(DecodePayloadExpectingFrameSizeError(kFrameData, header));
+}
+
+TEST_F(Http2FrameDecoderTest, AltSvcTruncatedOriginLength) {
+ const char kFrameData[] = {
+ '\x00', '\x00', '\x01', // Payload length: 3
+ '\x0a', // ALTSVC
+ '\x00', // Flags: none
+ '\x00', '\x00', '\x00', '\x02', // Stream ID: 2
+ '\x00', // Origin Length: truncated
+ };
+ Http2FrameHeader header(1, Http2FrameType::ALTSVC, 0, 2);
+ EXPECT_TRUE(DecodePayloadExpectingFrameSizeError(kFrameData, header));
+}
+
+TEST_F(Http2FrameDecoderTest, AltSvcTruncatedOrigin) {
+ const char kFrameData[] = {
+ '\x00', '\x00', '\x05', // Payload length: 3
+ '\x0a', // ALTSVC
+ '\x00', // Flags: none
+ '\x00', '\x00', '\x00', '\x02', // Stream ID: 2
+ '\x00', '\x04', // Origin Length: 4 (too long)
+ 'a', 'b', 'c', // Origin
+ };
+ Http2FrameHeader header(5, Http2FrameType::ALTSVC, 0, 2);
+ EXPECT_TRUE(DecodePayloadExpectingFrameSizeError(kFrameData, header));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Payload too long errors.
+
+// The decoder calls the listener's OnFrameSizeError method if the frame's
+// payload is longer than the currently configured maximum payload size.
+TEST_F(Http2FrameDecoderTest, BeyondMaximum) {
+ decoder_.set_maximum_payload_size(2);
+ const char kFrameData[] = {
+ '\x00', '\x00', '\x07', // Payload length: 7
+ '\x00', // DATA
+ '\x09', // Flags: END_STREAM | PADDED
+ '\x00', '\x00', '\x00', '\x02', // Stream ID: 0 (REQUIRES ID)
+ '\x03', // Pad Len
+ 'a', 'b', 'c', // Data
+ '\x00', '\x00', '\x00', // Padding
+ };
+ Http2FrameHeader header(7, Http2FrameType::DATA,
+ Http2FrameFlag::END_STREAM | Http2FrameFlag::PADDED,
+ 2);
+ FrameParts expected(header);
+ expected.SetHasFrameSizeError(true);
+ auto validator = [&expected, this](const DecodeBuffer& input,
+ DecodeStatus status) -> AssertionResult {
+ VERIFY_EQ(status, DecodeStatus::kDecodeError);
+ // The decoder detects this error after decoding the header, and without
+ // trying to decode the payload.
+ VERIFY_EQ(input.Offset(), Http2FrameHeader::EncodedSize());
+ VERIFY_AND_RETURN_SUCCESS(VerifyCollected(expected));
+ };
+ ResetDecodeSpeedCounters();
+ EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(ToStringPiece(kFrameData),
+ validator));
+ EXPECT_GT(fast_decode_count_, 0u);
+ EXPECT_GT(slow_decode_count_, 0u);
+}
+
+TEST_F(Http2FrameDecoderTest, PriorityTooLong) {
+ const char kFrameData[] = {
+ '\x00', '\x00', '\x06', // Length: 5
+ '\x02', // Type: PRIORITY
+ '\x00', // Flags: none
+ '\x00', '\x00', '\x00', '\x02', // Stream: 2
+ '\x80', '\x00', '\x00', '\x01', // Parent: 1 (Exclusive)
+ '\x10', // Weight: 17
+ '\x00', // Too much
+ };
+ Http2FrameHeader header(6, Http2FrameType::PRIORITY, 0, 2);
+ EXPECT_TRUE(DecodePayloadExpectingFrameSizeError(kFrameData, header));
+}
+
+TEST_F(Http2FrameDecoderTest, RstStreamTooLong) {
+ const char kFrameData[] = {
+ '\x00', '\x00', '\x05', // Length: 4
+ '\x03', // Type: RST_STREAM
+ '\x00', // Flags: none
+ '\x00', '\x00', '\x00', '\x01', // Stream: 1
+ '\x00', '\x00', '\x00', '\x01', // Error: PROTOCOL_ERROR
+ '\x00', // Too much
+ };
+ Http2FrameHeader header(5, Http2FrameType::RST_STREAM, 0, 1);
+ EXPECT_TRUE(DecodePayloadExpectingFrameSizeError(kFrameData, header));
+}
+
+TEST_F(Http2FrameDecoderTest, SettingsAckTooLong) {
+ const char kFrameData[] = {
+ '\x00', '\x00', '\x06', // Length: 6
+ '\x04', // Type: SETTINGS
+ '\x01', // Flags: ACK
+ '\x00', '\x00', '\x00', '\x00', // Stream: 0
+ '\x00', '\x00', // Extra
+ '\x00', '\x00', '\x00', '\x00', // Extra
+ };
+ Http2FrameHeader header(6, Http2FrameType::SETTINGS, Http2FrameFlag::ACK, 0);
+ EXPECT_TRUE(DecodePayloadExpectingFrameSizeError(kFrameData, header));
+}
+
+TEST_F(Http2FrameDecoderTest, PingAckTooLong) {
+ const char kFrameData[] = {
+ '\x00', '\x00', '\x09', // Length: 8
+ '\x06', // Type: PING
+ '\xff', // Flags: ACK | 0xfe
+ '\x00', '\x00', '\x00', '\x00', // Stream: 0
+ 's', 'o', 'm', 'e', // "some"
+ 'd', 'a', 't', 'a', // "data"
+ '\x00', // Too much
+ };
+ Http2FrameHeader header(9, Http2FrameType::PING, Http2FrameFlag::ACK, 0);
+ EXPECT_TRUE(DecodePayloadExpectingFrameSizeError(kFrameData, header));
+}
+
+TEST_F(Http2FrameDecoderTest, WindowUpdateTooLong) {
+ const char kFrameData[] = {
+ '\x00', '\x00', '\x05', // Length: 5
+ '\x08', // Type: WINDOW_UPDATE
+ '\x0f', // Flags: 0xff (no valid flags)
+ '\x00', '\x00', '\x00', '\x01', // Stream: 1
+ '\x80', '\x00', '\x04', '\x00', // Incr: 1024 (plus R bit)
+ '\x00', // Too much
+ };
+ Http2FrameHeader header(5, Http2FrameType::WINDOW_UPDATE, 0, 1);
+ EXPECT_TRUE(DecodePayloadExpectingFrameSizeError(kFrameData, header));
+}
+
+} // namespace
+} // namespace test
+} // namespace http2
diff --git a/chromium/net/third_party/quiche/src/http2/decoder/http2_structure_decoder.cc b/chromium/net/third_party/quiche/src/http2/decoder/http2_structure_decoder.cc
new file mode 100644
index 00000000000..e4f2c1af079
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/decoder/http2_structure_decoder.cc
@@ -0,0 +1,90 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/http2/decoder/http2_structure_decoder.h"
+
+#include <algorithm>
+
+#include "net/third_party/quiche/src/http2/platform/api/http2_bug_tracker.h"
+
+namespace http2 {
+
+// Below we have some defensive coding: if we somehow run off the end, don't
+// overwrite lots of memory. Note that most of this decoder is not defensive
+// against bugs in the decoder, only against malicious encoders, but since
+// we're copying memory into a buffer here, let's make sure we don't allow a
+// small mistake to grow larger. The decoder will get stuck if we hit the
+// HTTP2_BUG conditions, but shouldn't corrupt memory.
+
+uint32_t Http2StructureDecoder::IncompleteStart(DecodeBuffer* db,
+ uint32_t target_size) {
+ if (target_size > sizeof buffer_) {
+ HTTP2_BUG << "target_size too large for buffer: " << target_size;
+ return 0;
+ }
+ const uint32_t num_to_copy = db->MinLengthRemaining(target_size);
+ memcpy(buffer_, db->cursor(), num_to_copy);
+ offset_ = num_to_copy;
+ db->AdvanceCursor(num_to_copy);
+ return num_to_copy;
+}
+
+DecodeStatus Http2StructureDecoder::IncompleteStart(DecodeBuffer* db,
+ uint32_t* remaining_payload,
+ uint32_t target_size) {
+ DVLOG(1) << "IncompleteStart@" << this
+ << ": *remaining_payload=" << *remaining_payload
+ << "; target_size=" << target_size
+ << "; db->Remaining=" << db->Remaining();
+ *remaining_payload -=
+ IncompleteStart(db, std::min(target_size, *remaining_payload));
+ if (*remaining_payload > 0 && db->Empty()) {
+ return DecodeStatus::kDecodeInProgress;
+ }
+ DVLOG(1) << "IncompleteStart: kDecodeError";
+ return DecodeStatus::kDecodeError;
+}
+
+bool Http2StructureDecoder::ResumeFillingBuffer(DecodeBuffer* db,
+ uint32_t target_size) {
+ DVLOG(2) << "ResumeFillingBuffer@" << this << ": target_size=" << target_size
+ << "; offset_=" << offset_ << "; db->Remaining=" << db->Remaining();
+ if (target_size < offset_) {
+ HTTP2_BUG << "Already filled buffer_! target_size=" << target_size
+ << " offset_=" << offset_;
+ return false;
+ }
+ const uint32_t needed = target_size - offset_;
+ const uint32_t num_to_copy = db->MinLengthRemaining(needed);
+ DVLOG(2) << "ResumeFillingBuffer num_to_copy=" << num_to_copy;
+ memcpy(&buffer_[offset_], db->cursor(), num_to_copy);
+ db->AdvanceCursor(num_to_copy);
+ offset_ += num_to_copy;
+ return needed == num_to_copy;
+}
+
+bool Http2StructureDecoder::ResumeFillingBuffer(DecodeBuffer* db,
+ uint32_t* remaining_payload,
+ uint32_t target_size) {
+ DVLOG(2) << "ResumeFillingBuffer@" << this << ": target_size=" << target_size
+ << "; offset_=" << offset_
+ << "; *remaining_payload=" << *remaining_payload
+ << "; db->Remaining=" << db->Remaining();
+ if (target_size < offset_) {
+ HTTP2_BUG << "Already filled buffer_! target_size=" << target_size
+ << " offset_=" << offset_;
+ return false;
+ }
+ const uint32_t needed = target_size - offset_;
+ const uint32_t num_to_copy =
+ db->MinLengthRemaining(std::min(needed, *remaining_payload));
+ DVLOG(2) << "ResumeFillingBuffer num_to_copy=" << num_to_copy;
+ memcpy(&buffer_[offset_], db->cursor(), num_to_copy);
+ db->AdvanceCursor(num_to_copy);
+ offset_ += num_to_copy;
+ *remaining_payload -= num_to_copy;
+ return needed == num_to_copy;
+}
+
+} // namespace http2
diff --git a/chromium/net/third_party/quiche/src/http2/decoder/http2_structure_decoder.h b/chromium/net/third_party/quiche/src/http2/decoder/http2_structure_decoder.h
new file mode 100644
index 00000000000..46c88b85f9d
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/decoder/http2_structure_decoder.h
@@ -0,0 +1,131 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_HTTP2_DECODER_HTTP2_STRUCTURE_DECODER_H_
+#define QUICHE_HTTP2_DECODER_HTTP2_STRUCTURE_DECODER_H_
+
+// Http2StructureDecoder is a class for decoding the fixed size structures in
+// the HTTP/2 spec, defined in net/third_party/quiche/src/http2/http2_structures.h. This class
+// is in aid of deciding whether to keep the SlowDecode methods which I
+// (jamessynge) now think may not be worth their complexity. In particular,
+// if most transport buffers are large, so it is rare that a structure is
+// split across buffer boundaries, than the cost of buffering upon
+// those rare occurrences is small, which then simplifies the callers.
+
+#include <cstdint>
+
+#include "base/logging.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_http2_structures.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_status.h"
+#include "net/third_party/quiche/src/http2/http2_structures.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_export.h"
+
+namespace http2 {
+namespace test {
+class Http2StructureDecoderPeer;
+} // namespace test
+
+class HTTP2_EXPORT_PRIVATE Http2StructureDecoder {
+ public:
+ // The caller needs to keep track of whether to call Start or Resume.
+ //
+ // Start has an optimization for the case where the DecodeBuffer holds the
+ // entire encoded structure; in that case it decodes into *out and returns
+ // true, and does NOT touch the data members of the Http2StructureDecoder
+ // instance because the caller won't be calling Resume later.
+ //
+ // However, if the DecodeBuffer is too small to hold the entire encoded
+ // structure, Start copies the available bytes into the Http2StructureDecoder
+ // instance, and returns false to indicate that it has not been able to
+ // complete the decoding.
+ //
+ template <class S>
+ bool Start(S* out, DecodeBuffer* db) {
+ static_assert(S::EncodedSize() <= sizeof buffer_, "buffer_ is too small");
+ DVLOG(2) << __func__ << "@" << this << ": db->Remaining=" << db->Remaining()
+ << "; EncodedSize=" << S::EncodedSize();
+ if (db->Remaining() >= S::EncodedSize()) {
+ DoDecode(out, db);
+ return true;
+ }
+ IncompleteStart(db, S::EncodedSize());
+ return false;
+ }
+
+ template <class S>
+ bool Resume(S* out, DecodeBuffer* db) {
+ DVLOG(2) << __func__ << "@" << this << ": offset_=" << offset_
+ << "; db->Remaining=" << db->Remaining();
+ if (ResumeFillingBuffer(db, S::EncodedSize())) {
+ // We have the whole thing now.
+ DVLOG(2) << __func__ << "@" << this << " offset_=" << offset_
+ << " Ready to decode from buffer_.";
+ DecodeBuffer buffer_db(buffer_, S::EncodedSize());
+ DoDecode(out, &buffer_db);
+ return true;
+ }
+ DCHECK_LT(offset_, S::EncodedSize());
+ return false;
+ }
+
+ // A second pair of Start and Resume, where the caller has a variable,
+ // |remaining_payload| that is both tested for sufficiency and updated
+ // during decoding. Note that the decode buffer may extend beyond the
+ // remaining payload because the buffer may include padding.
+ template <class S>
+ DecodeStatus Start(S* out, DecodeBuffer* db, uint32_t* remaining_payload) {
+ static_assert(S::EncodedSize() <= sizeof buffer_, "buffer_ is too small");
+ DVLOG(2) << __func__ << "@" << this
+ << ": *remaining_payload=" << *remaining_payload
+ << "; db->Remaining=" << db->Remaining()
+ << "; EncodedSize=" << S::EncodedSize();
+ if (db->MinLengthRemaining(*remaining_payload) >= S::EncodedSize()) {
+ DoDecode(out, db);
+ *remaining_payload -= S::EncodedSize();
+ return DecodeStatus::kDecodeDone;
+ }
+ return IncompleteStart(db, remaining_payload, S::EncodedSize());
+ }
+
+ template <class S>
+ bool Resume(S* out, DecodeBuffer* db, uint32_t* remaining_payload) {
+ DVLOG(3) << __func__ << "@" << this << ": offset_=" << offset_
+ << "; *remaining_payload=" << *remaining_payload
+ << "; db->Remaining=" << db->Remaining()
+ << "; EncodedSize=" << S::EncodedSize();
+ if (ResumeFillingBuffer(db, remaining_payload, S::EncodedSize())) {
+ // We have the whole thing now.
+ DVLOG(2) << __func__ << "@" << this << ": offset_=" << offset_
+ << "; Ready to decode from buffer_.";
+ DecodeBuffer buffer_db(buffer_, S::EncodedSize());
+ DoDecode(out, &buffer_db);
+ return true;
+ }
+ DCHECK_LT(offset_, S::EncodedSize());
+ return false;
+ }
+
+ uint32_t offset() const { return offset_; }
+
+ private:
+ friend class test::Http2StructureDecoderPeer;
+
+ uint32_t IncompleteStart(DecodeBuffer* db, uint32_t target_size);
+ DecodeStatus IncompleteStart(DecodeBuffer* db,
+ uint32_t* remaining_payload,
+ uint32_t target_size);
+
+ bool ResumeFillingBuffer(DecodeBuffer* db, uint32_t target_size);
+ bool ResumeFillingBuffer(DecodeBuffer* db,
+ uint32_t* remaining_payload,
+ uint32_t target_size);
+
+ uint32_t offset_;
+ char buffer_[Http2FrameHeader::EncodedSize()];
+};
+
+} // namespace http2
+
+#endif // QUICHE_HTTP2_DECODER_HTTP2_STRUCTURE_DECODER_H_
diff --git a/chromium/net/third_party/quiche/src/http2/decoder/http2_structure_decoder_test.cc b/chromium/net/third_party/quiche/src/http2/decoder/http2_structure_decoder_test.cc
new file mode 100644
index 00000000000..8d8da7bb577
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/decoder/http2_structure_decoder_test.cc
@@ -0,0 +1,541 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/http2/decoder/http2_structure_decoder.h"
+
+// Tests decoding all of the fixed size HTTP/2 structures (i.e. those defined in
+// net/third_party/quiche/src/http2/http2_structures.h) using Http2StructureDecoder, which
+// handles buffering of structures split across input buffer boundaries, and in
+// turn uses DoDecode when it has all of a structure in a contiguous buffer.
+
+// NOTE: This tests the first pair of Start and Resume, which don't take
+// a remaining_payload parameter. The other pair are well tested via the
+// payload decoder tests, though...
+// TODO(jamessynge): Create type parameterized tests for Http2StructureDecoder
+// where the type is the type of structure, and with testing of both pairs of
+// Start and Resume methods; note that it appears that the first pair will be
+// used only for Http2FrameHeader, and the other pair only for structures in the
+// frame payload.
+
+#include <stddef.h>
+
+#include <cstdint>
+
+#include "base/logging.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_status.h"
+#include "net/third_party/quiche/src/http2/http2_constants.h"
+#include "net/third_party/quiche/src/http2/http2_structures_test_util.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_reconstruct_object.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string_piece.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_test_helpers.h"
+#include "net/third_party/quiche/src/http2/tools/http2_frame_builder.h"
+#include "net/third_party/quiche/src/http2/tools/random_decoder_test.h"
+
+using ::testing::AssertionFailure;
+using ::testing::AssertionResult;
+using ::testing::AssertionSuccess;
+
+namespace http2 {
+namespace test {
+namespace {
+const bool kMayReturnZeroOnFirst = false;
+
+template <class S>
+class Http2StructureDecoderTest : public RandomDecoderTest {
+ protected:
+ typedef S Structure;
+
+ Http2StructureDecoderTest() {
+ // IF the test adds more data after the encoded structure, stop as
+ // soon as the structure is decoded.
+ stop_decode_on_done_ = true;
+ }
+
+ DecodeStatus StartDecoding(DecodeBuffer* b) override {
+ // Overwrite the current contents of |structure_|, in to which we'll
+ // decode the buffer, so that we can be confident that we really decoded
+ // the structure every time.
+ Http2DefaultReconstructObject(&structure_, RandomPtr());
+ uint32_t old_remaining = b->Remaining();
+ if (structure_decoder_.Start(&structure_, b)) {
+ EXPECT_EQ(old_remaining - S::EncodedSize(), b->Remaining());
+ ++fast_decode_count_;
+ return DecodeStatus::kDecodeDone;
+ } else {
+ EXPECT_LT(structure_decoder_.offset(), S::EncodedSize());
+ EXPECT_EQ(0u, b->Remaining());
+ EXPECT_EQ(old_remaining - structure_decoder_.offset(), b->Remaining());
+ ++incomplete_start_count_;
+ return DecodeStatus::kDecodeInProgress;
+ }
+ }
+
+ DecodeStatus ResumeDecoding(DecodeBuffer* b) override {
+ uint32_t old_offset = structure_decoder_.offset();
+ EXPECT_LT(old_offset, S::EncodedSize());
+ uint32_t avail = b->Remaining();
+ if (structure_decoder_.Resume(&structure_, b)) {
+ EXPECT_LE(S::EncodedSize(), old_offset + avail);
+ EXPECT_EQ(b->Remaining(), avail - (S::EncodedSize() - old_offset));
+ ++slow_decode_count_;
+ return DecodeStatus::kDecodeDone;
+ } else {
+ EXPECT_LT(structure_decoder_.offset(), S::EncodedSize());
+ EXPECT_EQ(0u, b->Remaining());
+ EXPECT_GT(S::EncodedSize(), old_offset + avail);
+ ++incomplete_resume_count_;
+ return DecodeStatus::kDecodeInProgress;
+ }
+ }
+
+ // Fully decodes the Structure at the start of data, and confirms it matches
+ // *expected (if provided).
+ AssertionResult DecodeLeadingStructure(const S* expected,
+ Http2StringPiece data) {
+ VERIFY_LE(S::EncodedSize(), data.size());
+ DecodeBuffer original(data);
+
+ // The validator is called after each of the several times that the input
+ // DecodeBuffer is decoded, each with a different segmentation of the input.
+ // Validate that structure_ matches the expected value, if provided.
+ Validator validator;
+ if (expected != nullptr) {
+ validator = [expected, this](const DecodeBuffer& db,
+ DecodeStatus status) -> AssertionResult {
+ VERIFY_EQ(*expected, structure_);
+ return AssertionSuccess();
+ };
+ }
+
+ // Before that, validate that decoding is done and that we've advanced
+ // the cursor the expected amount.
+ validator = ValidateDoneAndOffset(S::EncodedSize(), validator);
+
+ // Decode several times, with several segmentations of the input buffer.
+ fast_decode_count_ = 0;
+ slow_decode_count_ = 0;
+ incomplete_start_count_ = 0;
+ incomplete_resume_count_ = 0;
+ VERIFY_SUCCESS(DecodeAndValidateSeveralWays(
+ &original, kMayReturnZeroOnFirst, validator));
+ VERIFY_FALSE(HasFailure());
+ VERIFY_EQ(S::EncodedSize(), structure_decoder_.offset());
+ VERIFY_EQ(S::EncodedSize(), original.Offset());
+ VERIFY_LT(0u, fast_decode_count_);
+ VERIFY_LT(0u, slow_decode_count_);
+ VERIFY_LT(0u, incomplete_start_count_);
+
+ // If the structure is large enough so that SelectZeroOrOne will have
+ // caused Resume to return false, check that occurred.
+ if (S::EncodedSize() >= 2) {
+ VERIFY_LE(0u, incomplete_resume_count_);
+ } else {
+ VERIFY_EQ(0u, incomplete_resume_count_);
+ }
+ if (expected != nullptr) {
+ DVLOG(1) << "DecodeLeadingStructure expected: " << *expected;
+ DVLOG(1) << "DecodeLeadingStructure actual: " << structure_;
+ VERIFY_EQ(*expected, structure_);
+ }
+ return AssertionSuccess();
+ }
+
+ template <size_t N>
+ AssertionResult DecodeLeadingStructure(const char (&data)[N]) {
+ VERIFY_AND_RETURN_SUCCESS(
+ DecodeLeadingStructure(nullptr, Http2StringPiece(data, N)));
+ }
+
+ template <size_t N>
+ AssertionResult DecodeLeadingStructure(const unsigned char (&data)[N]) {
+ VERIFY_AND_RETURN_SUCCESS(
+ DecodeLeadingStructure(nullptr, ToStringPiece(data)));
+ }
+
+ // Encode the structure |in_s| into bytes, then decode the bytes
+ // and validate that the decoder produced the same field values.
+ AssertionResult EncodeThenDecode(const S& in_s) {
+ Http2String bytes = SerializeStructure(in_s);
+ VERIFY_EQ(S::EncodedSize(), bytes.size());
+ VERIFY_AND_RETURN_SUCCESS(DecodeLeadingStructure(&in_s, bytes));
+ }
+
+ // Repeatedly fill a structure with random but valid contents, encode it, then
+ // decode it, and finally validate that the decoded structure matches the
+ // random input. Lather-rinse-and-repeat.
+ AssertionResult TestDecodingRandomizedStructures(size_t count) {
+ for (size_t i = 0; i < count; ++i) {
+ Structure input;
+ Randomize(&input, RandomPtr());
+ VERIFY_SUCCESS(EncodeThenDecode(input));
+ }
+ return AssertionSuccess();
+ }
+
+ AssertionResult TestDecodingRandomizedStructures() {
+ VERIFY_SUCCESS(TestDecodingRandomizedStructures(100));
+ return AssertionSuccess();
+ }
+
+ uint32_t decode_offset_ = 0;
+ S structure_;
+ Http2StructureDecoder structure_decoder_;
+ size_t fast_decode_count_ = 0;
+ size_t slow_decode_count_ = 0;
+ size_t incomplete_start_count_ = 0;
+ size_t incomplete_resume_count_ = 0;
+};
+
+class Http2FrameHeaderDecoderTest
+ : public Http2StructureDecoderTest<Http2FrameHeader> {};
+
+TEST_F(Http2FrameHeaderDecoderTest, DecodesLiteral) {
+ {
+ // Realistic input.
+ // clang-format off
+ const char kData[] = {
+ 0x00, 0x00, 0x05, // Payload length: 5
+ 0x01, // Frame type: HEADERS
+ 0x08, // Flags: PADDED
+ 0x00, 0x00, 0x00, 0x01, // Stream ID: 1
+ 0x04, // Padding length: 4
+ 0x00, 0x00, 0x00, 0x00, // Padding bytes
+ };
+ // clang-format on
+ ASSERT_TRUE(DecodeLeadingStructure(kData));
+ EXPECT_EQ(5u, structure_.payload_length);
+ EXPECT_EQ(Http2FrameType::HEADERS, structure_.type);
+ EXPECT_EQ(Http2FrameFlag::PADDED, structure_.flags);
+ EXPECT_EQ(1u, structure_.stream_id);
+ }
+ {
+ // Unlikely input.
+ // clang-format off
+ const unsigned char kData[] = {
+ 0xff, 0xff, 0xff, // Payload length: uint24 max
+ 0xff, // Frame type: Unknown
+ 0xff, // Flags: Unknown/All
+ 0xff, 0xff, 0xff, 0xff, // Stream ID: uint31 max, plus R-bit
+ };
+ // clang-format on
+ ASSERT_TRUE(DecodeLeadingStructure(kData));
+ EXPECT_EQ((1u << 24) - 1u, structure_.payload_length);
+ EXPECT_EQ(static_cast<Http2FrameType>(255), structure_.type);
+ EXPECT_EQ(255, structure_.flags);
+ EXPECT_EQ(0x7FFFFFFFu, structure_.stream_id);
+ }
+}
+
+TEST_F(Http2FrameHeaderDecoderTest, DecodesRandomized) {
+ TestDecodingRandomizedStructures();
+}
+
+//------------------------------------------------------------------------------
+
+class Http2PriorityFieldsDecoderTest
+ : public Http2StructureDecoderTest<Http2PriorityFields> {};
+
+TEST_F(Http2PriorityFieldsDecoderTest, DecodesLiteral) {
+ {
+ // clang-format off
+ const unsigned char kData[] = {
+ 0x80, 0x00, 0x00, 0x05, // Exclusive (yes) and Dependency (5)
+ 0xff, // Weight: 256 (after adding 1)
+ };
+ // clang-format on
+ ASSERT_TRUE(DecodeLeadingStructure(kData));
+ EXPECT_EQ(5u, structure_.stream_dependency);
+ EXPECT_EQ(256u, structure_.weight);
+ EXPECT_EQ(true, structure_.is_exclusive);
+ }
+ {
+ // clang-format off
+ const unsigned char kData[] = {
+ 0x7f, 0xff, 0xff, 0xff, // Excl. (no) and Dependency (uint31 max)
+ 0x00, // Weight: 1 (after adding 1)
+ };
+ // clang-format on
+ ASSERT_TRUE(DecodeLeadingStructure(kData));
+ EXPECT_EQ(StreamIdMask(), structure_.stream_dependency);
+ EXPECT_EQ(1u, structure_.weight);
+ EXPECT_FALSE(structure_.is_exclusive);
+ }
+}
+
+TEST_F(Http2PriorityFieldsDecoderTest, DecodesRandomized) {
+ TestDecodingRandomizedStructures();
+}
+
+//------------------------------------------------------------------------------
+
+class Http2RstStreamFieldsDecoderTest
+ : public Http2StructureDecoderTest<Http2RstStreamFields> {};
+
+TEST_F(Http2RstStreamFieldsDecoderTest, DecodesLiteral) {
+ {
+ // clang-format off
+ const char kData[] = {
+ 0x00, 0x00, 0x00, 0x01, // Error: PROTOCOL_ERROR
+ };
+ // clang-format on
+ ASSERT_TRUE(DecodeLeadingStructure(kData));
+ EXPECT_TRUE(structure_.IsSupportedErrorCode());
+ EXPECT_EQ(Http2ErrorCode::PROTOCOL_ERROR, structure_.error_code);
+ }
+ {
+ // clang-format off
+ const unsigned char kData[] = {
+ 0xff, 0xff, 0xff, 0xff, // Error: max uint32 (Unknown error code)
+ };
+ // clang-format on
+ ASSERT_TRUE(DecodeLeadingStructure(kData));
+ EXPECT_FALSE(structure_.IsSupportedErrorCode());
+ EXPECT_EQ(static_cast<Http2ErrorCode>(0xffffffff), structure_.error_code);
+ }
+}
+
+TEST_F(Http2RstStreamFieldsDecoderTest, DecodesRandomized) {
+ TestDecodingRandomizedStructures();
+}
+
+//------------------------------------------------------------------------------
+
+class Http2SettingFieldsDecoderTest
+ : public Http2StructureDecoderTest<Http2SettingFields> {};
+
+TEST_F(Http2SettingFieldsDecoderTest, DecodesLiteral) {
+ {
+ // clang-format off
+ const char kData[] = {
+ 0x00, 0x01, // Setting: HEADER_TABLE_SIZE
+ 0x00, 0x00, 0x40, 0x00, // Value: 16K
+ };
+ // clang-format on
+ ASSERT_TRUE(DecodeLeadingStructure(kData));
+ EXPECT_TRUE(structure_.IsSupportedParameter());
+ EXPECT_EQ(Http2SettingsParameter::HEADER_TABLE_SIZE, structure_.parameter);
+ EXPECT_EQ(1u << 14, structure_.value);
+ }
+ {
+ // clang-format off
+ const unsigned char kData[] = {
+ 0x00, 0x00, // Setting: Unknown (0)
+ 0xff, 0xff, 0xff, 0xff, // Value: max uint32
+ };
+ // clang-format on
+ ASSERT_TRUE(DecodeLeadingStructure(kData));
+ EXPECT_FALSE(structure_.IsSupportedParameter());
+ EXPECT_EQ(static_cast<Http2SettingsParameter>(0), structure_.parameter);
+ }
+}
+
+TEST_F(Http2SettingFieldsDecoderTest, DecodesRandomized) {
+ TestDecodingRandomizedStructures();
+}
+
+//------------------------------------------------------------------------------
+
+class Http2PushPromiseFieldsDecoderTest
+ : public Http2StructureDecoderTest<Http2PushPromiseFields> {};
+
+TEST_F(Http2PushPromiseFieldsDecoderTest, DecodesLiteral) {
+ {
+ // clang-format off
+ const unsigned char kData[] = {
+ 0x00, 0x01, 0x8a, 0x92, // Promised Stream ID: 101010
+ };
+ // clang-format on
+ ASSERT_TRUE(DecodeLeadingStructure(kData));
+ EXPECT_EQ(101010u, structure_.promised_stream_id);
+ }
+ {
+ // Promised stream id has R-bit (reserved for future use) set, which
+ // should be cleared by the decoder.
+ // clang-format off
+ const unsigned char kData[] = {
+ // Promised Stream ID: max uint31 and R-bit
+ 0xff, 0xff, 0xff, 0xff,
+ };
+ // clang-format on
+ ASSERT_TRUE(DecodeLeadingStructure(kData));
+ EXPECT_EQ(StreamIdMask(), structure_.promised_stream_id);
+ }
+}
+
+TEST_F(Http2PushPromiseFieldsDecoderTest, DecodesRandomized) {
+ TestDecodingRandomizedStructures();
+}
+
+//------------------------------------------------------------------------------
+
+class Http2PingFieldsDecoderTest
+ : public Http2StructureDecoderTest<Http2PingFields> {};
+
+TEST_F(Http2PingFieldsDecoderTest, DecodesLiteral) {
+ {
+ // Each byte is different, so can detect if order changed.
+ const char kData[] = {
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+ };
+ ASSERT_TRUE(DecodeLeadingStructure(kData));
+ EXPECT_EQ(ToStringPiece(kData), ToStringPiece(structure_.opaque_bytes));
+ }
+ {
+ // All zeros, detect problems handling NULs.
+ const char kData[] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ };
+ ASSERT_TRUE(DecodeLeadingStructure(kData));
+ EXPECT_EQ(ToStringPiece(kData), ToStringPiece(structure_.opaque_bytes));
+ }
+ {
+ const unsigned char kData[] = {
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ };
+ ASSERT_TRUE(DecodeLeadingStructure(kData));
+ EXPECT_EQ(ToStringPiece(kData), ToStringPiece(structure_.opaque_bytes));
+ }
+}
+
+TEST_F(Http2PingFieldsDecoderTest, DecodesRandomized) {
+ TestDecodingRandomizedStructures();
+}
+
+//------------------------------------------------------------------------------
+
+class Http2GoAwayFieldsDecoderTest
+ : public Http2StructureDecoderTest<Http2GoAwayFields> {};
+
+TEST_F(Http2GoAwayFieldsDecoderTest, DecodesLiteral) {
+ {
+ // clang-format off
+ const char kData[] = {
+ 0x00, 0x00, 0x00, 0x00, // Last Stream ID: 0
+ 0x00, 0x00, 0x00, 0x00, // Error: NO_ERROR (0)
+ };
+ // clang-format on
+ ASSERT_TRUE(DecodeLeadingStructure(kData));
+ EXPECT_EQ(0u, structure_.last_stream_id);
+ EXPECT_TRUE(structure_.IsSupportedErrorCode());
+ EXPECT_EQ(Http2ErrorCode::HTTP2_NO_ERROR, structure_.error_code);
+ }
+ {
+ // clang-format off
+ const char kData[] = {
+ 0x00, 0x00, 0x00, 0x01, // Last Stream ID: 1
+ 0x00, 0x00, 0x00, 0x0d, // Error: HTTP_1_1_REQUIRED
+ };
+ // clang-format on
+ ASSERT_TRUE(DecodeLeadingStructure(kData));
+ EXPECT_EQ(1u, structure_.last_stream_id);
+ EXPECT_TRUE(structure_.IsSupportedErrorCode());
+ EXPECT_EQ(Http2ErrorCode::HTTP_1_1_REQUIRED, structure_.error_code);
+ }
+ {
+ // clang-format off
+ const unsigned char kData[] = {
+ 0xff, 0xff, 0xff, 0xff, // Last Stream ID: max uint31 and R-bit
+ 0xff, 0xff, 0xff, 0xff, // Error: max uint32 (Unknown error code)
+ };
+ // clang-format on
+ ASSERT_TRUE(DecodeLeadingStructure(kData));
+ EXPECT_EQ(StreamIdMask(), structure_.last_stream_id); // No high-bit.
+ EXPECT_FALSE(structure_.IsSupportedErrorCode());
+ EXPECT_EQ(static_cast<Http2ErrorCode>(0xffffffff), structure_.error_code);
+ }
+}
+
+TEST_F(Http2GoAwayFieldsDecoderTest, DecodesRandomized) {
+ TestDecodingRandomizedStructures();
+}
+
+//------------------------------------------------------------------------------
+
+class Http2WindowUpdateFieldsDecoderTest
+ : public Http2StructureDecoderTest<Http2WindowUpdateFields> {};
+
+TEST_F(Http2WindowUpdateFieldsDecoderTest, DecodesLiteral) {
+ {
+ // clang-format off
+ const char kData[] = {
+ 0x00, 0x01, 0x00, 0x00, // Window Size Increment: 2 ^ 16
+ };
+ // clang-format on
+ ASSERT_TRUE(DecodeLeadingStructure(kData));
+ EXPECT_EQ(1u << 16, structure_.window_size_increment);
+ }
+ {
+ // Increment must be non-zero, but we need to be able to decode the invalid
+ // zero to detect it.
+ // clang-format off
+ const char kData[] = {
+ 0x00, 0x00, 0x00, 0x00, // Window Size Increment: 0
+ };
+ // clang-format on
+ ASSERT_TRUE(DecodeLeadingStructure(kData));
+ EXPECT_EQ(0u, structure_.window_size_increment);
+ }
+ {
+ // Increment has R-bit (reserved for future use) set, which
+ // should be cleared by the decoder.
+ // clang-format off
+ const unsigned char kData[] = {
+ // Window Size Increment: max uint31 and R-bit
+ 0xff, 0xff, 0xff, 0xff,
+ };
+ // clang-format on
+ ASSERT_TRUE(DecodeLeadingStructure(kData));
+ EXPECT_EQ(StreamIdMask(), structure_.window_size_increment);
+ }
+}
+
+TEST_F(Http2WindowUpdateFieldsDecoderTest, DecodesRandomized) {
+ TestDecodingRandomizedStructures();
+}
+
+//------------------------------------------------------------------------------
+
+class Http2AltSvcFieldsDecoderTest
+ : public Http2StructureDecoderTest<Http2AltSvcFields> {};
+
+TEST_F(Http2AltSvcFieldsDecoderTest, DecodesLiteral) {
+ {
+ // clang-format off
+ const char kData[] = {
+ 0x00, 0x00, // Origin Length: 0
+ };
+ // clang-format on
+ ASSERT_TRUE(DecodeLeadingStructure(kData));
+ EXPECT_EQ(0, structure_.origin_length);
+ }
+ {
+ // clang-format off
+ const char kData[] = {
+ 0x00, 0x14, // Origin Length: 20
+ };
+ // clang-format on
+ ASSERT_TRUE(DecodeLeadingStructure(kData));
+ EXPECT_EQ(20, structure_.origin_length);
+ }
+ {
+ // clang-format off
+ const unsigned char kData[] = {
+ 0xff, 0xff, // Origin Length: uint16 max
+ };
+ // clang-format on
+ ASSERT_TRUE(DecodeLeadingStructure(kData));
+ EXPECT_EQ(65535, structure_.origin_length);
+ }
+}
+
+TEST_F(Http2AltSvcFieldsDecoderTest, DecodesRandomized) {
+ TestDecodingRandomizedStructures();
+}
+
+} // namespace
+} // namespace test
+} // namespace http2
diff --git a/chromium/net/third_party/quiche/src/http2/decoder/http2_structure_decoder_test_util.cc b/chromium/net/third_party/quiche/src/http2/decoder/http2_structure_decoder_test_util.cc
new file mode 100644
index 00000000000..90805eab643
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/decoder/http2_structure_decoder_test_util.cc
@@ -0,0 +1,22 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/http2/decoder/http2_structure_decoder_test_util.h"
+
+#include <cstddef>
+
+namespace http2 {
+namespace test {
+
+// static
+void Http2StructureDecoderPeer::Randomize(Http2StructureDecoder* p,
+ Http2Random* rng) {
+ p->offset_ = rng->Rand32();
+ for (size_t i = 0; i < sizeof p->buffer_; ++i) {
+ p->buffer_[i] = rng->Rand8();
+ }
+}
+
+} // namespace test
+} // namespace http2
diff --git a/chromium/net/third_party/quiche/src/http2/decoder/http2_structure_decoder_test_util.h b/chromium/net/third_party/quiche/src/http2/decoder/http2_structure_decoder_test_util.h
new file mode 100644
index 00000000000..4b533c78e28
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/decoder/http2_structure_decoder_test_util.h
@@ -0,0 +1,24 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_HTTP2_DECODER_HTTP2_STRUCTURE_DECODER_TEST_UTIL_H_
+#define QUICHE_HTTP2_DECODER_HTTP2_STRUCTURE_DECODER_TEST_UTIL_H_
+
+#include "net/third_party/quiche/src/http2/decoder/http2_structure_decoder.h"
+
+#include "net/third_party/quiche/src/http2/test_tools/http2_random.h"
+
+namespace http2 {
+namespace test {
+
+class Http2StructureDecoderPeer {
+ public:
+ // Overwrite the Http2StructureDecoder instance with random values.
+ static void Randomize(Http2StructureDecoder* p, Http2Random* rng);
+};
+
+} // namespace test
+} // namespace http2
+
+#endif // QUICHE_HTTP2_DECODER_HTTP2_STRUCTURE_DECODER_TEST_UTIL_H_
diff --git a/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/altsvc_payload_decoder.cc b/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/altsvc_payload_decoder.cc
new file mode 100644
index 00000000000..4e4d860afd5
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/altsvc_payload_decoder.cc
@@ -0,0 +1,148 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/http2/decoder/payload_decoders/altsvc_payload_decoder.h"
+
+#include <stddef.h>
+
+#include "base/logging.h"
+#include "base/macros.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h"
+#include "net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener.h"
+#include "net/third_party/quiche/src/http2/http2_constants.h"
+#include "net/third_party/quiche/src/http2/http2_structures.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_bug_tracker.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_macros.h"
+
+namespace http2 {
+
+std::ostream& operator<<(std::ostream& out,
+ AltSvcPayloadDecoder::PayloadState v) {
+ switch (v) {
+ case AltSvcPayloadDecoder::PayloadState::kStartDecodingStruct:
+ return out << "kStartDecodingStruct";
+ case AltSvcPayloadDecoder::PayloadState::kMaybeDecodedStruct:
+ return out << "kMaybeDecodedStruct";
+ case AltSvcPayloadDecoder::PayloadState::kDecodingStrings:
+ return out << "kDecodingStrings";
+ case AltSvcPayloadDecoder::PayloadState::kResumeDecodingStruct:
+ return out << "kResumeDecodingStruct";
+ }
+ // Since the value doesn't come over the wire, only a programming bug should
+ // result in reaching this point.
+ int unknown = static_cast<int>(v);
+ HTTP2_BUG << "Invalid AltSvcPayloadDecoder::PayloadState: " << unknown;
+ return out << "AltSvcPayloadDecoder::PayloadState(" << unknown << ")";
+}
+
+DecodeStatus AltSvcPayloadDecoder::StartDecodingPayload(
+ FrameDecoderState* state,
+ DecodeBuffer* db) {
+ DVLOG(2) << "AltSvcPayloadDecoder::StartDecodingPayload: "
+ << state->frame_header();
+ DCHECK_EQ(Http2FrameType::ALTSVC, state->frame_header().type);
+ DCHECK_LE(db->Remaining(), state->frame_header().payload_length);
+ DCHECK_EQ(0, state->frame_header().flags);
+
+ state->InitializeRemainders();
+ payload_state_ = PayloadState::kStartDecodingStruct;
+
+ return ResumeDecodingPayload(state, db);
+}
+
+DecodeStatus AltSvcPayloadDecoder::ResumeDecodingPayload(
+ FrameDecoderState* state,
+ DecodeBuffer* db) {
+ const Http2FrameHeader& frame_header = state->frame_header();
+ DVLOG(2) << "AltSvcPayloadDecoder::ResumeDecodingPayload: " << frame_header;
+ DCHECK_EQ(Http2FrameType::ALTSVC, frame_header.type);
+ DCHECK_LE(state->remaining_payload(), frame_header.payload_length);
+ DCHECK_LE(db->Remaining(), state->remaining_payload());
+ DCHECK_NE(PayloadState::kMaybeDecodedStruct, payload_state_);
+ // |status| has to be initialized to some value to avoid compiler error in
+ // case PayloadState::kMaybeDecodedStruct below, but value does not matter,
+ // see DCHECK_NE above.
+ DecodeStatus status = DecodeStatus::kDecodeError;
+ while (true) {
+ DVLOG(2) << "AltSvcPayloadDecoder::ResumeDecodingPayload payload_state_="
+ << payload_state_;
+ switch (payload_state_) {
+ case PayloadState::kStartDecodingStruct:
+ status = state->StartDecodingStructureInPayload(&altsvc_fields_, db);
+ HTTP2_FALLTHROUGH;
+
+ case PayloadState::kMaybeDecodedStruct:
+ if (status == DecodeStatus::kDecodeDone &&
+ altsvc_fields_.origin_length <= state->remaining_payload()) {
+ size_t origin_length = altsvc_fields_.origin_length;
+ size_t value_length = state->remaining_payload() - origin_length;
+ state->listener()->OnAltSvcStart(frame_header, origin_length,
+ value_length);
+ } else if (status != DecodeStatus::kDecodeDone) {
+ DCHECK(state->remaining_payload() > 0 ||
+ status == DecodeStatus::kDecodeError)
+ << "\nremaining_payload: " << state->remaining_payload()
+ << "\nstatus: " << status << "\nheader: " << frame_header;
+ // Assume in progress.
+ payload_state_ = PayloadState::kResumeDecodingStruct;
+ return status;
+ } else {
+ // The origin's length is longer than the remaining payload.
+ DCHECK_GT(altsvc_fields_.origin_length, state->remaining_payload());
+ return state->ReportFrameSizeError();
+ }
+ HTTP2_FALLTHROUGH;
+
+ case PayloadState::kDecodingStrings:
+ return DecodeStrings(state, db);
+
+ case PayloadState::kResumeDecodingStruct:
+ status = state->ResumeDecodingStructureInPayload(&altsvc_fields_, db);
+ payload_state_ = PayloadState::kMaybeDecodedStruct;
+ continue;
+ }
+ HTTP2_BUG << "PayloadState: " << payload_state_;
+ }
+}
+
+DecodeStatus AltSvcPayloadDecoder::DecodeStrings(FrameDecoderState* state,
+ DecodeBuffer* db) {
+ DVLOG(2) << "AltSvcPayloadDecoder::DecodeStrings remaining_payload="
+ << state->remaining_payload()
+ << ", db->Remaining=" << db->Remaining();
+ // Note that we don't explicitly keep track of exactly how far through the
+ // origin; instead we compute it from how much is left of the original
+ // payload length and the decoded total length of the origin.
+ size_t origin_length = altsvc_fields_.origin_length;
+ size_t value_length = state->frame_header().payload_length - origin_length -
+ Http2AltSvcFields::EncodedSize();
+ if (state->remaining_payload() > value_length) {
+ size_t remaining_origin_length = state->remaining_payload() - value_length;
+ size_t avail = db->MinLengthRemaining(remaining_origin_length);
+ state->listener()->OnAltSvcOriginData(db->cursor(), avail);
+ db->AdvanceCursor(avail);
+ state->ConsumePayload(avail);
+ if (remaining_origin_length > avail) {
+ payload_state_ = PayloadState::kDecodingStrings;
+ return DecodeStatus::kDecodeInProgress;
+ }
+ }
+ // All that is left is the value string.
+ DCHECK_LE(state->remaining_payload(), value_length);
+ DCHECK_LE(db->Remaining(), state->remaining_payload());
+ if (db->HasData()) {
+ size_t avail = db->Remaining();
+ state->listener()->OnAltSvcValueData(db->cursor(), avail);
+ db->AdvanceCursor(avail);
+ state->ConsumePayload(avail);
+ }
+ if (state->remaining_payload() == 0) {
+ state->listener()->OnAltSvcEnd();
+ return DecodeStatus::kDecodeDone;
+ }
+ payload_state_ = PayloadState::kDecodingStrings;
+ return DecodeStatus::kDecodeInProgress;
+}
+
+} // namespace http2
diff --git a/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/altsvc_payload_decoder.h b/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/altsvc_payload_decoder.h
new file mode 100644
index 00000000000..e3523f9e0d2
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/altsvc_payload_decoder.h
@@ -0,0 +1,64 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_HTTP2_DECODER_PAYLOAD_DECODERS_ALTSVC_PAYLOAD_DECODER_H_
+#define QUICHE_HTTP2_DECODER_PAYLOAD_DECODERS_ALTSVC_PAYLOAD_DECODER_H_
+
+// Decodes the payload of a ALTSVC frame.
+
+#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_status.h"
+#include "net/third_party/quiche/src/http2/decoder/frame_decoder_state.h"
+#include "net/third_party/quiche/src/http2/http2_structures.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_export.h"
+
+namespace http2 {
+namespace test {
+class AltSvcPayloadDecoderPeer;
+} // namespace test
+
+class HTTP2_EXPORT_PRIVATE AltSvcPayloadDecoder {
+ public:
+ // States during decoding of a ALTSVC frame.
+ enum class PayloadState {
+ // Start decoding the fixed size structure at the start of an ALTSVC
+ // frame (Http2AltSvcFields).
+ kStartDecodingStruct,
+
+ // Handle the DecodeStatus returned from starting or resuming the
+ // decoding of Http2AltSvcFields. If complete, calls OnAltSvcStart.
+ kMaybeDecodedStruct,
+
+ // Reports the value of the strings (origin and value) of an ALTSVC frame
+ // to the listener.
+ kDecodingStrings,
+
+ // The initial decode buffer wasn't large enough for the Http2AltSvcFields,
+ // so this state resumes the decoding when ResumeDecodingPayload is called
+ // later with a new DecodeBuffer.
+ kResumeDecodingStruct,
+ };
+
+ // Starts the decoding of a ALTSVC frame's payload, and completes it if the
+ // entire payload is in the provided decode buffer.
+ DecodeStatus StartDecodingPayload(FrameDecoderState* state, DecodeBuffer* db);
+
+ // Resumes decoding a ALTSVC frame's payload that has been split across
+ // decode buffers.
+ DecodeStatus ResumeDecodingPayload(FrameDecoderState* state,
+ DecodeBuffer* db);
+
+ private:
+ friend class test::AltSvcPayloadDecoderPeer;
+
+ // Implements state kDecodingStrings.
+ DecodeStatus DecodeStrings(FrameDecoderState* state, DecodeBuffer* db);
+
+ Http2AltSvcFields altsvc_fields_;
+ PayloadState payload_state_;
+};
+
+} // namespace http2
+
+#endif // QUICHE_HTTP2_DECODER_PAYLOAD_DECODERS_ALTSVC_PAYLOAD_DECODER_H_
diff --git a/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/altsvc_payload_decoder_test.cc b/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/altsvc_payload_decoder_test.cc
new file mode 100644
index 00000000000..bf928d3ae50
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/altsvc_payload_decoder_test.cc
@@ -0,0 +1,121 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/http2/decoder/payload_decoders/altsvc_payload_decoder.h"
+
+#include <stddef.h>
+
+#include "base/logging.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener.h"
+#include "net/third_party/quiche/src/http2/decoder/payload_decoders/payload_decoder_base_test_util.h"
+#include "net/third_party/quiche/src/http2/http2_constants.h"
+#include "net/third_party/quiche/src/http2/http2_structures_test_util.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string.h"
+#include "net/third_party/quiche/src/http2/test_tools/frame_parts.h"
+#include "net/third_party/quiche/src/http2/test_tools/frame_parts_collector.h"
+#include "net/third_party/quiche/src/http2/test_tools/http2_random.h"
+#include "net/third_party/quiche/src/http2/tools/http2_frame_builder.h"
+#include "net/third_party/quiche/src/http2/tools/random_decoder_test.h"
+
+namespace http2 {
+namespace test {
+
+// Provides friend access to an instance of the payload decoder, and also
+// provides info to aid in testing.
+class AltSvcPayloadDecoderPeer {
+ public:
+ static constexpr Http2FrameType FrameType() { return Http2FrameType::ALTSVC; }
+
+ // Returns the mask of flags that affect the decoding of the payload (i.e.
+ // flags that that indicate the presence of certain fields or padding).
+ static constexpr uint8_t FlagsAffectingPayloadDecoding() { return 0; }
+};
+
+namespace {
+
+struct Listener : public FramePartsCollector {
+ void OnAltSvcStart(const Http2FrameHeader& header,
+ size_t origin_length,
+ size_t value_length) override {
+ VLOG(1) << "OnAltSvcStart header: " << header
+ << "; origin_length=" << origin_length
+ << "; value_length=" << value_length;
+ StartFrame(header)->OnAltSvcStart(header, origin_length, value_length);
+ }
+
+ void OnAltSvcOriginData(const char* data, size_t len) override {
+ VLOG(1) << "OnAltSvcOriginData: len=" << len;
+ CurrentFrame()->OnAltSvcOriginData(data, len);
+ }
+
+ void OnAltSvcValueData(const char* data, size_t len) override {
+ VLOG(1) << "OnAltSvcValueData: len=" << len;
+ CurrentFrame()->OnAltSvcValueData(data, len);
+ }
+
+ void OnAltSvcEnd() override {
+ VLOG(1) << "OnAltSvcEnd";
+ EndFrame()->OnAltSvcEnd();
+ }
+
+ void OnFrameSizeError(const Http2FrameHeader& header) override {
+ VLOG(1) << "OnFrameSizeError: " << header;
+ FrameError(header)->OnFrameSizeError(header);
+ }
+};
+
+class AltSvcPayloadDecoderTest
+ : public AbstractPayloadDecoderTest<AltSvcPayloadDecoder,
+ AltSvcPayloadDecoderPeer,
+ Listener> {};
+
+// Confirm we get an error if the payload is not long enough to hold
+// Http2AltSvcFields and the indicated length of origin.
+TEST_F(AltSvcPayloadDecoderTest, Truncated) {
+ Http2FrameBuilder fb;
+ fb.Append(Http2AltSvcFields{0xffff}); // The longest possible origin length.
+ fb.Append("Too little origin!");
+ EXPECT_TRUE(
+ VerifyDetectsFrameSizeError(0, fb.buffer(), /*approve_size*/ nullptr));
+}
+
+class AltSvcPayloadLengthTests : public AltSvcPayloadDecoderTest,
+ public ::testing::WithParamInterface<
+ ::testing::tuple<uint16_t, uint32_t>> {
+ protected:
+ AltSvcPayloadLengthTests()
+ : origin_length_(::testing::get<0>(GetParam())),
+ value_length_(::testing::get<1>(GetParam())) {
+ VLOG(1) << "################ origin_length_=" << origin_length_
+ << " value_length_=" << value_length_ << " ################";
+ }
+
+ const uint16_t origin_length_;
+ const uint32_t value_length_;
+};
+
+INSTANTIATE_TEST_CASE_P(VariousOriginAndValueLengths,
+ AltSvcPayloadLengthTests,
+ ::testing::Combine(::testing::Values(0, 1, 3, 65535),
+ ::testing::Values(0, 1, 3, 65537)));
+
+TEST_P(AltSvcPayloadLengthTests, ValidOriginAndValueLength) {
+ Http2String origin = Random().RandString(origin_length_);
+ Http2String value = Random().RandString(value_length_);
+ Http2FrameBuilder fb;
+ fb.Append(Http2AltSvcFields{origin_length_});
+ fb.Append(origin);
+ fb.Append(value);
+ Http2FrameHeader header(fb.size(), Http2FrameType::ALTSVC, RandFlags(),
+ RandStreamId());
+ set_frame_header(header);
+ FrameParts expected(header);
+ expected.SetAltSvcExpected(origin, value);
+ ASSERT_TRUE(DecodePayloadAndValidateSeveralWays(fb.buffer(), expected));
+}
+
+} // namespace
+} // namespace test
+} // namespace http2
diff --git a/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/continuation_payload_decoder.cc b/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/continuation_payload_decoder.cc
new file mode 100644
index 00000000000..aa9c8172f32
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/continuation_payload_decoder.cc
@@ -0,0 +1,58 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/http2/decoder/payload_decoders/continuation_payload_decoder.h"
+
+#include <stddef.h>
+
+#include "base/logging.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h"
+#include "net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener.h"
+#include "net/third_party/quiche/src/http2/http2_constants.h"
+#include "net/third_party/quiche/src/http2/http2_structures.h"
+
+namespace http2 {
+
+DecodeStatus ContinuationPayloadDecoder::StartDecodingPayload(
+ FrameDecoderState* state,
+ DecodeBuffer* db) {
+ const Http2FrameHeader& frame_header = state->frame_header();
+ const uint32_t total_length = frame_header.payload_length;
+
+ DVLOG(2) << "ContinuationPayloadDecoder::StartDecodingPayload: "
+ << frame_header;
+ DCHECK_EQ(Http2FrameType::CONTINUATION, frame_header.type);
+ DCHECK_LE(db->Remaining(), total_length);
+ DCHECK_EQ(0, frame_header.flags & ~(Http2FrameFlag::END_HEADERS));
+
+ state->InitializeRemainders();
+ state->listener()->OnContinuationStart(frame_header);
+ return ResumeDecodingPayload(state, db);
+}
+
+DecodeStatus ContinuationPayloadDecoder::ResumeDecodingPayload(
+ FrameDecoderState* state,
+ DecodeBuffer* db) {
+ DVLOG(2) << "ContinuationPayloadDecoder::ResumeDecodingPayload"
+ << " remaining_payload=" << state->remaining_payload()
+ << " db->Remaining=" << db->Remaining();
+ DCHECK_EQ(Http2FrameType::CONTINUATION, state->frame_header().type);
+ DCHECK_LE(state->remaining_payload(), state->frame_header().payload_length);
+ DCHECK_LE(db->Remaining(), state->remaining_payload());
+
+ size_t avail = db->Remaining();
+ DCHECK_LE(avail, state->remaining_payload());
+ if (avail > 0) {
+ state->listener()->OnHpackFragment(db->cursor(), avail);
+ db->AdvanceCursor(avail);
+ state->ConsumePayload(avail);
+ }
+ if (state->remaining_payload() == 0) {
+ state->listener()->OnContinuationEnd();
+ return DecodeStatus::kDecodeDone;
+ }
+ return DecodeStatus::kDecodeInProgress;
+}
+
+} // namespace http2
diff --git a/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/continuation_payload_decoder.h b/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/continuation_payload_decoder.h
new file mode 100644
index 00000000000..63b16ae638b
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/continuation_payload_decoder.h
@@ -0,0 +1,31 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_HTTP2_DECODER_PAYLOAD_DECODERS_CONTINUATION_PAYLOAD_DECODER_H_
+#define QUICHE_HTTP2_DECODER_PAYLOAD_DECODERS_CONTINUATION_PAYLOAD_DECODER_H_
+
+// Decodes the payload of a CONTINUATION frame.
+
+#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_status.h"
+#include "net/third_party/quiche/src/http2/decoder/frame_decoder_state.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_export.h"
+
+namespace http2 {
+
+class HTTP2_EXPORT_PRIVATE ContinuationPayloadDecoder {
+ public:
+ // Starts the decoding of a CONTINUATION frame's payload, and completes
+ // it if the entire payload is in the provided decode buffer.
+ DecodeStatus StartDecodingPayload(FrameDecoderState* state, DecodeBuffer* db);
+
+ // Resumes decoding a CONTINUATION frame's payload that has been split across
+ // decode buffers.
+ DecodeStatus ResumeDecodingPayload(FrameDecoderState* state,
+ DecodeBuffer* db);
+};
+
+} // namespace http2
+
+#endif // QUICHE_HTTP2_DECODER_PAYLOAD_DECODERS_CONTINUATION_PAYLOAD_DECODER_H_
diff --git a/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/continuation_payload_decoder_test.cc b/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/continuation_payload_decoder_test.cc
new file mode 100644
index 00000000000..63888d354a9
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/continuation_payload_decoder_test.cc
@@ -0,0 +1,85 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/http2/decoder/payload_decoders/continuation_payload_decoder.h"
+
+#include <stddef.h>
+
+#include <type_traits>
+
+#include "base/logging.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener.h"
+#include "net/third_party/quiche/src/http2/decoder/payload_decoders/payload_decoder_base_test_util.h"
+#include "net/third_party/quiche/src/http2/http2_constants.h"
+#include "net/third_party/quiche/src/http2/http2_structures.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string.h"
+#include "net/third_party/quiche/src/http2/test_tools/frame_parts.h"
+#include "net/third_party/quiche/src/http2/test_tools/frame_parts_collector.h"
+#include "net/third_party/quiche/src/http2/tools/random_decoder_test.h"
+
+namespace http2 {
+namespace test {
+
+// Provides friend access to an instance of the payload decoder, and also
+// provides info to aid in testing.
+class ContinuationPayloadDecoderPeer {
+ public:
+ static constexpr Http2FrameType FrameType() {
+ return Http2FrameType::CONTINUATION;
+ }
+
+ // Returns the mask of flags that affect the decoding of the payload (i.e.
+ // flags that that indicate the presence of certain fields or padding).
+ static constexpr uint8_t FlagsAffectingPayloadDecoding() { return 0; }
+};
+
+namespace {
+
+struct Listener : public FramePartsCollector {
+ void OnContinuationStart(const Http2FrameHeader& header) override {
+ VLOG(1) << "OnContinuationStart: " << header;
+ StartFrame(header)->OnContinuationStart(header);
+ }
+
+ void OnHpackFragment(const char* data, size_t len) override {
+ VLOG(1) << "OnHpackFragment: len=" << len;
+ CurrentFrame()->OnHpackFragment(data, len);
+ }
+
+ void OnContinuationEnd() override {
+ VLOG(1) << "OnContinuationEnd";
+ EndFrame()->OnContinuationEnd();
+ }
+};
+
+class ContinuationPayloadDecoderTest
+ : public AbstractPayloadDecoderTest<ContinuationPayloadDecoder,
+ ContinuationPayloadDecoderPeer,
+ Listener>,
+ public ::testing::WithParamInterface<uint32_t> {
+ protected:
+ ContinuationPayloadDecoderTest() : length_(GetParam()) {
+ VLOG(1) << "################ length_=" << length_ << " ################";
+ }
+
+ const uint32_t length_;
+};
+
+INSTANTIATE_TEST_CASE_P(VariousLengths,
+ ContinuationPayloadDecoderTest,
+ ::testing::Values(0, 1, 2, 3, 4, 5, 6));
+
+TEST_P(ContinuationPayloadDecoderTest, ValidLength) {
+ Http2String hpack_payload = Random().RandString(length_);
+ Http2FrameHeader frame_header(length_, Http2FrameType::CONTINUATION,
+ RandFlags(), RandStreamId());
+ set_frame_header(frame_header);
+ FrameParts expected(frame_header, hpack_payload);
+ EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(hpack_payload, expected));
+}
+
+} // namespace
+} // namespace test
+} // namespace http2
diff --git a/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/data_payload_decoder.cc b/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/data_payload_decoder.cc
new file mode 100644
index 00000000000..b6b80414738
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/data_payload_decoder.cc
@@ -0,0 +1,127 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/http2/decoder/payload_decoders/data_payload_decoder.h"
+
+#include <stddef.h>
+
+#include "base/logging.h"
+#include "base/macros.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h"
+#include "net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener.h"
+#include "net/third_party/quiche/src/http2/http2_constants.h"
+#include "net/third_party/quiche/src/http2/http2_structures.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_bug_tracker.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_macros.h"
+
+namespace http2 {
+
+std::ostream& operator<<(std::ostream& out,
+ DataPayloadDecoder::PayloadState v) {
+ switch (v) {
+ case DataPayloadDecoder::PayloadState::kReadPadLength:
+ return out << "kReadPadLength";
+ case DataPayloadDecoder::PayloadState::kReadPayload:
+ return out << "kReadPayload";
+ case DataPayloadDecoder::PayloadState::kSkipPadding:
+ return out << "kSkipPadding";
+ }
+ // Since the value doesn't come over the wire, only a programming bug should
+ // result in reaching this point.
+ int unknown = static_cast<int>(v);
+ HTTP2_BUG << "Invalid DataPayloadDecoder::PayloadState: " << unknown;
+ return out << "DataPayloadDecoder::PayloadState(" << unknown << ")";
+}
+
+DecodeStatus DataPayloadDecoder::StartDecodingPayload(FrameDecoderState* state,
+ DecodeBuffer* db) {
+ const Http2FrameHeader& frame_header = state->frame_header();
+ const uint32_t total_length = frame_header.payload_length;
+
+ DVLOG(2) << "DataPayloadDecoder::StartDecodingPayload: " << frame_header;
+ DCHECK_EQ(Http2FrameType::DATA, frame_header.type);
+ DCHECK_LE(db->Remaining(), total_length);
+ DCHECK_EQ(0, frame_header.flags &
+ ~(Http2FrameFlag::END_STREAM | Http2FrameFlag::PADDED));
+
+ // Special case for the hoped for common case: unpadded and fits fully into
+ // the decode buffer. TO BE SEEN if that is true. It certainly requires that
+ // the transport buffers be large (e.g. >> 16KB typically).
+ // TODO(jamessynge) Add counters.
+ DVLOG(2) << "StartDecodingPayload total_length=" << total_length;
+ if (!frame_header.IsPadded()) {
+ DVLOG(2) << "StartDecodingPayload !IsPadded";
+ if (db->Remaining() == total_length) {
+ DVLOG(2) << "StartDecodingPayload all present";
+ // Note that we don't cache the listener field so that the callee can
+ // replace it if the frame is bad.
+ // If this case is common enough, consider combining the 3 callbacks
+ // into one.
+ state->listener()->OnDataStart(frame_header);
+ if (total_length > 0) {
+ state->listener()->OnDataPayload(db->cursor(), total_length);
+ db->AdvanceCursor(total_length);
+ }
+ state->listener()->OnDataEnd();
+ return DecodeStatus::kDecodeDone;
+ }
+ payload_state_ = PayloadState::kReadPayload;
+ } else {
+ payload_state_ = PayloadState::kReadPadLength;
+ }
+ state->InitializeRemainders();
+ state->listener()->OnDataStart(frame_header);
+ return ResumeDecodingPayload(state, db);
+}
+
+DecodeStatus DataPayloadDecoder::ResumeDecodingPayload(FrameDecoderState* state,
+ DecodeBuffer* db) {
+ DVLOG(2) << "DataPayloadDecoder::ResumeDecodingPayload payload_state_="
+ << payload_state_;
+ const Http2FrameHeader& frame_header = state->frame_header();
+ DCHECK_EQ(Http2FrameType::DATA, frame_header.type);
+ DCHECK_LE(state->remaining_payload_and_padding(),
+ frame_header.payload_length);
+ DCHECK_LE(db->Remaining(), state->remaining_payload_and_padding());
+ DecodeStatus status;
+ size_t avail;
+ switch (payload_state_) {
+ case PayloadState::kReadPadLength:
+ // ReadPadLength handles the OnPadLength callback, and updating the
+ // remaining_payload and remaining_padding fields. If the amount of
+ // padding is too large to fit in the frame's payload, ReadPadLength
+ // instead calls OnPaddingTooLong and returns kDecodeError.
+ status = state->ReadPadLength(db, /*report_pad_length*/ true);
+ if (status != DecodeStatus::kDecodeDone) {
+ return status;
+ }
+ HTTP2_FALLTHROUGH;
+
+ case PayloadState::kReadPayload:
+ avail = state->AvailablePayload(db);
+ if (avail > 0) {
+ state->listener()->OnDataPayload(db->cursor(), avail);
+ db->AdvanceCursor(avail);
+ state->ConsumePayload(avail);
+ }
+ if (state->remaining_payload() > 0) {
+ payload_state_ = PayloadState::kReadPayload;
+ return DecodeStatus::kDecodeInProgress;
+ }
+ HTTP2_FALLTHROUGH;
+
+ case PayloadState::kSkipPadding:
+ // SkipPadding handles the OnPadding callback.
+ if (state->SkipPadding(db)) {
+ state->listener()->OnDataEnd();
+ return DecodeStatus::kDecodeDone;
+ }
+ payload_state_ = PayloadState::kSkipPadding;
+ return DecodeStatus::kDecodeInProgress;
+ }
+ HTTP2_BUG << "PayloadState: " << payload_state_;
+ return DecodeStatus::kDecodeError;
+}
+
+} // namespace http2
diff --git a/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/data_payload_decoder.h b/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/data_payload_decoder.h
new file mode 100644
index 00000000000..1e083a93343
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/data_payload_decoder.h
@@ -0,0 +1,54 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_HTTP2_DECODER_PAYLOAD_DECODERS_DATA_PAYLOAD_DECODER_H_
+#define QUICHE_HTTP2_DECODER_PAYLOAD_DECODERS_DATA_PAYLOAD_DECODER_H_
+
+// Decodes the payload of a DATA frame.
+
+#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_status.h"
+#include "net/third_party/quiche/src/http2/decoder/frame_decoder_state.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_export.h"
+
+namespace http2 {
+namespace test {
+class DataPayloadDecoderPeer;
+} // namespace test
+
+class HTTP2_EXPORT_PRIVATE DataPayloadDecoder {
+ public:
+ // States during decoding of a DATA frame.
+ enum class PayloadState {
+ // The frame is padded and we need to read the PAD_LENGTH field (1 byte),
+ // and then call OnPadLength
+ kReadPadLength,
+
+ // Report the non-padding portion of the payload to the listener's
+ // OnDataPayload method.
+ kReadPayload,
+
+ // The decoder has finished with the non-padding portion of the payload,
+ // and is now ready to skip the trailing padding, if the frame has any.
+ kSkipPadding,
+ };
+
+ // Starts decoding a DATA frame's payload, and completes it if
+ // the entire payload is in the provided decode buffer.
+ DecodeStatus StartDecodingPayload(FrameDecoderState* state, DecodeBuffer* db);
+
+ // Resumes decoding a DATA frame's payload that has been split across
+ // decode buffers.
+ DecodeStatus ResumeDecodingPayload(FrameDecoderState* state,
+ DecodeBuffer* db);
+
+ private:
+ friend class test::DataPayloadDecoderPeer;
+
+ PayloadState payload_state_;
+};
+
+} // namespace http2
+
+#endif // QUICHE_HTTP2_DECODER_PAYLOAD_DECODERS_DATA_PAYLOAD_DECODER_H_
diff --git a/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/data_payload_decoder_test.cc b/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/data_payload_decoder_test.cc
new file mode 100644
index 00000000000..e5253b93cd9
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/data_payload_decoder_test.cc
@@ -0,0 +1,113 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/http2/decoder/payload_decoders/data_payload_decoder.h"
+
+#include <stddef.h>
+
+#include "base/logging.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener.h"
+#include "net/third_party/quiche/src/http2/decoder/payload_decoders/payload_decoder_base_test_util.h"
+#include "net/third_party/quiche/src/http2/http2_constants.h"
+#include "net/third_party/quiche/src/http2/http2_structures.h"
+#include "net/third_party/quiche/src/http2/http2_structures_test_util.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_test_helpers.h"
+#include "net/third_party/quiche/src/http2/test_tools/frame_parts.h"
+#include "net/third_party/quiche/src/http2/test_tools/frame_parts_collector.h"
+#include "net/third_party/quiche/src/http2/test_tools/http2_random.h"
+#include "net/third_party/quiche/src/http2/tools/http2_frame_builder.h"
+#include "net/third_party/quiche/src/http2/tools/random_decoder_test.h"
+
+using ::testing::AssertionResult;
+
+namespace http2 {
+namespace test {
+
+// Provides friend access to an instance of the payload decoder, and also
+// provides info to aid in testing.
+class DataPayloadDecoderPeer {
+ public:
+ static constexpr Http2FrameType FrameType() { return Http2FrameType::DATA; }
+
+ // Returns the mask of flags that affect the decoding of the payload (i.e.
+ // flags that that indicate the presence of certain fields or padding).
+ static constexpr uint8_t FlagsAffectingPayloadDecoding() {
+ return Http2FrameFlag::PADDED;
+ }
+};
+
+namespace {
+
+struct Listener : public FramePartsCollector {
+ void OnDataStart(const Http2FrameHeader& header) override {
+ VLOG(1) << "OnDataStart: " << header;
+ StartFrame(header)->OnDataStart(header);
+ }
+
+ void OnDataPayload(const char* data, size_t len) override {
+ VLOG(1) << "OnDataPayload: len=" << len;
+ CurrentFrame()->OnDataPayload(data, len);
+ }
+
+ void OnDataEnd() override {
+ VLOG(1) << "OnDataEnd";
+ EndFrame()->OnDataEnd();
+ }
+
+ void OnPadLength(size_t pad_length) override {
+ VLOG(1) << "OnPadLength: " << pad_length;
+ CurrentFrame()->OnPadLength(pad_length);
+ }
+
+ void OnPadding(const char* padding, size_t skipped_length) override {
+ VLOG(1) << "OnPadding: " << skipped_length;
+ CurrentFrame()->OnPadding(padding, skipped_length);
+ }
+
+ void OnPaddingTooLong(const Http2FrameHeader& header,
+ size_t missing_length) override {
+ VLOG(1) << "OnPaddingTooLong: " << header
+ << " missing_length: " << missing_length;
+ EndFrame()->OnPaddingTooLong(header, missing_length);
+ }
+};
+
+class DataPayloadDecoderTest
+ : public AbstractPaddablePayloadDecoderTest<DataPayloadDecoder,
+ DataPayloadDecoderPeer,
+ Listener> {
+ protected:
+ AssertionResult CreateAndDecodeDataOfSize(size_t data_size) {
+ Reset();
+ uint8_t flags = RandFlags();
+
+ Http2String data_payload = Random().RandString(data_size);
+ frame_builder_.Append(data_payload);
+ MaybeAppendTrailingPadding();
+
+ Http2FrameHeader frame_header(frame_builder_.size(), Http2FrameType::DATA,
+ flags, RandStreamId());
+ set_frame_header(frame_header);
+ ScrubFlagsOfHeader(&frame_header);
+ FrameParts expected(frame_header, data_payload, total_pad_length_);
+ VERIFY_AND_RETURN_SUCCESS(
+ DecodePayloadAndValidateSeveralWays(frame_builder_.buffer(), expected));
+ }
+};
+
+INSTANTIATE_TEST_CASE_P(VariousPadLengths,
+ DataPayloadDecoderTest,
+ ::testing::Values(0, 1, 2, 3, 4, 254, 255, 256));
+
+TEST_P(DataPayloadDecoderTest, VariousDataPayloadSizes) {
+ for (size_t data_size : {0, 1, 2, 3, 255, 256, 1024}) {
+ EXPECT_TRUE(CreateAndDecodeDataOfSize(data_size));
+ }
+}
+
+} // namespace
+} // namespace test
+} // namespace http2
diff --git a/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/goaway_payload_decoder.cc b/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/goaway_payload_decoder.cc
new file mode 100644
index 00000000000..cf7b673c5ee
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/goaway_payload_decoder.cc
@@ -0,0 +1,121 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/http2/decoder/payload_decoders/goaway_payload_decoder.h"
+
+#include <stddef.h>
+
+#include "base/logging.h"
+#include "base/macros.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h"
+#include "net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener.h"
+#include "net/third_party/quiche/src/http2/http2_constants.h"
+#include "net/third_party/quiche/src/http2/http2_structures.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_bug_tracker.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_macros.h"
+
+namespace http2 {
+
+std::ostream& operator<<(std::ostream& out,
+ GoAwayPayloadDecoder::PayloadState v) {
+ switch (v) {
+ case GoAwayPayloadDecoder::PayloadState::kStartDecodingFixedFields:
+ return out << "kStartDecodingFixedFields";
+ case GoAwayPayloadDecoder::PayloadState::kHandleFixedFieldsStatus:
+ return out << "kHandleFixedFieldsStatus";
+ case GoAwayPayloadDecoder::PayloadState::kReadOpaqueData:
+ return out << "kReadOpaqueData";
+ case GoAwayPayloadDecoder::PayloadState::kResumeDecodingFixedFields:
+ return out << "kResumeDecodingFixedFields";
+ }
+ // Since the value doesn't come over the wire, only a programming bug should
+ // result in reaching this point.
+ int unknown = static_cast<int>(v);
+ HTTP2_BUG << "Invalid GoAwayPayloadDecoder::PayloadState: " << unknown;
+ return out << "GoAwayPayloadDecoder::PayloadState(" << unknown << ")";
+}
+
+DecodeStatus GoAwayPayloadDecoder::StartDecodingPayload(
+ FrameDecoderState* state,
+ DecodeBuffer* db) {
+ DVLOG(2) << "GoAwayPayloadDecoder::StartDecodingPayload: "
+ << state->frame_header();
+ DCHECK_EQ(Http2FrameType::GOAWAY, state->frame_header().type);
+ DCHECK_LE(db->Remaining(), state->frame_header().payload_length);
+ DCHECK_EQ(0, state->frame_header().flags);
+
+ state->InitializeRemainders();
+ payload_state_ = PayloadState::kStartDecodingFixedFields;
+ return ResumeDecodingPayload(state, db);
+}
+
+DecodeStatus GoAwayPayloadDecoder::ResumeDecodingPayload(
+ FrameDecoderState* state,
+ DecodeBuffer* db) {
+ DVLOG(2) << "GoAwayPayloadDecoder::ResumeDecodingPayload: remaining_payload="
+ << state->remaining_payload()
+ << ", db->Remaining=" << db->Remaining();
+
+ const Http2FrameHeader& frame_header = state->frame_header();
+ DCHECK_EQ(Http2FrameType::GOAWAY, frame_header.type);
+ DCHECK_LE(db->Remaining(), frame_header.payload_length);
+ DCHECK_NE(PayloadState::kHandleFixedFieldsStatus, payload_state_);
+
+ // |status| has to be initialized to some value to avoid compiler error in
+ // case PayloadState::kHandleFixedFieldsStatus below, but value does not
+ // matter, see DCHECK_NE above.
+ DecodeStatus status = DecodeStatus::kDecodeError;
+ size_t avail;
+ while (true) {
+ DVLOG(2) << "GoAwayPayloadDecoder::ResumeDecodingPayload payload_state_="
+ << payload_state_;
+ switch (payload_state_) {
+ case PayloadState::kStartDecodingFixedFields:
+ status = state->StartDecodingStructureInPayload(&goaway_fields_, db);
+ HTTP2_FALLTHROUGH;
+
+ case PayloadState::kHandleFixedFieldsStatus:
+ if (status == DecodeStatus::kDecodeDone) {
+ state->listener()->OnGoAwayStart(frame_header, goaway_fields_);
+ } else {
+ // Not done decoding the structure. Either we've got more payload
+ // to decode, or we've run out because the payload is too short,
+ // in which case OnFrameSizeError will have already been called.
+ DCHECK((status == DecodeStatus::kDecodeInProgress &&
+ state->remaining_payload() > 0) ||
+ (status == DecodeStatus::kDecodeError &&
+ state->remaining_payload() == 0))
+ << "\n status=" << status
+ << "; remaining_payload=" << state->remaining_payload();
+ payload_state_ = PayloadState::kResumeDecodingFixedFields;
+ return status;
+ }
+ HTTP2_FALLTHROUGH;
+
+ case PayloadState::kReadOpaqueData:
+ // The opaque data is all the remains to be decoded, so anything left
+ // in the decode buffer is opaque data.
+ avail = db->Remaining();
+ if (avail > 0) {
+ state->listener()->OnGoAwayOpaqueData(db->cursor(), avail);
+ db->AdvanceCursor(avail);
+ state->ConsumePayload(avail);
+ }
+ if (state->remaining_payload() > 0) {
+ payload_state_ = PayloadState::kReadOpaqueData;
+ return DecodeStatus::kDecodeInProgress;
+ }
+ state->listener()->OnGoAwayEnd();
+ return DecodeStatus::kDecodeDone;
+
+ case PayloadState::kResumeDecodingFixedFields:
+ status = state->ResumeDecodingStructureInPayload(&goaway_fields_, db);
+ payload_state_ = PayloadState::kHandleFixedFieldsStatus;
+ continue;
+ }
+ HTTP2_BUG << "PayloadState: " << payload_state_;
+ }
+}
+
+} // namespace http2
diff --git a/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/goaway_payload_decoder.h b/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/goaway_payload_decoder.h
new file mode 100644
index 00000000000..7a50873d108
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/goaway_payload_decoder.h
@@ -0,0 +1,66 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_HTTP2_DECODER_PAYLOAD_DECODERS_GOAWAY_PAYLOAD_DECODER_H_
+#define QUICHE_HTTP2_DECODER_PAYLOAD_DECODERS_GOAWAY_PAYLOAD_DECODER_H_
+
+// Decodes the payload of a GOAWAY frame.
+
+// TODO(jamessynge): Sweep through all payload decoders, changing the names of
+// the PayloadState enums so that they are really states, and not actions.
+
+#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_status.h"
+#include "net/third_party/quiche/src/http2/decoder/frame_decoder_state.h"
+#include "net/third_party/quiche/src/http2/http2_structures.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_export.h"
+
+namespace http2 {
+namespace test {
+class GoAwayPayloadDecoderPeer;
+} // namespace test
+
+class HTTP2_EXPORT_PRIVATE GoAwayPayloadDecoder {
+ public:
+ // States during decoding of a GOAWAY frame.
+ enum class PayloadState {
+ // At the start of the GOAWAY frame payload, ready to start decoding the
+ // fixed size fields into goaway_fields_.
+ kStartDecodingFixedFields,
+
+ // Handle the DecodeStatus returned from starting or resuming the
+ // decoding of Http2GoAwayFields into goaway_fields_. If complete,
+ // calls OnGoAwayStart.
+ kHandleFixedFieldsStatus,
+
+ // Report the Opaque Data portion of the payload to the listener's
+ // OnGoAwayOpaqueData method, and call OnGoAwayEnd when the end of the
+ // payload is reached.
+ kReadOpaqueData,
+
+ // The fixed size fields weren't all available when the decoder first
+ // tried to decode them (state kStartDecodingFixedFields); this state
+ // resumes the decoding when ResumeDecodingPayload is called later.
+ kResumeDecodingFixedFields,
+ };
+
+ // Starts the decoding of a GOAWAY frame's payload, and completes it if
+ // the entire payload is in the provided decode buffer.
+ DecodeStatus StartDecodingPayload(FrameDecoderState* state, DecodeBuffer* db);
+
+ // Resumes decoding a GOAWAY frame's payload that has been split across
+ // decode buffers.
+ DecodeStatus ResumeDecodingPayload(FrameDecoderState* state,
+ DecodeBuffer* db);
+
+ private:
+ friend class test::GoAwayPayloadDecoderPeer;
+
+ Http2GoAwayFields goaway_fields_;
+ PayloadState payload_state_;
+};
+
+} // namespace http2
+
+#endif // QUICHE_HTTP2_DECODER_PAYLOAD_DECODERS_GOAWAY_PAYLOAD_DECODER_H_
diff --git a/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/goaway_payload_decoder_test.cc b/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/goaway_payload_decoder_test.cc
new file mode 100644
index 00000000000..1df52148687
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/goaway_payload_decoder_test.cc
@@ -0,0 +1,107 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/http2/decoder/payload_decoders/goaway_payload_decoder.h"
+
+#include <stddef.h>
+
+#include "base/logging.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener.h"
+#include "net/third_party/quiche/src/http2/decoder/payload_decoders/payload_decoder_base_test_util.h"
+#include "net/third_party/quiche/src/http2/http2_constants.h"
+#include "net/third_party/quiche/src/http2/http2_structures_test_util.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string.h"
+#include "net/third_party/quiche/src/http2/test_tools/frame_parts.h"
+#include "net/third_party/quiche/src/http2/test_tools/frame_parts_collector.h"
+#include "net/third_party/quiche/src/http2/test_tools/http2_random.h"
+#include "net/third_party/quiche/src/http2/tools/http2_frame_builder.h"
+#include "net/third_party/quiche/src/http2/tools/random_decoder_test.h"
+
+namespace http2 {
+namespace test {
+
+class GoAwayPayloadDecoderPeer {
+ public:
+ static constexpr Http2FrameType FrameType() { return Http2FrameType::GOAWAY; }
+
+ // Returns the mask of flags that affect the decoding of the payload (i.e.
+ // flags that that indicate the presence of certain fields or padding).
+ static constexpr uint8_t FlagsAffectingPayloadDecoding() { return 0; }
+};
+
+namespace {
+
+struct Listener : public FramePartsCollector {
+ void OnGoAwayStart(const Http2FrameHeader& header,
+ const Http2GoAwayFields& goaway) override {
+ VLOG(1) << "OnGoAwayStart header: " << header << "; goaway: " << goaway;
+ StartFrame(header)->OnGoAwayStart(header, goaway);
+ }
+
+ void OnGoAwayOpaqueData(const char* data, size_t len) override {
+ VLOG(1) << "OnGoAwayOpaqueData: len=" << len;
+ CurrentFrame()->OnGoAwayOpaqueData(data, len);
+ }
+
+ void OnGoAwayEnd() override {
+ VLOG(1) << "OnGoAwayEnd";
+ EndFrame()->OnGoAwayEnd();
+ }
+
+ void OnFrameSizeError(const Http2FrameHeader& header) override {
+ VLOG(1) << "OnFrameSizeError: " << header;
+ FrameError(header)->OnFrameSizeError(header);
+ }
+};
+
+class GoAwayPayloadDecoderTest
+ : public AbstractPayloadDecoderTest<GoAwayPayloadDecoder,
+ GoAwayPayloadDecoderPeer,
+ Listener> {};
+
+// Confirm we get an error if the payload is not long enough to hold
+// Http2GoAwayFields.
+TEST_F(GoAwayPayloadDecoderTest, Truncated) {
+ auto approve_size = [](size_t size) {
+ return size != Http2GoAwayFields::EncodedSize();
+ };
+ Http2FrameBuilder fb;
+ fb.Append(Http2GoAwayFields(123, Http2ErrorCode::ENHANCE_YOUR_CALM));
+ EXPECT_TRUE(VerifyDetectsFrameSizeError(0, fb.buffer(), approve_size));
+}
+
+class GoAwayOpaqueDataLengthTests
+ : public GoAwayPayloadDecoderTest,
+ public ::testing::WithParamInterface<uint32_t> {
+ protected:
+ GoAwayOpaqueDataLengthTests() : length_(GetParam()) {
+ VLOG(1) << "################ length_=" << length_ << " ################";
+ }
+
+ const uint32_t length_;
+};
+
+INSTANTIATE_TEST_CASE_P(VariousLengths,
+ GoAwayOpaqueDataLengthTests,
+ ::testing::Values(0, 1, 2, 3, 4, 5, 6));
+
+TEST_P(GoAwayOpaqueDataLengthTests, ValidLength) {
+ Http2GoAwayFields goaway;
+ Randomize(&goaway, RandomPtr());
+ Http2String opaque_data = Random().RandString(length_);
+ Http2FrameBuilder fb;
+ fb.Append(goaway);
+ fb.Append(opaque_data);
+ Http2FrameHeader header(fb.size(), Http2FrameType::GOAWAY, RandFlags(),
+ RandStreamId());
+ set_frame_header(header);
+ FrameParts expected(header, opaque_data);
+ expected.SetOptGoaway(goaway);
+ ASSERT_TRUE(DecodePayloadAndValidateSeveralWays(fb.buffer(), expected));
+}
+
+} // namespace
+} // namespace test
+} // namespace http2
diff --git a/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/headers_payload_decoder.cc b/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/headers_payload_decoder.cc
new file mode 100644
index 00000000000..ecaf0fade11
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/headers_payload_decoder.cc
@@ -0,0 +1,175 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/http2/decoder/payload_decoders/headers_payload_decoder.h"
+
+#include <stddef.h>
+
+#include "base/logging.h"
+#include "base/macros.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h"
+#include "net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener.h"
+#include "net/third_party/quiche/src/http2/http2_constants.h"
+#include "net/third_party/quiche/src/http2/http2_structures.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_bug_tracker.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_macros.h"
+
+namespace http2 {
+
+std::ostream& operator<<(std::ostream& out,
+ HeadersPayloadDecoder::PayloadState v) {
+ switch (v) {
+ case HeadersPayloadDecoder::PayloadState::kReadPadLength:
+ return out << "kReadPadLength";
+ case HeadersPayloadDecoder::PayloadState::kStartDecodingPriorityFields:
+ return out << "kStartDecodingPriorityFields";
+ case HeadersPayloadDecoder::PayloadState::kResumeDecodingPriorityFields:
+ return out << "kResumeDecodingPriorityFields";
+ case HeadersPayloadDecoder::PayloadState::kReadPayload:
+ return out << "kReadPayload";
+ case HeadersPayloadDecoder::PayloadState::kSkipPadding:
+ return out << "kSkipPadding";
+ }
+ // Since the value doesn't come over the wire, only a programming bug should
+ // result in reaching this point.
+ int unknown = static_cast<int>(v);
+ HTTP2_BUG << "Invalid HeadersPayloadDecoder::PayloadState: " << unknown;
+ return out << "HeadersPayloadDecoder::PayloadState(" << unknown << ")";
+}
+
+DecodeStatus HeadersPayloadDecoder::StartDecodingPayload(
+ FrameDecoderState* state,
+ DecodeBuffer* db) {
+ const Http2FrameHeader& frame_header = state->frame_header();
+ const uint32_t total_length = frame_header.payload_length;
+
+ DVLOG(2) << "HeadersPayloadDecoder::StartDecodingPayload: " << frame_header;
+
+ DCHECK_EQ(Http2FrameType::HEADERS, frame_header.type);
+ DCHECK_LE(db->Remaining(), total_length);
+ DCHECK_EQ(0, frame_header.flags &
+ ~(Http2FrameFlag::END_STREAM | Http2FrameFlag::END_HEADERS |
+ Http2FrameFlag::PADDED | Http2FrameFlag::PRIORITY));
+
+ // Special case for HEADERS frames that contain only the HPACK block
+ // (fragment or whole) and that fit fully into the decode buffer.
+ // Why? Unencoded browser GET requests are typically under 1K and HPACK
+ // commonly shrinks request headers by 80%, so we can expect this to
+ // be common.
+ // TODO(jamessynge) Add counters here and to Spdy for determining how
+ // common this situation is. A possible approach is to create a
+ // Http2FrameDecoderListener that counts the callbacks and then forwards
+ // them on to another listener, which makes it easy to add and remove
+ // counting on a connection or even frame basis.
+
+ // PADDED and PRIORITY both extra steps to decode, but if neither flag is
+ // set then we can decode faster.
+ const auto payload_flags = Http2FrameFlag::PADDED | Http2FrameFlag::PRIORITY;
+ if (!frame_header.HasAnyFlags(payload_flags)) {
+ DVLOG(2) << "StartDecodingPayload !IsPadded && !HasPriority";
+ if (db->Remaining() == total_length) {
+ DVLOG(2) << "StartDecodingPayload all present";
+ // Note that we don't cache the listener field so that the callee can
+ // replace it if the frame is bad.
+ // If this case is common enough, consider combining the 3 callbacks
+ // into one, especially if END_HEADERS is also set.
+ state->listener()->OnHeadersStart(frame_header);
+ if (total_length > 0) {
+ state->listener()->OnHpackFragment(db->cursor(), total_length);
+ db->AdvanceCursor(total_length);
+ }
+ state->listener()->OnHeadersEnd();
+ return DecodeStatus::kDecodeDone;
+ }
+ payload_state_ = PayloadState::kReadPayload;
+ } else if (frame_header.IsPadded()) {
+ payload_state_ = PayloadState::kReadPadLength;
+ } else {
+ DCHECK(frame_header.HasPriority()) << frame_header;
+ payload_state_ = PayloadState::kStartDecodingPriorityFields;
+ }
+ state->InitializeRemainders();
+ state->listener()->OnHeadersStart(frame_header);
+ return ResumeDecodingPayload(state, db);
+}
+
+DecodeStatus HeadersPayloadDecoder::ResumeDecodingPayload(
+ FrameDecoderState* state,
+ DecodeBuffer* db) {
+ DVLOG(2) << "HeadersPayloadDecoder::ResumeDecodingPayload "
+ << "remaining_payload=" << state->remaining_payload()
+ << "; db->Remaining=" << db->Remaining();
+
+ const Http2FrameHeader& frame_header = state->frame_header();
+
+ DCHECK_EQ(Http2FrameType::HEADERS, frame_header.type);
+ DCHECK_LE(state->remaining_payload_and_padding(),
+ frame_header.payload_length);
+ DCHECK_LE(db->Remaining(), state->remaining_payload_and_padding());
+ DecodeStatus status;
+ size_t avail;
+ while (true) {
+ DVLOG(2) << "HeadersPayloadDecoder::ResumeDecodingPayload payload_state_="
+ << payload_state_;
+ switch (payload_state_) {
+ case PayloadState::kReadPadLength:
+ // ReadPadLength handles the OnPadLength callback, and updating the
+ // remaining_payload and remaining_padding fields. If the amount of
+ // padding is too large to fit in the frame's payload, ReadPadLength
+ // instead calls OnPaddingTooLong and returns kDecodeError.
+ status = state->ReadPadLength(db, /*report_pad_length*/ true);
+ if (status != DecodeStatus::kDecodeDone) {
+ return status;
+ }
+ if (!frame_header.HasPriority()) {
+ payload_state_ = PayloadState::kReadPayload;
+ continue;
+ }
+ HTTP2_FALLTHROUGH;
+
+ case PayloadState::kStartDecodingPriorityFields:
+ status = state->StartDecodingStructureInPayload(&priority_fields_, db);
+ if (status != DecodeStatus::kDecodeDone) {
+ payload_state_ = PayloadState::kResumeDecodingPriorityFields;
+ return status;
+ }
+ state->listener()->OnHeadersPriority(priority_fields_);
+ HTTP2_FALLTHROUGH;
+
+ case PayloadState::kReadPayload:
+ avail = state->AvailablePayload(db);
+ if (avail > 0) {
+ state->listener()->OnHpackFragment(db->cursor(), avail);
+ db->AdvanceCursor(avail);
+ state->ConsumePayload(avail);
+ }
+ if (state->remaining_payload() > 0) {
+ payload_state_ = PayloadState::kReadPayload;
+ return DecodeStatus::kDecodeInProgress;
+ }
+ HTTP2_FALLTHROUGH;
+
+ case PayloadState::kSkipPadding:
+ // SkipPadding handles the OnPadding callback.
+ if (state->SkipPadding(db)) {
+ state->listener()->OnHeadersEnd();
+ return DecodeStatus::kDecodeDone;
+ }
+ payload_state_ = PayloadState::kSkipPadding;
+ return DecodeStatus::kDecodeInProgress;
+
+ case PayloadState::kResumeDecodingPriorityFields:
+ status = state->ResumeDecodingStructureInPayload(&priority_fields_, db);
+ if (status != DecodeStatus::kDecodeDone) {
+ return status;
+ }
+ state->listener()->OnHeadersPriority(priority_fields_);
+ payload_state_ = PayloadState::kReadPayload;
+ continue;
+ }
+ HTTP2_BUG << "PayloadState: " << payload_state_;
+ }
+}
+
+} // namespace http2
diff --git a/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/headers_payload_decoder.h b/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/headers_payload_decoder.h
new file mode 100644
index 00000000000..d3cdfe59c74
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/headers_payload_decoder.h
@@ -0,0 +1,67 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_HTTP2_DECODER_PAYLOAD_DECODERS_HEADERS_PAYLOAD_DECODER_H_
+#define QUICHE_HTTP2_DECODER_PAYLOAD_DECODERS_HEADERS_PAYLOAD_DECODER_H_
+
+// Decodes the payload of a HEADERS frame.
+
+#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_status.h"
+#include "net/third_party/quiche/src/http2/decoder/frame_decoder_state.h"
+#include "net/third_party/quiche/src/http2/http2_structures.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_export.h"
+
+namespace http2 {
+namespace test {
+class HeadersPayloadDecoderPeer;
+} // namespace test
+
+class HTTP2_EXPORT_PRIVATE HeadersPayloadDecoder {
+ public:
+ // States during decoding of a HEADERS frame, unless the fast path kicks
+ // in, in which case the state machine will be bypassed.
+ enum class PayloadState {
+ // The PADDED flag is set, and we now need to read the Pad Length field
+ // (the first byte of the payload, after the common frame header).
+ kReadPadLength,
+
+ // The PRIORITY flag is set, and we now need to read the fixed size priority
+ // fields (E, Stream Dependency, Weight) into priority_fields_. Calls on
+ // OnHeadersPriority if completely decodes those fields.
+ kStartDecodingPriorityFields,
+
+ // The decoder passes the non-padding portion of the remaining payload
+ // (i.e. the HPACK block fragment) to the listener's OnHpackFragment method.
+ kReadPayload,
+
+ // The decoder has finished with the HPACK block fragment, and is now
+ // ready to skip the trailing padding, if the frame has any.
+ kSkipPadding,
+
+ // The fixed size fields weren't all available when the decoder first tried
+ // to decode them (state kStartDecodingPriorityFields); this state resumes
+ // the decoding when ResumeDecodingPayload is called later.
+ kResumeDecodingPriorityFields,
+ };
+
+ // Starts the decoding of a HEADERS frame's payload, and completes it if
+ // the entire payload is in the provided decode buffer.
+ DecodeStatus StartDecodingPayload(FrameDecoderState* state, DecodeBuffer* db);
+
+ // Resumes decoding a HEADERS frame's payload that has been split across
+ // decode buffers.
+ DecodeStatus ResumeDecodingPayload(FrameDecoderState* state,
+ DecodeBuffer* db);
+
+ private:
+ friend class test::HeadersPayloadDecoderPeer;
+
+ PayloadState payload_state_;
+ Http2PriorityFields priority_fields_;
+};
+
+} // namespace http2
+
+#endif // QUICHE_HTTP2_DECODER_PAYLOAD_DECODERS_HEADERS_PAYLOAD_DECODER_H_
diff --git a/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/headers_payload_decoder_test.cc b/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/headers_payload_decoder_test.cc
new file mode 100644
index 00000000000..a6fcb65bc30
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/headers_payload_decoder_test.cc
@@ -0,0 +1,158 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/http2/decoder/payload_decoders/headers_payload_decoder.h"
+
+#include <stddef.h>
+
+#include "base/logging.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener.h"
+#include "net/third_party/quiche/src/http2/decoder/payload_decoders/payload_decoder_base_test_util.h"
+#include "net/third_party/quiche/src/http2/http2_constants.h"
+#include "net/third_party/quiche/src/http2/http2_structures_test_util.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string.h"
+#include "net/third_party/quiche/src/http2/test_tools/frame_parts.h"
+#include "net/third_party/quiche/src/http2/test_tools/frame_parts_collector.h"
+#include "net/third_party/quiche/src/http2/test_tools/http2_random.h"
+#include "net/third_party/quiche/src/http2/tools/http2_frame_builder.h"
+#include "net/third_party/quiche/src/http2/tools/random_decoder_test.h"
+
+namespace http2 {
+namespace test {
+
+class HeadersPayloadDecoderPeer {
+ public:
+ static constexpr Http2FrameType FrameType() {
+ return Http2FrameType::HEADERS;
+ }
+
+ // Returns the mask of flags that affect the decoding of the payload (i.e.
+ // flags that that indicate the presence of certain fields or padding).
+ static constexpr uint8_t FlagsAffectingPayloadDecoding() {
+ return Http2FrameFlag::PADDED | Http2FrameFlag::PRIORITY;
+ }
+};
+
+namespace {
+
+// Listener handles all On* methods that are expected to be called. If any other
+// On* methods of Http2FrameDecoderListener is called then the test fails; this
+// is achieved by way of FailingHttp2FrameDecoderListener, the base class of
+// FramePartsCollector.
+// These On* methods make use of StartFrame, EndFrame, etc. of the base class
+// to create and access to FrameParts instance(s) that will record the details.
+// After decoding, the test validation code can access the FramePart instance(s)
+// via the public methods of FramePartsCollector.
+struct Listener : public FramePartsCollector {
+ void OnHeadersStart(const Http2FrameHeader& header) override {
+ VLOG(1) << "OnHeadersStart: " << header;
+ StartFrame(header)->OnHeadersStart(header);
+ }
+
+ void OnHeadersPriority(const Http2PriorityFields& priority) override {
+ VLOG(1) << "OnHeadersPriority: " << priority;
+ CurrentFrame()->OnHeadersPriority(priority);
+ }
+
+ void OnHpackFragment(const char* data, size_t len) override {
+ VLOG(1) << "OnHpackFragment: len=" << len;
+ CurrentFrame()->OnHpackFragment(data, len);
+ }
+
+ void OnHeadersEnd() override {
+ VLOG(1) << "OnHeadersEnd";
+ EndFrame()->OnHeadersEnd();
+ }
+
+ void OnPadLength(size_t pad_length) override {
+ VLOG(1) << "OnPadLength: " << pad_length;
+ CurrentFrame()->OnPadLength(pad_length);
+ }
+
+ void OnPadding(const char* padding, size_t skipped_length) override {
+ VLOG(1) << "OnPadding: " << skipped_length;
+ CurrentFrame()->OnPadding(padding, skipped_length);
+ }
+
+ void OnPaddingTooLong(const Http2FrameHeader& header,
+ size_t missing_length) override {
+ VLOG(1) << "OnPaddingTooLong: " << header
+ << "; missing_length: " << missing_length;
+ FrameError(header)->OnPaddingTooLong(header, missing_length);
+ }
+
+ void OnFrameSizeError(const Http2FrameHeader& header) override {
+ VLOG(1) << "OnFrameSizeError: " << header;
+ FrameError(header)->OnFrameSizeError(header);
+ }
+};
+
+class HeadersPayloadDecoderTest
+ : public AbstractPaddablePayloadDecoderTest<HeadersPayloadDecoder,
+ HeadersPayloadDecoderPeer,
+ Listener> {};
+
+INSTANTIATE_TEST_CASE_P(VariousPadLengths,
+ HeadersPayloadDecoderTest,
+ ::testing::Values(0, 1, 2, 3, 4, 254, 255, 256));
+
+// Decode various sizes of (fake) HPACK payload, both with and without the
+// PRIORITY flag set.
+TEST_P(HeadersPayloadDecoderTest, VariousHpackPayloadSizes) {
+ for (size_t hpack_size : {0, 1, 2, 3, 255, 256, 1024}) {
+ LOG(INFO) << "########### hpack_size = " << hpack_size << " ###########";
+ Http2PriorityFields priority(RandStreamId(), 1 + Random().Rand8(),
+ Random().OneIn(2));
+
+ for (bool has_priority : {false, true}) {
+ Reset();
+ ASSERT_EQ(IsPadded() ? 1u : 0u, frame_builder_.size());
+ uint8_t flags = RandFlags();
+ if (has_priority) {
+ flags |= Http2FrameFlag::PRIORITY;
+ frame_builder_.Append(priority);
+ }
+
+ Http2String hpack_payload = Random().RandString(hpack_size);
+ frame_builder_.Append(hpack_payload);
+
+ MaybeAppendTrailingPadding();
+ Http2FrameHeader frame_header(frame_builder_.size(),
+ Http2FrameType::HEADERS, flags,
+ RandStreamId());
+ set_frame_header(frame_header);
+ ScrubFlagsOfHeader(&frame_header);
+ FrameParts expected(frame_header, hpack_payload, total_pad_length_);
+ if (has_priority) {
+ expected.SetOptPriority(priority);
+ }
+ EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(frame_builder_.buffer(),
+ expected));
+ }
+ }
+}
+
+// Confirm we get an error if the PRIORITY flag is set but the payload is
+// not long enough, regardless of the amount of (valid) padding.
+TEST_P(HeadersPayloadDecoderTest, Truncated) {
+ auto approve_size = [](size_t size) {
+ return size != Http2PriorityFields::EncodedSize();
+ };
+ Http2FrameBuilder fb;
+ fb.Append(Http2PriorityFields(RandStreamId(), 1 + Random().Rand8(),
+ Random().OneIn(2)));
+ EXPECT_TRUE(VerifyDetectsMultipleFrameSizeErrors(
+ Http2FrameFlag::PRIORITY, fb.buffer(), approve_size, total_pad_length_));
+}
+
+// Confirm we get an error if the PADDED flag is set but the payload is not
+// long enough to hold even the Pad Length amount of padding.
+TEST_P(HeadersPayloadDecoderTest, PaddingTooLong) {
+ EXPECT_TRUE(VerifyDetectsPaddingTooLong());
+}
+
+} // namespace
+} // namespace test
+} // namespace http2
diff --git a/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/payload_decoder_base_test_util.cc b/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/payload_decoder_base_test_util.cc
new file mode 100644
index 00000000000..88e1b98afe2
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/payload_decoder_base_test_util.cc
@@ -0,0 +1,97 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/http2/decoder/payload_decoders/payload_decoder_base_test_util.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/http2/decoder/frame_decoder_state_test_util.h"
+#include "net/third_party/quiche/src/http2/http2_structures_test_util.h"
+
+namespace http2 {
+namespace test {
+PayloadDecoderBaseTest::PayloadDecoderBaseTest() {
+ // If the test adds more data after the frame payload,
+ // stop as soon as the payload is decoded.
+ stop_decode_on_done_ = true;
+ frame_header_is_set_ = false;
+ Randomize(&frame_header_, RandomPtr());
+}
+
+DecodeStatus PayloadDecoderBaseTest::StartDecoding(DecodeBuffer* db) {
+ DVLOG(2) << "StartDecoding, db->Remaining=" << db->Remaining();
+ // Make sure sub-class has set frame_header_ so that we can inject it
+ // into the payload decoder below.
+ if (!frame_header_is_set_) {
+ ADD_FAILURE() << "frame_header_ is not set";
+ return DecodeStatus::kDecodeError;
+ }
+ // The contract with the payload decoders is that they won't receive a
+ // decode buffer that extends beyond the end of the frame.
+ if (db->Remaining() > frame_header_.payload_length) {
+ ADD_FAILURE() << "DecodeBuffer has too much data: " << db->Remaining()
+ << " > " << frame_header_.payload_length;
+ return DecodeStatus::kDecodeError;
+ }
+
+ // Prepare the payload decoder.
+ PreparePayloadDecoder();
+
+ // Reconstruct the FrameDecoderState, prepare the listener, and add it to
+ // the FrameDecoderState.
+ Http2DefaultReconstructObject(&frame_decoder_state_, RandomPtr());
+ frame_decoder_state_.set_listener(PrepareListener());
+
+ // Make sure that a listener was provided.
+ if (frame_decoder_state_.listener() == nullptr) {
+ ADD_FAILURE() << "PrepareListener must return a listener.";
+ return DecodeStatus::kDecodeError;
+ }
+
+ // Now that nothing in the payload decoder should be valid, inject the
+ // Http2FrameHeader whose payload we're about to decode. That header is the
+ // only state that a payload decoder should expect is valid when its Start
+ // method is called.
+ FrameDecoderStatePeer::set_frame_header(frame_header_, &frame_decoder_state_);
+ DecodeStatus status = StartDecodingPayload(db);
+ if (status != DecodeStatus::kDecodeInProgress) {
+ // Keep track of this so that a concrete test can verify that both fast
+ // and slow decoding paths have been tested.
+ ++fast_decode_count_;
+ }
+ return status;
+}
+
+DecodeStatus PayloadDecoderBaseTest::ResumeDecoding(DecodeBuffer* db) {
+ DVLOG(2) << "ResumeDecoding, db->Remaining=" << db->Remaining();
+ DecodeStatus status = ResumeDecodingPayload(db);
+ if (status != DecodeStatus::kDecodeInProgress) {
+ // Keep track of this so that a concrete test can verify that both fast
+ // and slow decoding paths have been tested.
+ ++slow_decode_count_;
+ }
+ return status;
+}
+
+::testing::AssertionResult
+PayloadDecoderBaseTest::DecodePayloadAndValidateSeveralWays(
+ Http2StringPiece payload,
+ Validator validator) {
+ VERIFY_TRUE(frame_header_is_set_);
+ // Cap the payload to be decoded at the declared payload length. This is
+ // required by the decoders' preconditions; they are designed on the
+ // assumption that they're never passed more than they're permitted to
+ // consume.
+ // Note that it is OK if the payload is too short; the validator may be
+ // designed to check for that.
+ if (payload.size() > frame_header_.payload_length) {
+ payload = Http2StringPiece(payload.data(), frame_header_.payload_length);
+ }
+ DecodeBuffer db(payload);
+ ResetDecodeSpeedCounters();
+ const bool kMayReturnZeroOnFirst = false;
+ return DecodeAndValidateSeveralWays(&db, kMayReturnZeroOnFirst, validator);
+}
+
+} // namespace test
+} // namespace http2
diff --git a/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/payload_decoder_base_test_util.h b/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/payload_decoder_base_test_util.h
new file mode 100644
index 00000000000..8297e70dab4
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/payload_decoder_base_test_util.h
@@ -0,0 +1,455 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_HTTP2_DECODER_PAYLOAD_DECODERS_PAYLOAD_DECODER_BASE_TEST_UTIL_H_
+#define QUICHE_HTTP2_DECODER_PAYLOAD_DECODERS_PAYLOAD_DECODER_BASE_TEST_UTIL_H_
+
+// Base class for testing concrete payload decoder classes.
+
+#include <stddef.h>
+
+#include "base/logging.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_status.h"
+#include "net/third_party/quiche/src/http2/decoder/frame_decoder_state.h"
+#include "net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener.h"
+#include "net/third_party/quiche/src/http2/http2_constants.h"
+#include "net/third_party/quiche/src/http2/http2_constants_test_util.h"
+#include "net/third_party/quiche/src/http2/http2_structures.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_reconstruct_object.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string_piece.h"
+#include "net/third_party/quiche/src/http2/test_tools/frame_parts.h"
+#include "net/third_party/quiche/src/http2/tools/http2_frame_builder.h"
+#include "net/third_party/quiche/src/http2/tools/random_decoder_test.h"
+
+namespace http2 {
+namespace test {
+
+// Base class for tests of payload decoders. Below this there is a templated
+// sub-class that adds a bunch of type specific features.
+class PayloadDecoderBaseTest : public RandomDecoderTest {
+ protected:
+ PayloadDecoderBaseTest();
+
+ // Virtual functions to be implemented by the test classes for the individual
+ // payload decoders...
+
+ // Start decoding the payload.
+ virtual DecodeStatus StartDecodingPayload(DecodeBuffer* db) = 0;
+
+ // Resume decoding the payload.
+ virtual DecodeStatus ResumeDecodingPayload(DecodeBuffer* db) = 0;
+
+ // In support of ensuring that we're really accessing and updating the
+ // decoder, prepare the decoder by, for example, overwriting the decoder.
+ virtual void PreparePayloadDecoder() = 0;
+
+ // Get the listener to be inserted into the FrameDecoderState, ready for
+ // listening (e.g. reset if it is a FramePartsCollector).
+ virtual Http2FrameDecoderListener* PrepareListener() = 0;
+
+ // Record a frame header for use on each call to StartDecoding.
+ void set_frame_header(const Http2FrameHeader& header) {
+ EXPECT_EQ(0, InvalidFlagMaskForFrameType(header.type) & header.flags);
+ if (!frame_header_is_set_ || frame_header_ != header) {
+ VLOG(2) << "set_frame_header: " << frame_header_;
+ }
+ frame_header_ = header;
+ frame_header_is_set_ = true;
+ }
+
+ FrameDecoderState* mutable_state() { return &frame_decoder_state_; }
+
+ // Randomize the payload decoder, sets the payload decoder's frame_header_,
+ // then start decoding the payload. Called by RandomDecoderTest. This method
+ // is final so that we can always perform certain actions when
+ // RandomDecoderTest starts the decoding of a payload, such as randomizing the
+ // the payload decoder, injecting the frame header and counting fast decoding
+ // cases. Sub-classes must implement StartDecodingPayload to perform their
+ // initial decoding of a frame's payload.
+ DecodeStatus StartDecoding(DecodeBuffer* db) final;
+
+ // Called by RandomDecoderTest. This method is final so that we can always
+ // perform certain actions when RandomDecoderTest calls it, such as counting
+ // slow decode cases. Sub-classes must implement ResumeDecodingPayload to
+ // continue decoding the frame's payload, which must not all be in one buffer.
+ DecodeStatus ResumeDecoding(DecodeBuffer* db) final;
+
+ // Given the specified payload (without the common frame header), decode
+ // it with several partitionings of the payload.
+ ::testing::AssertionResult DecodePayloadAndValidateSeveralWays(
+ Http2StringPiece payload,
+ Validator validator);
+
+ // TODO(jamessynge): Add helper method for verifying these are both non-zero,
+ // and call the new method from tests that expect successful decoding.
+ void ResetDecodeSpeedCounters() {
+ fast_decode_count_ = 0;
+ slow_decode_count_ = 0;
+ }
+
+ // Count of payloads that are full decoded by StartDecodingPayload, or that
+ // an error was detected by StartDecodingPayload.
+ size_t fast_decode_count_ = 0;
+
+ // Count of payloads that require calling ResumeDecodingPayload in order to
+ // decode them completely (or to detect an error during decoding).
+ size_t slow_decode_count_ = 0;
+
+ private:
+ bool frame_header_is_set_ = false;
+ Http2FrameHeader frame_header_;
+ FrameDecoderState frame_decoder_state_;
+};
+
+// Base class for payload decoders of type Decoder, with corresponding test
+// peer of type DecoderPeer, and using class Listener as the implementation
+// of Http2FrameDecoderListenerInterface to be used during decoding.
+// Typically Listener is a sub-class of FramePartsCollector.
+// SupportedFrameType is set to false only for UnknownPayloadDecoder.
+template <class Decoder,
+ class DecoderPeer,
+ class Listener,
+ bool SupportedFrameType = true>
+class AbstractPayloadDecoderTest : public PayloadDecoderBaseTest {
+ protected:
+ // An ApproveSize function returns true to approve decoding the specified
+ // size of payload, else false to skip that size. Typically used for negative
+ // tests; for example, decoding a SETTINGS frame at all sizes except for
+ // multiples of 6.
+ typedef std::function<bool(size_t size)> ApproveSize;
+
+ AbstractPayloadDecoderTest() {}
+
+ // These tests are in setup rather than the constructor for two reasons:
+ // 1) Constructors are not allowed to fail, so gUnit documents that EXPECT_*
+ // and ASSERT_* are not allowed in constructors, and should instead be in
+ // SetUp if they are needed before the body of the test is executed.
+ // 2) To allow the sub-class constructor to make any desired modifications to
+ // the DecoderPeer before these tests are executed; in particular,
+ // UnknownPayloadDecoderPeer has not got a fixed frame type, but it is
+ // instead set during the test's constructor.
+ void SetUp() override {
+ PayloadDecoderBaseTest::SetUp();
+
+ // Confirm that DecoderPeer et al returns sensible values. Using auto as the
+ // variable type so that no (narrowing) conversions take place that hide
+ // problems; i.e. if someone changes KnownFlagsMaskForFrameType so that it
+ // doesn't return a uint8, and has bits above the low-order 8 bits set, this
+ // bit of paranoia should detect the problem before we get too far.
+ auto frame_type = DecoderPeer::FrameType();
+ if (SupportedFrameType) {
+ EXPECT_TRUE(IsSupportedHttp2FrameType(frame_type)) << frame_type;
+ } else {
+ EXPECT_FALSE(IsSupportedHttp2FrameType(frame_type)) << frame_type;
+ }
+
+ auto known_flags = KnownFlagsMaskForFrameType(frame_type);
+ EXPECT_EQ(known_flags, known_flags & 0xff);
+
+ auto flags_to_avoid = DecoderPeer::FlagsAffectingPayloadDecoding();
+ EXPECT_EQ(flags_to_avoid, flags_to_avoid & known_flags);
+ }
+
+ void PreparePayloadDecoder() override {
+ Http2DefaultReconstructObject(&payload_decoder_, RandomPtr());
+ }
+
+ Http2FrameDecoderListener* PrepareListener() override {
+ listener_.Reset();
+ return &listener_;
+ }
+
+ // Returns random flags, but only those valid for the frame type, yet not
+ // those that the DecoderPeer says will affect the decoding of the payload
+ // (e.g. the PRIORTY flag on a HEADERS frame or PADDED on DATA frames).
+ uint8_t RandFlags() {
+ return Random().Rand8() &
+ KnownFlagsMaskForFrameType(DecoderPeer::FrameType()) &
+ ~DecoderPeer::FlagsAffectingPayloadDecoding();
+ }
+
+ // Start decoding the payload.
+ DecodeStatus StartDecodingPayload(DecodeBuffer* db) override {
+ DVLOG(2) << "StartDecodingPayload, db->Remaining=" << db->Remaining();
+ return payload_decoder_.StartDecodingPayload(mutable_state(), db);
+ }
+
+ // Resume decoding the payload.
+ DecodeStatus ResumeDecodingPayload(DecodeBuffer* db) override {
+ DVLOG(2) << "ResumeDecodingPayload, db->Remaining=" << db->Remaining();
+ return payload_decoder_.ResumeDecodingPayload(mutable_state(), db);
+ }
+
+ // Decode one frame's payload and confirm that the listener recorded the
+ // expected FrameParts instance, and only FrameParts instance. The payload
+ // will be decoded several times with different partitionings of the payload,
+ // and after each the validator will be called.
+ AssertionResult DecodePayloadAndValidateSeveralWays(
+ Http2StringPiece payload,
+ const FrameParts& expected) {
+ auto validator = [&expected, this]() -> AssertionResult {
+ VERIFY_FALSE(listener_.IsInProgress());
+ VERIFY_EQ(1u, listener_.size());
+ VERIFY_AND_RETURN_SUCCESS(expected.VerifyEquals(*listener_.frame(0)));
+ };
+ return PayloadDecoderBaseTest::DecodePayloadAndValidateSeveralWays(
+ payload, ValidateDoneAndEmpty(validator));
+ }
+
+ // Decode one frame's payload, expecting that the final status will be
+ // kDecodeError, and that OnFrameSizeError will have been called on the
+ // listener. The payload will be decoded several times with different
+ // partitionings of the payload. The type WrappedValidator is either
+ // RandomDecoderTest::Validator, RandomDecoderTest::NoArgValidator or
+ // std::nullptr_t (not extra validation).
+ template <typename WrappedValidator>
+ ::testing::AssertionResult VerifyDetectsFrameSizeError(
+ Http2StringPiece payload,
+ const Http2FrameHeader& header,
+ WrappedValidator wrapped_validator) {
+ set_frame_header(header);
+ // If wrapped_validator is not a RandomDecoderTest::Validator, make it so.
+ Validator validator = ToValidator(wrapped_validator);
+ // And wrap that validator in another which will check that we've reached
+ // the expected state of kDecodeError with OnFrameSizeError having been
+ // called by the payload decoder.
+ validator = [header, validator, this](
+ const DecodeBuffer& input,
+ DecodeStatus status) -> ::testing::AssertionResult {
+ DVLOG(2) << "VerifyDetectsFrameSizeError validator; status=" << status
+ << "; input.Remaining=" << input.Remaining();
+ VERIFY_EQ(DecodeStatus::kDecodeError, status);
+ VERIFY_FALSE(listener_.IsInProgress());
+ VERIFY_EQ(1u, listener_.size());
+ const FrameParts* frame = listener_.frame(0);
+ VERIFY_EQ(header, frame->GetFrameHeader());
+ VERIFY_TRUE(frame->GetHasFrameSizeError());
+ // Verify did not get OnPaddingTooLong, as we should only ever produce
+ // one of these two errors for a single frame.
+ VERIFY_FALSE(frame->GetOptMissingLength());
+ return validator(input, status);
+ };
+ VERIFY_AND_RETURN_SUCCESS(
+ PayloadDecoderBaseTest::DecodePayloadAndValidateSeveralWays(payload,
+ validator));
+ }
+
+ // Confirm that we get OnFrameSizeError when trying to decode unpadded_payload
+ // at all sizes from zero to unpadded_payload.size(), except those sizes not
+ // approved by approve_size.
+ // If total_pad_length is greater than zero, then that amount of padding
+ // is added to the payload (including the Pad Length field).
+ // The flags will be required_flags, PADDED if total_pad_length > 0, and some
+ // randomly selected flag bits not excluded by FlagsAffectingPayloadDecoding.
+ ::testing::AssertionResult VerifyDetectsMultipleFrameSizeErrors(
+ uint8_t required_flags,
+ Http2StringPiece unpadded_payload,
+ ApproveSize approve_size,
+ int total_pad_length) {
+ // required_flags should come from those that are defined for the frame
+ // type AND are those that affect the decoding of the payload (otherwise,
+ // the flag shouldn't be required).
+ Http2FrameType frame_type = DecoderPeer::FrameType();
+ VERIFY_EQ(required_flags,
+ required_flags & KnownFlagsMaskForFrameType(frame_type));
+ VERIFY_EQ(required_flags,
+ required_flags & DecoderPeer::FlagsAffectingPayloadDecoding());
+
+ if (0 !=
+ (Http2FrameFlag::PADDED & KnownFlagsMaskForFrameType(frame_type))) {
+ // Frame type supports padding.
+ if (total_pad_length == 0) {
+ required_flags &= ~Http2FrameFlag::PADDED;
+ } else {
+ required_flags |= Http2FrameFlag::PADDED;
+ }
+ } else {
+ VERIFY_EQ(0, total_pad_length);
+ }
+
+ bool validated = false;
+ for (size_t real_payload_size = 0;
+ real_payload_size <= unpadded_payload.size(); ++real_payload_size) {
+ if (approve_size != nullptr && !approve_size(real_payload_size)) {
+ continue;
+ }
+ VLOG(1) << "real_payload_size=" << real_payload_size;
+ uint8_t flags = required_flags | RandFlags();
+ Http2FrameBuilder fb;
+ if (total_pad_length > 0) {
+ // total_pad_length_ includes the size of the Pad Length field, and thus
+ // ranges from 0 (no PADDED flag) to 256 (Pad Length == 255).
+ fb.AppendUInt8(total_pad_length - 1);
+ }
+ // Append a subset of the unpadded_payload, which the decoder should
+ // determine is not a valid amount.
+ fb.Append(unpadded_payload.substr(0, real_payload_size));
+ if (total_pad_length > 0) {
+ fb.AppendZeroes(total_pad_length - 1);
+ }
+ // We choose a random stream id because the payload decoders aren't
+ // checking stream ids.
+ uint32_t stream_id = RandStreamId();
+ Http2FrameHeader header(fb.size(), frame_type, flags, stream_id);
+ VERIFY_SUCCESS(VerifyDetectsFrameSizeError(fb.buffer(), header, nullptr));
+ validated = true;
+ }
+ VERIFY_TRUE(validated);
+ return ::testing::AssertionSuccess();
+ }
+
+ // As above, but for frames without padding.
+ ::testing::AssertionResult VerifyDetectsFrameSizeError(
+ uint8_t required_flags,
+ Http2StringPiece unpadded_payload,
+ const ApproveSize& approve_size) {
+ Http2FrameType frame_type = DecoderPeer::FrameType();
+ uint8_t known_flags = KnownFlagsMaskForFrameType(frame_type);
+ VERIFY_EQ(0, known_flags & Http2FrameFlag::PADDED);
+ VERIFY_EQ(0, required_flags & Http2FrameFlag::PADDED);
+ VERIFY_AND_RETURN_SUCCESS(VerifyDetectsMultipleFrameSizeErrors(
+ required_flags, unpadded_payload, approve_size, 0));
+ }
+
+ Listener listener_;
+ union {
+ // Confirm at compile time that Decoder can be in an anonymous union,
+ // i.e. complain loudly if Decoder has members that prevent this, as it
+ // becomes annoying and possibly difficult to deal with non-anonymous
+ // unions and such union members.
+ Decoder payload_decoder_;
+ };
+};
+
+// A base class for tests parameterized by the total number of bytes of
+// padding, including the Pad Length field (i.e. a total_pad_length of 0
+// means unpadded as there is then no room for the Pad Length field).
+// The frame type must support padding.
+template <class Decoder, class DecoderPeer, class Listener>
+class AbstractPaddablePayloadDecoderTest
+ : public AbstractPayloadDecoderTest<Decoder, DecoderPeer, Listener>,
+ public ::testing::WithParamInterface<int> {
+ typedef AbstractPayloadDecoderTest<Decoder, DecoderPeer, Listener> Base;
+
+ protected:
+ using Base::listener_;
+ using Base::Random;
+ using Base::RandStreamId;
+ using Base::set_frame_header;
+ typedef typename Base::Validator Validator;
+
+ AbstractPaddablePayloadDecoderTest() : total_pad_length_(GetParam()) {
+ LOG(INFO) << "total_pad_length_ = " << total_pad_length_;
+ }
+
+ // Note that total_pad_length_ includes the size of the Pad Length field,
+ // and thus ranges from 0 (no PADDED flag) to 256 (Pad Length == 255).
+ bool IsPadded() const { return total_pad_length_ > 0; }
+
+ // Value of the Pad Length field. Only call if IsPadded.
+ size_t pad_length() const {
+ EXPECT_TRUE(IsPadded());
+ return total_pad_length_ - 1;
+ }
+
+ // Clear the frame builder and add the Pad Length field if appropriate.
+ void Reset() {
+ frame_builder_ = Http2FrameBuilder();
+ if (IsPadded()) {
+ frame_builder_.AppendUInt8(pad_length());
+ }
+ }
+
+ void MaybeAppendTrailingPadding() {
+ if (IsPadded()) {
+ frame_builder_.AppendZeroes(pad_length());
+ }
+ }
+
+ uint8_t RandFlags() {
+ uint8_t flags = Base::RandFlags();
+ if (IsPadded()) {
+ flags |= Http2FrameFlag::PADDED;
+ } else {
+ flags &= ~Http2FrameFlag::PADDED;
+ }
+ return flags;
+ }
+
+ // Verify that we get OnPaddingTooLong when decoding payload, and that the
+ // amount of missing padding is as specified. header.IsPadded must be true,
+ // and the payload must be empty or the PadLength field must be too large.
+ ::testing::AssertionResult VerifyDetectsPaddingTooLong(
+ Http2StringPiece payload,
+ const Http2FrameHeader& header,
+ size_t expected_missing_length) {
+ set_frame_header(header);
+ auto& listener = listener_;
+ Validator validator =
+ [header, expected_missing_length, &listener](
+ const DecodeBuffer& input,
+ DecodeStatus status) -> ::testing::AssertionResult {
+ VERIFY_EQ(DecodeStatus::kDecodeError, status);
+ VERIFY_FALSE(listener.IsInProgress());
+ VERIFY_EQ(1u, listener.size());
+ const FrameParts* frame = listener.frame(0);
+ VERIFY_EQ(header, frame->GetFrameHeader());
+ VERIFY_TRUE(frame->GetOptMissingLength());
+ VERIFY_EQ(expected_missing_length, frame->GetOptMissingLength().value());
+ // Verify did not get OnFrameSizeError.
+ VERIFY_FALSE(frame->GetHasFrameSizeError());
+ return ::testing::AssertionSuccess();
+ };
+ VERIFY_AND_RETURN_SUCCESS(
+ PayloadDecoderBaseTest::DecodePayloadAndValidateSeveralWays(payload,
+ validator));
+ }
+
+ // Verifies that we get OnPaddingTooLong for a padded frame payload whose
+ // (randomly selected) payload length is less than total_pad_length_.
+ // Flags will be selected at random, except PADDED will be set and
+ // flags_to_avoid will not be set. The stream id is selected at random.
+ ::testing::AssertionResult VerifyDetectsPaddingTooLong() {
+ uint8_t flags = RandFlags() | Http2FrameFlag::PADDED;
+
+ // Create an all padding payload for total_pad_length_.
+ int payload_length = 0;
+ Http2FrameBuilder fb;
+ if (IsPadded()) {
+ fb.AppendUInt8(pad_length());
+ fb.AppendZeroes(pad_length());
+ VLOG(1) << "fb.size=" << fb.size();
+ // Pick a random length for the payload that is shorter than neccesary.
+ payload_length = Random().Uniform(fb.size());
+ }
+
+ VLOG(1) << "payload_length=" << payload_length;
+ Http2String payload = fb.buffer().substr(0, payload_length);
+
+ // The missing length is the amount we cut off the end, unless
+ // payload_length is zero, in which case the decoder knows only that 1
+ // byte, the Pad Length field, is missing.
+ size_t missing_length =
+ payload_length == 0 ? 1 : fb.size() - payload_length;
+ VLOG(1) << "missing_length=" << missing_length;
+
+ const Http2FrameHeader header(payload_length, DecoderPeer::FrameType(),
+ flags, RandStreamId());
+ VERIFY_AND_RETURN_SUCCESS(
+ VerifyDetectsPaddingTooLong(payload, header, missing_length));
+ }
+
+ // total_pad_length_ includes the size of the Pad Length field, and thus
+ // ranges from 0 (no PADDED flag) to 256 (Pad Length == 255).
+ const size_t total_pad_length_;
+ Http2FrameBuilder frame_builder_;
+};
+
+} // namespace test
+} // namespace http2
+
+#endif // QUICHE_HTTP2_DECODER_PAYLOAD_DECODERS_PAYLOAD_DECODER_BASE_TEST_UTIL_H_
diff --git a/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/ping_payload_decoder.cc b/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/ping_payload_decoder.cc
new file mode 100644
index 00000000000..8d98046d70a
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/ping_payload_decoder.cc
@@ -0,0 +1,89 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/http2/decoder/payload_decoders/ping_payload_decoder.h"
+
+#include "base/logging.h"
+#include "net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener.h"
+#include "net/third_party/quiche/src/http2/http2_constants.h"
+
+namespace http2 {
+namespace {
+constexpr auto kOpaqueSize = Http2PingFields::EncodedSize();
+}
+
+DecodeStatus PingPayloadDecoder::StartDecodingPayload(FrameDecoderState* state,
+ DecodeBuffer* db) {
+ const Http2FrameHeader& frame_header = state->frame_header();
+ const uint32_t total_length = frame_header.payload_length;
+
+ DVLOG(2) << "PingPayloadDecoder::StartDecodingPayload: " << frame_header;
+ DCHECK_EQ(Http2FrameType::PING, frame_header.type);
+ DCHECK_LE(db->Remaining(), total_length);
+ DCHECK_EQ(0, frame_header.flags & ~(Http2FrameFlag::ACK));
+
+ // Is the payload entirely in the decode buffer and is it the correct size?
+ // Given the size of the header and payload (17 bytes total), this is most
+ // likely the case the vast majority of the time.
+ if (db->Remaining() == kOpaqueSize && total_length == kOpaqueSize) {
+ // Special case this situation as it allows us to avoid any copying;
+ // the other path makes two copies, first into the buffer in
+ // Http2StructureDecoder as it accumulates the 8 bytes of opaque data,
+ // and a second copy into the Http2PingFields member of in this class.
+ // This supports the claim that this decoder is (mostly) non-buffering.
+ static_assert(sizeof(Http2PingFields) == kOpaqueSize,
+ "If not, then can't enter this block!");
+ auto* ping = reinterpret_cast<const Http2PingFields*>(db->cursor());
+ if (frame_header.IsAck()) {
+ state->listener()->OnPingAck(frame_header, *ping);
+ } else {
+ state->listener()->OnPing(frame_header, *ping);
+ }
+ db->AdvanceCursor(kOpaqueSize);
+ return DecodeStatus::kDecodeDone;
+ }
+ state->InitializeRemainders();
+ return HandleStatus(
+ state, state->StartDecodingStructureInPayload(&ping_fields_, db));
+}
+
+DecodeStatus PingPayloadDecoder::ResumeDecodingPayload(FrameDecoderState* state,
+ DecodeBuffer* db) {
+ DVLOG(2) << "ResumeDecodingPayload: remaining_payload="
+ << state->remaining_payload();
+ DCHECK_EQ(Http2FrameType::PING, state->frame_header().type);
+ DCHECK_LE(db->Remaining(), state->frame_header().payload_length);
+ return HandleStatus(
+ state, state->ResumeDecodingStructureInPayload(&ping_fields_, db));
+}
+
+DecodeStatus PingPayloadDecoder::HandleStatus(FrameDecoderState* state,
+ DecodeStatus status) {
+ DVLOG(2) << "HandleStatus: status=" << status
+ << "; remaining_payload=" << state->remaining_payload();
+ if (status == DecodeStatus::kDecodeDone) {
+ if (state->remaining_payload() == 0) {
+ const Http2FrameHeader& frame_header = state->frame_header();
+ if (frame_header.IsAck()) {
+ state->listener()->OnPingAck(frame_header, ping_fields_);
+ } else {
+ state->listener()->OnPing(frame_header, ping_fields_);
+ }
+ return DecodeStatus::kDecodeDone;
+ }
+ // Payload is too long.
+ return state->ReportFrameSizeError();
+ }
+ // Not done decoding the structure. Either we've got more payload to decode,
+ // or we've run out because the payload is too short.
+ DCHECK(
+ (status == DecodeStatus::kDecodeInProgress &&
+ state->remaining_payload() > 0) ||
+ (status == DecodeStatus::kDecodeError && state->remaining_payload() == 0))
+ << "\n status=" << status
+ << "; remaining_payload=" << state->remaining_payload();
+ return status;
+}
+
+} // namespace http2
diff --git a/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/ping_payload_decoder.h b/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/ping_payload_decoder.h
new file mode 100644
index 00000000000..84704fb77a7
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/ping_payload_decoder.h
@@ -0,0 +1,43 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_HTTP2_DECODER_PAYLOAD_DECODERS_PING_PAYLOAD_DECODER_H_
+#define QUICHE_HTTP2_DECODER_PAYLOAD_DECODERS_PING_PAYLOAD_DECODER_H_
+
+// Decodes the payload of a PING frame; for the RFC, see:
+// http://httpwg.org/specs/rfc7540.html#PING
+
+#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_status.h"
+#include "net/third_party/quiche/src/http2/decoder/frame_decoder_state.h"
+#include "net/third_party/quiche/src/http2/http2_structures.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_export.h"
+
+namespace http2 {
+namespace test {
+class PingPayloadDecoderPeer;
+} // namespace test
+
+class HTTP2_EXPORT_PRIVATE PingPayloadDecoder {
+ public:
+ // Starts the decoding of a PING frame's payload, and completes it if the
+ // entire payload is in the provided decode buffer.
+ DecodeStatus StartDecodingPayload(FrameDecoderState* state, DecodeBuffer* db);
+
+ // Resumes decoding a PING frame's payload that has been split across
+ // decode buffers.
+ DecodeStatus ResumeDecodingPayload(FrameDecoderState* state,
+ DecodeBuffer* db);
+
+ private:
+ friend class test::PingPayloadDecoderPeer;
+
+ DecodeStatus HandleStatus(FrameDecoderState* state, DecodeStatus status);
+
+ Http2PingFields ping_fields_;
+};
+
+} // namespace http2
+
+#endif // QUICHE_HTTP2_DECODER_PAYLOAD_DECODERS_PING_PAYLOAD_DECODER_H_
diff --git a/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/ping_payload_decoder_test.cc b/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/ping_payload_decoder_test.cc
new file mode 100644
index 00000000000..34833b2f50a
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/ping_payload_decoder_test.cc
@@ -0,0 +1,110 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/http2/decoder/payload_decoders/ping_payload_decoder.h"
+
+#include <stddef.h>
+
+#include "base/logging.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener.h"
+#include "net/third_party/quiche/src/http2/decoder/payload_decoders/payload_decoder_base_test_util.h"
+#include "net/third_party/quiche/src/http2/http2_constants.h"
+#include "net/third_party/quiche/src/http2/http2_structures_test_util.h"
+#include "net/third_party/quiche/src/http2/test_tools/frame_parts.h"
+#include "net/third_party/quiche/src/http2/test_tools/frame_parts_collector.h"
+#include "net/third_party/quiche/src/http2/test_tools/http2_random.h"
+#include "net/third_party/quiche/src/http2/tools/http2_frame_builder.h"
+#include "net/third_party/quiche/src/http2/tools/random_decoder_test.h"
+
+namespace http2 {
+namespace test {
+
+class PingPayloadDecoderPeer {
+ public:
+ static constexpr Http2FrameType FrameType() { return Http2FrameType::PING; }
+
+ // Returns the mask of flags that affect the decoding of the payload (i.e.
+ // flags that that indicate the presence of certain fields or padding).
+ static constexpr uint8_t FlagsAffectingPayloadDecoding() { return 0; }
+};
+
+namespace {
+
+struct Listener : public FramePartsCollector {
+ void OnPing(const Http2FrameHeader& header,
+ const Http2PingFields& ping) override {
+ VLOG(1) << "OnPing: " << header << "; " << ping;
+ StartAndEndFrame(header)->OnPing(header, ping);
+ }
+
+ void OnPingAck(const Http2FrameHeader& header,
+ const Http2PingFields& ping) override {
+ VLOG(1) << "OnPingAck: " << header << "; " << ping;
+ StartAndEndFrame(header)->OnPingAck(header, ping);
+ }
+
+ void OnFrameSizeError(const Http2FrameHeader& header) override {
+ VLOG(1) << "OnFrameSizeError: " << header;
+ FrameError(header)->OnFrameSizeError(header);
+ }
+};
+
+class PingPayloadDecoderTest
+ : public AbstractPayloadDecoderTest<PingPayloadDecoder,
+ PingPayloadDecoderPeer,
+ Listener> {
+ protected:
+ Http2PingFields RandPingFields() {
+ Http2PingFields fields;
+ test::Randomize(&fields, RandomPtr());
+ return fields;
+ }
+};
+
+// Confirm we get an error if the payload is not the correct size to hold
+// exactly one Http2PingFields.
+TEST_F(PingPayloadDecoderTest, WrongSize) {
+ auto approve_size = [](size_t size) {
+ return size != Http2PingFields::EncodedSize();
+ };
+ Http2FrameBuilder fb;
+ fb.Append(RandPingFields());
+ fb.Append(RandPingFields());
+ fb.Append(RandPingFields());
+ EXPECT_TRUE(VerifyDetectsFrameSizeError(0, fb.buffer(), approve_size));
+}
+
+TEST_F(PingPayloadDecoderTest, Ping) {
+ for (int n = 0; n < 100; ++n) {
+ Http2PingFields fields = RandPingFields();
+ Http2FrameBuilder fb;
+ fb.Append(fields);
+ Http2FrameHeader header(fb.size(), Http2FrameType::PING,
+ RandFlags() & ~Http2FrameFlag::ACK, RandStreamId());
+ set_frame_header(header);
+ FrameParts expected(header);
+ expected.SetOptPing(fields);
+ EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(fb.buffer(), expected));
+ }
+}
+
+TEST_F(PingPayloadDecoderTest, PingAck) {
+ for (int n = 0; n < 100; ++n) {
+ Http2PingFields fields;
+ Randomize(&fields, RandomPtr());
+ Http2FrameBuilder fb;
+ fb.Append(fields);
+ Http2FrameHeader header(fb.size(), Http2FrameType::PING,
+ RandFlags() | Http2FrameFlag::ACK, RandStreamId());
+ set_frame_header(header);
+ FrameParts expected(header);
+ expected.SetOptPing(fields);
+ EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(fb.buffer(), expected));
+ }
+}
+
+} // namespace
+} // namespace test
+} // namespace http2
diff --git a/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/priority_payload_decoder.cc b/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/priority_payload_decoder.cc
new file mode 100644
index 00000000000..7be1c95073b
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/priority_payload_decoder.cc
@@ -0,0 +1,64 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/http2/decoder/payload_decoders/priority_payload_decoder.h"
+
+#include "base/logging.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h"
+#include "net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener.h"
+#include "net/third_party/quiche/src/http2/http2_constants.h"
+#include "net/third_party/quiche/src/http2/http2_structures.h"
+
+namespace http2 {
+
+DecodeStatus PriorityPayloadDecoder::StartDecodingPayload(
+ FrameDecoderState* state,
+ DecodeBuffer* db) {
+ DVLOG(2) << "PriorityPayloadDecoder::StartDecodingPayload: "
+ << state->frame_header();
+ DCHECK_EQ(Http2FrameType::PRIORITY, state->frame_header().type);
+ DCHECK_LE(db->Remaining(), state->frame_header().payload_length);
+ // PRIORITY frames have no flags.
+ DCHECK_EQ(0, state->frame_header().flags);
+ state->InitializeRemainders();
+ return HandleStatus(
+ state, state->StartDecodingStructureInPayload(&priority_fields_, db));
+}
+
+DecodeStatus PriorityPayloadDecoder::ResumeDecodingPayload(
+ FrameDecoderState* state,
+ DecodeBuffer* db) {
+ DVLOG(2) << "PriorityPayloadDecoder::ResumeDecodingPayload"
+ << " remaining_payload=" << state->remaining_payload()
+ << " db->Remaining=" << db->Remaining();
+ DCHECK_EQ(Http2FrameType::PRIORITY, state->frame_header().type);
+ DCHECK_LE(db->Remaining(), state->frame_header().payload_length);
+ return HandleStatus(
+ state, state->ResumeDecodingStructureInPayload(&priority_fields_, db));
+}
+
+DecodeStatus PriorityPayloadDecoder::HandleStatus(FrameDecoderState* state,
+ DecodeStatus status) {
+ if (status == DecodeStatus::kDecodeDone) {
+ if (state->remaining_payload() == 0) {
+ state->listener()->OnPriorityFrame(state->frame_header(),
+ priority_fields_);
+ return DecodeStatus::kDecodeDone;
+ }
+ // Payload is too long.
+ return state->ReportFrameSizeError();
+ }
+ // Not done decoding the structure. Either we've got more payload to decode,
+ // or we've run out because the payload is too short, in which case
+ // OnFrameSizeError will have already been called.
+ DCHECK(
+ (status == DecodeStatus::kDecodeInProgress &&
+ state->remaining_payload() > 0) ||
+ (status == DecodeStatus::kDecodeError && state->remaining_payload() == 0))
+ << "\n status=" << status
+ << "; remaining_payload=" << state->remaining_payload();
+ return status;
+}
+
+} // namespace http2
diff --git a/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/priority_payload_decoder.h b/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/priority_payload_decoder.h
new file mode 100644
index 00000000000..921eefeb2c7
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/priority_payload_decoder.h
@@ -0,0 +1,44 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_HTTP2_DECODER_PAYLOAD_DECODERS_PRIORITY_PAYLOAD_DECODER_H_
+#define QUICHE_HTTP2_DECODER_PAYLOAD_DECODERS_PRIORITY_PAYLOAD_DECODER_H_
+
+// Decodes the payload of a PRIORITY frame.
+
+#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_status.h"
+#include "net/third_party/quiche/src/http2/decoder/frame_decoder_state.h"
+#include "net/third_party/quiche/src/http2/http2_structures.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_export.h"
+
+namespace http2 {
+namespace test {
+class PriorityPayloadDecoderPeer;
+} // namespace test
+
+class HTTP2_EXPORT_PRIVATE PriorityPayloadDecoder {
+ public:
+ // Starts the decoding of a PRIORITY frame's payload, and completes it if
+ // the entire payload is in the provided decode buffer.
+ DecodeStatus StartDecodingPayload(FrameDecoderState* state, DecodeBuffer* db);
+
+ // Resumes decoding a PRIORITY frame that has been split across decode
+ // buffers.
+ DecodeStatus ResumeDecodingPayload(FrameDecoderState* state,
+ DecodeBuffer* db);
+
+ private:
+ friend class test::PriorityPayloadDecoderPeer;
+
+ // Determines whether to report the PRIORITY to the listener, wait for more
+ // input, or to report a Frame Size Error.
+ DecodeStatus HandleStatus(FrameDecoderState* state, DecodeStatus status);
+
+ Http2PriorityFields priority_fields_;
+};
+
+} // namespace http2
+
+#endif // QUICHE_HTTP2_DECODER_PAYLOAD_DECODERS_PRIORITY_PAYLOAD_DECODER_H_
diff --git a/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/priority_payload_decoder_test.cc b/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/priority_payload_decoder_test.cc
new file mode 100644
index 00000000000..4e44ebaebae
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/priority_payload_decoder_test.cc
@@ -0,0 +1,90 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/http2/decoder/payload_decoders/priority_payload_decoder.h"
+
+#include <stddef.h>
+
+#include "base/logging.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener.h"
+#include "net/third_party/quiche/src/http2/decoder/payload_decoders/payload_decoder_base_test_util.h"
+#include "net/third_party/quiche/src/http2/http2_constants.h"
+#include "net/third_party/quiche/src/http2/http2_structures_test_util.h"
+#include "net/third_party/quiche/src/http2/test_tools/frame_parts.h"
+#include "net/third_party/quiche/src/http2/test_tools/frame_parts_collector.h"
+#include "net/third_party/quiche/src/http2/test_tools/http2_random.h"
+#include "net/third_party/quiche/src/http2/tools/http2_frame_builder.h"
+#include "net/third_party/quiche/src/http2/tools/random_decoder_test.h"
+
+namespace http2 {
+namespace test {
+
+class PriorityPayloadDecoderPeer {
+ public:
+ static constexpr Http2FrameType FrameType() {
+ return Http2FrameType::PRIORITY;
+ }
+
+ // Returns the mask of flags that affect the decoding of the payload (i.e.
+ // flags that that indicate the presence of certain fields or padding).
+ static constexpr uint8_t FlagsAffectingPayloadDecoding() { return 0; }
+};
+
+namespace {
+
+struct Listener : public FramePartsCollector {
+ void OnPriorityFrame(const Http2FrameHeader& header,
+ const Http2PriorityFields& priority_fields) override {
+ VLOG(1) << "OnPriority: " << header << "; " << priority_fields;
+ StartAndEndFrame(header)->OnPriorityFrame(header, priority_fields);
+ }
+
+ void OnFrameSizeError(const Http2FrameHeader& header) override {
+ VLOG(1) << "OnFrameSizeError: " << header;
+ FrameError(header)->OnFrameSizeError(header);
+ }
+};
+
+class PriorityPayloadDecoderTest
+ : public AbstractPayloadDecoderTest<PriorityPayloadDecoder,
+ PriorityPayloadDecoderPeer,
+ Listener> {
+ protected:
+ Http2PriorityFields RandPriorityFields() {
+ Http2PriorityFields fields;
+ test::Randomize(&fields, RandomPtr());
+ return fields;
+ }
+};
+
+// Confirm we get an error if the payload is not the correct size to hold
+// exactly one Http2PriorityFields.
+TEST_F(PriorityPayloadDecoderTest, WrongSize) {
+ auto approve_size = [](size_t size) {
+ return size != Http2PriorityFields::EncodedSize();
+ };
+ Http2FrameBuilder fb;
+ fb.Append(RandPriorityFields());
+ fb.Append(RandPriorityFields());
+ EXPECT_TRUE(VerifyDetectsFrameSizeError(0, fb.buffer(), approve_size));
+}
+
+TEST_F(PriorityPayloadDecoderTest, VariousPayloads) {
+ for (int n = 0; n < 100; ++n) {
+ Http2PriorityFields fields = RandPriorityFields();
+ Http2FrameBuilder fb;
+ fb.Append(fields);
+ Http2FrameHeader header(fb.size(), Http2FrameType::PRIORITY, RandFlags(),
+ RandStreamId());
+ set_frame_header(header);
+ FrameParts expected(header);
+ expected.SetOptPriority(fields);
+ EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(fb.buffer(), expected));
+ }
+}
+
+} // namespace
+} // namespace test
+} // namespace http2
diff --git a/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/push_promise_payload_decoder.cc b/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/push_promise_payload_decoder.cc
new file mode 100644
index 00000000000..cec1c072f90
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/push_promise_payload_decoder.cc
@@ -0,0 +1,172 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/http2/decoder/payload_decoders/push_promise_payload_decoder.h"
+
+#include <stddef.h>
+
+#include "base/logging.h"
+#include "base/macros.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h"
+#include "net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener.h"
+#include "net/third_party/quiche/src/http2/http2_constants.h"
+#include "net/third_party/quiche/src/http2/http2_structures.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_bug_tracker.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_macros.h"
+
+namespace http2 {
+
+std::ostream& operator<<(std::ostream& out,
+ PushPromisePayloadDecoder::PayloadState v) {
+ switch (v) {
+ case PushPromisePayloadDecoder::PayloadState::kReadPadLength:
+ return out << "kReadPadLength";
+ case PushPromisePayloadDecoder::PayloadState::
+ kStartDecodingPushPromiseFields:
+ return out << "kStartDecodingPushPromiseFields";
+ case PushPromisePayloadDecoder::PayloadState::kReadPayload:
+ return out << "kReadPayload";
+ case PushPromisePayloadDecoder::PayloadState::kSkipPadding:
+ return out << "kSkipPadding";
+ case PushPromisePayloadDecoder::PayloadState::
+ kResumeDecodingPushPromiseFields:
+ return out << "kResumeDecodingPushPromiseFields";
+ }
+ return out << static_cast<int>(v);
+}
+
+DecodeStatus PushPromisePayloadDecoder::StartDecodingPayload(
+ FrameDecoderState* state,
+ DecodeBuffer* db) {
+ const Http2FrameHeader& frame_header = state->frame_header();
+ const uint32_t total_length = frame_header.payload_length;
+
+ DVLOG(2) << "PushPromisePayloadDecoder::StartDecodingPayload: "
+ << frame_header;
+
+ DCHECK_EQ(Http2FrameType::PUSH_PROMISE, frame_header.type);
+ DCHECK_LE(db->Remaining(), total_length);
+ DCHECK_EQ(0, frame_header.flags &
+ ~(Http2FrameFlag::END_HEADERS | Http2FrameFlag::PADDED));
+
+ if (!frame_header.IsPadded()) {
+ // If it turns out that PUSH_PROMISE frames without padding are sufficiently
+ // common, and that they are usually short enough that they fit entirely
+ // into one DecodeBuffer, we can detect that here and implement a special
+ // case, avoiding the state machine in ResumeDecodingPayload.
+ payload_state_ = PayloadState::kStartDecodingPushPromiseFields;
+ } else {
+ payload_state_ = PayloadState::kReadPadLength;
+ }
+ state->InitializeRemainders();
+ return ResumeDecodingPayload(state, db);
+}
+
+DecodeStatus PushPromisePayloadDecoder::ResumeDecodingPayload(
+ FrameDecoderState* state,
+ DecodeBuffer* db) {
+ DVLOG(2) << "UnknownPayloadDecoder::ResumeDecodingPayload"
+ << " remaining_payload=" << state->remaining_payload()
+ << " db->Remaining=" << db->Remaining();
+
+ const Http2FrameHeader& frame_header = state->frame_header();
+ DCHECK_EQ(Http2FrameType::PUSH_PROMISE, frame_header.type);
+ DCHECK_LE(state->remaining_payload(), frame_header.payload_length);
+ DCHECK_LE(db->Remaining(), frame_header.payload_length);
+
+ DecodeStatus status;
+ while (true) {
+ DVLOG(2)
+ << "PushPromisePayloadDecoder::ResumeDecodingPayload payload_state_="
+ << payload_state_;
+ switch (payload_state_) {
+ case PayloadState::kReadPadLength:
+ DCHECK_EQ(state->remaining_payload(), frame_header.payload_length);
+ // ReadPadLength handles the OnPadLength callback, and updating the
+ // remaining_payload and remaining_padding fields. If the amount of
+ // padding is too large to fit in the frame's payload, ReadPadLength
+ // instead calls OnPaddingTooLong and returns kDecodeError.
+ // Suppress the call to OnPadLength because we haven't yet called
+ // OnPushPromiseStart, which needs to wait until we've decoded the
+ // Promised Stream ID.
+ status = state->ReadPadLength(db, /*report_pad_length*/ false);
+ if (status != DecodeStatus::kDecodeDone) {
+ payload_state_ = PayloadState::kReadPadLength;
+ return status;
+ }
+ HTTP2_FALLTHROUGH;
+
+ case PayloadState::kStartDecodingPushPromiseFields:
+ status =
+ state->StartDecodingStructureInPayload(&push_promise_fields_, db);
+ if (status != DecodeStatus::kDecodeDone) {
+ payload_state_ = PayloadState::kResumeDecodingPushPromiseFields;
+ return status;
+ }
+ // Finished decoding the Promised Stream ID. Can now tell the listener
+ // that we're starting to decode a PUSH_PROMISE frame.
+ ReportPushPromise(state);
+ HTTP2_FALLTHROUGH;
+
+ case PayloadState::kReadPayload:
+ DCHECK_LT(state->remaining_payload(), frame_header.payload_length);
+ DCHECK_LE(state->remaining_payload(),
+ frame_header.payload_length -
+ Http2PushPromiseFields::EncodedSize());
+ DCHECK_LE(
+ state->remaining_payload(),
+ frame_header.payload_length -
+ Http2PushPromiseFields::EncodedSize() -
+ (frame_header.IsPadded() ? (1 + state->remaining_padding())
+ : 0));
+ {
+ size_t avail = state->AvailablePayload(db);
+ state->listener()->OnHpackFragment(db->cursor(), avail);
+ db->AdvanceCursor(avail);
+ state->ConsumePayload(avail);
+ }
+ if (state->remaining_payload() > 0) {
+ payload_state_ = PayloadState::kReadPayload;
+ return DecodeStatus::kDecodeInProgress;
+ }
+ HTTP2_FALLTHROUGH;
+
+ case PayloadState::kSkipPadding:
+ // SkipPadding handles the OnPadding callback.
+ if (state->SkipPadding(db)) {
+ state->listener()->OnPushPromiseEnd();
+ return DecodeStatus::kDecodeDone;
+ }
+ payload_state_ = PayloadState::kSkipPadding;
+ return DecodeStatus::kDecodeInProgress;
+
+ case PayloadState::kResumeDecodingPushPromiseFields:
+ status =
+ state->ResumeDecodingStructureInPayload(&push_promise_fields_, db);
+ if (status == DecodeStatus::kDecodeDone) {
+ // Finished decoding the Promised Stream ID. Can now tell the listener
+ // that we're starting to decode a PUSH_PROMISE frame.
+ ReportPushPromise(state);
+ payload_state_ = PayloadState::kReadPayload;
+ continue;
+ }
+ payload_state_ = PayloadState::kResumeDecodingPushPromiseFields;
+ return status;
+ }
+ HTTP2_BUG << "PayloadState: " << payload_state_;
+ }
+}
+
+void PushPromisePayloadDecoder::ReportPushPromise(FrameDecoderState* state) {
+ const Http2FrameHeader& frame_header = state->frame_header();
+ if (frame_header.IsPadded()) {
+ state->listener()->OnPushPromiseStart(frame_header, push_promise_fields_,
+ 1 + state->remaining_padding());
+ } else {
+ state->listener()->OnPushPromiseStart(frame_header, push_promise_fields_,
+ 0);
+ }
+}
+
+} // namespace http2
diff --git a/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/push_promise_payload_decoder.h b/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/push_promise_payload_decoder.h
new file mode 100644
index 00000000000..2db9cb359c4
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/push_promise_payload_decoder.h
@@ -0,0 +1,66 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_HTTP2_DECODER_PAYLOAD_DECODERS_PUSH_PROMISE_PAYLOAD_DECODER_H_
+#define QUICHE_HTTP2_DECODER_PAYLOAD_DECODERS_PUSH_PROMISE_PAYLOAD_DECODER_H_
+
+// Decodes the payload of a PUSH_PROMISE frame.
+
+#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_status.h"
+#include "net/third_party/quiche/src/http2/decoder/frame_decoder_state.h"
+#include "net/third_party/quiche/src/http2/http2_structures.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_export.h"
+
+namespace http2 {
+namespace test {
+class PushPromisePayloadDecoderPeer;
+} // namespace test
+
+class HTTP2_EXPORT_PRIVATE PushPromisePayloadDecoder {
+ public:
+ // States during decoding of a PUSH_PROMISE frame.
+ enum class PayloadState {
+ // The frame is padded and we need to read the PAD_LENGTH field (1 byte).
+ kReadPadLength,
+
+ // Ready to start decoding the fixed size fields of the PUSH_PROMISE
+ // frame into push_promise_fields_.
+ kStartDecodingPushPromiseFields,
+
+ // The decoder has already called OnPushPromiseStart, and is now reporting
+ // the HPACK block fragment to the listener's OnHpackFragment method.
+ kReadPayload,
+
+ // The decoder has finished with the HPACK block fragment, and is now
+ // ready to skip the trailing padding, if the frame has any.
+ kSkipPadding,
+
+ // The fixed size fields weren't all available when the decoder first tried
+ // to decode them (state kStartDecodingPushPromiseFields); this state
+ // resumes the decoding when ResumeDecodingPayload is called later.
+ kResumeDecodingPushPromiseFields,
+ };
+
+ // Starts the decoding of a PUSH_PROMISE frame's payload, and completes it if
+ // the entire payload is in the provided decode buffer.
+ DecodeStatus StartDecodingPayload(FrameDecoderState* state, DecodeBuffer* db);
+
+ // Resumes decoding a PUSH_PROMISE frame's payload that has been split across
+ // decode buffers.
+ DecodeStatus ResumeDecodingPayload(FrameDecoderState* state,
+ DecodeBuffer* db);
+
+ private:
+ friend class test::PushPromisePayloadDecoderPeer;
+
+ void ReportPushPromise(FrameDecoderState* state);
+
+ PayloadState payload_state_;
+ Http2PushPromiseFields push_promise_fields_;
+};
+
+} // namespace http2
+
+#endif // QUICHE_HTTP2_DECODER_PAYLOAD_DECODERS_PUSH_PROMISE_PAYLOAD_DECODER_H_
diff --git a/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/push_promise_payload_decoder_test.cc b/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/push_promise_payload_decoder_test.cc
new file mode 100644
index 00000000000..9d55a803043
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/push_promise_payload_decoder_test.cc
@@ -0,0 +1,137 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/http2/decoder/payload_decoders/push_promise_payload_decoder.h"
+
+#include <stddef.h>
+
+#include "base/logging.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener.h"
+#include "net/third_party/quiche/src/http2/decoder/payload_decoders/payload_decoder_base_test_util.h"
+#include "net/third_party/quiche/src/http2/http2_constants.h"
+#include "net/third_party/quiche/src/http2/http2_structures_test_util.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string.h"
+#include "net/third_party/quiche/src/http2/test_tools/frame_parts.h"
+#include "net/third_party/quiche/src/http2/test_tools/frame_parts_collector.h"
+#include "net/third_party/quiche/src/http2/test_tools/http2_random.h"
+#include "net/third_party/quiche/src/http2/tools/http2_frame_builder.h"
+#include "net/third_party/quiche/src/http2/tools/random_decoder_test.h"
+
+namespace http2 {
+namespace test {
+
+// Provides friend access to an instance of the payload decoder, and also
+// provides info to aid in testing.
+class PushPromisePayloadDecoderPeer {
+ public:
+ static constexpr Http2FrameType FrameType() {
+ return Http2FrameType::PUSH_PROMISE;
+ }
+
+ // Returns the mask of flags that affect the decoding of the payload (i.e.
+ // flags that that indicate the presence of certain fields or padding).
+ static constexpr uint8_t FlagsAffectingPayloadDecoding() {
+ return Http2FrameFlag::PADDED;
+ }
+};
+
+namespace {
+
+// Listener listens for only those methods expected by the payload decoder
+// under test, and forwards them onto the FrameParts instance for the current
+// frame.
+struct Listener : public FramePartsCollector {
+ void OnPushPromiseStart(const Http2FrameHeader& header,
+ const Http2PushPromiseFields& promise,
+ size_t total_padding_length) override {
+ VLOG(1) << "OnPushPromiseStart header: " << header
+ << " promise: " << promise
+ << " total_padding_length: " << total_padding_length;
+ EXPECT_EQ(Http2FrameType::PUSH_PROMISE, header.type);
+ StartFrame(header)->OnPushPromiseStart(header, promise,
+ total_padding_length);
+ }
+
+ void OnHpackFragment(const char* data, size_t len) override {
+ VLOG(1) << "OnHpackFragment: len=" << len;
+ CurrentFrame()->OnHpackFragment(data, len);
+ }
+
+ void OnPushPromiseEnd() override {
+ VLOG(1) << "OnPushPromiseEnd";
+ EndFrame()->OnPushPromiseEnd();
+ }
+
+ void OnPadding(const char* padding, size_t skipped_length) override {
+ VLOG(1) << "OnPadding: " << skipped_length;
+ CurrentFrame()->OnPadding(padding, skipped_length);
+ }
+
+ void OnPaddingTooLong(const Http2FrameHeader& header,
+ size_t missing_length) override {
+ VLOG(1) << "OnPaddingTooLong: " << header
+ << "; missing_length: " << missing_length;
+ FrameError(header)->OnPaddingTooLong(header, missing_length);
+ }
+
+ void OnFrameSizeError(const Http2FrameHeader& header) override {
+ VLOG(1) << "OnFrameSizeError: " << header;
+ FrameError(header)->OnFrameSizeError(header);
+ }
+};
+
+class PushPromisePayloadDecoderTest
+ : public AbstractPaddablePayloadDecoderTest<PushPromisePayloadDecoder,
+ PushPromisePayloadDecoderPeer,
+ Listener> {};
+
+INSTANTIATE_TEST_CASE_P(VariousPadLengths,
+ PushPromisePayloadDecoderTest,
+ ::testing::Values(0, 1, 2, 3, 4, 254, 255, 256));
+
+// Payload contains the required Http2PushPromiseFields, followed by some
+// (fake) HPACK payload.
+TEST_P(PushPromisePayloadDecoderTest, VariousHpackPayloadSizes) {
+ for (size_t hpack_size : {0, 1, 2, 3, 255, 256, 1024}) {
+ LOG(INFO) << "########### hpack_size = " << hpack_size << " ###########";
+ Reset();
+ Http2String hpack_payload = Random().RandString(hpack_size);
+ Http2PushPromiseFields push_promise{RandStreamId()};
+ frame_builder_.Append(push_promise);
+ frame_builder_.Append(hpack_payload);
+ MaybeAppendTrailingPadding();
+ Http2FrameHeader frame_header(frame_builder_.size(),
+ Http2FrameType::PUSH_PROMISE, RandFlags(),
+ RandStreamId());
+ set_frame_header(frame_header);
+ FrameParts expected(frame_header, hpack_payload, total_pad_length_);
+ expected.SetOptPushPromise(push_promise);
+ EXPECT_TRUE(
+ DecodePayloadAndValidateSeveralWays(frame_builder_.buffer(), expected));
+ }
+}
+
+// Confirm we get an error if the payload is not long enough for the required
+// portion of the payload, regardless of the amount of (valid) padding.
+TEST_P(PushPromisePayloadDecoderTest, Truncated) {
+ auto approve_size = [](size_t size) {
+ return size != Http2PushPromiseFields::EncodedSize();
+ };
+ Http2PushPromiseFields push_promise{RandStreamId()};
+ Http2FrameBuilder fb;
+ fb.Append(push_promise);
+ EXPECT_TRUE(VerifyDetectsMultipleFrameSizeErrors(0, fb.buffer(), approve_size,
+ total_pad_length_));
+}
+
+// Confirm we get an error if the PADDED flag is set but the payload is not
+// long enough to hold even the Pad Length amount of padding.
+TEST_P(PushPromisePayloadDecoderTest, PaddingTooLong) {
+ EXPECT_TRUE(VerifyDetectsPaddingTooLong());
+}
+
+} // namespace
+} // namespace test
+} // namespace http2
diff --git a/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/rst_stream_payload_decoder.cc b/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/rst_stream_payload_decoder.cc
new file mode 100644
index 00000000000..c39a16a8aa4
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/rst_stream_payload_decoder.cc
@@ -0,0 +1,66 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/http2/decoder/payload_decoders/rst_stream_payload_decoder.h"
+
+#include "base/logging.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h"
+#include "net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener.h"
+#include "net/third_party/quiche/src/http2/http2_constants.h"
+#include "net/third_party/quiche/src/http2/http2_structures.h"
+
+namespace http2 {
+
+DecodeStatus RstStreamPayloadDecoder::StartDecodingPayload(
+ FrameDecoderState* state,
+ DecodeBuffer* db) {
+ DVLOG(2) << "RstStreamPayloadDecoder::StartDecodingPayload: "
+ << state->frame_header();
+ DCHECK_EQ(Http2FrameType::RST_STREAM, state->frame_header().type);
+ DCHECK_LE(db->Remaining(), state->frame_header().payload_length);
+ // RST_STREAM has no flags.
+ DCHECK_EQ(0, state->frame_header().flags);
+ state->InitializeRemainders();
+ return HandleStatus(
+ state, state->StartDecodingStructureInPayload(&rst_stream_fields_, db));
+}
+
+DecodeStatus RstStreamPayloadDecoder::ResumeDecodingPayload(
+ FrameDecoderState* state,
+ DecodeBuffer* db) {
+ DVLOG(2) << "RstStreamPayloadDecoder::ResumeDecodingPayload"
+ << " remaining_payload=" << state->remaining_payload()
+ << " db->Remaining=" << db->Remaining();
+ DCHECK_EQ(Http2FrameType::RST_STREAM, state->frame_header().type);
+ DCHECK_LE(db->Remaining(), state->frame_header().payload_length);
+ return HandleStatus(
+ state, state->ResumeDecodingStructureInPayload(&rst_stream_fields_, db));
+}
+
+DecodeStatus RstStreamPayloadDecoder::HandleStatus(FrameDecoderState* state,
+ DecodeStatus status) {
+ DVLOG(2) << "HandleStatus: status=" << status
+ << "; remaining_payload=" << state->remaining_payload();
+ if (status == DecodeStatus::kDecodeDone) {
+ if (state->remaining_payload() == 0) {
+ state->listener()->OnRstStream(state->frame_header(),
+ rst_stream_fields_.error_code);
+ return DecodeStatus::kDecodeDone;
+ }
+ // Payload is too long.
+ return state->ReportFrameSizeError();
+ }
+ // Not done decoding the structure. Either we've got more payload to decode,
+ // or we've run out because the payload is too short, in which case
+ // OnFrameSizeError will have already been called by the FrameDecoderState.
+ DCHECK(
+ (status == DecodeStatus::kDecodeInProgress &&
+ state->remaining_payload() > 0) ||
+ (status == DecodeStatus::kDecodeError && state->remaining_payload() == 0))
+ << "\n status=" << status
+ << "; remaining_payload=" << state->remaining_payload();
+ return status;
+}
+
+} // namespace http2
diff --git a/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/rst_stream_payload_decoder.h b/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/rst_stream_payload_decoder.h
new file mode 100644
index 00000000000..947fc06d09e
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/rst_stream_payload_decoder.h
@@ -0,0 +1,42 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_HTTP2_DECODER_PAYLOAD_DECODERS_RST_STREAM_PAYLOAD_DECODER_H_
+#define QUICHE_HTTP2_DECODER_PAYLOAD_DECODERS_RST_STREAM_PAYLOAD_DECODER_H_
+
+// Decodes the payload of a RST_STREAM frame.
+
+#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_status.h"
+#include "net/third_party/quiche/src/http2/decoder/frame_decoder_state.h"
+#include "net/third_party/quiche/src/http2/http2_structures.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_export.h"
+
+namespace http2 {
+namespace test {
+class RstStreamPayloadDecoderPeer;
+} // namespace test
+
+class HTTP2_EXPORT_PRIVATE RstStreamPayloadDecoder {
+ public:
+ // Starts the decoding of a RST_STREAM frame's payload, and completes it if
+ // the entire payload is in the provided decode buffer.
+ DecodeStatus StartDecodingPayload(FrameDecoderState* state, DecodeBuffer* db);
+
+ // Resumes decoding a RST_STREAM frame's payload that has been split across
+ // decode buffers.
+ DecodeStatus ResumeDecodingPayload(FrameDecoderState* state,
+ DecodeBuffer* db);
+
+ private:
+ friend class test::RstStreamPayloadDecoderPeer;
+
+ DecodeStatus HandleStatus(FrameDecoderState* state, DecodeStatus status);
+
+ Http2RstStreamFields rst_stream_fields_;
+};
+
+} // namespace http2
+
+#endif // QUICHE_HTTP2_DECODER_PAYLOAD_DECODERS_RST_STREAM_PAYLOAD_DECODER_H_
diff --git a/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/rst_stream_payload_decoder_test.cc b/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/rst_stream_payload_decoder_test.cc
new file mode 100644
index 00000000000..08f66210518
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/rst_stream_payload_decoder_test.cc
@@ -0,0 +1,92 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/http2/decoder/payload_decoders/rst_stream_payload_decoder.h"
+
+#include <stddef.h>
+
+#include "base/logging.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener.h"
+#include "net/third_party/quiche/src/http2/decoder/payload_decoders/payload_decoder_base_test_util.h"
+#include "net/third_party/quiche/src/http2/http2_constants.h"
+#include "net/third_party/quiche/src/http2/http2_constants_test_util.h"
+#include "net/third_party/quiche/src/http2/http2_structures_test_util.h"
+#include "net/third_party/quiche/src/http2/test_tools/frame_parts.h"
+#include "net/third_party/quiche/src/http2/test_tools/frame_parts_collector.h"
+#include "net/third_party/quiche/src/http2/test_tools/http2_random.h"
+#include "net/third_party/quiche/src/http2/tools/http2_frame_builder.h"
+#include "net/third_party/quiche/src/http2/tools/random_decoder_test.h"
+
+namespace http2 {
+namespace test {
+
+class RstStreamPayloadDecoderPeer {
+ public:
+ static constexpr Http2FrameType FrameType() {
+ return Http2FrameType::RST_STREAM;
+ }
+
+ // Returns the mask of flags that affect the decoding of the payload (i.e.
+ // flags that that indicate the presence of certain fields or padding).
+ static constexpr uint8_t FlagsAffectingPayloadDecoding() { return 0; }
+};
+
+namespace {
+
+struct Listener : public FramePartsCollector {
+ void OnRstStream(const Http2FrameHeader& header,
+ Http2ErrorCode error_code) override {
+ VLOG(1) << "OnRstStream: " << header << "; error_code=" << error_code;
+ StartAndEndFrame(header)->OnRstStream(header, error_code);
+ }
+
+ void OnFrameSizeError(const Http2FrameHeader& header) override {
+ VLOG(1) << "OnFrameSizeError: " << header;
+ FrameError(header)->OnFrameSizeError(header);
+ }
+};
+
+class RstStreamPayloadDecoderTest
+ : public AbstractPayloadDecoderTest<RstStreamPayloadDecoder,
+ RstStreamPayloadDecoderPeer,
+ Listener> {
+ protected:
+ Http2RstStreamFields RandRstStreamFields() {
+ Http2RstStreamFields fields;
+ test::Randomize(&fields, RandomPtr());
+ return fields;
+ }
+};
+
+// Confirm we get an error if the payload is not the correct size to hold
+// exactly one Http2RstStreamFields.
+TEST_F(RstStreamPayloadDecoderTest, WrongSize) {
+ auto approve_size = [](size_t size) {
+ return size != Http2RstStreamFields::EncodedSize();
+ };
+ Http2FrameBuilder fb;
+ fb.Append(RandRstStreamFields());
+ fb.Append(RandRstStreamFields());
+ fb.Append(RandRstStreamFields());
+ EXPECT_TRUE(VerifyDetectsFrameSizeError(0, fb.buffer(), approve_size));
+}
+
+TEST_F(RstStreamPayloadDecoderTest, AllErrors) {
+ for (auto error_code : AllHttp2ErrorCodes()) {
+ Http2RstStreamFields fields{error_code};
+ Http2FrameBuilder fb;
+ fb.Append(fields);
+ Http2FrameHeader header(fb.size(), Http2FrameType::RST_STREAM, RandFlags(),
+ RandStreamId());
+ set_frame_header(header);
+ FrameParts expected(header);
+ expected.SetOptRstStreamErrorCode(error_code);
+ EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(fb.buffer(), expected));
+ }
+}
+
+} // namespace
+} // namespace test
+} // namespace http2
diff --git a/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/settings_payload_decoder.cc b/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/settings_payload_decoder.cc
new file mode 100644
index 00000000000..bf29c4d84fb
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/settings_payload_decoder.cc
@@ -0,0 +1,97 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/http2/decoder/payload_decoders/settings_payload_decoder.h"
+
+#include "base/logging.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h"
+#include "net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener.h"
+#include "net/third_party/quiche/src/http2/http2_constants.h"
+#include "net/third_party/quiche/src/http2/http2_structures.h"
+
+namespace http2 {
+
+DecodeStatus SettingsPayloadDecoder::StartDecodingPayload(
+ FrameDecoderState* state,
+ DecodeBuffer* db) {
+ const Http2FrameHeader& frame_header = state->frame_header();
+ const uint32_t total_length = frame_header.payload_length;
+
+ DVLOG(2) << "SettingsPayloadDecoder::StartDecodingPayload: " << frame_header;
+ DCHECK_EQ(Http2FrameType::SETTINGS, frame_header.type);
+ DCHECK_LE(db->Remaining(), total_length);
+ DCHECK_EQ(0, frame_header.flags & ~(Http2FrameFlag::ACK));
+
+ if (frame_header.IsAck()) {
+ if (total_length == 0) {
+ state->listener()->OnSettingsAck(frame_header);
+ return DecodeStatus::kDecodeDone;
+ } else {
+ state->InitializeRemainders();
+ return state->ReportFrameSizeError();
+ }
+ } else {
+ state->InitializeRemainders();
+ state->listener()->OnSettingsStart(frame_header);
+ return StartDecodingSettings(state, db);
+ }
+}
+
+DecodeStatus SettingsPayloadDecoder::ResumeDecodingPayload(
+ FrameDecoderState* state,
+ DecodeBuffer* db) {
+ DVLOG(2) << "SettingsPayloadDecoder::ResumeDecodingPayload"
+ << " remaining_payload=" << state->remaining_payload()
+ << " db->Remaining=" << db->Remaining();
+ DCHECK_EQ(Http2FrameType::SETTINGS, state->frame_header().type);
+ DCHECK_LE(db->Remaining(), state->frame_header().payload_length);
+
+ DecodeStatus status =
+ state->ResumeDecodingStructureInPayload(&setting_fields_, db);
+ if (status == DecodeStatus::kDecodeDone) {
+ state->listener()->OnSetting(setting_fields_);
+ return StartDecodingSettings(state, db);
+ }
+ return HandleNotDone(state, db, status);
+}
+
+DecodeStatus SettingsPayloadDecoder::StartDecodingSettings(
+ FrameDecoderState* state,
+ DecodeBuffer* db) {
+ DVLOG(2) << "SettingsPayloadDecoder::StartDecodingSettings"
+ << " remaining_payload=" << state->remaining_payload()
+ << " db->Remaining=" << db->Remaining();
+ while (state->remaining_payload() > 0) {
+ DecodeStatus status =
+ state->StartDecodingStructureInPayload(&setting_fields_, db);
+ if (status == DecodeStatus::kDecodeDone) {
+ state->listener()->OnSetting(setting_fields_);
+ continue;
+ }
+ return HandleNotDone(state, db, status);
+ }
+ DVLOG(2) << "LEAVING SettingsPayloadDecoder::StartDecodingSettings"
+ << "\n\tdb->Remaining=" << db->Remaining()
+ << "\n\t remaining_payload=" << state->remaining_payload();
+ state->listener()->OnSettingsEnd();
+ return DecodeStatus::kDecodeDone;
+}
+
+DecodeStatus SettingsPayloadDecoder::HandleNotDone(FrameDecoderState* state,
+ DecodeBuffer* db,
+ DecodeStatus status) {
+ // Not done decoding the structure. Either we've got more payload to decode,
+ // or we've run out because the payload is too short, in which case
+ // OnFrameSizeError will have already been called.
+ DCHECK(
+ (status == DecodeStatus::kDecodeInProgress &&
+ state->remaining_payload() > 0) ||
+ (status == DecodeStatus::kDecodeError && state->remaining_payload() == 0))
+ << "\n status=" << status
+ << "; remaining_payload=" << state->remaining_payload()
+ << "; db->Remaining=" << db->Remaining();
+ return status;
+}
+
+} // namespace http2
diff --git a/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/settings_payload_decoder.h b/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/settings_payload_decoder.h
new file mode 100644
index 00000000000..7e3c313bb16
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/settings_payload_decoder.h
@@ -0,0 +1,54 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_HTTP2_DECODER_PAYLOAD_DECODERS_SETTINGS_PAYLOAD_DECODER_H_
+#define QUICHE_HTTP2_DECODER_PAYLOAD_DECODERS_SETTINGS_PAYLOAD_DECODER_H_
+
+// Decodes the payload of a SETTINGS frame; for the RFC, see:
+// http://httpwg.org/specs/rfc7540.html#SETTINGS
+
+#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_status.h"
+#include "net/third_party/quiche/src/http2/decoder/frame_decoder_state.h"
+#include "net/third_party/quiche/src/http2/http2_structures.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_export.h"
+
+namespace http2 {
+namespace test {
+class SettingsPayloadDecoderPeer;
+} // namespace test
+
+class HTTP2_EXPORT_PRIVATE SettingsPayloadDecoder {
+ public:
+ // Starts the decoding of a SETTINGS frame's payload, and completes it if
+ // the entire payload is in the provided decode buffer.
+ DecodeStatus StartDecodingPayload(FrameDecoderState* state, DecodeBuffer* db);
+
+ // Resumes decoding a SETTINGS frame that has been split across decode
+ // buffers.
+ DecodeStatus ResumeDecodingPayload(FrameDecoderState* state,
+ DecodeBuffer* db);
+
+ private:
+ friend class test::SettingsPayloadDecoderPeer;
+
+ // Decodes as many settings as are available in the decode buffer, starting at
+ // the first byte of one setting; if a single setting is split across buffers,
+ // ResumeDecodingPayload will handle starting from where the previous call
+ // left off, and then will call StartDecodingSettings.
+ DecodeStatus StartDecodingSettings(FrameDecoderState* state,
+ DecodeBuffer* db);
+
+ // Decoding a single SETTING returned a status other than kDecodeDone; this
+ // method just brings together the DCHECKs to reduce duplication.
+ DecodeStatus HandleNotDone(FrameDecoderState* state,
+ DecodeBuffer* db,
+ DecodeStatus status);
+
+ Http2SettingFields setting_fields_;
+};
+
+} // namespace http2
+
+#endif // QUICHE_HTTP2_DECODER_PAYLOAD_DECODERS_SETTINGS_PAYLOAD_DECODER_H_
diff --git a/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/settings_payload_decoder_test.cc b/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/settings_payload_decoder_test.cc
new file mode 100644
index 00000000000..3429779faf7
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/settings_payload_decoder_test.cc
@@ -0,0 +1,160 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/http2/decoder/payload_decoders/settings_payload_decoder.h"
+
+#include <stddef.h>
+
+#include <vector>
+
+#include "base/logging.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener.h"
+#include "net/third_party/quiche/src/http2/decoder/payload_decoders/payload_decoder_base_test_util.h"
+#include "net/third_party/quiche/src/http2/http2_constants.h"
+#include "net/third_party/quiche/src/http2/http2_constants_test_util.h"
+#include "net/third_party/quiche/src/http2/http2_structures_test_util.h"
+#include "net/third_party/quiche/src/http2/test_tools/frame_parts.h"
+#include "net/third_party/quiche/src/http2/test_tools/frame_parts_collector.h"
+#include "net/third_party/quiche/src/http2/test_tools/http2_random.h"
+#include "net/third_party/quiche/src/http2/tools/http2_frame_builder.h"
+#include "net/third_party/quiche/src/http2/tools/random_decoder_test.h"
+
+namespace http2 {
+namespace test {
+
+class SettingsPayloadDecoderPeer {
+ public:
+ static constexpr Http2FrameType FrameType() {
+ return Http2FrameType::SETTINGS;
+ }
+
+ // Returns the mask of flags that affect the decoding of the payload (i.e.
+ // flags that that indicate the presence of certain fields or padding).
+ static constexpr uint8_t FlagsAffectingPayloadDecoding() {
+ return Http2FrameFlag::ACK;
+ }
+};
+
+namespace {
+
+struct Listener : public FramePartsCollector {
+ void OnSettingsStart(const Http2FrameHeader& header) override {
+ VLOG(1) << "OnSettingsStart: " << header;
+ EXPECT_EQ(Http2FrameType::SETTINGS, header.type) << header;
+ EXPECT_EQ(Http2FrameFlag(), header.flags) << header;
+ StartFrame(header)->OnSettingsStart(header);
+ }
+
+ void OnSetting(const Http2SettingFields& setting_fields) override {
+ VLOG(1) << "Http2SettingFields: setting_fields=" << setting_fields;
+ CurrentFrame()->OnSetting(setting_fields);
+ }
+
+ void OnSettingsEnd() override {
+ VLOG(1) << "OnSettingsEnd";
+ EndFrame()->OnSettingsEnd();
+ }
+
+ void OnSettingsAck(const Http2FrameHeader& header) override {
+ VLOG(1) << "OnSettingsAck: " << header;
+ StartAndEndFrame(header)->OnSettingsAck(header);
+ }
+
+ void OnFrameSizeError(const Http2FrameHeader& header) override {
+ VLOG(1) << "OnFrameSizeError: " << header;
+ FrameError(header)->OnFrameSizeError(header);
+ }
+};
+
+class SettingsPayloadDecoderTest
+ : public AbstractPayloadDecoderTest<SettingsPayloadDecoder,
+ SettingsPayloadDecoderPeer,
+ Listener> {
+ protected:
+ Http2SettingFields RandSettingsFields() {
+ Http2SettingFields fields;
+ test::Randomize(&fields, RandomPtr());
+ return fields;
+ }
+};
+
+// Confirm we get an error if the SETTINGS payload is not the correct size
+// to hold exactly zero or more whole Http2SettingFields.
+TEST_F(SettingsPayloadDecoderTest, SettingsWrongSize) {
+ auto approve_size = [](size_t size) {
+ // Should get an error if size is not an integral multiple of the size
+ // of one setting.
+ return 0 != (size % Http2SettingFields::EncodedSize());
+ };
+ Http2FrameBuilder fb;
+ fb.Append(RandSettingsFields());
+ fb.Append(RandSettingsFields());
+ fb.Append(RandSettingsFields());
+ EXPECT_TRUE(VerifyDetectsFrameSizeError(0, fb.buffer(), approve_size));
+}
+
+// Confirm we get an error if the SETTINGS ACK payload is not empty.
+TEST_F(SettingsPayloadDecoderTest, SettingsAkcWrongSize) {
+ auto approve_size = [](size_t size) { return size != 0; };
+ Http2FrameBuilder fb;
+ fb.Append(RandSettingsFields());
+ fb.Append(RandSettingsFields());
+ fb.Append(RandSettingsFields());
+ EXPECT_TRUE(VerifyDetectsFrameSizeError(Http2FrameFlag::ACK, fb.buffer(),
+ approve_size));
+}
+
+// SETTINGS must have stream_id==0, but the payload decoder doesn't check that.
+TEST_F(SettingsPayloadDecoderTest, SettingsAck) {
+ for (int stream_id = 0; stream_id < 3; ++stream_id) {
+ Http2FrameHeader header(0, Http2FrameType::SETTINGS,
+ RandFlags() | Http2FrameFlag::ACK, stream_id);
+ set_frame_header(header);
+ FrameParts expected(header);
+ EXPECT_TRUE(DecodePayloadAndValidateSeveralWays("", expected));
+ }
+}
+
+// Try several values of each known SETTINGS parameter.
+TEST_F(SettingsPayloadDecoderTest, OneRealSetting) {
+ std::vector<uint32_t> values = {0, 1, 0xffffffff, Random().Rand32()};
+ for (auto param : AllHttp2SettingsParameters()) {
+ for (uint32_t value : values) {
+ Http2SettingFields fields(param, value);
+ Http2FrameBuilder fb;
+ fb.Append(fields);
+ Http2FrameHeader header(fb.size(), Http2FrameType::SETTINGS, RandFlags(),
+ RandStreamId());
+ set_frame_header(header);
+ FrameParts expected(header);
+ expected.AppendSetting(fields);
+ EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(fb.buffer(), expected));
+ }
+ }
+}
+
+// Decode a SETTINGS frame with lots of fields.
+TEST_F(SettingsPayloadDecoderTest, ManySettings) {
+ const size_t num_settings = 100;
+ const size_t size = Http2SettingFields::EncodedSize() * num_settings;
+ Http2FrameHeader header(size, Http2FrameType::SETTINGS,
+ RandFlags(), // & ~Http2FrameFlag::ACK,
+ RandStreamId());
+ set_frame_header(header);
+ FrameParts expected(header);
+ Http2FrameBuilder fb;
+ for (size_t n = 0; n < num_settings; ++n) {
+ Http2SettingFields fields(static_cast<Http2SettingsParameter>(n),
+ Random().Rand32());
+ fb.Append(fields);
+ expected.AppendSetting(fields);
+ }
+ ASSERT_EQ(size, fb.size());
+ EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(fb.buffer(), expected));
+}
+
+} // namespace
+} // namespace test
+} // namespace http2
diff --git a/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/unknown_payload_decoder.cc b/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/unknown_payload_decoder.cc
new file mode 100644
index 00000000000..10eaf2438a5
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/unknown_payload_decoder.cc
@@ -0,0 +1,55 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/http2/decoder/payload_decoders/unknown_payload_decoder.h"
+
+#include <stddef.h>
+
+#include "base/logging.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h"
+#include "net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener.h"
+#include "net/third_party/quiche/src/http2/http2_constants.h"
+#include "net/third_party/quiche/src/http2/http2_structures.h"
+
+namespace http2 {
+
+DecodeStatus UnknownPayloadDecoder::StartDecodingPayload(
+ FrameDecoderState* state,
+ DecodeBuffer* db) {
+ const Http2FrameHeader& frame_header = state->frame_header();
+
+ DVLOG(2) << "UnknownPayloadDecoder::StartDecodingPayload: " << frame_header;
+ DCHECK(!IsSupportedHttp2FrameType(frame_header.type)) << frame_header;
+ DCHECK_LE(db->Remaining(), frame_header.payload_length);
+
+ state->InitializeRemainders();
+ state->listener()->OnUnknownStart(frame_header);
+ return ResumeDecodingPayload(state, db);
+}
+
+DecodeStatus UnknownPayloadDecoder::ResumeDecodingPayload(
+ FrameDecoderState* state,
+ DecodeBuffer* db) {
+ DVLOG(2) << "UnknownPayloadDecoder::ResumeDecodingPayload "
+ << "remaining_payload=" << state->remaining_payload()
+ << "; db->Remaining=" << db->Remaining();
+ DCHECK(!IsSupportedHttp2FrameType(state->frame_header().type))
+ << state->frame_header();
+ DCHECK_LE(state->remaining_payload(), state->frame_header().payload_length);
+ DCHECK_LE(db->Remaining(), state->remaining_payload());
+
+ size_t avail = db->Remaining();
+ if (avail > 0) {
+ state->listener()->OnUnknownPayload(db->cursor(), avail);
+ db->AdvanceCursor(avail);
+ state->ConsumePayload(avail);
+ }
+ if (state->remaining_payload() == 0) {
+ state->listener()->OnUnknownEnd();
+ return DecodeStatus::kDecodeDone;
+ }
+ return DecodeStatus::kDecodeInProgress;
+}
+
+} // namespace http2
diff --git a/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/unknown_payload_decoder.h b/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/unknown_payload_decoder.h
new file mode 100644
index 00000000000..7bf4103dcd0
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/unknown_payload_decoder.h
@@ -0,0 +1,33 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_HTTP2_DECODER_PAYLOAD_DECODERS_UNKNOWN_PAYLOAD_DECODER_H_
+#define QUICHE_HTTP2_DECODER_PAYLOAD_DECODERS_UNKNOWN_PAYLOAD_DECODER_H_
+
+// Decodes the payload of a frame whose type unknown. According to the HTTP/2
+// specification (http://httpwg.org/specs/rfc7540.html#FrameHeader):
+// Implementations MUST ignore and discard any frame that has
+// a type that is unknown.
+
+#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_status.h"
+#include "net/third_party/quiche/src/http2/decoder/frame_decoder_state.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_export.h"
+
+namespace http2 {
+
+class HTTP2_EXPORT_PRIVATE UnknownPayloadDecoder {
+ public:
+ // Starts decoding a payload of unknown type; just passes it to the listener.
+ DecodeStatus StartDecodingPayload(FrameDecoderState* state, DecodeBuffer* db);
+
+ // Resumes decoding a payload of unknown type that has been split across
+ // decode buffers.
+ DecodeStatus ResumeDecodingPayload(FrameDecoderState* state,
+ DecodeBuffer* db);
+};
+
+} // namespace http2
+
+#endif // QUICHE_HTTP2_DECODER_PAYLOAD_DECODERS_UNKNOWN_PAYLOAD_DECODER_H_
diff --git a/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/unknown_payload_decoder_test.cc b/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/unknown_payload_decoder_test.cc
new file mode 100644
index 00000000000..7ba95f746b8
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/unknown_payload_decoder_test.cc
@@ -0,0 +1,100 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/http2/decoder/payload_decoders/unknown_payload_decoder.h"
+
+#include <stddef.h>
+
+#include <type_traits>
+
+#include "base/logging.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener.h"
+#include "net/third_party/quiche/src/http2/decoder/payload_decoders/payload_decoder_base_test_util.h"
+#include "net/third_party/quiche/src/http2/http2_constants.h"
+#include "net/third_party/quiche/src/http2/http2_structures.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string.h"
+#include "net/third_party/quiche/src/http2/test_tools/frame_parts.h"
+#include "net/third_party/quiche/src/http2/test_tools/frame_parts_collector.h"
+#include "net/third_party/quiche/src/http2/test_tools/http2_random.h"
+#include "net/third_party/quiche/src/http2/tools/random_decoder_test.h"
+
+namespace http2 {
+namespace test {
+namespace {
+Http2FrameType g_unknown_frame_type;
+} // namespace
+
+// Provides friend access to an instance of the payload decoder, and also
+// provides info to aid in testing.
+class UnknownPayloadDecoderPeer {
+ public:
+ static Http2FrameType FrameType() { return g_unknown_frame_type; }
+
+ // Returns the mask of flags that affect the decoding of the payload (i.e.
+ // flags that that indicate the presence of certain fields or padding).
+ static constexpr uint8_t FlagsAffectingPayloadDecoding() { return 0; }
+};
+
+namespace {
+
+struct Listener : public FramePartsCollector {
+ void OnUnknownStart(const Http2FrameHeader& header) override {
+ VLOG(1) << "OnUnknownStart: " << header;
+ StartFrame(header)->OnUnknownStart(header);
+ }
+
+ void OnUnknownPayload(const char* data, size_t len) override {
+ VLOG(1) << "OnUnknownPayload: len=" << len;
+ CurrentFrame()->OnUnknownPayload(data, len);
+ }
+
+ void OnUnknownEnd() override {
+ VLOG(1) << "OnUnknownEnd";
+ EndFrame()->OnUnknownEnd();
+ }
+};
+
+constexpr bool SupportedFrameType = false;
+
+class UnknownPayloadDecoderTest
+ : public AbstractPayloadDecoderTest<UnknownPayloadDecoder,
+ UnknownPayloadDecoderPeer,
+ Listener,
+ SupportedFrameType>,
+ public ::testing::WithParamInterface<uint32_t> {
+ protected:
+ UnknownPayloadDecoderTest() : length_(GetParam()) {
+ VLOG(1) << "################ length_=" << length_ << " ################";
+
+ // Each test case will choose a random frame type that isn't supported.
+ do {
+ g_unknown_frame_type = static_cast<Http2FrameType>(Random().Rand8());
+ } while (IsSupportedHttp2FrameType(g_unknown_frame_type));
+ }
+
+ const uint32_t length_;
+};
+
+INSTANTIATE_TEST_CASE_P(VariousLengths,
+ UnknownPayloadDecoderTest,
+ ::testing::Values(0, 1, 2, 3, 255, 256));
+
+TEST_P(UnknownPayloadDecoderTest, ValidLength) {
+ Http2String unknown_payload = Random().RandString(length_);
+ Http2FrameHeader frame_header(length_, g_unknown_frame_type, Random().Rand8(),
+ RandStreamId());
+ set_frame_header(frame_header);
+ FrameParts expected(frame_header, unknown_payload);
+ EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(unknown_payload, expected));
+ // TODO(jamessynge): Check here (and in other such tests) that the fast
+ // and slow decode counts are both non-zero. Perhaps also add some kind of
+ // test for the listener having been called. That could simply be a test
+ // that there is a single collected FrameParts instance, and that it matches
+ // expected.
+}
+
+} // namespace
+} // namespace test
+} // namespace http2
diff --git a/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/window_update_payload_decoder.cc b/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/window_update_payload_decoder.cc
new file mode 100644
index 00000000000..c0bb028b005
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/window_update_payload_decoder.cc
@@ -0,0 +1,82 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/http2/decoder/payload_decoders/window_update_payload_decoder.h"
+
+#include "base/logging.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_http2_structures.h"
+#include "net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener.h"
+#include "net/third_party/quiche/src/http2/http2_constants.h"
+#include "net/third_party/quiche/src/http2/http2_structures.h"
+
+namespace http2 {
+
+DecodeStatus WindowUpdatePayloadDecoder::StartDecodingPayload(
+ FrameDecoderState* state,
+ DecodeBuffer* db) {
+ const Http2FrameHeader& frame_header = state->frame_header();
+ const uint32_t total_length = frame_header.payload_length;
+
+ DVLOG(2) << "WindowUpdatePayloadDecoder::StartDecodingPayload: "
+ << frame_header;
+
+ DCHECK_EQ(Http2FrameType::WINDOW_UPDATE, frame_header.type);
+ DCHECK_LE(db->Remaining(), total_length);
+
+ // WINDOW_UPDATE frames have no flags.
+ DCHECK_EQ(0, frame_header.flags);
+
+ // Special case for when the payload is the correct size and entirely in
+ // the buffer.
+ if (db->Remaining() == Http2WindowUpdateFields::EncodedSize() &&
+ total_length == Http2WindowUpdateFields::EncodedSize()) {
+ DoDecode(&window_update_fields_, db);
+ state->listener()->OnWindowUpdate(
+ frame_header, window_update_fields_.window_size_increment);
+ return DecodeStatus::kDecodeDone;
+ }
+ state->InitializeRemainders();
+ return HandleStatus(state, state->StartDecodingStructureInPayload(
+ &window_update_fields_, db));
+}
+
+DecodeStatus WindowUpdatePayloadDecoder::ResumeDecodingPayload(
+ FrameDecoderState* state,
+ DecodeBuffer* db) {
+ DVLOG(2) << "ResumeDecodingPayload: remaining_payload="
+ << state->remaining_payload()
+ << "; db->Remaining=" << db->Remaining();
+ DCHECK_EQ(Http2FrameType::WINDOW_UPDATE, state->frame_header().type);
+ DCHECK_LE(db->Remaining(), state->frame_header().payload_length);
+ return HandleStatus(state, state->ResumeDecodingStructureInPayload(
+ &window_update_fields_, db));
+}
+
+DecodeStatus WindowUpdatePayloadDecoder::HandleStatus(FrameDecoderState* state,
+ DecodeStatus status) {
+ DVLOG(2) << "HandleStatus: status=" << status
+ << "; remaining_payload=" << state->remaining_payload();
+ if (status == DecodeStatus::kDecodeDone) {
+ if (state->remaining_payload() == 0) {
+ state->listener()->OnWindowUpdate(
+ state->frame_header(), window_update_fields_.window_size_increment);
+ return DecodeStatus::kDecodeDone;
+ }
+ // Payload is too long.
+ return state->ReportFrameSizeError();
+ }
+ // Not done decoding the structure. Either we've got more payload to decode,
+ // or we've run out because the payload is too short, in which case
+ // OnFrameSizeError will have already been called.
+ DCHECK(
+ (status == DecodeStatus::kDecodeInProgress &&
+ state->remaining_payload() > 0) ||
+ (status == DecodeStatus::kDecodeError && state->remaining_payload() == 0))
+ << "\n status=" << status
+ << "; remaining_payload=" << state->remaining_payload();
+ return status;
+}
+
+} // namespace http2
diff --git a/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/window_update_payload_decoder.h b/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/window_update_payload_decoder.h
new file mode 100644
index 00000000000..158c16502db
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/window_update_payload_decoder.h
@@ -0,0 +1,42 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_HTTP2_DECODER_PAYLOAD_DECODERS_WINDOW_UPDATE_PAYLOAD_DECODER_H_
+#define QUICHE_HTTP2_DECODER_PAYLOAD_DECODERS_WINDOW_UPDATE_PAYLOAD_DECODER_H_
+
+// Decodes the payload of a WINDOW_UPDATE frame.
+
+#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_status.h"
+#include "net/third_party/quiche/src/http2/decoder/frame_decoder_state.h"
+#include "net/third_party/quiche/src/http2/http2_structures.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_export.h"
+
+namespace http2 {
+namespace test {
+class WindowUpdatePayloadDecoderPeer;
+} // namespace test
+
+class HTTP2_EXPORT_PRIVATE WindowUpdatePayloadDecoder {
+ public:
+ // Starts decoding a WINDOW_UPDATE frame's payload, and completes it if
+ // the entire payload is in the provided decode buffer.
+ DecodeStatus StartDecodingPayload(FrameDecoderState* state, DecodeBuffer* db);
+
+ // Resumes decoding a WINDOW_UPDATE frame's payload that has been split across
+ // decode buffers.
+ DecodeStatus ResumeDecodingPayload(FrameDecoderState* state,
+ DecodeBuffer* db);
+
+ private:
+ friend class test::WindowUpdatePayloadDecoderPeer;
+
+ DecodeStatus HandleStatus(FrameDecoderState* state, DecodeStatus status);
+
+ Http2WindowUpdateFields window_update_fields_;
+};
+
+} // namespace http2
+
+#endif // QUICHE_HTTP2_DECODER_PAYLOAD_DECODERS_WINDOW_UPDATE_PAYLOAD_DECODER_H_
diff --git a/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/window_update_payload_decoder_test.cc b/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/window_update_payload_decoder_test.cc
new file mode 100644
index 00000000000..77a2b90a45c
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/decoder/payload_decoders/window_update_payload_decoder_test.cc
@@ -0,0 +1,95 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/http2/decoder/payload_decoders/window_update_payload_decoder.h"
+
+#include <stddef.h>
+
+#include "base/logging.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener.h"
+#include "net/third_party/quiche/src/http2/decoder/payload_decoders/payload_decoder_base_test_util.h"
+#include "net/third_party/quiche/src/http2/http2_constants.h"
+#include "net/third_party/quiche/src/http2/http2_structures_test_util.h"
+#include "net/third_party/quiche/src/http2/test_tools/frame_parts.h"
+#include "net/third_party/quiche/src/http2/test_tools/frame_parts_collector.h"
+#include "net/third_party/quiche/src/http2/test_tools/http2_random.h"
+#include "net/third_party/quiche/src/http2/tools/http2_frame_builder.h"
+#include "net/third_party/quiche/src/http2/tools/random_decoder_test.h"
+
+namespace http2 {
+namespace test {
+
+class WindowUpdatePayloadDecoderPeer {
+ public:
+ static constexpr Http2FrameType FrameType() {
+ return Http2FrameType::WINDOW_UPDATE;
+ }
+
+ // Returns the mask of flags that affect the decoding of the payload (i.e.
+ // flags that that indicate the presence of certain fields or padding).
+ static constexpr uint8_t FlagsAffectingPayloadDecoding() { return 0; }
+};
+
+namespace {
+
+struct Listener : public FramePartsCollector {
+ void OnWindowUpdate(const Http2FrameHeader& header,
+ uint32_t window_size_increment) override {
+ VLOG(1) << "OnWindowUpdate: " << header
+ << "; window_size_increment=" << window_size_increment;
+ EXPECT_EQ(Http2FrameType::WINDOW_UPDATE, header.type);
+ StartAndEndFrame(header)->OnWindowUpdate(header, window_size_increment);
+ }
+
+ void OnFrameSizeError(const Http2FrameHeader& header) override {
+ VLOG(1) << "OnFrameSizeError: " << header;
+ FrameError(header)->OnFrameSizeError(header);
+ }
+};
+
+class WindowUpdatePayloadDecoderTest
+ : public AbstractPayloadDecoderTest<WindowUpdatePayloadDecoder,
+ WindowUpdatePayloadDecoderPeer,
+ Listener> {
+ protected:
+ Http2WindowUpdateFields RandWindowUpdateFields() {
+ Http2WindowUpdateFields fields;
+ test::Randomize(&fields, RandomPtr());
+ VLOG(3) << "RandWindowUpdateFields: " << fields;
+ return fields;
+ }
+};
+
+// Confirm we get an error if the payload is not the correct size to hold
+// exactly one Http2WindowUpdateFields.
+TEST_F(WindowUpdatePayloadDecoderTest, WrongSize) {
+ auto approve_size = [](size_t size) {
+ return size != Http2WindowUpdateFields::EncodedSize();
+ };
+ Http2FrameBuilder fb;
+ fb.Append(RandWindowUpdateFields());
+ fb.Append(RandWindowUpdateFields());
+ fb.Append(RandWindowUpdateFields());
+ EXPECT_TRUE(VerifyDetectsFrameSizeError(0, fb.buffer(), approve_size));
+}
+
+TEST_F(WindowUpdatePayloadDecoderTest, VariousPayloads) {
+ for (int n = 0; n < 100; ++n) {
+ uint32_t stream_id = n == 0 ? 0 : RandStreamId();
+ Http2WindowUpdateFields fields = RandWindowUpdateFields();
+ Http2FrameBuilder fb;
+ fb.Append(fields);
+ Http2FrameHeader header(fb.size(), Http2FrameType::WINDOW_UPDATE,
+ RandFlags(), stream_id);
+ set_frame_header(header);
+ FrameParts expected(header);
+ expected.SetOptWindowUpdateIncrement(fields.window_size_increment);
+ EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(fb.buffer(), expected));
+ }
+}
+
+} // namespace
+} // namespace test
+} // namespace http2
diff --git a/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_block_collector.cc b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_block_collector.cc
new file mode 100644
index 00000000000..5b2c6b7293a
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_block_collector.cc
@@ -0,0 +1,151 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_block_collector.h"
+
+#include <algorithm>
+#include <memory>
+
+#include "base/logging.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_test_helpers.h"
+
+using ::testing::AssertionResult;
+using ::testing::AssertionSuccess;
+
+namespace http2 {
+namespace test {
+
+HpackBlockCollector::HpackBlockCollector() = default;
+HpackBlockCollector::HpackBlockCollector(const HpackBlockCollector& other)
+ : pending_entry_(other.pending_entry_), entries_(other.entries_) {}
+HpackBlockCollector::~HpackBlockCollector() = default;
+
+void HpackBlockCollector::OnIndexedHeader(size_t index) {
+ pending_entry_.OnIndexedHeader(index);
+ PushPendingEntry();
+}
+void HpackBlockCollector::OnDynamicTableSizeUpdate(size_t size) {
+ pending_entry_.OnDynamicTableSizeUpdate(size);
+ PushPendingEntry();
+}
+void HpackBlockCollector::OnStartLiteralHeader(HpackEntryType header_type,
+ size_t maybe_name_index) {
+ pending_entry_.OnStartLiteralHeader(header_type, maybe_name_index);
+}
+void HpackBlockCollector::OnNameStart(bool huffman_encoded, size_t len) {
+ pending_entry_.OnNameStart(huffman_encoded, len);
+}
+void HpackBlockCollector::OnNameData(const char* data, size_t len) {
+ pending_entry_.OnNameData(data, len);
+}
+void HpackBlockCollector::OnNameEnd() {
+ pending_entry_.OnNameEnd();
+}
+void HpackBlockCollector::OnValueStart(bool huffman_encoded, size_t len) {
+ pending_entry_.OnValueStart(huffman_encoded, len);
+}
+void HpackBlockCollector::OnValueData(const char* data, size_t len) {
+ pending_entry_.OnValueData(data, len);
+}
+void HpackBlockCollector::OnValueEnd() {
+ pending_entry_.OnValueEnd();
+ PushPendingEntry();
+}
+
+void HpackBlockCollector::PushPendingEntry() {
+ EXPECT_TRUE(pending_entry_.IsComplete());
+ DVLOG(2) << "PushPendingEntry: " << pending_entry_;
+ entries_.push_back(pending_entry_);
+ EXPECT_TRUE(entries_.back().IsComplete());
+ pending_entry_.Clear();
+}
+void HpackBlockCollector::Clear() {
+ pending_entry_.Clear();
+ entries_.clear();
+}
+
+void HpackBlockCollector::ExpectIndexedHeader(size_t index) {
+ entries_.push_back(
+ HpackEntryCollector(HpackEntryType::kIndexedHeader, index));
+}
+void HpackBlockCollector::ExpectDynamicTableSizeUpdate(size_t size) {
+ entries_.push_back(
+ HpackEntryCollector(HpackEntryType::kDynamicTableSizeUpdate, size));
+}
+void HpackBlockCollector::ExpectNameIndexAndLiteralValue(
+ HpackEntryType type,
+ size_t index,
+ bool value_huffman,
+ const Http2String& value) {
+ entries_.push_back(HpackEntryCollector(type, index, value_huffman, value));
+}
+void HpackBlockCollector::ExpectLiteralNameAndValue(HpackEntryType type,
+ bool name_huffman,
+ const Http2String& name,
+ bool value_huffman,
+ const Http2String& value) {
+ entries_.push_back(
+ HpackEntryCollector(type, name_huffman, name, value_huffman, value));
+}
+
+void HpackBlockCollector::ShuffleEntries(Http2Random* rng) {
+ std::shuffle(entries_.begin(), entries_.end(), *rng);
+}
+
+void HpackBlockCollector::AppendToHpackBlockBuilder(
+ HpackBlockBuilder* hbb) const {
+ CHECK(IsNotPending());
+ for (const auto& entry : entries_) {
+ entry.AppendToHpackBlockBuilder(hbb);
+ }
+}
+
+AssertionResult HpackBlockCollector::ValidateSoleIndexedHeader(
+ size_t ndx) const {
+ VERIFY_TRUE(pending_entry_.IsClear());
+ VERIFY_EQ(1u, entries_.size());
+ VERIFY_TRUE(entries_.front().ValidateIndexedHeader(ndx));
+ return AssertionSuccess();
+}
+AssertionResult HpackBlockCollector::ValidateSoleLiteralValueHeader(
+ HpackEntryType expected_type,
+ size_t expected_index,
+ bool expected_value_huffman,
+ Http2StringPiece expected_value) const {
+ VERIFY_TRUE(pending_entry_.IsClear());
+ VERIFY_EQ(1u, entries_.size());
+ VERIFY_TRUE(entries_.front().ValidateLiteralValueHeader(
+ expected_type, expected_index, expected_value_huffman, expected_value));
+ return AssertionSuccess();
+}
+AssertionResult HpackBlockCollector::ValidateSoleLiteralNameValueHeader(
+ HpackEntryType expected_type,
+ bool expected_name_huffman,
+ Http2StringPiece expected_name,
+ bool expected_value_huffman,
+ Http2StringPiece expected_value) const {
+ VERIFY_TRUE(pending_entry_.IsClear());
+ VERIFY_EQ(1u, entries_.size());
+ VERIFY_TRUE(entries_.front().ValidateLiteralNameValueHeader(
+ expected_type, expected_name_huffman, expected_name,
+ expected_value_huffman, expected_value));
+ return AssertionSuccess();
+}
+AssertionResult HpackBlockCollector::ValidateSoleDynamicTableSizeUpdate(
+ size_t size) const {
+ VERIFY_TRUE(pending_entry_.IsClear());
+ VERIFY_EQ(1u, entries_.size());
+ VERIFY_TRUE(entries_.front().ValidateDynamicTableSizeUpdate(size));
+ return AssertionSuccess();
+}
+
+AssertionResult HpackBlockCollector::VerifyEq(
+ const HpackBlockCollector& that) const {
+ VERIFY_EQ(pending_entry_, that.pending_entry_);
+ VERIFY_EQ(entries_, that.entries_);
+ return AssertionSuccess();
+}
+
+} // namespace test
+} // namespace http2
diff --git a/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_block_collector.h b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_block_collector.h
new file mode 100644
index 00000000000..6ad14057c24
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_block_collector.h
@@ -0,0 +1,129 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_HTTP2_HPACK_DECODER_HPACK_BLOCK_COLLECTOR_H_
+#define QUICHE_HTTP2_HPACK_DECODER_HPACK_BLOCK_COLLECTOR_H_
+
+// HpackBlockCollector implements HpackEntryDecoderListener in order to record
+// the calls using HpackEntryCollector instances (one per HPACK entry). This
+// supports testing of HpackBlockDecoder, which decodes entire HPACK blocks.
+//
+// In addition to implementing the callback methods, HpackBlockCollector also
+// supports comparing two HpackBlockCollector instances (i.e. an expected and
+// an actual), or a sole HPACK entry against an expected value.
+
+#include <stddef.h>
+
+#include <vector>
+
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_collector.h"
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_decoder_listener.h"
+#include "net/third_party/quiche/src/http2/hpack/http2_hpack_constants.h"
+#include "net/third_party/quiche/src/http2/hpack/tools/hpack_block_builder.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string_piece.h"
+#include "net/third_party/quiche/src/http2/test_tools/http2_random.h"
+
+namespace http2 {
+namespace test {
+
+class HpackBlockCollector : public HpackEntryDecoderListener {
+ public:
+ HpackBlockCollector();
+ HpackBlockCollector(const HpackBlockCollector& other);
+ ~HpackBlockCollector() override;
+
+ // Implementations of HpackEntryDecoderListener, forwarding to pending_entry_,
+ // an HpackEntryCollector for the "in-progress" HPACK entry. OnIndexedHeader
+ // and OnDynamicTableSizeUpdate are pending only for that one call, while
+ // OnStartLiteralHeader is followed by many calls, ending with OnValueEnd.
+ // Once all the calls for one HPACK entry have been received, PushPendingEntry
+ // is used to append the pending_entry_ entry to the collected entries_.
+ void OnIndexedHeader(size_t index) override;
+ void OnDynamicTableSizeUpdate(size_t size) override;
+ void OnStartLiteralHeader(HpackEntryType header_type,
+ size_t maybe_name_index) override;
+ void OnNameStart(bool huffman_encoded, size_t len) override;
+ void OnNameData(const char* data, size_t len) override;
+ void OnNameEnd() override;
+ void OnValueStart(bool huffman_encoded, size_t len) override;
+ void OnValueData(const char* data, size_t len) override;
+ void OnValueEnd() override;
+
+ // Methods for creating a set of expectations (i.e. HPACK entries to compare
+ // against those collected by another instance of HpackBlockCollector).
+
+ // Add an HPACK entry for an indexed header.
+ void ExpectIndexedHeader(size_t index);
+
+ // Add an HPACK entry for a dynamic table size update.
+ void ExpectDynamicTableSizeUpdate(size_t size);
+
+ // Add an HPACK entry for a header entry with an index for the name, and a
+ // literal value.
+ void ExpectNameIndexAndLiteralValue(HpackEntryType type,
+ size_t index,
+ bool value_huffman,
+ const Http2String& value);
+
+ // Add an HPACK entry for a header entry with a literal name and value.
+ void ExpectLiteralNameAndValue(HpackEntryType type,
+ bool name_huffman,
+ const Http2String& name,
+ bool value_huffman,
+ const Http2String& value);
+
+ // Shuffle the entries, in support of generating an HPACK block of entries
+ // in some random order.
+ void ShuffleEntries(Http2Random* rng);
+
+ // Serialize entries_ to the HpackBlockBuilder.
+ void AppendToHpackBlockBuilder(HpackBlockBuilder* hbb) const;
+
+ // Return AssertionSuccess if there is just one entry, and it is an
+ // Indexed Header with the specified index.
+ ::testing::AssertionResult ValidateSoleIndexedHeader(size_t ndx) const;
+
+ // Return AssertionSuccess if there is just one entry, and it is a
+ // Dynamic Table Size Update with the specified size.
+ ::testing::AssertionResult ValidateSoleDynamicTableSizeUpdate(
+ size_t size) const;
+
+ // Return AssertionSuccess if there is just one entry, and it is a Header
+ // entry with an index for the name and a literal value.
+ ::testing::AssertionResult ValidateSoleLiteralValueHeader(
+ HpackEntryType expected_type,
+ size_t expected_index,
+ bool expected_value_huffman,
+ Http2StringPiece expected_value) const;
+
+ // Return AssertionSuccess if there is just one entry, and it is a Header
+ // with a literal name and literal value.
+ ::testing::AssertionResult ValidateSoleLiteralNameValueHeader(
+ HpackEntryType expected_type,
+ bool expected_name_huffman,
+ Http2StringPiece expected_name,
+ bool expected_value_huffman,
+ Http2StringPiece expected_value) const;
+
+ bool IsNotPending() const { return pending_entry_.IsClear(); }
+ bool IsClear() const { return IsNotPending() && entries_.empty(); }
+ void Clear();
+
+ ::testing::AssertionResult VerifyEq(const HpackBlockCollector& that) const;
+
+ private:
+ // Push the value of pending_entry_ onto entries_, and clear pending_entry_.
+ // The pending_entry_ must be complete.
+ void PushPendingEntry();
+
+ HpackEntryCollector pending_entry_;
+ std::vector<HpackEntryCollector> entries_;
+};
+
+} // namespace test
+} // namespace http2
+
+#endif // QUICHE_HTTP2_HPACK_DECODER_HPACK_BLOCK_COLLECTOR_H_
diff --git a/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_block_decoder.cc b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_block_decoder.cc
new file mode 100644
index 00000000000..c6882d292c4
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_block_decoder.cc
@@ -0,0 +1,62 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_block_decoder.h"
+
+#include <cstdint>
+
+#include "base/logging.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string_utils.h"
+
+namespace http2 {
+
+DecodeStatus HpackBlockDecoder::Decode(DecodeBuffer* db) {
+ if (!before_entry_) {
+ DVLOG(2) << "HpackBlockDecoder::Decode resume entry, db->Remaining="
+ << db->Remaining();
+ DecodeStatus status = entry_decoder_.Resume(db, listener_);
+ switch (status) {
+ case DecodeStatus::kDecodeDone:
+ before_entry_ = true;
+ break;
+ case DecodeStatus::kDecodeInProgress:
+ DCHECK_EQ(0u, db->Remaining());
+ return DecodeStatus::kDecodeInProgress;
+ case DecodeStatus::kDecodeError:
+ return DecodeStatus::kDecodeError;
+ }
+ }
+ DCHECK(before_entry_);
+ while (db->HasData()) {
+ DVLOG(2) << "HpackBlockDecoder::Decode start entry, db->Remaining="
+ << db->Remaining();
+ DecodeStatus status = entry_decoder_.Start(db, listener_);
+ switch (status) {
+ case DecodeStatus::kDecodeDone:
+ continue;
+ case DecodeStatus::kDecodeInProgress:
+ DCHECK_EQ(0u, db->Remaining());
+ before_entry_ = false;
+ return DecodeStatus::kDecodeInProgress;
+ case DecodeStatus::kDecodeError:
+ return DecodeStatus::kDecodeError;
+ }
+ DCHECK(false);
+ }
+ DCHECK(before_entry_);
+ return DecodeStatus::kDecodeDone;
+}
+
+Http2String HpackBlockDecoder::DebugString() const {
+ return Http2StrCat("HpackBlockDecoder(", entry_decoder_.DebugString(),
+ ", listener@",
+ Http2Hex(reinterpret_cast<intptr_t>(listener_)),
+ (before_entry_ ? ", between entries)" : ", in an entry)"));
+}
+
+std::ostream& operator<<(std::ostream& out, const HpackBlockDecoder& v) {
+ return out << v.DebugString();
+}
+
+} // namespace http2
diff --git a/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_block_decoder.h b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_block_decoder.h
new file mode 100644
index 00000000000..e8b23e16dce
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_block_decoder.h
@@ -0,0 +1,64 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_HTTP2_HPACK_DECODER_HPACK_BLOCK_DECODER_H_
+#define QUICHE_HTTP2_HPACK_DECODER_HPACK_BLOCK_DECODER_H_
+
+// HpackBlockDecoder decodes an entire HPACK block (or the available portion
+// thereof in the DecodeBuffer) into entries, but doesn't include HPACK static
+// or dynamic table support, so table indices remain indices at this level.
+// Reports the entries to an HpackEntryDecoderListener.
+
+#include "base/logging.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_status.h"
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_decoder.h"
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_decoder_listener.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_export.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string.h"
+
+namespace http2 {
+
+class HTTP2_EXPORT_PRIVATE HpackBlockDecoder {
+ public:
+ explicit HpackBlockDecoder(HpackEntryDecoderListener* listener)
+ : listener_(listener) {
+ DCHECK_NE(listener_, nullptr);
+ }
+ ~HpackBlockDecoder() {}
+
+ HpackBlockDecoder(const HpackBlockDecoder&) = delete;
+ HpackBlockDecoder& operator=(const HpackBlockDecoder&) = delete;
+
+ // Prepares the decoder to start decoding a new HPACK block. Expected
+ // to be called from an implementation of Http2FrameDecoderListener's
+ // OnHeadersStart or OnPushPromiseStart methods.
+ void Reset() {
+ DVLOG(2) << "HpackBlockDecoder::Reset";
+ before_entry_ = true;
+ }
+
+ // Decode the fragment of the HPACK block contained in the decode buffer.
+ // Expected to be called from an implementation of Http2FrameDecoderListener's
+ // OnHpackFragment method.
+ DecodeStatus Decode(DecodeBuffer* db);
+
+ // Is the decoding process between entries (i.e. would the next byte be the
+ // first byte of a new HPACK entry)?
+ bool before_entry() const { return before_entry_; }
+
+ Http2String DebugString() const;
+
+ private:
+ HpackEntryDecoder entry_decoder_;
+ HpackEntryDecoderListener* const listener_;
+ bool before_entry_ = true;
+};
+
+HTTP2_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& out,
+ const HpackBlockDecoder& v);
+
+} // namespace http2
+
+#endif // QUICHE_HTTP2_HPACK_DECODER_HPACK_BLOCK_DECODER_H_
diff --git a/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_block_decoder_test.cc b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_block_decoder_test.cc
new file mode 100644
index 00000000000..fce45b2a316
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_block_decoder_test.cc
@@ -0,0 +1,294 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_block_decoder.h"
+
+// Tests of HpackBlockDecoder.
+
+#include <cstdint>
+
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h"
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_block_collector.h"
+#include "net/third_party/quiche/src/http2/hpack/http2_hpack_constants.h"
+#include "net/third_party/quiche/src/http2/hpack/tools/hpack_block_builder.h"
+#include "net/third_party/quiche/src/http2/hpack/tools/hpack_example.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string_piece.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_test_helpers.h"
+#include "net/third_party/quiche/src/http2/test_tools/http2_random.h"
+#include "net/third_party/quiche/src/http2/tools/random_decoder_test.h"
+
+using ::testing::AssertionSuccess;
+
+namespace http2 {
+namespace test {
+namespace {
+
+class HpackBlockDecoderTest : public RandomDecoderTest {
+ protected:
+ HpackBlockDecoderTest() : listener_(&collector_), decoder_(&listener_) {
+ stop_decode_on_done_ = false;
+ decoder_.Reset();
+ // Make sure logging doesn't crash. Not examining the result.
+ std::ostringstream strm;
+ strm << decoder_;
+ }
+
+ DecodeStatus StartDecoding(DecodeBuffer* db) override {
+ collector_.Clear();
+ decoder_.Reset();
+ return ResumeDecoding(db);
+ }
+
+ DecodeStatus ResumeDecoding(DecodeBuffer* db) override {
+ DecodeStatus status = decoder_.Decode(db);
+
+ // Make sure logging doesn't crash. Not examining the result.
+ std::ostringstream strm;
+ strm << decoder_;
+
+ return status;
+ }
+
+ AssertionResult DecodeAndValidateSeveralWays(DecodeBuffer* db,
+ const Validator& validator) {
+ bool return_non_zero_on_first = false;
+ return RandomDecoderTest::DecodeAndValidateSeveralWays(
+ db, return_non_zero_on_first, validator);
+ }
+
+ AssertionResult DecodeAndValidateSeveralWays(const HpackBlockBuilder& hbb,
+ const Validator& validator) {
+ DecodeBuffer db(hbb.buffer());
+ return DecodeAndValidateSeveralWays(&db, validator);
+ }
+
+ AssertionResult DecodeHpackExampleAndValidateSeveralWays(
+ Http2StringPiece hpack_example,
+ Validator validator) {
+ Http2String input = HpackExampleToStringOrDie(hpack_example);
+ DecodeBuffer db(input);
+ return DecodeAndValidateSeveralWays(&db, validator);
+ }
+
+ uint8_t Rand8() { return Random().Rand8(); }
+
+ Http2String Rand8String() { return Random().RandString(Rand8()); }
+
+ HpackBlockCollector collector_;
+ HpackEntryDecoderVLoggingListener listener_;
+ HpackBlockDecoder decoder_;
+};
+
+// http://httpwg.org/specs/rfc7541.html#rfc.section.C.2.1
+TEST_F(HpackBlockDecoderTest, SpecExample_C_2_1) {
+ auto do_check = [this]() {
+ VERIFY_AND_RETURN_SUCCESS(collector_.ValidateSoleLiteralNameValueHeader(
+ HpackEntryType::kIndexedLiteralHeader, false, "custom-key", false,
+ "custom-header"));
+ };
+ const char hpack_example[] = R"(
+ 40 | == Literal indexed ==
+ 0a | Literal name (len = 10)
+ 6375 7374 6f6d 2d6b 6579 | custom-key
+ 0d | Literal value (len = 13)
+ 6375 7374 6f6d 2d68 6561 6465 72 | custom-header
+ | -> custom-key:
+ | custom-header
+ )";
+ EXPECT_TRUE(DecodeHpackExampleAndValidateSeveralWays(
+ hpack_example, ValidateDoneAndEmpty(do_check)));
+ EXPECT_TRUE(do_check());
+}
+
+// http://httpwg.org/specs/rfc7541.html#rfc.section.C.2.2
+TEST_F(HpackBlockDecoderTest, SpecExample_C_2_2) {
+ auto do_check = [this]() {
+ VERIFY_AND_RETURN_SUCCESS(collector_.ValidateSoleLiteralValueHeader(
+ HpackEntryType::kUnindexedLiteralHeader, 4, false, "/sample/path"));
+ };
+ const char hpack_example[] = R"(
+ 04 | == Literal not indexed ==
+ | Indexed name (idx = 4)
+ | :path
+ 0c | Literal value (len = 12)
+ 2f73 616d 706c 652f 7061 7468 | /sample/path
+ | -> :path: /sample/path
+ )";
+ EXPECT_TRUE(DecodeHpackExampleAndValidateSeveralWays(
+ hpack_example, ValidateDoneAndEmpty(do_check)));
+ EXPECT_TRUE(do_check());
+}
+
+// http://httpwg.org/specs/rfc7541.html#rfc.section.C.2.3
+TEST_F(HpackBlockDecoderTest, SpecExample_C_2_3) {
+ auto do_check = [this]() {
+ VERIFY_AND_RETURN_SUCCESS(collector_.ValidateSoleLiteralNameValueHeader(
+ HpackEntryType::kNeverIndexedLiteralHeader, false, "password", false,
+ "secret"));
+ };
+ const char hpack_example[] = R"(
+ 10 | == Literal never indexed ==
+ 08 | Literal name (len = 8)
+ 7061 7373 776f 7264 | password
+ 06 | Literal value (len = 6)
+ 7365 6372 6574 | secret
+ | -> password: secret
+ )";
+ EXPECT_TRUE(DecodeHpackExampleAndValidateSeveralWays(
+ hpack_example, ValidateDoneAndEmpty(do_check)));
+ EXPECT_TRUE(do_check());
+}
+
+// http://httpwg.org/specs/rfc7541.html#rfc.section.C.2.4
+TEST_F(HpackBlockDecoderTest, SpecExample_C_2_4) {
+ auto do_check = [this]() {
+ VERIFY_AND_RETURN_SUCCESS(collector_.ValidateSoleIndexedHeader(2));
+ };
+ const char hpack_example[] = R"(
+ 82 | == Indexed - Add ==
+ | idx = 2
+ | -> :method: GET
+ )";
+ EXPECT_TRUE(DecodeHpackExampleAndValidateSeveralWays(
+ hpack_example, ValidateDoneAndEmpty(do_check)));
+ EXPECT_TRUE(do_check());
+}
+// http://httpwg.org/specs/rfc7541.html#rfc.section.C.3.1
+TEST_F(HpackBlockDecoderTest, SpecExample_C_3_1) {
+ Http2String example = R"(
+ 82 | == Indexed - Add ==
+ | idx = 2
+ | -> :method: GET
+ 86 | == Indexed - Add ==
+ | idx = 6
+ | -> :scheme: http
+ 84 | == Indexed - Add ==
+ | idx = 4
+ | -> :path: /
+ 41 | == Literal indexed ==
+ | Indexed name (idx = 1)
+ | :authority
+ 0f | Literal value (len = 15)
+ 7777 772e 6578 616d 706c 652e 636f 6d | www.example.com
+ | -> :authority:
+ | www.example.com
+ )";
+ HpackBlockCollector expected;
+ expected.ExpectIndexedHeader(2);
+ expected.ExpectIndexedHeader(6);
+ expected.ExpectIndexedHeader(4);
+ expected.ExpectNameIndexAndLiteralValue(HpackEntryType::kIndexedLiteralHeader,
+ 1, false, "www.example.com");
+ NoArgValidator do_check = [expected, this]() {
+ VERIFY_AND_RETURN_SUCCESS(collector_.VerifyEq(expected));
+ };
+ EXPECT_TRUE(DecodeHpackExampleAndValidateSeveralWays(
+ example, ValidateDoneAndEmpty(do_check)));
+ EXPECT_TRUE(do_check());
+}
+
+// http://httpwg.org/specs/rfc7541.html#rfc.section.C.5.1
+TEST_F(HpackBlockDecoderTest, SpecExample_C_5_1) {
+ Http2String example = R"(
+ 48 | == Literal indexed ==
+ | Indexed name (idx = 8)
+ | :status
+ 03 | Literal value (len = 3)
+ 3330 32 | 302
+ | -> :status: 302
+ 58 | == Literal indexed ==
+ | Indexed name (idx = 24)
+ | cache-control
+ 07 | Literal value (len = 7)
+ 7072 6976 6174 65 | private
+ | -> cache-control: private
+ 61 | == Literal indexed ==
+ | Indexed name (idx = 33)
+ | date
+ 1d | Literal value (len = 29)
+ 4d6f 6e2c 2032 3120 4f63 7420 3230 3133 | Mon, 21 Oct 2013
+ 2032 303a 3133 3a32 3120 474d 54 | 20:13:21 GMT
+ | -> date: Mon, 21 Oct 2013
+ | 20:13:21 GMT
+ 6e | == Literal indexed ==
+ | Indexed name (idx = 46)
+ | location
+ 17 | Literal value (len = 23)
+ 6874 7470 733a 2f2f 7777 772e 6578 616d | https://www.exam
+ 706c 652e 636f 6d | ple.com
+ | -> location:
+ | https://www.example.com
+ )";
+ HpackBlockCollector expected;
+ expected.ExpectNameIndexAndLiteralValue(HpackEntryType::kIndexedLiteralHeader,
+ 8, false, "302");
+ expected.ExpectNameIndexAndLiteralValue(HpackEntryType::kIndexedLiteralHeader,
+ 24, false, "private");
+ expected.ExpectNameIndexAndLiteralValue(HpackEntryType::kIndexedLiteralHeader,
+ 33, false,
+ "Mon, 21 Oct 2013 20:13:21 GMT");
+ expected.ExpectNameIndexAndLiteralValue(HpackEntryType::kIndexedLiteralHeader,
+ 46, false, "https://www.example.com");
+ NoArgValidator do_check = [expected, this]() {
+ VERIFY_AND_RETURN_SUCCESS(collector_.VerifyEq(expected));
+ };
+ EXPECT_TRUE(DecodeHpackExampleAndValidateSeveralWays(
+ example, ValidateDoneAndEmpty(do_check)));
+ EXPECT_TRUE(do_check());
+}
+
+// Generate a bunch of HPACK block entries to expect, use those expectations
+// to generate an HPACK block, then decode it and confirm it matches those
+// expectations. Some of these are invalid (such as Indexed, with index=0),
+// but well-formed, and the decoder doesn't check for validity, just
+// well-formedness. That includes the validity of the strings not being checked,
+// such as lower-case ascii for the names, and valid Huffman encodings.
+TEST_F(HpackBlockDecoderTest, Computed) {
+ HpackBlockCollector expected;
+ expected.ExpectIndexedHeader(0);
+ expected.ExpectIndexedHeader(1);
+ expected.ExpectIndexedHeader(126);
+ expected.ExpectIndexedHeader(127);
+ expected.ExpectIndexedHeader(128);
+ expected.ExpectDynamicTableSizeUpdate(0);
+ expected.ExpectDynamicTableSizeUpdate(1);
+ expected.ExpectDynamicTableSizeUpdate(14);
+ expected.ExpectDynamicTableSizeUpdate(15);
+ expected.ExpectDynamicTableSizeUpdate(30);
+ expected.ExpectDynamicTableSizeUpdate(31);
+ expected.ExpectDynamicTableSizeUpdate(4095);
+ expected.ExpectDynamicTableSizeUpdate(4096);
+ expected.ExpectDynamicTableSizeUpdate(8192);
+ for (auto type : {HpackEntryType::kIndexedLiteralHeader,
+ HpackEntryType::kUnindexedLiteralHeader,
+ HpackEntryType::kNeverIndexedLiteralHeader}) {
+ for (bool value_huffman : {false, true}) {
+ // An entry with an index for the name. Ensure the name index
+ // is not zero by adding one to the Rand8() result.
+ expected.ExpectNameIndexAndLiteralValue(type, Rand8() + 1, value_huffman,
+ Rand8String());
+ // And two entries with literal names, one plain, one huffman encoded.
+ expected.ExpectLiteralNameAndValue(type, false, Rand8String(),
+ value_huffman, Rand8String());
+ expected.ExpectLiteralNameAndValue(type, true, Rand8String(),
+ value_huffman, Rand8String());
+ }
+ }
+ // Shuffle the entries and serialize them to produce an HPACK block.
+ expected.ShuffleEntries(RandomPtr());
+ HpackBlockBuilder hbb;
+ expected.AppendToHpackBlockBuilder(&hbb);
+
+ NoArgValidator do_check = [expected, this]() {
+ VERIFY_AND_RETURN_SUCCESS(collector_.VerifyEq(expected));
+ };
+ EXPECT_TRUE(
+ DecodeAndValidateSeveralWays(hbb, ValidateDoneAndEmpty(do_check)));
+ EXPECT_TRUE(do_check());
+}
+
+} // namespace
+} // namespace test
+} // namespace http2
diff --git a/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder.cc b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder.cc
new file mode 100644
index 00000000000..d6897cbbfa1
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder.cc
@@ -0,0 +1,122 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder.h"
+
+#include "base/logging.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_status.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_estimate_memory_usage.h"
+
+namespace http2 {
+
+HpackDecoder::HpackDecoder(HpackDecoderListener* listener,
+ size_t max_string_size)
+ : decoder_state_(listener),
+ entry_buffer_(&decoder_state_, max_string_size),
+ block_decoder_(&entry_buffer_),
+ error_detected_(false) {}
+
+HpackDecoder::~HpackDecoder() = default;
+
+void HpackDecoder::set_tables_debug_listener(
+ HpackDecoderTablesDebugListener* debug_listener) {
+ decoder_state_.set_tables_debug_listener(debug_listener);
+}
+
+void HpackDecoder::set_max_string_size_bytes(size_t max_string_size_bytes) {
+ entry_buffer_.set_max_string_size_bytes(max_string_size_bytes);
+}
+
+void HpackDecoder::ApplyHeaderTableSizeSetting(uint32_t max_header_table_size) {
+ decoder_state_.ApplyHeaderTableSizeSetting(max_header_table_size);
+}
+
+bool HpackDecoder::StartDecodingBlock() {
+ DVLOG(3) << "HpackDecoder::StartDecodingBlock, error_detected="
+ << (error_detected() ? "true" : "false");
+ if (error_detected()) {
+ return false;
+ }
+ // TODO(jamessynge): Eliminate Reset(), which shouldn't be necessary
+ // if there are no errors, and shouldn't be necessary with errors if
+ // we never resume decoding after an error has been detected.
+ block_decoder_.Reset();
+ decoder_state_.OnHeaderBlockStart();
+ return true;
+}
+
+bool HpackDecoder::DecodeFragment(DecodeBuffer* db) {
+ DVLOG(3) << "HpackDecoder::DecodeFragment, error_detected="
+ << (error_detected() ? "true" : "false")
+ << ", size=" << db->Remaining();
+ if (error_detected()) {
+ return false;
+ }
+ // Decode contents of db as an HPACK block fragment, forwards the decoded
+ // entries to entry_buffer_, which in turn forwards them to decode_state_,
+ // which finally forwards them to the HpackDecoderListener.
+ DecodeStatus status = block_decoder_.Decode(db);
+ if (status == DecodeStatus::kDecodeError) {
+ // This has probably already been reported, but just in case...
+ ReportError("HPACK block malformed.");
+ return false;
+ } else if (error_detected()) {
+ return false;
+ }
+ // Should be positioned between entries iff decoding is complete.
+ DCHECK_EQ(block_decoder_.before_entry(), status == DecodeStatus::kDecodeDone)
+ << status;
+ if (!block_decoder_.before_entry()) {
+ entry_buffer_.BufferStringsIfUnbuffered();
+ }
+ return true;
+}
+
+bool HpackDecoder::EndDecodingBlock() {
+ DVLOG(3) << "HpackDecoder::EndDecodingBlock, error_detected="
+ << (error_detected() ? "true" : "false");
+ if (error_detected()) {
+ return false;
+ }
+ if (!block_decoder_.before_entry()) {
+ // The HPACK block ended in the middle of an entry.
+ ReportError("HPACK block truncated.");
+ return false;
+ }
+ decoder_state_.OnHeaderBlockEnd();
+ if (error_detected()) {
+ // HpackDecoderState will have reported the error.
+ return false;
+ }
+ return true;
+}
+
+bool HpackDecoder::error_detected() {
+ if (!error_detected_) {
+ if (entry_buffer_.error_detected()) {
+ DVLOG(2) << "HpackDecoder::error_detected in entry_buffer_";
+ error_detected_ = true;
+ } else if (decoder_state_.error_detected()) {
+ DVLOG(2) << "HpackDecoder::error_detected in decoder_state_";
+ error_detected_ = true;
+ }
+ }
+ return error_detected_;
+}
+
+size_t HpackDecoder::EstimateMemoryUsage() const {
+ return Http2EstimateMemoryUsage(entry_buffer_);
+}
+
+void HpackDecoder::ReportError(Http2StringPiece error_message) {
+ DVLOG(3) << "HpackDecoder::ReportError is new="
+ << (!error_detected_ ? "true" : "false")
+ << ", error_message: " << error_message;
+ if (!error_detected_) {
+ error_detected_ = true;
+ decoder_state_.listener()->OnHeaderErrorDetected(error_message);
+ }
+}
+
+} // namespace http2
diff --git a/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder.h b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder.h
new file mode 100644
index 00000000000..e173bc6af19
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder.h
@@ -0,0 +1,123 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_HTTP2_HPACK_DECODER_HPACK_DECODER_H_
+#define QUICHE_HTTP2_HPACK_DECODER_HPACK_DECODER_H_
+
+// Decodes HPACK blocks, calls an HpackDecoderListener with the decoded header
+// entries. Also notifies the listener of errors and of the boundaries of the
+// HPACK blocks.
+
+// TODO(jamessynge): Add feature allowing an HpackEntryDecoderListener
+// sub-class (and possibly others) to be passed in for counting events,
+// so that deciding whether to count is not done by having lots of if
+// statements, but instead by inserting an indirection only when needed.
+
+// TODO(jamessynge): Consider whether to return false from methods below
+// when an error has been previously detected. It protects calling code
+// from its failure to pay attention to previous errors, but should we
+// spend time to do that?
+
+#include <stddef.h>
+
+#include <cstdint>
+
+#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h"
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_block_decoder.h"
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_listener.h"
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_state.h"
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_tables.h"
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_whole_entry_buffer.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_export.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string_piece.h"
+
+namespace http2 {
+namespace test {
+class HpackDecoderPeer;
+} // namespace test
+
+class HTTP2_EXPORT_PRIVATE HpackDecoder {
+ public:
+ HpackDecoder(HpackDecoderListener* listener, size_t max_string_size);
+ virtual ~HpackDecoder();
+
+ HpackDecoder(const HpackDecoder&) = delete;
+ HpackDecoder& operator=(const HpackDecoder&) = delete;
+
+ // Set listener to be notified of insertions into the HPACK dynamic table,
+ // and uses of those entries.
+ void set_tables_debug_listener(
+ HpackDecoderTablesDebugListener* debug_listener);
+
+ // max_string_size specifies the maximum size of an on-the-wire string (name
+ // or value, plain or Huffman encoded) that will be accepted. See sections
+ // 5.1 and 5.2 of RFC 7541. This is a defense against OOM attacks; HTTP/2
+ // allows a decoder to enforce any limit of the size of the header lists
+ // that it is willing to decode, including less than the MAX_HEADER_LIST_SIZE
+ // setting, a setting that is initially unlimited. For example, we might
+ // choose to send a MAX_HEADER_LIST_SIZE of 64KB, and to use that same value
+ // as the upper bound for individual strings.
+ void set_max_string_size_bytes(size_t max_string_size_bytes);
+
+ // ApplyHeaderTableSizeSetting notifies this object that this endpoint has
+ // received a SETTINGS ACK frame acknowledging an earlier SETTINGS frame from
+ // this endpoint specifying a new value for SETTINGS_HEADER_TABLE_SIZE (the
+ // maximum size of the dynamic table that this endpoint will use to decode
+ // HPACK blocks).
+ // Because a SETTINGS frame can contain SETTINGS_HEADER_TABLE_SIZE values,
+ // the caller must keep track of those multiple changes, and make
+ // corresponding calls to this method. In particular, a call must be made
+ // with the lowest value acknowledged by the peer, and a call must be made
+ // with the final value acknowledged, in that order; additional calls may
+ // be made if additional values were sent. These calls must be made between
+ // decoding the SETTINGS ACK, and before the next HPACK block is decoded.
+ void ApplyHeaderTableSizeSetting(uint32_t max_header_table_size);
+
+ // Prepares the decoder for decoding a new HPACK block, and announces this to
+ // its listener. Returns true if OK to continue with decoding, false if an
+ // error has been detected, which for StartDecodingBlock means the error was
+ // detected while decoding a previous HPACK block.
+ bool StartDecodingBlock();
+
+ // Decodes a fragment (some or all of the remainder) of an HPACK block,
+ // reporting header entries (name & value pairs) that it completely decodes
+ // in the process to the listener. Returns true successfully decoded, false if
+ // an error has been detected, either during decoding of the fragment, or
+ // prior to this call.
+ bool DecodeFragment(DecodeBuffer* db);
+
+ // Completes the process of decoding an HPACK block: if the HPACK block was
+ // properly terminated, announces the end of the header list to the listener
+ // and returns true; else returns false.
+ bool EndDecodingBlock();
+
+ // Was an error detected?
+ bool error_detected();
+
+ // Returns the estimate of dynamically allocated memory in bytes.
+ size_t EstimateMemoryUsage() const;
+
+ private:
+ friend class test::HpackDecoderPeer;
+
+ // Reports an error to the listener IF this is the first error detected.
+ void ReportError(Http2StringPiece error_message);
+
+ // The decompressor state, as defined by HPACK (i.e. the static and dynamic
+ // tables).
+ HpackDecoderState decoder_state_;
+
+ // Assembles the various parts of a header entry into whole entries.
+ HpackWholeEntryBuffer entry_buffer_;
+
+ // The decoder of HPACK blocks into entry parts, passed to entry_buffer_.
+ HpackBlockDecoder block_decoder_;
+
+ // Has an error been detected?
+ bool error_detected_;
+};
+
+} // namespace http2
+
+#endif // QUICHE_HTTP2_HPACK_DECODER_HPACK_DECODER_H_
diff --git a/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_listener.cc b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_listener.cc
new file mode 100644
index 00000000000..8afa8aad316
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_listener.cc
@@ -0,0 +1,30 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_listener.h"
+
+namespace http2 {
+
+HpackDecoderListener::HpackDecoderListener() = default;
+HpackDecoderListener::~HpackDecoderListener() = default;
+
+HpackDecoderNoOpListener::HpackDecoderNoOpListener() = default;
+HpackDecoderNoOpListener::~HpackDecoderNoOpListener() = default;
+
+void HpackDecoderNoOpListener::OnHeaderListStart() {}
+void HpackDecoderNoOpListener::OnHeader(HpackEntryType entry_type,
+ const HpackString& name,
+ const HpackString& value) {}
+void HpackDecoderNoOpListener::OnHeaderListEnd() {}
+void HpackDecoderNoOpListener::OnHeaderErrorDetected(
+ Http2StringPiece error_message) {}
+
+// static
+HpackDecoderNoOpListener* HpackDecoderNoOpListener::NoOpListener() {
+ static HpackDecoderNoOpListener* static_instance =
+ new HpackDecoderNoOpListener();
+ return static_instance;
+}
+
+} // namespace http2
diff --git a/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_listener.h b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_listener.h
new file mode 100644
index 00000000000..fa685916625
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_listener.h
@@ -0,0 +1,66 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Defines HpackDecoderListener, the base class of listeners for HTTP header
+// lists decoded from an HPACK block.
+
+#ifndef QUICHE_HTTP2_HPACK_DECODER_HPACK_DECODER_LISTENER_H_
+#define QUICHE_HTTP2_HPACK_DECODER_HPACK_DECODER_LISTENER_H_
+
+#include "net/third_party/quiche/src/http2/hpack/hpack_string.h"
+#include "net/third_party/quiche/src/http2/hpack/http2_hpack_constants.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_export.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string_piece.h"
+
+namespace http2 {
+
+class HTTP2_EXPORT_PRIVATE HpackDecoderListener {
+ public:
+ HpackDecoderListener();
+ virtual ~HpackDecoderListener();
+
+ // OnHeaderListStart is called at the start of decoding an HPACK block into
+ // an HTTP/2 header list. Will only be called once per block, even if it
+ // extends into CONTINUATION frames.
+ virtual void OnHeaderListStart() = 0;
+
+ // Called for each header name-value pair that is decoded, in the order they
+ // appear in the HPACK block. Multiple values for a given key will be emitted
+ // as multiple calls to OnHeader.
+ virtual void OnHeader(HpackEntryType entry_type,
+ const HpackString& name,
+ const HpackString& value) = 0;
+
+ // OnHeaderListEnd is called after successfully decoding an HPACK block into
+ // an HTTP/2 header list. Will only be called once per block, even if it
+ // extends into CONTINUATION frames.
+ virtual void OnHeaderListEnd() = 0;
+
+ // OnHeaderErrorDetected is called if an error is detected while decoding.
+ // error_message may be used in a GOAWAY frame as the Opaque Data.
+ virtual void OnHeaderErrorDetected(Http2StringPiece error_message) = 0;
+};
+
+// A no-op implementation of HpackDecoderListener, useful for ignoring
+// callbacks once an error is detected.
+class HTTP2_EXPORT_PRIVATE HpackDecoderNoOpListener
+ : public HpackDecoderListener {
+ public:
+ HpackDecoderNoOpListener();
+ ~HpackDecoderNoOpListener() override;
+
+ void OnHeaderListStart() override;
+ void OnHeader(HpackEntryType entry_type,
+ const HpackString& name,
+ const HpackString& value) override;
+ void OnHeaderListEnd() override;
+ void OnHeaderErrorDetected(Http2StringPiece error_message) override;
+
+ // Returns a listener that ignores all the calls.
+ static HpackDecoderNoOpListener* NoOpListener();
+};
+
+} // namespace http2
+
+#endif // QUICHE_HTTP2_HPACK_DECODER_HPACK_DECODER_LISTENER_H_
diff --git a/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_state.cc b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_state.cc
new file mode 100644
index 00000000000..2ddf421775d
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_state.cc
@@ -0,0 +1,218 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_state.h"
+
+#include "base/logging.h"
+#include "net/third_party/quiche/src/http2/hpack/hpack_string.h"
+#include "net/third_party/quiche/src/http2/http2_constants.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_macros.h"
+
+namespace http2 {
+namespace {
+
+HpackString ExtractHpackString(HpackDecoderStringBuffer* string_buffer) {
+ if (string_buffer->IsBuffered()) {
+ return HpackString(string_buffer->ReleaseString());
+ } else {
+ auto result = HpackString(string_buffer->str());
+ string_buffer->Reset();
+ return result;
+ }
+}
+
+} // namespace
+
+HpackDecoderState::HpackDecoderState(HpackDecoderListener* listener)
+ : listener_(HTTP2_DIE_IF_NULL(listener)),
+ final_header_table_size_(Http2SettingsInfo::DefaultHeaderTableSize()),
+ lowest_header_table_size_(final_header_table_size_),
+ require_dynamic_table_size_update_(false),
+ allow_dynamic_table_size_update_(true),
+ saw_dynamic_table_size_update_(false),
+ error_detected_(false) {}
+HpackDecoderState::~HpackDecoderState() = default;
+
+void HpackDecoderState::set_tables_debug_listener(
+ HpackDecoderTablesDebugListener* debug_listener) {
+ decoder_tables_.set_debug_listener(debug_listener);
+}
+
+void HpackDecoderState::ApplyHeaderTableSizeSetting(
+ uint32_t header_table_size) {
+ DVLOG(2) << "HpackDecoderState::ApplyHeaderTableSizeSetting("
+ << header_table_size << ")";
+ DCHECK_LE(lowest_header_table_size_, final_header_table_size_);
+ if (header_table_size < lowest_header_table_size_) {
+ lowest_header_table_size_ = header_table_size;
+ }
+ final_header_table_size_ = header_table_size;
+ DVLOG(2) << "low water mark: " << lowest_header_table_size_;
+ DVLOG(2) << "final limit: " << final_header_table_size_;
+}
+
+// Called to notify this object that we're starting to decode an HPACK block
+// (e.g. a HEADERS or PUSH_PROMISE frame's header has been decoded).
+void HpackDecoderState::OnHeaderBlockStart() {
+ DVLOG(2) << "HpackDecoderState::OnHeaderBlockStart";
+ // This instance can't be reused after an error has been detected, as we must
+ // assume that the encoder and decoder compression states are no longer
+ // synchronized.
+ DCHECK(!error_detected_);
+ DCHECK_LE(lowest_header_table_size_, final_header_table_size_);
+ allow_dynamic_table_size_update_ = true;
+ saw_dynamic_table_size_update_ = false;
+ // If the peer has acknowledged a HEADER_TABLE_SIZE smaller than that which
+ // its HPACK encoder has been using, then the next HPACK block it sends MUST
+ // start with a Dynamic Table Size Update entry that is at least as low as
+ // lowest_header_table_size_. That may be followed by another as great as
+ // final_header_table_size_, if those are different.
+ require_dynamic_table_size_update_ =
+ (lowest_header_table_size_ <
+ decoder_tables_.current_header_table_size() ||
+ final_header_table_size_ < decoder_tables_.header_table_size_limit());
+ DVLOG(2) << "HpackDecoderState::OnHeaderListStart "
+ << "require_dynamic_table_size_update_="
+ << require_dynamic_table_size_update_;
+ listener_->OnHeaderListStart();
+}
+
+void HpackDecoderState::OnIndexedHeader(size_t index) {
+ DVLOG(2) << "HpackDecoderState::OnIndexedHeader: " << index;
+ if (error_detected_) {
+ return;
+ }
+ if (require_dynamic_table_size_update_) {
+ ReportError("Missing dynamic table size update.");
+ return;
+ }
+ allow_dynamic_table_size_update_ = false;
+ const HpackStringPair* entry = decoder_tables_.Lookup(index);
+ if (entry != nullptr) {
+ listener_->OnHeader(HpackEntryType::kIndexedHeader, entry->name,
+ entry->value);
+ } else {
+ ReportError("Invalid index.");
+ }
+}
+
+void HpackDecoderState::OnNameIndexAndLiteralValue(
+ HpackEntryType entry_type,
+ size_t name_index,
+ HpackDecoderStringBuffer* value_buffer) {
+ DVLOG(2) << "HpackDecoderState::OnNameIndexAndLiteralValue " << entry_type
+ << ", " << name_index << ", " << value_buffer->str();
+ if (error_detected_) {
+ return;
+ }
+ if (require_dynamic_table_size_update_) {
+ ReportError("Missing dynamic table size update.");
+ return;
+ }
+ allow_dynamic_table_size_update_ = false;
+ const HpackStringPair* entry = decoder_tables_.Lookup(name_index);
+ if (entry != nullptr) {
+ HpackString value(ExtractHpackString(value_buffer));
+ listener_->OnHeader(entry_type, entry->name, value);
+ if (entry_type == HpackEntryType::kIndexedLiteralHeader) {
+ decoder_tables_.Insert(entry->name, value);
+ }
+ } else {
+ ReportError("Invalid name index.");
+ }
+}
+
+void HpackDecoderState::OnLiteralNameAndValue(
+ HpackEntryType entry_type,
+ HpackDecoderStringBuffer* name_buffer,
+ HpackDecoderStringBuffer* value_buffer) {
+ DVLOG(2) << "HpackDecoderState::OnLiteralNameAndValue " << entry_type << ", "
+ << name_buffer->str() << ", " << value_buffer->str();
+ if (error_detected_) {
+ return;
+ }
+ if (require_dynamic_table_size_update_) {
+ ReportError("Missing dynamic table size update.");
+ return;
+ }
+ allow_dynamic_table_size_update_ = false;
+ HpackString name(ExtractHpackString(name_buffer));
+ HpackString value(ExtractHpackString(value_buffer));
+ listener_->OnHeader(entry_type, name, value);
+ if (entry_type == HpackEntryType::kIndexedLiteralHeader) {
+ decoder_tables_.Insert(name, value);
+ }
+}
+
+void HpackDecoderState::OnDynamicTableSizeUpdate(size_t size_limit) {
+ DVLOG(2) << "HpackDecoderState::OnDynamicTableSizeUpdate " << size_limit
+ << ", required="
+ << (require_dynamic_table_size_update_ ? "true" : "false")
+ << ", allowed="
+ << (allow_dynamic_table_size_update_ ? "true" : "false");
+ if (error_detected_) {
+ return;
+ }
+ DCHECK_LE(lowest_header_table_size_, final_header_table_size_);
+ if (!allow_dynamic_table_size_update_) {
+ // At most two dynamic table size updates allowed at the start, and not
+ // after a header.
+ ReportError("Dynamic table size update not allowed.");
+ return;
+ }
+ if (require_dynamic_table_size_update_) {
+ // The new size must not be greater than the low water mark.
+ if (size_limit > lowest_header_table_size_) {
+ ReportError("Initial dynamic table size update is above low water mark.");
+ return;
+ }
+ require_dynamic_table_size_update_ = false;
+ } else if (size_limit > final_header_table_size_) {
+ // The new size must not be greater than the final max header table size
+ // that the peer acknowledged.
+ ReportError("Dynamic table size update is above acknowledged setting.");
+ return;
+ }
+ decoder_tables_.DynamicTableSizeUpdate(size_limit);
+ if (saw_dynamic_table_size_update_) {
+ allow_dynamic_table_size_update_ = false;
+ } else {
+ saw_dynamic_table_size_update_ = true;
+ }
+ // We no longer need to keep an eye out for a lower header table size.
+ lowest_header_table_size_ = final_header_table_size_;
+}
+
+void HpackDecoderState::OnHpackDecodeError(Http2StringPiece error_message) {
+ DVLOG(2) << "HpackDecoderState::OnHpackDecodeError " << error_message;
+ if (!error_detected_) {
+ ReportError(error_message);
+ }
+}
+
+void HpackDecoderState::OnHeaderBlockEnd() {
+ DVLOG(2) << "HpackDecoderState::OnHeaderBlockEnd";
+ if (error_detected_) {
+ return;
+ }
+ if (require_dynamic_table_size_update_) {
+ // Apparently the HPACK block was empty, but we needed it to contain at
+ // least 1 dynamic table size update.
+ ReportError("Missing dynamic table size update.");
+ } else {
+ listener_->OnHeaderListEnd();
+ }
+}
+
+void HpackDecoderState::ReportError(Http2StringPiece error_message) {
+ DVLOG(2) << "HpackDecoderState::ReportError is new="
+ << (!error_detected_ ? "true" : "false")
+ << ", error_message: " << error_message;
+ if (!error_detected_) {
+ listener_->OnHeaderErrorDetected(error_message);
+ error_detected_ = true;
+ }
+}
+
+} // namespace http2
diff --git a/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_state.h b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_state.h
new file mode 100644
index 00000000000..fd341793539
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_state.h
@@ -0,0 +1,129 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// HpackDecoderState maintains the HPACK decompressor state; i.e. updates the
+// HPACK dynamic table according to RFC 7541 as the entries in an HPACK block
+// are decoded, and reads from the static and dynamic tables in order to build
+// complete header entries. Calls an HpackDecoderListener with the completely
+// decoded headers (i.e. after resolving table indices into names or values),
+// thus translating the decoded HPACK entries into HTTP/2 headers.
+
+#ifndef QUICHE_HTTP2_HPACK_DECODER_HPACK_DECODER_STATE_H_
+#define QUICHE_HTTP2_HPACK_DECODER_HPACK_DECODER_STATE_H_
+
+#include <stddef.h>
+
+#include <cstdint>
+
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_listener.h"
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_string_buffer.h"
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_tables.h"
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_whole_entry_listener.h"
+#include "net/third_party/quiche/src/http2/hpack/http2_hpack_constants.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_export.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string_piece.h"
+
+namespace http2 {
+namespace test {
+class HpackDecoderStatePeer;
+} // namespace test
+
+class HTTP2_EXPORT_PRIVATE HpackDecoderState : public HpackWholeEntryListener {
+ public:
+ explicit HpackDecoderState(HpackDecoderListener* listener);
+ ~HpackDecoderState() override;
+
+ HpackDecoderState(const HpackDecoderState&) = delete;
+ HpackDecoderState& operator=(const HpackDecoderState&) = delete;
+
+ // Set the listener to be notified when a whole entry has been decoded,
+ // including resolving name or name and value references.
+ // The listener may be changed at any time.
+ HpackDecoderListener* listener() const { return listener_; }
+
+ // Set listener to be notified of insertions into the HPACK dynamic table,
+ // and uses of those entries.
+ void set_tables_debug_listener(
+ HpackDecoderTablesDebugListener* debug_listener);
+
+ // ApplyHeaderTableSizeSetting notifies this object that this endpoint has
+ // received a SETTINGS ACK frame acknowledging an earlier SETTINGS frame from
+ // this endpoint specifying a new value for SETTINGS_HEADER_TABLE_SIZE (the
+ // maximum size of the dynamic table that this endpoint will use to decode
+ // HPACK blocks).
+ // Because a SETTINGS frame can contain SETTINGS_HEADER_TABLE_SIZE values,
+ // the caller must keep track of those multiple changes, and make
+ // corresponding calls to this method. In particular, a call must be made
+ // with the lowest value acknowledged by the peer, and a call must be made
+ // with the final value acknowledged, in that order; additional calls may
+ // be made if additional values were sent. These calls must be made between
+ // decoding the SETTINGS ACK, and before the next HPACK block is decoded.
+ void ApplyHeaderTableSizeSetting(uint32_t max_header_table_size);
+
+ // OnHeaderBlockStart notifies this object that we're starting to decode the
+ // HPACK payload of a HEADERS or PUSH_PROMISE frame.
+ void OnHeaderBlockStart();
+
+ // Implement the HpackWholeEntryListener methods, each of which notifies this
+ // object when an entire entry has been decoded.
+ void OnIndexedHeader(size_t index) override;
+ void OnNameIndexAndLiteralValue(
+ HpackEntryType entry_type,
+ size_t name_index,
+ HpackDecoderStringBuffer* value_buffer) override;
+ void OnLiteralNameAndValue(HpackEntryType entry_type,
+ HpackDecoderStringBuffer* name_buffer,
+ HpackDecoderStringBuffer* value_buffer) override;
+ void OnDynamicTableSizeUpdate(size_t size) override;
+ void OnHpackDecodeError(Http2StringPiece error_message) override;
+
+ // OnHeaderBlockEnd notifies this object that an entire HPACK block has been
+ // decoded, which might have extended into CONTINUATION blocks.
+ void OnHeaderBlockEnd();
+
+ // Was an error detected? After an error has been detected and reported,
+ // no further callbacks will be made to the listener.
+ bool error_detected() const { return error_detected_; }
+
+ const HpackDecoderTables& decoder_tables_for_test() const {
+ return decoder_tables_;
+ }
+
+ private:
+ friend class test::HpackDecoderStatePeer;
+
+ // Reports an error to the listener IF this is the first error detected.
+ void ReportError(Http2StringPiece error_message);
+
+ // The static and dynamic HPACK tables.
+ HpackDecoderTables decoder_tables_;
+
+ // The listener to be notified of headers, the start and end of header
+ // lists, and of errors.
+ HpackDecoderListener* listener_;
+
+ // The most recent HEADER_TABLE_SIZE setting acknowledged by the peer.
+ uint32_t final_header_table_size_;
+
+ // The lowest HEADER_TABLE_SIZE setting acknowledged by the peer; valid until
+ // the next HPACK block is decoded.
+ // TODO(jamessynge): Test raising the HEADER_TABLE_SIZE.
+ uint32_t lowest_header_table_size_;
+
+ // Must the next (first) HPACK entry be a dynamic table size update?
+ bool require_dynamic_table_size_update_;
+
+ // May the next (first or second) HPACK entry be a dynamic table size update?
+ bool allow_dynamic_table_size_update_;
+
+ // Have we already seen a dynamic table size update in this HPACK block?
+ bool saw_dynamic_table_size_update_;
+
+ // Has an error already been detected and reported to the listener?
+ bool error_detected_;
+};
+
+} // namespace http2
+
+#endif // QUICHE_HTTP2_HPACK_DECODER_HPACK_DECODER_STATE_H_
diff --git a/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_state_test.cc b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_state_test.cc
new file mode 100644
index 00000000000..115827e1912
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_state_test.cc
@@ -0,0 +1,539 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_state.h"
+
+// Tests of HpackDecoderState.
+
+#include <utility>
+#include <vector>
+
+#include "base/logging.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/http2/hpack/hpack_string.h"
+#include "net/third_party/quiche/src/http2/hpack/http2_hpack_constants.h"
+#include "net/third_party/quiche/src/http2/http2_constants.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string_piece.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_test_helpers.h"
+
+using ::testing::AssertionResult;
+using ::testing::AssertionSuccess;
+using ::testing::Eq;
+using ::testing::HasSubstr;
+using ::testing::Mock;
+using ::testing::StrictMock;
+
+namespace http2 {
+namespace test {
+class HpackDecoderStatePeer {
+ public:
+ static HpackDecoderTables* GetDecoderTables(HpackDecoderState* state) {
+ return &state->decoder_tables_;
+ }
+};
+
+namespace {
+
+class MockHpackDecoderListener : public HpackDecoderListener {
+ public:
+ MOCK_METHOD0(OnHeaderListStart, void());
+ MOCK_METHOD3(OnHeader,
+ void(HpackEntryType entry_type,
+ const HpackString& name,
+ const HpackString& value));
+ MOCK_METHOD0(OnHeaderListEnd, void());
+ MOCK_METHOD1(OnHeaderErrorDetected, void(Http2StringPiece error_message));
+};
+
+enum StringBacking { STATIC, UNBUFFERED, BUFFERED };
+
+class HpackDecoderStateTest : public ::testing::Test {
+ protected:
+ HpackDecoderStateTest() : decoder_state_(&listener_) {}
+
+ HpackDecoderTables* GetDecoderTables() {
+ return HpackDecoderStatePeer::GetDecoderTables(&decoder_state_);
+ }
+
+ const HpackStringPair* Lookup(size_t index) {
+ return GetDecoderTables()->Lookup(index);
+ }
+
+ size_t current_header_table_size() {
+ return GetDecoderTables()->current_header_table_size();
+ }
+
+ size_t header_table_size_limit() {
+ return GetDecoderTables()->header_table_size_limit();
+ }
+
+ void set_header_table_size_limit(size_t size) {
+ GetDecoderTables()->DynamicTableSizeUpdate(size);
+ }
+
+ void SetStringBuffer(const char* s,
+ StringBacking backing,
+ HpackDecoderStringBuffer* string_buffer) {
+ switch (backing) {
+ case STATIC:
+ string_buffer->Set(s, true);
+ break;
+ case UNBUFFERED:
+ string_buffer->Set(s, false);
+ break;
+ case BUFFERED:
+ string_buffer->Set(s, false);
+ string_buffer->BufferStringIfUnbuffered();
+ break;
+ }
+ }
+
+ void SetName(const char* s, StringBacking backing) {
+ SetStringBuffer(s, backing, &name_buffer_);
+ }
+
+ void SetValue(const char* s, StringBacking backing) {
+ SetStringBuffer(s, backing, &value_buffer_);
+ }
+
+ void SendStartAndVerifyCallback() {
+ EXPECT_CALL(listener_, OnHeaderListStart());
+ decoder_state_.OnHeaderBlockStart();
+ Mock::VerifyAndClearExpectations(&listener_);
+ }
+
+ void SendSizeUpdate(size_t size) {
+ decoder_state_.OnDynamicTableSizeUpdate(size);
+ Mock::VerifyAndClearExpectations(&listener_);
+ }
+
+ void SendIndexAndVerifyCallback(size_t index,
+ HpackEntryType expected_type,
+ const char* expected_name,
+ const char* expected_value) {
+ EXPECT_CALL(listener_,
+ OnHeader(expected_type, Eq(expected_name), Eq(expected_value)));
+ decoder_state_.OnIndexedHeader(index);
+ Mock::VerifyAndClearExpectations(&listener_);
+ }
+
+ void SendValueAndVerifyCallback(size_t name_index,
+ HpackEntryType entry_type,
+ const char* name,
+ const char* value,
+ StringBacking value_backing) {
+ SetValue(value, value_backing);
+ EXPECT_CALL(listener_, OnHeader(entry_type, Eq(name), Eq(value)));
+ decoder_state_.OnNameIndexAndLiteralValue(entry_type, name_index,
+ &value_buffer_);
+ Mock::VerifyAndClearExpectations(&listener_);
+ }
+
+ void SendNameAndValueAndVerifyCallback(HpackEntryType entry_type,
+ const char* name,
+ StringBacking name_backing,
+ const char* value,
+ StringBacking value_backing) {
+ SetName(name, name_backing);
+ SetValue(value, value_backing);
+ EXPECT_CALL(listener_, OnHeader(entry_type, Eq(name), Eq(value)));
+ decoder_state_.OnLiteralNameAndValue(entry_type, &name_buffer_,
+ &value_buffer_);
+ Mock::VerifyAndClearExpectations(&listener_);
+ }
+
+ void SendEndAndVerifyCallback() {
+ EXPECT_CALL(listener_, OnHeaderListEnd());
+ decoder_state_.OnHeaderBlockEnd();
+ Mock::VerifyAndClearExpectations(&listener_);
+ }
+
+ // dynamic_index is one-based, because that is the way RFC 7541 shows it.
+ AssertionResult VerifyEntry(size_t dynamic_index,
+ const char* name,
+ const char* value) {
+ const HpackStringPair* entry =
+ Lookup(dynamic_index + kFirstDynamicTableIndex - 1);
+ VERIFY_NE(entry, nullptr);
+ VERIFY_EQ(entry->name.ToStringPiece(), name);
+ VERIFY_EQ(entry->value.ToStringPiece(), value);
+ return AssertionSuccess();
+ }
+ AssertionResult VerifyNoEntry(size_t dynamic_index) {
+ const HpackStringPair* entry =
+ Lookup(dynamic_index + kFirstDynamicTableIndex - 1);
+ VERIFY_EQ(entry, nullptr);
+ return AssertionSuccess();
+ }
+ AssertionResult VerifyDynamicTableContents(
+ const std::vector<std::pair<const char*, const char*>>& entries) {
+ size_t index = 1;
+ for (const auto& entry : entries) {
+ VERIFY_SUCCESS(VerifyEntry(index, entry.first, entry.second));
+ ++index;
+ }
+ VERIFY_SUCCESS(VerifyNoEntry(index));
+ return AssertionSuccess();
+ }
+
+ StrictMock<MockHpackDecoderListener> listener_;
+ HpackDecoderState decoder_state_;
+ HpackDecoderStringBuffer name_buffer_, value_buffer_;
+};
+
+// Test based on RFC 7541, section C.3: Request Examples without Huffman Coding.
+// This section shows several consecutive header lists, corresponding to HTTP
+// requests, on the same connection.
+TEST_F(HpackDecoderStateTest, C3_RequestExamples) {
+ // C.3.1 First Request
+ //
+ // Header list to encode:
+ //
+ // :method: GET
+ // :scheme: http
+ // :path: /
+ // :authority: www.example.com
+
+ SendStartAndVerifyCallback();
+ SendIndexAndVerifyCallback(2, HpackEntryType::kIndexedHeader, ":method",
+ "GET");
+ SendIndexAndVerifyCallback(6, HpackEntryType::kIndexedHeader, ":scheme",
+ "http");
+ SendIndexAndVerifyCallback(4, HpackEntryType::kIndexedHeader, ":path", "/");
+ SendValueAndVerifyCallback(1, HpackEntryType::kIndexedLiteralHeader,
+ ":authority", "www.example.com", UNBUFFERED);
+ SendEndAndVerifyCallback();
+
+ // Dynamic Table (after decoding):
+ //
+ // [ 1] (s = 57) :authority: www.example.com
+ // Table size: 57
+
+ ASSERT_TRUE(VerifyDynamicTableContents({{":authority", "www.example.com"}}));
+ ASSERT_EQ(57u, current_header_table_size());
+
+ // C.3.2 Second Request
+ //
+ // Header list to encode:
+ //
+ // :method: GET
+ // :scheme: http
+ // :path: /
+ // :authority: www.example.com
+ // cache-control: no-cache
+
+ SendStartAndVerifyCallback();
+ SendIndexAndVerifyCallback(2, HpackEntryType::kIndexedHeader, ":method",
+ "GET");
+ SendIndexAndVerifyCallback(6, HpackEntryType::kIndexedHeader, ":scheme",
+ "http");
+ SendIndexAndVerifyCallback(4, HpackEntryType::kIndexedHeader, ":path", "/");
+ SendIndexAndVerifyCallback(62, HpackEntryType::kIndexedHeader, ":authority",
+ "www.example.com");
+ SendValueAndVerifyCallback(24, HpackEntryType::kIndexedLiteralHeader,
+ "cache-control", "no-cache", UNBUFFERED);
+ SendEndAndVerifyCallback();
+
+ // Dynamic Table (after decoding):
+ //
+ // [ 1] (s = 53) cache-control: no-cache
+ // [ 2] (s = 57) :authority: www.example.com
+ // Table size: 110
+
+ ASSERT_TRUE(VerifyDynamicTableContents(
+ {{"cache-control", "no-cache"}, {":authority", "www.example.com"}}));
+ ASSERT_EQ(110u, current_header_table_size());
+
+ // C.3.3 Third Request
+ //
+ // Header list to encode:
+ //
+ // :method: GET
+ // :scheme: https
+ // :path: /index.html
+ // :authority: www.example.com
+ // custom-key: custom-value
+
+ SendStartAndVerifyCallback();
+ SendIndexAndVerifyCallback(2, HpackEntryType::kIndexedHeader, ":method",
+ "GET");
+ SendIndexAndVerifyCallback(7, HpackEntryType::kIndexedHeader, ":scheme",
+ "https");
+ SendIndexAndVerifyCallback(5, HpackEntryType::kIndexedHeader, ":path",
+ "/index.html");
+ SendIndexAndVerifyCallback(63, HpackEntryType::kIndexedHeader, ":authority",
+ "www.example.com");
+ SendNameAndValueAndVerifyCallback(HpackEntryType::kIndexedLiteralHeader,
+ "custom-key", UNBUFFERED, "custom-value",
+ UNBUFFERED);
+ SendEndAndVerifyCallback();
+
+ // Dynamic Table (after decoding):
+ //
+ // [ 1] (s = 54) custom-key: custom-value
+ // [ 2] (s = 53) cache-control: no-cache
+ // [ 3] (s = 57) :authority: www.example.com
+ // Table size: 164
+
+ ASSERT_TRUE(VerifyDynamicTableContents({{"custom-key", "custom-value"},
+ {"cache-control", "no-cache"},
+ {":authority", "www.example.com"}}));
+ ASSERT_EQ(164u, current_header_table_size());
+}
+
+// Test based on RFC 7541, section C.5: Response Examples without Huffman
+// Coding. This section shows several consecutive header lists, corresponding
+// to HTTP responses, on the same connection. The HTTP/2 setting parameter
+// SETTINGS_HEADER_TABLE_SIZE is set to the value of 256 octets, causing
+// some evictions to occur.
+TEST_F(HpackDecoderStateTest, C5_ResponseExamples) {
+ set_header_table_size_limit(256);
+
+ // C.5.1 First Response
+ //
+ // Header list to encode:
+ //
+ // :status: 302
+ // cache-control: private
+ // date: Mon, 21 Oct 2013 20:13:21 GMT
+ // location: https://www.example.com
+
+ SendStartAndVerifyCallback();
+ SendValueAndVerifyCallback(8, HpackEntryType::kIndexedLiteralHeader,
+ ":status", "302", BUFFERED);
+ SendValueAndVerifyCallback(24, HpackEntryType::kIndexedLiteralHeader,
+ "cache-control", "private", UNBUFFERED);
+ SendValueAndVerifyCallback(33, HpackEntryType::kIndexedLiteralHeader, "date",
+ "Mon, 21 Oct 2013 20:13:21 GMT", UNBUFFERED);
+ SendValueAndVerifyCallback(46, HpackEntryType::kIndexedLiteralHeader,
+ "location", "https://www.example.com", UNBUFFERED);
+ SendEndAndVerifyCallback();
+
+ // Dynamic Table (after decoding):
+ //
+ // [ 1] (s = 63) location: https://www.example.com
+ // [ 2] (s = 65) date: Mon, 21 Oct 2013 20:13:21 GMT
+ // [ 3] (s = 52) cache-control: private
+ // [ 4] (s = 42) :status: 302
+ // Table size: 222
+
+ ASSERT_TRUE(
+ VerifyDynamicTableContents({{"location", "https://www.example.com"},
+ {"date", "Mon, 21 Oct 2013 20:13:21 GMT"},
+ {"cache-control", "private"},
+ {":status", "302"}}));
+ ASSERT_EQ(222u, current_header_table_size());
+
+ // C.5.2 Second Response
+ //
+ // The (":status", "302") header field is evicted from the dynamic table to
+ // free space to allow adding the (":status", "307") header field.
+ //
+ // Header list to encode:
+ //
+ // :status: 307
+ // cache-control: private
+ // date: Mon, 21 Oct 2013 20:13:21 GMT
+ // location: https://www.example.com
+
+ SendStartAndVerifyCallback();
+ SendValueAndVerifyCallback(8, HpackEntryType::kIndexedLiteralHeader,
+ ":status", "307", BUFFERED);
+ SendIndexAndVerifyCallback(65, HpackEntryType::kIndexedHeader,
+ "cache-control", "private");
+ SendIndexAndVerifyCallback(64, HpackEntryType::kIndexedHeader, "date",
+ "Mon, 21 Oct 2013 20:13:21 GMT");
+ SendIndexAndVerifyCallback(63, HpackEntryType::kIndexedHeader, "location",
+ "https://www.example.com");
+ SendEndAndVerifyCallback();
+
+ // Dynamic Table (after decoding):
+ //
+ // [ 1] (s = 42) :status: 307
+ // [ 2] (s = 63) location: https://www.example.com
+ // [ 3] (s = 65) date: Mon, 21 Oct 2013 20:13:21 GMT
+ // [ 4] (s = 52) cache-control: private
+ // Table size: 222
+
+ ASSERT_TRUE(
+ VerifyDynamicTableContents({{":status", "307"},
+ {"location", "https://www.example.com"},
+ {"date", "Mon, 21 Oct 2013 20:13:21 GMT"},
+ {"cache-control", "private"}}));
+ ASSERT_EQ(222u, current_header_table_size());
+
+ // C.5.3 Third Response
+ //
+ // Several header fields are evicted from the dynamic table during the
+ // processing of this header list.
+ //
+ // Header list to encode:
+ //
+ // :status: 200
+ // cache-control: private
+ // date: Mon, 21 Oct 2013 20:13:22 GMT
+ // location: https://www.example.com
+ // content-encoding: gzip
+ // set-cookie: foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1
+
+ SendStartAndVerifyCallback();
+ SendIndexAndVerifyCallback(8, HpackEntryType::kIndexedHeader, ":status",
+ "200");
+ SendIndexAndVerifyCallback(65, HpackEntryType::kIndexedHeader,
+ "cache-control", "private");
+ SendValueAndVerifyCallback(33, HpackEntryType::kIndexedLiteralHeader, "date",
+ "Mon, 21 Oct 2013 20:13:22 GMT", BUFFERED);
+ SendIndexAndVerifyCallback(64, HpackEntryType::kIndexedHeader, "location",
+ "https://www.example.com");
+ SendValueAndVerifyCallback(26, HpackEntryType::kIndexedLiteralHeader,
+ "content-encoding", "gzip", UNBUFFERED);
+ SendValueAndVerifyCallback(
+ 55, HpackEntryType::kIndexedLiteralHeader, "set-cookie",
+ "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1", BUFFERED);
+ SendEndAndVerifyCallback();
+
+ // Dynamic Table (after decoding):
+ //
+ // [ 1] (s = 98) set-cookie: foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU;
+ // max-age=3600; version=1
+ // [ 2] (s = 52) content-encoding: gzip
+ // [ 3] (s = 65) date: Mon, 21 Oct 2013 20:13:22 GMT
+ // Table size: 215
+
+ ASSERT_TRUE(VerifyDynamicTableContents(
+ {{"set-cookie",
+ "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1"},
+ {"content-encoding", "gzip"},
+ {"date", "Mon, 21 Oct 2013 20:13:22 GMT"}}));
+ ASSERT_EQ(215u, current_header_table_size());
+}
+
+// Confirm that the table size can be changed, but at most twice.
+TEST_F(HpackDecoderStateTest, OptionalTableSizeChanges) {
+ SendStartAndVerifyCallback();
+ EXPECT_EQ(Http2SettingsInfo::DefaultHeaderTableSize(),
+ header_table_size_limit());
+ SendSizeUpdate(1024);
+ EXPECT_EQ(1024u, header_table_size_limit());
+ SendSizeUpdate(0);
+ EXPECT_EQ(0u, header_table_size_limit());
+
+ // Three updates aren't allowed.
+ EXPECT_CALL(listener_,
+ OnHeaderErrorDetected(HasSubstr("size update not allowed")));
+ SendSizeUpdate(0);
+}
+
+// Confirm that required size updates are indeed required before headers.
+TEST_F(HpackDecoderStateTest, RequiredTableSizeChangeBeforeHeader) {
+ decoder_state_.ApplyHeaderTableSizeSetting(1024);
+ decoder_state_.ApplyHeaderTableSizeSetting(2048);
+
+ // First provide the required update, and an allowed second update.
+ SendStartAndVerifyCallback();
+ EXPECT_EQ(Http2SettingsInfo::DefaultHeaderTableSize(),
+ header_table_size_limit());
+ SendSizeUpdate(1024);
+ EXPECT_EQ(1024u, header_table_size_limit());
+ SendSizeUpdate(1500);
+ EXPECT_EQ(1500u, header_table_size_limit());
+ SendEndAndVerifyCallback();
+
+ // Another HPACK block, but this time missing the required size update.
+ decoder_state_.ApplyHeaderTableSizeSetting(1024);
+ SendStartAndVerifyCallback();
+ EXPECT_CALL(listener_, OnHeaderErrorDetected(
+ HasSubstr("Missing dynamic table size update")));
+ decoder_state_.OnIndexedHeader(1);
+
+ // Further decoded entries are ignored.
+ decoder_state_.OnIndexedHeader(1);
+ decoder_state_.OnDynamicTableSizeUpdate(1);
+ SetValue("value", UNBUFFERED);
+ decoder_state_.OnNameIndexAndLiteralValue(
+ HpackEntryType::kIndexedLiteralHeader, 4, &value_buffer_);
+ SetName("name", UNBUFFERED);
+ decoder_state_.OnLiteralNameAndValue(HpackEntryType::kIndexedLiteralHeader,
+ &name_buffer_, &value_buffer_);
+ decoder_state_.OnHeaderBlockEnd();
+ decoder_state_.OnHpackDecodeError("NOT FORWARDED");
+}
+
+// Confirm that required size updates are validated.
+TEST_F(HpackDecoderStateTest, InvalidRequiredSizeUpdate) {
+ // Require a size update, but provide one that isn't small enough.
+ decoder_state_.ApplyHeaderTableSizeSetting(1024);
+ SendStartAndVerifyCallback();
+ EXPECT_EQ(Http2SettingsInfo::DefaultHeaderTableSize(),
+ header_table_size_limit());
+ EXPECT_CALL(listener_,
+ OnHeaderErrorDetected(HasSubstr("above low water mark")));
+ SendSizeUpdate(2048);
+}
+
+// Confirm that required size updates are indeed required before the end.
+TEST_F(HpackDecoderStateTest, RequiredTableSizeChangeBeforeEnd) {
+ decoder_state_.ApplyHeaderTableSizeSetting(1024);
+ SendStartAndVerifyCallback();
+ EXPECT_CALL(listener_, OnHeaderErrorDetected(
+ HasSubstr("Missing dynamic table size update")));
+ decoder_state_.OnHeaderBlockEnd();
+}
+
+// Confirm that optional size updates are validated.
+TEST_F(HpackDecoderStateTest, InvalidOptionalSizeUpdate) {
+ // Require a size update, but provide one that isn't small enough.
+ SendStartAndVerifyCallback();
+ EXPECT_EQ(Http2SettingsInfo::DefaultHeaderTableSize(),
+ header_table_size_limit());
+ EXPECT_CALL(listener_,
+ OnHeaderErrorDetected(HasSubstr("size update is above")));
+ SendSizeUpdate(Http2SettingsInfo::DefaultHeaderTableSize() + 1);
+}
+
+TEST_F(HpackDecoderStateTest, InvalidStaticIndex) {
+ SendStartAndVerifyCallback();
+ EXPECT_CALL(listener_, OnHeaderErrorDetected(HasSubstr("Invalid index")));
+ decoder_state_.OnIndexedHeader(0);
+}
+
+TEST_F(HpackDecoderStateTest, InvalidDynamicIndex) {
+ SendStartAndVerifyCallback();
+ EXPECT_CALL(listener_, OnHeaderErrorDetected(HasSubstr("Invalid index")));
+ decoder_state_.OnIndexedHeader(kFirstDynamicTableIndex);
+}
+
+TEST_F(HpackDecoderStateTest, InvalidNameIndex) {
+ SendStartAndVerifyCallback();
+ EXPECT_CALL(listener_,
+ OnHeaderErrorDetected(HasSubstr("Invalid name index")));
+ SetValue("value", UNBUFFERED);
+ decoder_state_.OnNameIndexAndLiteralValue(
+ HpackEntryType::kIndexedLiteralHeader, kFirstDynamicTableIndex,
+ &value_buffer_);
+}
+
+TEST_F(HpackDecoderStateTest, ErrorsSuppressCallbacks) {
+ SendStartAndVerifyCallback();
+ EXPECT_CALL(listener_,
+ OnHeaderErrorDetected(Http2StringPiece("Huffman decode error.")));
+ decoder_state_.OnHpackDecodeError("Huffman decode error.");
+
+ // Further decoded entries are ignored.
+ decoder_state_.OnIndexedHeader(1);
+ decoder_state_.OnDynamicTableSizeUpdate(1);
+ SetValue("value", UNBUFFERED);
+ decoder_state_.OnNameIndexAndLiteralValue(
+ HpackEntryType::kIndexedLiteralHeader, 4, &value_buffer_);
+ SetName("name", UNBUFFERED);
+ decoder_state_.OnLiteralNameAndValue(HpackEntryType::kIndexedLiteralHeader,
+ &name_buffer_, &value_buffer_);
+ decoder_state_.OnHeaderBlockEnd();
+ decoder_state_.OnHpackDecodeError("NOT FORWARDED");
+}
+
+} // namespace
+} // namespace test
+} // namespace http2
diff --git a/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_string_buffer.cc b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_string_buffer.cc
new file mode 100644
index 00000000000..b20c37a8204
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_string_buffer.cc
@@ -0,0 +1,235 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_string_buffer.h"
+
+#include <utility>
+
+#include "base/logging.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_bug_tracker.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_estimate_memory_usage.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string.h"
+
+namespace http2 {
+
+std::ostream& operator<<(std::ostream& out,
+ const HpackDecoderStringBuffer::State v) {
+ switch (v) {
+ case HpackDecoderStringBuffer::State::RESET:
+ return out << "RESET";
+ case HpackDecoderStringBuffer::State::COLLECTING:
+ return out << "COLLECTING";
+ case HpackDecoderStringBuffer::State::COMPLETE:
+ return out << "COMPLETE";
+ }
+ // Since the value doesn't come over the wire, only a programming bug should
+ // result in reaching this point.
+ int unknown = static_cast<int>(v);
+ HTTP2_BUG << "Invalid HpackDecoderStringBuffer::State: " << unknown;
+ return out << "HpackDecoderStringBuffer::State(" << unknown << ")";
+}
+
+std::ostream& operator<<(std::ostream& out,
+ const HpackDecoderStringBuffer::Backing v) {
+ switch (v) {
+ case HpackDecoderStringBuffer::Backing::RESET:
+ return out << "RESET";
+ case HpackDecoderStringBuffer::Backing::UNBUFFERED:
+ return out << "UNBUFFERED";
+ case HpackDecoderStringBuffer::Backing::BUFFERED:
+ return out << "BUFFERED";
+ case HpackDecoderStringBuffer::Backing::STATIC:
+ return out << "STATIC";
+ }
+ // Since the value doesn't come over the wire, only a programming bug should
+ // result in reaching this point.
+ auto v2 = static_cast<int>(v);
+ HTTP2_BUG << "Invalid HpackDecoderStringBuffer::Backing: " << v2;
+ return out << "HpackDecoderStringBuffer::Backing(" << v2 << ")";
+}
+
+HpackDecoderStringBuffer::HpackDecoderStringBuffer()
+ : remaining_len_(0),
+ is_huffman_encoded_(false),
+ state_(State::RESET),
+ backing_(Backing::RESET) {}
+HpackDecoderStringBuffer::~HpackDecoderStringBuffer() = default;
+
+void HpackDecoderStringBuffer::Reset() {
+ DVLOG(3) << "HpackDecoderStringBuffer::Reset";
+ state_ = State::RESET;
+}
+
+void HpackDecoderStringBuffer::Set(Http2StringPiece value, bool is_static) {
+ DVLOG(2) << "HpackDecoderStringBuffer::Set";
+ DCHECK_EQ(state_, State::RESET);
+ value_ = value;
+ state_ = State::COMPLETE;
+ backing_ = is_static ? Backing::STATIC : Backing::UNBUFFERED;
+ // TODO(jamessynge): Determine which of these two fields must be set.
+ remaining_len_ = 0;
+ is_huffman_encoded_ = false;
+}
+
+void HpackDecoderStringBuffer::OnStart(bool huffman_encoded, size_t len) {
+ DVLOG(2) << "HpackDecoderStringBuffer::OnStart";
+ DCHECK_EQ(state_, State::RESET);
+
+ remaining_len_ = len;
+ is_huffman_encoded_ = huffman_encoded;
+ state_ = State::COLLECTING;
+
+ if (huffman_encoded) {
+ // We don't set, clear or use value_ for buffered strings until OnEnd.
+ decoder_.Reset();
+ buffer_.clear();
+ backing_ = Backing::BUFFERED;
+
+ // Reserve space in buffer_ for the uncompressed string, assuming the
+ // maximum expansion. The shortest Huffman codes in the RFC are 5 bits long,
+ // which then expand to 8 bits during decoding (i.e. each code is for one
+ // plain text octet, aka byte), so the maximum size is 60% longer than the
+ // encoded size.
+ len = len * 8 / 5;
+ if (buffer_.capacity() < len) {
+ buffer_.reserve(len);
+ }
+ } else {
+ // Assume for now that we won't need to use buffer_, so don't reserve space
+ // in it.
+ backing_ = Backing::RESET;
+ // OnData is not called for empty (zero length) strings, so make sure that
+ // value_ is cleared.
+ value_ = Http2StringPiece();
+ }
+}
+
+bool HpackDecoderStringBuffer::OnData(const char* data, size_t len) {
+ DVLOG(2) << "HpackDecoderStringBuffer::OnData state=" << state_
+ << ", backing=" << backing_;
+ DCHECK_EQ(state_, State::COLLECTING);
+ DCHECK_LE(len, remaining_len_);
+ remaining_len_ -= len;
+
+ if (is_huffman_encoded_) {
+ DCHECK_EQ(backing_, Backing::BUFFERED);
+ return decoder_.Decode(Http2StringPiece(data, len), &buffer_);
+ }
+
+ if (backing_ == Backing::RESET) {
+ // This is the first call to OnData. If data contains the entire string,
+ // don't copy the string. If we later find that the HPACK entry is split
+ // across input buffers, then we'll copy the string into buffer_.
+ if (remaining_len_ == 0) {
+ value_ = Http2StringPiece(data, len);
+ backing_ = Backing::UNBUFFERED;
+ return true;
+ }
+
+ // We need to buffer the string because it is split across input buffers.
+ // Reserve space in buffer_ for the entire string.
+ backing_ = Backing::BUFFERED;
+ buffer_.reserve(remaining_len_ + len);
+ buffer_.assign(data, len);
+ return true;
+ }
+
+ // This is not the first call to OnData for this string, so it should be
+ // buffered.
+ DCHECK_EQ(backing_, Backing::BUFFERED);
+
+ // Append to the current contents of the buffer.
+ buffer_.append(data, len);
+ return true;
+}
+
+bool HpackDecoderStringBuffer::OnEnd() {
+ DVLOG(2) << "HpackDecoderStringBuffer::OnEnd";
+ DCHECK_EQ(state_, State::COLLECTING);
+ DCHECK_EQ(0u, remaining_len_);
+
+ if (is_huffman_encoded_) {
+ DCHECK_EQ(backing_, Backing::BUFFERED);
+ // Did the Huffman encoding of the string end properly?
+ if (!decoder_.InputProperlyTerminated()) {
+ return false; // No, it didn't.
+ }
+ value_ = buffer_;
+ } else if (backing_ == Backing::BUFFERED) {
+ value_ = buffer_;
+ }
+ state_ = State::COMPLETE;
+ return true;
+}
+
+void HpackDecoderStringBuffer::BufferStringIfUnbuffered() {
+ DVLOG(3) << "HpackDecoderStringBuffer::BufferStringIfUnbuffered state="
+ << state_ << ", backing=" << backing_;
+ if (state_ != State::RESET && backing_ == Backing::UNBUFFERED) {
+ DVLOG(2) << "HpackDecoderStringBuffer buffering Http2String of length "
+ << value_.size();
+ buffer_.assign(value_.data(), value_.size());
+ if (state_ == State::COMPLETE) {
+ value_ = buffer_;
+ }
+ backing_ = Backing::BUFFERED;
+ }
+}
+
+bool HpackDecoderStringBuffer::IsBuffered() const {
+ DVLOG(3) << "HpackDecoderStringBuffer::IsBuffered";
+ return state_ != State::RESET && backing_ == Backing::BUFFERED;
+}
+
+size_t HpackDecoderStringBuffer::BufferedLength() const {
+ DVLOG(3) << "HpackDecoderStringBuffer::BufferedLength";
+ return IsBuffered() ? buffer_.size() : 0;
+}
+
+Http2StringPiece HpackDecoderStringBuffer::str() const {
+ DVLOG(3) << "HpackDecoderStringBuffer::str";
+ DCHECK_EQ(state_, State::COMPLETE);
+ return value_;
+}
+
+Http2String HpackDecoderStringBuffer::ReleaseString() {
+ DVLOG(3) << "HpackDecoderStringBuffer::ReleaseString";
+ DCHECK_EQ(state_, State::COMPLETE);
+ DCHECK_EQ(backing_, Backing::BUFFERED);
+ if (state_ == State::COMPLETE) {
+ state_ = State::RESET;
+ if (backing_ == Backing::BUFFERED) {
+ return std::move(buffer_);
+ } else {
+ return Http2String(value_);
+ }
+ }
+ return "";
+}
+
+void HpackDecoderStringBuffer::OutputDebugStringTo(std::ostream& out) const {
+ out << "{state=" << state_;
+ if (state_ != State::RESET) {
+ out << ", backing=" << backing_;
+ out << ", remaining_len=" << remaining_len_;
+ out << ", is_huffman_encoded=" << is_huffman_encoded_;
+ if (backing_ == Backing::BUFFERED) {
+ out << ", buffer: " << buffer_;
+ } else {
+ out << ", value: " << value_;
+ }
+ }
+ out << "}";
+}
+
+size_t HpackDecoderStringBuffer::EstimateMemoryUsage() const {
+ return Http2EstimateMemoryUsage(buffer_);
+}
+
+std::ostream& operator<<(std::ostream& out, const HpackDecoderStringBuffer& v) {
+ v.OutputDebugStringTo(out);
+ return out;
+}
+
+} // namespace http2
diff --git a/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_string_buffer.h b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_string_buffer.h
new file mode 100644
index 00000000000..8a810b2d9d3
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_string_buffer.h
@@ -0,0 +1,102 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_HTTP2_HPACK_DECODER_HPACK_DECODER_STRING_BUFFER_H_
+#define QUICHE_HTTP2_HPACK_DECODER_HPACK_DECODER_STRING_BUFFER_H_
+
+// HpackDecoderStringBuffer helps an HPACK decoder to avoid copies of a string
+// literal (name or value) except when necessary (e.g. when split across two
+// or more HPACK block fragments).
+
+#include <stddef.h>
+
+#include <ostream>
+
+#include "net/third_party/quiche/src/http2/hpack/huffman/hpack_huffman_decoder.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_export.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string_piece.h"
+
+namespace http2 {
+
+class HTTP2_EXPORT_PRIVATE HpackDecoderStringBuffer {
+ public:
+ enum class State : uint8_t { RESET, COLLECTING, COMPLETE };
+ enum class Backing : uint8_t { RESET, UNBUFFERED, BUFFERED, STATIC };
+
+ HpackDecoderStringBuffer();
+ ~HpackDecoderStringBuffer();
+
+ HpackDecoderStringBuffer(const HpackDecoderStringBuffer&) = delete;
+ HpackDecoderStringBuffer& operator=(const HpackDecoderStringBuffer&) = delete;
+
+ void Reset();
+ void Set(Http2StringPiece value, bool is_static);
+
+ // Note that for Huffman encoded strings the length of the string after
+ // decoding may be larger (expected), the same or even smaller; the latter
+ // are unlikely, but possible if the encoder makes odd choices.
+ void OnStart(bool huffman_encoded, size_t len);
+ bool OnData(const char* data, size_t len);
+ bool OnEnd();
+ void BufferStringIfUnbuffered();
+ bool IsBuffered() const;
+ size_t BufferedLength() const;
+
+ // Accessors for the completely collected string (i.e. Set or OnEnd has just
+ // been called, and no reset of the state has occurred).
+
+ // Returns a Http2StringPiece pointing to the backing store for the string,
+ // either the internal buffer or the original transport buffer (e.g. for a
+ // literal value that wasn't Huffman encoded, and that wasn't split across
+ // transport buffers).
+ Http2StringPiece str() const;
+
+ // Returns the completely collected string by value, using std::move in an
+ // effort to avoid unnecessary copies. ReleaseString() must not be called
+ // unless the string has been buffered (to avoid forcing a potentially
+ // unnecessary copy). ReleaseString() also resets the instance so that it can
+ // be used to collect another string.
+ Http2String ReleaseString();
+
+ State state_for_testing() const { return state_; }
+ Backing backing_for_testing() const { return backing_; }
+ void OutputDebugStringTo(std::ostream& out) const;
+
+ // Returns the estimate of dynamically allocated memory in bytes.
+ size_t EstimateMemoryUsage() const;
+
+ private:
+ // Storage for the string being buffered, if buffering is necessary
+ // (e.g. if Huffman encoded, buffer_ is storage for the decoded string).
+ Http2String buffer_;
+
+ // The Http2StringPiece to be returned by HpackDecoderStringBuffer::str(). If
+ // a string has been collected, but not buffered, value_ points to that
+ // string.
+ Http2StringPiece value_;
+
+ // The decoder to use if the string is Huffman encoded.
+ HpackHuffmanDecoder decoder_;
+
+ // Count of bytes not yet passed to OnData.
+ size_t remaining_len_;
+
+ // Is the HPACK string Huffman encoded?
+ bool is_huffman_encoded_;
+
+ // State of the string decoding process.
+ State state_;
+
+ // Where is the string stored?
+ Backing backing_;
+};
+
+HTTP2_EXPORT_PRIVATE std::ostream& operator<<(
+ std::ostream& out,
+ const HpackDecoderStringBuffer& v);
+
+} // namespace http2
+
+#endif // QUICHE_HTTP2_HPACK_DECODER_HPACK_DECODER_STRING_BUFFER_H_
diff --git a/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_string_buffer_test.cc b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_string_buffer_test.cc
new file mode 100644
index 00000000000..009b894fbe6
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_string_buffer_test.cc
@@ -0,0 +1,251 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_string_buffer.h"
+
+// Tests of HpackDecoderStringBuffer.
+
+#include <initializer_list>
+
+#include "base/logging.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string_utils.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_test_helpers.h"
+
+using ::testing::AssertionResult;
+using ::testing::AssertionSuccess;
+using ::testing::HasSubstr;
+
+namespace http2 {
+namespace test {
+namespace {
+
+class HpackDecoderStringBufferTest : public ::testing::Test {
+ protected:
+ typedef HpackDecoderStringBuffer::State State;
+ typedef HpackDecoderStringBuffer::Backing Backing;
+
+ State state() const { return buf_.state_for_testing(); }
+ Backing backing() const { return buf_.backing_for_testing(); }
+
+ // We want to know that LOG(x) << buf_ will work in production should that
+ // be needed, so we test that it outputs the expected values.
+ AssertionResult VerifyLogHasSubstrs(std::initializer_list<Http2String> strs) {
+ VLOG(1) << buf_;
+ std::ostringstream ss;
+ buf_.OutputDebugStringTo(ss);
+ Http2String dbg_str(ss.str());
+ for (const auto& expected : strs) {
+ VERIFY_THAT(dbg_str, HasSubstr(expected));
+ }
+ return AssertionSuccess();
+ }
+
+ HpackDecoderStringBuffer buf_;
+};
+
+TEST_F(HpackDecoderStringBufferTest, SetStatic) {
+ Http2StringPiece data("static string");
+
+ EXPECT_EQ(state(), State::RESET);
+ EXPECT_TRUE(VerifyLogHasSubstrs({"state=RESET"}));
+
+ buf_.Set(data, /*is_static*/ true);
+ LOG(INFO) << buf_;
+ EXPECT_EQ(state(), State::COMPLETE);
+ EXPECT_EQ(backing(), Backing::STATIC);
+ EXPECT_EQ(data, buf_.str());
+ EXPECT_EQ(data.data(), buf_.str().data());
+ EXPECT_TRUE(VerifyLogHasSubstrs(
+ {"state=COMPLETE", "backing=STATIC", "value: static string"}));
+
+ // The string is static, so BufferStringIfUnbuffered won't change anything.
+ buf_.BufferStringIfUnbuffered();
+ EXPECT_EQ(state(), State::COMPLETE);
+ EXPECT_EQ(backing(), Backing::STATIC);
+ EXPECT_EQ(data, buf_.str());
+ EXPECT_EQ(data.data(), buf_.str().data());
+ EXPECT_TRUE(VerifyLogHasSubstrs(
+ {"state=COMPLETE", "backing=STATIC", "value: static string"}));
+}
+
+TEST_F(HpackDecoderStringBufferTest, PlainWhole) {
+ Http2StringPiece data("some text.");
+
+ LOG(INFO) << buf_;
+ EXPECT_EQ(state(), State::RESET);
+
+ buf_.OnStart(/*huffman_encoded*/ false, data.size());
+ EXPECT_EQ(state(), State::COLLECTING);
+ EXPECT_EQ(backing(), Backing::RESET);
+ LOG(INFO) << buf_;
+
+ EXPECT_TRUE(buf_.OnData(data.data(), data.size()));
+ EXPECT_EQ(state(), State::COLLECTING);
+ EXPECT_EQ(backing(), Backing::UNBUFFERED);
+
+ EXPECT_TRUE(buf_.OnEnd());
+ EXPECT_EQ(state(), State::COMPLETE);
+ EXPECT_EQ(backing(), Backing::UNBUFFERED);
+ EXPECT_EQ(0u, buf_.BufferedLength());
+ EXPECT_TRUE(VerifyLogHasSubstrs(
+ {"state=COMPLETE", "backing=UNBUFFERED", "value: some text."}));
+
+ // We expect that the string buffer points to the passed in Http2StringPiece's
+ // backing store.
+ EXPECT_EQ(data.data(), buf_.str().data());
+
+ // Now force it to buffer the string, after which it will still have the same
+ // string value, but the backing store will be different.
+ buf_.BufferStringIfUnbuffered();
+ LOG(INFO) << buf_;
+ EXPECT_EQ(backing(), Backing::BUFFERED);
+ EXPECT_EQ(buf_.BufferedLength(), data.size());
+ EXPECT_EQ(data, buf_.str());
+ EXPECT_NE(data.data(), buf_.str().data());
+ EXPECT_TRUE(VerifyLogHasSubstrs(
+ {"state=COMPLETE", "backing=BUFFERED", "buffer: some text."}));
+}
+
+TEST_F(HpackDecoderStringBufferTest, PlainSplit) {
+ Http2StringPiece data("some text.");
+ Http2StringPiece part1 = data.substr(0, 1);
+ Http2StringPiece part2 = data.substr(1);
+
+ EXPECT_EQ(state(), State::RESET);
+ buf_.OnStart(/*huffman_encoded*/ false, data.size());
+ EXPECT_EQ(state(), State::COLLECTING);
+ EXPECT_EQ(backing(), Backing::RESET);
+
+ // OnData with only a part of the data, not the whole, so buf_ will buffer
+ // the data.
+ EXPECT_TRUE(buf_.OnData(part1.data(), part1.size()));
+ EXPECT_EQ(state(), State::COLLECTING);
+ EXPECT_EQ(backing(), Backing::BUFFERED);
+ EXPECT_EQ(buf_.BufferedLength(), part1.size());
+ LOG(INFO) << buf_;
+
+ EXPECT_TRUE(buf_.OnData(part2.data(), part2.size()));
+ EXPECT_EQ(state(), State::COLLECTING);
+ EXPECT_EQ(backing(), Backing::BUFFERED);
+ EXPECT_EQ(buf_.BufferedLength(), data.size());
+
+ EXPECT_TRUE(buf_.OnEnd());
+ EXPECT_EQ(state(), State::COMPLETE);
+ EXPECT_EQ(backing(), Backing::BUFFERED);
+ EXPECT_EQ(buf_.BufferedLength(), data.size());
+ LOG(INFO) << buf_;
+
+ Http2StringPiece buffered = buf_.str();
+ EXPECT_EQ(data, buffered);
+ EXPECT_NE(data.data(), buffered.data());
+
+ // The string is already buffered, so BufferStringIfUnbuffered should not make
+ // any change.
+ buf_.BufferStringIfUnbuffered();
+ EXPECT_EQ(backing(), Backing::BUFFERED);
+ EXPECT_EQ(buf_.BufferedLength(), data.size());
+ EXPECT_EQ(buffered, buf_.str());
+ EXPECT_EQ(buffered.data(), buf_.str().data());
+}
+
+TEST_F(HpackDecoderStringBufferTest, HuffmanWhole) {
+ Http2String encoded = Http2HexDecode("f1e3c2e5f23a6ba0ab90f4ff");
+ Http2StringPiece decoded("www.example.com");
+
+ EXPECT_EQ(state(), State::RESET);
+ buf_.OnStart(/*huffman_encoded*/ true, encoded.size());
+ EXPECT_EQ(state(), State::COLLECTING);
+
+ EXPECT_TRUE(buf_.OnData(encoded.data(), encoded.size()));
+ EXPECT_EQ(state(), State::COLLECTING);
+ EXPECT_EQ(backing(), Backing::BUFFERED);
+
+ EXPECT_TRUE(buf_.OnEnd());
+ EXPECT_EQ(state(), State::COMPLETE);
+ EXPECT_EQ(backing(), Backing::BUFFERED);
+ EXPECT_EQ(buf_.BufferedLength(), decoded.size());
+ EXPECT_EQ(decoded, buf_.str());
+ EXPECT_TRUE(VerifyLogHasSubstrs(
+ {"{state=COMPLETE", "backing=BUFFERED", "buffer: www.example.com}"}));
+
+ Http2String s = buf_.ReleaseString();
+ EXPECT_EQ(s, decoded);
+ EXPECT_EQ(state(), State::RESET);
+}
+
+TEST_F(HpackDecoderStringBufferTest, HuffmanSplit) {
+ Http2String encoded = Http2HexDecode("f1e3c2e5f23a6ba0ab90f4ff");
+ Http2String part1 = encoded.substr(0, 5);
+ Http2String part2 = encoded.substr(5);
+ Http2StringPiece decoded("www.example.com");
+
+ EXPECT_EQ(state(), State::RESET);
+ buf_.OnStart(/*huffman_encoded*/ true, encoded.size());
+ EXPECT_EQ(state(), State::COLLECTING);
+ EXPECT_EQ(backing(), Backing::BUFFERED);
+ EXPECT_EQ(0u, buf_.BufferedLength());
+ LOG(INFO) << buf_;
+
+ EXPECT_TRUE(buf_.OnData(part1.data(), part1.size()));
+ EXPECT_EQ(state(), State::COLLECTING);
+ EXPECT_EQ(backing(), Backing::BUFFERED);
+ EXPECT_GT(buf_.BufferedLength(), 0u);
+ EXPECT_LT(buf_.BufferedLength(), decoded.size());
+ LOG(INFO) << buf_;
+
+ EXPECT_TRUE(buf_.OnData(part2.data(), part2.size()));
+ EXPECT_EQ(state(), State::COLLECTING);
+ EXPECT_EQ(backing(), Backing::BUFFERED);
+ EXPECT_EQ(buf_.BufferedLength(), decoded.size());
+ LOG(INFO) << buf_;
+
+ EXPECT_TRUE(buf_.OnEnd());
+ EXPECT_EQ(state(), State::COMPLETE);
+ EXPECT_EQ(backing(), Backing::BUFFERED);
+ EXPECT_EQ(buf_.BufferedLength(), decoded.size());
+ EXPECT_EQ(decoded, buf_.str());
+ LOG(INFO) << buf_;
+
+ buf_.Reset();
+ EXPECT_EQ(state(), State::RESET);
+ LOG(INFO) << buf_;
+}
+
+TEST_F(HpackDecoderStringBufferTest, InvalidHuffmanOnData) {
+ // Explicitly encode the End-of-String symbol, a no-no.
+ Http2String encoded = Http2HexDecode("ffffffff");
+
+ buf_.OnStart(/*huffman_encoded*/ true, encoded.size());
+ EXPECT_EQ(state(), State::COLLECTING);
+
+ EXPECT_FALSE(buf_.OnData(encoded.data(), encoded.size()));
+ EXPECT_EQ(state(), State::COLLECTING);
+ EXPECT_EQ(backing(), Backing::BUFFERED);
+
+ LOG(INFO) << buf_;
+}
+
+TEST_F(HpackDecoderStringBufferTest, InvalidHuffmanOnEnd) {
+ // Last byte of string doesn't end with prefix of End-of-String symbol.
+ Http2String encoded = Http2HexDecode("00");
+
+ buf_.OnStart(/*huffman_encoded*/ true, encoded.size());
+ EXPECT_EQ(state(), State::COLLECTING);
+
+ EXPECT_TRUE(buf_.OnData(encoded.data(), encoded.size()));
+ EXPECT_EQ(state(), State::COLLECTING);
+ EXPECT_EQ(backing(), Backing::BUFFERED);
+
+ EXPECT_FALSE(buf_.OnEnd());
+ LOG(INFO) << buf_;
+}
+
+// TODO(jamessynge): Add tests for ReleaseString().
+
+} // namespace
+} // namespace test
+} // namespace http2
diff --git a/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_tables.cc b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_tables.cc
new file mode 100644
index 00000000000..c88648ff597
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_tables.cc
@@ -0,0 +1,153 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_tables.h"
+
+#include "base/logging.h"
+#include "net/third_party/quiche/src/http2/hpack/http2_hpack_constants.h"
+
+namespace http2 {
+namespace {
+
+std::vector<HpackStringPair>* MakeStaticTable() {
+ auto* ptr = new std::vector<HpackStringPair>();
+ ptr->reserve(kFirstDynamicTableIndex);
+ ptr->emplace_back("", "");
+
+#define STATIC_TABLE_ENTRY(name, value, index) \
+ DCHECK_EQ(ptr->size(), static_cast<size_t>(index)); \
+ ptr->emplace_back(name, value)
+
+#include "net/third_party/quiche/src/http2/hpack/hpack_static_table_entries.inc"
+
+#undef STATIC_TABLE_ENTRY
+
+ return ptr;
+}
+
+const std::vector<HpackStringPair>* GetStaticTable() {
+ static const std::vector<HpackStringPair>* const g_static_table =
+ MakeStaticTable();
+ return g_static_table;
+}
+
+} // namespace
+
+HpackDecoderTablesDebugListener::HpackDecoderTablesDebugListener() = default;
+HpackDecoderTablesDebugListener::~HpackDecoderTablesDebugListener() = default;
+
+HpackDecoderStaticTable::HpackDecoderStaticTable(
+ const std::vector<HpackStringPair>* table)
+ : table_(table) {}
+
+HpackDecoderStaticTable::HpackDecoderStaticTable() : table_(GetStaticTable()) {}
+
+const HpackStringPair* HpackDecoderStaticTable::Lookup(size_t index) const {
+ if (0 < index && index < kFirstDynamicTableIndex) {
+ return &((*table_)[index]);
+ }
+ return nullptr;
+}
+
+HpackDecoderDynamicTable::HpackDecoderTableEntry::HpackDecoderTableEntry(
+ const HpackString& name,
+ const HpackString& value)
+ : HpackStringPair(name, value) {}
+
+HpackDecoderDynamicTable::HpackDecoderDynamicTable()
+ : insert_count_(kFirstDynamicTableIndex - 1), debug_listener_(nullptr) {}
+HpackDecoderDynamicTable::~HpackDecoderDynamicTable() = default;
+
+void HpackDecoderDynamicTable::DynamicTableSizeUpdate(size_t size_limit) {
+ DVLOG(3) << "HpackDecoderDynamicTable::DynamicTableSizeUpdate " << size_limit;
+ EnsureSizeNoMoreThan(size_limit);
+ DCHECK_LE(current_size_, size_limit);
+ size_limit_ = size_limit;
+}
+
+// TODO(jamessynge): Check somewhere before here that names received from the
+// peer are valid (e.g. are lower-case, no whitespace, etc.).
+bool HpackDecoderDynamicTable::Insert(const HpackString& name,
+ const HpackString& value) {
+ HpackDecoderTableEntry entry(name, value);
+ size_t entry_size = entry.size();
+ DVLOG(2) << "InsertEntry of size=" << entry_size << "\n name: " << name
+ << "\n value: " << value;
+ if (entry_size > size_limit_) {
+ DVLOG(2) << "InsertEntry: entry larger than table, removing "
+ << table_.size() << " entries, of total size " << current_size_
+ << " bytes.";
+ table_.clear();
+ current_size_ = 0;
+ return false; // Not inserted because too large.
+ }
+ ++insert_count_;
+ if (debug_listener_ != nullptr) {
+ entry.time_added = debug_listener_->OnEntryInserted(entry, insert_count_);
+ DVLOG(2) << "OnEntryInserted returned time_added=" << entry.time_added
+ << " for insert_count_=" << insert_count_;
+ }
+ size_t insert_limit = size_limit_ - entry_size;
+ EnsureSizeNoMoreThan(insert_limit);
+ table_.push_front(entry);
+ current_size_ += entry_size;
+ DVLOG(2) << "InsertEntry: current_size_=" << current_size_;
+ DCHECK_GE(current_size_, entry_size);
+ DCHECK_LE(current_size_, size_limit_);
+ return true;
+}
+
+const HpackStringPair* HpackDecoderDynamicTable::Lookup(size_t index) const {
+ if (index < table_.size()) {
+ const HpackDecoderTableEntry& entry = table_[index];
+ if (debug_listener_ != nullptr) {
+ size_t insert_count_of_index = insert_count_ + table_.size() - index;
+ debug_listener_->OnUseEntry(entry, insert_count_of_index,
+ entry.time_added);
+ }
+ return &entry;
+ }
+ return nullptr;
+}
+
+void HpackDecoderDynamicTable::EnsureSizeNoMoreThan(size_t limit) {
+ DVLOG(2) << "EnsureSizeNoMoreThan limit=" << limit
+ << ", current_size_=" << current_size_;
+ // Not the most efficient choice, but any easy way to start.
+ while (current_size_ > limit) {
+ RemoveLastEntry();
+ }
+ DCHECK_LE(current_size_, limit);
+}
+
+void HpackDecoderDynamicTable::RemoveLastEntry() {
+ DCHECK(!table_.empty());
+ if (!table_.empty()) {
+ DVLOG(2) << "RemoveLastEntry current_size_=" << current_size_
+ << ", last entry size=" << table_.back().size();
+ DCHECK_GE(current_size_, table_.back().size());
+ current_size_ -= table_.back().size();
+ table_.pop_back();
+ // Empty IFF current_size_ == 0.
+ DCHECK_EQ(table_.empty(), current_size_ == 0);
+ }
+}
+
+HpackDecoderTables::HpackDecoderTables() = default;
+HpackDecoderTables::~HpackDecoderTables() = default;
+
+void HpackDecoderTables::set_debug_listener(
+ HpackDecoderTablesDebugListener* debug_listener) {
+ dynamic_table_.set_debug_listener(debug_listener);
+}
+
+const HpackStringPair* HpackDecoderTables::Lookup(size_t index) const {
+ if (index < kFirstDynamicTableIndex) {
+ return static_table_.Lookup(index);
+ } else {
+ return dynamic_table_.Lookup(index - kFirstDynamicTableIndex);
+ }
+}
+
+} // namespace http2
diff --git a/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_tables.h b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_tables.h
new file mode 100644
index 00000000000..6df145c9b7e
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_tables.h
@@ -0,0 +1,197 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_HTTP2_HPACK_DECODER_HPACK_DECODER_TABLES_H_
+#define QUICHE_HTTP2_HPACK_DECODER_HPACK_DECODER_TABLES_H_
+
+// Static and dynamic tables for the HPACK decoder. See:
+// http://httpwg.org/specs/rfc7541.html#indexing.tables
+
+// Note that the Lookup methods return nullptr if the requested index was not
+// found. This should be treated as a COMPRESSION error according to the HTTP/2
+// spec, which is a connection level protocol error (i.e. the connection must
+// be terminated). See these sections in the two RFCs:
+// http://httpwg.org/specs/rfc7541.html#indexed.header.representation
+// http://httpwg.org/specs/rfc7541.html#index.address.space
+// http://httpwg.org/specs/rfc7540.html#HeaderBlock
+
+#include <stddef.h>
+
+#include <cstdint>
+#include <vector>
+
+#include "net/third_party/quiche/src/http2/hpack/hpack_string.h"
+#include "net/third_party/quiche/src/http2/http2_constants.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_containers.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_export.h"
+
+namespace http2 {
+namespace test {
+class HpackDecoderTablesPeer;
+} // namespace test
+
+// HpackDecoderTablesDebugListener supports a QUIC experiment, enabling
+// the gathering of information about the time-line of use of HPACK
+// dynamic table entries.
+class HTTP2_EXPORT_PRIVATE HpackDecoderTablesDebugListener {
+ public:
+ HpackDecoderTablesDebugListener();
+ virtual ~HpackDecoderTablesDebugListener();
+
+ HpackDecoderTablesDebugListener(const HpackDecoderTablesDebugListener&) =
+ delete;
+ HpackDecoderTablesDebugListener& operator=(
+ const HpackDecoderTablesDebugListener&) = delete;
+
+ // The entry has been inserted into the dynamic table. insert_count starts at
+ // 62 because 61 is the last index in the static table; insert_count increases
+ // by 1 with each insert into the dynamic table; it is not incremented when
+ // when a entry is too large to fit into the dynamic table at all (which has
+ // the effect of emptying the dynamic table).
+ // Returns a value that can be used as time_added in OnUseEntry.
+ virtual int64_t OnEntryInserted(const HpackStringPair& entry,
+ size_t insert_count) = 0;
+
+ // The entry has been used, either for the name or for the name and value.
+ // insert_count is the same as passed to OnEntryInserted when entry was
+ // inserted to the dynamic table, and time_added is the value that was
+ // returned by OnEntryInserted.
+ virtual void OnUseEntry(const HpackStringPair& entry,
+ size_t insert_count,
+ int64_t time_added) = 0;
+};
+
+// See http://httpwg.org/specs/rfc7541.html#static.table.definition for the
+// contents, and http://httpwg.org/specs/rfc7541.html#index.address.space for
+// info about accessing the static table.
+class HTTP2_EXPORT_PRIVATE HpackDecoderStaticTable {
+ public:
+ explicit HpackDecoderStaticTable(const std::vector<HpackStringPair>* table);
+ // Uses a global table shared by all threads.
+ HpackDecoderStaticTable();
+
+ // If index is valid, returns a pointer to the entry, otherwise returns
+ // nullptr.
+ const HpackStringPair* Lookup(size_t index) const;
+
+ private:
+ friend class test::HpackDecoderTablesPeer;
+ const std::vector<HpackStringPair>* const table_;
+};
+
+// HpackDecoderDynamicTable implements HPACK compression feature "indexed
+// headers"; previously sent headers may be referenced later by their index
+// in the dynamic table. See these sections of the RFC:
+// http://httpwg.org/specs/rfc7541.html#dynamic.table
+// http://httpwg.org/specs/rfc7541.html#dynamic.table.management
+class HTTP2_EXPORT_PRIVATE HpackDecoderDynamicTable {
+ public:
+ HpackDecoderDynamicTable();
+ ~HpackDecoderDynamicTable();
+
+ HpackDecoderDynamicTable(const HpackDecoderDynamicTable&) = delete;
+ HpackDecoderDynamicTable& operator=(const HpackDecoderDynamicTable&) = delete;
+
+ // Set the listener to be notified of insertions into this table, and later
+ // uses of those entries. Added for evaluation of changes to QUIC's use
+ // of HPACK.
+ void set_debug_listener(HpackDecoderTablesDebugListener* debug_listener) {
+ debug_listener_ = debug_listener;
+ }
+
+ // Sets a new size limit, received from the peer; performs evictions if
+ // necessary to ensure that the current size does not exceed the new limit.
+ // The caller needs to have validated that size_limit does not
+ // exceed the acknowledged value of SETTINGS_HEADER_TABLE_SIZE.
+ void DynamicTableSizeUpdate(size_t size_limit);
+
+ // Returns true if inserted, false if too large (at which point the
+ // dynamic table will be empty.)
+ bool Insert(const HpackString& name, const HpackString& value);
+
+ // If index is valid, returns a pointer to the entry, otherwise returns
+ // nullptr.
+ const HpackStringPair* Lookup(size_t index) const;
+
+ size_t size_limit() const { return size_limit_; }
+ size_t current_size() const { return current_size_; }
+
+ private:
+ friend class test::HpackDecoderTablesPeer;
+ struct HpackDecoderTableEntry : public HpackStringPair {
+ HpackDecoderTableEntry(const HpackString& name, const HpackString& value);
+ int64_t time_added;
+ };
+
+ // Drop older entries to ensure the size is not greater than limit.
+ void EnsureSizeNoMoreThan(size_t limit);
+
+ // Removes the oldest dynamic table entry.
+ void RemoveLastEntry();
+
+ Http2Deque<HpackDecoderTableEntry> table_;
+
+ // The last received DynamicTableSizeUpdate value, initialized to
+ // SETTINGS_HEADER_TABLE_SIZE.
+ size_t size_limit_ = Http2SettingsInfo::DefaultHeaderTableSize();
+
+ size_t current_size_ = 0;
+
+ // insert_count_ and debug_listener_ are used by a QUIC experiment; remove
+ // when the experiment is done.
+ size_t insert_count_;
+ HpackDecoderTablesDebugListener* debug_listener_;
+};
+
+class HTTP2_EXPORT_PRIVATE HpackDecoderTables {
+ public:
+ HpackDecoderTables();
+ ~HpackDecoderTables();
+
+ HpackDecoderTables(const HpackDecoderTables&) = delete;
+ HpackDecoderTables& operator=(const HpackDecoderTables&) = delete;
+
+ // Set the listener to be notified of insertions into the dynamic table, and
+ // later uses of those entries. Added for evaluation of changes to QUIC's use
+ // of HPACK.
+ void set_debug_listener(HpackDecoderTablesDebugListener* debug_listener);
+
+ // Sets a new size limit, received from the peer; performs evictions if
+ // necessary to ensure that the current size does not exceed the new limit.
+ // The caller needs to have validated that size_limit does not
+ // exceed the acknowledged value of SETTINGS_HEADER_TABLE_SIZE.
+ void DynamicTableSizeUpdate(size_t size_limit) {
+ dynamic_table_.DynamicTableSizeUpdate(size_limit);
+ }
+
+ // Returns true if inserted, false if too large (at which point the
+ // dynamic table will be empty.)
+ // TODO(jamessynge): Add methods for moving the string(s) into the table,
+ // or for otherwise avoiding unnecessary copies.
+ bool Insert(const HpackString& name, const HpackString& value) {
+ return dynamic_table_.Insert(name, value);
+ }
+
+ // If index is valid, returns a pointer to the entry, otherwise returns
+ // nullptr.
+ const HpackStringPair* Lookup(size_t index) const;
+
+ // The size limit that the peer (the HPACK encoder) has told the decoder it is
+ // currently operating with. Defaults to SETTINGS_HEADER_TABLE_SIZE, 4096.
+ size_t header_table_size_limit() const { return dynamic_table_.size_limit(); }
+
+ // Sum of the sizes of the dynamic table entries.
+ size_t current_header_table_size() const {
+ return dynamic_table_.current_size();
+ }
+
+ private:
+ friend class test::HpackDecoderTablesPeer;
+ HpackDecoderStaticTable static_table_;
+ HpackDecoderDynamicTable dynamic_table_;
+};
+
+} // namespace http2
+
+#endif // QUICHE_HTTP2_HPACK_DECODER_HPACK_DECODER_TABLES_H_
diff --git a/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_tables_test.cc b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_tables_test.cc
new file mode 100644
index 00000000000..a54fa5f053b
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_tables_test.cc
@@ -0,0 +1,266 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_tables.h"
+
+#include <algorithm>
+#include <tuple>
+#include <vector>
+
+#include "base/logging.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/http2/hpack/http2_hpack_constants.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_test_helpers.h"
+#include "net/third_party/quiche/src/http2/test_tools/http2_random.h"
+#include "net/third_party/quiche/src/http2/tools/random_util.h"
+
+using ::testing::AssertionResult;
+using ::testing::AssertionSuccess;
+
+namespace http2 {
+namespace test {
+class HpackDecoderTablesPeer {
+ public:
+ static size_t num_dynamic_entries(const HpackDecoderTables& tables) {
+ return tables.dynamic_table_.table_.size();
+ }
+};
+
+namespace {
+struct StaticEntry {
+ const char* name;
+ const char* value;
+ size_t index;
+};
+
+std::vector<StaticEntry> MakeSpecStaticEntries() {
+ std::vector<StaticEntry> static_entries;
+
+#define STATIC_TABLE_ENTRY(name, value, index) \
+ DCHECK_EQ(static_entries.size() + 1, static_cast<size_t>(index)); \
+ static_entries.push_back({name, value, index});
+
+#include "net/third_party/quiche/src/http2/hpack/hpack_static_table_entries.inc"
+
+#undef STATIC_TABLE_ENTRY
+
+ return static_entries;
+}
+
+template <class C>
+void ShuffleCollection(C* collection, Http2Random* r) {
+ std::shuffle(collection->begin(), collection->end(), *r);
+}
+
+class HpackDecoderStaticTableTest : public ::testing::Test {
+ protected:
+ HpackDecoderStaticTableTest() = default;
+
+ std::vector<StaticEntry> shuffled_static_entries() {
+ std::vector<StaticEntry> entries = MakeSpecStaticEntries();
+ ShuffleCollection(&entries, &random_);
+ return entries;
+ }
+
+ // This test is in a function so that it can be applied to both the static
+ // table and the combined static+dynamic tables.
+ AssertionResult VerifyStaticTableContents() {
+ for (const auto& expected : shuffled_static_entries()) {
+ const HpackStringPair* found = Lookup(expected.index);
+ VERIFY_NE(found, nullptr);
+ VERIFY_EQ(expected.name, found->name) << expected.index;
+ VERIFY_EQ(expected.value, found->value) << expected.index;
+ }
+
+ // There should be no entry with index 0.
+ VERIFY_EQ(nullptr, Lookup(0));
+ return AssertionSuccess();
+ }
+
+ virtual const HpackStringPair* Lookup(size_t index) {
+ return static_table_.Lookup(index);
+ }
+
+ Http2Random* RandomPtr() { return &random_; }
+
+ Http2Random random_;
+
+ private:
+ HpackDecoderStaticTable static_table_;
+};
+
+TEST_F(HpackDecoderStaticTableTest, StaticTableContents) {
+ EXPECT_TRUE(VerifyStaticTableContents());
+}
+
+size_t Size(const Http2String& name, const Http2String& value) {
+ return name.size() + value.size() + 32;
+}
+
+// To support tests with more than a few of hand crafted changes to the dynamic
+// table, we have another, exceedingly simple, implementation of the HPACK
+// dynamic table containing FakeHpackEntry instances. We can thus compare the
+// contents of the actual table with those in fake_dynamic_table_.
+
+typedef std::tuple<Http2String, Http2String, size_t> FakeHpackEntry;
+const Http2String& Name(const FakeHpackEntry& entry) {
+ return std::get<0>(entry);
+}
+const Http2String& Value(const FakeHpackEntry& entry) {
+ return std::get<1>(entry);
+}
+size_t Size(const FakeHpackEntry& entry) {
+ return std::get<2>(entry);
+}
+
+class HpackDecoderTablesTest : public HpackDecoderStaticTableTest {
+ protected:
+ const HpackStringPair* Lookup(size_t index) override {
+ return tables_.Lookup(index);
+ }
+
+ size_t dynamic_size_limit() const {
+ return tables_.header_table_size_limit();
+ }
+ size_t current_dynamic_size() const {
+ return tables_.current_header_table_size();
+ }
+ size_t num_dynamic_entries() const {
+ return HpackDecoderTablesPeer::num_dynamic_entries(tables_);
+ }
+
+ // Insert the name and value into fake_dynamic_table_.
+ void FakeInsert(const Http2String& name, const Http2String& value) {
+ FakeHpackEntry entry(name, value, Size(name, value));
+ fake_dynamic_table_.insert(fake_dynamic_table_.begin(), entry);
+ }
+
+ // Add up the size of all entries in fake_dynamic_table_.
+ size_t FakeSize() {
+ size_t sz = 0;
+ for (const auto& entry : fake_dynamic_table_) {
+ sz += Size(entry);
+ }
+ return sz;
+ }
+
+ // If the total size of the fake_dynamic_table_ is greater than limit,
+ // keep the first N entries such that those N entries have a size not
+ // greater than limit, and such that keeping entry N+1 would have a size
+ // greater than limit. Returns the count of removed bytes.
+ size_t FakeTrim(size_t limit) {
+ size_t original_size = FakeSize();
+ size_t total_size = 0;
+ for (size_t ndx = 0; ndx < fake_dynamic_table_.size(); ++ndx) {
+ total_size += Size(fake_dynamic_table_[ndx]);
+ if (total_size > limit) {
+ // Need to get rid of ndx and all following entries.
+ fake_dynamic_table_.erase(fake_dynamic_table_.begin() + ndx,
+ fake_dynamic_table_.end());
+ return original_size - FakeSize();
+ }
+ }
+ return 0;
+ }
+
+ // Verify that the contents of the actual dynamic table match those in
+ // fake_dynamic_table_.
+ AssertionResult VerifyDynamicTableContents() {
+ VERIFY_EQ(current_dynamic_size(), FakeSize());
+ VERIFY_EQ(num_dynamic_entries(), fake_dynamic_table_.size());
+
+ for (size_t ndx = 0; ndx < fake_dynamic_table_.size(); ++ndx) {
+ const HpackStringPair* found = Lookup(ndx + kFirstDynamicTableIndex);
+ VERIFY_NE(found, nullptr);
+
+ const auto& expected = fake_dynamic_table_[ndx];
+ VERIFY_EQ(Name(expected), found->name);
+ VERIFY_EQ(Value(expected), found->value);
+ }
+
+ // Make sure there are no more entries.
+ VERIFY_EQ(nullptr,
+ Lookup(fake_dynamic_table_.size() + kFirstDynamicTableIndex));
+ return AssertionSuccess();
+ }
+
+ // Apply an update to the limit on the maximum size of the dynamic table.
+ AssertionResult DynamicTableSizeUpdate(size_t size_limit) {
+ VERIFY_EQ(current_dynamic_size(), FakeSize());
+ if (size_limit < current_dynamic_size()) {
+ // Will need to trim the dynamic table's oldest entries.
+ tables_.DynamicTableSizeUpdate(size_limit);
+ FakeTrim(size_limit);
+ return VerifyDynamicTableContents();
+ }
+ // Shouldn't change the size.
+ tables_.DynamicTableSizeUpdate(size_limit);
+ return VerifyDynamicTableContents();
+ }
+
+ // Insert an entry into the dynamic table, confirming that trimming of entries
+ // occurs if the total size is greater than the limit, and that older entries
+ // move up by 1 index.
+ AssertionResult Insert(const Http2String& name, const Http2String& value) {
+ size_t old_count = num_dynamic_entries();
+ if (tables_.Insert(HpackString(name), HpackString(value))) {
+ VERIFY_GT(current_dynamic_size(), 0u);
+ VERIFY_GT(num_dynamic_entries(), 0u);
+ } else {
+ VERIFY_EQ(current_dynamic_size(), 0u);
+ VERIFY_EQ(num_dynamic_entries(), 0u);
+ }
+ FakeInsert(name, value);
+ VERIFY_EQ(old_count + 1, fake_dynamic_table_.size());
+ FakeTrim(dynamic_size_limit());
+ VERIFY_EQ(current_dynamic_size(), FakeSize());
+ VERIFY_EQ(num_dynamic_entries(), fake_dynamic_table_.size());
+ return VerifyDynamicTableContents();
+ }
+
+ private:
+ HpackDecoderTables tables_;
+
+ std::vector<FakeHpackEntry> fake_dynamic_table_;
+};
+
+TEST_F(HpackDecoderTablesTest, StaticTableContents) {
+ EXPECT_TRUE(VerifyStaticTableContents());
+}
+
+// Generate a bunch of random header entries, insert them, and confirm they
+// present, as required by the RFC, using VerifyDynamicTableContents above on
+// each Insert. Also apply various resizings of the dynamic table.
+TEST_F(HpackDecoderTablesTest, RandomDynamicTable) {
+ EXPECT_EQ(0u, current_dynamic_size());
+ EXPECT_TRUE(VerifyStaticTableContents());
+ EXPECT_TRUE(VerifyDynamicTableContents());
+
+ std::vector<size_t> table_sizes;
+ table_sizes.push_back(dynamic_size_limit());
+ table_sizes.push_back(0);
+ table_sizes.push_back(dynamic_size_limit() / 2);
+ table_sizes.push_back(dynamic_size_limit());
+ table_sizes.push_back(dynamic_size_limit() / 2);
+ table_sizes.push_back(0);
+ table_sizes.push_back(dynamic_size_limit());
+
+ for (size_t limit : table_sizes) {
+ ASSERT_TRUE(DynamicTableSizeUpdate(limit));
+ for (int insert_count = 0; insert_count < 100; ++insert_count) {
+ Http2String name =
+ GenerateHttp2HeaderName(random_.UniformInRange(2, 40), RandomPtr());
+ Http2String value =
+ GenerateWebSafeString(random_.UniformInRange(2, 600), RandomPtr());
+ ASSERT_TRUE(Insert(name, value));
+ }
+ EXPECT_TRUE(VerifyStaticTableContents());
+ }
+}
+
+} // namespace
+} // namespace test
+} // namespace http2
diff --git a/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_test.cc b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_test.cc
new file mode 100644
index 00000000000..563e977a07f
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_test.cc
@@ -0,0 +1,1219 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder.h"
+
+// Tests of HpackDecoder.
+
+#include <tuple>
+#include <utility>
+#include <vector>
+
+#include "base/logging.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h"
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_listener.h"
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_state.h"
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_tables.h"
+#include "net/third_party/quiche/src/http2/hpack/hpack_string.h"
+#include "net/third_party/quiche/src/http2/hpack/http2_hpack_constants.h"
+#include "net/third_party/quiche/src/http2/hpack/tools/hpack_block_builder.h"
+#include "net/third_party/quiche/src/http2/hpack/tools/hpack_example.h"
+#include "net/third_party/quiche/src/http2/http2_constants.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_test_helpers.h"
+#include "net/third_party/quiche/src/http2/test_tools/http2_random.h"
+#include "net/third_party/quiche/src/http2/tools/random_util.h"
+
+using ::testing::AssertionFailure;
+using ::testing::AssertionResult;
+using ::testing::AssertionSuccess;
+using ::testing::ElementsAreArray;
+using ::testing::HasSubstr;
+
+namespace http2 {
+namespace test {
+class HpackDecoderStatePeer {
+ public:
+ static HpackDecoderTables* GetDecoderTables(HpackDecoderState* state) {
+ return &state->decoder_tables_;
+ }
+ static void set_listener(HpackDecoderState* state,
+ HpackDecoderListener* listener) {
+ state->listener_ = listener;
+ }
+};
+class HpackDecoderPeer {
+ public:
+ static HpackDecoderState* GetDecoderState(HpackDecoder* decoder) {
+ return &decoder->decoder_state_;
+ }
+ static HpackDecoderTables* GetDecoderTables(HpackDecoder* decoder) {
+ return HpackDecoderStatePeer::GetDecoderTables(GetDecoderState(decoder));
+ }
+};
+
+namespace {
+
+typedef std::tuple<HpackEntryType, Http2String, Http2String> HpackHeaderEntry;
+typedef std::vector<HpackHeaderEntry> HpackHeaderEntries;
+
+// TODO(jamessynge): Create a ...test_utils.h file with the mock listener
+// and with VerifyDynamicTableContents.
+class MockHpackDecoderListener : public HpackDecoderListener {
+ public:
+ MOCK_METHOD0(OnHeaderListStart, void());
+ MOCK_METHOD3(OnHeader,
+ void(HpackEntryType entry_type,
+ const HpackString& name,
+ const HpackString& value));
+ MOCK_METHOD0(OnHeaderListEnd, void());
+ MOCK_METHOD1(OnHeaderErrorDetected, void(Http2StringPiece error_message));
+};
+
+class HpackDecoderTest : public ::testing::TestWithParam<bool>,
+ public HpackDecoderListener {
+ protected:
+ // Note that we initialize the random number generator with the same seed
+ // for each individual test, therefore the order in which the tests are
+ // executed does not effect the sequence produced by the RNG within any
+ // one test.
+ HpackDecoderTest() : decoder_(this, 4096) {
+ fragment_the_hpack_block_ = GetParam();
+ }
+ ~HpackDecoderTest() override = default;
+
+ void OnHeaderListStart() override {
+ ASSERT_FALSE(saw_start_);
+ ASSERT_FALSE(saw_end_);
+ saw_start_ = true;
+ header_entries_.clear();
+ }
+
+ // Called for each header name-value pair that is decoded, in the order they
+ // appear in the HPACK block. Multiple values for a given key will be emitted
+ // as multiple calls to OnHeader.
+ void OnHeader(HpackEntryType entry_type,
+ const HpackString& name,
+ const HpackString& value) override {
+ ASSERT_TRUE(saw_start_);
+ ASSERT_FALSE(saw_end_);
+ // header_entries_.push_back({entry_type, name.ToString(),
+ // value.ToString()});
+ header_entries_.emplace_back(entry_type, name.ToString(), value.ToString());
+ }
+
+ // OnHeaderBlockEnd is called after successfully decoding an HPACK block. Will
+ // only be called once per block, even if it extends into CONTINUATION frames.
+ // A callback method which notifies when the parser finishes handling a
+ // header block (i.e. the containing frame has the END_STREAM flag set).
+ // Also indicates the total number of bytes in this block.
+ void OnHeaderListEnd() override {
+ ASSERT_TRUE(saw_start_);
+ ASSERT_FALSE(saw_end_);
+ ASSERT_TRUE(error_messages_.empty());
+ saw_end_ = true;
+ }
+
+ // OnHeaderErrorDetected is called if an error is detected while decoding.
+ // error_message may be used in a GOAWAY frame as the Opaque Data.
+ void OnHeaderErrorDetected(Http2StringPiece error_message) override {
+ ASSERT_TRUE(saw_start_);
+ error_messages_.push_back(Http2String(error_message));
+ // No further callbacks should be made at this point, so replace 'this' as
+ // the listener with mock_listener_, which is a strict mock, so will
+ // generate an error for any calls.
+ HpackDecoderStatePeer::set_listener(
+ HpackDecoderPeer::GetDecoderState(&decoder_), &mock_listener_);
+ }
+
+ AssertionResult DecodeBlock(Http2StringPiece block) {
+ VLOG(1) << "HpackDecoderTest::DecodeBlock";
+
+ VERIFY_FALSE(decoder_.error_detected());
+ VERIFY_TRUE(error_messages_.empty());
+ VERIFY_FALSE(saw_start_);
+ VERIFY_FALSE(saw_end_);
+ header_entries_.clear();
+
+ VERIFY_FALSE(decoder_.error_detected());
+ VERIFY_TRUE(decoder_.StartDecodingBlock());
+ VERIFY_FALSE(decoder_.error_detected());
+
+ if (fragment_the_hpack_block_) {
+ // See note in ctor regarding RNG.
+ while (!block.empty()) {
+ size_t fragment_size = random_.RandomSizeSkewedLow(block.size());
+ DecodeBuffer db(block.substr(0, fragment_size));
+ VERIFY_TRUE(decoder_.DecodeFragment(&db));
+ VERIFY_EQ(0u, db.Remaining());
+ block.remove_prefix(fragment_size);
+ }
+ } else {
+ DecodeBuffer db(block);
+ VERIFY_TRUE(decoder_.DecodeFragment(&db));
+ VERIFY_EQ(0u, db.Remaining());
+ }
+ VERIFY_FALSE(decoder_.error_detected());
+
+ VERIFY_TRUE(decoder_.EndDecodingBlock());
+ if (saw_end_) {
+ VERIFY_FALSE(decoder_.error_detected());
+ VERIFY_TRUE(error_messages_.empty());
+ } else {
+ VERIFY_TRUE(decoder_.error_detected());
+ VERIFY_FALSE(error_messages_.empty());
+ }
+
+ saw_start_ = saw_end_ = false;
+ return AssertionSuccess();
+ }
+
+ const HpackDecoderTables& GetDecoderTables() {
+ return *HpackDecoderPeer::GetDecoderTables(&decoder_);
+ }
+ const HpackStringPair* Lookup(size_t index) {
+ return GetDecoderTables().Lookup(index);
+ }
+ size_t current_header_table_size() {
+ return GetDecoderTables().current_header_table_size();
+ }
+ size_t header_table_size_limit() {
+ return GetDecoderTables().header_table_size_limit();
+ }
+ void set_header_table_size_limit(size_t size) {
+ HpackDecoderPeer::GetDecoderTables(&decoder_)->DynamicTableSizeUpdate(size);
+ }
+
+ // dynamic_index is one-based, because that is the way RFC 7541 shows it.
+ AssertionResult VerifyEntry(size_t dynamic_index,
+ const char* name,
+ const char* value) {
+ const HpackStringPair* entry =
+ Lookup(dynamic_index + kFirstDynamicTableIndex - 1);
+ VERIFY_NE(entry, nullptr);
+ VERIFY_EQ(entry->name.ToStringPiece(), name);
+ VERIFY_EQ(entry->value.ToStringPiece(), value);
+ return AssertionSuccess();
+ }
+ AssertionResult VerifyNoEntry(size_t dynamic_index) {
+ const HpackStringPair* entry =
+ Lookup(dynamic_index + kFirstDynamicTableIndex - 1);
+ VERIFY_EQ(entry, nullptr);
+ return AssertionSuccess();
+ }
+ AssertionResult VerifyDynamicTableContents(
+ const std::vector<std::pair<const char*, const char*>>& entries) {
+ size_t index = 1;
+ for (const auto& entry : entries) {
+ VERIFY_SUCCESS(VerifyEntry(index, entry.first, entry.second));
+ ++index;
+ }
+ VERIFY_SUCCESS(VerifyNoEntry(index));
+ return AssertionSuccess();
+ }
+
+ Http2Random random_;
+ HpackDecoder decoder_;
+ testing::StrictMock<MockHpackDecoderListener> mock_listener_;
+ HpackHeaderEntries header_entries_;
+ std::vector<Http2String> error_messages_;
+ bool fragment_the_hpack_block_;
+ bool saw_start_ = false;
+ bool saw_end_ = false;
+};
+INSTANTIATE_TEST_CASE_P(AllWays, HpackDecoderTest, ::testing::Bool());
+
+// Test based on RFC 7541, section C.3: Request Examples without Huffman Coding.
+// This section shows several consecutive header lists, corresponding to HTTP
+// requests, on the same connection.
+// http://httpwg.org/specs/rfc7541.html#rfc.section.C.3
+TEST_P(HpackDecoderTest, C3_RequestExamples) {
+ // C.3.1 First Request
+ Http2String hpack_block = HpackExampleToStringOrDie(R"(
+ 82 | == Indexed - Add ==
+ | idx = 2
+ | -> :method: GET
+ 86 | == Indexed - Add ==
+ | idx = 6
+ | -> :scheme: http
+ 84 | == Indexed - Add ==
+ | idx = 4
+ | -> :path: /
+ 41 | == Literal indexed ==
+ | Indexed name (idx = 1)
+ | :authority
+ 0f | Literal value (len = 15)
+ 7777 772e 6578 616d 706c 652e 636f 6d | www.example.com
+ | -> :authority:
+ | www.example.com
+ )");
+ EXPECT_TRUE(DecodeBlock(hpack_block));
+ ASSERT_THAT(
+ header_entries_,
+ ElementsAreArray({
+ HpackHeaderEntry{HpackEntryType::kIndexedHeader, ":method", "GET"},
+ HpackHeaderEntry{HpackEntryType::kIndexedHeader, ":scheme", "http"},
+ HpackHeaderEntry{HpackEntryType::kIndexedHeader, ":path", "/"},
+ HpackHeaderEntry{HpackEntryType::kIndexedLiteralHeader, ":authority",
+ "www.example.com"},
+ }));
+
+ // Dynamic Table (after decoding):
+ //
+ // [ 1] (s = 57) :authority: www.example.com
+ // Table size: 57
+ ASSERT_TRUE(VerifyDynamicTableContents({{":authority", "www.example.com"}}));
+ ASSERT_EQ(57u, current_header_table_size());
+
+ // C.3.2 Second Request
+ hpack_block = HpackExampleToStringOrDie(R"(
+ 82 | == Indexed - Add ==
+ | idx = 2
+ | -> :method: GET
+ 86 | == Indexed - Add ==
+ | idx = 6
+ | -> :scheme: http
+ 84 | == Indexed - Add ==
+ | idx = 4
+ | -> :path: /
+ be | == Indexed - Add ==
+ | idx = 62
+ | -> :authority:
+ | www.example.com
+ 58 | == Literal indexed ==
+ | Indexed name (idx = 24)
+ | cache-control
+ 08 | Literal value (len = 8)
+ 6e6f 2d63 6163 6865 | no-cache
+ | -> cache-control: no-cache
+ )");
+ EXPECT_TRUE(DecodeBlock(hpack_block));
+ ASSERT_THAT(
+ header_entries_,
+ ElementsAreArray({
+ HpackHeaderEntry{HpackEntryType::kIndexedHeader, ":method", "GET"},
+ HpackHeaderEntry{HpackEntryType::kIndexedHeader, ":scheme", "http"},
+ HpackHeaderEntry{HpackEntryType::kIndexedHeader, ":path", "/"},
+ HpackHeaderEntry{HpackEntryType::kIndexedHeader, ":authority",
+ "www.example.com"},
+ HpackHeaderEntry{HpackEntryType::kIndexedLiteralHeader,
+ "cache-control", "no-cache"},
+ }));
+
+ // Dynamic Table (after decoding):
+ //
+ // [ 1] (s = 53) cache-control: no-cache
+ // [ 2] (s = 57) :authority: www.example.com
+ // Table size: 110
+ ASSERT_TRUE(VerifyDynamicTableContents(
+ {{"cache-control", "no-cache"}, {":authority", "www.example.com"}}));
+ ASSERT_EQ(110u, current_header_table_size());
+
+ // C.3.2 Third Request
+ hpack_block = HpackExampleToStringOrDie(R"(
+ 82 | == Indexed - Add ==
+ | idx = 2
+ | -> :method: GET
+ 87 | == Indexed - Add ==
+ | idx = 7
+ | -> :scheme: https
+ 85 | == Indexed - Add ==
+ | idx = 5
+ | -> :path: /index.html
+ bf | == Indexed - Add ==
+ | idx = 63
+ | -> :authority:
+ | www.example.com
+ 40 | == Literal indexed ==
+ 0a | Literal name (len = 10)
+ 6375 7374 6f6d 2d6b 6579 | custom-key
+ 0c | Literal value (len = 12)
+ 6375 7374 6f6d 2d76 616c 7565 | custom-value
+ | -> custom-key:
+ | custom-value
+ )");
+ EXPECT_TRUE(DecodeBlock(hpack_block));
+ ASSERT_THAT(
+ header_entries_,
+ ElementsAreArray({
+ HpackHeaderEntry{HpackEntryType::kIndexedHeader, ":method", "GET"},
+ HpackHeaderEntry{HpackEntryType::kIndexedHeader, ":scheme", "https"},
+ HpackHeaderEntry{HpackEntryType::kIndexedHeader, ":path",
+ "/index.html"},
+ HpackHeaderEntry{HpackEntryType::kIndexedHeader, ":authority",
+ "www.example.com"},
+ HpackHeaderEntry{HpackEntryType::kIndexedLiteralHeader, "custom-key",
+ "custom-value"},
+ }));
+
+ // Dynamic Table (after decoding):
+ //
+ // [ 1] (s = 54) custom-key: custom-value
+ // [ 2] (s = 53) cache-control: no-cache
+ // [ 3] (s = 57) :authority: www.example.com
+ // Table size: 164
+ ASSERT_TRUE(VerifyDynamicTableContents({{"custom-key", "custom-value"},
+ {"cache-control", "no-cache"},
+ {":authority", "www.example.com"}}));
+ ASSERT_EQ(164u, current_header_table_size());
+}
+
+// Test based on RFC 7541, section C.4 Request Examples with Huffman Coding.
+// This section shows the same examples as the previous section but uses
+// Huffman encoding for the literal values.
+// http://httpwg.org/specs/rfc7541.html#rfc.section.C.4
+TEST_P(HpackDecoderTest, C4_RequestExamplesWithHuffmanEncoding) {
+ // C.4.1 First Request
+ Http2String hpack_block = HpackExampleToStringOrDie(R"(
+ 82 | == Indexed - Add ==
+ | idx = 2
+ | -> :method: GET
+ 86 | == Indexed - Add ==
+ | idx = 6
+ | -> :scheme: http
+ 84 | == Indexed - Add ==
+ | idx = 4
+ | -> :path: /
+ 41 | == Literal indexed ==
+ | Indexed name (idx = 1)
+ | :authority
+ 8c | Literal value (len = 12)
+ | Huffman encoded:
+ f1e3 c2e5 f23a 6ba0 ab90 f4ff | .....:k.....
+ | Decoded:
+ | www.example.com
+ | -> :authority:
+ | www.example.com
+ )");
+ EXPECT_TRUE(DecodeBlock(hpack_block));
+ ASSERT_THAT(
+ header_entries_,
+ ElementsAreArray({
+ HpackHeaderEntry{HpackEntryType::kIndexedHeader, ":method", "GET"},
+ HpackHeaderEntry{HpackEntryType::kIndexedHeader, ":scheme", "http"},
+ HpackHeaderEntry{HpackEntryType::kIndexedHeader, ":path", "/"},
+ HpackHeaderEntry{HpackEntryType::kIndexedLiteralHeader, ":authority",
+ "www.example.com"},
+ }));
+
+ // Dynamic Table (after decoding):
+ //
+ // [ 1] (s = 57) :authority: www.example.com
+ // Table size: 57
+ ASSERT_TRUE(VerifyDynamicTableContents({{":authority", "www.example.com"}}));
+ ASSERT_EQ(57u, current_header_table_size());
+
+ // C.4.2 Second Request
+ hpack_block = HpackExampleToStringOrDie(R"(
+ 82 | == Indexed - Add ==
+ | idx = 2
+ | -> :method: GET
+ 86 | == Indexed - Add ==
+ | idx = 6
+ | -> :scheme: http
+ 84 | == Indexed - Add ==
+ | idx = 4
+ | -> :path: /
+ be | == Indexed - Add ==
+ | idx = 62
+ | -> :authority:
+ | www.example.com
+ 58 | == Literal indexed ==
+ | Indexed name (idx = 24)
+ | cache-control
+ 86 | Literal value (len = 6)
+ | Huffman encoded:
+ a8eb 1064 9cbf | ...d..
+ | Decoded:
+ | no-cache
+ | -> cache-control: no-cache
+ )");
+ EXPECT_TRUE(DecodeBlock(hpack_block));
+ ASSERT_THAT(
+ header_entries_,
+ ElementsAreArray({
+ HpackHeaderEntry{HpackEntryType::kIndexedHeader, ":method", "GET"},
+ HpackHeaderEntry{HpackEntryType::kIndexedHeader, ":scheme", "http"},
+ HpackHeaderEntry{HpackEntryType::kIndexedHeader, ":path", "/"},
+ HpackHeaderEntry{HpackEntryType::kIndexedHeader, ":authority",
+ "www.example.com"},
+ HpackHeaderEntry{HpackEntryType::kIndexedLiteralHeader,
+ "cache-control", "no-cache"},
+ }));
+
+ // Dynamic Table (after decoding):
+ //
+ // [ 1] (s = 53) cache-control: no-cache
+ // [ 2] (s = 57) :authority: www.example.com
+ // Table size: 110
+ ASSERT_TRUE(VerifyDynamicTableContents(
+ {{"cache-control", "no-cache"}, {":authority", "www.example.com"}}));
+ ASSERT_EQ(110u, current_header_table_size());
+
+ // C.4.2 Third Request
+ hpack_block = HpackExampleToStringOrDie(R"(
+ 82 | == Indexed - Add ==
+ | idx = 2
+ | -> :method: GET
+ 87 | == Indexed - Add ==
+ | idx = 7
+ | -> :scheme: https
+ 85 | == Indexed - Add ==
+ | idx = 5
+ | -> :path: /index.html
+ bf | == Indexed - Add ==
+ | idx = 63
+ | -> :authority:
+ | www.example.com
+ 40 | == Literal indexed ==
+ 88 | Literal name (len = 8)
+ | Huffman encoded:
+ 25a8 49e9 5ba9 7d7f | %.I.[.}.
+ | Decoded:
+ | custom-key
+ 89 | Literal value (len = 9)
+ | Huffman encoded:
+ 25a8 49e9 5bb8 e8b4 bf | %.I.[....
+ | Decoded:
+ | custom-value
+ | -> custom-key:
+ | custom-value
+ )");
+ EXPECT_TRUE(DecodeBlock(hpack_block));
+ ASSERT_THAT(
+ header_entries_,
+ ElementsAreArray({
+ HpackHeaderEntry{HpackEntryType::kIndexedHeader, ":method", "GET"},
+ HpackHeaderEntry{HpackEntryType::kIndexedHeader, ":scheme", "https"},
+ HpackHeaderEntry{HpackEntryType::kIndexedHeader, ":path",
+ "/index.html"},
+ HpackHeaderEntry{HpackEntryType::kIndexedHeader, ":authority",
+ "www.example.com"},
+ HpackHeaderEntry{HpackEntryType::kIndexedLiteralHeader, "custom-key",
+ "custom-value"},
+ }));
+
+ // Dynamic Table (after decoding):
+ //
+ // [ 1] (s = 54) custom-key: custom-value
+ // [ 2] (s = 53) cache-control: no-cache
+ // [ 3] (s = 57) :authority: www.example.com
+ // Table size: 164
+ ASSERT_TRUE(VerifyDynamicTableContents({{"custom-key", "custom-value"},
+ {"cache-control", "no-cache"},
+ {":authority", "www.example.com"}}));
+ ASSERT_EQ(164u, current_header_table_size());
+}
+
+// Test based on RFC 7541, section C.5: Response Examples without Huffman
+// Coding. This section shows several consecutive header lists, corresponding
+// to HTTP responses, on the same connection. The HTTP/2 setting parameter
+// SETTINGS_HEADER_TABLE_SIZE is set to the value of 256 octets, causing
+// some evictions to occur.
+// http://httpwg.org/specs/rfc7541.html#rfc.section.C.5
+TEST_P(HpackDecoderTest, C5_ResponseExamples) {
+ set_header_table_size_limit(256);
+
+ // C.5.1 First Response
+ //
+ // Header list to encode:
+ //
+ // :status: 302
+ // cache-control: private
+ // date: Mon, 21 Oct 2013 20:13:21 GMT
+ // location: https://www.example.com
+
+ Http2String hpack_block = HpackExampleToStringOrDie(R"(
+ 48 | == Literal indexed ==
+ | Indexed name (idx = 8)
+ | :status
+ 03 | Literal value (len = 3)
+ 3330 32 | 302
+ | -> :status: 302
+ 58 | == Literal indexed ==
+ | Indexed name (idx = 24)
+ | cache-control
+ 07 | Literal value (len = 7)
+ 7072 6976 6174 65 | private
+ | -> cache-control: private
+ 61 | == Literal indexed ==
+ | Indexed name (idx = 33)
+ | date
+ 1d | Literal value (len = 29)
+ 4d6f 6e2c 2032 3120 4f63 7420 3230 3133 | Mon, 21 Oct 2013
+ 2032 303a 3133 3a32 3120 474d 54 | 20:13:21 GMT
+ | -> date: Mon, 21 Oct 2013
+ | 20:13:21 GMT
+ 6e | == Literal indexed ==
+ | Indexed name (idx = 46)
+ | location
+ 17 | Literal value (len = 23)
+ 6874 7470 733a 2f2f 7777 772e 6578 616d | https://www.exam
+ 706c 652e 636f 6d | ple.com
+ | -> location:
+ | https://www.example.com
+ )");
+ EXPECT_TRUE(DecodeBlock(hpack_block));
+ ASSERT_THAT(header_entries_,
+ ElementsAreArray({
+ HpackHeaderEntry{HpackEntryType::kIndexedLiteralHeader,
+ ":status", "302"},
+ HpackHeaderEntry{HpackEntryType::kIndexedLiteralHeader,
+ "cache-control", "private"},
+ HpackHeaderEntry{HpackEntryType::kIndexedLiteralHeader,
+ "date", "Mon, 21 Oct 2013 20:13:21 GMT"},
+ HpackHeaderEntry{HpackEntryType::kIndexedLiteralHeader,
+ "location", "https://www.example.com"},
+ }));
+
+ // Dynamic Table (after decoding):
+ //
+ // [ 1] (s = 63) location: https://www.example.com
+ // [ 2] (s = 65) date: Mon, 21 Oct 2013 20:13:21 GMT
+ // [ 3] (s = 52) cache-control: private
+ // [ 4] (s = 42) :status: 302
+ // Table size: 222
+ ASSERT_TRUE(
+ VerifyDynamicTableContents({{"location", "https://www.example.com"},
+ {"date", "Mon, 21 Oct 2013 20:13:21 GMT"},
+ {"cache-control", "private"},
+ {":status", "302"}}));
+ ASSERT_EQ(222u, current_header_table_size());
+
+ // C.5.2 Second Response
+ //
+ // The (":status", "302") header field is evicted from the dynamic table to
+ // free space to allow adding the (":status", "307") header field.
+ //
+ // Header list to encode:
+ //
+ // :status: 307
+ // cache-control: private
+ // date: Mon, 21 Oct 2013 20:13:21 GMT
+ // location: https://www.example.com
+
+ hpack_block = HpackExampleToStringOrDie(R"(
+ 48 | == Literal indexed ==
+ | Indexed name (idx = 8)
+ | :status
+ 03 | Literal value (len = 3)
+ 3330 37 | 307
+ | - evict: :status: 302
+ | -> :status: 307
+ c1 | == Indexed - Add ==
+ | idx = 65
+ | -> cache-control: private
+ c0 | == Indexed - Add ==
+ | idx = 64
+ | -> date: Mon, 21 Oct 2013
+ | 20:13:21 GMT
+ bf | == Indexed - Add ==
+ | idx = 63
+ | -> location:
+ | https://www.example.com
+ )");
+ EXPECT_TRUE(DecodeBlock(hpack_block));
+ ASSERT_THAT(header_entries_,
+ ElementsAreArray({
+ HpackHeaderEntry{HpackEntryType::kIndexedLiteralHeader,
+ ":status", "307"},
+ HpackHeaderEntry{HpackEntryType::kIndexedHeader,
+ "cache-control", "private"},
+ HpackHeaderEntry{HpackEntryType::kIndexedHeader, "date",
+ "Mon, 21 Oct 2013 20:13:21 GMT"},
+ HpackHeaderEntry{HpackEntryType::kIndexedHeader, "location",
+ "https://www.example.com"},
+ }));
+
+ // Dynamic Table (after decoding):
+ //
+ // [ 1] (s = 42) :status: 307
+ // [ 2] (s = 63) location: https://www.example.com
+ // [ 3] (s = 65) date: Mon, 21 Oct 2013 20:13:21 GMT
+ // [ 4] (s = 52) cache-control: private
+ // Table size: 222
+
+ ASSERT_TRUE(
+ VerifyDynamicTableContents({{":status", "307"},
+ {"location", "https://www.example.com"},
+ {"date", "Mon, 21 Oct 2013 20:13:21 GMT"},
+ {"cache-control", "private"}}));
+ ASSERT_EQ(222u, current_header_table_size());
+
+ // C.5.3 Third Response
+ //
+ // Several header fields are evicted from the dynamic table during the
+ // processing of this header list.
+ //
+ // Header list to encode:
+ //
+ // :status: 200
+ // cache-control: private
+ // date: Mon, 21 Oct 2013 20:13:22 GMT
+ // location: https://www.example.com
+ // content-encoding: gzip
+ // set-cookie: foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1
+ hpack_block = HpackExampleToStringOrDie(R"(
+ 88 | == Indexed - Add ==
+ | idx = 8
+ | -> :status: 200
+ c1 | == Indexed - Add ==
+ | idx = 65
+ | -> cache-control: private
+ 61 | == Literal indexed ==
+ | Indexed name (idx = 33)
+ | date
+ 1d | Literal value (len = 29)
+ 4d6f 6e2c 2032 3120 4f63 7420 3230 3133 | Mon, 21 Oct 2013
+ 2032 303a 3133 3a32 3220 474d 54 | 20:13:22 GMT
+ | - evict: cache-control:
+ | private
+ | -> date: Mon, 21 Oct 2013
+ | 20:13:22 GMT
+ c0 | == Indexed - Add ==
+ | idx = 64
+ | -> location:
+ | https://www.example.com
+ 5a | == Literal indexed ==
+ | Indexed name (idx = 26)
+ | content-encoding
+ 04 | Literal value (len = 4)
+ 677a 6970 | gzip
+ | - evict: date: Mon, 21 Oct
+ | 2013 20:13:21 GMT
+ | -> content-encoding: gzip
+ 77 | == Literal indexed ==
+ | Indexed name (idx = 55)
+ | set-cookie
+ 38 | Literal value (len = 56)
+ 666f 6f3d 4153 444a 4b48 514b 425a 584f | foo=ASDJKHQKBZXO
+ 5157 454f 5049 5541 5851 5745 4f49 553b | QWEOPIUAXQWEOIU;
+ 206d 6178 2d61 6765 3d33 3630 303b 2076 | max-age=3600; v
+ 6572 7369 6f6e 3d31 | ersion=1
+ | - evict: location:
+ | https://www.example.com
+ | - evict: :status: 307
+ | -> set-cookie: foo=ASDJKHQ
+ | KBZXOQWEOPIUAXQWEOIU; ma
+ | x-age=3600; version=1
+ )");
+ EXPECT_TRUE(DecodeBlock(hpack_block));
+ ASSERT_THAT(
+ header_entries_,
+ ElementsAreArray({
+ HpackHeaderEntry{HpackEntryType::kIndexedHeader, ":status", "200"},
+ HpackHeaderEntry{HpackEntryType::kIndexedHeader, "cache-control",
+ "private"},
+ HpackHeaderEntry{HpackEntryType::kIndexedLiteralHeader, "date",
+ "Mon, 21 Oct 2013 20:13:22 GMT"},
+ HpackHeaderEntry{HpackEntryType::kIndexedHeader, "location",
+ "https://www.example.com"},
+ HpackHeaderEntry{HpackEntryType::kIndexedLiteralHeader,
+ "content-encoding", "gzip"},
+ HpackHeaderEntry{
+ HpackEntryType::kIndexedLiteralHeader, "set-cookie",
+ "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1"},
+ }));
+
+ // Dynamic Table (after decoding):
+ //
+ // [ 1] (s = 98) set-cookie: foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU;
+ // max-age=3600; version=1
+ // [ 2] (s = 52) content-encoding: gzip
+ // [ 3] (s = 65) date: Mon, 21 Oct 2013 20:13:22 GMT
+ // Table size: 215
+ ASSERT_TRUE(VerifyDynamicTableContents(
+ {{"set-cookie",
+ "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1"},
+ {"content-encoding", "gzip"},
+ {"date", "Mon, 21 Oct 2013 20:13:22 GMT"}}));
+ ASSERT_EQ(215u, current_header_table_size());
+}
+
+// Test based on RFC 7541, section C.6: Response Examples with Huffman Coding.
+// This section shows the same examples as the previous section but uses Huffman
+// encoding for the literal values. The HTTP/2 setting parameter
+// SETTINGS_HEADER_TABLE_SIZE is set to the value of 256 octets, causing some
+// evictions to occur. The eviction mechanism uses the length of the decoded
+// literal values, so the same evictions occur as in the previous section.
+// http://httpwg.org/specs/rfc7541.html#rfc.section.C.6
+TEST_P(HpackDecoderTest, C6_ResponseExamplesWithHuffmanEncoding) {
+ set_header_table_size_limit(256);
+
+ // C.5.1 First Response
+ //
+ // Header list to encode:
+ //
+ // :status: 302
+ // cache-control: private
+ // date: Mon, 21 Oct 2013 20:13:21 GMT
+ // location: https://www.example.com
+ Http2String hpack_block = HpackExampleToStringOrDie(R"(
+ 48 | == Literal indexed ==
+ | Indexed name (idx = 8)
+ | :status
+ 03 | Literal value (len = 3)
+ 3330 32 | 302
+ | -> :status: 302
+ 58 | == Literal indexed ==
+ | Indexed name (idx = 24)
+ | cache-control
+ 07 | Literal value (len = 7)
+ 7072 6976 6174 65 | private
+ | -> cache-control: private
+ 61 | == Literal indexed ==
+ | Indexed name (idx = 33)
+ | date
+ 1d | Literal value (len = 29)
+ 4d6f 6e2c 2032 3120 4f63 7420 3230 3133 | Mon, 21 Oct 2013
+ 2032 303a 3133 3a32 3120 474d 54 | 20:13:21 GMT
+ | -> date: Mon, 21 Oct 2013
+ | 20:13:21 GMT
+ 6e | == Literal indexed ==
+ | Indexed name (idx = 46)
+ | location
+ 17 | Literal value (len = 23)
+ 6874 7470 733a 2f2f 7777 772e 6578 616d | https://www.exam
+ 706c 652e 636f 6d | ple.com
+ | -> location:
+ | https://www.example.com
+ )");
+ EXPECT_TRUE(DecodeBlock(hpack_block));
+ ASSERT_THAT(header_entries_,
+ ElementsAreArray({
+ HpackHeaderEntry{HpackEntryType::kIndexedLiteralHeader,
+ ":status", "302"},
+ HpackHeaderEntry{HpackEntryType::kIndexedLiteralHeader,
+ "cache-control", "private"},
+ HpackHeaderEntry{HpackEntryType::kIndexedLiteralHeader,
+ "date", "Mon, 21 Oct 2013 20:13:21 GMT"},
+ HpackHeaderEntry{HpackEntryType::kIndexedLiteralHeader,
+ "location", "https://www.example.com"},
+ }));
+
+ // Dynamic Table (after decoding):
+ //
+ // [ 1] (s = 63) location: https://www.example.com
+ // [ 2] (s = 65) date: Mon, 21 Oct 2013 20:13:21 GMT
+ // [ 3] (s = 52) cache-control: private
+ // [ 4] (s = 42) :status: 302
+ // Table size: 222
+ ASSERT_TRUE(
+ VerifyDynamicTableContents({{"location", "https://www.example.com"},
+ {"date", "Mon, 21 Oct 2013 20:13:21 GMT"},
+ {"cache-control", "private"},
+ {":status", "302"}}));
+ ASSERT_EQ(222u, current_header_table_size());
+
+ // C.5.2 Second Response
+ //
+ // The (":status", "302") header field is evicted from the dynamic table to
+ // free space to allow adding the (":status", "307") header field.
+ //
+ // Header list to encode:
+ //
+ // :status: 307
+ // cache-control: private
+ // date: Mon, 21 Oct 2013 20:13:21 GMT
+ // location: https://www.example.com
+ hpack_block = HpackExampleToStringOrDie(R"(
+ 48 | == Literal indexed ==
+ | Indexed name (idx = 8)
+ | :status
+ 03 | Literal value (len = 3)
+ 3330 37 | 307
+ | - evict: :status: 302
+ | -> :status: 307
+ c1 | == Indexed - Add ==
+ | idx = 65
+ | -> cache-control: private
+ c0 | == Indexed - Add ==
+ | idx = 64
+ | -> date: Mon, 21 Oct 2013
+ | 20:13:21 GMT
+ bf | == Indexed - Add ==
+ | idx = 63
+ | -> location:
+ | https://www.example.com
+ )");
+ EXPECT_TRUE(DecodeBlock(hpack_block));
+ ASSERT_THAT(header_entries_,
+ ElementsAreArray({
+ HpackHeaderEntry{HpackEntryType::kIndexedLiteralHeader,
+ ":status", "307"},
+ HpackHeaderEntry{HpackEntryType::kIndexedHeader,
+ "cache-control", "private"},
+ HpackHeaderEntry{HpackEntryType::kIndexedHeader, "date",
+ "Mon, 21 Oct 2013 20:13:21 GMT"},
+ HpackHeaderEntry{HpackEntryType::kIndexedHeader, "location",
+ "https://www.example.com"},
+ }));
+
+ // Dynamic Table (after decoding):
+ //
+ // [ 1] (s = 42) :status: 307
+ // [ 2] (s = 63) location: https://www.example.com
+ // [ 3] (s = 65) date: Mon, 21 Oct 2013 20:13:21 GMT
+ // [ 4] (s = 52) cache-control: private
+ // Table size: 222
+ ASSERT_TRUE(
+ VerifyDynamicTableContents({{":status", "307"},
+ {"location", "https://www.example.com"},
+ {"date", "Mon, 21 Oct 2013 20:13:21 GMT"},
+ {"cache-control", "private"}}));
+ ASSERT_EQ(222u, current_header_table_size());
+
+ // C.5.3 Third Response
+ //
+ // Several header fields are evicted from the dynamic table during the
+ // processing of this header list.
+ //
+ // Header list to encode:
+ //
+ // :status: 200
+ // cache-control: private
+ // date: Mon, 21 Oct 2013 20:13:22 GMT
+ // location: https://www.example.com
+ // content-encoding: gzip
+ // set-cookie: foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1
+ hpack_block = HpackExampleToStringOrDie(R"(
+ 88 | == Indexed - Add ==
+ | idx = 8
+ | -> :status: 200
+ c1 | == Indexed - Add ==
+ | idx = 65
+ | -> cache-control: private
+ 61 | == Literal indexed ==
+ | Indexed name (idx = 33)
+ | date
+ 1d | Literal value (len = 29)
+ 4d6f 6e2c 2032 3120 4f63 7420 3230 3133 | Mon, 21 Oct 2013
+ 2032 303a 3133 3a32 3220 474d 54 | 20:13:22 GMT
+ | - evict: cache-control:
+ | private
+ | -> date: Mon, 21 Oct 2013
+ | 20:13:22 GMT
+ c0 | == Indexed - Add ==
+ | idx = 64
+ | -> location:
+ | https://www.example.com
+ 5a | == Literal indexed ==
+ | Indexed name (idx = 26)
+ | content-encoding
+ 04 | Literal value (len = 4)
+ 677a 6970 | gzip
+ | - evict: date: Mon, 21 Oct
+ | 2013 20:13:21 GMT
+ | -> content-encoding: gzip
+ 77 | == Literal indexed ==
+ | Indexed name (idx = 55)
+ | set-cookie
+ 38 | Literal value (len = 56)
+ 666f 6f3d 4153 444a 4b48 514b 425a 584f | foo=ASDJKHQKBZXO
+ 5157 454f 5049 5541 5851 5745 4f49 553b | QWEOPIUAXQWEOIU;
+ 206d 6178 2d61 6765 3d33 3630 303b 2076 | max-age=3600; v
+ 6572 7369 6f6e 3d31 | ersion=1
+ | - evict: location:
+ | https://www.example.com
+ | - evict: :status: 307
+ | -> set-cookie: foo=ASDJKHQ
+ | KBZXOQWEOPIUAXQWEOIU; ma
+ | x-age=3600; version=1
+ )");
+ EXPECT_TRUE(DecodeBlock(hpack_block));
+ ASSERT_THAT(
+ header_entries_,
+ ElementsAreArray({
+ HpackHeaderEntry{HpackEntryType::kIndexedHeader, ":status", "200"},
+ HpackHeaderEntry{HpackEntryType::kIndexedHeader, "cache-control",
+ "private"},
+ HpackHeaderEntry{HpackEntryType::kIndexedLiteralHeader, "date",
+ "Mon, 21 Oct 2013 20:13:22 GMT"},
+ HpackHeaderEntry{HpackEntryType::kIndexedHeader, "location",
+ "https://www.example.com"},
+ HpackHeaderEntry{HpackEntryType::kIndexedLiteralHeader,
+ "content-encoding", "gzip"},
+ HpackHeaderEntry{
+ HpackEntryType::kIndexedLiteralHeader, "set-cookie",
+ "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1"},
+ }));
+
+ // Dynamic Table (after decoding):
+ //
+ // [ 1] (s = 98) set-cookie: foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU;
+ // max-age=3600; version=1
+ // [ 2] (s = 52) content-encoding: gzip
+ // [ 3] (s = 65) date: Mon, 21 Oct 2013 20:13:22 GMT
+ // Table size: 215
+ ASSERT_TRUE(VerifyDynamicTableContents(
+ {{"set-cookie",
+ "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1"},
+ {"content-encoding", "gzip"},
+ {"date", "Mon, 21 Oct 2013 20:13:22 GMT"}}));
+ ASSERT_EQ(215u, current_header_table_size());
+}
+
+// Confirm that the table size can be changed, but at most twice.
+TEST_P(HpackDecoderTest, ProcessesOptionalTableSizeUpdates) {
+ EXPECT_EQ(Http2SettingsInfo::DefaultHeaderTableSize(),
+ header_table_size_limit());
+ // One update allowed.
+ {
+ HpackBlockBuilder hbb;
+ hbb.AppendDynamicTableSizeUpdate(3000);
+ EXPECT_TRUE(DecodeBlock(hbb.buffer()));
+ EXPECT_EQ(3000u, header_table_size_limit());
+ EXPECT_EQ(0u, current_header_table_size());
+ EXPECT_TRUE(header_entries_.empty());
+ }
+ // Two updates allowed.
+ {
+ HpackBlockBuilder hbb;
+ hbb.AppendDynamicTableSizeUpdate(2000);
+ hbb.AppendDynamicTableSizeUpdate(2500);
+ EXPECT_TRUE(DecodeBlock(hbb.buffer()));
+ EXPECT_EQ(2500u, header_table_size_limit());
+ EXPECT_EQ(0u, current_header_table_size());
+ EXPECT_TRUE(header_entries_.empty());
+ }
+ // A third update in the same HPACK block is rejected, so the final
+ // size is 1000, not 500.
+ {
+ HpackBlockBuilder hbb;
+ hbb.AppendDynamicTableSizeUpdate(1500);
+ hbb.AppendDynamicTableSizeUpdate(1000);
+ hbb.AppendDynamicTableSizeUpdate(500);
+ EXPECT_FALSE(DecodeBlock(hbb.buffer()));
+ EXPECT_EQ(1u, error_messages_.size());
+ EXPECT_THAT(error_messages_[0], HasSubstr("size update not allowed"));
+ EXPECT_EQ(1000u, header_table_size_limit());
+ EXPECT_EQ(0u, current_header_table_size());
+ EXPECT_TRUE(header_entries_.empty());
+ }
+ // An error has been detected, so calls to HpackDecoder::DecodeFragment
+ // should return immediately.
+ DecodeBuffer db("\x80");
+ EXPECT_FALSE(decoder_.DecodeFragment(&db));
+ EXPECT_EQ(0u, db.Offset());
+ EXPECT_EQ(1u, error_messages_.size());
+}
+
+// Confirm that the table size can be changed when required, but at most twice.
+TEST_P(HpackDecoderTest, ProcessesRequiredTableSizeUpdate) {
+ // One update required, two allowed, one provided, followed by a header.
+ decoder_.ApplyHeaderTableSizeSetting(1024);
+ decoder_.ApplyHeaderTableSizeSetting(2048);
+ EXPECT_EQ(Http2SettingsInfo::DefaultHeaderTableSize(),
+ header_table_size_limit());
+ {
+ HpackBlockBuilder hbb;
+ hbb.AppendDynamicTableSizeUpdate(1024);
+ hbb.AppendIndexedHeader(4); // :path: /
+ EXPECT_TRUE(DecodeBlock(hbb.buffer()));
+ EXPECT_THAT(header_entries_,
+ ElementsAreArray({HpackHeaderEntry{
+ HpackEntryType::kIndexedHeader, ":path", "/"}}));
+ EXPECT_EQ(1024u, header_table_size_limit());
+ EXPECT_EQ(0u, current_header_table_size());
+ }
+ // One update required, two allowed, two provided, followed by a header.
+ decoder_.ApplyHeaderTableSizeSetting(1000);
+ decoder_.ApplyHeaderTableSizeSetting(1500);
+ {
+ HpackBlockBuilder hbb;
+ hbb.AppendDynamicTableSizeUpdate(500);
+ hbb.AppendDynamicTableSizeUpdate(1250);
+ hbb.AppendIndexedHeader(5); // :path: /index.html
+ EXPECT_TRUE(DecodeBlock(hbb.buffer()));
+ EXPECT_THAT(header_entries_,
+ ElementsAreArray({HpackHeaderEntry{
+ HpackEntryType::kIndexedHeader, ":path", "/index.html"}}));
+ EXPECT_EQ(1250u, header_table_size_limit());
+ EXPECT_EQ(0u, current_header_table_size());
+ }
+ // One update required, two allowed, three provided, followed by a header.
+ // The third update is rejected, so the final size is 1000, not 500.
+ decoder_.ApplyHeaderTableSizeSetting(500);
+ decoder_.ApplyHeaderTableSizeSetting(1000);
+ {
+ HpackBlockBuilder hbb;
+ hbb.AppendDynamicTableSizeUpdate(200);
+ hbb.AppendDynamicTableSizeUpdate(700);
+ hbb.AppendDynamicTableSizeUpdate(900);
+ hbb.AppendIndexedHeader(5); // Not decoded.
+ EXPECT_FALSE(DecodeBlock(hbb.buffer()));
+ EXPECT_FALSE(saw_end_);
+ EXPECT_EQ(1u, error_messages_.size());
+ EXPECT_THAT(error_messages_[0], HasSubstr("size update not allowed"));
+ EXPECT_EQ(700u, header_table_size_limit());
+ EXPECT_EQ(0u, current_header_table_size());
+ EXPECT_TRUE(header_entries_.empty());
+ }
+ // Now that an error has been detected, StartDecodingBlock should return
+ // false.
+ EXPECT_FALSE(decoder_.StartDecodingBlock());
+}
+
+// Confirm that required size updates are validated.
+TEST_P(HpackDecoderTest, InvalidRequiredSizeUpdate) {
+ // Require a size update, but provide one that isn't small enough (must be
+ // zero or one, in this case).
+ decoder_.ApplyHeaderTableSizeSetting(1);
+ decoder_.ApplyHeaderTableSizeSetting(1024);
+ HpackBlockBuilder hbb;
+ hbb.AppendDynamicTableSizeUpdate(2);
+ EXPECT_TRUE(decoder_.StartDecodingBlock());
+ DecodeBuffer db(hbb.buffer());
+ EXPECT_FALSE(decoder_.DecodeFragment(&db));
+ EXPECT_FALSE(saw_end_);
+ EXPECT_EQ(1u, error_messages_.size());
+ EXPECT_THAT(error_messages_[0], HasSubstr("above low water mark"));
+ EXPECT_EQ(Http2SettingsInfo::DefaultHeaderTableSize(),
+ header_table_size_limit());
+}
+
+// Confirm that required size updates are indeed required before the end.
+TEST_P(HpackDecoderTest, RequiredTableSizeChangeBeforeEnd) {
+ decoder_.ApplyHeaderTableSizeSetting(1024);
+ EXPECT_FALSE(DecodeBlock(""));
+ EXPECT_EQ(1u, error_messages_.size());
+ EXPECT_THAT(error_messages_[0],
+ HasSubstr("Missing dynamic table size update"));
+ EXPECT_FALSE(saw_end_);
+}
+
+// Confirm that required size updates are indeed required before an
+// indexed header.
+TEST_P(HpackDecoderTest, RequiredTableSizeChangeBeforeIndexedHeader) {
+ decoder_.ApplyHeaderTableSizeSetting(1024);
+ HpackBlockBuilder hbb;
+ hbb.AppendIndexedHeader(1);
+ EXPECT_FALSE(DecodeBlock(hbb.buffer()));
+ EXPECT_EQ(1u, error_messages_.size());
+ EXPECT_THAT(error_messages_[0],
+ HasSubstr("Missing dynamic table size update"));
+ EXPECT_FALSE(saw_end_);
+ EXPECT_TRUE(header_entries_.empty());
+}
+
+// Confirm that required size updates are indeed required before an indexed
+// header name.
+// TODO(jamessynge): Move some of these to hpack_decoder_state_test.cc.
+TEST_P(HpackDecoderTest, RequiredTableSizeChangeBeforeIndexedHeaderName) {
+ decoder_.ApplyHeaderTableSizeSetting(1024);
+ HpackBlockBuilder hbb;
+ hbb.AppendNameIndexAndLiteralValue(HpackEntryType::kIndexedLiteralHeader, 2,
+ false, "PUT");
+ EXPECT_FALSE(DecodeBlock(hbb.buffer()));
+ EXPECT_EQ(1u, error_messages_.size());
+ EXPECT_THAT(error_messages_[0],
+ HasSubstr("Missing dynamic table size update"));
+ EXPECT_FALSE(saw_end_);
+ EXPECT_TRUE(header_entries_.empty());
+}
+
+// Confirm that required size updates are indeed required before a literal
+// header name.
+TEST_P(HpackDecoderTest, RequiredTableSizeChangeBeforeLiteralName) {
+ decoder_.ApplyHeaderTableSizeSetting(1024);
+ HpackBlockBuilder hbb;
+ hbb.AppendLiteralNameAndValue(HpackEntryType::kNeverIndexedLiteralHeader,
+ false, "name", false, "some data.");
+ EXPECT_FALSE(DecodeBlock(hbb.buffer()));
+ EXPECT_EQ(1u, error_messages_.size());
+ EXPECT_THAT(error_messages_[0],
+ HasSubstr("Missing dynamic table size update"));
+ EXPECT_FALSE(saw_end_);
+ EXPECT_TRUE(header_entries_.empty());
+}
+
+// Confirm that an excessively long varint is detected, in this case an
+// index of 127, but with lots of additional high-order 0 bits provided,
+// too many to be allowed.
+TEST_P(HpackDecoderTest, InvalidIndexedHeaderVarint) {
+ EXPECT_TRUE(decoder_.StartDecodingBlock());
+ DecodeBuffer db("\xff\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x00");
+ EXPECT_FALSE(decoder_.DecodeFragment(&db));
+ EXPECT_TRUE(decoder_.error_detected());
+ EXPECT_FALSE(saw_end_);
+ EXPECT_EQ(1u, error_messages_.size());
+ EXPECT_THAT(error_messages_[0], HasSubstr("malformed"));
+ EXPECT_TRUE(header_entries_.empty());
+ // Now that an error has been detected, EndDecodingBlock should not succeed.
+ EXPECT_FALSE(decoder_.EndDecodingBlock());
+}
+
+// Confirm that an invalid index into the tables is detected, in this case an
+// index of 0.
+TEST_P(HpackDecoderTest, InvalidIndex) {
+ EXPECT_TRUE(decoder_.StartDecodingBlock());
+ DecodeBuffer db("\x80");
+ EXPECT_FALSE(decoder_.DecodeFragment(&db));
+ EXPECT_TRUE(decoder_.error_detected());
+ EXPECT_FALSE(saw_end_);
+ EXPECT_EQ(1u, error_messages_.size());
+ EXPECT_THAT(error_messages_[0], HasSubstr("Invalid index"));
+ EXPECT_TRUE(header_entries_.empty());
+ // Now that an error has been detected, EndDecodingBlock should not succeed.
+ EXPECT_FALSE(decoder_.EndDecodingBlock());
+}
+
+// Confirm that EndDecodingBlock detects a truncated HPACK block.
+TEST_P(HpackDecoderTest, TruncatedBlock) {
+ HpackBlockBuilder hbb;
+ hbb.AppendDynamicTableSizeUpdate(3000);
+ EXPECT_EQ(3u, hbb.size());
+ hbb.AppendDynamicTableSizeUpdate(4000);
+ EXPECT_EQ(6u, hbb.size());
+ // Decodes this block if the whole thing is provided.
+ EXPECT_TRUE(DecodeBlock(hbb.buffer()));
+ EXPECT_EQ(4000u, header_table_size_limit());
+ // Multiple times even.
+ EXPECT_TRUE(DecodeBlock(hbb.buffer()));
+ EXPECT_EQ(4000u, header_table_size_limit());
+ // But not if the block is truncated.
+ EXPECT_FALSE(DecodeBlock(hbb.buffer().substr(0, hbb.size() - 1)));
+ EXPECT_FALSE(saw_end_);
+ EXPECT_EQ(1u, error_messages_.size());
+ EXPECT_THAT(error_messages_[0], HasSubstr("truncated"));
+ // The first update was decoded.
+ EXPECT_EQ(3000u, header_table_size_limit());
+ EXPECT_EQ(0u, current_header_table_size());
+ EXPECT_TRUE(header_entries_.empty());
+}
+
+// Confirm that an oversized string is detected, ending decoding.
+TEST_P(HpackDecoderTest, OversizeStringDetected) {
+ HpackBlockBuilder hbb;
+ hbb.AppendLiteralNameAndValue(HpackEntryType::kNeverIndexedLiteralHeader,
+ false, "name", false, "some data.");
+ hbb.AppendLiteralNameAndValue(HpackEntryType::kUnindexedLiteralHeader, false,
+ "name2", false, "longer data");
+
+ // Normally able to decode this block.
+ EXPECT_TRUE(DecodeBlock(hbb.buffer()));
+ EXPECT_THAT(header_entries_,
+ ElementsAreArray(
+ {HpackHeaderEntry{HpackEntryType::kNeverIndexedLiteralHeader,
+ "name", "some data."},
+ HpackHeaderEntry{HpackEntryType::kUnindexedLiteralHeader,
+ "name2", "longer data"}}));
+
+ // But not if the maximum size of strings is less than the longest string.
+ decoder_.set_max_string_size_bytes(10);
+ EXPECT_FALSE(DecodeBlock(hbb.buffer()));
+ EXPECT_THAT(
+ header_entries_,
+ ElementsAreArray({HpackHeaderEntry{
+ HpackEntryType::kNeverIndexedLiteralHeader, "name", "some data."}}));
+ EXPECT_FALSE(saw_end_);
+ EXPECT_EQ(1u, error_messages_.size());
+ EXPECT_THAT(error_messages_[0], HasSubstr("too long"));
+}
+
+} // namespace
+} // namespace test
+} // namespace http2
diff --git a/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_collector.cc b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_collector.cc
new file mode 100644
index 00000000000..9d17465577f
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_collector.cc
@@ -0,0 +1,301 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_collector.h"
+
+#include "base/logging.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_string_collector.h"
+#include "net/third_party/quiche/src/http2/hpack/http2_hpack_constants.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string_utils.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_test_helpers.h"
+
+using ::testing::AssertionResult;
+
+namespace http2 {
+namespace test {
+namespace {
+
+const HpackEntryType kInvalidHeaderType = static_cast<HpackEntryType>(99);
+const size_t kInvalidIndex = 99999999;
+
+} // namespace
+
+HpackEntryCollector::HpackEntryCollector() {
+ Clear();
+}
+
+HpackEntryCollector::HpackEntryCollector(const HpackEntryCollector& other) =
+ default;
+
+HpackEntryCollector::HpackEntryCollector(HpackEntryType type,
+ size_t index_or_size)
+ : header_type_(type), index_(index_or_size), started_(true), ended_(true) {}
+HpackEntryCollector::HpackEntryCollector(HpackEntryType type,
+ size_t index,
+ bool value_huffman,
+ const Http2String& value)
+ : header_type_(type),
+ index_(index),
+ value_(value, value_huffman),
+ started_(true),
+ ended_(true) {}
+HpackEntryCollector::HpackEntryCollector(HpackEntryType type,
+ bool name_huffman,
+ const Http2String& name,
+ bool value_huffman,
+ const Http2String& value)
+ : header_type_(type),
+ index_(0),
+ name_(name, name_huffman),
+ value_(value, value_huffman),
+ started_(true),
+ ended_(true) {}
+
+HpackEntryCollector::~HpackEntryCollector() = default;
+
+void HpackEntryCollector::OnIndexedHeader(size_t index) {
+ ASSERT_FALSE(started_);
+ ASSERT_TRUE(IsClear()) << ToString();
+ Init(HpackEntryType::kIndexedHeader, index);
+ ended_ = true;
+}
+void HpackEntryCollector::OnStartLiteralHeader(HpackEntryType header_type,
+ size_t maybe_name_index) {
+ ASSERT_FALSE(started_);
+ ASSERT_TRUE(IsClear()) << ToString();
+ Init(header_type, maybe_name_index);
+}
+void HpackEntryCollector::OnNameStart(bool huffman_encoded, size_t len) {
+ ASSERT_TRUE(started_);
+ ASSERT_FALSE(ended_);
+ ASSERT_FALSE(IsClear());
+ ASSERT_TRUE(LiteralNameExpected()) << ToString();
+ name_.OnStringStart(huffman_encoded, len);
+}
+void HpackEntryCollector::OnNameData(const char* data, size_t len) {
+ ASSERT_TRUE(started_);
+ ASSERT_FALSE(ended_);
+ ASSERT_TRUE(LiteralNameExpected()) << ToString();
+ ASSERT_TRUE(name_.IsInProgress());
+ name_.OnStringData(data, len);
+}
+void HpackEntryCollector::OnNameEnd() {
+ ASSERT_TRUE(started_);
+ ASSERT_FALSE(ended_);
+ ASSERT_TRUE(LiteralNameExpected()) << ToString();
+ ASSERT_TRUE(name_.IsInProgress());
+ name_.OnStringEnd();
+}
+void HpackEntryCollector::OnValueStart(bool huffman_encoded, size_t len) {
+ ASSERT_TRUE(started_);
+ ASSERT_FALSE(ended_);
+ if (LiteralNameExpected()) {
+ ASSERT_TRUE(name_.HasEnded());
+ }
+ ASSERT_TRUE(LiteralValueExpected()) << ToString();
+ ASSERT_TRUE(value_.IsClear()) << value_.ToString();
+ value_.OnStringStart(huffman_encoded, len);
+}
+void HpackEntryCollector::OnValueData(const char* data, size_t len) {
+ ASSERT_TRUE(started_);
+ ASSERT_FALSE(ended_);
+ ASSERT_TRUE(LiteralValueExpected()) << ToString();
+ ASSERT_TRUE(value_.IsInProgress());
+ value_.OnStringData(data, len);
+}
+void HpackEntryCollector::OnValueEnd() {
+ ASSERT_TRUE(started_);
+ ASSERT_FALSE(ended_);
+ ASSERT_TRUE(LiteralValueExpected()) << ToString();
+ ASSERT_TRUE(value_.IsInProgress());
+ value_.OnStringEnd();
+ ended_ = true;
+}
+void HpackEntryCollector::OnDynamicTableSizeUpdate(size_t size) {
+ ASSERT_FALSE(started_);
+ ASSERT_TRUE(IsClear()) << ToString();
+ Init(HpackEntryType::kDynamicTableSizeUpdate, size);
+ ended_ = true;
+}
+
+void HpackEntryCollector::Clear() {
+ header_type_ = kInvalidHeaderType;
+ index_ = kInvalidIndex;
+ name_.Clear();
+ value_.Clear();
+ started_ = ended_ = false;
+}
+bool HpackEntryCollector::IsClear() const {
+ return header_type_ == kInvalidHeaderType && index_ == kInvalidIndex &&
+ name_.IsClear() && value_.IsClear() && !started_ && !ended_;
+}
+bool HpackEntryCollector::IsComplete() const {
+ return started_ && ended_;
+}
+bool HpackEntryCollector::LiteralNameExpected() const {
+ switch (header_type_) {
+ case HpackEntryType::kIndexedLiteralHeader:
+ case HpackEntryType::kUnindexedLiteralHeader:
+ case HpackEntryType::kNeverIndexedLiteralHeader:
+ return index_ == 0;
+ default:
+ return false;
+ }
+}
+bool HpackEntryCollector::LiteralValueExpected() const {
+ switch (header_type_) {
+ case HpackEntryType::kIndexedLiteralHeader:
+ case HpackEntryType::kUnindexedLiteralHeader:
+ case HpackEntryType::kNeverIndexedLiteralHeader:
+ return true;
+ default:
+ return false;
+ }
+}
+AssertionResult HpackEntryCollector::ValidateIndexedHeader(
+ size_t expected_index) const {
+ VERIFY_TRUE(started_);
+ VERIFY_TRUE(ended_);
+ VERIFY_EQ(HpackEntryType::kIndexedHeader, header_type_);
+ VERIFY_EQ(expected_index, index_);
+ return ::testing::AssertionSuccess();
+}
+AssertionResult HpackEntryCollector::ValidateLiteralValueHeader(
+ HpackEntryType expected_type,
+ size_t expected_index,
+ bool expected_value_huffman,
+ Http2StringPiece expected_value) const {
+ VERIFY_TRUE(started_);
+ VERIFY_TRUE(ended_);
+ VERIFY_EQ(expected_type, header_type_);
+ VERIFY_NE(0u, expected_index);
+ VERIFY_EQ(expected_index, index_);
+ VERIFY_TRUE(name_.IsClear());
+ VERIFY_SUCCESS(value_.Collected(expected_value, expected_value_huffman));
+ return ::testing::AssertionSuccess();
+}
+AssertionResult HpackEntryCollector::ValidateLiteralNameValueHeader(
+ HpackEntryType expected_type,
+ bool expected_name_huffman,
+ Http2StringPiece expected_name,
+ bool expected_value_huffman,
+ Http2StringPiece expected_value) const {
+ VERIFY_TRUE(started_);
+ VERIFY_TRUE(ended_);
+ VERIFY_EQ(expected_type, header_type_);
+ VERIFY_EQ(0u, index_);
+ VERIFY_SUCCESS(name_.Collected(expected_name, expected_name_huffman));
+ VERIFY_SUCCESS(value_.Collected(expected_value, expected_value_huffman));
+ return ::testing::AssertionSuccess();
+}
+AssertionResult HpackEntryCollector::ValidateDynamicTableSizeUpdate(
+ size_t size) const {
+ VERIFY_TRUE(started_);
+ VERIFY_TRUE(ended_);
+ VERIFY_EQ(HpackEntryType::kDynamicTableSizeUpdate, header_type_);
+ VERIFY_EQ(index_, size);
+ return ::testing::AssertionSuccess();
+}
+
+void HpackEntryCollector::AppendToHpackBlockBuilder(
+ HpackBlockBuilder* hbb) const {
+ ASSERT_TRUE(started_ && ended_) << *this;
+ switch (header_type_) {
+ case HpackEntryType::kIndexedHeader:
+ hbb->AppendIndexedHeader(index_);
+ return;
+
+ case HpackEntryType::kDynamicTableSizeUpdate:
+ hbb->AppendDynamicTableSizeUpdate(index_);
+ return;
+
+ case HpackEntryType::kIndexedLiteralHeader:
+ case HpackEntryType::kUnindexedLiteralHeader:
+ case HpackEntryType::kNeverIndexedLiteralHeader:
+ ASSERT_TRUE(value_.HasEnded()) << *this;
+ if (index_ != 0) {
+ CHECK(name_.IsClear());
+ hbb->AppendNameIndexAndLiteralValue(header_type_, index_,
+ value_.huffman_encoded, value_.s);
+ } else {
+ CHECK(name_.HasEnded()) << *this;
+ hbb->AppendLiteralNameAndValue(header_type_, name_.huffman_encoded,
+ name_.s, value_.huffman_encoded,
+ value_.s);
+ }
+ return;
+
+ default:
+ ADD_FAILURE() << *this;
+ }
+}
+
+Http2String HpackEntryCollector::ToString() const {
+ Http2String result("Type=");
+ switch (header_type_) {
+ case HpackEntryType::kIndexedHeader:
+ result += "IndexedHeader";
+ break;
+ case HpackEntryType::kDynamicTableSizeUpdate:
+ result += "DynamicTableSizeUpdate";
+ break;
+ case HpackEntryType::kIndexedLiteralHeader:
+ result += "IndexedLiteralHeader";
+ break;
+ case HpackEntryType::kUnindexedLiteralHeader:
+ result += "UnindexedLiteralHeader";
+ break;
+ case HpackEntryType::kNeverIndexedLiteralHeader:
+ result += "NeverIndexedLiteralHeader";
+ break;
+ default:
+ if (header_type_ == kInvalidHeaderType) {
+ result += "<unset>";
+ } else {
+ Http2StrAppend(&result, header_type_);
+ }
+ }
+ if (index_ != 0) {
+ Http2StrAppend(&result, " Index=", index_);
+ }
+ if (!name_.IsClear()) {
+ Http2StrAppend(&result, " Name", name_.ToString());
+ }
+ if (!value_.IsClear()) {
+ Http2StrAppend(&result, " Value", value_.ToString());
+ }
+ if (!started_) {
+ EXPECT_FALSE(ended_);
+ Http2StrAppend(&result, " !started");
+ } else if (!ended_) {
+ Http2StrAppend(&result, " !ended");
+ } else {
+ Http2StrAppend(&result, " Complete");
+ }
+ return result;
+}
+
+void HpackEntryCollector::Init(HpackEntryType type, size_t maybe_index) {
+ ASSERT_TRUE(IsClear()) << ToString();
+ header_type_ = type;
+ index_ = maybe_index;
+ started_ = true;
+}
+
+bool operator==(const HpackEntryCollector& a, const HpackEntryCollector& b) {
+ return a.name() == b.name() && a.value() == b.value() &&
+ a.index() == b.index() && a.header_type() == b.header_type() &&
+ a.started() == b.started() && a.ended() == b.ended();
+}
+bool operator!=(const HpackEntryCollector& a, const HpackEntryCollector& b) {
+ return !(a == b);
+}
+
+std::ostream& operator<<(std::ostream& out, const HpackEntryCollector& v) {
+ return out << v.ToString();
+}
+
+} // namespace test
+} // namespace http2
diff --git a/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_collector.h b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_collector.h
new file mode 100644
index 00000000000..c2c5fe5830d
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_collector.h
@@ -0,0 +1,155 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_HTTP2_HPACK_DECODER_HPACK_ENTRY_COLLECTOR_H_
+#define QUICHE_HTTP2_HPACK_DECODER_HPACK_ENTRY_COLLECTOR_H_
+
+// HpackEntryCollector records calls to HpackEntryDecoderListener in support
+// of tests of HpackEntryDecoder, or which use it. Can only record the callbacks
+// for the decoding of a single entry; call Clear() between decoding successive
+// entries or use a distinct HpackEntryCollector for each entry.
+
+#include <stddef.h>
+
+#include <iosfwd>
+
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_decoder_listener.h"
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_string_collector.h"
+#include "net/third_party/quiche/src/http2/hpack/http2_hpack_constants.h"
+#include "net/third_party/quiche/src/http2/hpack/tools/hpack_block_builder.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string_piece.h"
+
+namespace http2 {
+namespace test {
+
+class HpackEntryCollector : public HpackEntryDecoderListener {
+ public:
+ HpackEntryCollector();
+ HpackEntryCollector(const HpackEntryCollector& other);
+
+ // These next three constructors are intended for use in tests that create
+ // an HpackEntryCollector "manually", and then compare it against another
+ // that is populated via calls to the HpackEntryDecoderListener methods.
+ HpackEntryCollector(HpackEntryType type, size_t index_or_size);
+ HpackEntryCollector(HpackEntryType type,
+ size_t index,
+ bool value_huffman,
+ const Http2String& value);
+ HpackEntryCollector(HpackEntryType type,
+ bool name_huffman,
+ const Http2String& name,
+ bool value_huffman,
+ const Http2String& value);
+
+ ~HpackEntryCollector() override;
+
+ // Methods defined by HpackEntryDecoderListener.
+ void OnIndexedHeader(size_t index) override;
+ void OnStartLiteralHeader(HpackEntryType header_type,
+ size_t maybe_name_index) override;
+ void OnNameStart(bool huffman_encoded, size_t len) override;
+ void OnNameData(const char* data, size_t len) override;
+ void OnNameEnd() override;
+ void OnValueStart(bool huffman_encoded, size_t len) override;
+ void OnValueData(const char* data, size_t len) override;
+ void OnValueEnd() override;
+ void OnDynamicTableSizeUpdate(size_t size) override;
+
+ // Clears the fields of the collector so that it is ready to start collecting
+ // another HPACK block entry.
+ void Clear();
+
+ // Is the collector ready to start collecting another HPACK block entry.
+ bool IsClear() const;
+
+ // Has a complete entry been collected?
+ bool IsComplete() const;
+
+ // Based on the HpackEntryType, is a literal name expected?
+ bool LiteralNameExpected() const;
+
+ // Based on the HpackEntryType, is a literal value expected?
+ bool LiteralValueExpected() const;
+
+ // Returns success if collected an Indexed Header (i.e. OnIndexedHeader was
+ // called).
+ ::testing::AssertionResult ValidateIndexedHeader(size_t expected_index) const;
+
+ // Returns success if collected a Header with an indexed name and literal
+ // value (i.e. OnStartLiteralHeader was called with a non-zero index for
+ // the name, which must match expected_index).
+ ::testing::AssertionResult ValidateLiteralValueHeader(
+ HpackEntryType expected_type,
+ size_t expected_index,
+ bool expected_value_huffman,
+ Http2StringPiece expected_value) const;
+
+ // Returns success if collected a Header with an literal name and literal
+ // value.
+ ::testing::AssertionResult ValidateLiteralNameValueHeader(
+ HpackEntryType expected_type,
+ bool expected_name_huffman,
+ Http2StringPiece expected_name,
+ bool expected_value_huffman,
+ Http2StringPiece expected_value) const;
+
+ // Returns success if collected a Dynamic Table Size Update,
+ // with the specified size.
+ ::testing::AssertionResult ValidateDynamicTableSizeUpdate(
+ size_t expected_size) const;
+
+ void set_header_type(HpackEntryType v) { header_type_ = v; }
+ HpackEntryType header_type() const { return header_type_; }
+
+ void set_index(size_t v) { index_ = v; }
+ size_t index() const { return index_; }
+
+ void set_name(const HpackStringCollector& v) { name_ = v; }
+ const HpackStringCollector& name() const { return name_; }
+
+ void set_value(const HpackStringCollector& v) { value_ = v; }
+ const HpackStringCollector& value() const { return value_; }
+
+ void set_started(bool v) { started_ = v; }
+ bool started() const { return started_; }
+
+ void set_ended(bool v) { ended_ = v; }
+ bool ended() const { return ended_; }
+
+ void AppendToHpackBlockBuilder(HpackBlockBuilder* hbb) const;
+
+ // Returns a debug string.
+ Http2String ToString() const;
+
+ private:
+ void Init(HpackEntryType type, size_t maybe_index);
+
+ HpackEntryType header_type_;
+ size_t index_;
+
+ HpackStringCollector name_;
+ HpackStringCollector value_;
+
+ // True if has received a call to an HpackEntryDecoderListener method
+ // indicating the start of decoding an HPACK entry; for example,
+ // OnIndexedHeader set it true, but OnNameStart does not change it.
+ bool started_ = false;
+
+ // True if has received a call to an HpackEntryDecoderListener method
+ // indicating the end of decoding an HPACK entry; for example,
+ // OnIndexedHeader and OnValueEnd both set it true, but OnNameEnd does
+ // not change it.
+ bool ended_ = false;
+};
+
+bool operator==(const HpackEntryCollector& a, const HpackEntryCollector& b);
+bool operator!=(const HpackEntryCollector& a, const HpackEntryCollector& b);
+std::ostream& operator<<(std::ostream& out, const HpackEntryCollector& v);
+
+} // namespace test
+} // namespace http2
+
+#endif // QUICHE_HTTP2_HPACK_DECODER_HPACK_ENTRY_COLLECTOR_H_
diff --git a/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_decoder.cc b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_decoder.cc
new file mode 100644
index 00000000000..96b9f066030
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_decoder.cc
@@ -0,0 +1,267 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_decoder.h"
+
+#include <stddef.h>
+
+#include <cstdint>
+
+#include "base/logging.h"
+#include "base/macros.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_bug_tracker.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_macros.h"
+
+namespace http2 {
+namespace {
+// Converts calls from HpackStringDecoder when decoding a header name into the
+// appropriate HpackEntryDecoderListener::OnName* calls.
+class NameDecoderListener {
+ public:
+ explicit NameDecoderListener(HpackEntryDecoderListener* listener)
+ : listener_(listener) {}
+ bool OnStringStart(bool huffman_encoded, size_t len) {
+ listener_->OnNameStart(huffman_encoded, len);
+ return true;
+ }
+ void OnStringData(const char* data, size_t len) {
+ listener_->OnNameData(data, len);
+ }
+ void OnStringEnd() { listener_->OnNameEnd(); }
+
+ private:
+ HpackEntryDecoderListener* listener_;
+};
+
+// Converts calls from HpackStringDecoder when decoding a header value into
+// the appropriate HpackEntryDecoderListener::OnValue* calls.
+class ValueDecoderListener {
+ public:
+ explicit ValueDecoderListener(HpackEntryDecoderListener* listener)
+ : listener_(listener) {}
+ bool OnStringStart(bool huffman_encoded, size_t len) {
+ listener_->OnValueStart(huffman_encoded, len);
+ return true;
+ }
+ void OnStringData(const char* data, size_t len) {
+ listener_->OnValueData(data, len);
+ }
+ void OnStringEnd() { listener_->OnValueEnd(); }
+
+ private:
+ HpackEntryDecoderListener* listener_;
+};
+} // namespace
+
+DecodeStatus HpackEntryDecoder::Start(DecodeBuffer* db,
+ HpackEntryDecoderListener* listener) {
+ DCHECK(db != nullptr);
+ DCHECK(listener != nullptr);
+ DCHECK(db->HasData());
+ DecodeStatus status = entry_type_decoder_.Start(db);
+ switch (status) {
+ case DecodeStatus::kDecodeDone:
+ // The type of the entry and its varint fit into the current decode
+ // buffer.
+ if (entry_type_decoder_.entry_type() == HpackEntryType::kIndexedHeader) {
+ // The entry consists solely of the entry type and varint.
+ // This is by far the most common case in practice.
+ listener->OnIndexedHeader(entry_type_decoder_.varint());
+ return DecodeStatus::kDecodeDone;
+ }
+ state_ = EntryDecoderState::kDecodedType;
+ return Resume(db, listener);
+ case DecodeStatus::kDecodeInProgress:
+ // Hit the end of the decode buffer before fully decoding
+ // the entry type and varint.
+ DCHECK_EQ(0u, db->Remaining());
+ state_ = EntryDecoderState::kResumeDecodingType;
+ return status;
+ case DecodeStatus::kDecodeError:
+ // The varint must have been invalid (too long).
+ return status;
+ }
+
+ HTTP2_BUG << "Unreachable";
+ return DecodeStatus::kDecodeError;
+}
+
+DecodeStatus HpackEntryDecoder::Resume(DecodeBuffer* db,
+ HpackEntryDecoderListener* listener) {
+ DCHECK(db != nullptr);
+ DCHECK(listener != nullptr);
+
+ DecodeStatus status;
+
+ do {
+ switch (state_) {
+ case EntryDecoderState::kResumeDecodingType:
+ // entry_type_decoder_ returned kDecodeInProgress when last called.
+ DVLOG(1) << "kResumeDecodingType: db->Remaining=" << db->Remaining();
+ status = entry_type_decoder_.Resume(db);
+ if (status != DecodeStatus::kDecodeDone) {
+ return status;
+ }
+ state_ = EntryDecoderState::kDecodedType;
+ HTTP2_FALLTHROUGH;
+
+ case EntryDecoderState::kDecodedType:
+ // entry_type_decoder_ returned kDecodeDone, now need to decide how
+ // to proceed.
+ DVLOG(1) << "kDecodedType: db->Remaining=" << db->Remaining();
+ if (DispatchOnType(listener)) {
+ // All done.
+ return DecodeStatus::kDecodeDone;
+ }
+ continue;
+
+ case EntryDecoderState::kStartDecodingName:
+ DVLOG(1) << "kStartDecodingName: db->Remaining=" << db->Remaining();
+ {
+ NameDecoderListener ncb(listener);
+ status = string_decoder_.Start(db, &ncb);
+ }
+ if (status != DecodeStatus::kDecodeDone) {
+ // On the assumption that the status is kDecodeInProgress, set
+ // state_ accordingly; unnecessary if status is kDecodeError, but
+ // that will only happen if the varint encoding the name's length
+ // is too long.
+ state_ = EntryDecoderState::kResumeDecodingName;
+ return status;
+ }
+ state_ = EntryDecoderState::kStartDecodingValue;
+ HTTP2_FALLTHROUGH;
+
+ case EntryDecoderState::kStartDecodingValue:
+ DVLOG(1) << "kStartDecodingValue: db->Remaining=" << db->Remaining();
+ {
+ ValueDecoderListener vcb(listener);
+ status = string_decoder_.Start(db, &vcb);
+ }
+ if (status == DecodeStatus::kDecodeDone) {
+ // Done with decoding the literal value, so we've reached the
+ // end of the header entry.
+ return status;
+ }
+ // On the assumption that the status is kDecodeInProgress, set
+ // state_ accordingly; unnecessary if status is kDecodeError, but
+ // that will only happen if the varint encoding the value's length
+ // is too long.
+ state_ = EntryDecoderState::kResumeDecodingValue;
+ return status;
+
+ case EntryDecoderState::kResumeDecodingName:
+ // The literal name was split across decode buffers.
+ DVLOG(1) << "kResumeDecodingName: db->Remaining=" << db->Remaining();
+ {
+ NameDecoderListener ncb(listener);
+ status = string_decoder_.Resume(db, &ncb);
+ }
+ if (status != DecodeStatus::kDecodeDone) {
+ // On the assumption that the status is kDecodeInProgress, set
+ // state_ accordingly; unnecessary if status is kDecodeError, but
+ // that will only happen if the varint encoding the name's length
+ // is too long.
+ state_ = EntryDecoderState::kResumeDecodingName;
+ return status;
+ }
+ state_ = EntryDecoderState::kStartDecodingValue;
+ break;
+
+ case EntryDecoderState::kResumeDecodingValue:
+ // The literal value was split across decode buffers.
+ DVLOG(1) << "kResumeDecodingValue: db->Remaining=" << db->Remaining();
+ {
+ ValueDecoderListener vcb(listener);
+ status = string_decoder_.Resume(db, &vcb);
+ }
+ if (status == DecodeStatus::kDecodeDone) {
+ // Done with decoding the value, therefore the entry as a whole.
+ return status;
+ }
+ // On the assumption that the status is kDecodeInProgress, set
+ // state_ accordingly; unnecessary if status is kDecodeError, but
+ // that will only happen if the varint encoding the value's length
+ // is too long.
+ state_ = EntryDecoderState::kResumeDecodingValue;
+ return status;
+ }
+ } while (true);
+}
+
+bool HpackEntryDecoder::DispatchOnType(HpackEntryDecoderListener* listener) {
+ const HpackEntryType entry_type = entry_type_decoder_.entry_type();
+ const uint32_t varint = static_cast<uint32_t>(entry_type_decoder_.varint());
+ switch (entry_type) {
+ case HpackEntryType::kIndexedHeader:
+ // The entry consists solely of the entry type and varint. See:
+ // http://httpwg.org/specs/rfc7541.html#indexed.header.representation
+ listener->OnIndexedHeader(varint);
+ return true;
+
+ case HpackEntryType::kIndexedLiteralHeader:
+ case HpackEntryType::kUnindexedLiteralHeader:
+ case HpackEntryType::kNeverIndexedLiteralHeader:
+ // The entry has a literal value, and if the varint is zero also has a
+ // literal name preceding the value. See:
+ // http://httpwg.org/specs/rfc7541.html#literal.header.representation
+ listener->OnStartLiteralHeader(entry_type, varint);
+ if (varint == 0) {
+ state_ = EntryDecoderState::kStartDecodingName;
+ } else {
+ state_ = EntryDecoderState::kStartDecodingValue;
+ }
+ return false;
+
+ case HpackEntryType::kDynamicTableSizeUpdate:
+ // The entry consists solely of the entry type and varint. FWIW, I've
+ // never seen this type of entry in production (primarily browser
+ // traffic) so if you're designing an HPACK successor someday, consider
+ // dropping it or giving it a much longer prefix. See:
+ // http://httpwg.org/specs/rfc7541.html#encoding.context.update
+ listener->OnDynamicTableSizeUpdate(varint);
+ return true;
+ }
+
+ HTTP2_BUG << "Unreachable, entry_type=" << entry_type;
+ return true;
+}
+
+void HpackEntryDecoder::OutputDebugString(std::ostream& out) const {
+ out << "HpackEntryDecoder(state=" << state_ << ", " << entry_type_decoder_
+ << ", " << string_decoder_ << ")";
+}
+
+Http2String HpackEntryDecoder::DebugString() const {
+ std::stringstream s;
+ s << *this;
+ return s.str();
+}
+
+std::ostream& operator<<(std::ostream& out, const HpackEntryDecoder& v) {
+ v.OutputDebugString(out);
+ return out;
+}
+
+std::ostream& operator<<(std::ostream& out,
+ HpackEntryDecoder::EntryDecoderState state) {
+ typedef HpackEntryDecoder::EntryDecoderState EntryDecoderState;
+ switch (state) {
+ case EntryDecoderState::kResumeDecodingType:
+ return out << "kResumeDecodingType";
+ case EntryDecoderState::kDecodedType:
+ return out << "kDecodedType";
+ case EntryDecoderState::kStartDecodingName:
+ return out << "kStartDecodingName";
+ case EntryDecoderState::kResumeDecodingName:
+ return out << "kResumeDecodingName";
+ case EntryDecoderState::kStartDecodingValue:
+ return out << "kStartDecodingValue";
+ case EntryDecoderState::kResumeDecodingValue:
+ return out << "kResumeDecodingValue";
+ }
+ return out << static_cast<int>(state);
+}
+
+} // namespace http2
diff --git a/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_decoder.h b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_decoder.h
new file mode 100644
index 00000000000..fe59d964841
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_decoder.h
@@ -0,0 +1,85 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_HTTP2_HPACK_DECODER_HPACK_ENTRY_DECODER_H_
+#define QUICHE_HTTP2_HPACK_DECODER_HPACK_ENTRY_DECODER_H_
+
+// HpackEntryDecoder decodes a single HPACK entry (i.e. one header or one
+// dynamic table size update), in a resumable fashion. The first call, Start(),
+// must provide a non-empty decode buffer. Continue with calls to Resume() if
+// Start, and any subsequent calls to Resume, returns kDecodeInProgress.
+
+#include "base/logging.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_status.h"
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_decoder_listener.h"
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_type_decoder.h"
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_string_decoder.h"
+#include "net/third_party/quiche/src/http2/hpack/http2_hpack_constants.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_export.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string.h"
+
+namespace http2 {
+
+class HTTP2_EXPORT_PRIVATE HpackEntryDecoder {
+ public:
+ enum class EntryDecoderState {
+ // Have started decoding the type/varint, but didn't finish on the previous
+ // attempt. Next state is kResumeDecodingType or kDecodedType.
+ kResumeDecodingType,
+
+ // Have just finished decoding the type/varint. Final state if the type is
+ // kIndexedHeader or kDynamicTableSizeUpdate. Otherwise, the next state is
+ // kStartDecodingName (if the varint is 0), else kStartDecodingValue.
+ kDecodedType,
+
+ // Ready to start decoding the literal name of a header entry. Next state
+ // is kResumeDecodingName (if the name is split across decode buffers),
+ // else kStartDecodingValue.
+ kStartDecodingName,
+
+ // Resume decoding the literal name of a header that is split across decode
+ // buffers.
+ kResumeDecodingName,
+
+ // Ready to start decoding the literal value of a header entry. Final state
+ // if the value string is entirely in the decode buffer, else the next state
+ // is kResumeDecodingValue.
+ kStartDecodingValue,
+
+ // Resume decoding the literal value of a header that is split across decode
+ // buffers.
+ kResumeDecodingValue,
+ };
+
+ // Only call when the decode buffer has data (i.e. HpackBlockDecoder must
+ // not call until there is data).
+ DecodeStatus Start(DecodeBuffer* db, HpackEntryDecoderListener* listener);
+
+ // Only call Resume if the previous call (Start or Resume) returned
+ // kDecodeInProgress; Resume is also called from Start when it has succeeded
+ // in decoding the entry type and its varint.
+ DecodeStatus Resume(DecodeBuffer* db, HpackEntryDecoderListener* listener);
+
+ Http2String DebugString() const;
+ void OutputDebugString(std::ostream& out) const;
+
+ private:
+ // Implements handling state kDecodedType.
+ bool DispatchOnType(HpackEntryDecoderListener* listener);
+
+ HpackEntryTypeDecoder entry_type_decoder_;
+ HpackStringDecoder string_decoder_;
+ EntryDecoderState state_ = EntryDecoderState();
+};
+
+HTTP2_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& out,
+ const HpackEntryDecoder& v);
+HTTP2_EXPORT_PRIVATE std::ostream& operator<<(
+ std::ostream& out,
+ HpackEntryDecoder::EntryDecoderState state);
+
+} // namespace http2
+
+#endif // QUICHE_HTTP2_HPACK_DECODER_HPACK_ENTRY_DECODER_H_
diff --git a/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_decoder_listener.cc b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_decoder_listener.cc
new file mode 100644
index 00000000000..b783b150da8
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_decoder_listener.cc
@@ -0,0 +1,81 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_decoder_listener.h"
+
+#include "base/logging.h"
+
+namespace http2 {
+
+void HpackEntryDecoderVLoggingListener::OnIndexedHeader(size_t index) {
+ VLOG(1) << "OnIndexedHeader, index=" << index;
+ if (wrapped_) {
+ wrapped_->OnIndexedHeader(index);
+ }
+}
+
+void HpackEntryDecoderVLoggingListener::OnStartLiteralHeader(
+ HpackEntryType entry_type,
+ size_t maybe_name_index) {
+ VLOG(1) << "OnStartLiteralHeader: entry_type=" << entry_type
+ << ", maybe_name_index=" << maybe_name_index;
+ if (wrapped_) {
+ wrapped_->OnStartLiteralHeader(entry_type, maybe_name_index);
+ }
+}
+
+void HpackEntryDecoderVLoggingListener::OnNameStart(bool huffman_encoded,
+ size_t len) {
+ VLOG(1) << "OnNameStart: H=" << huffman_encoded << ", len=" << len;
+ if (wrapped_) {
+ wrapped_->OnNameStart(huffman_encoded, len);
+ }
+}
+
+void HpackEntryDecoderVLoggingListener::OnNameData(const char* data,
+ size_t len) {
+ VLOG(1) << "OnNameData: len=" << len;
+ if (wrapped_) {
+ wrapped_->OnNameData(data, len);
+ }
+}
+
+void HpackEntryDecoderVLoggingListener::OnNameEnd() {
+ VLOG(1) << "OnNameEnd";
+ if (wrapped_) {
+ wrapped_->OnNameEnd();
+ }
+}
+
+void HpackEntryDecoderVLoggingListener::OnValueStart(bool huffman_encoded,
+ size_t len) {
+ VLOG(1) << "OnValueStart: H=" << huffman_encoded << ", len=" << len;
+ if (wrapped_) {
+ wrapped_->OnValueStart(huffman_encoded, len);
+ }
+}
+
+void HpackEntryDecoderVLoggingListener::OnValueData(const char* data,
+ size_t len) {
+ VLOG(1) << "OnValueData: len=" << len;
+ if (wrapped_) {
+ wrapped_->OnValueData(data, len);
+ }
+}
+
+void HpackEntryDecoderVLoggingListener::OnValueEnd() {
+ VLOG(1) << "OnValueEnd";
+ if (wrapped_) {
+ wrapped_->OnValueEnd();
+ }
+}
+
+void HpackEntryDecoderVLoggingListener::OnDynamicTableSizeUpdate(size_t size) {
+ VLOG(1) << "OnDynamicTableSizeUpdate: size=" << size;
+ if (wrapped_) {
+ wrapped_->OnDynamicTableSizeUpdate(size);
+ }
+}
+
+} // namespace http2
diff --git a/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_decoder_listener.h b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_decoder_listener.h
new file mode 100644
index 00000000000..fd11f59ef4a
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_decoder_listener.h
@@ -0,0 +1,110 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_HTTP2_HPACK_DECODER_HPACK_ENTRY_DECODER_LISTENER_H_
+#define QUICHE_HTTP2_HPACK_DECODER_HPACK_ENTRY_DECODER_LISTENER_H_
+
+// Defines HpackEntryDecoderListener, the base class of listeners that
+// HpackEntryDecoder calls. Also defines HpackEntryDecoderVLoggingListener
+// which logs before calling another HpackEntryDecoderListener implementation.
+
+#include <stddef.h>
+
+#include "net/third_party/quiche/src/http2/hpack/http2_hpack_constants.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_export.h"
+
+namespace http2 {
+
+class HTTP2_EXPORT_PRIVATE HpackEntryDecoderListener {
+ public:
+ virtual ~HpackEntryDecoderListener() {}
+
+ // Called when an indexed header (i.e. one in the static or dynamic table) has
+ // been decoded from an HPACK block. index is supposed to be non-zero, but
+ // that has not been checked by the caller.
+ virtual void OnIndexedHeader(size_t index) = 0;
+
+ // Called when the start of a header with a literal value, and maybe a literal
+ // name, has been decoded. maybe_name_index is zero if the header has a
+ // literal name, else it is a reference into the static or dynamic table, from
+ // which the name should be determined. When the name is literal, the next
+ // call will be to OnNameStart; else it will be to OnValueStart. entry_type
+ // indicates whether the peer has added the entry to its dynamic table, and
+ // whether a proxy is permitted to do so when forwarding the entry.
+ virtual void OnStartLiteralHeader(HpackEntryType entry_type,
+ size_t maybe_name_index) = 0;
+
+ // Called when the encoding (Huffman compressed or plain text) and the encoded
+ // length of a literal name has been decoded. OnNameData will be called next,
+ // and repeatedly until the sum of lengths passed to OnNameData is len.
+ virtual void OnNameStart(bool huffman_encoded, size_t len) = 0;
+
+ // Called when len bytes of an encoded header name have been decoded.
+ virtual void OnNameData(const char* data, size_t len) = 0;
+
+ // Called after the entire name has been passed to OnNameData.
+ // OnValueStart will be called next.
+ virtual void OnNameEnd() = 0;
+
+ // Called when the encoding (Huffman compressed or plain text) and the encoded
+ // length of a literal value has been decoded. OnValueData will be called
+ // next, and repeatedly until the sum of lengths passed to OnValueData is len.
+ virtual void OnValueStart(bool huffman_encoded, size_t len) = 0;
+
+ // Called when len bytes of an encoded header value have been decoded.
+ virtual void OnValueData(const char* data, size_t len) = 0;
+
+ // Called after the entire value has been passed to OnValueData, marking the
+ // end of a header entry with a literal value, and maybe a literal name.
+ virtual void OnValueEnd() = 0;
+
+ // Called when an update to the size of the peer's dynamic table has been
+ // decoded.
+ virtual void OnDynamicTableSizeUpdate(size_t size) = 0;
+};
+
+class HTTP2_EXPORT_PRIVATE HpackEntryDecoderVLoggingListener
+ : public HpackEntryDecoderListener {
+ public:
+ HpackEntryDecoderVLoggingListener() : wrapped_(nullptr) {}
+ explicit HpackEntryDecoderVLoggingListener(HpackEntryDecoderListener* wrapped)
+ : wrapped_(wrapped) {}
+ ~HpackEntryDecoderVLoggingListener() override {}
+
+ void OnIndexedHeader(size_t index) override;
+ void OnStartLiteralHeader(HpackEntryType entry_type,
+ size_t maybe_name_index) override;
+ void OnNameStart(bool huffman_encoded, size_t len) override;
+ void OnNameData(const char* data, size_t len) override;
+ void OnNameEnd() override;
+ void OnValueStart(bool huffman_encoded, size_t len) override;
+ void OnValueData(const char* data, size_t len) override;
+ void OnValueEnd() override;
+ void OnDynamicTableSizeUpdate(size_t size) override;
+
+ private:
+ HpackEntryDecoderListener* const wrapped_;
+};
+
+// A no-op implementation of HpackEntryDecoderListener.
+class HTTP2_EXPORT_PRIVATE HpackEntryDecoderNoOpListener
+ : public HpackEntryDecoderListener {
+ public:
+ ~HpackEntryDecoderNoOpListener() override {}
+
+ void OnIndexedHeader(size_t index) override {}
+ void OnStartLiteralHeader(HpackEntryType entry_type,
+ size_t maybe_name_index) override {}
+ void OnNameStart(bool huffman_encoded, size_t len) override {}
+ void OnNameData(const char* data, size_t len) override {}
+ void OnNameEnd() override {}
+ void OnValueStart(bool huffman_encoded, size_t len) override {}
+ void OnValueData(const char* data, size_t len) override {}
+ void OnValueEnd() override {}
+ void OnDynamicTableSizeUpdate(size_t size) override {}
+};
+
+} // namespace http2
+
+#endif // QUICHE_HTTP2_HPACK_DECODER_HPACK_ENTRY_DECODER_LISTENER_H_
diff --git a/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_decoder_test.cc b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_decoder_test.cc
new file mode 100644
index 00000000000..7249bd558a4
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_decoder_test.cc
@@ -0,0 +1,211 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_decoder.h"
+
+// Tests of HpackEntryDecoder.
+
+#include <cstdint>
+
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_collector.h"
+#include "net/third_party/quiche/src/http2/hpack/tools/hpack_block_builder.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_test_helpers.h"
+#include "net/third_party/quiche/src/http2/test_tools/http2_random.h"
+#include "net/third_party/quiche/src/http2/tools/random_decoder_test.h"
+
+using ::testing::AssertionResult;
+
+namespace http2 {
+namespace test {
+namespace {
+
+class HpackEntryDecoderTest : public RandomDecoderTest {
+ protected:
+ HpackEntryDecoderTest() : listener_(&collector_) {}
+
+ DecodeStatus StartDecoding(DecodeBuffer* b) override {
+ collector_.Clear();
+ return decoder_.Start(b, &listener_);
+ }
+
+ DecodeStatus ResumeDecoding(DecodeBuffer* b) override {
+ return decoder_.Resume(b, &listener_);
+ }
+
+ AssertionResult DecodeAndValidateSeveralWays(DecodeBuffer* db,
+ const Validator& validator) {
+ // StartDecoding, above, requires the DecodeBuffer be non-empty so that it
+ // can call Start with the prefix byte.
+ bool return_non_zero_on_first = true;
+ return RandomDecoderTest::DecodeAndValidateSeveralWays(
+ db, return_non_zero_on_first, validator);
+ }
+
+ AssertionResult DecodeAndValidateSeveralWays(const HpackBlockBuilder& hbb,
+ const Validator& validator) {
+ DecodeBuffer db(hbb.buffer());
+ return DecodeAndValidateSeveralWays(&db, validator);
+ }
+
+ HpackEntryDecoder decoder_;
+ HpackEntryCollector collector_;
+ HpackEntryDecoderVLoggingListener listener_;
+};
+
+TEST_F(HpackEntryDecoderTest, IndexedHeader_Literals) {
+ {
+ const char input[] = {'\x82'}; // == Index 2 ==
+ DecodeBuffer b(input);
+ auto do_check = [this]() {
+ VERIFY_AND_RETURN_SUCCESS(collector_.ValidateIndexedHeader(2));
+ };
+ EXPECT_TRUE(
+ DecodeAndValidateSeveralWays(&b, ValidateDoneAndEmpty(do_check)));
+ EXPECT_TRUE(do_check());
+ }
+ collector_.Clear();
+ {
+ const char input[] = {'\xfe'}; // == Index 126 ==
+ DecodeBuffer b(input);
+ auto do_check = [this]() {
+ VERIFY_AND_RETURN_SUCCESS(collector_.ValidateIndexedHeader(126));
+ };
+ EXPECT_TRUE(
+ DecodeAndValidateSeveralWays(&b, ValidateDoneAndEmpty(do_check)));
+ EXPECT_TRUE(do_check());
+ }
+ collector_.Clear();
+ {
+ const char input[] = {'\xff', '\x00'}; // == Index 127 ==
+ DecodeBuffer b(input);
+ auto do_check = [this]() {
+ VERIFY_AND_RETURN_SUCCESS(collector_.ValidateIndexedHeader(127));
+ };
+ EXPECT_TRUE(
+ DecodeAndValidateSeveralWays(&b, ValidateDoneAndEmpty(do_check)));
+ EXPECT_TRUE(do_check());
+ }
+}
+
+TEST_F(HpackEntryDecoderTest, IndexedHeader_Various) {
+ // Indices chosen to hit encoding and table boundaries.
+ for (const uint32_t ndx : {1, 2, 61, 62, 63, 126, 127, 254, 255, 256}) {
+ HpackBlockBuilder hbb;
+ hbb.AppendIndexedHeader(ndx);
+
+ auto do_check = [this, ndx]() {
+ VERIFY_AND_RETURN_SUCCESS(collector_.ValidateIndexedHeader(ndx));
+ };
+ EXPECT_TRUE(
+ DecodeAndValidateSeveralWays(hbb, ValidateDoneAndEmpty(do_check)));
+ EXPECT_TRUE(do_check());
+ }
+}
+
+TEST_F(HpackEntryDecoderTest, IndexedLiteralValue_Literal) {
+ const char input[] =
+ "\x7f" // == Literal indexed, name index 0x40 ==
+ "\x01" // 2nd byte of name index (0x01 + 0x3f == 0x40)
+ "\x0d" // Value length (13)
+ "custom-header"; // Value
+ DecodeBuffer b(input, sizeof input - 1);
+ auto do_check = [this]() {
+ VERIFY_AND_RETURN_SUCCESS(collector_.ValidateLiteralValueHeader(
+ HpackEntryType::kIndexedLiteralHeader, 0x40, false, "custom-header"));
+ };
+ EXPECT_TRUE(DecodeAndValidateSeveralWays(&b, ValidateDoneAndEmpty(do_check)));
+ EXPECT_TRUE(do_check());
+}
+
+TEST_F(HpackEntryDecoderTest, IndexedLiteralNameValue_Literal) {
+ const char input[] =
+ "\x40" // == Literal indexed ==
+ "\x0a" // Name length (10)
+ "custom-key" // Name
+ "\x0d" // Value length (13)
+ "custom-header"; // Value
+
+ DecodeBuffer b(input, sizeof input - 1);
+ auto do_check = [this]() {
+ VERIFY_AND_RETURN_SUCCESS(collector_.ValidateLiteralNameValueHeader(
+ HpackEntryType::kIndexedLiteralHeader, false, "custom-key", false,
+ "custom-header"));
+ };
+ EXPECT_TRUE(DecodeAndValidateSeveralWays(&b, ValidateDoneAndEmpty(do_check)));
+ EXPECT_TRUE(do_check());
+}
+
+TEST_F(HpackEntryDecoderTest, DynamicTableSizeUpdate_Literal) {
+ // Size update, length 31.
+ const char input[] = "\x3f\x00";
+ DecodeBuffer b(input, 2);
+ auto do_check = [this]() {
+ VERIFY_AND_RETURN_SUCCESS(collector_.ValidateDynamicTableSizeUpdate(31));
+ };
+ EXPECT_TRUE(DecodeAndValidateSeveralWays(&b, ValidateDoneAndEmpty(do_check)));
+ EXPECT_TRUE(do_check());
+}
+
+class HpackLiteralEntryDecoderTest
+ : public HpackEntryDecoderTest,
+ public ::testing::WithParamInterface<HpackEntryType> {
+ protected:
+ HpackLiteralEntryDecoderTest() : entry_type_(GetParam()) {}
+
+ const HpackEntryType entry_type_;
+};
+
+INSTANTIATE_TEST_CASE_P(
+ AllLiteralTypes,
+ HpackLiteralEntryDecoderTest,
+ testing::Values(HpackEntryType::kIndexedLiteralHeader,
+ HpackEntryType::kUnindexedLiteralHeader,
+ HpackEntryType::kNeverIndexedLiteralHeader));
+
+TEST_P(HpackLiteralEntryDecoderTest, RandNameIndexAndLiteralValue) {
+ for (int n = 0; n < 10; n++) {
+ const uint32_t ndx = 1 + Random().Rand8();
+ const bool value_is_huffman_encoded = (n % 2) == 0;
+ const Http2String value = Random().RandString(Random().Rand8());
+ HpackBlockBuilder hbb;
+ hbb.AppendNameIndexAndLiteralValue(entry_type_, ndx,
+ value_is_huffman_encoded, value);
+ auto do_check = [this, ndx, value_is_huffman_encoded,
+ value]() -> AssertionResult {
+ VERIFY_AND_RETURN_SUCCESS(collector_.ValidateLiteralValueHeader(
+ entry_type_, ndx, value_is_huffman_encoded, value));
+ };
+ EXPECT_TRUE(
+ DecodeAndValidateSeveralWays(hbb, ValidateDoneAndEmpty(do_check)));
+ EXPECT_TRUE(do_check());
+ }
+}
+
+TEST_P(HpackLiteralEntryDecoderTest, RandLiteralNameAndValue) {
+ for (int n = 0; n < 10; n++) {
+ const bool name_is_huffman_encoded = (n & 1) == 0;
+ const int name_len = 1 + Random().Rand8();
+ const Http2String name = Random().RandString(name_len);
+ const bool value_is_huffman_encoded = (n & 2) == 0;
+ const int value_len = Random().Skewed(10);
+ const Http2String value = Random().RandString(value_len);
+ HpackBlockBuilder hbb;
+ hbb.AppendLiteralNameAndValue(entry_type_, name_is_huffman_encoded, name,
+ value_is_huffman_encoded, value);
+ auto do_check = [this, name_is_huffman_encoded, name,
+ value_is_huffman_encoded, value]() -> AssertionResult {
+ VERIFY_AND_RETURN_SUCCESS(collector_.ValidateLiteralNameValueHeader(
+ entry_type_, name_is_huffman_encoded, name, value_is_huffman_encoded,
+ value));
+ };
+ EXPECT_TRUE(
+ DecodeAndValidateSeveralWays(hbb, ValidateDoneAndEmpty(do_check)));
+ EXPECT_TRUE(do_check());
+ }
+}
+
+} // namespace
+} // namespace test
+} // namespace http2
diff --git a/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_type_decoder.cc b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_type_decoder.cc
new file mode 100644
index 00000000000..121e3d0b02c
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_type_decoder.cc
@@ -0,0 +1,358 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_type_decoder.h"
+
+#include "base/logging.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_bug_tracker.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string_utils.h"
+
+namespace http2 {
+
+Http2String HpackEntryTypeDecoder::DebugString() const {
+ return Http2StrCat(
+ "HpackEntryTypeDecoder(varint_decoder=", varint_decoder_.DebugString(),
+ ", entry_type=", entry_type_, ")");
+}
+
+std::ostream& operator<<(std::ostream& out, const HpackEntryTypeDecoder& v) {
+ return out << v.DebugString();
+}
+
+// This ridiculous looking function turned out to be the winner in benchmarking
+// of several very different alternative implementations. It would be even
+// faster (~7%) if inlined in the header file, but I'm not sure if that is
+// worth doing... yet.
+// TODO(jamessynge): Benchmark again at a higher level (e.g. at least at the
+// full HTTP/2 decoder level, but preferably still higher) to determine if the
+// alternatives that take less code/data space are preferable in that situation.
+DecodeStatus HpackEntryTypeDecoder::Start(DecodeBuffer* db) {
+ DCHECK(db != nullptr);
+ DCHECK(db->HasData());
+
+ // The high four bits (nibble) of first byte of the entry determine the type
+ // of the entry, and may also be the initial bits of the varint that
+ // represents an index or table size. Note the use of the word 'initial'
+ // rather than 'high'; the HPACK encoding of varints is not in network
+ // order (i.e. not big-endian, the high-order byte isn't first), nor in
+ // little-endian order. See:
+ // http://httpwg.org/specs/rfc7541.html#integer.representation
+ uint8_t byte = db->DecodeUInt8();
+ switch (byte) {
+ case 0b00000000:
+ case 0b00000001:
+ case 0b00000010:
+ case 0b00000011:
+ case 0b00000100:
+ case 0b00000101:
+ case 0b00000110:
+ case 0b00000111:
+ case 0b00001000:
+ case 0b00001001:
+ case 0b00001010:
+ case 0b00001011:
+ case 0b00001100:
+ case 0b00001101:
+ case 0b00001110:
+ // The low 4 bits of |byte| are the initial bits of the varint.
+ // One of those bits is 0, so the varint is only one byte long.
+ entry_type_ = HpackEntryType::kUnindexedLiteralHeader;
+ varint_decoder_.set_value(byte);
+ return DecodeStatus::kDecodeDone;
+
+ case 0b00001111:
+ // The low 4 bits of |byte| are the initial bits of the varint. All 4
+ // are 1, so the varint extends into another byte.
+ entry_type_ = HpackEntryType::kUnindexedLiteralHeader;
+ return varint_decoder_.StartExtended(4, db);
+
+ case 0b00010000:
+ case 0b00010001:
+ case 0b00010010:
+ case 0b00010011:
+ case 0b00010100:
+ case 0b00010101:
+ case 0b00010110:
+ case 0b00010111:
+ case 0b00011000:
+ case 0b00011001:
+ case 0b00011010:
+ case 0b00011011:
+ case 0b00011100:
+ case 0b00011101:
+ case 0b00011110:
+ // The low 4 bits of |byte| are the initial bits of the varint.
+ // One of those bits is 0, so the varint is only one byte long.
+ entry_type_ = HpackEntryType::kNeverIndexedLiteralHeader;
+ varint_decoder_.set_value(byte & 0x0f);
+ return DecodeStatus::kDecodeDone;
+
+ case 0b00011111:
+ // The low 4 bits of |byte| are the initial bits of the varint.
+ // All of those bits are 1, so the varint extends into another byte.
+ entry_type_ = HpackEntryType::kNeverIndexedLiteralHeader;
+ return varint_decoder_.StartExtended(4, db);
+
+ case 0b00100000:
+ case 0b00100001:
+ case 0b00100010:
+ case 0b00100011:
+ case 0b00100100:
+ case 0b00100101:
+ case 0b00100110:
+ case 0b00100111:
+ case 0b00101000:
+ case 0b00101001:
+ case 0b00101010:
+ case 0b00101011:
+ case 0b00101100:
+ case 0b00101101:
+ case 0b00101110:
+ case 0b00101111:
+ case 0b00110000:
+ case 0b00110001:
+ case 0b00110010:
+ case 0b00110011:
+ case 0b00110100:
+ case 0b00110101:
+ case 0b00110110:
+ case 0b00110111:
+ case 0b00111000:
+ case 0b00111001:
+ case 0b00111010:
+ case 0b00111011:
+ case 0b00111100:
+ case 0b00111101:
+ case 0b00111110:
+ entry_type_ = HpackEntryType::kDynamicTableSizeUpdate;
+ // The low 5 bits of |byte| are the initial bits of the varint.
+ // One of those bits is 0, so the varint is only one byte long.
+ varint_decoder_.set_value(byte & 0x01f);
+ return DecodeStatus::kDecodeDone;
+
+ case 0b00111111:
+ entry_type_ = HpackEntryType::kDynamicTableSizeUpdate;
+ // The low 5 bits of |byte| are the initial bits of the varint.
+ // All of those bits are 1, so the varint extends into another byte.
+ return varint_decoder_.StartExtended(5, db);
+
+ case 0b01000000:
+ case 0b01000001:
+ case 0b01000010:
+ case 0b01000011:
+ case 0b01000100:
+ case 0b01000101:
+ case 0b01000110:
+ case 0b01000111:
+ case 0b01001000:
+ case 0b01001001:
+ case 0b01001010:
+ case 0b01001011:
+ case 0b01001100:
+ case 0b01001101:
+ case 0b01001110:
+ case 0b01001111:
+ case 0b01010000:
+ case 0b01010001:
+ case 0b01010010:
+ case 0b01010011:
+ case 0b01010100:
+ case 0b01010101:
+ case 0b01010110:
+ case 0b01010111:
+ case 0b01011000:
+ case 0b01011001:
+ case 0b01011010:
+ case 0b01011011:
+ case 0b01011100:
+ case 0b01011101:
+ case 0b01011110:
+ case 0b01011111:
+ case 0b01100000:
+ case 0b01100001:
+ case 0b01100010:
+ case 0b01100011:
+ case 0b01100100:
+ case 0b01100101:
+ case 0b01100110:
+ case 0b01100111:
+ case 0b01101000:
+ case 0b01101001:
+ case 0b01101010:
+ case 0b01101011:
+ case 0b01101100:
+ case 0b01101101:
+ case 0b01101110:
+ case 0b01101111:
+ case 0b01110000:
+ case 0b01110001:
+ case 0b01110010:
+ case 0b01110011:
+ case 0b01110100:
+ case 0b01110101:
+ case 0b01110110:
+ case 0b01110111:
+ case 0b01111000:
+ case 0b01111001:
+ case 0b01111010:
+ case 0b01111011:
+ case 0b01111100:
+ case 0b01111101:
+ case 0b01111110:
+ entry_type_ = HpackEntryType::kIndexedLiteralHeader;
+ // The low 6 bits of |byte| are the initial bits of the varint.
+ // One of those bits is 0, so the varint is only one byte long.
+ varint_decoder_.set_value(byte & 0x03f);
+ return DecodeStatus::kDecodeDone;
+
+ case 0b01111111:
+ entry_type_ = HpackEntryType::kIndexedLiteralHeader;
+ // The low 6 bits of |byte| are the initial bits of the varint.
+ // All of those bits are 1, so the varint extends into another byte.
+ return varint_decoder_.StartExtended(6, db);
+
+ case 0b10000000:
+ case 0b10000001:
+ case 0b10000010:
+ case 0b10000011:
+ case 0b10000100:
+ case 0b10000101:
+ case 0b10000110:
+ case 0b10000111:
+ case 0b10001000:
+ case 0b10001001:
+ case 0b10001010:
+ case 0b10001011:
+ case 0b10001100:
+ case 0b10001101:
+ case 0b10001110:
+ case 0b10001111:
+ case 0b10010000:
+ case 0b10010001:
+ case 0b10010010:
+ case 0b10010011:
+ case 0b10010100:
+ case 0b10010101:
+ case 0b10010110:
+ case 0b10010111:
+ case 0b10011000:
+ case 0b10011001:
+ case 0b10011010:
+ case 0b10011011:
+ case 0b10011100:
+ case 0b10011101:
+ case 0b10011110:
+ case 0b10011111:
+ case 0b10100000:
+ case 0b10100001:
+ case 0b10100010:
+ case 0b10100011:
+ case 0b10100100:
+ case 0b10100101:
+ case 0b10100110:
+ case 0b10100111:
+ case 0b10101000:
+ case 0b10101001:
+ case 0b10101010:
+ case 0b10101011:
+ case 0b10101100:
+ case 0b10101101:
+ case 0b10101110:
+ case 0b10101111:
+ case 0b10110000:
+ case 0b10110001:
+ case 0b10110010:
+ case 0b10110011:
+ case 0b10110100:
+ case 0b10110101:
+ case 0b10110110:
+ case 0b10110111:
+ case 0b10111000:
+ case 0b10111001:
+ case 0b10111010:
+ case 0b10111011:
+ case 0b10111100:
+ case 0b10111101:
+ case 0b10111110:
+ case 0b10111111:
+ case 0b11000000:
+ case 0b11000001:
+ case 0b11000010:
+ case 0b11000011:
+ case 0b11000100:
+ case 0b11000101:
+ case 0b11000110:
+ case 0b11000111:
+ case 0b11001000:
+ case 0b11001001:
+ case 0b11001010:
+ case 0b11001011:
+ case 0b11001100:
+ case 0b11001101:
+ case 0b11001110:
+ case 0b11001111:
+ case 0b11010000:
+ case 0b11010001:
+ case 0b11010010:
+ case 0b11010011:
+ case 0b11010100:
+ case 0b11010101:
+ case 0b11010110:
+ case 0b11010111:
+ case 0b11011000:
+ case 0b11011001:
+ case 0b11011010:
+ case 0b11011011:
+ case 0b11011100:
+ case 0b11011101:
+ case 0b11011110:
+ case 0b11011111:
+ case 0b11100000:
+ case 0b11100001:
+ case 0b11100010:
+ case 0b11100011:
+ case 0b11100100:
+ case 0b11100101:
+ case 0b11100110:
+ case 0b11100111:
+ case 0b11101000:
+ case 0b11101001:
+ case 0b11101010:
+ case 0b11101011:
+ case 0b11101100:
+ case 0b11101101:
+ case 0b11101110:
+ case 0b11101111:
+ case 0b11110000:
+ case 0b11110001:
+ case 0b11110010:
+ case 0b11110011:
+ case 0b11110100:
+ case 0b11110101:
+ case 0b11110110:
+ case 0b11110111:
+ case 0b11111000:
+ case 0b11111001:
+ case 0b11111010:
+ case 0b11111011:
+ case 0b11111100:
+ case 0b11111101:
+ case 0b11111110:
+ entry_type_ = HpackEntryType::kIndexedHeader;
+ // The low 7 bits of |byte| are the initial bits of the varint.
+ // One of those bits is 0, so the varint is only one byte long.
+ varint_decoder_.set_value(byte & 0x07f);
+ return DecodeStatus::kDecodeDone;
+
+ case 0b11111111:
+ entry_type_ = HpackEntryType::kIndexedHeader;
+ // The low 7 bits of |byte| are the initial bits of the varint.
+ // All of those bits are 1, so the varint extends into another byte.
+ return varint_decoder_.StartExtended(7, db);
+ }
+ HTTP2_BUG << "Unreachable, byte=" << std::hex << static_cast<uint32_t>(byte);
+ return DecodeStatus::kDecodeError;
+}
+
+} // namespace http2
diff --git a/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_type_decoder.h b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_type_decoder.h
new file mode 100644
index 00000000000..79898becbef
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_type_decoder.h
@@ -0,0 +1,57 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_HTTP2_HPACK_DECODER_HPACK_ENTRY_TYPE_DECODER_H_
+#define QUICHE_HTTP2_HPACK_DECODER_HPACK_ENTRY_TYPE_DECODER_H_
+
+// Decodes the type of an HPACK entry, and the variable length integer whose
+// prefix is in the low-order bits of the same byte, "below" the type bits.
+// The integer represents an index into static or dynamic table, which may be
+// zero, or is the new size limit of the dynamic table.
+
+#include <cstdint>
+
+#include "base/logging.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_status.h"
+#include "net/third_party/quiche/src/http2/hpack/http2_hpack_constants.h"
+#include "net/third_party/quiche/src/http2/hpack/varint/hpack_varint_decoder.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_export.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string.h"
+
+namespace http2 {
+
+class HTTP2_EXPORT_PRIVATE HpackEntryTypeDecoder {
+ public:
+ // Only call when the decode buffer has data (i.e. HpackEntryDecoder must
+ // not call until there is data).
+ DecodeStatus Start(DecodeBuffer* db);
+
+ // Only call Resume if the previous call (Start or Resume) returned
+ // DecodeStatus::kDecodeInProgress.
+ DecodeStatus Resume(DecodeBuffer* db) { return varint_decoder_.Resume(db); }
+
+ // Returns the decoded entry type. Only call if the preceding call to Start
+ // or Resume returned kDecodeDone.
+ HpackEntryType entry_type() const { return entry_type_; }
+
+ // Returns the decoded variable length integer. Only call if the
+ // preceding call to Start or Resume returned kDecodeDone.
+ uint64_t varint() const { return varint_decoder_.value(); }
+
+ Http2String DebugString() const;
+
+ private:
+ HpackVarintDecoder varint_decoder_;
+
+ // This field is initialized just to keep ASAN happy about reading it
+ // from DebugString().
+ HpackEntryType entry_type_ = HpackEntryType::kIndexedHeader;
+};
+
+HTTP2_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& out,
+ const HpackEntryTypeDecoder& v);
+
+} // namespace http2
+#endif // QUICHE_HTTP2_HPACK_DECODER_HPACK_ENTRY_TYPE_DECODER_H_
diff --git a/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_type_decoder_test.cc b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_type_decoder_test.cc
new file mode 100644
index 00000000000..afff7cc629d
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_type_decoder_test.cc
@@ -0,0 +1,87 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_type_decoder.h"
+
+#include <vector>
+
+#include "base/logging.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/http2/hpack/tools/hpack_block_builder.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_test_helpers.h"
+#include "net/third_party/quiche/src/http2/tools/random_decoder_test.h"
+
+using ::testing::AssertionFailure;
+using ::testing::AssertionResult;
+using ::testing::AssertionSuccess;
+
+namespace http2 {
+namespace test {
+namespace {
+const bool kReturnNonZeroOnFirst = true;
+
+class HpackEntryTypeDecoderTest : public RandomDecoderTest {
+ protected:
+ DecodeStatus StartDecoding(DecodeBuffer* b) override {
+ CHECK_LT(0u, b->Remaining());
+ return decoder_.Start(b);
+ }
+
+ DecodeStatus ResumeDecoding(DecodeBuffer* b) override {
+ return decoder_.Resume(b);
+ }
+
+ HpackEntryTypeDecoder decoder_;
+};
+
+TEST_F(HpackEntryTypeDecoderTest, DynamicTableSizeUpdate) {
+ for (uint32_t size = 0; size < 1000 * 1000; size += 256) {
+ HpackBlockBuilder bb;
+ bb.AppendDynamicTableSizeUpdate(size);
+ DecodeBuffer db(bb.buffer());
+ auto validator = [size, this]() -> AssertionResult {
+ VERIFY_EQ(HpackEntryType::kDynamicTableSizeUpdate, decoder_.entry_type());
+ VERIFY_EQ(size, decoder_.varint());
+ return AssertionSuccess();
+ };
+ EXPECT_TRUE(DecodeAndValidateSeveralWays(&db, kReturnNonZeroOnFirst,
+ ValidateDoneAndEmpty(validator)))
+ << "\nentry_type=kDynamicTableSizeUpdate, size=" << size;
+ // Run the validator again to make sure that DecodeAndValidateSeveralWays
+ // did the right thing.
+ EXPECT_TRUE(validator());
+ }
+}
+
+TEST_F(HpackEntryTypeDecoderTest, HeaderWithIndex) {
+ std::vector<HpackEntryType> entry_types = {
+ HpackEntryType::kIndexedHeader,
+ HpackEntryType::kIndexedLiteralHeader,
+ HpackEntryType::kUnindexedLiteralHeader,
+ HpackEntryType::kNeverIndexedLiteralHeader,
+ };
+ for (const HpackEntryType entry_type : entry_types) {
+ const uint32_t first = entry_type == HpackEntryType::kIndexedHeader ? 1 : 0;
+ for (uint32_t index = first; index < 1000; ++index) {
+ HpackBlockBuilder bb;
+ bb.AppendEntryTypeAndVarint(entry_type, index);
+ DecodeBuffer db(bb.buffer());
+ auto validator = [entry_type, index, this]() -> AssertionResult {
+ VERIFY_EQ(entry_type, decoder_.entry_type());
+ VERIFY_EQ(index, decoder_.varint());
+ return AssertionSuccess();
+ };
+ EXPECT_TRUE(DecodeAndValidateSeveralWays(&db, kReturnNonZeroOnFirst,
+ ValidateDoneAndEmpty(validator)))
+ << "\nentry_type=" << entry_type << ", index=" << index;
+ // Run the validator again to make sure that DecodeAndValidateSeveralWays
+ // did the right thing.
+ EXPECT_TRUE(validator());
+ }
+ }
+}
+
+} // namespace
+} // namespace test
+} // namespace http2
diff --git a/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_string_collector.cc b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_string_collector.cc
new file mode 100644
index 00000000000..247ce9c2a6a
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_string_collector.cc
@@ -0,0 +1,123 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_string_collector.h"
+
+#include <stddef.h>
+
+#include <iosfwd>
+#include <ostream>
+
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string_utils.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_test_helpers.h"
+
+namespace http2 {
+namespace test {
+namespace {
+
+std::ostream& operator<<(std::ostream& out,
+ HpackStringCollector::CollectorState v) {
+ switch (v) {
+ case HpackStringCollector::CollectorState::kGenesis:
+ return out << "kGenesis";
+ case HpackStringCollector::CollectorState::kStarted:
+ return out << "kStarted";
+ case HpackStringCollector::CollectorState::kEnded:
+ return out << "kEnded";
+ }
+ return out << "UnknownCollectorState";
+}
+
+} // namespace
+
+HpackStringCollector::HpackStringCollector() {
+ Clear();
+}
+
+HpackStringCollector::HpackStringCollector(const Http2String& str, bool huffman)
+ : s(str), len(str.size()), huffman_encoded(huffman), state(kEnded) {}
+
+void HpackStringCollector::Clear() {
+ s = "";
+ len = 0;
+ huffman_encoded = false;
+ state = kGenesis;
+}
+
+bool HpackStringCollector::IsClear() const {
+ return s.empty() && len == 0 && huffman_encoded == false && state == kGenesis;
+}
+
+bool HpackStringCollector::IsInProgress() const {
+ return state == kStarted;
+}
+
+bool HpackStringCollector::HasEnded() const {
+ return state == kEnded;
+}
+
+void HpackStringCollector::OnStringStart(bool huffman, size_t length) {
+ EXPECT_TRUE(IsClear()) << ToString();
+ state = kStarted;
+ huffman_encoded = huffman;
+ len = length;
+}
+
+void HpackStringCollector::OnStringData(const char* data, size_t length) {
+ Http2StringPiece sp(data, length);
+ EXPECT_TRUE(IsInProgress()) << ToString();
+ EXPECT_LE(sp.size(), len) << ToString();
+ Http2StrAppend(&s, sp);
+ EXPECT_LE(s.size(), len) << ToString();
+}
+
+void HpackStringCollector::OnStringEnd() {
+ EXPECT_TRUE(IsInProgress()) << ToString();
+ EXPECT_EQ(s.size(), len) << ToString();
+ state = kEnded;
+}
+
+::testing::AssertionResult HpackStringCollector::Collected(
+ Http2StringPiece str,
+ bool is_huffman_encoded) const {
+ VERIFY_TRUE(HasEnded());
+ VERIFY_EQ(str.size(), len);
+ VERIFY_EQ(is_huffman_encoded, huffman_encoded);
+ VERIFY_EQ(str, s);
+ return ::testing::AssertionSuccess();
+}
+
+Http2String HpackStringCollector::ToString() const {
+ std::stringstream ss;
+ ss << *this;
+ return ss.str();
+}
+
+bool operator==(const HpackStringCollector& a, const HpackStringCollector& b) {
+ return a.s == b.s && a.len == b.len &&
+ a.huffman_encoded == b.huffman_encoded && a.state == b.state;
+}
+
+bool operator!=(const HpackStringCollector& a, const HpackStringCollector& b) {
+ return !(a == b);
+}
+
+std::ostream& operator<<(std::ostream& out, const HpackStringCollector& v) {
+ out << "HpackStringCollector(state=" << v.state;
+ if (v.state == HpackStringCollector::kGenesis) {
+ return out << ")";
+ }
+ if (v.huffman_encoded) {
+ out << ", Huffman Encoded";
+ }
+ out << ", Length=" << v.len;
+ if (!v.s.empty() && v.len != v.s.size()) {
+ out << " (" << v.s.size() << ")";
+ }
+ return out << ", String=\"" << Http2HexEscape(v.s) << "\")";
+}
+
+} // namespace test
+} // namespace http2
diff --git a/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_string_collector.h b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_string_collector.h
new file mode 100644
index 00000000000..76be13b743c
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_string_collector.h
@@ -0,0 +1,63 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_HTTP2_HPACK_DECODER_HPACK_STRING_COLLECTOR_H_
+#define QUICHE_HTTP2_HPACK_DECODER_HPACK_STRING_COLLECTOR_H_
+
+// Supports tests of decoding HPACK strings.
+
+#include <stddef.h>
+
+#include <iosfwd>
+
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_string_decoder_listener.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string_piece.h"
+
+namespace http2 {
+namespace test {
+
+// Records the callbacks associated with a decoding a string; must
+// call Clear() between decoding successive strings.
+struct HpackStringCollector : public HpackStringDecoderListener {
+ enum CollectorState {
+ kGenesis,
+ kStarted,
+ kEnded,
+ };
+
+ HpackStringCollector();
+ HpackStringCollector(const Http2String& str, bool huffman);
+
+ void Clear();
+ bool IsClear() const;
+ bool IsInProgress() const;
+ bool HasEnded() const;
+
+ void OnStringStart(bool huffman, size_t length) override;
+ void OnStringData(const char* data, size_t length) override;
+ void OnStringEnd() override;
+
+ ::testing::AssertionResult Collected(Http2StringPiece str,
+ bool is_huffman_encoded) const;
+
+ Http2String ToString() const;
+
+ Http2String s;
+ size_t len;
+ bool huffman_encoded;
+ CollectorState state;
+};
+
+bool operator==(const HpackStringCollector& a, const HpackStringCollector& b);
+
+bool operator!=(const HpackStringCollector& a, const HpackStringCollector& b);
+
+std::ostream& operator<<(std::ostream& out, const HpackStringCollector& v);
+
+} // namespace test
+} // namespace http2
+
+#endif // QUICHE_HTTP2_HPACK_DECODER_HPACK_STRING_COLLECTOR_H_
diff --git a/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_string_decoder.cc b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_string_decoder.cc
new file mode 100644
index 00000000000..0b9eb596835
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_string_decoder.cc
@@ -0,0 +1,35 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_string_decoder.h"
+
+#include "net/third_party/quiche/src/http2/platform/api/http2_string_utils.h"
+
+namespace http2 {
+
+Http2String HpackStringDecoder::DebugString() const {
+ return Http2StrCat("HpackStringDecoder(state=", StateToString(state_),
+ ", length=", length_decoder_.DebugString(),
+ ", remaining=", remaining_,
+ ", huffman=", huffman_encoded_ ? "true)" : "false)");
+}
+
+// static
+Http2String HpackStringDecoder::StateToString(StringDecoderState v) {
+ switch (v) {
+ case kStartDecodingLength:
+ return "kStartDecodingLength";
+ case kDecodingString:
+ return "kDecodingString";
+ case kResumeDecodingLength:
+ return "kResumeDecodingLength";
+ }
+ return Http2StrCat("UNKNOWN_STATE(", static_cast<uint32_t>(v), ")");
+}
+
+std::ostream& operator<<(std::ostream& out, const HpackStringDecoder& v) {
+ return out << v.DebugString();
+}
+
+} // namespace http2
diff --git a/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_string_decoder.h b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_string_decoder.h
new file mode 100644
index 00000000000..2128728e9d1
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_string_decoder.h
@@ -0,0 +1,209 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_HTTP2_HPACK_DECODER_HPACK_STRING_DECODER_H_
+#define QUICHE_HTTP2_HPACK_DECODER_HPACK_STRING_DECODER_H_
+
+// HpackStringDecoder decodes strings encoded per the HPACK spec; this does
+// not mean decompressing Huffman encoded strings, just identifying the length,
+// encoding and contents for a listener.
+
+#include <stddef.h>
+
+#include <algorithm>
+#include <cstdint>
+
+#include "base/logging.h"
+#include "base/macros.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_status.h"
+#include "net/third_party/quiche/src/http2/hpack/varint/hpack_varint_decoder.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_export.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_macros.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string.h"
+
+namespace http2 {
+
+// Decodes a single string in an HPACK header entry. The high order bit of
+// the first byte of the length is the H (Huffman) bit indicating whether
+// the value is Huffman encoded, and the remainder of the byte is the first
+// 7 bits of an HPACK varint.
+//
+// Call Start() to begin decoding; if it returns kDecodeInProgress, then call
+// Resume() when more input is available, repeating until kDecodeInProgress is
+// not returned. If kDecodeDone or kDecodeError is returned, then Resume() must
+// not be called until Start() has been called to start decoding a new string.
+class HTTP2_EXPORT_PRIVATE HpackStringDecoder {
+ public:
+ enum StringDecoderState {
+ kStartDecodingLength,
+ kDecodingString,
+ kResumeDecodingLength,
+ };
+
+ template <class Listener>
+ DecodeStatus Start(DecodeBuffer* db, Listener* cb) {
+ // Fast decode path is used if the string is under 127 bytes and the
+ // entire length of the string is in the decode buffer. More than 83% of
+ // string lengths are encoded in just one byte.
+ if (db->HasData() && (*db->cursor() & 0x7f) != 0x7f) {
+ // The string is short.
+ uint8_t h_and_prefix = db->DecodeUInt8();
+ uint8_t length = h_and_prefix & 0x7f;
+ bool huffman_encoded = (h_and_prefix & 0x80) == 0x80;
+ cb->OnStringStart(huffman_encoded, length);
+ if (length <= db->Remaining()) {
+ // Yeah, we've got the whole thing in the decode buffer.
+ // Ideally this will be the common case. Note that we don't
+ // update any of the member variables in this path.
+ cb->OnStringData(db->cursor(), length);
+ db->AdvanceCursor(length);
+ cb->OnStringEnd();
+ return DecodeStatus::kDecodeDone;
+ }
+ // Not all in the buffer.
+ huffman_encoded_ = huffman_encoded;
+ remaining_ = length;
+ // Call Resume to decode the string body, which is only partially
+ // in the decode buffer (or not at all).
+ state_ = kDecodingString;
+ return Resume(db, cb);
+ }
+ // Call Resume to decode the string length, which is either not in
+ // the decode buffer, or spans multiple bytes.
+ state_ = kStartDecodingLength;
+ return Resume(db, cb);
+ }
+
+ template <class Listener>
+ DecodeStatus Resume(DecodeBuffer* db, Listener* cb) {
+ DecodeStatus status;
+ while (true) {
+ switch (state_) {
+ case kStartDecodingLength:
+ DVLOG(2) << "kStartDecodingLength: db->Remaining=" << db->Remaining();
+ if (!StartDecodingLength(db, cb, &status)) {
+ // The length is split across decode buffers.
+ return status;
+ }
+ // We've finished decoding the length, which spanned one or more
+ // bytes. Approximately 17% of strings have a length that is greater
+ // than 126 bytes, and thus the length is encoded in more than one
+ // byte, and so doesn't get the benefit of the optimization in
+ // Start() for single byte lengths. But, we still expect that most
+ // of such strings will be contained entirely in a single decode
+ // buffer, and hence this fall through skips another trip through the
+ // switch above and more importantly skips setting the state_ variable
+ // again in those cases where we don't need it.
+ HTTP2_FALLTHROUGH;
+
+ case kDecodingString:
+ DVLOG(2) << "kDecodingString: db->Remaining=" << db->Remaining()
+ << " remaining_=" << remaining_;
+ return DecodeString(db, cb);
+
+ case kResumeDecodingLength:
+ DVLOG(2) << "kResumeDecodingLength: db->Remaining="
+ << db->Remaining();
+ if (!ResumeDecodingLength(db, cb, &status)) {
+ return status;
+ }
+ }
+ }
+ }
+
+ Http2String DebugString() const;
+
+ private:
+ static Http2String StateToString(StringDecoderState v);
+
+ // Returns true if the length is fully decoded and the listener wants the
+ // decoding to continue, false otherwise; status is set to the status from
+ // the varint decoder.
+ // If the length is not fully decoded, case state_ is set appropriately
+ // for the next call to Resume.
+ template <class Listener>
+ bool StartDecodingLength(DecodeBuffer* db,
+ Listener* cb,
+ DecodeStatus* status) {
+ if (db->Empty()) {
+ *status = DecodeStatus::kDecodeInProgress;
+ state_ = kStartDecodingLength;
+ return false;
+ }
+ uint8_t h_and_prefix = db->DecodeUInt8();
+ huffman_encoded_ = (h_and_prefix & 0x80) == 0x80;
+ *status = length_decoder_.Start(h_and_prefix, 7, db);
+ if (*status == DecodeStatus::kDecodeDone) {
+ OnStringStart(cb, status);
+ return true;
+ }
+ // Set the state to cover the DecodeStatus::kDecodeInProgress case.
+ // Won't be needed if the status is kDecodeError.
+ state_ = kResumeDecodingLength;
+ return false;
+ }
+
+ // Returns true if the length is fully decoded and the listener wants the
+ // decoding to continue, false otherwise; status is set to the status from
+ // the varint decoder; state_ is updated when fully decoded.
+ // If the length is not fully decoded, case state_ is set appropriately
+ // for the next call to Resume.
+ template <class Listener>
+ bool ResumeDecodingLength(DecodeBuffer* db,
+ Listener* cb,
+ DecodeStatus* status) {
+ DCHECK_EQ(state_, kResumeDecodingLength);
+ *status = length_decoder_.Resume(db);
+ if (*status == DecodeStatus::kDecodeDone) {
+ state_ = kDecodingString;
+ OnStringStart(cb, status);
+ return true;
+ }
+ return false;
+ }
+
+ // Returns true if the listener wants the decoding to continue, and
+ // false otherwise, in which case status set.
+ template <class Listener>
+ void OnStringStart(Listener* cb, DecodeStatus* status) {
+ // TODO(vasilvv): fail explicitly in case of truncation.
+ remaining_ = static_cast<size_t>(length_decoder_.value());
+ // Make callback so consumer knows what is coming.
+ cb->OnStringStart(huffman_encoded_, remaining_);
+ }
+
+ // Passes the available portion of the string to the listener, and signals
+ // the end of the string when it is reached. Returns kDecodeDone or
+ // kDecodeInProgress as appropriate.
+ template <class Listener>
+ DecodeStatus DecodeString(DecodeBuffer* db, Listener* cb) {
+ size_t len = std::min(remaining_, db->Remaining());
+ if (len > 0) {
+ cb->OnStringData(db->cursor(), len);
+ db->AdvanceCursor(len);
+ remaining_ -= len;
+ }
+ if (remaining_ == 0) {
+ cb->OnStringEnd();
+ return DecodeStatus::kDecodeDone;
+ }
+ state_ = kDecodingString;
+ return DecodeStatus::kDecodeInProgress;
+ }
+
+ HpackVarintDecoder length_decoder_;
+
+ // These fields are initialized just to keep ASAN happy about reading
+ // them from DebugString().
+ size_t remaining_ = 0;
+ StringDecoderState state_ = kStartDecodingLength;
+ bool huffman_encoded_ = false;
+};
+
+HTTP2_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& out,
+ const HpackStringDecoder& v);
+
+} // namespace http2
+#endif // QUICHE_HTTP2_HPACK_DECODER_HPACK_STRING_DECODER_H_
diff --git a/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_string_decoder_listener.cc b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_string_decoder_listener.cc
new file mode 100644
index 00000000000..e0fbc657ef9
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_string_decoder_listener.cc
@@ -0,0 +1,36 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_string_decoder_listener.h"
+
+#include "base/logging.h"
+
+namespace http2 {
+namespace test {
+
+void HpackStringDecoderVLoggingListener::OnStringStart(bool huffman_encoded,
+ size_t len) {
+ VLOG(1) << "OnStringStart: H=" << huffman_encoded << ", len=" << len;
+ if (wrapped_) {
+ wrapped_->OnStringStart(huffman_encoded, len);
+ }
+}
+
+void HpackStringDecoderVLoggingListener::OnStringData(const char* data,
+ size_t len) {
+ VLOG(1) << "OnStringData: len=" << len;
+ if (wrapped_) {
+ return wrapped_->OnStringData(data, len);
+ }
+}
+
+void HpackStringDecoderVLoggingListener::OnStringEnd() {
+ VLOG(1) << "OnStringEnd";
+ if (wrapped_) {
+ return wrapped_->OnStringEnd();
+ }
+}
+
+} // namespace test
+} // namespace http2
diff --git a/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_string_decoder_listener.h b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_string_decoder_listener.h
new file mode 100644
index 00000000000..35a04170f99
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_string_decoder_listener.h
@@ -0,0 +1,62 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_HTTP2_HPACK_DECODER_HPACK_STRING_DECODER_LISTENER_H_
+#define QUICHE_HTTP2_HPACK_DECODER_HPACK_STRING_DECODER_LISTENER_H_
+
+// Defines HpackStringDecoderListener which defines the methods required by an
+// HpackStringDecoder. Also defines HpackStringDecoderVLoggingListener which
+// logs before calling another HpackStringDecoderListener implementation.
+// For now these are only used by tests, so placed in the test namespace.
+
+#include <stddef.h>
+
+#include "net/third_party/quiche/src/http2/platform/api/http2_export.h"
+
+namespace http2 {
+namespace test {
+
+// HpackStringDecoder methods require a listener that implements the methods
+// below, but it is NOT necessary to extend this class because the methods
+// are templates.
+class HTTP2_EXPORT_PRIVATE HpackStringDecoderListener {
+ public:
+ virtual ~HpackStringDecoderListener() {}
+
+ // Called at the start of decoding an HPACK string. The encoded length of the
+ // string is |len| bytes, which may be zero. The string is Huffman encoded
+ // if huffman_encoded is true, else it is plain text (i.e. the encoded length
+ // is then the plain text length).
+ virtual void OnStringStart(bool huffman_encoded, size_t len) = 0;
+
+ // Called when some data is available, or once when the string length is zero
+ // (to simplify the decoder, it doesn't have a special case for len==0).
+ virtual void OnStringData(const char* data, size_t len) = 0;
+
+ // Called after OnStringData has provided all of the encoded bytes of the
+ // string.
+ virtual void OnStringEnd() = 0;
+};
+
+class HTTP2_EXPORT_PRIVATE HpackStringDecoderVLoggingListener
+ : public HpackStringDecoderListener {
+ public:
+ HpackStringDecoderVLoggingListener() : wrapped_(nullptr) {}
+ explicit HpackStringDecoderVLoggingListener(
+ HpackStringDecoderListener* wrapped)
+ : wrapped_(wrapped) {}
+ ~HpackStringDecoderVLoggingListener() override {}
+
+ void OnStringStart(bool huffman_encoded, size_t len) override;
+ void OnStringData(const char* data, size_t len) override;
+ void OnStringEnd() override;
+
+ private:
+ HpackStringDecoderListener* const wrapped_;
+};
+
+} // namespace test
+} // namespace http2
+
+#endif // QUICHE_HTTP2_HPACK_DECODER_HPACK_STRING_DECODER_LISTENER_H_
diff --git a/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_string_decoder_test.cc b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_string_decoder_test.cc
new file mode 100644
index 00000000000..349b649d211
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_string_decoder_test.cc
@@ -0,0 +1,155 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_string_decoder.h"
+
+// Tests of HpackStringDecoder.
+
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_string_collector.h"
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_string_decoder_listener.h"
+#include "net/third_party/quiche/src/http2/hpack/tools/hpack_block_builder.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string_piece.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_test_helpers.h"
+#include "net/third_party/quiche/src/http2/test_tools/http2_random.h"
+#include "net/third_party/quiche/src/http2/tools/random_decoder_test.h"
+
+using ::testing::AssertionResult;
+
+namespace http2 {
+namespace test {
+namespace {
+
+const bool kMayReturnZeroOnFirst = false;
+const bool kCompressed = true;
+const bool kUncompressed = false;
+
+class HpackStringDecoderTest : public RandomDecoderTest {
+ protected:
+ HpackStringDecoderTest() : listener_(&collector_) {}
+
+ DecodeStatus StartDecoding(DecodeBuffer* b) override {
+ ++start_decoding_calls_;
+ collector_.Clear();
+ return decoder_.Start(b, &listener_);
+ }
+
+ DecodeStatus ResumeDecoding(DecodeBuffer* b) override {
+ // Provides coverage of DebugString and StateToString.
+ // Not validating output.
+ VLOG(1) << decoder_.DebugString();
+ VLOG(2) << collector_;
+ return decoder_.Resume(b, &listener_);
+ }
+
+ AssertionResult Collected(Http2StringPiece s, bool huffman_encoded) {
+ VLOG(1) << collector_;
+ return collector_.Collected(s, huffman_encoded);
+ }
+
+ // expected_str is a Http2String rather than a const Http2String& or
+ // Http2StringPiece so that the lambda makes a copy of the string, and thus
+ // the string to be passed to Collected outlives the call to MakeValidator.
+ Validator MakeValidator(const Http2String& expected_str,
+ bool expected_huffman) {
+ return
+ [expected_str, expected_huffman, this](
+ const DecodeBuffer& input, DecodeStatus status) -> AssertionResult {
+ AssertionResult result = Collected(expected_str, expected_huffman);
+ if (result) {
+ VERIFY_EQ(collector_,
+ HpackStringCollector(expected_str, expected_huffman));
+ } else {
+ VERIFY_NE(collector_,
+ HpackStringCollector(expected_str, expected_huffman));
+ }
+ VLOG(2) << collector_.ToString();
+ collector_.Clear();
+ VLOG(2) << collector_;
+ return result;
+ };
+ }
+
+ HpackStringDecoder decoder_;
+ HpackStringCollector collector_;
+ HpackStringDecoderVLoggingListener listener_;
+ size_t start_decoding_calls_ = 0;
+};
+
+TEST_F(HpackStringDecoderTest, DecodeEmptyString) {
+ {
+ Validator validator = ValidateDoneAndEmpty(MakeValidator("", kCompressed));
+ const char kData[] = {'\x80'};
+ DecodeBuffer b(kData);
+ EXPECT_TRUE(
+ DecodeAndValidateSeveralWays(&b, kMayReturnZeroOnFirst, validator));
+ }
+ {
+ // Make sure it stops after decoding the empty string.
+ Validator validator =
+ ValidateDoneAndOffset(1, MakeValidator("", kUncompressed));
+ const char kData[] = {'\x00', '\xff'};
+ DecodeBuffer b(kData);
+ EXPECT_EQ(2u, b.Remaining());
+ EXPECT_TRUE(
+ DecodeAndValidateSeveralWays(&b, kMayReturnZeroOnFirst, validator));
+ EXPECT_EQ(1u, b.Remaining());
+ }
+}
+
+TEST_F(HpackStringDecoderTest, DecodeShortString) {
+ {
+ // Make sure it stops after decoding the non-empty string.
+ Validator validator =
+ ValidateDoneAndOffset(11, MakeValidator("start end.", kCompressed));
+ const char kData[] = "\x8astart end.Don't peek at this.";
+ DecodeBuffer b(kData);
+ EXPECT_TRUE(
+ DecodeAndValidateSeveralWays(&b, kMayReturnZeroOnFirst, validator));
+ }
+ {
+ Validator validator =
+ ValidateDoneAndOffset(11, MakeValidator("start end.", kUncompressed));
+ Http2StringPiece data("\x0astart end.");
+ DecodeBuffer b(data);
+ EXPECT_TRUE(
+ DecodeAndValidateSeveralWays(&b, kMayReturnZeroOnFirst, validator));
+ }
+}
+
+TEST_F(HpackStringDecoderTest, DecodeLongStrings) {
+ Http2String name = Random().RandString(1024);
+ Http2String value = Random().RandString(65536);
+ HpackBlockBuilder hbb;
+
+ hbb.AppendString(false, name);
+ uint32_t offset_after_name = hbb.size();
+ EXPECT_EQ(3 + name.size(), offset_after_name);
+
+ hbb.AppendString(true, value);
+ uint32_t offset_after_value = hbb.size();
+ EXPECT_EQ(3 + name.size() + 4 + value.size(), offset_after_value);
+
+ DecodeBuffer b(hbb.buffer());
+
+ // Decode the name...
+ EXPECT_TRUE(DecodeAndValidateSeveralWays(
+ &b, kMayReturnZeroOnFirst,
+ ValidateDoneAndOffset(offset_after_name,
+ MakeValidator(name, kUncompressed))));
+ EXPECT_EQ(offset_after_name, b.Offset());
+ EXPECT_EQ(offset_after_value - offset_after_name, b.Remaining());
+
+ // Decode the value...
+ EXPECT_TRUE(DecodeAndValidateSeveralWays(
+ &b, kMayReturnZeroOnFirst,
+ ValidateDoneAndOffset(offset_after_value - offset_after_name,
+ MakeValidator(value, kCompressed))));
+ EXPECT_EQ(offset_after_value, b.Offset());
+ EXPECT_EQ(0u, b.Remaining());
+}
+
+} // namespace
+} // namespace test
+} // namespace http2
diff --git a/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_whole_entry_buffer.cc b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_whole_entry_buffer.cc
new file mode 100644
index 00000000000..b3e29b176cc
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_whole_entry_buffer.cc
@@ -0,0 +1,139 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_whole_entry_buffer.h"
+
+#include "base/logging.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_estimate_memory_usage.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_macros.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string_utils.h"
+
+namespace http2 {
+
+HpackWholeEntryBuffer::HpackWholeEntryBuffer(HpackWholeEntryListener* listener,
+ size_t max_string_size_bytes)
+ : max_string_size_bytes_(max_string_size_bytes) {
+ set_listener(listener);
+}
+HpackWholeEntryBuffer::~HpackWholeEntryBuffer() = default;
+
+void HpackWholeEntryBuffer::set_listener(HpackWholeEntryListener* listener) {
+ listener_ = HTTP2_DIE_IF_NULL(listener);
+}
+
+void HpackWholeEntryBuffer::set_max_string_size_bytes(
+ size_t max_string_size_bytes) {
+ max_string_size_bytes_ = max_string_size_bytes;
+}
+
+void HpackWholeEntryBuffer::BufferStringsIfUnbuffered() {
+ name_.BufferStringIfUnbuffered();
+ value_.BufferStringIfUnbuffered();
+}
+
+size_t HpackWholeEntryBuffer::EstimateMemoryUsage() const {
+ return Http2EstimateMemoryUsage(name_) + Http2EstimateMemoryUsage(value_);
+}
+
+void HpackWholeEntryBuffer::OnIndexedHeader(size_t index) {
+ DVLOG(2) << "HpackWholeEntryBuffer::OnIndexedHeader: index=" << index;
+ listener_->OnIndexedHeader(index);
+}
+
+void HpackWholeEntryBuffer::OnStartLiteralHeader(HpackEntryType entry_type,
+ size_t maybe_name_index) {
+ DVLOG(2) << "HpackWholeEntryBuffer::OnStartLiteralHeader: entry_type="
+ << entry_type << ", maybe_name_index=" << maybe_name_index;
+ entry_type_ = entry_type;
+ maybe_name_index_ = maybe_name_index;
+}
+
+void HpackWholeEntryBuffer::OnNameStart(bool huffman_encoded, size_t len) {
+ DVLOG(2) << "HpackWholeEntryBuffer::OnNameStart: huffman_encoded="
+ << (huffman_encoded ? "true" : "false") << ", len=" << len;
+ DCHECK_EQ(maybe_name_index_, 0u);
+ if (!error_detected_) {
+ if (len > max_string_size_bytes_) {
+ DVLOG(1) << "Name length (" << len << ") is longer than permitted ("
+ << max_string_size_bytes_ << ")";
+ ReportError("HPACK entry name size is too long.");
+ return;
+ }
+ name_.OnStart(huffman_encoded, len);
+ }
+}
+
+void HpackWholeEntryBuffer::OnNameData(const char* data, size_t len) {
+ DVLOG(2) << "HpackWholeEntryBuffer::OnNameData: len=" << len << " data:\n"
+ << Http2HexDump(Http2StringPiece(data, len));
+ DCHECK_EQ(maybe_name_index_, 0u);
+ if (!error_detected_ && !name_.OnData(data, len)) {
+ ReportError("Error decoding HPACK entry name.");
+ }
+}
+
+void HpackWholeEntryBuffer::OnNameEnd() {
+ DVLOG(2) << "HpackWholeEntryBuffer::OnNameEnd";
+ DCHECK_EQ(maybe_name_index_, 0u);
+ if (!error_detected_ && !name_.OnEnd()) {
+ ReportError("Error decoding HPACK entry name.");
+ }
+}
+
+void HpackWholeEntryBuffer::OnValueStart(bool huffman_encoded, size_t len) {
+ DVLOG(2) << "HpackWholeEntryBuffer::OnValueStart: huffman_encoded="
+ << (huffman_encoded ? "true" : "false") << ", len=" << len;
+ if (!error_detected_) {
+ if (len > max_string_size_bytes_) {
+ DVLOG(1) << "Value length (" << len << ") is longer than permitted ("
+ << max_string_size_bytes_ << ")";
+ ReportError("HPACK entry value size is too long.");
+ return;
+ }
+ value_.OnStart(huffman_encoded, len);
+ }
+}
+
+void HpackWholeEntryBuffer::OnValueData(const char* data, size_t len) {
+ DVLOG(2) << "HpackWholeEntryBuffer::OnValueData: len=" << len << " data:\n"
+ << Http2HexDump(Http2StringPiece(data, len));
+ if (!error_detected_ && !value_.OnData(data, len)) {
+ ReportError("Error decoding HPACK entry value.");
+ }
+}
+
+void HpackWholeEntryBuffer::OnValueEnd() {
+ DVLOG(2) << "HpackWholeEntryBuffer::OnValueEnd";
+ if (error_detected_) {
+ return;
+ }
+ if (!value_.OnEnd()) {
+ ReportError("Error decoding HPACK entry value.");
+ return;
+ }
+ if (maybe_name_index_ == 0) {
+ listener_->OnLiteralNameAndValue(entry_type_, &name_, &value_);
+ name_.Reset();
+ } else {
+ listener_->OnNameIndexAndLiteralValue(entry_type_, maybe_name_index_,
+ &value_);
+ }
+ value_.Reset();
+}
+
+void HpackWholeEntryBuffer::OnDynamicTableSizeUpdate(size_t size) {
+ DVLOG(2) << "HpackWholeEntryBuffer::OnDynamicTableSizeUpdate: size=" << size;
+ listener_->OnDynamicTableSizeUpdate(size);
+}
+
+void HpackWholeEntryBuffer::ReportError(Http2StringPiece error_message) {
+ if (!error_detected_) {
+ DVLOG(1) << "HpackWholeEntryBuffer::ReportError: " << error_message;
+ error_detected_ = true;
+ listener_->OnHpackDecodeError(error_message);
+ listener_ = HpackWholeEntryNoOpListener::NoOpListener();
+ }
+}
+
+} // namespace http2
diff --git a/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_whole_entry_buffer.h b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_whole_entry_buffer.h
new file mode 100644
index 00000000000..61bf58350ed
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_whole_entry_buffer.h
@@ -0,0 +1,104 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_HTTP2_HPACK_DECODER_HPACK_WHOLE_ENTRY_BUFFER_H_
+#define QUICHE_HTTP2_HPACK_DECODER_HPACK_WHOLE_ENTRY_BUFFER_H_
+
+// HpackWholeEntryBuffer isolates a listener from the fact that an entry may
+// be split across multiple input buffers, providing one callback per entry.
+// HpackWholeEntryBuffer requires that the HpackEntryDecoderListener be made in
+// the correct order, which is tested by hpack_entry_decoder_test.cc.
+
+#include <stddef.h>
+
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_string_buffer.h"
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_entry_decoder_listener.h"
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_whole_entry_listener.h"
+#include "net/third_party/quiche/src/http2/hpack/http2_hpack_constants.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_export.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string_piece.h"
+
+namespace http2 {
+
+// TODO(jamessynge): Consider renaming HpackEntryDecoderListener to
+// HpackEntryPartsListener or HpackEntryFragmentsListener.
+class HTTP2_EXPORT_PRIVATE HpackWholeEntryBuffer
+ : public HpackEntryDecoderListener {
+ public:
+ // max_string_size specifies the maximum size of an on-the-wire string (name
+ // or value, plain or Huffman encoded) that will be accepted. See sections
+ // 5.1 and 5.2 of RFC 7541. This is a defense against OOM attacks; HTTP/2
+ // allows a decoder to enforce any limit of the size of the header lists
+ // that it is willing decode, including less than the MAX_HEADER_LIST_SIZE
+ // setting, a setting that is initially unlimited. For example, we might
+ // choose to send a MAX_HEADER_LIST_SIZE of 64KB, and to use that same value
+ // as the upper bound for individual strings.
+ HpackWholeEntryBuffer(HpackWholeEntryListener* listener,
+ size_t max_string_size);
+ ~HpackWholeEntryBuffer() override;
+
+ HpackWholeEntryBuffer(const HpackWholeEntryBuffer&) = delete;
+ HpackWholeEntryBuffer& operator=(const HpackWholeEntryBuffer&) = delete;
+
+ // Set the listener to be notified when a whole entry has been decoded.
+ // The listener may be changed at any time.
+ void set_listener(HpackWholeEntryListener* listener);
+
+ // Set how much encoded data this decoder is willing to buffer.
+ // TODO(jamessynge): Come up with consistent semantics for this protection
+ // across the various decoders; e.g. should it be for a single string or
+ // a single header entry?
+ void set_max_string_size_bytes(size_t max_string_size_bytes);
+
+ // Ensure that decoded strings pointed to by the HpackDecoderStringBuffer
+ // instances name_ and value_ are buffered, which allows any underlying
+ // transport buffer to be freed or reused without overwriting the decoded
+ // strings. This is needed only when an HPACK entry is split across transport
+ // buffers. See HpackDecoder::DecodeFragment.
+ void BufferStringsIfUnbuffered();
+
+ // Was an error detected? After an error has been detected and reported,
+ // no further callbacks will be made to the listener.
+ bool error_detected() const { return error_detected_; }
+
+ // Returns the estimate of dynamically allocated memory in bytes.
+ size_t EstimateMemoryUsage() const;
+
+ // Implement the HpackEntryDecoderListener methods.
+
+ void OnIndexedHeader(size_t index) override;
+ void OnStartLiteralHeader(HpackEntryType entry_type,
+ size_t maybe_name_index) override;
+ void OnNameStart(bool huffman_encoded, size_t len) override;
+ void OnNameData(const char* data, size_t len) override;
+ void OnNameEnd() override;
+ void OnValueStart(bool huffman_encoded, size_t len) override;
+ void OnValueData(const char* data, size_t len) override;
+ void OnValueEnd() override;
+ void OnDynamicTableSizeUpdate(size_t size) override;
+
+ private:
+ void ReportError(Http2StringPiece error_message);
+
+ HpackWholeEntryListener* listener_;
+ HpackDecoderStringBuffer name_, value_;
+
+ // max_string_size_bytes_ specifies the maximum allowed size of an on-the-wire
+ // string. Larger strings will be reported as errors to the listener; the
+ // endpoint should treat these as COMPRESSION errors, which are CONNECTION
+ // level errors.
+ size_t max_string_size_bytes_;
+
+ // The name index (or zero) of the current header entry with a literal value.
+ size_t maybe_name_index_;
+
+ // The type of the current header entry (with literals) that is being decoded.
+ HpackEntryType entry_type_;
+
+ bool error_detected_ = false;
+};
+
+} // namespace http2
+
+#endif // QUICHE_HTTP2_HPACK_DECODER_HPACK_WHOLE_ENTRY_BUFFER_H_
diff --git a/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_whole_entry_buffer_test.cc b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_whole_entry_buffer_test.cc
new file mode 100644
index 00000000000..75b281c062d
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_whole_entry_buffer_test.cc
@@ -0,0 +1,206 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_whole_entry_buffer.h"
+
+// Tests of HpackWholeEntryBuffer: does it buffer correctly, and does it
+// detect Huffman decoding errors and oversize string errors?
+
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using ::testing::AllOf;
+using ::testing::HasSubstr;
+using ::testing::InSequence;
+using ::testing::Property;
+using ::testing::StrictMock;
+
+namespace http2 {
+namespace test {
+namespace {
+
+constexpr size_t kMaxStringSize = 20;
+
+class MockHpackWholeEntryListener : public HpackWholeEntryListener {
+ public:
+ ~MockHpackWholeEntryListener() override = default;
+
+ MOCK_METHOD1(OnIndexedHeader, void(size_t index));
+ MOCK_METHOD3(OnNameIndexAndLiteralValue,
+ void(HpackEntryType entry_type,
+ size_t name_index,
+ HpackDecoderStringBuffer* value_buffer));
+ MOCK_METHOD3(OnLiteralNameAndValue,
+ void(HpackEntryType entry_type,
+ HpackDecoderStringBuffer* name_buffer,
+ HpackDecoderStringBuffer* value_buffer));
+ MOCK_METHOD1(OnDynamicTableSizeUpdate, void(size_t size));
+ MOCK_METHOD1(OnHpackDecodeError, void(Http2StringPiece error_message));
+};
+
+class HpackWholeEntryBufferTest : public ::testing::Test {
+ protected:
+ HpackWholeEntryBufferTest() : entry_buffer_(&listener_, kMaxStringSize) {}
+ ~HpackWholeEntryBufferTest() override = default;
+
+ StrictMock<MockHpackWholeEntryListener> listener_;
+ HpackWholeEntryBuffer entry_buffer_;
+};
+
+// OnIndexedHeader is an immediate pass through.
+TEST_F(HpackWholeEntryBufferTest, OnIndexedHeader) {
+ {
+ InSequence seq;
+ EXPECT_CALL(listener_, OnIndexedHeader(17));
+ entry_buffer_.OnIndexedHeader(17);
+ }
+ {
+ InSequence seq;
+ EXPECT_CALL(listener_, OnIndexedHeader(62));
+ entry_buffer_.OnIndexedHeader(62);
+ }
+ {
+ InSequence seq;
+ EXPECT_CALL(listener_, OnIndexedHeader(62));
+ entry_buffer_.OnIndexedHeader(62);
+ }
+ {
+ InSequence seq;
+ EXPECT_CALL(listener_, OnIndexedHeader(128));
+ entry_buffer_.OnIndexedHeader(128);
+ }
+ StrictMock<MockHpackWholeEntryListener> listener2;
+ entry_buffer_.set_listener(&listener2);
+ {
+ InSequence seq;
+ EXPECT_CALL(listener2, OnIndexedHeader(100));
+ entry_buffer_.OnIndexedHeader(100);
+ }
+}
+
+// OnDynamicTableSizeUpdate is an immediate pass through.
+TEST_F(HpackWholeEntryBufferTest, OnDynamicTableSizeUpdate) {
+ {
+ InSequence seq;
+ EXPECT_CALL(listener_, OnDynamicTableSizeUpdate(4096));
+ entry_buffer_.OnDynamicTableSizeUpdate(4096);
+ }
+ {
+ InSequence seq;
+ EXPECT_CALL(listener_, OnDynamicTableSizeUpdate(0));
+ entry_buffer_.OnDynamicTableSizeUpdate(0);
+ }
+ {
+ InSequence seq;
+ EXPECT_CALL(listener_, OnDynamicTableSizeUpdate(1024));
+ entry_buffer_.OnDynamicTableSizeUpdate(1024);
+ }
+ {
+ InSequence seq;
+ EXPECT_CALL(listener_, OnDynamicTableSizeUpdate(1024));
+ entry_buffer_.OnDynamicTableSizeUpdate(1024);
+ }
+ StrictMock<MockHpackWholeEntryListener> listener2;
+ entry_buffer_.set_listener(&listener2);
+ {
+ InSequence seq;
+ EXPECT_CALL(listener2, OnDynamicTableSizeUpdate(0));
+ entry_buffer_.OnDynamicTableSizeUpdate(0);
+ }
+}
+
+TEST_F(HpackWholeEntryBufferTest, OnNameIndexAndLiteralValue) {
+ entry_buffer_.OnStartLiteralHeader(HpackEntryType::kNeverIndexedLiteralHeader,
+ 123);
+ entry_buffer_.OnValueStart(false, 10);
+ entry_buffer_.OnValueData("some data.", 10);
+
+ // Force the value to be buffered.
+ entry_buffer_.BufferStringsIfUnbuffered();
+
+ EXPECT_CALL(
+ listener_,
+ OnNameIndexAndLiteralValue(
+ HpackEntryType::kNeverIndexedLiteralHeader, 123,
+ AllOf(Property(&HpackDecoderStringBuffer::str, "some data."),
+ Property(&HpackDecoderStringBuffer::BufferedLength, 10))));
+
+ entry_buffer_.OnValueEnd();
+}
+
+TEST_F(HpackWholeEntryBufferTest, OnLiteralNameAndValue) {
+ entry_buffer_.OnStartLiteralHeader(HpackEntryType::kIndexedLiteralHeader, 0);
+ // Force the name to be buffered by delivering it in two pieces.
+ entry_buffer_.OnNameStart(false, 9);
+ entry_buffer_.OnNameData("some-", 5);
+ entry_buffer_.OnNameData("name", 4);
+ entry_buffer_.OnNameEnd();
+ entry_buffer_.OnValueStart(false, 12);
+ entry_buffer_.OnValueData("Header Value", 12);
+
+ EXPECT_CALL(
+ listener_,
+ OnLiteralNameAndValue(
+ HpackEntryType::kIndexedLiteralHeader,
+ AllOf(Property(&HpackDecoderStringBuffer::str, "some-name"),
+ Property(&HpackDecoderStringBuffer::BufferedLength, 9)),
+ AllOf(Property(&HpackDecoderStringBuffer::str, "Header Value"),
+ Property(&HpackDecoderStringBuffer::BufferedLength, 0))));
+
+ entry_buffer_.OnValueEnd();
+}
+
+// Verify that a name longer than the allowed size generates an error.
+TEST_F(HpackWholeEntryBufferTest, NameTooLong) {
+ entry_buffer_.OnStartLiteralHeader(HpackEntryType::kIndexedLiteralHeader, 0);
+ EXPECT_CALL(listener_, OnHpackDecodeError(HasSubstr("HPACK entry name")));
+ entry_buffer_.OnNameStart(false, kMaxStringSize + 1);
+}
+
+// Verify that a name longer than the allowed size generates an error.
+TEST_F(HpackWholeEntryBufferTest, ValueTooLong) {
+ entry_buffer_.OnStartLiteralHeader(HpackEntryType::kIndexedLiteralHeader, 1);
+ EXPECT_CALL(listener_, OnHpackDecodeError(HasSubstr("HPACK entry value")));
+ entry_buffer_.OnValueStart(false, kMaxStringSize + 1);
+}
+
+// Verify that a Huffman encoded name with an explicit EOS generates an error
+// for an explicit EOS.
+TEST_F(HpackWholeEntryBufferTest, NameHuffmanError) {
+ const char data[] = "\xff\xff\xff";
+ entry_buffer_.OnStartLiteralHeader(HpackEntryType::kUnindexedLiteralHeader,
+ 0);
+ entry_buffer_.OnNameStart(true, 4);
+ entry_buffer_.OnNameData(data, 3);
+
+ EXPECT_CALL(listener_, OnHpackDecodeError(HasSubstr("HPACK entry name")));
+
+ entry_buffer_.OnNameData(data, 1);
+
+ // After an error is reported, the listener is not called again.
+ EXPECT_CALL(listener_, OnDynamicTableSizeUpdate(8096)).Times(0);
+ entry_buffer_.OnDynamicTableSizeUpdate(8096);
+}
+
+// Verify that a Huffman encoded value that isn't properly terminated with
+// a partial EOS symbol generates an error.
+TEST_F(HpackWholeEntryBufferTest, ValueeHuffmanError) {
+ const char data[] = "\x00\x00\x00";
+ entry_buffer_.OnStartLiteralHeader(HpackEntryType::kNeverIndexedLiteralHeader,
+ 61);
+ entry_buffer_.OnValueStart(true, 3);
+ entry_buffer_.OnValueData(data, 3);
+
+ EXPECT_CALL(listener_, OnHpackDecodeError(HasSubstr("HPACK entry value")));
+
+ entry_buffer_.OnValueEnd();
+
+ // After an error is reported, the listener is not called again.
+ EXPECT_CALL(listener_, OnIndexedHeader(17)).Times(0);
+ entry_buffer_.OnIndexedHeader(17);
+}
+
+} // namespace
+} // namespace test
+} // namespace http2
diff --git a/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_whole_entry_listener.cc b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_whole_entry_listener.cc
new file mode 100644
index 00000000000..b92e64a7887
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_whole_entry_listener.cc
@@ -0,0 +1,33 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_whole_entry_listener.h"
+
+namespace http2 {
+
+HpackWholeEntryListener::~HpackWholeEntryListener() = default;
+
+HpackWholeEntryNoOpListener::~HpackWholeEntryNoOpListener() = default;
+
+void HpackWholeEntryNoOpListener::OnIndexedHeader(size_t index) {}
+void HpackWholeEntryNoOpListener::OnNameIndexAndLiteralValue(
+ HpackEntryType entry_type,
+ size_t name_index,
+ HpackDecoderStringBuffer* value_buffer) {}
+void HpackWholeEntryNoOpListener::OnLiteralNameAndValue(
+ HpackEntryType entry_type,
+ HpackDecoderStringBuffer* name_buffer,
+ HpackDecoderStringBuffer* value_buffer) {}
+void HpackWholeEntryNoOpListener::OnDynamicTableSizeUpdate(size_t size) {}
+void HpackWholeEntryNoOpListener::OnHpackDecodeError(
+ Http2StringPiece error_message) {}
+
+// static
+HpackWholeEntryNoOpListener* HpackWholeEntryNoOpListener::NoOpListener() {
+ static HpackWholeEntryNoOpListener* static_instance =
+ new HpackWholeEntryNoOpListener();
+ return static_instance;
+}
+
+} // namespace http2
diff --git a/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_whole_entry_listener.h b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_whole_entry_listener.h
new file mode 100644
index 00000000000..2e559cef4d7
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/hpack/decoder/hpack_whole_entry_listener.h
@@ -0,0 +1,80 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Defines HpackWholeEntryListener, the base class of listeners for decoded
+// complete HPACK entries, as opposed to HpackEntryDecoderListener which
+// receives multiple callbacks for some single entries.
+
+#ifndef QUICHE_HTTP2_HPACK_DECODER_HPACK_WHOLE_ENTRY_LISTENER_H_
+#define QUICHE_HTTP2_HPACK_DECODER_HPACK_WHOLE_ENTRY_LISTENER_H_
+
+#include <stddef.h>
+
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_string_buffer.h"
+#include "net/third_party/quiche/src/http2/hpack/http2_hpack_constants.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_export.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string_piece.h"
+
+namespace http2 {
+
+class HTTP2_EXPORT_PRIVATE HpackWholeEntryListener {
+ public:
+ virtual ~HpackWholeEntryListener();
+
+ // Called when an indexed header (i.e. one in the static or dynamic table) has
+ // been decoded from an HPACK block. index is supposed to be non-zero, but
+ // that has not been checked by the caller.
+ virtual void OnIndexedHeader(size_t index) = 0;
+
+ // Called when a header entry with a name index and literal value has
+ // been fully decoded from an HPACK block. name_index is NOT zero.
+ // entry_type will be kIndexedLiteralHeader, kUnindexedLiteralHeader, or
+ // kNeverIndexedLiteralHeader.
+ virtual void OnNameIndexAndLiteralValue(
+ HpackEntryType entry_type,
+ size_t name_index,
+ HpackDecoderStringBuffer* value_buffer) = 0;
+
+ // Called when a header entry with a literal name and literal value
+ // has been fully decoded from an HPACK block. entry_type will be
+ // kIndexedLiteralHeader, kUnindexedLiteralHeader, or
+ // kNeverIndexedLiteralHeader.
+ virtual void OnLiteralNameAndValue(
+ HpackEntryType entry_type,
+ HpackDecoderStringBuffer* name_buffer,
+ HpackDecoderStringBuffer* value_buffer) = 0;
+
+ // Called when an update to the size of the peer's dynamic table has been
+ // decoded.
+ virtual void OnDynamicTableSizeUpdate(size_t size) = 0;
+
+ // OnHpackDecodeError is called if an error is detected while decoding.
+ // error_message may be used in a GOAWAY frame as the Opaque Data.
+ virtual void OnHpackDecodeError(Http2StringPiece error_message) = 0;
+};
+
+// A no-op implementation of HpackWholeEntryDecoderListener, useful for ignoring
+// callbacks once an error is detected.
+class HpackWholeEntryNoOpListener : public HpackWholeEntryListener {
+ public:
+ ~HpackWholeEntryNoOpListener() override;
+
+ void OnIndexedHeader(size_t index) override;
+ void OnNameIndexAndLiteralValue(
+ HpackEntryType entry_type,
+ size_t name_index,
+ HpackDecoderStringBuffer* value_buffer) override;
+ void OnLiteralNameAndValue(HpackEntryType entry_type,
+ HpackDecoderStringBuffer* name_buffer,
+ HpackDecoderStringBuffer* value_buffer) override;
+ void OnDynamicTableSizeUpdate(size_t size) override;
+ void OnHpackDecodeError(Http2StringPiece error_message) override;
+
+ // Returns a listener that ignores all the calls.
+ static HpackWholeEntryNoOpListener* NoOpListener();
+};
+
+} // namespace http2
+
+#endif // QUICHE_HTTP2_HPACK_DECODER_HPACK_WHOLE_ENTRY_LISTENER_H_
diff --git a/chromium/net/third_party/quiche/src/http2/hpack/hpack_static_table_entries.inc b/chromium/net/third_party/quiche/src/http2/hpack/hpack_static_table_entries.inc
new file mode 100644
index 00000000000..c6ae125f3b2
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/hpack/hpack_static_table_entries.inc
@@ -0,0 +1,65 @@
+// This file is designed to be included by C/C++ files which need the contents
+// of the HPACK static table. It may be included more than once if necessary.
+// See http://httpwg.org/specs/rfc7541.html#static.table.definition
+
+STATIC_TABLE_ENTRY(":authority", "", 1);
+STATIC_TABLE_ENTRY(":method", "GET", 2);
+STATIC_TABLE_ENTRY(":method", "POST", 3);
+STATIC_TABLE_ENTRY(":path", "/", 4);
+STATIC_TABLE_ENTRY(":path", "/index.html", 5);
+STATIC_TABLE_ENTRY(":scheme", "http", 6);
+STATIC_TABLE_ENTRY(":scheme", "https", 7);
+STATIC_TABLE_ENTRY(":status", "200", 8);
+STATIC_TABLE_ENTRY(":status", "204", 9);
+STATIC_TABLE_ENTRY(":status", "206", 10);
+STATIC_TABLE_ENTRY(":status", "304", 11);
+STATIC_TABLE_ENTRY(":status", "400", 12);
+STATIC_TABLE_ENTRY(":status", "404", 13);
+STATIC_TABLE_ENTRY(":status", "500", 14);
+STATIC_TABLE_ENTRY("accept-charset", "", 15);
+STATIC_TABLE_ENTRY("accept-encoding", "gzip, deflate", 16);
+STATIC_TABLE_ENTRY("accept-language", "", 17);
+STATIC_TABLE_ENTRY("accept-ranges", "", 18);
+STATIC_TABLE_ENTRY("accept", "", 19);
+STATIC_TABLE_ENTRY("access-control-allow-origin", "", 20);
+STATIC_TABLE_ENTRY("age", "", 21);
+STATIC_TABLE_ENTRY("allow", "", 22);
+STATIC_TABLE_ENTRY("authorization", "", 23);
+STATIC_TABLE_ENTRY("cache-control", "", 24);
+STATIC_TABLE_ENTRY("content-disposition", "", 25);
+STATIC_TABLE_ENTRY("content-encoding", "", 26);
+STATIC_TABLE_ENTRY("content-language", "", 27);
+STATIC_TABLE_ENTRY("content-length", "", 28);
+STATIC_TABLE_ENTRY("content-location", "", 29);
+STATIC_TABLE_ENTRY("content-range", "", 30);
+STATIC_TABLE_ENTRY("content-type", "", 31);
+STATIC_TABLE_ENTRY("cookie", "", 32);
+STATIC_TABLE_ENTRY("date", "", 33);
+STATIC_TABLE_ENTRY("etag", "", 34);
+STATIC_TABLE_ENTRY("expect", "", 35);
+STATIC_TABLE_ENTRY("expires", "", 36);
+STATIC_TABLE_ENTRY("from", "", 37);
+STATIC_TABLE_ENTRY("host", "", 38);
+STATIC_TABLE_ENTRY("if-match", "", 39);
+STATIC_TABLE_ENTRY("if-modified-since", "", 40);
+STATIC_TABLE_ENTRY("if-none-match", "", 41);
+STATIC_TABLE_ENTRY("if-range", "", 42);
+STATIC_TABLE_ENTRY("if-unmodified-since", "", 43);
+STATIC_TABLE_ENTRY("last-modified", "", 44);
+STATIC_TABLE_ENTRY("link", "", 45);
+STATIC_TABLE_ENTRY("location", "", 46);
+STATIC_TABLE_ENTRY("max-forwards", "", 47);
+STATIC_TABLE_ENTRY("proxy-authenticate", "", 48);
+STATIC_TABLE_ENTRY("proxy-authorization", "", 49);
+STATIC_TABLE_ENTRY("range", "", 50);
+STATIC_TABLE_ENTRY("referer", "", 51);
+STATIC_TABLE_ENTRY("refresh", "", 52);
+STATIC_TABLE_ENTRY("retry-after", "", 53);
+STATIC_TABLE_ENTRY("server", "", 54);
+STATIC_TABLE_ENTRY("set-cookie", "", 55);
+STATIC_TABLE_ENTRY("strict-transport-security", "", 56);
+STATIC_TABLE_ENTRY("transfer-encoding", "", 57);
+STATIC_TABLE_ENTRY("user-agent", "", 58);
+STATIC_TABLE_ENTRY("vary", "", 59);
+STATIC_TABLE_ENTRY("via", "", 60);
+STATIC_TABLE_ENTRY("www-authenticate", "", 61);
diff --git a/chromium/net/third_party/quiche/src/http2/hpack/hpack_string.cc b/chromium/net/third_party/quiche/src/http2/hpack/hpack_string.cc
new file mode 100644
index 00000000000..58bb821810e
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/hpack/hpack_string.cc
@@ -0,0 +1,72 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/http2/hpack/hpack_string.h"
+
+#include <utility>
+
+#include "base/logging.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string_utils.h"
+
+namespace http2 {
+
+HpackString::HpackString(const char* data) : str_(data) {}
+HpackString::HpackString(Http2StringPiece str) : str_(Http2String(str)) {}
+HpackString::HpackString(Http2String str) : str_(std::move(str)) {}
+HpackString::HpackString(const HpackString& other) = default;
+HpackString::~HpackString() = default;
+
+Http2StringPiece HpackString::ToStringPiece() const {
+ return str_;
+}
+
+bool HpackString::operator==(const HpackString& other) const {
+ return str_ == other.str_;
+}
+bool HpackString::operator==(Http2StringPiece str) const {
+ return str == str_;
+}
+
+bool operator==(Http2StringPiece a, const HpackString& b) {
+ return b == a;
+}
+bool operator!=(Http2StringPiece a, const HpackString& b) {
+ return !(b == a);
+}
+bool operator!=(const HpackString& a, const HpackString& b) {
+ return !(a == b);
+}
+bool operator!=(const HpackString& a, Http2StringPiece b) {
+ return !(a == b);
+}
+std::ostream& operator<<(std::ostream& out, const HpackString& v) {
+ return out << v.ToString();
+}
+
+HpackStringPair::HpackStringPair(const HpackString& name,
+ const HpackString& value)
+ : name(name), value(value) {
+ DVLOG(3) << DebugString() << " ctor";
+}
+
+HpackStringPair::HpackStringPair(Http2StringPiece name, Http2StringPiece value)
+ : name(name), value(value) {
+ DVLOG(3) << DebugString() << " ctor";
+}
+
+HpackStringPair::~HpackStringPair() {
+ DVLOG(3) << DebugString() << " dtor";
+}
+
+Http2String HpackStringPair::DebugString() const {
+ return Http2StrCat("HpackStringPair(name=", name.ToString(),
+ ", value=", value.ToString(), ")");
+}
+
+std::ostream& operator<<(std::ostream& os, const HpackStringPair& p) {
+ os << p.DebugString();
+ return os;
+}
+
+} // namespace http2
diff --git a/chromium/net/third_party/quiche/src/http2/hpack/hpack_string.h b/chromium/net/third_party/quiche/src/http2/hpack/hpack_string.h
new file mode 100644
index 00000000000..b1c499c95be
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/hpack/hpack_string.h
@@ -0,0 +1,75 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_HTTP2_HPACK_HPACK_STRING_H_
+#define QUICHE_HTTP2_HPACK_HPACK_STRING_H_
+
+// HpackString is currently a very simple container for a string, but allows us
+// to relatively easily experiment with alternate string storage mechanisms for
+// handling strings to be encoded with HPACK, or decoded from HPACK, such as
+// a ref-counted string.
+
+#include <stddef.h>
+
+#include <iosfwd>
+
+#include "net/third_party/quiche/src/http2/platform/api/http2_export.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string_piece.h"
+
+namespace http2 {
+
+class HTTP2_EXPORT_PRIVATE HpackString {
+ public:
+ explicit HpackString(const char* data);
+ explicit HpackString(Http2StringPiece str);
+ explicit HpackString(Http2String str);
+ HpackString(const HpackString& other);
+
+ // Not sure yet whether this move ctor is required/sensible.
+ HpackString(HpackString&& other) = default;
+
+ ~HpackString();
+
+ size_t size() const { return str_.size(); }
+ const Http2String& ToString() const { return str_; }
+ Http2StringPiece ToStringPiece() const;
+
+ bool operator==(const HpackString& other) const;
+
+ bool operator==(Http2StringPiece str) const;
+
+ private:
+ Http2String str_;
+};
+
+HTTP2_EXPORT_PRIVATE bool operator==(Http2StringPiece a, const HpackString& b);
+HTTP2_EXPORT_PRIVATE bool operator!=(Http2StringPiece a, const HpackString& b);
+HTTP2_EXPORT_PRIVATE bool operator!=(const HpackString& a,
+ const HpackString& b);
+HTTP2_EXPORT_PRIVATE bool operator!=(const HpackString& a, Http2StringPiece b);
+HTTP2_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& out,
+ const HpackString& v);
+
+struct HTTP2_EXPORT_PRIVATE HpackStringPair {
+ HpackStringPair(const HpackString& name, const HpackString& value);
+ HpackStringPair(Http2StringPiece name, Http2StringPiece value);
+ ~HpackStringPair();
+
+ // Returns the size of a header entry with this name and value, per the RFC:
+ // http://httpwg.org/specs/rfc7541.html#calculating.table.size
+ size_t size() const { return 32 + name.size() + value.size(); }
+
+ Http2String DebugString() const;
+
+ const HpackString name;
+ const HpackString value;
+};
+
+HTTP2_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& os,
+ const HpackStringPair& p);
+
+} // namespace http2
+
+#endif // QUICHE_HTTP2_HPACK_HPACK_STRING_H_
diff --git a/chromium/net/third_party/quiche/src/http2/hpack/hpack_string_test.cc b/chromium/net/third_party/quiche/src/http2/hpack/hpack_string_test.cc
new file mode 100644
index 00000000000..87f5975886d
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/hpack/hpack_string_test.cc
@@ -0,0 +1,149 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/http2/hpack/hpack_string.h"
+
+// Tests of HpackString.
+
+#include <utility>
+
+#include "base/logging.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_test_helpers.h"
+
+using ::testing::AssertionFailure;
+using ::testing::AssertionResult;
+using ::testing::AssertionSuccess;
+
+namespace http2 {
+namespace test {
+namespace {
+
+const char kStr0[] = "s0: Some string to be copied into another string.";
+const char kStr1[] = "S1 - some string to be copied into yet another string.";
+
+class HpackStringTest : public ::testing::Test {
+ protected:
+ AssertionResult VerifyNotEqual(HpackString* actual,
+ const Http2String& not_expected_str) {
+ const char* not_expected_ptr = not_expected_str.c_str();
+ Http2StringPiece not_expected_sp(not_expected_str);
+
+ VERIFY_NE(*actual, not_expected_ptr);
+ VERIFY_NE(*actual, not_expected_sp);
+ VERIFY_NE(*actual, not_expected_str);
+ VERIFY_NE(actual->ToStringPiece(), not_expected_sp);
+
+ if (!(not_expected_ptr != *actual)) {
+ return AssertionFailure();
+ }
+ if (!(not_expected_sp != *actual)) {
+ return AssertionFailure();
+ }
+ if (!(not_expected_str != *actual)) {
+ return AssertionFailure();
+ }
+ if (!(not_expected_sp != actual->ToStringPiece())) {
+ return AssertionFailure();
+ }
+
+ return AssertionSuccess();
+ }
+
+ AssertionResult VerifyEqual(HpackString* actual,
+ const Http2String& expected_str) {
+ VERIFY_EQ(actual->size(), expected_str.size());
+
+ const char* expected_ptr = expected_str.c_str();
+ const Http2StringPiece expected_sp(expected_str);
+
+ VERIFY_EQ(*actual, expected_ptr);
+ VERIFY_EQ(*actual, expected_sp);
+ VERIFY_EQ(*actual, expected_str);
+ VERIFY_EQ(actual->ToStringPiece(), expected_sp);
+
+ if (!(expected_sp == *actual)) {
+ return AssertionFailure();
+ }
+ if (!(expected_ptr == *actual)) {
+ return AssertionFailure();
+ }
+ if (!(expected_str == *actual)) {
+ return AssertionFailure();
+ }
+ if (!(expected_sp == actual->ToStringPiece())) {
+ return AssertionFailure();
+ }
+
+ return AssertionSuccess();
+ }
+};
+
+TEST_F(HpackStringTest, CharArrayConstructor) {
+ HpackString hs0(kStr0);
+ EXPECT_TRUE(VerifyEqual(&hs0, kStr0));
+ EXPECT_TRUE(VerifyNotEqual(&hs0, kStr1));
+
+ HpackString hs1(kStr1);
+ EXPECT_TRUE(VerifyEqual(&hs1, kStr1));
+ EXPECT_TRUE(VerifyNotEqual(&hs1, kStr0));
+}
+
+TEST_F(HpackStringTest, StringPieceConstructor) {
+ Http2StringPiece sp0(kStr0);
+ HpackString hs0(sp0);
+ EXPECT_TRUE(VerifyEqual(&hs0, kStr0));
+ EXPECT_TRUE(VerifyNotEqual(&hs0, kStr1));
+
+ Http2StringPiece sp1(kStr1);
+ HpackString hs1(sp1);
+ EXPECT_TRUE(VerifyEqual(&hs1, kStr1));
+ EXPECT_TRUE(VerifyNotEqual(&hs1, kStr0));
+}
+
+TEST_F(HpackStringTest, MoveStringConstructor) {
+ Http2String str0(kStr0);
+ HpackString hs0(str0);
+ EXPECT_TRUE(VerifyEqual(&hs0, kStr0));
+ EXPECT_TRUE(VerifyNotEqual(&hs0, kStr1));
+
+ Http2String str1(kStr1);
+ HpackString hs1(str1);
+ EXPECT_TRUE(VerifyEqual(&hs1, kStr1));
+ EXPECT_TRUE(VerifyNotEqual(&hs1, kStr0));
+}
+
+TEST_F(HpackStringTest, CopyConstructor) {
+ Http2StringPiece sp0(kStr0);
+ HpackString hs0(sp0);
+ HpackString hs1(hs0);
+ EXPECT_EQ(hs0, hs1);
+
+ EXPECT_TRUE(VerifyEqual(&hs0, kStr0));
+ EXPECT_TRUE(VerifyEqual(&hs1, kStr0));
+
+ EXPECT_TRUE(VerifyNotEqual(&hs0, kStr1));
+ EXPECT_TRUE(VerifyNotEqual(&hs1, kStr1));
+}
+
+TEST_F(HpackStringTest, MoveConstructor) {
+ Http2StringPiece sp0(kStr0);
+ HpackString hs0(sp0);
+ EXPECT_TRUE(VerifyEqual(&hs0, kStr0));
+ EXPECT_TRUE(VerifyNotEqual(&hs0, ""));
+
+ HpackString hs1(std::move(hs0));
+ EXPECT_NE(hs0, hs1);
+
+ EXPECT_TRUE(VerifyEqual(&hs1, kStr0));
+ EXPECT_TRUE(VerifyEqual(&hs0, ""));
+ EXPECT_TRUE(VerifyNotEqual(&hs1, ""));
+
+ LOG(INFO) << hs0;
+ LOG(INFO) << hs1;
+}
+
+} // namespace
+} // namespace test
+} // namespace http2
diff --git a/chromium/net/third_party/quiche/src/http2/hpack/http2_hpack_constants.cc b/chromium/net/third_party/quiche/src/http2/hpack/http2_hpack_constants.cc
new file mode 100644
index 00000000000..f258ab9bcbd
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/hpack/http2_hpack_constants.cc
@@ -0,0 +1,31 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/http2/hpack/http2_hpack_constants.h"
+
+#include "net/third_party/quiche/src/http2/platform/api/http2_string_utils.h"
+
+namespace http2 {
+
+Http2String HpackEntryTypeToString(HpackEntryType v) {
+ switch (v) {
+ case HpackEntryType::kIndexedHeader:
+ return "kIndexedHeader";
+ case HpackEntryType::kDynamicTableSizeUpdate:
+ return "kDynamicTableSizeUpdate";
+ case HpackEntryType::kIndexedLiteralHeader:
+ return "kIndexedLiteralHeader";
+ case HpackEntryType::kUnindexedLiteralHeader:
+ return "kUnindexedLiteralHeader";
+ case HpackEntryType::kNeverIndexedLiteralHeader:
+ return "kNeverIndexedLiteralHeader";
+ }
+ return Http2StrCat("UnknownHpackEntryType(", static_cast<int>(v), ")");
+}
+
+std::ostream& operator<<(std::ostream& out, HpackEntryType v) {
+ return out << HpackEntryTypeToString(v);
+}
+
+} // namespace http2
diff --git a/chromium/net/third_party/quiche/src/http2/hpack/http2_hpack_constants.h b/chromium/net/third_party/quiche/src/http2/hpack/http2_hpack_constants.h
new file mode 100644
index 00000000000..de0d685595d
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/hpack/http2_hpack_constants.h
@@ -0,0 +1,63 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_HTTP2_HPACK_HTTP2_HPACK_CONSTANTS_H_
+#define QUICHE_HTTP2_HPACK_HTTP2_HPACK_CONSTANTS_H_
+
+// Enum HpackEntryType identifies the 5 basic types of HPACK Block Entries.
+//
+// See the spec for details:
+// https://http2.github.io/http2-spec/compression.html#rfc.section.6
+
+#include <ostream>
+
+#include "net/third_party/quiche/src/http2/platform/api/http2_export.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string.h"
+
+namespace http2 {
+
+const size_t kFirstDynamicTableIndex = 62;
+
+enum class HpackEntryType {
+ // Entry is an index into the static or dynamic table. Decoding it has no
+ // effect on the dynamic table.
+ kIndexedHeader,
+
+ // The entry contains a literal value. The name may be either a literal or a
+ // reference to an entry in the static or dynamic table.
+ // The entry is added to the dynamic table after decoding.
+ kIndexedLiteralHeader,
+
+ // The entry contains a literal value. The name may be either a literal or a
+ // reference to an entry in the static or dynamic table.
+ // The entry is not added to the dynamic table after decoding, but a proxy
+ // may choose to insert the entry into its dynamic table when forwarding
+ // to another endpoint.
+ kUnindexedLiteralHeader,
+
+ // The entry contains a literal value. The name may be either a literal or a
+ // reference to an entry in the static or dynamic table.
+ // The entry is not added to the dynamic table after decoding, and a proxy
+ // must NOT insert the entry into its dynamic table when forwarding to another
+ // endpoint.
+ kNeverIndexedLiteralHeader,
+
+ // Entry conveys the size limit of the dynamic table of the encoder to
+ // the decoder. May be used to flush the table by sending a zero and then
+ // resetting the size back up to the maximum that the encoder will use
+ // (within the limits of SETTINGS_HEADER_TABLE_SIZE sent by the
+ // decoder to the encoder, with the default of 4096 assumed).
+ kDynamicTableSizeUpdate,
+};
+
+// Returns the name of the enum member.
+HTTP2_EXPORT_PRIVATE Http2String HpackEntryTypeToString(HpackEntryType v);
+
+// Inserts the name of the enum member into |out|.
+HTTP2_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& out,
+ HpackEntryType v);
+
+} // namespace http2
+
+#endif // QUICHE_HTTP2_HPACK_HTTP2_HPACK_CONSTANTS_H_
diff --git a/chromium/net/third_party/quiche/src/http2/hpack/http2_hpack_constants_test.cc b/chromium/net/third_party/quiche/src/http2/hpack/http2_hpack_constants_test.cc
new file mode 100644
index 00000000000..5b20b186efd
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/hpack/http2_hpack_constants_test.cc
@@ -0,0 +1,72 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/http2/hpack/http2_hpack_constants.h"
+
+#include "base/logging.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_mock_log.h"
+
+namespace http2 {
+namespace test {
+namespace {
+
+TEST(HpackEntryTypeTest, HpackEntryTypeToString) {
+ EXPECT_EQ("kIndexedHeader",
+ HpackEntryTypeToString(HpackEntryType::kIndexedHeader));
+ EXPECT_EQ("kDynamicTableSizeUpdate",
+ HpackEntryTypeToString(HpackEntryType::kDynamicTableSizeUpdate));
+ EXPECT_EQ("kIndexedLiteralHeader",
+ HpackEntryTypeToString(HpackEntryType::kIndexedLiteralHeader));
+ EXPECT_EQ("kUnindexedLiteralHeader",
+ HpackEntryTypeToString(HpackEntryType::kUnindexedLiteralHeader));
+ EXPECT_EQ("kNeverIndexedLiteralHeader",
+ HpackEntryTypeToString(HpackEntryType::kNeverIndexedLiteralHeader));
+ EXPECT_EQ("UnknownHpackEntryType(12321)",
+ HpackEntryTypeToString(static_cast<HpackEntryType>(12321)));
+}
+
+TEST(HpackEntryTypeTest, OutputHpackEntryType) {
+ {
+ CREATE_HTTP2_MOCK_LOG(log);
+ log.StartCapturingLogs();
+ EXPECT_HTTP2_LOG_CALL_CONTAINS(log, INFO, "kIndexedHeader");
+ LOG(INFO) << HpackEntryType::kIndexedHeader;
+ }
+ {
+ CREATE_HTTP2_MOCK_LOG(log);
+ log.StartCapturingLogs();
+ EXPECT_HTTP2_LOG_CALL_CONTAINS(log, INFO, "kDynamicTableSizeUpdate");
+ LOG(INFO) << HpackEntryType::kDynamicTableSizeUpdate;
+ }
+ {
+ CREATE_HTTP2_MOCK_LOG(log);
+ log.StartCapturingLogs();
+ EXPECT_HTTP2_LOG_CALL_CONTAINS(log, INFO, "kIndexedLiteralHeader");
+ LOG(INFO) << HpackEntryType::kIndexedLiteralHeader;
+ }
+ {
+ CREATE_HTTP2_MOCK_LOG(log);
+ log.StartCapturingLogs();
+ EXPECT_HTTP2_LOG_CALL_CONTAINS(log, INFO, "kUnindexedLiteralHeader");
+ LOG(INFO) << HpackEntryType::kUnindexedLiteralHeader;
+ }
+ {
+ CREATE_HTTP2_MOCK_LOG(log);
+ log.StartCapturingLogs();
+ EXPECT_HTTP2_LOG_CALL_CONTAINS(log, INFO, "kNeverIndexedLiteralHeader");
+ LOG(INFO) << HpackEntryType::kNeverIndexedLiteralHeader;
+ }
+ {
+ CREATE_HTTP2_MOCK_LOG(log);
+ log.StartCapturingLogs();
+ EXPECT_HTTP2_LOG_CALL_CONTAINS(log, INFO, "UnknownHpackEntryType(1234321)");
+ LOG(INFO) << static_cast<HpackEntryType>(1234321);
+ }
+}
+
+} // namespace
+} // namespace test
+} // namespace http2
diff --git a/chromium/net/third_party/quiche/src/http2/hpack/huffman/hpack_huffman_decoder.cc b/chromium/net/third_party/quiche/src/http2/hpack/huffman/hpack_huffman_decoder.cc
new file mode 100644
index 00000000000..d1076ed3c07
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/hpack/huffman/hpack_huffman_decoder.cc
@@ -0,0 +1,487 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/http2/hpack/huffman/hpack_huffman_decoder.h"
+
+#include <bitset>
+#include <limits>
+
+#include "base/logging.h"
+
+// Terminology:
+//
+// Symbol - a plain text (unencoded) character (uint8), or the End-of-String
+// (EOS) symbol, 256.
+//
+// Code - the sequence of bits used to encode a symbol, varying in length from
+// 5 bits for the most common symbols (e.g. '0', '1', and 'a'), to
+// 30 bits for the least common (e.g. the EOS symbol).
+// For those symbols whose codes have the same length, their code values
+// are sorted such that the lower symbol value has a lower code value.
+//
+// Canonical - a symbol's cardinal value when sorted first by code length, and
+// then by symbol value. For example, canonical 0 is for ASCII '0'
+// (uint8 value 0x30), which is the first of the symbols whose code
+// is 5 bits long, and the last canonical is EOS, which is the last
+// of the symbols whose code is 30 bits long.
+
+// TODO(jamessynge): Remove use of binary literals, that is a C++ 14 feature.
+
+namespace http2 {
+namespace {
+
+// HuffmanCode is used to store the codes associated with symbols (a pattern of
+// from 5 to 30 bits).
+typedef uint32_t HuffmanCode;
+
+// HuffmanCodeBitCount is used to store a count of bits in a code.
+typedef uint16_t HuffmanCodeBitCount;
+
+// HuffmanCodeBitSet is used for producing a string version of a code because
+// std::bitset logs nicely.
+typedef std::bitset<32> HuffmanCodeBitSet;
+typedef std::bitset<64> HuffmanAccumulatorBitSet;
+
+static constexpr HuffmanCodeBitCount kMinCodeBitCount = 5;
+static constexpr HuffmanCodeBitCount kMaxCodeBitCount = 30;
+static constexpr HuffmanCodeBitCount kHuffmanCodeBitCount =
+ std::numeric_limits<HuffmanCode>::digits;
+
+static_assert(std::numeric_limits<HuffmanCode>::digits >= kMaxCodeBitCount,
+ "HuffmanCode isn't big enough.");
+
+static_assert(std::numeric_limits<HuffmanAccumulator>::digits >=
+ kMaxCodeBitCount,
+ "HuffmanAccumulator isn't big enough.");
+
+static constexpr HuffmanAccumulatorBitCount kHuffmanAccumulatorBitCount =
+ std::numeric_limits<HuffmanAccumulator>::digits;
+static constexpr HuffmanAccumulatorBitCount kExtraAccumulatorBitCount =
+ kHuffmanAccumulatorBitCount - kHuffmanCodeBitCount;
+
+// PrefixInfo holds info about a group of codes that are all of the same length.
+struct PrefixInfo {
+ // Given the leading bits (32 in this case) of the encoded string, and that
+ // they start with a code of length |code_length|, return the corresponding
+ // canonical for that leading code.
+ uint32_t DecodeToCanonical(HuffmanCode bits) const {
+ // What is the position of the canonical symbol being decoded within
+ // the canonical symbols of |length|?
+ HuffmanCode ordinal_in_length =
+ ((bits - first_code) >> (kHuffmanCodeBitCount - code_length));
+
+ // Combined with |canonical| to produce the position of the canonical symbol
+ // being decoded within all of the canonical symbols.
+ return first_canonical + ordinal_in_length;
+ }
+
+ const HuffmanCode first_code; // First code of this length, left justified in
+ // the field (i.e. the first bit of the code is
+ // the high-order bit).
+ const uint16_t code_length; // Length of the prefix code |base|.
+ const uint16_t first_canonical; // First canonical symbol of this length.
+};
+
+inline std::ostream& operator<<(std::ostream& out, const PrefixInfo& v) {
+ return out << "{first_code: " << HuffmanCodeBitSet(v.first_code)
+ << ", code_length: " << v.code_length
+ << ", first_canonical: " << v.first_canonical << "}";
+}
+
+// Given |value|, a sequence of the leading bits remaining to be decoded,
+// figure out which group of canonicals (by code length) that value starts
+// with. This function was generated.
+PrefixInfo PrefixToInfo(HuffmanCode value) {
+ if (value < 0b10111000000000000000000000000000) {
+ if (value < 0b01010000000000000000000000000000) {
+ return {0b00000000000000000000000000000000, 5, 0};
+ } else {
+ return {0b01010000000000000000000000000000, 6, 10};
+ }
+ } else {
+ if (value < 0b11111110000000000000000000000000) {
+ if (value < 0b11111000000000000000000000000000) {
+ return {0b10111000000000000000000000000000, 7, 36};
+ } else {
+ return {0b11111000000000000000000000000000, 8, 68};
+ }
+ } else {
+ if (value < 0b11111111110000000000000000000000) {
+ if (value < 0b11111111101000000000000000000000) {
+ if (value < 0b11111111010000000000000000000000) {
+ return {0b11111110000000000000000000000000, 10, 74};
+ } else {
+ return {0b11111111010000000000000000000000, 11, 79};
+ }
+ } else {
+ return {0b11111111101000000000000000000000, 12, 82};
+ }
+ } else {
+ if (value < 0b11111111111111100000000000000000) {
+ if (value < 0b11111111111110000000000000000000) {
+ if (value < 0b11111111111100000000000000000000) {
+ return {0b11111111110000000000000000000000, 13, 84};
+ } else {
+ return {0b11111111111100000000000000000000, 14, 90};
+ }
+ } else {
+ return {0b11111111111110000000000000000000, 15, 92};
+ }
+ } else {
+ if (value < 0b11111111111111110100100000000000) {
+ if (value < 0b11111111111111101110000000000000) {
+ if (value < 0b11111111111111100110000000000000) {
+ return {0b11111111111111100000000000000000, 19, 95};
+ } else {
+ return {0b11111111111111100110000000000000, 20, 98};
+ }
+ } else {
+ return {0b11111111111111101110000000000000, 21, 106};
+ }
+ } else {
+ if (value < 0b11111111111111111110101000000000) {
+ if (value < 0b11111111111111111011000000000000) {
+ return {0b11111111111111110100100000000000, 22, 119};
+ } else {
+ return {0b11111111111111111011000000000000, 23, 145};
+ }
+ } else {
+ if (value < 0b11111111111111111111101111000000) {
+ if (value < 0b11111111111111111111100000000000) {
+ if (value < 0b11111111111111111111011000000000) {
+ return {0b11111111111111111110101000000000, 24, 174};
+ } else {
+ return {0b11111111111111111111011000000000, 25, 186};
+ }
+ } else {
+ return {0b11111111111111111111100000000000, 26, 190};
+ }
+ } else {
+ if (value < 0b11111111111111111111111111110000) {
+ if (value < 0b11111111111111111111111000100000) {
+ return {0b11111111111111111111101111000000, 27, 205};
+ } else {
+ return {0b11111111111111111111111000100000, 28, 224};
+ }
+ } else {
+ return {0b11111111111111111111111111110000, 30, 253};
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+// Mapping from canonical symbol (0 to 255) to actual symbol.
+// clang-format off
+constexpr unsigned char kCanonicalToSymbol[] = {
+ '0', '1', '2', 'a', 'c', 'e', 'i', 'o',
+ 's', 't', 0x20, '%', '-', '.', '/', '3',
+ '4', '5', '6', '7', '8', '9', '=', 'A',
+ '_', 'b', 'd', 'f', 'g', 'h', 'l', 'm',
+ 'n', 'p', 'r', 'u', ':', 'B', 'C', 'D',
+ 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L',
+ 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T',
+ 'U', 'V', 'W', 'Y', 'j', 'k', 'q', 'v',
+ 'w', 'x', 'y', 'z', '&', '*', ',', ';',
+ 'X', 'Z', '!', '\"', '(', ')', '?', '\'',
+ '+', '|', '#', '>', 0x00, '$', '@', '[',
+ ']', '~', '^', '}', '<', '`', '{', '\\',
+ 0xc3, 0xd0, 0x80, 0x82, 0x83, 0xa2, 0xb8, 0xc2,
+ 0xe0, 0xe2, 0x99, 0xa1, 0xa7, 0xac, 0xb0, 0xb1,
+ 0xb3, 0xd1, 0xd8, 0xd9, 0xe3, 0xe5, 0xe6, 0x81,
+ 0x84, 0x85, 0x86, 0x88, 0x92, 0x9a, 0x9c, 0xa0,
+ 0xa3, 0xa4, 0xa9, 0xaa, 0xad, 0xb2, 0xb5, 0xb9,
+ 0xba, 0xbb, 0xbd, 0xbe, 0xc4, 0xc6, 0xe4, 0xe8,
+ 0xe9, 0x01, 0x87, 0x89, 0x8a, 0x8b, 0x8c, 0x8d,
+ 0x8f, 0x93, 0x95, 0x96, 0x97, 0x98, 0x9b, 0x9d,
+ 0x9e, 0xa5, 0xa6, 0xa8, 0xae, 0xaf, 0xb4, 0xb6,
+ 0xb7, 0xbc, 0xbf, 0xc5, 0xe7, 0xef, 0x09, 0x8e,
+ 0x90, 0x91, 0x94, 0x9f, 0xab, 0xce, 0xd7, 0xe1,
+ 0xec, 0xed, 0xc7, 0xcf, 0xea, 0xeb, 0xc0, 0xc1,
+ 0xc8, 0xc9, 0xca, 0xcd, 0xd2, 0xd5, 0xda, 0xdb,
+ 0xee, 0xf0, 0xf2, 0xf3, 0xff, 0xcb, 0xcc, 0xd3,
+ 0xd4, 0xd6, 0xdd, 0xde, 0xdf, 0xf1, 0xf4, 0xf5,
+ 0xf6, 0xf7, 0xf8, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe,
+ 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x0b,
+ 0x0c, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14,
+ 0x15, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d,
+ 0x1e, 0x1f, 0x7f, 0xdc, 0xf9, 0x0a, 0x0d, 0x16,
+};
+// clang-format on
+
+constexpr size_t kShortCodeTableSize = 124;
+struct ShortCodeInfo {
+ uint8_t symbol;
+ uint8_t length;
+} kShortCodeTable[kShortCodeTableSize] = {
+ {0x30, 5}, // Match: 0b0000000, Symbol: 0
+ {0x30, 5}, // Match: 0b0000001, Symbol: 0
+ {0x30, 5}, // Match: 0b0000010, Symbol: 0
+ {0x30, 5}, // Match: 0b0000011, Symbol: 0
+ {0x31, 5}, // Match: 0b0000100, Symbol: 1
+ {0x31, 5}, // Match: 0b0000101, Symbol: 1
+ {0x31, 5}, // Match: 0b0000110, Symbol: 1
+ {0x31, 5}, // Match: 0b0000111, Symbol: 1
+ {0x32, 5}, // Match: 0b0001000, Symbol: 2
+ {0x32, 5}, // Match: 0b0001001, Symbol: 2
+ {0x32, 5}, // Match: 0b0001010, Symbol: 2
+ {0x32, 5}, // Match: 0b0001011, Symbol: 2
+ {0x61, 5}, // Match: 0b0001100, Symbol: a
+ {0x61, 5}, // Match: 0b0001101, Symbol: a
+ {0x61, 5}, // Match: 0b0001110, Symbol: a
+ {0x61, 5}, // Match: 0b0001111, Symbol: a
+ {0x63, 5}, // Match: 0b0010000, Symbol: c
+ {0x63, 5}, // Match: 0b0010001, Symbol: c
+ {0x63, 5}, // Match: 0b0010010, Symbol: c
+ {0x63, 5}, // Match: 0b0010011, Symbol: c
+ {0x65, 5}, // Match: 0b0010100, Symbol: e
+ {0x65, 5}, // Match: 0b0010101, Symbol: e
+ {0x65, 5}, // Match: 0b0010110, Symbol: e
+ {0x65, 5}, // Match: 0b0010111, Symbol: e
+ {0x69, 5}, // Match: 0b0011000, Symbol: i
+ {0x69, 5}, // Match: 0b0011001, Symbol: i
+ {0x69, 5}, // Match: 0b0011010, Symbol: i
+ {0x69, 5}, // Match: 0b0011011, Symbol: i
+ {0x6f, 5}, // Match: 0b0011100, Symbol: o
+ {0x6f, 5}, // Match: 0b0011101, Symbol: o
+ {0x6f, 5}, // Match: 0b0011110, Symbol: o
+ {0x6f, 5}, // Match: 0b0011111, Symbol: o
+ {0x73, 5}, // Match: 0b0100000, Symbol: s
+ {0x73, 5}, // Match: 0b0100001, Symbol: s
+ {0x73, 5}, // Match: 0b0100010, Symbol: s
+ {0x73, 5}, // Match: 0b0100011, Symbol: s
+ {0x74, 5}, // Match: 0b0100100, Symbol: t
+ {0x74, 5}, // Match: 0b0100101, Symbol: t
+ {0x74, 5}, // Match: 0b0100110, Symbol: t
+ {0x74, 5}, // Match: 0b0100111, Symbol: t
+ {0x20, 6}, // Match: 0b0101000, Symbol: (space)
+ {0x20, 6}, // Match: 0b0101001, Symbol: (space)
+ {0x25, 6}, // Match: 0b0101010, Symbol: %
+ {0x25, 6}, // Match: 0b0101011, Symbol: %
+ {0x2d, 6}, // Match: 0b0101100, Symbol: -
+ {0x2d, 6}, // Match: 0b0101101, Symbol: -
+ {0x2e, 6}, // Match: 0b0101110, Symbol: .
+ {0x2e, 6}, // Match: 0b0101111, Symbol: .
+ {0x2f, 6}, // Match: 0b0110000, Symbol: /
+ {0x2f, 6}, // Match: 0b0110001, Symbol: /
+ {0x33, 6}, // Match: 0b0110010, Symbol: 3
+ {0x33, 6}, // Match: 0b0110011, Symbol: 3
+ {0x34, 6}, // Match: 0b0110100, Symbol: 4
+ {0x34, 6}, // Match: 0b0110101, Symbol: 4
+ {0x35, 6}, // Match: 0b0110110, Symbol: 5
+ {0x35, 6}, // Match: 0b0110111, Symbol: 5
+ {0x36, 6}, // Match: 0b0111000, Symbol: 6
+ {0x36, 6}, // Match: 0b0111001, Symbol: 6
+ {0x37, 6}, // Match: 0b0111010, Symbol: 7
+ {0x37, 6}, // Match: 0b0111011, Symbol: 7
+ {0x38, 6}, // Match: 0b0111100, Symbol: 8
+ {0x38, 6}, // Match: 0b0111101, Symbol: 8
+ {0x39, 6}, // Match: 0b0111110, Symbol: 9
+ {0x39, 6}, // Match: 0b0111111, Symbol: 9
+ {0x3d, 6}, // Match: 0b1000000, Symbol: =
+ {0x3d, 6}, // Match: 0b1000001, Symbol: =
+ {0x41, 6}, // Match: 0b1000010, Symbol: A
+ {0x41, 6}, // Match: 0b1000011, Symbol: A
+ {0x5f, 6}, // Match: 0b1000100, Symbol: _
+ {0x5f, 6}, // Match: 0b1000101, Symbol: _
+ {0x62, 6}, // Match: 0b1000110, Symbol: b
+ {0x62, 6}, // Match: 0b1000111, Symbol: b
+ {0x64, 6}, // Match: 0b1001000, Symbol: d
+ {0x64, 6}, // Match: 0b1001001, Symbol: d
+ {0x66, 6}, // Match: 0b1001010, Symbol: f
+ {0x66, 6}, // Match: 0b1001011, Symbol: f
+ {0x67, 6}, // Match: 0b1001100, Symbol: g
+ {0x67, 6}, // Match: 0b1001101, Symbol: g
+ {0x68, 6}, // Match: 0b1001110, Symbol: h
+ {0x68, 6}, // Match: 0b1001111, Symbol: h
+ {0x6c, 6}, // Match: 0b1010000, Symbol: l
+ {0x6c, 6}, // Match: 0b1010001, Symbol: l
+ {0x6d, 6}, // Match: 0b1010010, Symbol: m
+ {0x6d, 6}, // Match: 0b1010011, Symbol: m
+ {0x6e, 6}, // Match: 0b1010100, Symbol: n
+ {0x6e, 6}, // Match: 0b1010101, Symbol: n
+ {0x70, 6}, // Match: 0b1010110, Symbol: p
+ {0x70, 6}, // Match: 0b1010111, Symbol: p
+ {0x72, 6}, // Match: 0b1011000, Symbol: r
+ {0x72, 6}, // Match: 0b1011001, Symbol: r
+ {0x75, 6}, // Match: 0b1011010, Symbol: u
+ {0x75, 6}, // Match: 0b1011011, Symbol: u
+ {0x3a, 7}, // Match: 0b1011100, Symbol: :
+ {0x42, 7}, // Match: 0b1011101, Symbol: B
+ {0x43, 7}, // Match: 0b1011110, Symbol: C
+ {0x44, 7}, // Match: 0b1011111, Symbol: D
+ {0x45, 7}, // Match: 0b1100000, Symbol: E
+ {0x46, 7}, // Match: 0b1100001, Symbol: F
+ {0x47, 7}, // Match: 0b1100010, Symbol: G
+ {0x48, 7}, // Match: 0b1100011, Symbol: H
+ {0x49, 7}, // Match: 0b1100100, Symbol: I
+ {0x4a, 7}, // Match: 0b1100101, Symbol: J
+ {0x4b, 7}, // Match: 0b1100110, Symbol: K
+ {0x4c, 7}, // Match: 0b1100111, Symbol: L
+ {0x4d, 7}, // Match: 0b1101000, Symbol: M
+ {0x4e, 7}, // Match: 0b1101001, Symbol: N
+ {0x4f, 7}, // Match: 0b1101010, Symbol: O
+ {0x50, 7}, // Match: 0b1101011, Symbol: P
+ {0x51, 7}, // Match: 0b1101100, Symbol: Q
+ {0x52, 7}, // Match: 0b1101101, Symbol: R
+ {0x53, 7}, // Match: 0b1101110, Symbol: S
+ {0x54, 7}, // Match: 0b1101111, Symbol: T
+ {0x55, 7}, // Match: 0b1110000, Symbol: U
+ {0x56, 7}, // Match: 0b1110001, Symbol: V
+ {0x57, 7}, // Match: 0b1110010, Symbol: W
+ {0x59, 7}, // Match: 0b1110011, Symbol: Y
+ {0x6a, 7}, // Match: 0b1110100, Symbol: j
+ {0x6b, 7}, // Match: 0b1110101, Symbol: k
+ {0x71, 7}, // Match: 0b1110110, Symbol: q
+ {0x76, 7}, // Match: 0b1110111, Symbol: v
+ {0x77, 7}, // Match: 0b1111000, Symbol: w
+ {0x78, 7}, // Match: 0b1111001, Symbol: x
+ {0x79, 7}, // Match: 0b1111010, Symbol: y
+ {0x7a, 7}, // Match: 0b1111011, Symbol: z
+};
+
+} // namespace
+
+HuffmanBitBuffer::HuffmanBitBuffer() {
+ Reset();
+}
+
+void HuffmanBitBuffer::Reset() {
+ accumulator_ = 0;
+ count_ = 0;
+}
+
+size_t HuffmanBitBuffer::AppendBytes(Http2StringPiece input) {
+ HuffmanAccumulatorBitCount free_cnt = free_count();
+ size_t bytes_available = input.size();
+ if (free_cnt < 8 || bytes_available == 0) {
+ return 0;
+ }
+
+ // Top up |accumulator_| until there isn't room for a whole byte.
+ size_t bytes_used = 0;
+ auto* ptr = reinterpret_cast<const uint8_t*>(input.data());
+ do {
+ auto b = static_cast<HuffmanAccumulator>(*ptr++);
+ free_cnt -= 8;
+ accumulator_ |= (b << free_cnt);
+ ++bytes_used;
+ } while (free_cnt >= 8 && bytes_used < bytes_available);
+ count_ += (bytes_used * 8);
+ return bytes_used;
+}
+
+HuffmanAccumulatorBitCount HuffmanBitBuffer::free_count() const {
+ return kHuffmanAccumulatorBitCount - count_;
+}
+
+void HuffmanBitBuffer::ConsumeBits(HuffmanAccumulatorBitCount code_length) {
+ DCHECK_LE(code_length, count_);
+ accumulator_ <<= code_length;
+ count_ -= code_length;
+}
+
+bool HuffmanBitBuffer::InputProperlyTerminated() const {
+ auto cnt = count();
+ if (cnt < 8) {
+ if (cnt == 0) {
+ return true;
+ }
+ HuffmanAccumulator expected = ~(~HuffmanAccumulator() >> cnt);
+ // We expect all the bits below the high order |cnt| bits of accumulator_
+ // to be cleared as we perform left shift operations while decoding.
+ DCHECK_EQ(accumulator_ & ~expected, 0u)
+ << "\n expected: " << HuffmanAccumulatorBitSet(expected) << "\n "
+ << *this;
+ return accumulator_ == expected;
+ }
+ return false;
+}
+
+Http2String HuffmanBitBuffer::DebugString() const {
+ std::stringstream ss;
+ ss << "{accumulator: " << HuffmanAccumulatorBitSet(accumulator_)
+ << "; count: " << count_ << "}";
+ return ss.str();
+}
+
+HpackHuffmanDecoder::HpackHuffmanDecoder() = default;
+
+HpackHuffmanDecoder::~HpackHuffmanDecoder() = default;
+
+bool HpackHuffmanDecoder::Decode(Http2StringPiece input, Http2String* output) {
+ DVLOG(1) << "HpackHuffmanDecoder::Decode";
+
+ // Fill bit_buffer_ from input.
+ input.remove_prefix(bit_buffer_.AppendBytes(input));
+
+ while (true) {
+ DVLOG(3) << "Enter Decode Loop, bit_buffer_: " << bit_buffer_;
+ if (bit_buffer_.count() >= 7) {
+ // Get high 7 bits of the bit buffer, see if that contains a complete
+ // code of 5, 6 or 7 bits.
+ uint8_t short_code =
+ bit_buffer_.value() >> (kHuffmanAccumulatorBitCount - 7);
+ DCHECK_LT(short_code, 128);
+ if (short_code < kShortCodeTableSize) {
+ ShortCodeInfo info = kShortCodeTable[short_code];
+ bit_buffer_.ConsumeBits(info.length);
+ output->push_back(static_cast<char>(info.symbol));
+ continue;
+ }
+ // The code is more than 7 bits long. Use PrefixToInfo, etc. to decode
+ // longer codes.
+ } else {
+ // We may have (mostly) drained bit_buffer_. If we can top it up, try
+ // using the table decoder above.
+ size_t byte_count = bit_buffer_.AppendBytes(input);
+ if (byte_count > 0) {
+ input.remove_prefix(byte_count);
+ continue;
+ }
+ }
+
+ HuffmanCode code_prefix = bit_buffer_.value() >> kExtraAccumulatorBitCount;
+ DVLOG(3) << "code_prefix: " << HuffmanCodeBitSet(code_prefix);
+
+ PrefixInfo prefix_info = PrefixToInfo(code_prefix);
+ DVLOG(3) << "prefix_info: " << prefix_info;
+ DCHECK_LE(kMinCodeBitCount, prefix_info.code_length);
+ DCHECK_LE(prefix_info.code_length, kMaxCodeBitCount);
+
+ if (prefix_info.code_length <= bit_buffer_.count()) {
+ // We have enough bits for one code.
+ uint32_t canonical = prefix_info.DecodeToCanonical(code_prefix);
+ if (canonical < 256) {
+ // Valid code.
+ char c = kCanonicalToSymbol[canonical];
+ output->push_back(c);
+ bit_buffer_.ConsumeBits(prefix_info.code_length);
+ continue;
+ }
+ // Encoder is not supposed to explicity encode the EOS symbol.
+ DLOG(ERROR) << "EOS explicitly encoded!\n " << bit_buffer_ << "\n "
+ << prefix_info;
+ return false;
+ }
+ // bit_buffer_ doesn't have enough bits in it to decode the next symbol.
+ // Append to it as many bytes as are available AND fit.
+ size_t byte_count = bit_buffer_.AppendBytes(input);
+ if (byte_count == 0) {
+ DCHECK_EQ(input.size(), 0u);
+ return true;
+ }
+ input.remove_prefix(byte_count);
+ }
+}
+
+Http2String HpackHuffmanDecoder::DebugString() const {
+ return bit_buffer_.DebugString();
+}
+
+} // namespace http2
diff --git a/chromium/net/third_party/quiche/src/http2/hpack/huffman/hpack_huffman_decoder.h b/chromium/net/third_party/quiche/src/http2/hpack/huffman/hpack_huffman_decoder.h
new file mode 100644
index 00000000000..8e511d62ae9
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/hpack/huffman/hpack_huffman_decoder.h
@@ -0,0 +1,134 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_HTTP2_HPACK_HUFFMAN_HPACK_HUFFMAN_DECODER_H_
+#define QUICHE_HTTP2_HPACK_HUFFMAN_HPACK_HUFFMAN_DECODER_H_
+
+// HpackHuffmanDecoder is an incremental decoder of strings that have been
+// encoded using the Huffman table defined in the HPACK spec.
+// By incremental, we mean that the HpackHuffmanDecoder::Decode method does
+// not require the entire string to be provided, and can instead decode the
+// string as fragments of it become available (e.g. as HPACK block fragments
+// are received for decoding by HpackEntryDecoder).
+
+#include <stddef.h>
+
+#include <cstdint>
+#include <iosfwd>
+
+#include "net/third_party/quiche/src/http2/platform/api/http2_export.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string_piece.h"
+
+namespace http2 {
+
+// HuffmanAccumulator is used to store bits during decoding, e.g. next N bits
+// that have not yet been decoded, but have been extracted from the encoded
+// string). An advantage of using a uint64 for the accumulator
+// is that it has room for the bits of the longest code plus the bits of a full
+// byte; that means that when adding more bits to the accumulator, it can always
+// be done in whole bytes. For example, if we currently have 26 bits in the
+// accumulator, and need more to decode the current symbol, we can add a whole
+// byte to the accumulator, and not have to do juggling with adding 6 bits (to
+// reach 30), and then keep track of the last two bits we've not been able to
+// add to the accumulator.
+typedef uint64_t HuffmanAccumulator;
+typedef size_t HuffmanAccumulatorBitCount;
+
+// HuffmanBitBuffer stores the leading edge of bits to be decoded. The high
+// order bit of accumulator_ is the next bit to be decoded.
+class HTTP2_EXPORT_PRIVATE HuffmanBitBuffer {
+ public:
+ HuffmanBitBuffer();
+
+ // Prepare for decoding a new Huffman encoded string.
+ void Reset();
+
+ // Add as many whole bytes to the accumulator (accumulator_) as possible,
+ // returning the number of bytes added.
+ size_t AppendBytes(Http2StringPiece input);
+
+ // Get the bits of the accumulator.
+ HuffmanAccumulator value() const { return accumulator_; }
+
+ // Number of bits of the encoded string that are in the accumulator
+ // (accumulator_).
+ HuffmanAccumulatorBitCount count() const { return count_; }
+
+ // Are there no bits in the accumulator?
+ bool IsEmpty() const { return count_ == 0; }
+
+ // Number of additional bits that can be added to the accumulator.
+ HuffmanAccumulatorBitCount free_count() const;
+
+ // Consume the leading |code_length| bits of the accumulator.
+ void ConsumeBits(HuffmanAccumulatorBitCount code_length);
+
+ // Are the contents valid for the end of a Huffman encoded string? The RFC
+ // states that EOS (end-of-string) symbol must not be explicitly encoded in
+ // the bit stream, but any unused bits in the final byte must be set to the
+ // prefix of the EOS symbol, which is all 1 bits. So there can be at most 7
+ // such bits.
+ // Returns true if the bit buffer is empty, or contains at most 7 bits, all
+ // of them 1. Otherwise returns false.
+ bool InputProperlyTerminated() const;
+
+ Http2String DebugString() const;
+
+ private:
+ HuffmanAccumulator accumulator_;
+ HuffmanAccumulatorBitCount count_;
+};
+
+inline std::ostream& operator<<(std::ostream& out, const HuffmanBitBuffer& v) {
+ return out << v.DebugString();
+}
+
+class HTTP2_EXPORT_PRIVATE HpackHuffmanDecoder {
+ public:
+ HpackHuffmanDecoder();
+ ~HpackHuffmanDecoder();
+
+ // Prepare for decoding a new Huffman encoded string.
+ void Reset() { bit_buffer_.Reset(); }
+
+ // Decode the portion of a HPACK Huffman encoded string that is in |input|,
+ // appending the decoded symbols into |*output|, stopping when more bits are
+ // needed to determine the next symbol, which/ means that the input has been
+ // drained, and also that the bit_buffer_ is empty or that the bits that are
+ // in it are not a whole symbol.
+ // If |input| is the start of a string, the caller must first call Reset.
+ // If |input| includes the end of the encoded string, the caller must call
+ // InputProperlyTerminated after Decode has returned true in order to
+ // determine if the encoded string was properly terminated.
+ // Returns false if something went wrong (e.g. the encoding contains the code
+ // EOS symbol). Otherwise returns true, in which case input has been fully
+ // decoded or buffered; in particular, if the low-order bit of the final byte
+ // of the input is not the last bit of an encoded symbol, then bit_buffer_
+ // will contain the leading bits of the code for that symbol, but not the
+ // final bits of that code.
+ // Note that output should be empty, but that it is not cleared by Decode().
+ bool Decode(Http2StringPiece input, Http2String* output);
+
+ // Is what remains in the bit_buffer_ valid at the end of an encoded string?
+ // Call after passing the the final portion of a Huffman string to Decode,
+ // and getting true as the result.
+ bool InputProperlyTerminated() const {
+ return bit_buffer_.InputProperlyTerminated();
+ }
+
+ Http2String DebugString() const;
+
+ private:
+ HuffmanBitBuffer bit_buffer_;
+};
+
+inline std::ostream& operator<<(std::ostream& out,
+ const HpackHuffmanDecoder& v) {
+ return out << v.DebugString();
+}
+
+} // namespace http2
+
+#endif // QUICHE_HTTP2_HPACK_HUFFMAN_HPACK_HUFFMAN_DECODER_H_
diff --git a/chromium/net/third_party/quiche/src/http2/hpack/huffman/hpack_huffman_decoder_test.cc b/chromium/net/third_party/quiche/src/http2/hpack/huffman/hpack_huffman_decoder_test.cc
new file mode 100644
index 00000000000..6c07ca5b55b
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/hpack/huffman/hpack_huffman_decoder_test.cc
@@ -0,0 +1,245 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/http2/hpack/huffman/hpack_huffman_decoder.h"
+
+// Tests of HpackHuffmanDecoder and HuffmanBitBuffer.
+
+#include <iostream>
+
+#include "base/macros.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_status.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_arraysize.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string_utils.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_test_helpers.h"
+#include "net/third_party/quiche/src/http2/tools/random_decoder_test.h"
+
+using ::testing::AssertionResult;
+
+namespace http2 {
+namespace test {
+namespace {
+
+TEST(HuffmanBitBufferTest, Reset) {
+ HuffmanBitBuffer bb;
+ EXPECT_TRUE(bb.IsEmpty());
+ EXPECT_TRUE(bb.InputProperlyTerminated());
+ EXPECT_EQ(bb.count(), 0u);
+ EXPECT_EQ(bb.free_count(), 64u);
+ EXPECT_EQ(bb.value(), 0u);
+}
+
+TEST(HuffmanBitBufferTest, AppendBytesAligned) {
+ Http2String s;
+ s.push_back('\x11');
+ s.push_back('\x22');
+ s.push_back('\x33');
+ Http2StringPiece sp(s);
+
+ HuffmanBitBuffer bb;
+ sp.remove_prefix(bb.AppendBytes(sp));
+ EXPECT_TRUE(sp.empty());
+ EXPECT_FALSE(bb.IsEmpty()) << bb;
+ EXPECT_FALSE(bb.InputProperlyTerminated());
+ EXPECT_EQ(bb.count(), 24u) << bb;
+ EXPECT_EQ(bb.free_count(), 40u) << bb;
+ EXPECT_EQ(bb.value(), HuffmanAccumulator(0x112233) << 40) << bb;
+
+ s.clear();
+ s.push_back('\x44');
+ sp = s;
+
+ sp.remove_prefix(bb.AppendBytes(sp));
+ EXPECT_TRUE(sp.empty());
+ EXPECT_EQ(bb.count(), 32u) << bb;
+ EXPECT_EQ(bb.free_count(), 32u) << bb;
+ EXPECT_EQ(bb.value(), HuffmanAccumulator(0x11223344) << 32) << bb;
+
+ s.clear();
+ s.push_back('\x55');
+ s.push_back('\x66');
+ s.push_back('\x77');
+ s.push_back('\x88');
+ s.push_back('\x99');
+ sp = s;
+
+ sp.remove_prefix(bb.AppendBytes(sp));
+ EXPECT_EQ(sp.size(), 1u);
+ EXPECT_EQ('\x99', sp[0]);
+ EXPECT_EQ(bb.count(), 64u) << bb;
+ EXPECT_EQ(bb.free_count(), 0u) << bb;
+ EXPECT_EQ(bb.value(), HuffmanAccumulator(0x1122334455667788LL)) << bb;
+
+ sp.remove_prefix(bb.AppendBytes(sp));
+ EXPECT_EQ(sp.size(), 1u);
+ EXPECT_EQ('\x99', sp[0]);
+ EXPECT_EQ(bb.count(), 64u) << bb;
+ EXPECT_EQ(bb.free_count(), 0u) << bb;
+ EXPECT_EQ(bb.value(), HuffmanAccumulator(0x1122334455667788LL)) << bb;
+}
+
+TEST(HuffmanBitBufferTest, ConsumeBits) {
+ Http2String s;
+ s.push_back('\x11');
+ s.push_back('\x22');
+ s.push_back('\x33');
+ Http2StringPiece sp(s);
+
+ HuffmanBitBuffer bb;
+ sp.remove_prefix(bb.AppendBytes(sp));
+ EXPECT_TRUE(sp.empty());
+
+ bb.ConsumeBits(1);
+ EXPECT_EQ(bb.count(), 23u) << bb;
+ EXPECT_EQ(bb.free_count(), 41u) << bb;
+ EXPECT_EQ(bb.value(), HuffmanAccumulator(0x112233) << 41) << bb;
+
+ bb.ConsumeBits(20);
+ EXPECT_EQ(bb.count(), 3u) << bb;
+ EXPECT_EQ(bb.free_count(), 61u) << bb;
+ EXPECT_EQ(bb.value(), HuffmanAccumulator(0x3) << 61) << bb;
+}
+
+TEST(HuffmanBitBufferTest, AppendBytesUnaligned) {
+ Http2String s;
+ s.push_back('\x11');
+ s.push_back('\x22');
+ s.push_back('\x33');
+ s.push_back('\x44');
+ s.push_back('\x55');
+ s.push_back('\x66');
+ s.push_back('\x77');
+ s.push_back('\x88');
+ s.push_back('\x99');
+ s.push_back('\xaa');
+ s.push_back('\xbb');
+ s.push_back('\xcc');
+ s.push_back('\xdd');
+ Http2StringPiece sp(s);
+
+ HuffmanBitBuffer bb;
+ sp.remove_prefix(bb.AppendBytes(sp));
+ EXPECT_EQ(sp.size(), 5u);
+ EXPECT_FALSE(bb.InputProperlyTerminated());
+
+ bb.ConsumeBits(15);
+ EXPECT_EQ(bb.count(), 49u) << bb;
+ EXPECT_EQ(bb.free_count(), 15u) << bb;
+
+ HuffmanAccumulator expected(0x1122334455667788);
+ expected <<= 15;
+ EXPECT_EQ(bb.value(), expected);
+
+ sp.remove_prefix(bb.AppendBytes(sp));
+ EXPECT_EQ(sp.size(), 4u);
+ EXPECT_EQ(bb.count(), 57u) << bb;
+ EXPECT_EQ(bb.free_count(), 7u) << bb;
+
+ expected |= (HuffmanAccumulator(0x99) << 7);
+ EXPECT_EQ(bb.value(), expected)
+ << bb << std::hex << "\n actual: " << bb.value()
+ << "\n expected: " << expected;
+}
+
+class HpackHuffmanDecoderTest : public RandomDecoderTest {
+ protected:
+ HpackHuffmanDecoderTest() {
+ // The decoder may return true, and its accumulator may be empty, at
+ // many boundaries while decoding, and yet the whole string hasn't
+ // been decoded.
+ stop_decode_on_done_ = false;
+ }
+
+ DecodeStatus StartDecoding(DecodeBuffer* b) override {
+ input_bytes_seen_ = 0;
+ output_buffer_.clear();
+ decoder_.Reset();
+ return ResumeDecoding(b);
+ }
+
+ DecodeStatus ResumeDecoding(DecodeBuffer* b) override {
+ input_bytes_seen_ += b->Remaining();
+ Http2StringPiece sp(b->cursor(), b->Remaining());
+ if (decoder_.Decode(sp, &output_buffer_)) {
+ b->AdvanceCursor(b->Remaining());
+ // Successfully decoded (or buffered) the bytes in Http2StringPiece.
+ EXPECT_LE(input_bytes_seen_, input_bytes_expected_);
+ // Have we reached the end of the encoded string?
+ if (input_bytes_expected_ == input_bytes_seen_) {
+ if (decoder_.InputProperlyTerminated()) {
+ return DecodeStatus::kDecodeDone;
+ } else {
+ return DecodeStatus::kDecodeError;
+ }
+ }
+ return DecodeStatus::kDecodeInProgress;
+ }
+ return DecodeStatus::kDecodeError;
+ }
+
+ HpackHuffmanDecoder decoder_;
+ Http2String output_buffer_;
+ size_t input_bytes_seen_;
+ size_t input_bytes_expected_;
+};
+
+TEST_F(HpackHuffmanDecoderTest, SpecRequestExamples) {
+ HpackHuffmanDecoder decoder;
+ Http2String test_table[] = {
+ Http2HexDecode("f1e3c2e5f23a6ba0ab90f4ff"),
+ "www.example.com",
+ Http2HexDecode("a8eb10649cbf"),
+ "no-cache",
+ Http2HexDecode("25a849e95ba97d7f"),
+ "custom-key",
+ Http2HexDecode("25a849e95bb8e8b4bf"),
+ "custom-value",
+ };
+ for (size_t i = 0; i != HTTP2_ARRAYSIZE(test_table); i += 2) {
+ const Http2String& huffman_encoded(test_table[i]);
+ const Http2String& plain_string(test_table[i + 1]);
+ Http2String buffer;
+ decoder.Reset();
+ EXPECT_TRUE(decoder.Decode(huffman_encoded, &buffer)) << decoder;
+ EXPECT_TRUE(decoder.InputProperlyTerminated()) << decoder;
+ EXPECT_EQ(buffer, plain_string);
+ }
+}
+
+TEST_F(HpackHuffmanDecoderTest, SpecResponseExamples) {
+ HpackHuffmanDecoder decoder;
+ // clang-format off
+ Http2String test_table[] = {
+ Http2HexDecode("6402"),
+ "302",
+ Http2HexDecode("aec3771a4b"),
+ "private",
+ Http2HexDecode("d07abe941054d444a8200595040b8166"
+ "e082a62d1bff"),
+ "Mon, 21 Oct 2013 20:13:21 GMT",
+ Http2HexDecode("9d29ad171863c78f0b97c8e9ae82ae43"
+ "d3"),
+ "https://www.example.com",
+ Http2HexDecode("94e7821dd7f2e6c7b335dfdfcd5b3960"
+ "d5af27087f3672c1ab270fb5291f9587"
+ "316065c003ed4ee5b1063d5007"),
+ "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1",
+ };
+ // clang-format on
+ for (size_t i = 0; i != HTTP2_ARRAYSIZE(test_table); i += 2) {
+ const Http2String& huffman_encoded(test_table[i]);
+ const Http2String& plain_string(test_table[i + 1]);
+ Http2String buffer;
+ decoder.Reset();
+ EXPECT_TRUE(decoder.Decode(huffman_encoded, &buffer)) << decoder;
+ EXPECT_TRUE(decoder.InputProperlyTerminated()) << decoder;
+ EXPECT_EQ(buffer, plain_string);
+ }
+}
+
+} // namespace
+} // namespace test
+} // namespace http2
diff --git a/chromium/net/third_party/quiche/src/http2/hpack/huffman/hpack_huffman_encoder.cc b/chromium/net/third_party/quiche/src/http2/hpack/huffman/hpack_huffman_encoder.cc
new file mode 100644
index 00000000000..9f561eedf46
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/hpack/huffman/hpack_huffman_encoder.cc
@@ -0,0 +1,110 @@
+// Copyright (c) 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/http2/hpack/huffman/hpack_huffman_encoder.h"
+
+#include "base/logging.h"
+#include "net/third_party/quiche/src/http2/hpack/huffman/huffman_spec_tables.h"
+
+// TODO(jamessynge): Remove use of binary literals, that is a C++ 14 feature.
+
+namespace http2 {
+
+size_t ExactHuffmanSize(Http2StringPiece plain) {
+ size_t bits = 0;
+ for (const uint8_t c : plain) {
+ bits += HuffmanSpecTables::kCodeLengths[c];
+ }
+ return (bits + 7) / 8;
+}
+
+size_t BoundedHuffmanSize(Http2StringPiece plain) {
+ // TODO(jamessynge): Determine whether we should set the min size for Huffman
+ // encoding much higher (i.e. if less than N, then the savings isn't worth
+ // the cost of encoding and decoding). Of course, we need to decide on a
+ // value function, which might be throughput on a full load test, or a
+ // microbenchmark of the time to encode and then decode a HEADERS frame,
+ // possibly with the cost of crypto included (i.e. crypto is going to have
+ // a fairly constant per-byte cost, so reducing the number of bytes in-transit
+ // reduces the number that must be encrypted and later decrypted).
+ if (plain.size() < 3) {
+ // Huffman encoded string can't be smaller than the plain size for very
+ // short strings.
+ return plain.size();
+ }
+ // TODO(jamessynge): Measure whether this can be done more efficiently with
+ // nested loops (e.g. make exact measurement of 8 bytes, then check if min
+ // remaining is too long).
+ // Compute the number of bits in an encoding that is shorter than the plain
+ // string (i.e. the number of bits in a string 1 byte shorter than plain),
+ // and use this as the limit of the size of the encoding.
+ const size_t limit_bits = (plain.size() - 1) * 8;
+ // The shortest code length in the Huffman table of the HPACK spec has 5 bits
+ // (e.g. for 0, 1, a and e).
+ const size_t min_code_length = 5;
+ // We can therefore say that all plain text bytes whose code length we've not
+ // yet looked up will take at least 5 bits.
+ size_t min_bits_remaining = plain.size() * min_code_length;
+ size_t bits = 0;
+ for (const uint8_t c : plain) {
+ bits += HuffmanSpecTables::kCodeLengths[c];
+ min_bits_remaining -= min_code_length;
+ // If our minimum estimate of the total number of bits won't yield an
+ // encoding shorter the plain text, let's bail.
+ const size_t minimum_bits_total = bits + min_bits_remaining;
+ if (minimum_bits_total > limit_bits) {
+ bits += min_bits_remaining;
+ break;
+ }
+ }
+ return (bits + 7) / 8;
+}
+
+void HuffmanEncode(Http2StringPiece plain, Http2String* huffman) {
+ DCHECK(huffman != nullptr);
+ huffman->clear(); // Note that this doesn't release memory.
+ uint64_t bit_buffer = 0; // High-bit is next bit to output. Not clear if that
+ // is more performant than having the low-bit be the
+ // last to be output.
+ size_t bits_unused = 64; // Number of bits available for the next code.
+ for (uint8_t c : plain) {
+ size_t code_length = HuffmanSpecTables::kCodeLengths[c];
+ if (bits_unused < code_length) {
+ // There isn't enough room in bit_buffer for the code of c.
+ // Flush until bits_unused > 56 (i.e. 64 - 8).
+ do {
+ char h = static_cast<char>(bit_buffer >> 56);
+ bit_buffer <<= 8;
+ bits_unused += 8;
+ // Perhaps would be more efficient if we populated an array of chars,
+ // so we don't have to call push_back each time. Reconsider if used
+ // for production.
+ huffman->push_back(h);
+ } while (bits_unused <= 56);
+ }
+ uint64_t code = HuffmanSpecTables::kRightCodes[c];
+ size_t shift_by = bits_unused - code_length;
+ bit_buffer |= (code << shift_by);
+ bits_unused -= code_length;
+ }
+ // bit_buffer contains (64-bits_unused) bits that still need to be flushed.
+ // Output whole bytes until we don't have any whole bytes left.
+ size_t bits_used = 64 - bits_unused;
+ while (bits_used >= 8) {
+ char h = static_cast<char>(bit_buffer >> 56);
+ bit_buffer <<= 8;
+ bits_used -= 8;
+ huffman->push_back(h);
+ }
+ if (bits_used > 0) {
+ // We have less than a byte left to output. The spec calls for padding out
+ // the final byte with the leading bits of the EOS symbol (30 1-bits).
+ constexpr uint64_t leading_eos_bits = 0b11111111;
+ bit_buffer |= (leading_eos_bits << (56 - bits_used));
+ char h = static_cast<char>(bit_buffer >> 56);
+ huffman->push_back(h);
+ }
+}
+
+} // namespace http2
diff --git a/chromium/net/third_party/quiche/src/http2/hpack/huffman/hpack_huffman_encoder.h b/chromium/net/third_party/quiche/src/http2/hpack/huffman/hpack_huffman_encoder.h
new file mode 100644
index 00000000000..bf6b1b23b34
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/hpack/huffman/hpack_huffman_encoder.h
@@ -0,0 +1,40 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_HTTP2_HPACK_HUFFMAN_HPACK_HUFFMAN_ENCODER_H_
+#define QUICHE_HTTP2_HPACK_HUFFMAN_HPACK_HUFFMAN_ENCODER_H_
+
+// Functions supporting the encoding of strings using the HPACK-defined Huffman
+// table.
+
+#include <cstddef> // For size_t
+
+#include "net/third_party/quiche/src/http2/platform/api/http2_export.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string_piece.h"
+
+namespace http2 {
+
+// Returns the size of the Huffman encoding of |plain|, which may be greater
+// than plain.size(). Mostly present for testing.
+HTTP2_EXPORT_PRIVATE size_t ExactHuffmanSize(Http2StringPiece plain);
+
+// Returns the size of the Huffman encoding of |plain|, unless it is greater
+// than or equal to plain.size(), in which case a value greater than or equal to
+// plain.size() is returned. The advantage of this over ExactHuffmanSize is that
+// it doesn't read as much of the input string in the event that the string is
+// not compressible by HuffmanEncode (i.e. when the encoding is longer than the
+// original string, it stops reading the input string as soon as it knows that).
+HTTP2_EXPORT_PRIVATE size_t BoundedHuffmanSize(Http2StringPiece plain);
+
+// Encode the plain text string |plain| with the Huffman encoding defined in
+// the HPACK RFC, 7541. |*huffman| does not have to be empty, it is cleared at
+// the beginning of this function. This allows reusing the same string object
+// across multiple invocations.
+HTTP2_EXPORT_PRIVATE void HuffmanEncode(Http2StringPiece plain,
+ Http2String* huffman);
+
+} // namespace http2
+
+#endif // QUICHE_HTTP2_HPACK_HUFFMAN_HPACK_HUFFMAN_ENCODER_H_
diff --git a/chromium/net/third_party/quiche/src/http2/hpack/huffman/hpack_huffman_encoder_test.cc b/chromium/net/third_party/quiche/src/http2/hpack/huffman/hpack_huffman_encoder_test.cc
new file mode 100644
index 00000000000..ccb6983bc66
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/hpack/huffman/hpack_huffman_encoder_test.cc
@@ -0,0 +1,97 @@
+// Copyright (c) 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/http2/hpack/huffman/hpack_huffman_encoder.h"
+
+#include "base/macros.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_arraysize.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string_utils.h"
+
+namespace http2 {
+namespace {
+
+TEST(HuffmanEncoderTest, SpecRequestExamples) {
+ Http2String test_table[] = {
+ Http2HexDecode("f1e3c2e5f23a6ba0ab90f4ff"),
+ "www.example.com",
+ Http2HexDecode("a8eb10649cbf"),
+ "no-cache",
+ Http2HexDecode("25a849e95ba97d7f"),
+ "custom-key",
+ Http2HexDecode("25a849e95bb8e8b4bf"),
+ "custom-value",
+ };
+ for (size_t i = 0; i != HTTP2_ARRAYSIZE(test_table); i += 2) {
+ const Http2String& huffman_encoded(test_table[i]);
+ const Http2String& plain_string(test_table[i + 1]);
+ EXPECT_EQ(ExactHuffmanSize(plain_string), huffman_encoded.size());
+ EXPECT_EQ(BoundedHuffmanSize(plain_string), huffman_encoded.size());
+ Http2String buffer;
+ buffer.reserve();
+ HuffmanEncode(plain_string, &buffer);
+ EXPECT_EQ(buffer, huffman_encoded) << "Error encoding " << plain_string;
+ }
+}
+
+TEST(HuffmanEncoderTest, SpecResponseExamples) {
+ // clang-format off
+ Http2String test_table[] = {
+ Http2HexDecode("6402"),
+ "302",
+ Http2HexDecode("aec3771a4b"),
+ "private",
+ Http2HexDecode("d07abe941054d444a8200595040b8166"
+ "e082a62d1bff"),
+ "Mon, 21 Oct 2013 20:13:21 GMT",
+ Http2HexDecode("9d29ad171863c78f0b97c8e9ae82ae43"
+ "d3"),
+ "https://www.example.com",
+ Http2HexDecode("94e7821dd7f2e6c7b335dfdfcd5b3960"
+ "d5af27087f3672c1ab270fb5291f9587"
+ "316065c003ed4ee5b1063d5007"),
+ "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1",
+ };
+ // clang-format on
+ for (size_t i = 0; i != HTTP2_ARRAYSIZE(test_table); i += 2) {
+ const Http2String& huffman_encoded(test_table[i]);
+ const Http2String& plain_string(test_table[i + 1]);
+ EXPECT_EQ(ExactHuffmanSize(plain_string), huffman_encoded.size());
+ EXPECT_EQ(BoundedHuffmanSize(plain_string), huffman_encoded.size());
+ Http2String buffer;
+ buffer.reserve(huffman_encoded.size());
+ const size_t capacity = buffer.capacity();
+ HuffmanEncode(plain_string, &buffer);
+ EXPECT_EQ(buffer, huffman_encoded) << "Error encoding " << plain_string;
+ EXPECT_EQ(capacity, buffer.capacity());
+ }
+}
+
+TEST(HuffmanEncoderTest, EncodedSizeAgreesWithEncodeString) {
+ Http2String test_table[] = {
+ "",
+ "Mon, 21 Oct 2013 20:13:21 GMT",
+ "https://www.example.com",
+ "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1",
+ Http2String(1, '\0'),
+ Http2String("foo\0bar", 7),
+ Http2String(256, '\0'),
+ };
+ // Modify last |test_table| entry to cover all codes.
+ for (size_t i = 0; i != 256; ++i) {
+ test_table[HTTP2_ARRAYSIZE(test_table) - 1][i] = static_cast<char>(i);
+ }
+
+ for (size_t i = 0; i != HTTP2_ARRAYSIZE(test_table); ++i) {
+ const Http2String& plain_string = test_table[i];
+ Http2String huffman_encoded;
+ HuffmanEncode(plain_string, &huffman_encoded);
+ EXPECT_EQ(huffman_encoded.size(), ExactHuffmanSize(plain_string));
+ EXPECT_LE(BoundedHuffmanSize(plain_string), plain_string.size());
+ EXPECT_LE(BoundedHuffmanSize(plain_string), ExactHuffmanSize(plain_string));
+ }
+}
+
+} // namespace
+} // namespace http2
diff --git a/chromium/net/third_party/quiche/src/http2/hpack/huffman/hpack_huffman_transcoder_test.cc b/chromium/net/third_party/quiche/src/http2/hpack/huffman/hpack_huffman_transcoder_test.cc
new file mode 100644
index 00000000000..cfd73b515ad
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/hpack/huffman/hpack_huffman_transcoder_test.cc
@@ -0,0 +1,184 @@
+// Copyright (c) 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// A test of roundtrips through the encoder and decoder.
+
+#include <stddef.h>
+
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_status.h"
+#include "net/third_party/quiche/src/http2/hpack/huffman/hpack_huffman_decoder.h"
+#include "net/third_party/quiche/src/http2/hpack/huffman/hpack_huffman_encoder.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string_piece.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string_utils.h"
+#include "net/third_party/quiche/src/http2/tools/random_decoder_test.h"
+
+using ::testing::AssertionResult;
+using ::testing::AssertionSuccess;
+using ::testing::tuple;
+
+namespace http2 {
+namespace test {
+namespace {
+
+Http2String GenAsciiNonControlSet() {
+ Http2String s;
+ const char space = ' '; // First character after the control characters: 0x20
+ const char del = 127; // First character after the non-control characters.
+ for (char c = space; c < del; ++c) {
+ s.push_back(c);
+ }
+ return s;
+}
+
+class HpackHuffmanTranscoderTest : public RandomDecoderTest {
+ protected:
+ HpackHuffmanTranscoderTest()
+ : ascii_non_control_set_(GenAsciiNonControlSet()) {
+ // The decoder may return true, and its accumulator may be empty, at
+ // many boundaries while decoding, and yet the whole string hasn't
+ // been decoded.
+ stop_decode_on_done_ = false;
+ }
+
+ DecodeStatus StartDecoding(DecodeBuffer* b) override {
+ input_bytes_seen_ = 0;
+ output_buffer_.clear();
+ decoder_.Reset();
+ return ResumeDecoding(b);
+ }
+
+ DecodeStatus ResumeDecoding(DecodeBuffer* b) override {
+ input_bytes_seen_ += b->Remaining();
+ Http2StringPiece sp(b->cursor(), b->Remaining());
+ if (decoder_.Decode(sp, &output_buffer_)) {
+ b->AdvanceCursor(b->Remaining());
+ // Successfully decoded (or buffered) the bytes in Http2StringPiece.
+ EXPECT_LE(input_bytes_seen_, input_bytes_expected_);
+ // Have we reached the end of the encoded string?
+ if (input_bytes_expected_ == input_bytes_seen_) {
+ if (decoder_.InputProperlyTerminated()) {
+ return DecodeStatus::kDecodeDone;
+ } else {
+ return DecodeStatus::kDecodeError;
+ }
+ }
+ return DecodeStatus::kDecodeInProgress;
+ }
+ return DecodeStatus::kDecodeError;
+ }
+
+ AssertionResult TranscodeAndValidateSeveralWays(
+ Http2StringPiece plain,
+ Http2StringPiece expected_huffman) {
+ Http2String encoded;
+ HuffmanEncode(plain, &encoded);
+ if (expected_huffman.size() > 0 || plain.empty()) {
+ VERIFY_EQ(encoded, expected_huffman);
+ }
+ input_bytes_expected_ = encoded.size();
+ auto validator = [plain, this]() -> AssertionResult {
+ VERIFY_EQ(output_buffer_.size(), plain.size());
+ VERIFY_EQ(output_buffer_, plain);
+ return AssertionSuccess();
+ };
+ DecodeBuffer db(encoded);
+ bool return_non_zero_on_first = false;
+ return DecodeAndValidateSeveralWays(&db, return_non_zero_on_first,
+ ValidateDoneAndEmpty(validator));
+ }
+
+ AssertionResult TranscodeAndValidateSeveralWays(Http2StringPiece plain) {
+ return TranscodeAndValidateSeveralWays(plain, "");
+ }
+
+ Http2String RandomAsciiNonControlString(int length) {
+ return Random().RandStringWithAlphabet(length, ascii_non_control_set_);
+ }
+
+ Http2String RandomBytes(int length) { return Random().RandString(length); }
+
+ const Http2String ascii_non_control_set_;
+ HpackHuffmanDecoder decoder_;
+ Http2String output_buffer_;
+ size_t input_bytes_seen_;
+ size_t input_bytes_expected_;
+};
+
+TEST_F(HpackHuffmanTranscoderTest, RoundTripRandomAsciiNonControlString) {
+ for (size_t length = 0; length != 20; length++) {
+ const Http2String s = RandomAsciiNonControlString(length);
+ ASSERT_TRUE(TranscodeAndValidateSeveralWays(s))
+ << "Unable to decode:\n\n"
+ << Http2HexDump(s) << "\n\noutput_buffer_:\n"
+ << Http2HexDump(output_buffer_);
+ }
+}
+
+TEST_F(HpackHuffmanTranscoderTest, RoundTripRandomBytes) {
+ for (size_t length = 0; length != 20; length++) {
+ const Http2String s = RandomBytes(length);
+ ASSERT_TRUE(TranscodeAndValidateSeveralWays(s))
+ << "Unable to decode:\n\n"
+ << Http2HexDump(s) << "\n\noutput_buffer_:\n"
+ << Http2HexDump(output_buffer_);
+ }
+}
+
+// Two parameters: decoder choice, and the character to round-trip.
+class HpackHuffmanTranscoderAdjacentCharTest
+ : public HpackHuffmanTranscoderTest,
+ public ::testing::WithParamInterface<int> {
+ protected:
+ HpackHuffmanTranscoderAdjacentCharTest()
+ : c_(static_cast<char>(GetParam())) {}
+
+ const char c_;
+};
+
+INSTANTIATE_TEST_CASE_P(HpackHuffmanTranscoderAdjacentCharTest,
+ HpackHuffmanTranscoderAdjacentCharTest,
+ ::testing::Range(0, 256));
+
+// Test c_ adjacent to every other character, both before and after.
+TEST_P(HpackHuffmanTranscoderAdjacentCharTest, RoundTripAdjacentChar) {
+ Http2String s;
+ for (int a = 0; a < 256; ++a) {
+ s.push_back(static_cast<char>(a));
+ s.push_back(c_);
+ s.push_back(static_cast<char>(a));
+ }
+ ASSERT_TRUE(TranscodeAndValidateSeveralWays(s));
+}
+
+// Two parameters: character to repeat, number of repeats.
+class HpackHuffmanTranscoderRepeatedCharTest
+ : public HpackHuffmanTranscoderTest,
+ public ::testing::WithParamInterface<tuple<int, int>> {
+ protected:
+ HpackHuffmanTranscoderRepeatedCharTest()
+ : c_(static_cast<char>(::testing::get<0>(GetParam()))),
+ length_(::testing::get<1>(GetParam())) {}
+ Http2String MakeString() { return Http2String(length_, c_); }
+
+ private:
+ const char c_;
+ const size_t length_;
+};
+
+INSTANTIATE_TEST_CASE_P(
+ HpackHuffmanTranscoderRepeatedCharTest,
+ HpackHuffmanTranscoderRepeatedCharTest,
+ ::testing::Combine(::testing::Range(0, 256),
+ ::testing::Values(1, 2, 3, 4, 8, 16, 32)));
+
+TEST_P(HpackHuffmanTranscoderRepeatedCharTest, RoundTripRepeatedChar) {
+ ASSERT_TRUE(TranscodeAndValidateSeveralWays(MakeString()));
+}
+
+} // namespace
+} // namespace test
+} // namespace http2
diff --git a/chromium/net/third_party/quiche/src/http2/hpack/huffman/huffman_spec_tables.cc b/chromium/net/third_party/quiche/src/http2/hpack/huffman/huffman_spec_tables.cc
new file mode 100644
index 00000000000..27707b98120
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/hpack/huffman/huffman_spec_tables.cc
@@ -0,0 +1,578 @@
+// Copyright (c) 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/http2/hpack/huffman/huffman_spec_tables.h"
+
+namespace http2 {
+
+// clang-format off
+// static
+const uint8_t HuffmanSpecTables::kCodeLengths[] = {
+ 13, 23, 28, 28, 28, 28, 28, 28, // 0 - 7
+ 28, 24, 30, 28, 28, 30, 28, 28, // 8 - 15
+ 28, 28, 28, 28, 28, 28, 30, 28, // 16 - 23
+ 28, 28, 28, 28, 28, 28, 28, 28, // 24 - 31
+ 6, 10, 10, 12, 13, 6, 8, 11, // 32 - 39
+ 10, 10, 8, 11, 8, 6, 6, 6, // 40 - 47
+ 5, 5, 5, 6, 6, 6, 6, 6, // 48 - 55
+ 6, 6, 7, 8, 15, 6, 12, 10, // 56 - 63
+ 13, 6, 7, 7, 7, 7, 7, 7, // 64 - 71
+ 7, 7, 7, 7, 7, 7, 7, 7, // 72 - 79
+ 7, 7, 7, 7, 7, 7, 7, 7, // 80 - 87
+ 8, 7, 8, 13, 19, 13, 14, 6, // 88 - 95
+ 15, 5, 6, 5, 6, 5, 6, 6, // 96 - 103
+ 6, 5, 7, 7, 6, 6, 6, 5, // 104 - 111
+ 6, 7, 6, 5, 5, 6, 7, 7, // 112 - 119
+ 7, 7, 7, 15, 11, 14, 13, 28, // 120 - 127
+ 20, 22, 20, 20, 22, 22, 22, 23, // 128 - 135
+ 22, 23, 23, 23, 23, 23, 24, 23, // 136 - 143
+ 24, 24, 22, 23, 24, 23, 23, 23, // 144 - 151
+ 23, 21, 22, 23, 22, 23, 23, 24, // 152 - 159
+ 22, 21, 20, 22, 22, 23, 23, 21, // 160 - 167
+ 23, 22, 22, 24, 21, 22, 23, 23, // 168 - 175
+ 21, 21, 22, 21, 23, 22, 23, 23, // 176 - 183
+ 20, 22, 22, 22, 23, 22, 22, 23, // 184 - 191
+ 26, 26, 20, 19, 22, 23, 22, 25, // 192 - 199
+ 26, 26, 26, 27, 27, 26, 24, 25, // 200 - 207
+ 19, 21, 26, 27, 27, 26, 27, 24, // 208 - 215
+ 21, 21, 26, 26, 28, 27, 27, 27, // 216 - 223
+ 20, 24, 20, 21, 22, 21, 21, 23, // 224 - 231
+ 22, 22, 25, 25, 24, 24, 26, 23, // 232 - 239
+ 26, 27, 26, 26, 27, 27, 27, 27, // 240 - 247
+ 27, 28, 27, 27, 27, 27, 27, 26, // 248 - 255
+ 30, // 256
+};
+
+// TODO(jamessynge): Remove use of binary literals, that is a C++ 14 feature.
+
+// Uncomment these codes if needed for generating Huffman output, as opposed
+// to decoding Huffman input.
+/*
+// The encoding of each symbol, left justified (as printed), which means that
+// the first bit of the encoding is the high-order bit of the uint32.
+// static
+const uint32_t HuffmanSpecTables::kLeftCodes[] = {
+ 0b11111111110000000000000000000000, // 0x00
+ 0b11111111111111111011000000000000, // 0x01
+ 0b11111111111111111111111000100000, // 0x02
+ 0b11111111111111111111111000110000, // 0x03
+ 0b11111111111111111111111001000000, // 0x04
+ 0b11111111111111111111111001010000, // 0x05
+ 0b11111111111111111111111001100000, // 0x06
+ 0b11111111111111111111111001110000, // 0x07
+ 0b11111111111111111111111010000000, // 0x08
+ 0b11111111111111111110101000000000, // 0x09
+ 0b11111111111111111111111111110000, // 0x0a
+ 0b11111111111111111111111010010000, // 0x0b
+ 0b11111111111111111111111010100000, // 0x0c
+ 0b11111111111111111111111111110100, // 0x0d
+ 0b11111111111111111111111010110000, // 0x0e
+ 0b11111111111111111111111011000000, // 0x0f
+ 0b11111111111111111111111011010000, // 0x10
+ 0b11111111111111111111111011100000, // 0x11
+ 0b11111111111111111111111011110000, // 0x12
+ 0b11111111111111111111111100000000, // 0x13
+ 0b11111111111111111111111100010000, // 0x14
+ 0b11111111111111111111111100100000, // 0x15
+ 0b11111111111111111111111111111000, // 0x16
+ 0b11111111111111111111111100110000, // 0x17
+ 0b11111111111111111111111101000000, // 0x18
+ 0b11111111111111111111111101010000, // 0x19
+ 0b11111111111111111111111101100000, // 0x1a
+ 0b11111111111111111111111101110000, // 0x1b
+ 0b11111111111111111111111110000000, // 0x1c
+ 0b11111111111111111111111110010000, // 0x1d
+ 0b11111111111111111111111110100000, // 0x1e
+ 0b11111111111111111111111110110000, // 0x1f
+ 0b01010000000000000000000000000000, // 0x20
+ 0b11111110000000000000000000000000, // '!'
+ 0b11111110010000000000000000000000, // '\"'
+ 0b11111111101000000000000000000000, // '#'
+ 0b11111111110010000000000000000000, // '$'
+ 0b01010100000000000000000000000000, // '%'
+ 0b11111000000000000000000000000000, // '&'
+ 0b11111111010000000000000000000000, // '\''
+ 0b11111110100000000000000000000000, // '('
+ 0b11111110110000000000000000000000, // ')'
+ 0b11111001000000000000000000000000, // '*'
+ 0b11111111011000000000000000000000, // '+'
+ 0b11111010000000000000000000000000, // ','
+ 0b01011000000000000000000000000000, // '-'
+ 0b01011100000000000000000000000000, // '.'
+ 0b01100000000000000000000000000000, // '/'
+ 0b00000000000000000000000000000000, // '0'
+ 0b00001000000000000000000000000000, // '1'
+ 0b00010000000000000000000000000000, // '2'
+ 0b01100100000000000000000000000000, // '3'
+ 0b01101000000000000000000000000000, // '4'
+ 0b01101100000000000000000000000000, // '5'
+ 0b01110000000000000000000000000000, // '6'
+ 0b01110100000000000000000000000000, // '7'
+ 0b01111000000000000000000000000000, // '8'
+ 0b01111100000000000000000000000000, // '9'
+ 0b10111000000000000000000000000000, // ':'
+ 0b11111011000000000000000000000000, // ';'
+ 0b11111111111110000000000000000000, // '<'
+ 0b10000000000000000000000000000000, // '='
+ 0b11111111101100000000000000000000, // '>'
+ 0b11111111000000000000000000000000, // '?'
+ 0b11111111110100000000000000000000, // '@'
+ 0b10000100000000000000000000000000, // 'A'
+ 0b10111010000000000000000000000000, // 'B'
+ 0b10111100000000000000000000000000, // 'C'
+ 0b10111110000000000000000000000000, // 'D'
+ 0b11000000000000000000000000000000, // 'E'
+ 0b11000010000000000000000000000000, // 'F'
+ 0b11000100000000000000000000000000, // 'G'
+ 0b11000110000000000000000000000000, // 'H'
+ 0b11001000000000000000000000000000, // 'I'
+ 0b11001010000000000000000000000000, // 'J'
+ 0b11001100000000000000000000000000, // 'K'
+ 0b11001110000000000000000000000000, // 'L'
+ 0b11010000000000000000000000000000, // 'M'
+ 0b11010010000000000000000000000000, // 'N'
+ 0b11010100000000000000000000000000, // 'O'
+ 0b11010110000000000000000000000000, // 'P'
+ 0b11011000000000000000000000000000, // 'Q'
+ 0b11011010000000000000000000000000, // 'R'
+ 0b11011100000000000000000000000000, // 'S'
+ 0b11011110000000000000000000000000, // 'T'
+ 0b11100000000000000000000000000000, // 'U'
+ 0b11100010000000000000000000000000, // 'V'
+ 0b11100100000000000000000000000000, // 'W'
+ 0b11111100000000000000000000000000, // 'X'
+ 0b11100110000000000000000000000000, // 'Y'
+ 0b11111101000000000000000000000000, // 'Z'
+ 0b11111111110110000000000000000000, // '['
+ 0b11111111111111100000000000000000, // '\\'
+ 0b11111111111000000000000000000000, // ']'
+ 0b11111111111100000000000000000000, // '^'
+ 0b10001000000000000000000000000000, // '_'
+ 0b11111111111110100000000000000000, // '`'
+ 0b00011000000000000000000000000000, // 'a'
+ 0b10001100000000000000000000000000, // 'b'
+ 0b00100000000000000000000000000000, // 'c'
+ 0b10010000000000000000000000000000, // 'd'
+ 0b00101000000000000000000000000000, // 'e'
+ 0b10010100000000000000000000000000, // 'f'
+ 0b10011000000000000000000000000000, // 'g'
+ 0b10011100000000000000000000000000, // 'h'
+ 0b00110000000000000000000000000000, // 'i'
+ 0b11101000000000000000000000000000, // 'j'
+ 0b11101010000000000000000000000000, // 'k'
+ 0b10100000000000000000000000000000, // 'l'
+ 0b10100100000000000000000000000000, // 'm'
+ 0b10101000000000000000000000000000, // 'n'
+ 0b00111000000000000000000000000000, // 'o'
+ 0b10101100000000000000000000000000, // 'p'
+ 0b11101100000000000000000000000000, // 'q'
+ 0b10110000000000000000000000000000, // 'r'
+ 0b01000000000000000000000000000000, // 's'
+ 0b01001000000000000000000000000000, // 't'
+ 0b10110100000000000000000000000000, // 'u'
+ 0b11101110000000000000000000000000, // 'v'
+ 0b11110000000000000000000000000000, // 'w'
+ 0b11110010000000000000000000000000, // 'x'
+ 0b11110100000000000000000000000000, // 'y'
+ 0b11110110000000000000000000000000, // 'z'
+ 0b11111111111111000000000000000000, // '{'
+ 0b11111111100000000000000000000000, // '|'
+ 0b11111111111101000000000000000000, // '}'
+ 0b11111111111010000000000000000000, // '~'
+ 0b11111111111111111111111111000000, // 0x7f
+ 0b11111111111111100110000000000000, // 0x80
+ 0b11111111111111110100100000000000, // 0x81
+ 0b11111111111111100111000000000000, // 0x82
+ 0b11111111111111101000000000000000, // 0x83
+ 0b11111111111111110100110000000000, // 0x84
+ 0b11111111111111110101000000000000, // 0x85
+ 0b11111111111111110101010000000000, // 0x86
+ 0b11111111111111111011001000000000, // 0x87
+ 0b11111111111111110101100000000000, // 0x88
+ 0b11111111111111111011010000000000, // 0x89
+ 0b11111111111111111011011000000000, // 0x8a
+ 0b11111111111111111011100000000000, // 0x8b
+ 0b11111111111111111011101000000000, // 0x8c
+ 0b11111111111111111011110000000000, // 0x8d
+ 0b11111111111111111110101100000000, // 0x8e
+ 0b11111111111111111011111000000000, // 0x8f
+ 0b11111111111111111110110000000000, // 0x90
+ 0b11111111111111111110110100000000, // 0x91
+ 0b11111111111111110101110000000000, // 0x92
+ 0b11111111111111111100000000000000, // 0x93
+ 0b11111111111111111110111000000000, // 0x94
+ 0b11111111111111111100001000000000, // 0x95
+ 0b11111111111111111100010000000000, // 0x96
+ 0b11111111111111111100011000000000, // 0x97
+ 0b11111111111111111100100000000000, // 0x98
+ 0b11111111111111101110000000000000, // 0x99
+ 0b11111111111111110110000000000000, // 0x9a
+ 0b11111111111111111100101000000000, // 0x9b
+ 0b11111111111111110110010000000000, // 0x9c
+ 0b11111111111111111100110000000000, // 0x9d
+ 0b11111111111111111100111000000000, // 0x9e
+ 0b11111111111111111110111100000000, // 0x9f
+ 0b11111111111111110110100000000000, // 0xa0
+ 0b11111111111111101110100000000000, // 0xa1
+ 0b11111111111111101001000000000000, // 0xa2
+ 0b11111111111111110110110000000000, // 0xa3
+ 0b11111111111111110111000000000000, // 0xa4
+ 0b11111111111111111101000000000000, // 0xa5
+ 0b11111111111111111101001000000000, // 0xa6
+ 0b11111111111111101111000000000000, // 0xa7
+ 0b11111111111111111101010000000000, // 0xa8
+ 0b11111111111111110111010000000000, // 0xa9
+ 0b11111111111111110111100000000000, // 0xaa
+ 0b11111111111111111111000000000000, // 0xab
+ 0b11111111111111101111100000000000, // 0xac
+ 0b11111111111111110111110000000000, // 0xad
+ 0b11111111111111111101011000000000, // 0xae
+ 0b11111111111111111101100000000000, // 0xaf
+ 0b11111111111111110000000000000000, // 0xb0
+ 0b11111111111111110000100000000000, // 0xb1
+ 0b11111111111111111000000000000000, // 0xb2
+ 0b11111111111111110001000000000000, // 0xb3
+ 0b11111111111111111101101000000000, // 0xb4
+ 0b11111111111111111000010000000000, // 0xb5
+ 0b11111111111111111101110000000000, // 0xb6
+ 0b11111111111111111101111000000000, // 0xb7
+ 0b11111111111111101010000000000000, // 0xb8
+ 0b11111111111111111000100000000000, // 0xb9
+ 0b11111111111111111000110000000000, // 0xba
+ 0b11111111111111111001000000000000, // 0xbb
+ 0b11111111111111111110000000000000, // 0xbc
+ 0b11111111111111111001010000000000, // 0xbd
+ 0b11111111111111111001100000000000, // 0xbe
+ 0b11111111111111111110001000000000, // 0xbf
+ 0b11111111111111111111100000000000, // 0xc0
+ 0b11111111111111111111100001000000, // 0xc1
+ 0b11111111111111101011000000000000, // 0xc2
+ 0b11111111111111100010000000000000, // 0xc3
+ 0b11111111111111111001110000000000, // 0xc4
+ 0b11111111111111111110010000000000, // 0xc5
+ 0b11111111111111111010000000000000, // 0xc6
+ 0b11111111111111111111011000000000, // 0xc7
+ 0b11111111111111111111100010000000, // 0xc8
+ 0b11111111111111111111100011000000, // 0xc9
+ 0b11111111111111111111100100000000, // 0xca
+ 0b11111111111111111111101111000000, // 0xcb
+ 0b11111111111111111111101111100000, // 0xcc
+ 0b11111111111111111111100101000000, // 0xcd
+ 0b11111111111111111111000100000000, // 0xce
+ 0b11111111111111111111011010000000, // 0xcf
+ 0b11111111111111100100000000000000, // 0xd0
+ 0b11111111111111110001100000000000, // 0xd1
+ 0b11111111111111111111100110000000, // 0xd2
+ 0b11111111111111111111110000000000, // 0xd3
+ 0b11111111111111111111110000100000, // 0xd4
+ 0b11111111111111111111100111000000, // 0xd5
+ 0b11111111111111111111110001000000, // 0xd6
+ 0b11111111111111111111001000000000, // 0xd7
+ 0b11111111111111110010000000000000, // 0xd8
+ 0b11111111111111110010100000000000, // 0xd9
+ 0b11111111111111111111101000000000, // 0xda
+ 0b11111111111111111111101001000000, // 0xdb
+ 0b11111111111111111111111111010000, // 0xdc
+ 0b11111111111111111111110001100000, // 0xdd
+ 0b11111111111111111111110010000000, // 0xde
+ 0b11111111111111111111110010100000, // 0xdf
+ 0b11111111111111101100000000000000, // 0xe0
+ 0b11111111111111111111001100000000, // 0xe1
+ 0b11111111111111101101000000000000, // 0xe2
+ 0b11111111111111110011000000000000, // 0xe3
+ 0b11111111111111111010010000000000, // 0xe4
+ 0b11111111111111110011100000000000, // 0xe5
+ 0b11111111111111110100000000000000, // 0xe6
+ 0b11111111111111111110011000000000, // 0xe7
+ 0b11111111111111111010100000000000, // 0xe8
+ 0b11111111111111111010110000000000, // 0xe9
+ 0b11111111111111111111011100000000, // 0xea
+ 0b11111111111111111111011110000000, // 0xeb
+ 0b11111111111111111111010000000000, // 0xec
+ 0b11111111111111111111010100000000, // 0xed
+ 0b11111111111111111111101010000000, // 0xee
+ 0b11111111111111111110100000000000, // 0xef
+ 0b11111111111111111111101011000000, // 0xf0
+ 0b11111111111111111111110011000000, // 0xf1
+ 0b11111111111111111111101100000000, // 0xf2
+ 0b11111111111111111111101101000000, // 0xf3
+ 0b11111111111111111111110011100000, // 0xf4
+ 0b11111111111111111111110100000000, // 0xf5
+ 0b11111111111111111111110100100000, // 0xf6
+ 0b11111111111111111111110101000000, // 0xf7
+ 0b11111111111111111111110101100000, // 0xf8
+ 0b11111111111111111111111111100000, // 0xf9
+ 0b11111111111111111111110110000000, // 0xfa
+ 0b11111111111111111111110110100000, // 0xfb
+ 0b11111111111111111111110111000000, // 0xfc
+ 0b11111111111111111111110111100000, // 0xfd
+ 0b11111111111111111111111000000000, // 0xfe
+ 0b11111111111111111111101110000000, // 0xff
+ 0b11111111111111111111111111111100, // 0x100
+};
+*/
+
+// static
+const uint32_t HuffmanSpecTables::kRightCodes[] = {
+ 0b00000000000000000001111111111000, // 0x00
+ 0b00000000011111111111111111011000, // 0x01
+ 0b00001111111111111111111111100010, // 0x02
+ 0b00001111111111111111111111100011, // 0x03
+ 0b00001111111111111111111111100100, // 0x04
+ 0b00001111111111111111111111100101, // 0x05
+ 0b00001111111111111111111111100110, // 0x06
+ 0b00001111111111111111111111100111, // 0x07
+ 0b00001111111111111111111111101000, // 0x08
+ 0b00000000111111111111111111101010, // 0x09
+ 0b00111111111111111111111111111100, // 0x0a
+ 0b00001111111111111111111111101001, // 0x0b
+ 0b00001111111111111111111111101010, // 0x0c
+ 0b00111111111111111111111111111101, // 0x0d
+ 0b00001111111111111111111111101011, // 0x0e
+ 0b00001111111111111111111111101100, // 0x0f
+ 0b00001111111111111111111111101101, // 0x10
+ 0b00001111111111111111111111101110, // 0x11
+ 0b00001111111111111111111111101111, // 0x12
+ 0b00001111111111111111111111110000, // 0x13
+ 0b00001111111111111111111111110001, // 0x14
+ 0b00001111111111111111111111110010, // 0x15
+ 0b00111111111111111111111111111110, // 0x16
+ 0b00001111111111111111111111110011, // 0x17
+ 0b00001111111111111111111111110100, // 0x18
+ 0b00001111111111111111111111110101, // 0x19
+ 0b00001111111111111111111111110110, // 0x1a
+ 0b00001111111111111111111111110111, // 0x1b
+ 0b00001111111111111111111111111000, // 0x1c
+ 0b00001111111111111111111111111001, // 0x1d
+ 0b00001111111111111111111111111010, // 0x1e
+ 0b00001111111111111111111111111011, // 0x1f
+ 0b00000000000000000000000000010100, // 0x20
+ 0b00000000000000000000001111111000, // '!'
+ 0b00000000000000000000001111111001, // '\"'
+ 0b00000000000000000000111111111010, // '#'
+ 0b00000000000000000001111111111001, // '$'
+ 0b00000000000000000000000000010101, // '%'
+ 0b00000000000000000000000011111000, // '&'
+ 0b00000000000000000000011111111010, // '\''
+ 0b00000000000000000000001111111010, // '('
+ 0b00000000000000000000001111111011, // ')'
+ 0b00000000000000000000000011111001, // '*'
+ 0b00000000000000000000011111111011, // '+'
+ 0b00000000000000000000000011111010, // ','
+ 0b00000000000000000000000000010110, // '-'
+ 0b00000000000000000000000000010111, // '.'
+ 0b00000000000000000000000000011000, // '/'
+ 0b00000000000000000000000000000000, // '0'
+ 0b00000000000000000000000000000001, // '1'
+ 0b00000000000000000000000000000010, // '2'
+ 0b00000000000000000000000000011001, // '3'
+ 0b00000000000000000000000000011010, // '4'
+ 0b00000000000000000000000000011011, // '5'
+ 0b00000000000000000000000000011100, // '6'
+ 0b00000000000000000000000000011101, // '7'
+ 0b00000000000000000000000000011110, // '8'
+ 0b00000000000000000000000000011111, // '9'
+ 0b00000000000000000000000001011100, // ':'
+ 0b00000000000000000000000011111011, // ';'
+ 0b00000000000000000111111111111100, // '<'
+ 0b00000000000000000000000000100000, // '='
+ 0b00000000000000000000111111111011, // '>'
+ 0b00000000000000000000001111111100, // '?'
+ 0b00000000000000000001111111111010, // '@'
+ 0b00000000000000000000000000100001, // 'A'
+ 0b00000000000000000000000001011101, // 'B'
+ 0b00000000000000000000000001011110, // 'C'
+ 0b00000000000000000000000001011111, // 'D'
+ 0b00000000000000000000000001100000, // 'E'
+ 0b00000000000000000000000001100001, // 'F'
+ 0b00000000000000000000000001100010, // 'G'
+ 0b00000000000000000000000001100011, // 'H'
+ 0b00000000000000000000000001100100, // 'I'
+ 0b00000000000000000000000001100101, // 'J'
+ 0b00000000000000000000000001100110, // 'K'
+ 0b00000000000000000000000001100111, // 'L'
+ 0b00000000000000000000000001101000, // 'M'
+ 0b00000000000000000000000001101001, // 'N'
+ 0b00000000000000000000000001101010, // 'O'
+ 0b00000000000000000000000001101011, // 'P'
+ 0b00000000000000000000000001101100, // 'Q'
+ 0b00000000000000000000000001101101, // 'R'
+ 0b00000000000000000000000001101110, // 'S'
+ 0b00000000000000000000000001101111, // 'T'
+ 0b00000000000000000000000001110000, // 'U'
+ 0b00000000000000000000000001110001, // 'V'
+ 0b00000000000000000000000001110010, // 'W'
+ 0b00000000000000000000000011111100, // 'X'
+ 0b00000000000000000000000001110011, // 'Y'
+ 0b00000000000000000000000011111101, // 'Z'
+ 0b00000000000000000001111111111011, // '['
+ 0b00000000000001111111111111110000, // '\\'
+ 0b00000000000000000001111111111100, // ']'
+ 0b00000000000000000011111111111100, // '^'
+ 0b00000000000000000000000000100010, // '_'
+ 0b00000000000000000111111111111101, // '`'
+ 0b00000000000000000000000000000011, // 'a'
+ 0b00000000000000000000000000100011, // 'b'
+ 0b00000000000000000000000000000100, // 'c'
+ 0b00000000000000000000000000100100, // 'd'
+ 0b00000000000000000000000000000101, // 'e'
+ 0b00000000000000000000000000100101, // 'f'
+ 0b00000000000000000000000000100110, // 'g'
+ 0b00000000000000000000000000100111, // 'h'
+ 0b00000000000000000000000000000110, // 'i'
+ 0b00000000000000000000000001110100, // 'j'
+ 0b00000000000000000000000001110101, // 'k'
+ 0b00000000000000000000000000101000, // 'l'
+ 0b00000000000000000000000000101001, // 'm'
+ 0b00000000000000000000000000101010, // 'n'
+ 0b00000000000000000000000000000111, // 'o'
+ 0b00000000000000000000000000101011, // 'p'
+ 0b00000000000000000000000001110110, // 'q'
+ 0b00000000000000000000000000101100, // 'r'
+ 0b00000000000000000000000000001000, // 's'
+ 0b00000000000000000000000000001001, // 't'
+ 0b00000000000000000000000000101101, // 'u'
+ 0b00000000000000000000000001110111, // 'v'
+ 0b00000000000000000000000001111000, // 'w'
+ 0b00000000000000000000000001111001, // 'x'
+ 0b00000000000000000000000001111010, // 'y'
+ 0b00000000000000000000000001111011, // 'z'
+ 0b00000000000000000111111111111110, // '{'
+ 0b00000000000000000000011111111100, // '|'
+ 0b00000000000000000011111111111101, // '}'
+ 0b00000000000000000001111111111101, // '~'
+ 0b00001111111111111111111111111100, // 0x7f
+ 0b00000000000011111111111111100110, // 0x80
+ 0b00000000001111111111111111010010, // 0x81
+ 0b00000000000011111111111111100111, // 0x82
+ 0b00000000000011111111111111101000, // 0x83
+ 0b00000000001111111111111111010011, // 0x84
+ 0b00000000001111111111111111010100, // 0x85
+ 0b00000000001111111111111111010101, // 0x86
+ 0b00000000011111111111111111011001, // 0x87
+ 0b00000000001111111111111111010110, // 0x88
+ 0b00000000011111111111111111011010, // 0x89
+ 0b00000000011111111111111111011011, // 0x8a
+ 0b00000000011111111111111111011100, // 0x8b
+ 0b00000000011111111111111111011101, // 0x8c
+ 0b00000000011111111111111111011110, // 0x8d
+ 0b00000000111111111111111111101011, // 0x8e
+ 0b00000000011111111111111111011111, // 0x8f
+ 0b00000000111111111111111111101100, // 0x90
+ 0b00000000111111111111111111101101, // 0x91
+ 0b00000000001111111111111111010111, // 0x92
+ 0b00000000011111111111111111100000, // 0x93
+ 0b00000000111111111111111111101110, // 0x94
+ 0b00000000011111111111111111100001, // 0x95
+ 0b00000000011111111111111111100010, // 0x96
+ 0b00000000011111111111111111100011, // 0x97
+ 0b00000000011111111111111111100100, // 0x98
+ 0b00000000000111111111111111011100, // 0x99
+ 0b00000000001111111111111111011000, // 0x9a
+ 0b00000000011111111111111111100101, // 0x9b
+ 0b00000000001111111111111111011001, // 0x9c
+ 0b00000000011111111111111111100110, // 0x9d
+ 0b00000000011111111111111111100111, // 0x9e
+ 0b00000000111111111111111111101111, // 0x9f
+ 0b00000000001111111111111111011010, // 0xa0
+ 0b00000000000111111111111111011101, // 0xa1
+ 0b00000000000011111111111111101001, // 0xa2
+ 0b00000000001111111111111111011011, // 0xa3
+ 0b00000000001111111111111111011100, // 0xa4
+ 0b00000000011111111111111111101000, // 0xa5
+ 0b00000000011111111111111111101001, // 0xa6
+ 0b00000000000111111111111111011110, // 0xa7
+ 0b00000000011111111111111111101010, // 0xa8
+ 0b00000000001111111111111111011101, // 0xa9
+ 0b00000000001111111111111111011110, // 0xaa
+ 0b00000000111111111111111111110000, // 0xab
+ 0b00000000000111111111111111011111, // 0xac
+ 0b00000000001111111111111111011111, // 0xad
+ 0b00000000011111111111111111101011, // 0xae
+ 0b00000000011111111111111111101100, // 0xaf
+ 0b00000000000111111111111111100000, // 0xb0
+ 0b00000000000111111111111111100001, // 0xb1
+ 0b00000000001111111111111111100000, // 0xb2
+ 0b00000000000111111111111111100010, // 0xb3
+ 0b00000000011111111111111111101101, // 0xb4
+ 0b00000000001111111111111111100001, // 0xb5
+ 0b00000000011111111111111111101110, // 0xb6
+ 0b00000000011111111111111111101111, // 0xb7
+ 0b00000000000011111111111111101010, // 0xb8
+ 0b00000000001111111111111111100010, // 0xb9
+ 0b00000000001111111111111111100011, // 0xba
+ 0b00000000001111111111111111100100, // 0xbb
+ 0b00000000011111111111111111110000, // 0xbc
+ 0b00000000001111111111111111100101, // 0xbd
+ 0b00000000001111111111111111100110, // 0xbe
+ 0b00000000011111111111111111110001, // 0xbf
+ 0b00000011111111111111111111100000, // 0xc0
+ 0b00000011111111111111111111100001, // 0xc1
+ 0b00000000000011111111111111101011, // 0xc2
+ 0b00000000000001111111111111110001, // 0xc3
+ 0b00000000001111111111111111100111, // 0xc4
+ 0b00000000011111111111111111110010, // 0xc5
+ 0b00000000001111111111111111101000, // 0xc6
+ 0b00000001111111111111111111101100, // 0xc7
+ 0b00000011111111111111111111100010, // 0xc8
+ 0b00000011111111111111111111100011, // 0xc9
+ 0b00000011111111111111111111100100, // 0xca
+ 0b00000111111111111111111111011110, // 0xcb
+ 0b00000111111111111111111111011111, // 0xcc
+ 0b00000011111111111111111111100101, // 0xcd
+ 0b00000000111111111111111111110001, // 0xce
+ 0b00000001111111111111111111101101, // 0xcf
+ 0b00000000000001111111111111110010, // 0xd0
+ 0b00000000000111111111111111100011, // 0xd1
+ 0b00000011111111111111111111100110, // 0xd2
+ 0b00000111111111111111111111100000, // 0xd3
+ 0b00000111111111111111111111100001, // 0xd4
+ 0b00000011111111111111111111100111, // 0xd5
+ 0b00000111111111111111111111100010, // 0xd6
+ 0b00000000111111111111111111110010, // 0xd7
+ 0b00000000000111111111111111100100, // 0xd8
+ 0b00000000000111111111111111100101, // 0xd9
+ 0b00000011111111111111111111101000, // 0xda
+ 0b00000011111111111111111111101001, // 0xdb
+ 0b00001111111111111111111111111101, // 0xdc
+ 0b00000111111111111111111111100011, // 0xdd
+ 0b00000111111111111111111111100100, // 0xde
+ 0b00000111111111111111111111100101, // 0xdf
+ 0b00000000000011111111111111101100, // 0xe0
+ 0b00000000111111111111111111110011, // 0xe1
+ 0b00000000000011111111111111101101, // 0xe2
+ 0b00000000000111111111111111100110, // 0xe3
+ 0b00000000001111111111111111101001, // 0xe4
+ 0b00000000000111111111111111100111, // 0xe5
+ 0b00000000000111111111111111101000, // 0xe6
+ 0b00000000011111111111111111110011, // 0xe7
+ 0b00000000001111111111111111101010, // 0xe8
+ 0b00000000001111111111111111101011, // 0xe9
+ 0b00000001111111111111111111101110, // 0xea
+ 0b00000001111111111111111111101111, // 0xeb
+ 0b00000000111111111111111111110100, // 0xec
+ 0b00000000111111111111111111110101, // 0xed
+ 0b00000011111111111111111111101010, // 0xee
+ 0b00000000011111111111111111110100, // 0xef
+ 0b00000011111111111111111111101011, // 0xf0
+ 0b00000111111111111111111111100110, // 0xf1
+ 0b00000011111111111111111111101100, // 0xf2
+ 0b00000011111111111111111111101101, // 0xf3
+ 0b00000111111111111111111111100111, // 0xf4
+ 0b00000111111111111111111111101000, // 0xf5
+ 0b00000111111111111111111111101001, // 0xf6
+ 0b00000111111111111111111111101010, // 0xf7
+ 0b00000111111111111111111111101011, // 0xf8
+ 0b00001111111111111111111111111110, // 0xf9
+ 0b00000111111111111111111111101100, // 0xfa
+ 0b00000111111111111111111111101101, // 0xfb
+ 0b00000111111111111111111111101110, // 0xfc
+ 0b00000111111111111111111111101111, // 0xfd
+ 0b00000111111111111111111111110000, // 0xfe
+ 0b00000011111111111111111111101110, // 0xff
+ 0b00111111111111111111111111111111, // 0x100
+};
+// clang-format off
+
+} // namespace http2
diff --git a/chromium/net/third_party/quiche/src/http2/hpack/huffman/huffman_spec_tables.h b/chromium/net/third_party/quiche/src/http2/hpack/huffman/huffman_spec_tables.h
new file mode 100644
index 00000000000..6cd8726d7b4
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/hpack/huffman/huffman_spec_tables.h
@@ -0,0 +1,25 @@
+// Copyright (c) 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_HTTP2_HPACK_HUFFMAN_HUFFMAN_SPEC_TABLES_H_
+#define QUICHE_HTTP2_HPACK_HUFFMAN_HUFFMAN_SPEC_TABLES_H_
+
+// Tables describing the Huffman encoding of bytes as specified by RFC7541.
+
+#include <cstdint>
+
+namespace http2 {
+
+struct HuffmanSpecTables {
+ // Number of bits in the encoding of each symbol (byte).
+ static const uint8_t kCodeLengths[257];
+
+ // The encoding of each symbol, right justified (as printed), which means that
+ // the last bit of the encoding is the low-order bit of the uint32.
+ static const uint32_t kRightCodes[257];
+};
+
+} // namespace http2
+
+#endif // QUICHE_HTTP2_HPACK_HUFFMAN_HUFFMAN_SPEC_TABLES_H_
diff --git a/chromium/net/third_party/quiche/src/http2/hpack/tools/hpack_block_builder.cc b/chromium/net/third_party/quiche/src/http2/hpack/tools/hpack_block_builder.cc
new file mode 100644
index 00000000000..5a79f78b20c
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/hpack/tools/hpack_block_builder.cc
@@ -0,0 +1,80 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/http2/hpack/tools/hpack_block_builder.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/http2/hpack/varint/hpack_varint_encoder.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_bug_tracker.h"
+
+namespace http2 {
+namespace test {
+
+void HpackBlockBuilder::AppendHighBitsAndVarint(uint8_t high_bits,
+ uint8_t prefix_length,
+ uint64_t varint) {
+ EXPECT_LE(3, prefix_length);
+ EXPECT_LE(prefix_length, 8);
+
+ HpackVarintEncoder varint_encoder;
+
+ unsigned char c =
+ varint_encoder.StartEncoding(high_bits, prefix_length, varint);
+ buffer_.push_back(c);
+
+ if (!varint_encoder.IsEncodingInProgress()) {
+ return;
+ }
+
+ // After the prefix, at most 63 bits can remain to be encoded.
+ // Each octet holds 7 bits, so at most 9 octets are necessary.
+ // TODO(bnc): Move this into a constant in HpackVarintEncoder.
+ varint_encoder.ResumeEncoding(/* max_encoded_bytes = */ 10, &buffer_);
+ DCHECK(!varint_encoder.IsEncodingInProgress());
+}
+
+void HpackBlockBuilder::AppendEntryTypeAndVarint(HpackEntryType entry_type,
+ uint64_t varint) {
+ uint8_t high_bits;
+ uint8_t prefix_length; // Bits of the varint prefix in the first byte.
+ switch (entry_type) {
+ case HpackEntryType::kIndexedHeader:
+ high_bits = 0x80;
+ prefix_length = 7;
+ break;
+ case HpackEntryType::kDynamicTableSizeUpdate:
+ high_bits = 0x20;
+ prefix_length = 5;
+ break;
+ case HpackEntryType::kIndexedLiteralHeader:
+ high_bits = 0x40;
+ prefix_length = 6;
+ break;
+ case HpackEntryType::kUnindexedLiteralHeader:
+ high_bits = 0x00;
+ prefix_length = 4;
+ break;
+ case HpackEntryType::kNeverIndexedLiteralHeader:
+ high_bits = 0x10;
+ prefix_length = 4;
+ break;
+ default:
+ HTTP2_BUG << "Unreached, entry_type=" << entry_type;
+ high_bits = 0;
+ prefix_length = 0;
+ break;
+ }
+ AppendHighBitsAndVarint(high_bits, prefix_length, varint);
+}
+
+void HpackBlockBuilder::AppendString(bool is_huffman_encoded,
+ Http2StringPiece str) {
+ uint8_t high_bits = is_huffman_encoded ? 0x80 : 0;
+ uint8_t prefix_length = 7;
+ AppendHighBitsAndVarint(high_bits, prefix_length, str.size());
+ buffer_.append(str.data(), str.size());
+}
+
+} // namespace test
+} // namespace http2
diff --git a/chromium/net/third_party/quiche/src/http2/hpack/tools/hpack_block_builder.h b/chromium/net/third_party/quiche/src/http2/hpack/tools/hpack_block_builder.h
new file mode 100644
index 00000000000..560953cc341
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/hpack/tools/hpack_block_builder.h
@@ -0,0 +1,96 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_HTTP2_HPACK_TOOLS_HPACK_BLOCK_BUILDER_H_
+#define QUICHE_HTTP2_HPACK_TOOLS_HPACK_BLOCK_BUILDER_H_
+
+// HpackBlockBuilder builds wire-format HPACK blocks (or fragments thereof)
+// from components.
+
+// Supports very large varints to enable tests to create HPACK blocks with
+// values that the decoder should reject. For now, this is only intended for
+// use in tests, and thus has EXPECT* in the code. If desired to use it in an
+// encoder, it will need optimization work, especially w.r.t memory mgmt, and
+// the EXPECT* will need to be removed or replaced with DCHECKs. And of course
+// the support for very large varints will not be needed in production code.
+
+#include <stddef.h>
+
+#include <cstdint>
+
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/http2/hpack/http2_hpack_constants.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string_piece.h"
+
+namespace http2 {
+namespace test {
+
+class HpackBlockBuilder {
+ public:
+ explicit HpackBlockBuilder(Http2StringPiece initial_contents)
+ : buffer_(initial_contents.data(), initial_contents.size()) {}
+ HpackBlockBuilder() {}
+ ~HpackBlockBuilder() {}
+
+ size_t size() const { return buffer_.size(); }
+ const Http2String& buffer() const { return buffer_; }
+
+ //----------------------------------------------------------------------------
+ // Methods for appending a valid HPACK entry.
+
+ void AppendIndexedHeader(uint64_t index) {
+ AppendEntryTypeAndVarint(HpackEntryType::kIndexedHeader, index);
+ }
+
+ void AppendDynamicTableSizeUpdate(uint64_t size) {
+ AppendEntryTypeAndVarint(HpackEntryType::kDynamicTableSizeUpdate, size);
+ }
+
+ void AppendNameIndexAndLiteralValue(HpackEntryType entry_type,
+ uint64_t name_index,
+ bool value_is_huffman_encoded,
+ Http2StringPiece value) {
+ // name_index==0 would indicate that the entry includes a literal name.
+ // Call AppendLiteralNameAndValue in that case.
+ EXPECT_NE(0u, name_index);
+ AppendEntryTypeAndVarint(entry_type, name_index);
+ AppendString(value_is_huffman_encoded, value);
+ }
+
+ void AppendLiteralNameAndValue(HpackEntryType entry_type,
+ bool name_is_huffman_encoded,
+ Http2StringPiece name,
+ bool value_is_huffman_encoded,
+ Http2StringPiece value) {
+ AppendEntryTypeAndVarint(entry_type, 0);
+ AppendString(name_is_huffman_encoded, name);
+ AppendString(value_is_huffman_encoded, value);
+ }
+
+ //----------------------------------------------------------------------------
+ // Primitive methods that are not guaranteed to write a valid HPACK entry.
+
+ // Appends a varint, with the specified high_bits above the prefix of the
+ // varint.
+ void AppendHighBitsAndVarint(uint8_t high_bits,
+ uint8_t prefix_length,
+ uint64_t varint);
+
+ // Append the start of an HPACK entry for the specified type, with the
+ // specified varint.
+ void AppendEntryTypeAndVarint(HpackEntryType entry_type, uint64_t varint);
+
+ // Append a header string (i.e. a header name or value) in HPACK format.
+ // Does NOT perform Huffman encoding.
+ void AppendString(bool is_huffman_encoded, Http2StringPiece str);
+
+ private:
+ Http2String buffer_;
+};
+
+} // namespace test
+} // namespace http2
+
+#endif // QUICHE_HTTP2_HPACK_TOOLS_HPACK_BLOCK_BUILDER_H_
diff --git a/chromium/net/third_party/quiche/src/http2/hpack/tools/hpack_block_builder_test.cc b/chromium/net/third_party/quiche/src/http2/hpack/tools/hpack_block_builder_test.cc
new file mode 100644
index 00000000000..a7b80647979
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/hpack/tools/hpack_block_builder_test.cc
@@ -0,0 +1,169 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/http2/hpack/tools/hpack_block_builder.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string_utils.h"
+
+namespace http2 {
+namespace test {
+namespace {
+const bool kUncompressed = false;
+const bool kCompressed = true;
+
+// TODO(jamessynge): Once static table code is checked in, switch to using
+// constants from there.
+const uint32_t kStaticTableMethodGET = 2;
+const uint32_t kStaticTablePathSlash = 4;
+const uint32_t kStaticTableSchemeHttp = 6;
+
+// Tests of encoding per the RFC. See:
+// http://httpwg.org/specs/rfc7541.html#header.field.representation.examples
+// The expected values have been copied from the RFC.
+TEST(HpackBlockBuilderTest, ExamplesFromSpecC2) {
+ {
+ HpackBlockBuilder b;
+ b.AppendLiteralNameAndValue(HpackEntryType::kIndexedLiteralHeader,
+ kUncompressed, "custom-key", kUncompressed,
+ "custom-header");
+ EXPECT_EQ(26u, b.size());
+
+ const char kExpected[] =
+ "\x40" // == Literal indexed ==
+ "\x0a" // Name length (10)
+ "custom-key" // Name
+ "\x0d" // Value length (13)
+ "custom-header"; // Value
+ EXPECT_EQ(kExpected, b.buffer());
+ }
+ {
+ HpackBlockBuilder b;
+ b.AppendNameIndexAndLiteralValue(HpackEntryType::kUnindexedLiteralHeader, 4,
+ kUncompressed, "/sample/path");
+ EXPECT_EQ(14u, b.size());
+
+ const char kExpected[] =
+ "\x04" // == Literal unindexed, name index 0x04 ==
+ "\x0c" // Value length (12)
+ "/sample/path"; // Value
+ EXPECT_EQ(kExpected, b.buffer());
+ }
+ {
+ HpackBlockBuilder b;
+ b.AppendLiteralNameAndValue(HpackEntryType::kNeverIndexedLiteralHeader,
+ kUncompressed, "password", kUncompressed,
+ "secret");
+ EXPECT_EQ(17u, b.size());
+
+ const char kExpected[] =
+ "\x10" // == Literal never indexed ==
+ "\x08" // Name length (8)
+ "password" // Name
+ "\x06" // Value length (6)
+ "secret"; // Value
+ EXPECT_EQ(kExpected, b.buffer());
+ }
+ {
+ HpackBlockBuilder b;
+ b.AppendIndexedHeader(2);
+ EXPECT_EQ(1u, b.size());
+
+ const char kExpected[] = "\x82"; // == Indexed (2) ==
+ EXPECT_EQ(kExpected, b.buffer());
+ }
+}
+
+// Tests of encoding per the RFC. See:
+// http://httpwg.org/specs/rfc7541.html#request.examples.without.huffman.coding
+TEST(HpackBlockBuilderTest, ExamplesFromSpecC3) {
+ {
+ // Header block to encode:
+ // :method: GET
+ // :scheme: http
+ // :path: /
+ // :authority: www.example.com
+ HpackBlockBuilder b;
+ b.AppendIndexedHeader(2); // :method: GET
+ b.AppendIndexedHeader(6); // :scheme: http
+ b.AppendIndexedHeader(4); // :path: /
+ b.AppendNameIndexAndLiteralValue(HpackEntryType::kIndexedLiteralHeader, 1,
+ kUncompressed, "www.example.com");
+ EXPECT_EQ(20u, b.size());
+
+ // Hex dump of encoded data (copied from RFC):
+ // 0x0000: 8286 8441 0f77 7777 2e65 7861 6d70 6c65 ...A.www.example
+ // 0x0010: 2e63 6f6d .com
+
+ const Http2String expected =
+ Http2HexDecode("828684410f7777772e6578616d706c652e636f6d");
+ EXPECT_EQ(expected, b.buffer());
+ }
+}
+
+// Tests of encoding per the RFC. See:
+// http://httpwg.org/specs/rfc7541.html#request.examples.with.huffman.coding
+TEST(HpackBlockBuilderTest, ExamplesFromSpecC4) {
+ {
+ // Header block to encode:
+ // :method: GET
+ // :scheme: http
+ // :path: /
+ // :authority: www.example.com (Huffman encoded)
+ HpackBlockBuilder b;
+ b.AppendIndexedHeader(kStaticTableMethodGET);
+ b.AppendIndexedHeader(kStaticTableSchemeHttp);
+ b.AppendIndexedHeader(kStaticTablePathSlash);
+ const char kHuffmanWwwExampleCom[] = {'\xf1', '\xe3', '\xc2', '\xe5',
+ '\xf2', '\x3a', '\x6b', '\xa0',
+ '\xab', '\x90', '\xf4', '\xff'};
+ b.AppendNameIndexAndLiteralValue(
+ HpackEntryType::kIndexedLiteralHeader, 1, kCompressed,
+ Http2StringPiece(kHuffmanWwwExampleCom, sizeof kHuffmanWwwExampleCom));
+ EXPECT_EQ(17u, b.size());
+
+ // Hex dump of encoded data (copied from RFC):
+ // 0x0000: 8286 8441 8cf1 e3c2 e5f2 3a6b a0ab 90f4 ...A......:k....
+ // 0x0010: ff .
+
+ const Http2String expected =
+ Http2HexDecode("828684418cf1e3c2e5f23a6ba0ab90f4ff");
+ EXPECT_EQ(expected, b.buffer());
+ }
+}
+
+TEST(HpackBlockBuilderTest, DynamicTableSizeUpdate) {
+ {
+ HpackBlockBuilder b;
+ b.AppendDynamicTableSizeUpdate(0);
+ EXPECT_EQ(1u, b.size());
+
+ const char kData[] = {'\x20'};
+ Http2StringPiece expected(kData, sizeof kData);
+ EXPECT_EQ(expected, b.buffer());
+ }
+ {
+ HpackBlockBuilder b;
+ b.AppendDynamicTableSizeUpdate(4096); // The default size.
+ EXPECT_EQ(3u, b.size());
+
+ const char kData[] = {'\x3f', '\xe1', '\x1f'};
+ Http2StringPiece expected(kData, sizeof kData);
+ EXPECT_EQ(expected, b.buffer());
+ }
+ {
+ HpackBlockBuilder b;
+ b.AppendDynamicTableSizeUpdate(1000000000000); // A very large value.
+ EXPECT_EQ(7u, b.size());
+
+ const char kData[] = {'\x3f', '\xe1', '\x9f', '\x94',
+ '\xa5', '\x8d', '\x1d'};
+ Http2StringPiece expected(kData, sizeof kData);
+ EXPECT_EQ(expected, b.buffer());
+ }
+}
+
+} // namespace
+} // namespace test
+} // namespace http2
diff --git a/chromium/net/third_party/quiche/src/http2/hpack/tools/hpack_example.cc b/chromium/net/third_party/quiche/src/http2/hpack/tools/hpack_example.cc
new file mode 100644
index 00000000000..d20eb354260
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/hpack/tools/hpack_example.cc
@@ -0,0 +1,58 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/http2/hpack/tools/hpack_example.h"
+
+#include <ctype.h>
+
+#include "base/logging.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_bug_tracker.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string_utils.h"
+
+namespace http2 {
+namespace test {
+namespace {
+
+void HpackExampleToStringOrDie(Http2StringPiece example, Http2String* output) {
+ while (!example.empty()) {
+ const char c0 = example[0];
+ if (isxdigit(c0)) {
+ CHECK_GT(example.size(), 1u) << "Truncated hex byte?";
+ const char c1 = example[1];
+ CHECK(isxdigit(c1)) << "Found half a byte?";
+ *output += Http2HexDecode(example.substr(0, 2));
+ example.remove_prefix(2);
+ continue;
+ }
+ if (isspace(c0)) {
+ example.remove_prefix(1);
+ continue;
+ }
+ if (!example.empty() && example[0] == '|') {
+ // Start of a comment. Skip to end of line or of input.
+ auto pos = example.find('\n');
+ if (pos == Http2StringPiece::npos) {
+ // End of input.
+ break;
+ }
+ example.remove_prefix(pos + 1);
+ continue;
+ }
+ HTTP2_BUG << "Can't parse byte " << static_cast<int>(c0)
+ << Http2StrCat(" (0x", Http2Hex(c0), ")")
+ << "\nExample: " << example;
+ }
+ CHECK_LT(0u, output->size()) << "Example is empty.";
+}
+
+} // namespace
+
+Http2String HpackExampleToStringOrDie(Http2StringPiece example) {
+ Http2String output;
+ HpackExampleToStringOrDie(example, &output);
+ return output;
+}
+
+} // namespace test
+} // namespace http2
diff --git a/chromium/net/third_party/quiche/src/http2/hpack/tools/hpack_example.h b/chromium/net/third_party/quiche/src/http2/hpack/tools/hpack_example.h
new file mode 100644
index 00000000000..ddb22a32534
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/hpack/tools/hpack_example.h
@@ -0,0 +1,31 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_HTTP2_HPACK_TOOLS_HPACK_EXAMPLE_H_
+#define QUICHE_HTTP2_HPACK_TOOLS_HPACK_EXAMPLE_H_
+
+#include "net/third_party/quiche/src/http2/platform/api/http2_string.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string_piece.h"
+
+// Parses HPACK examples in the format seen in the HPACK specification,
+// RFC 7541. For example:
+//
+// 10 | == Literal never indexed ==
+// 08 | Literal name (len = 8)
+// 7061 7373 776f 7264 | password
+// 06 | Literal value (len = 6)
+// 7365 6372 6574 | secret
+// | -> password: secret
+//
+// (excluding the leading "//").
+
+namespace http2 {
+namespace test {
+
+Http2String HpackExampleToStringOrDie(Http2StringPiece example);
+
+} // namespace test
+} // namespace http2
+
+#endif // QUICHE_HTTP2_HPACK_TOOLS_HPACK_EXAMPLE_H_
diff --git a/chromium/net/third_party/quiche/src/http2/hpack/varint/hpack_varint_decoder.cc b/chromium/net/third_party/quiche/src/http2/hpack/varint/hpack_varint_decoder.cc
new file mode 100644
index 00000000000..dd64025e506
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/hpack/varint/hpack_varint_decoder.cc
@@ -0,0 +1,172 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/http2/hpack/varint/hpack_varint_decoder.h"
+
+#include "net/third_party/quiche/src/http2/platform/api/http2_flag_utils.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string_utils.h"
+
+namespace http2 {
+
+DecodeStatus HpackVarintDecoder::Start(uint8_t prefix_value,
+ uint8_t prefix_length,
+ DecodeBuffer* db) {
+ DCHECK_LE(3u, prefix_length);
+ DCHECK_LE(prefix_length, 8u);
+
+ // |prefix_mask| defines the sequence of low-order bits of the first byte
+ // that encode the prefix of the value. It is also the marker in those bits
+ // of the first byte indicating that at least one extension byte is needed.
+ const uint8_t prefix_mask = (1 << prefix_length) - 1;
+
+ // Ignore the bits that aren't a part of the prefix of the varint.
+ value_ = prefix_value & prefix_mask;
+
+ if (value_ < prefix_mask) {
+ MarkDone();
+ return DecodeStatus::kDecodeDone;
+ }
+
+ offset_ = 0;
+ return Resume(db);
+}
+
+DecodeStatus HpackVarintDecoder::StartExtended(uint8_t prefix_length,
+ DecodeBuffer* db) {
+ DCHECK_LE(3u, prefix_length);
+ DCHECK_LE(prefix_length, 8u);
+
+ value_ = (1 << prefix_length) - 1;
+ offset_ = 0;
+ return Resume(db);
+}
+
+DecodeStatus HpackVarintDecoder::Resume(DecodeBuffer* db) {
+ if (decode_64_bits_) {
+ HTTP2_RELOADABLE_FLAG_COUNT(http2_varint_decode_64_bits);
+ // There can be at most 10 continuation bytes. Offset is zero for the
+ // first one and increases by 7 for each subsequent one.
+ const uint8_t kMaxOffset = 63;
+ CheckNotDone();
+
+ // Process most extension bytes without the need for overflow checking.
+ while (offset_ < kMaxOffset) {
+ if (db->Empty()) {
+ return DecodeStatus::kDecodeInProgress;
+ }
+
+ uint8_t byte = db->DecodeUInt8();
+ uint64_t summand = byte & 0x7f;
+
+ // Shifting a 7 bit value to the left by at most 56 places can never
+ // overflow on uint64_t.
+ DCHECK_LE(offset_, 56);
+ DCHECK_LE(summand, std::numeric_limits<uint64_t>::max() >> offset_);
+
+ summand <<= offset_;
+
+ // At this point,
+ // |value_| is at most (2^prefix_length - 1) + (2^49 - 1), and
+ // |summand| is at most 255 << 56 (which is smaller than 2^63),
+ // so adding them can never overflow on uint64_t.
+ DCHECK_LE(value_, std::numeric_limits<uint64_t>::max() - summand);
+
+ value_ += summand;
+
+ // Decoding ends if continuation flag is not set.
+ if ((byte & 0x80) == 0) {
+ MarkDone();
+ return DecodeStatus::kDecodeDone;
+ }
+
+ offset_ += 7;
+ }
+
+ if (db->Empty()) {
+ return DecodeStatus::kDecodeInProgress;
+ }
+
+ DCHECK_EQ(kMaxOffset, offset_);
+
+ uint8_t byte = db->DecodeUInt8();
+ // No more extension bytes are allowed after this.
+ if ((byte & 0x80) == 0) {
+ uint64_t summand = byte & 0x7f;
+ // Check for overflow in left shift.
+ if (summand <= std::numeric_limits<uint64_t>::max() >> offset_) {
+ summand <<= offset_;
+ // Check for overflow in addition.
+ if (value_ <= std::numeric_limits<uint64_t>::max() - summand) {
+ value_ += summand;
+ MarkDone();
+ return DecodeStatus::kDecodeDone;
+ }
+ }
+ }
+
+ // Signal error if value is too large or there are too many extension bytes.
+ DLOG(WARNING) << "Variable length int encoding is too large or too long. "
+ << DebugString();
+ MarkDone();
+ return DecodeStatus::kDecodeError;
+ }
+
+ // Old code path. TODO(bnc): remove.
+ DCHECK(!decode_64_bits_);
+ const uint8_t kMaxOffset = 28;
+ CheckNotDone();
+ do {
+ if (db->Empty()) {
+ return DecodeStatus::kDecodeInProgress;
+ }
+ uint8_t byte = db->DecodeUInt8();
+ if (offset_ == kMaxOffset && byte != 0)
+ break;
+ DCHECK(offset_ <= kMaxOffset - 7 || byte == 0);
+ // Shifting a 7 bit value to the left by at most 21 places can never
+ // overflow on uint32_t. Shifting 0 to the left cannot overflow either.
+ value_ += (byte & 0x7f) << offset_;
+ if ((byte & 0x80) == 0) {
+ MarkDone();
+ return DecodeStatus::kDecodeDone;
+ }
+ offset_ += 7;
+ } while (offset_ <= kMaxOffset);
+ DLOG(WARNING) << "Variable length int encoding is too large or too long. "
+ << DebugString();
+ MarkDone();
+ return DecodeStatus::kDecodeError;
+}
+
+uint64_t HpackVarintDecoder::value() const {
+ CheckDone();
+ return value_;
+}
+
+void HpackVarintDecoder::set_value(uint64_t v) {
+ MarkDone();
+ value_ = v;
+}
+
+Http2String HpackVarintDecoder::DebugString() const {
+ return Http2StrCat("HpackVarintDecoder(value=", value_, ", offset=", offset_,
+ ")");
+}
+
+DecodeStatus HpackVarintDecoder::StartForTest(uint8_t prefix_value,
+ uint8_t prefix_length,
+ DecodeBuffer* db) {
+ return Start(prefix_value, prefix_length, db);
+}
+
+DecodeStatus HpackVarintDecoder::StartExtendedForTest(uint8_t prefix_length,
+ DecodeBuffer* db) {
+ return StartExtended(prefix_length, db);
+}
+
+DecodeStatus HpackVarintDecoder::ResumeForTest(DecodeBuffer* db) {
+ return Resume(db);
+}
+
+} // namespace http2
diff --git a/chromium/net/third_party/quiche/src/http2/hpack/varint/hpack_varint_decoder.h b/chromium/net/third_party/quiche/src/http2/hpack/varint/hpack_varint_decoder.h
new file mode 100644
index 00000000000..c793500eaf6
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/hpack/varint/hpack_varint_decoder.h
@@ -0,0 +1,144 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// HpackVarintDecoder decodes HPACK variable length unsigned integers. In HPACK,
+// these integers are used to identify static or dynamic table index entries, to
+// specify string lengths, and to update the size limit of the dynamic table.
+// In QPACK, in addition to these uses, these integers also identify streams.
+//
+// The caller will need to validate that the decoded value is in an acceptable
+// range.
+//
+// For details of the encoding, see:
+// http://httpwg.org/specs/rfc7541.html#integer.representation
+//
+// If GetHttp2ReloadableFlag(http2_varint_decode_64_bits) is true, then this
+// decoder supports decoding any integer that can be represented on uint64_t,
+// thereby exceeding the requirements for QPACK: "QPACK implementations MUST be
+// able to decode integers up to 62 bits long." See
+// https://quicwg.org/base-drafts/draft-ietf-quic-qpack.html#rfc.section.5.1.1
+//
+// If GetHttp2ReloadableFlag(http2_varint_decode_64_bits) is false, then this
+// decoder supports decoding integers up to 2^28 + 2^prefix_length - 2.
+//
+// This decoder supports at most 10 extension bytes (bytes following the prefix,
+// also called continuation bytes) if
+// GetHttp2ReloadableFlag(http2_varint_decode_64_bits) is true, and at most 5
+// extension bytes if GetHttp2ReloadableFlag(http2_varint_decode_64_bits) is
+// false. An encoder is allowed to zero pad the encoded integer on the left,
+// thereby increasing the number of extension bytes. If an encoder uses so much
+// padding that the number of extension bytes exceeds the limit, then this
+// decoder signals an error.
+
+#ifndef QUICHE_HTTP2_HPACK_VARINT_HPACK_VARINT_DECODER_H_
+#define QUICHE_HTTP2_HPACK_VARINT_HPACK_VARINT_DECODER_H_
+
+#include <cstdint>
+#include <limits>
+
+#include "base/logging.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_status.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_export.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_flags.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string.h"
+
+namespace http2 {
+
+// Sentinel value for |HpackVarintDecoder::offset_| to signify that decoding is
+// completed. Only used in debug builds.
+#ifndef NDEBUG
+const uint8_t kHpackVarintDecoderOffsetDone =
+ std::numeric_limits<uint8_t>::max();
+#endif
+
+// Decodes an HPACK variable length unsigned integer, in a resumable fashion
+// so it can handle running out of input in the DecodeBuffer. Call Start or
+// StartExtended the first time (when decoding the byte that contains the
+// prefix), then call Resume later if it is necessary to resume. When done,
+// call value() to retrieve the decoded value.
+//
+// No constructor or destructor. Holds no resources, so destruction isn't
+// needed. Start and StartExtended handles the initialization of member
+// variables. This is necessary in order for HpackVarintDecoder to be part
+// of a union.
+class HTTP2_EXPORT_PRIVATE HpackVarintDecoder {
+ public:
+ // |prefix_value| is the first byte of the encoded varint.
+ // |prefix_length| is number of bits in the first byte that are used for
+ // encoding the integer. |db| is the rest of the buffer, that is, not
+ // including the first byte.
+ DecodeStatus Start(uint8_t prefix_value,
+ uint8_t prefix_length,
+ DecodeBuffer* db);
+
+ // The caller has already determined that the encoding requires multiple
+ // bytes, i.e. that the 3 to 8 low-order bits (the number determined by
+ // |prefix_length|) of the first byte are are all 1. |db| is the rest of the
+ // buffer, that is, not including the first byte.
+ DecodeStatus StartExtended(uint8_t prefix_length, DecodeBuffer* db);
+
+ // Resume decoding a variable length integer after an earlier
+ // call to Start or StartExtended returned kDecodeInProgress.
+ DecodeStatus Resume(DecodeBuffer* db);
+
+ uint64_t value() const;
+
+ // This supports optimizations for the case of a varint with zero extension
+ // bytes, where the handling of the prefix is done by the caller.
+ void set_value(uint64_t v);
+
+ // All the public methods below are for supporting assertions and tests.
+
+ Http2String DebugString() const;
+
+ // For benchmarking, these methods ensure the decoder
+ // is NOT inlined into the caller.
+ DecodeStatus StartForTest(uint8_t prefix_value,
+ uint8_t prefix_length,
+ DecodeBuffer* db);
+ DecodeStatus StartExtendedForTest(uint8_t prefix_length, DecodeBuffer* db);
+ DecodeStatus ResumeForTest(DecodeBuffer* db);
+
+ private:
+ // Protection in case Resume is called when it shouldn't be.
+ void MarkDone() {
+#ifndef NDEBUG
+ offset_ = kHpackVarintDecoderOffsetDone;
+#endif
+ }
+ void CheckNotDone() const {
+#ifndef NDEBUG
+ DCHECK_NE(kHpackVarintDecoderOffsetDone, offset_);
+#endif
+ }
+ void CheckDone() const {
+#ifndef NDEBUG
+ DCHECK_EQ(kHpackVarintDecoderOffsetDone, offset_);
+#endif
+ }
+
+ // If true, decode integers up to 2^64 - 1, and accept at most 10 extension
+ // bytes (some of which might be padding).
+ // If false, decode integers up to 2^28 + 2^prefix_length - 2, and accept at
+ // most 5 extension bytes (some of which might be padding).
+ bool decode_64_bits_ = GetHttp2ReloadableFlag(http2_varint_decode_64_bits);
+
+ // These fields are initialized just to keep ASAN happy about reading
+ // them from DebugString().
+
+ // The encoded integer is being accumulated in |value_|. When decoding is
+ // complete, |value_| holds the result.
+ uint64_t value_ = 0;
+
+ // Each extension byte encodes in its lowest 7 bits a segment of the integer.
+ // |offset_| is the number of places this segment has to be shifted to the
+ // left for decoding. It is zero for the first extension byte, and increases
+ // by 7 for each subsequent extension byte.
+ uint8_t offset_ = 0;
+};
+
+} // namespace http2
+
+#endif // QUICHE_HTTP2_HPACK_VARINT_HPACK_VARINT_DECODER_H_
diff --git a/chromium/net/third_party/quiche/src/http2/hpack/varint/hpack_varint_decoder_test.cc b/chromium/net/third_party/quiche/src/http2/hpack/varint/hpack_varint_decoder_test.cc
new file mode 100644
index 00000000000..e36ad07949d
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/hpack/varint/hpack_varint_decoder_test.cc
@@ -0,0 +1,486 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/http2/hpack/varint/hpack_varint_decoder.h"
+
+// Test HpackVarintDecoder against hardcoded data.
+
+#include <stddef.h>
+
+#include "base/logging.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_arraysize.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string_piece.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string_utils.h"
+#include "net/third_party/quiche/src/http2/tools/random_decoder_test.h"
+
+using ::testing::AssertionFailure;
+using ::testing::AssertionSuccess;
+
+namespace http2 {
+namespace test {
+namespace {
+
+// Save previous value of flag and restore on destruction.
+class FlagSaver {
+ public:
+ FlagSaver() = delete;
+ explicit FlagSaver(bool decode_64_bits)
+ : saved_value_(GetHttp2ReloadableFlag(http2_varint_decode_64_bits)) {
+ SetHttp2ReloadableFlag(http2_varint_decode_64_bits, decode_64_bits);
+ }
+ ~FlagSaver() {
+ SetHttp2ReloadableFlag(http2_varint_decode_64_bits, saved_value_);
+ }
+
+ private:
+ const bool saved_value_;
+};
+
+class HpackVarintDecoderTest
+ : public RandomDecoderTest,
+ public ::testing::WithParamInterface<
+ ::testing::tuple<bool, uint8_t, const char*>> {
+ protected:
+ HpackVarintDecoderTest()
+ : decode_64_bits_(::testing::get<0>(GetParam())),
+ high_bits_(::testing::get<1>(GetParam())),
+ suffix_(Http2HexDecode(::testing::get<2>(GetParam()))),
+ flag_saver_(decode_64_bits_),
+ prefix_length_(0) {}
+
+ void DecodeExpectSuccess(Http2StringPiece data,
+ uint32_t prefix_length,
+ uint64_t expected_value) {
+ Validator validator = [expected_value, this](
+ const DecodeBuffer& db,
+ DecodeStatus status) -> AssertionResult {
+ VERIFY_EQ(expected_value, decoder_.value())
+ << "Value doesn't match expected: " << decoder_.value()
+ << " != " << expected_value;
+ return AssertionSuccess();
+ };
+
+ // First validate that decoding is done and that we've advanced the cursor
+ // the expected amount.
+ validator = ValidateDoneAndOffset(/* offset = */ data.size(), validator);
+
+ EXPECT_TRUE(Decode(data, prefix_length, validator));
+
+ EXPECT_EQ(expected_value, decoder_.value());
+ }
+
+ void DecodeExpectError(Http2StringPiece data, uint32_t prefix_length) {
+ Validator validator = [](const DecodeBuffer& db,
+ DecodeStatus status) -> AssertionResult {
+ VERIFY_EQ(DecodeStatus::kDecodeError, status);
+ return AssertionSuccess();
+ };
+
+ EXPECT_TRUE(Decode(data, prefix_length, validator));
+ }
+
+ bool decode_64_bits() const { return decode_64_bits_; }
+
+ private:
+ AssertionResult Decode(Http2StringPiece data,
+ uint32_t prefix_length,
+ const Validator validator) {
+ prefix_length_ = prefix_length;
+
+ // Copy |data| so that it can be modified.
+ Http2String data_copy(data);
+
+ // Bits of the first byte not part of the prefix should be ignored.
+ uint8_t high_bits_mask = 0b11111111 << prefix_length_;
+ data_copy[0] |= (high_bits_mask & high_bits_);
+
+ // Extra bytes appended to the input should be ignored.
+ data_copy.append(suffix_);
+
+ DecodeBuffer b(data_copy);
+
+ // StartDecoding, above, requires the DecodeBuffer be non-empty so that it
+ // can call Start with the prefix byte.
+ bool return_non_zero_on_first = true;
+
+ return DecodeAndValidateSeveralWays(&b, return_non_zero_on_first,
+ validator);
+ }
+
+ DecodeStatus StartDecoding(DecodeBuffer* b) override {
+ CHECK_LT(0u, b->Remaining());
+ uint8_t prefix = b->DecodeUInt8();
+ return decoder_.Start(prefix, prefix_length_, b);
+ }
+
+ DecodeStatus ResumeDecoding(DecodeBuffer* b) override {
+ return decoder_.Resume(b);
+ }
+
+ // Test new or old behavior.
+ const bool decode_64_bits_;
+ // Bits of the first byte not part of the prefix.
+ const uint8_t high_bits_;
+ // Extra bytes appended to the input.
+ const Http2String suffix_;
+
+ // |flag_saver_| must preceed |decoder_| so that the flag is already set when
+ // |decoder_| is constructed.
+ FlagSaver flag_saver_;
+ HpackVarintDecoder decoder_;
+ uint8_t prefix_length_;
+};
+
+INSTANTIATE_TEST_CASE_P(
+ HpackVarintDecoderTest,
+ HpackVarintDecoderTest,
+ ::testing::Combine(
+ // Test both the new version (supporting 64 bit integers) and the old
+ // one (only supporting up to 2^28 + 2^prefix_length - 2.
+ ::testing::Bool(),
+ // Bits of the first byte not part of the prefix should be ignored.
+ ::testing::Values(0b00000000, 0b11111111, 0b10101010),
+ // Extra bytes appended to the input should be ignored.
+ ::testing::Values("", "00", "666f6f")));
+
+// Test data used when decode_64_bits() == true.
+struct {
+ const char* data;
+ uint32_t prefix_length;
+ uint64_t expected_value;
+} kSuccessTestData[] = {
+ // Zero value with different prefix lengths.
+ {"00", 3, 0},
+ {"00", 4, 0},
+ {"00", 5, 0},
+ {"00", 6, 0},
+ {"00", 7, 0},
+ {"00", 8, 0},
+ // Small values that fit in the prefix.
+ {"06", 3, 6},
+ {"0d", 4, 13},
+ {"10", 5, 16},
+ {"29", 6, 41},
+ {"56", 7, 86},
+ {"bf", 8, 191},
+ // Values of 2^n-1, which have an all-zero extension byte.
+ {"0700", 3, 7},
+ {"0f00", 4, 15},
+ {"1f00", 5, 31},
+ {"3f00", 6, 63},
+ {"7f00", 7, 127},
+ {"ff00", 8, 255},
+ // Values of 2^n-1, plus one extra byte of padding.
+ {"078000", 3, 7},
+ {"0f8000", 4, 15},
+ {"1f8000", 5, 31},
+ {"3f8000", 6, 63},
+ {"7f8000", 7, 127},
+ {"ff8000", 8, 255},
+ // Values requiring one extension byte.
+ {"0760", 3, 103},
+ {"0f2a", 4, 57},
+ {"1f7f", 5, 158},
+ {"3f02", 6, 65},
+ {"7f49", 7, 200},
+ {"ff6f", 8, 366},
+ // Values requiring one extension byte, plus one byte of padding.
+ {"07e000", 3, 103},
+ {"0faa00", 4, 57},
+ {"1fff00", 5, 158},
+ {"3f8200", 6, 65},
+ {"7fc900", 7, 200},
+ {"ffef00", 8, 366},
+ // Values requiring one extension byte, plus two bytes of padding.
+ {"07e08000", 3, 103},
+ {"0faa8000", 4, 57},
+ {"1fff8000", 5, 158},
+ {"3f828000", 6, 65},
+ {"7fc98000", 7, 200},
+ {"ffef8000", 8, 366},
+ // Values requiring one extension byte, plus the maximum amount of padding.
+ {"07e0808080808080808000", 3, 103},
+ {"0faa808080808080808000", 4, 57},
+ {"1fff808080808080808000", 5, 158},
+ {"3f82808080808080808000", 6, 65},
+ {"7fc9808080808080808000", 7, 200},
+ {"ffef808080808080808000", 8, 366},
+ // Values requiring two extension bytes.
+ {"07b260", 3, 12345},
+ {"0f8a2a", 4, 5401},
+ {"1fa87f", 5, 16327},
+ {"3fd002", 6, 399},
+ {"7fff49", 7, 9598},
+ {"ffe32f", 8, 6370},
+ // Values requiring two extension bytes, plus one byte of padding.
+ {"07b2e000", 3, 12345},
+ {"0f8aaa00", 4, 5401},
+ {"1fa8ff00", 5, 16327},
+ {"3fd08200", 6, 399},
+ {"7fffc900", 7, 9598},
+ {"ffe3af00", 8, 6370},
+ // Values requiring two extension bytes, plus the maximum amount of padding.
+ {"07b2e080808080808000", 3, 12345},
+ {"0f8aaa80808080808000", 4, 5401},
+ {"1fa8ff80808080808000", 5, 16327},
+ {"3fd08280808080808000", 6, 399},
+ {"7fffc980808080808000", 7, 9598},
+ {"ffe3af80808080808000", 8, 6370},
+ // Values requiring three extension bytes.
+ {"078ab260", 3, 1579281},
+ {"0fc18a2a", 4, 689488},
+ {"1fada87f", 5, 2085964},
+ {"3fa0d002", 6, 43103},
+ {"7ffeff49", 7, 1212541},
+ {"ff93de23", 8, 585746},
+ // Values requiring three extension bytes, plus one byte of padding.
+ {"078ab2e000", 3, 1579281},
+ {"0fc18aaa00", 4, 689488},
+ {"1fada8ff00", 5, 2085964},
+ {"3fa0d08200", 6, 43103},
+ {"7ffeffc900", 7, 1212541},
+ {"ff93dea300", 8, 585746},
+ // Values requiring four extension bytes.
+ {"079f8ab260", 3, 202147110},
+ {"0fa2c18a2a", 4, 88252593},
+ {"1fd0ada87f", 5, 266999535},
+ {"3ff9a0d002", 6, 5509304},
+ {"7f9efeff49", 7, 155189149},
+ {"ffaa82f404", 8, 10289705},
+ // Values requiring four extension bytes, plus one byte of padding.
+ {"079f8ab2e000", 3, 202147110},
+ {"0fa2c18aaa00", 4, 88252593},
+ {"1fd0ada8ff00", 5, 266999535},
+ {"3ff9a0d08200", 6, 5509304},
+ {"7f9efeffc900", 7, 155189149},
+ {"ffaa82f48400", 8, 10289705},
+ // Values requiring six extension bytes.
+ {"0783aa9f8ab260", 3, 3311978140938},
+ {"0ff0b0a2c18a2a", 4, 1445930244223},
+ {"1fda84d0ada87f", 5, 4374519874169},
+ {"3fb5fbf9a0d002", 6, 90263420404},
+ {"7fcff19efeff49", 7, 2542616951118},
+ {"ff9fa486bbc327", 8, 1358138807070},
+ // Values requiring eight extension bytes.
+ {"07f19883aa9f8ab260", 3, 54263449861016696},
+ {"0f84fdf0b0a2c18a2a", 4, 23690121121119891},
+ {"1fa0dfda84d0ada87f", 5, 71672133617889215},
+ {"3f9ff0b5fbf9a0d002", 6, 1478875878881374},
+ {"7ffbc1cff19efeff49", 7, 41658236125045114},
+ {"ff91b6fb85af99c342", 8, 37450237664484368},
+ // Values requiring ten extension bytes.
+ {"0794f1f19883aa9f8ab201", 3, 12832019021693745307u},
+ {"0fa08f84fdf0b0a2c18a01", 4, 9980690937382242223u},
+ {"1fbfdda0dfda84d0ada801", 5, 12131360551794650846u},
+ {"3f9dc79ff0b5fbf9a0d001", 6, 15006530362736632796u},
+ {"7f8790fbc1cff19efeff01", 7, 18445754019193211014u},
+ {"fffba8c5b8d3fe9f8c8401", 8, 9518498503615141242u},
+ // Maximum value: 2^64-1.
+ {"07f8ffffffffffffffff01", 3, 18446744073709551615u},
+ {"0ff0ffffffffffffffff01", 4, 18446744073709551615u},
+ {"1fe0ffffffffffffffff01", 5, 18446744073709551615u},
+ {"3fc0ffffffffffffffff01", 6, 18446744073709551615u},
+ {"7f80ffffffffffffffff01", 7, 18446744073709551615u},
+ {"ff80feffffffffffffff01", 8, 18446744073709551615u},
+ // Examples from RFC7541 C.1.
+ {"0a", 5, 10},
+ {"1f9a0a", 5, 1337},
+};
+
+// Test data used when decode_64_bits() == false.
+struct {
+ const char* data;
+ uint32_t prefix_length;
+ uint64_t expected_value;
+} kSuccessTestDataOld[] = {
+ // Zero value with different prefix lengths.
+ {"00", 3, 0},
+ {"00", 4, 0},
+ {"00", 5, 0},
+ {"00", 6, 0},
+ {"00", 7, 0},
+ // Small values that fit in the prefix.
+ {"06", 3, 6},
+ {"0d", 4, 13},
+ {"10", 5, 16},
+ {"29", 6, 41},
+ {"56", 7, 86},
+ // Values of 2^n-1, which have an all-zero extension byte.
+ {"0700", 3, 7},
+ {"0f00", 4, 15},
+ {"1f00", 5, 31},
+ {"3f00", 6, 63},
+ {"7f00", 7, 127},
+ // Values of 2^n-1, plus one extra byte of padding.
+ {"078000", 3, 7},
+ {"0f8000", 4, 15},
+ {"1f8000", 5, 31},
+ {"3f8000", 6, 63},
+ {"7f8000", 7, 127},
+ // Values requiring one extension byte.
+ {"0760", 3, 103},
+ {"0f2a", 4, 57},
+ {"1f7f", 5, 158},
+ {"3f02", 6, 65},
+ {"7f49", 7, 200},
+ // Values requiring one extension byte, plus one byte of padding.
+ {"07e000", 3, 103},
+ {"0faa00", 4, 57},
+ {"1fff00", 5, 158},
+ {"3f8200", 6, 65},
+ {"7fc900", 7, 200},
+ // Values requiring one extension byte, plus two bytes of padding.
+ {"07e08000", 3, 103},
+ {"0faa8000", 4, 57},
+ {"1fff8000", 5, 158},
+ {"3f828000", 6, 65},
+ {"7fc98000", 7, 200},
+ // Values requiring one extension byte, plus the maximum amount of padding.
+ {"07e080808000", 3, 103},
+ {"0faa80808000", 4, 57},
+ {"1fff80808000", 5, 158},
+ {"3f8280808000", 6, 65},
+ {"7fc980808000", 7, 200},
+ // Values requiring two extension bytes.
+ {"07b260", 3, 12345},
+ {"0f8a2a", 4, 5401},
+ {"1fa87f", 5, 16327},
+ {"3fd002", 6, 399},
+ {"7fff49", 7, 9598},
+ // Values requiring two extension bytes, plus one byte of padding.
+ {"07b2e000", 3, 12345},
+ {"0f8aaa00", 4, 5401},
+ {"1fa8ff00", 5, 16327},
+ {"3fd08200", 6, 399},
+ {"7fffc900", 7, 9598},
+ // Values requiring two extension bytes, plus the maximum amount of padding.
+ {"07b2e0808000", 3, 12345},
+ {"0f8aaa808000", 4, 5401},
+ {"1fa8ff808000", 5, 16327},
+ {"3fd082808000", 6, 399},
+ {"7fffc9808000", 7, 9598},
+ // Values requiring three extension bytes.
+ {"078ab260", 3, 1579281},
+ {"0fc18a2a", 4, 689488},
+ {"1fada87f", 5, 2085964},
+ {"3fa0d002", 6, 43103},
+ {"7ffeff49", 7, 1212541},
+ // Values requiring three extension bytes, plus one byte of padding.
+ {"078ab2e000", 3, 1579281},
+ {"0fc18aaa00", 4, 689488},
+ {"1fada8ff00", 5, 2085964},
+ {"3fa0d08200", 6, 43103},
+ {"7ffeffc900", 7, 1212541},
+ // Values requiring four extension bytes.
+ {"079f8ab260", 3, 202147110},
+ {"0fa2c18a2a", 4, 88252593},
+ {"1fd0ada87f", 5, 266999535},
+ {"3ff9a0d002", 6, 5509304},
+ {"7f9efeff49", 7, 155189149},
+ // Values requiring four extension bytes, plus one byte of padding.
+ {"079f8ab2e000", 3, 202147110},
+ {"0fa2c18aaa00", 4, 88252593},
+ {"1fd0ada8ff00", 5, 266999535},
+ {"3ff9a0d08200", 6, 5509304},
+ {"7f9efeffc900", 7, 155189149},
+ // Examples from RFC7541 C.1.
+ {"0a", 5, 10},
+ {"1f9a0a", 5, 1337},
+};
+
+TEST_P(HpackVarintDecoderTest, Success) {
+ if (decode_64_bits()) {
+ for (size_t i = 0; i < HTTP2_ARRAYSIZE(kSuccessTestData); ++i) {
+ DecodeExpectSuccess(Http2HexDecode(kSuccessTestData[i].data),
+ kSuccessTestData[i].prefix_length,
+ kSuccessTestData[i].expected_value);
+ }
+ } else {
+ for (size_t i = 0; i < HTTP2_ARRAYSIZE(kSuccessTestDataOld); ++i) {
+ DecodeExpectSuccess(Http2HexDecode(kSuccessTestDataOld[i].data),
+ kSuccessTestDataOld[i].prefix_length,
+ kSuccessTestDataOld[i].expected_value);
+ }
+ }
+}
+
+// Test data used when decode_64_bits() == true.
+struct {
+ const char* data;
+ uint32_t prefix_length;
+} kErrorTestData[] = {
+ // Too many extension bytes, all 0s (except for extension bit in each byte).
+ {"0780808080808080808080", 3},
+ {"0f80808080808080808080", 4},
+ {"1f80808080808080808080", 5},
+ {"3f80808080808080808080", 6},
+ {"7f80808080808080808080", 7},
+ {"ff80808080808080808080", 8},
+ // Too many extension bytes, all 1s.
+ {"07ffffffffffffffffffff", 3},
+ {"0fffffffffffffffffffff", 4},
+ {"1fffffffffffffffffffff", 5},
+ {"3fffffffffffffffffffff", 6},
+ {"7fffffffffffffffffffff", 7},
+ {"ffffffffffffffffffffff", 8},
+ // Value of 2^64, one higher than maximum of 2^64-1.
+ {"07f9ffffffffffffffff01", 3},
+ {"0ff1ffffffffffffffff01", 4},
+ {"1fe1ffffffffffffffff01", 5},
+ {"3fc1ffffffffffffffff01", 6},
+ {"7f81ffffffffffffffff01", 7},
+ {"ff81feffffffffffffff01", 8},
+ // Maximum value: 2^64-1, with one byte of padding.
+ {"07f8ffffffffffffffff8100", 3},
+ {"0ff0ffffffffffffffff8100", 4},
+ {"1fe0ffffffffffffffff8100", 5},
+ {"3fc0ffffffffffffffff8100", 6},
+ {"7f80ffffffffffffffff8100", 7},
+ {"ff80feffffffffffffff8100", 8}};
+
+// Test data used when decode_64_bits() == false.
+// In this mode, HpackVarintDecoder allows at most five extension bytes,
+// and fifth extension byte must be zero.
+struct {
+ const char* data;
+ uint32_t prefix_length;
+} kErrorTestDataOld[] = {
+ // Maximum number of extension bytes but last byte is non-zero.
+ {"078080808001", 3},
+ {"0f8080808001", 4},
+ {"1f8080808001", 5},
+ {"3f8080808001", 6},
+ {"7f8080808001", 7},
+ // Too many extension bytes, all 0s (except for extension bit in each byte).
+ {"078080808080", 3},
+ {"0f8080808080", 4},
+ {"1f8080808080", 5},
+ {"3f8080808080", 6},
+ {"7f8080808080", 7},
+ // Too many extension bytes, all 1s.
+ {"07ffffffffff", 3},
+ {"0fffffffffff", 4},
+ {"1fffffffffff", 5},
+ {"3fffffffffff", 6},
+ {"7fffffffffff", 7},
+};
+
+TEST_P(HpackVarintDecoderTest, Error) {
+ if (decode_64_bits()) {
+ for (size_t i = 0; i < HTTP2_ARRAYSIZE(kErrorTestData); ++i) {
+ DecodeExpectError(Http2HexDecode(kErrorTestData[i].data),
+ kErrorTestData[i].prefix_length);
+ }
+ } else {
+ for (size_t i = 0; i < HTTP2_ARRAYSIZE(kErrorTestDataOld); ++i) {
+ DecodeExpectError(Http2HexDecode(kErrorTestDataOld[i].data),
+ kErrorTestDataOld[i].prefix_length);
+ }
+ }
+}
+
+} // namespace
+} // namespace test
+} // namespace http2
diff --git a/chromium/net/third_party/quiche/src/http2/hpack/varint/hpack_varint_encoder.cc b/chromium/net/third_party/quiche/src/http2/hpack/varint/hpack_varint_encoder.cc
new file mode 100644
index 00000000000..d3685d988cd
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/hpack/varint/hpack_varint_encoder.cc
@@ -0,0 +1,65 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/http2/hpack/varint/hpack_varint_encoder.h"
+
+#include "base/logging.h"
+
+namespace http2 {
+
+HpackVarintEncoder::HpackVarintEncoder()
+ : varint_(0), encoding_in_progress_(false) {}
+
+unsigned char HpackVarintEncoder::StartEncoding(uint8_t high_bits,
+ uint8_t prefix_length,
+ uint64_t varint) {
+ DCHECK(!encoding_in_progress_);
+ DCHECK_EQ(0u, varint_);
+ DCHECK_LE(1u, prefix_length);
+ DCHECK_LE(prefix_length, 8u);
+
+ // prefix_mask defines the sequence of low-order bits of the first byte
+ // that encode the prefix of the value. It is also the marker in those bits
+ // of the first byte indicating that at least one extension byte is needed.
+ const uint8_t prefix_mask = (1 << prefix_length) - 1;
+ DCHECK_EQ(0, high_bits & prefix_mask);
+
+ if (varint < prefix_mask) {
+ // The integer fits into the prefix in its entirety.
+ return high_bits | static_cast<unsigned char>(varint);
+ }
+
+ // We need extension bytes.
+ varint_ = varint - prefix_mask;
+ encoding_in_progress_ = true;
+ return high_bits | prefix_mask;
+}
+
+size_t HpackVarintEncoder::ResumeEncoding(size_t max_encoded_bytes,
+ Http2String* output) {
+ DCHECK(encoding_in_progress_);
+ DCHECK_NE(0u, max_encoded_bytes);
+
+ size_t encoded_bytes = 0;
+ while (encoded_bytes < max_encoded_bytes) {
+ ++encoded_bytes;
+ if (varint_ < 128) {
+ // Encode final seven bits, with continuation bit set to zero.
+ output->push_back(varint_);
+ varint_ = 0;
+ encoding_in_progress_ = false;
+ break;
+ }
+ // Encode the next seven bits, with continuation bit set to one.
+ output->push_back(0b10000000 | (varint_ % 128));
+ varint_ >>= 7;
+ }
+ return encoded_bytes;
+}
+
+bool HpackVarintEncoder::IsEncodingInProgress() const {
+ return encoding_in_progress_;
+}
+
+} // namespace http2
diff --git a/chromium/net/third_party/quiche/src/http2/hpack/varint/hpack_varint_encoder.h b/chromium/net/third_party/quiche/src/http2/hpack/varint/hpack_varint_encoder.h
new file mode 100644
index 00000000000..68f7474817f
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/hpack/varint/hpack_varint_encoder.h
@@ -0,0 +1,51 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_HTTP2_HPACK_VARINT_HPACK_VARINT_ENCODER_H_
+#define QUICHE_HTTP2_HPACK_VARINT_HPACK_VARINT_ENCODER_H_
+
+#include <cstdint>
+
+#include "net/third_party/quiche/src/http2/platform/api/http2_export.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string.h"
+
+namespace http2 {
+
+// HPACK integer encoder class implementing variable length integer
+// representation defined in RFC7541, Section 5.1:
+// https://httpwg.org/specs/rfc7541.html#integer.representation
+class HTTP2_EXPORT_PRIVATE HpackVarintEncoder {
+ public:
+ HpackVarintEncoder();
+
+ // Start encoding an integer. Return the first encoded byte (composed of
+ // optional high bits and 1 to 8 bit long prefix). It is possible that this
+ // completes the encoding. Must not be called when previously started
+ // encoding is still in progress.
+ unsigned char StartEncoding(uint8_t high_bits,
+ uint8_t prefix_length,
+ uint64_t varint);
+
+ // Continue encoding the integer |varint| passed in to StartEncoding().
+ // Append the next at most |max_encoded_bytes| encoded octets to |output|.
+ // Returns the number of encoded octets. Must not be called unless a
+ // previously started encoding is still in progress.
+ size_t ResumeEncoding(size_t max_encoded_bytes, Http2String* output);
+
+ // Returns true if encoding an integer has started and is not completed yet.
+ bool IsEncodingInProgress() const;
+
+ private:
+ // The original integer shifted to the right by the number of bits already
+ // encoded. The lower bits shifted away have already been encoded, and
+ // |varint_| has the higher bits that remain to be encoded.
+ uint64_t varint_;
+
+ // True when encoding an integer has started and is not completed yet.
+ bool encoding_in_progress_;
+};
+
+} // namespace http2
+
+#endif // QUICHE_HTTP2_HPACK_VARINT_HPACK_VARINT_ENCODER_H_
diff --git a/chromium/net/third_party/quiche/src/http2/hpack/varint/hpack_varint_encoder_test.cc b/chromium/net/third_party/quiche/src/http2/hpack/varint/hpack_varint_encoder_test.cc
new file mode 100644
index 00000000000..a6043a0dd2c
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/hpack/varint/hpack_varint_encoder_test.cc
@@ -0,0 +1,178 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/http2/hpack/varint/hpack_varint_encoder.h"
+
+#include "net/third_party/quiche/src/http2/platform/api/http2_arraysize.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string_utils.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace http2 {
+namespace test {
+namespace {
+
+// Freshly constructed encoder is not in the process of encoding.
+TEST(HpackVarintEncoderTest, Done) {
+ HpackVarintEncoder varint_encoder;
+ EXPECT_FALSE(varint_encoder.IsEncodingInProgress());
+}
+
+struct {
+ uint8_t high_bits;
+ uint8_t prefix_length;
+ uint64_t value;
+ uint8_t expected_encoding;
+} kShortTestData[] = {{0b10110010, 1, 0, 0b10110010},
+ {0b10101100, 2, 2, 0b10101110},
+ {0b10100000, 3, 6, 0b10100110},
+ {0b10110000, 4, 13, 0b10111101},
+ {0b10100000, 5, 8, 0b10101000},
+ {0b11000000, 6, 48, 0b11110000},
+ {0b10000000, 7, 99, 0b11100011},
+ // Example from RFC7541 C.1.
+ {0b00000000, 5, 10, 0b00001010}};
+
+// Encode integers that fit in the prefix.
+TEST(HpackVarintEncoderTest, Short) {
+ HpackVarintEncoder varint_encoder;
+
+ for (size_t i = 0; i < HTTP2_ARRAYSIZE(kShortTestData); ++i) {
+ EXPECT_EQ(kShortTestData[i].expected_encoding,
+ varint_encoder.StartEncoding(kShortTestData[i].high_bits,
+ kShortTestData[i].prefix_length,
+ kShortTestData[i].value));
+ EXPECT_FALSE(varint_encoder.IsEncodingInProgress());
+ }
+}
+
+struct {
+ uint8_t high_bits;
+ uint8_t prefix_length;
+ uint64_t value;
+ const char* expected_encoding;
+} kLongTestData[] = {
+ // One extension byte.
+ {0b10011000, 3, 103, "9f60"},
+ {0b10010000, 4, 57, "9f2a"},
+ {0b11000000, 5, 158, "df7f"},
+ {0b01000000, 6, 65, "7f02"},
+ {0b00000000, 7, 200, "7f49"},
+ // Two extension bytes.
+ {0b10011000, 3, 12345, "9fb260"},
+ {0b10010000, 4, 5401, "9f8a2a"},
+ {0b11000000, 5, 16327, "dfa87f"},
+ {0b01000000, 6, 399, "7fd002"},
+ {0b00000000, 7, 9598, "7fff49"},
+ // Three extension bytes.
+ {0b10011000, 3, 1579281, "9f8ab260"},
+ {0b10010000, 4, 689488, "9fc18a2a"},
+ {0b11000000, 5, 2085964, "dfada87f"},
+ {0b01000000, 6, 43103, "7fa0d002"},
+ {0b00000000, 7, 1212541, "7ffeff49"},
+ // Four extension bytes.
+ {0b10011000, 3, 202147110, "9f9f8ab260"},
+ {0b10010000, 4, 88252593, "9fa2c18a2a"},
+ {0b11000000, 5, 266999535, "dfd0ada87f"},
+ {0b01000000, 6, 5509304, "7ff9a0d002"},
+ {0b00000000, 7, 155189149, "7f9efeff49"},
+ // Six extension bytes.
+ {0b10011000, 3, 3311978140938, "9f83aa9f8ab260"},
+ {0b10010000, 4, 1445930244223, "9ff0b0a2c18a2a"},
+ {0b11000000, 5, 4374519874169, "dfda84d0ada87f"},
+ {0b01000000, 6, 90263420404, "7fb5fbf9a0d002"},
+ {0b00000000, 7, 2542616951118, "7fcff19efeff49"},
+ // Eight extension bytes.
+ {0b10011000, 3, 54263449861016696, "9ff19883aa9f8ab260"},
+ {0b10010000, 4, 23690121121119891, "9f84fdf0b0a2c18a2a"},
+ {0b11000000, 5, 71672133617889215, "dfa0dfda84d0ada87f"},
+ {0b01000000, 6, 1478875878881374, "7f9ff0b5fbf9a0d002"},
+ {0b00000000, 7, 41658236125045114, "7ffbc1cff19efeff49"},
+ // Ten extension bytes.
+ {0b10011000, 3, 12832019021693745307u, "9f94f1f19883aa9f8ab201"},
+ {0b10010000, 4, 9980690937382242223u, "9fa08f84fdf0b0a2c18a01"},
+ {0b11000000, 5, 12131360551794650846u, "dfbfdda0dfda84d0ada801"},
+ {0b01000000, 6, 15006530362736632796u, "7f9dc79ff0b5fbf9a0d001"},
+ {0b00000000, 7, 18445754019193211014u, "7f8790fbc1cff19efeff01"},
+ // Maximum value: 2^64-1.
+ {0b10011000, 3, 18446744073709551615u, "9ff8ffffffffffffffff01"},
+ {0b10010000, 4, 18446744073709551615u, "9ff0ffffffffffffffff01"},
+ {0b11000000, 5, 18446744073709551615u, "dfe0ffffffffffffffff01"},
+ {0b01000000, 6, 18446744073709551615u, "7fc0ffffffffffffffff01"},
+ {0b00000000, 7, 18446744073709551615u, "7f80ffffffffffffffff01"},
+ // Example from RFC7541 C.1.
+ {0b00000000, 5, 1337, "1f9a0a"},
+};
+
+// Encode integers that do not fit in the prefix.
+TEST(HpackVarintEncoderTest, Long) {
+ HpackVarintEncoder varint_encoder;
+
+ // Test encoding byte by byte, also test encoding in
+ // a single ResumeEncoding() call.
+ for (bool byte_by_byte : {true, false}) {
+ for (size_t i = 0; i < HTTP2_ARRAYSIZE(kLongTestData); ++i) {
+ Http2String expected_encoding =
+ Http2HexDecode(kLongTestData[i].expected_encoding);
+ ASSERT_FALSE(expected_encoding.empty());
+
+ EXPECT_EQ(static_cast<unsigned char>(expected_encoding[0]),
+ varint_encoder.StartEncoding(kLongTestData[i].high_bits,
+ kLongTestData[i].prefix_length,
+ kLongTestData[i].value));
+ EXPECT_TRUE(varint_encoder.IsEncodingInProgress());
+
+ Http2String output;
+ if (byte_by_byte) {
+ while (varint_encoder.IsEncodingInProgress()) {
+ EXPECT_EQ(1u, varint_encoder.ResumeEncoding(1, &output));
+ }
+ } else {
+ // TODO(bnc): Factor out maximum number of extension bytes into a
+ // constant in HpackVarintEncoder.
+ EXPECT_EQ(expected_encoding.size() - 1,
+ varint_encoder.ResumeEncoding(10, &output));
+ EXPECT_FALSE(varint_encoder.IsEncodingInProgress());
+ }
+ EXPECT_EQ(expected_encoding.size() - 1, output.size());
+ EXPECT_EQ(expected_encoding.substr(1), output);
+ }
+ }
+}
+
+struct {
+ uint8_t high_bits;
+ uint8_t prefix_length;
+ uint64_t value;
+ uint8_t expected_encoding_first_byte;
+} kLastByteIsZeroTestData[] = {
+ {0b10110010, 1, 1, 0b10110011}, {0b10101100, 2, 3, 0b10101111},
+ {0b10101000, 3, 7, 0b10101111}, {0b10110000, 4, 15, 0b10111111},
+ {0b10100000, 5, 31, 0b10111111}, {0b11000000, 6, 63, 0b11111111},
+ {0b10000000, 7, 127, 0b11111111}, {0b00000000, 8, 255, 0b11111111}};
+
+// Make sure that the encoder outputs the last byte even when it is zero. This
+// happens exactly when encoding the value 2^prefix_length - 1.
+TEST(HpackVarintEncoderTest, LastByteIsZero) {
+ HpackVarintEncoder varint_encoder;
+
+ for (size_t i = 0; i < HTTP2_ARRAYSIZE(kLastByteIsZeroTestData); ++i) {
+ EXPECT_EQ(
+ kLastByteIsZeroTestData[i].expected_encoding_first_byte,
+ varint_encoder.StartEncoding(kLastByteIsZeroTestData[i].high_bits,
+ kLastByteIsZeroTestData[i].prefix_length,
+ kLastByteIsZeroTestData[i].value));
+ EXPECT_TRUE(varint_encoder.IsEncodingInProgress());
+
+ Http2String output;
+ EXPECT_EQ(1u, varint_encoder.ResumeEncoding(1, &output));
+ ASSERT_EQ(1u, output.size());
+ EXPECT_EQ(0b00000000, output[0]);
+ EXPECT_FALSE(varint_encoder.IsEncodingInProgress());
+ }
+}
+
+} // namespace
+} // namespace test
+} // namespace http2
diff --git a/chromium/net/third_party/quiche/src/http2/hpack/varint/hpack_varint_round_trip_test.cc b/chromium/net/third_party/quiche/src/http2/hpack/varint/hpack_varint_round_trip_test.cc
new file mode 100644
index 00000000000..3299d5e2261
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/hpack/varint/hpack_varint_round_trip_test.cc
@@ -0,0 +1,513 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/http2/hpack/varint/hpack_varint_decoder.h"
+
+// Test HpackVarintDecoder against data encoded via HpackBlockBuilder,
+// which uses HpackVarintEncoder under the hood.
+
+#include <stddef.h>
+
+#include <iterator>
+#include <set>
+#include <vector>
+
+#include "base/logging.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/http2/hpack/tools/hpack_block_builder.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string_piece.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string_utils.h"
+#include "net/third_party/quiche/src/http2/tools/random_decoder_test.h"
+
+using ::testing::AssertionFailure;
+using ::testing::AssertionSuccess;
+using ::testing::Bool;
+using ::testing::WithParamInterface;
+
+namespace http2 {
+namespace test {
+namespace {
+
+// Save previous value of flag and restore on destruction.
+class FlagSaver {
+ public:
+ FlagSaver() = delete;
+ explicit FlagSaver(bool decode_64_bits)
+ : saved_value_(GetHttp2ReloadableFlag(http2_varint_decode_64_bits)) {
+ SetHttp2ReloadableFlag(http2_varint_decode_64_bits, decode_64_bits);
+ }
+ ~FlagSaver() {
+ SetHttp2ReloadableFlag(http2_varint_decode_64_bits, saved_value_);
+ }
+
+ private:
+ const bool saved_value_;
+};
+
+// Returns the highest value with the specified number of extension bytes
+// and the specified prefix length (bits).
+uint64_t HiValueOfExtensionBytes(uint32_t extension_bytes,
+ uint32_t prefix_length) {
+ return (1 << prefix_length) - 2 +
+ (extension_bytes == 0 ? 0 : (1LLU << (extension_bytes * 7)));
+}
+
+class HpackVarintRoundTripTest : public RandomDecoderTest,
+ public WithParamInterface<bool> {
+ protected:
+ HpackVarintRoundTripTest()
+ : decode_64_bits_(GetParam()),
+ flag_saver_(decode_64_bits_),
+ prefix_length_(0) {}
+
+ DecodeStatus StartDecoding(DecodeBuffer* b) override {
+ CHECK_LT(0u, b->Remaining());
+ uint8_t prefix = b->DecodeUInt8();
+ return decoder_.Start(prefix, prefix_length_, b);
+ }
+
+ DecodeStatus ResumeDecoding(DecodeBuffer* b) override {
+ return decoder_.Resume(b);
+ }
+
+ void DecodeSeveralWays(uint64_t expected_value, uint32_t expected_offset) {
+ // The validator is called after each of the several times that the input
+ // DecodeBuffer is decoded, each with a different segmentation of the input.
+ // Validate that decoder_.value() matches the expected value.
+ Validator validator = [expected_value, this](
+ const DecodeBuffer& db,
+ DecodeStatus status) -> AssertionResult {
+ if (decoder_.value() != expected_value) {
+ return AssertionFailure()
+ << "Value doesn't match expected: " << decoder_.value()
+ << " != " << expected_value;
+ }
+ return AssertionSuccess();
+ };
+
+ // First validate that decoding is done and that we've advanced the cursor
+ // the expected amount.
+ validator = ValidateDoneAndOffset(expected_offset, validator);
+
+ // StartDecoding, above, requires the DecodeBuffer be non-empty so that it
+ // can call Start with the prefix byte.
+ bool return_non_zero_on_first = true;
+
+ DecodeBuffer b(buffer_);
+ EXPECT_TRUE(
+ DecodeAndValidateSeveralWays(&b, return_non_zero_on_first, validator));
+
+ EXPECT_EQ(expected_value, decoder_.value());
+ EXPECT_EQ(expected_offset, b.Offset());
+ }
+
+ void EncodeNoRandom(uint64_t value, uint8_t prefix_length) {
+ DCHECK_LE(3, prefix_length);
+ DCHECK_LE(prefix_length, 8);
+ prefix_length_ = prefix_length;
+
+ HpackBlockBuilder bb;
+ bb.AppendHighBitsAndVarint(0, prefix_length_, value);
+ buffer_ = bb.buffer();
+ ASSERT_LT(0u, buffer_.size());
+
+ const uint8_t prefix_mask = (1 << prefix_length_) - 1;
+ ASSERT_EQ(static_cast<uint8_t>(buffer_[0]),
+ static_cast<uint8_t>(buffer_[0]) & prefix_mask);
+ }
+
+ void Encode(uint64_t value, uint8_t prefix_length) {
+ EncodeNoRandom(value, prefix_length);
+ // Add some random bits to the prefix (the first byte) above the mask.
+ uint8_t prefix = buffer_[0];
+ buffer_[0] = prefix | (Random().Rand8() << prefix_length);
+ const uint8_t prefix_mask = (1 << prefix_length_) - 1;
+ ASSERT_EQ(prefix, buffer_[0] & prefix_mask);
+ }
+
+ // This is really a test of HpackBlockBuilder, making sure that the input to
+ // HpackVarintDecoder is as expected, which also acts as confirmation that
+ // my thinking about the encodings being used by the tests, i.e. cover the
+ // range desired.
+ void ValidateEncoding(uint64_t value,
+ uint64_t minimum,
+ uint64_t maximum,
+ size_t expected_bytes) {
+ ASSERT_EQ(expected_bytes, buffer_.size());
+ if (expected_bytes > 1) {
+ const uint8_t prefix_mask = (1 << prefix_length_) - 1;
+ EXPECT_EQ(prefix_mask, buffer_[0] & prefix_mask);
+ size_t last = expected_bytes - 1;
+ for (size_t ndx = 1; ndx < last; ++ndx) {
+ // Before the last extension byte, we expect the high-bit set.
+ uint8_t byte = buffer_[ndx];
+ if (value == minimum) {
+ EXPECT_EQ(0x80, byte) << "ndx=" << ndx;
+ } else if (value == maximum) {
+ if (expected_bytes < 11) {
+ EXPECT_EQ(0xff, byte) << "ndx=" << ndx;
+ }
+ } else {
+ EXPECT_EQ(0x80, byte & 0x80) << "ndx=" << ndx;
+ }
+ }
+ // The last extension byte should not have the high-bit set.
+ uint8_t byte = buffer_[last];
+ if (value == minimum) {
+ if (expected_bytes == 2) {
+ EXPECT_EQ(0x00, byte);
+ } else {
+ EXPECT_EQ(0x01, byte);
+ }
+ } else if (value == maximum) {
+ if (expected_bytes < 11) {
+ EXPECT_EQ(0x7f, byte);
+ }
+ } else {
+ EXPECT_EQ(0x00, byte & 0x80);
+ }
+ } else {
+ const uint8_t prefix_mask = (1 << prefix_length_) - 1;
+ EXPECT_EQ(value, static_cast<uint32_t>(buffer_[0] & prefix_mask));
+ EXPECT_LT(value, prefix_mask);
+ }
+ }
+
+ void EncodeAndDecodeValues(const std::set<uint64_t>& values,
+ uint8_t prefix_length,
+ size_t expected_bytes) {
+ CHECK(!values.empty());
+ const uint64_t minimum = *values.begin();
+ const uint64_t maximum = *values.rbegin();
+ for (const uint64_t value : values) {
+ Encode(value, prefix_length); // Sets buffer_.
+
+ Http2String msg = Http2StrCat("value=", value, " (0x", Http2Hex(value),
+ "), prefix_length=", prefix_length,
+ ", expected_bytes=", expected_bytes, "\n",
+ Http2HexDump(buffer_));
+
+ if (value == minimum) {
+ LOG(INFO) << "Checking minimum; " << msg;
+ } else if (value == maximum) {
+ LOG(INFO) << "Checking maximum; " << msg;
+ }
+
+ SCOPED_TRACE(msg);
+ ValidateEncoding(value, minimum, maximum, expected_bytes);
+ DecodeSeveralWays(value, expected_bytes);
+
+ // Append some random data to the end of buffer_ and repeat. That random
+ // data should be ignored.
+ buffer_.append(Random().RandString(1 + Random().Uniform(10)));
+ DecodeSeveralWays(value, expected_bytes);
+
+ // If possible, add extension bytes that don't change the value.
+ if (1 < expected_bytes) {
+ buffer_.resize(expected_bytes);
+ for (uint8_t total_bytes = expected_bytes + 1; total_bytes <= 6;
+ ++total_bytes) {
+ // Mark the current last byte as not being the last one.
+ EXPECT_EQ(0x00, 0x80 & buffer_.back());
+ buffer_.back() |= 0x80;
+ buffer_.push_back('\0');
+ DecodeSeveralWays(value, total_bytes);
+ }
+ }
+ }
+ }
+
+ // Encode values (all or some of it) in [start, start+range). Check
+ // that |start| is the smallest value and |start+range-1| is the largest value
+ // corresponding to |expected_bytes|, except if |expected_bytes| is maximal.
+ void EncodeAndDecodeValuesInRange(uint64_t start,
+ uint64_t range,
+ uint8_t prefix_length,
+ size_t expected_bytes) {
+ const uint8_t prefix_mask = (1 << prefix_length) - 1;
+ const uint64_t beyond = start + range;
+
+ LOG(INFO) << "############################################################";
+ LOG(INFO) << "prefix_length=" << static_cast<int>(prefix_length);
+ LOG(INFO) << "prefix_mask=" << std::hex << static_cast<int>(prefix_mask);
+ LOG(INFO) << "start=" << start << " (" << std::hex << start << ")";
+ LOG(INFO) << "range=" << range << " (" << std::hex << range << ")";
+ LOG(INFO) << "beyond=" << beyond << " (" << std::hex << beyond << ")";
+ LOG(INFO) << "expected_bytes=" << expected_bytes;
+
+ if (expected_bytes < 11) {
+ // Confirm the claim that beyond requires more bytes.
+ Encode(beyond, prefix_length);
+ EXPECT_EQ(expected_bytes + 1, buffer_.size()) << Http2HexDump(buffer_);
+ }
+
+ std::set<uint64_t> values;
+ if (range < 200) {
+ // Select all values in the range.
+ for (uint64_t offset = 0; offset < range; ++offset) {
+ values.insert(start + offset);
+ }
+ } else {
+ // Select some values in this range, including the minimum and maximum
+ // values that require exactly |expected_bytes| extension bytes.
+ values.insert({start, start + 1, beyond - 2, beyond - 1});
+ while (values.size() < 100) {
+ values.insert(Random().UniformInRange(start, beyond - 1));
+ }
+ }
+
+ EncodeAndDecodeValues(values, prefix_length, expected_bytes);
+ }
+
+ // |flag_saver_| must preceed |decoder_| so that the flag is already set when
+ // |decoder_| is constructed.
+ const bool decode_64_bits_;
+ FlagSaver flag_saver_;
+ HpackVarintDecoder decoder_;
+ Http2String buffer_;
+ uint8_t prefix_length_;
+};
+
+INSTANTIATE_TEST_CASE_P(HpackVarintRoundTripTest,
+ HpackVarintRoundTripTest,
+ Bool());
+
+// To help me and future debuggers of varint encodings, this LOGs out the
+// transition points where a new extension byte is added.
+TEST_P(HpackVarintRoundTripTest, Encode) {
+ for (int prefix_length = 3; prefix_length <= 8; ++prefix_length) {
+ const uint64_t a = HiValueOfExtensionBytes(0, prefix_length);
+ const uint64_t b = HiValueOfExtensionBytes(1, prefix_length);
+ const uint64_t c = HiValueOfExtensionBytes(2, prefix_length);
+ const uint64_t d = HiValueOfExtensionBytes(3, prefix_length);
+ const uint64_t e = HiValueOfExtensionBytes(4, prefix_length);
+ const uint64_t f = HiValueOfExtensionBytes(5, prefix_length);
+ const uint64_t g = HiValueOfExtensionBytes(6, prefix_length);
+ const uint64_t h = HiValueOfExtensionBytes(7, prefix_length);
+ const uint64_t i = HiValueOfExtensionBytes(8, prefix_length);
+ const uint64_t j = HiValueOfExtensionBytes(9, prefix_length);
+
+ LOG(INFO) << "############################################################";
+ LOG(INFO) << "prefix_length=" << prefix_length << " a=" << a
+ << " b=" << b << " c=" << c << " d=" << d << " e=" << e
+ << " f=" << f << " g=" << g << " h=" << h << " i=" << i
+ << " j=" << j;
+
+ std::vector<uint64_t> values = {
+ 0, 1, // Force line break.
+ a - 1, a, a + 1, a + 2, a + 3, // Force line break.
+ b - 1, b, b + 1, b + 2, b + 3, // Force line break.
+ c - 1, c, c + 1, c + 2, c + 3, // Force line break.
+ d - 1, d, d + 1, d + 2, d + 3, // Force line break.
+ e - 1, e, e + 1, e + 2, e + 3 // Force line break.
+ };
+ if (decode_64_bits_) {
+ for (auto value : {
+ f - 1, f, f + 1, f + 2, f + 3, // Force line break.
+ g - 1, g, g + 1, g + 2, g + 3, // Force line break.
+ h - 1, h, h + 1, h + 2, h + 3, // Force line break.
+ i - 1, i, i + 1, i + 2, i + 3, // Force line break.
+ j - 1, j, j + 1, j + 2, j + 3, // Force line break.
+ }) {
+ values.push_back(value);
+ }
+ }
+
+ for (uint64_t value : values) {
+ EncodeNoRandom(value, prefix_length);
+ Http2String dump = Http2HexDump(buffer_);
+ LOG(INFO) << Http2StringPrintf("%10llu %0#18x ", value, value)
+ << Http2HexDump(buffer_).substr(7);
+ }
+ }
+}
+
+TEST_P(HpackVarintRoundTripTest, FromSpec1337) {
+ DecodeBuffer b(Http2StringPiece("\x1f\x9a\x0a"));
+ uint32_t prefix_length = 5;
+ uint8_t p = b.DecodeUInt8();
+ EXPECT_EQ(1u, b.Offset());
+ EXPECT_EQ(DecodeStatus::kDecodeDone, decoder_.Start(p, prefix_length, &b));
+ EXPECT_EQ(3u, b.Offset());
+ EXPECT_EQ(1337u, decoder_.value());
+
+ EncodeNoRandom(1337, prefix_length);
+ EXPECT_EQ(3u, buffer_.size());
+ EXPECT_EQ('\x1f', buffer_[0]);
+ EXPECT_EQ('\x9a', buffer_[1]);
+ EXPECT_EQ('\x0a', buffer_[2]);
+}
+
+// Test all the values that fit into the prefix (one less than the mask).
+TEST_P(HpackVarintRoundTripTest, ValidatePrefixOnly) {
+ for (int prefix_length = 3; prefix_length <= 8; ++prefix_length) {
+ const uint8_t prefix_mask = (1 << prefix_length) - 1;
+ EncodeAndDecodeValuesInRange(0, prefix_mask, prefix_length, 1);
+ }
+}
+
+// Test all values that require exactly 1 extension byte.
+TEST_P(HpackVarintRoundTripTest, ValidateOneExtensionByte) {
+ for (int prefix_length = 3; prefix_length <= 8; ++prefix_length) {
+ const uint64_t start = HiValueOfExtensionBytes(0, prefix_length) + 1;
+ EncodeAndDecodeValuesInRange(start, 128, prefix_length, 2);
+ }
+}
+
+// Test *some* values that require exactly 2 extension bytes.
+TEST_P(HpackVarintRoundTripTest, ValidateTwoExtensionBytes) {
+ for (int prefix_length = 3; prefix_length <= 8; ++prefix_length) {
+ const uint64_t start = HiValueOfExtensionBytes(1, prefix_length) + 1;
+ const uint64_t range = 127 << 7;
+
+ EncodeAndDecodeValuesInRange(start, range, prefix_length, 3);
+ }
+}
+
+// Test *some* values that require 3 extension bytes.
+TEST_P(HpackVarintRoundTripTest, ValidateThreeExtensionBytes) {
+ for (int prefix_length = 3; prefix_length <= 8; ++prefix_length) {
+ const uint64_t start = HiValueOfExtensionBytes(2, prefix_length) + 1;
+ const uint64_t range = 127 << 14;
+
+ EncodeAndDecodeValuesInRange(start, range, prefix_length, 4);
+ }
+}
+
+// Test *some* values that require 4 extension bytes.
+TEST_P(HpackVarintRoundTripTest, ValidateFourExtensionBytes) {
+ for (int prefix_length = 3; prefix_length <= 8; ++prefix_length) {
+ const uint64_t start = HiValueOfExtensionBytes(3, prefix_length) + 1;
+ const uint64_t range = 127 << 21;
+
+ EncodeAndDecodeValuesInRange(start, range, prefix_length, 5);
+ }
+}
+
+// Test *some* values that require 5 extension bytes.
+TEST_P(HpackVarintRoundTripTest, ValidateFiveExtensionBytes) {
+ if (!decode_64_bits_) {
+ return;
+ }
+
+ for (int prefix_length = 3; prefix_length <= 8; ++prefix_length) {
+ const uint64_t start = HiValueOfExtensionBytes(4, prefix_length) + 1;
+ const uint64_t range = 127llu << 28;
+
+ EncodeAndDecodeValuesInRange(start, range, prefix_length, 6);
+ }
+}
+
+// Test *some* values that require 6 extension bytes.
+TEST_P(HpackVarintRoundTripTest, ValidateSixExtensionBytes) {
+ if (!decode_64_bits_) {
+ return;
+ }
+
+ for (int prefix_length = 3; prefix_length <= 8; ++prefix_length) {
+ const uint64_t start = HiValueOfExtensionBytes(5, prefix_length) + 1;
+ const uint64_t range = 127llu << 35;
+
+ EncodeAndDecodeValuesInRange(start, range, prefix_length, 7);
+ }
+}
+
+// Test *some* values that require 7 extension bytes.
+TEST_P(HpackVarintRoundTripTest, ValidateSevenExtensionBytes) {
+ if (!decode_64_bits_) {
+ return;
+ }
+
+ for (int prefix_length = 3; prefix_length <= 8; ++prefix_length) {
+ const uint64_t start = HiValueOfExtensionBytes(6, prefix_length) + 1;
+ const uint64_t range = 127llu << 42;
+
+ EncodeAndDecodeValuesInRange(start, range, prefix_length, 8);
+ }
+}
+
+// Test *some* values that require 8 extension bytes.
+TEST_P(HpackVarintRoundTripTest, ValidateEightExtensionBytes) {
+ if (!decode_64_bits_) {
+ return;
+ }
+
+ for (int prefix_length = 3; prefix_length <= 8; ++prefix_length) {
+ const uint64_t start = HiValueOfExtensionBytes(7, prefix_length) + 1;
+ const uint64_t range = 127llu << 49;
+
+ EncodeAndDecodeValuesInRange(start, range, prefix_length, 9);
+ }
+}
+
+// Test *some* values that require 9 extension bytes.
+TEST_P(HpackVarintRoundTripTest, ValidateNineExtensionBytes) {
+ if (!decode_64_bits_) {
+ return;
+ }
+
+ for (int prefix_length = 3; prefix_length <= 8; ++prefix_length) {
+ const uint64_t start = HiValueOfExtensionBytes(8, prefix_length) + 1;
+ const uint64_t range = 127llu << 56;
+
+ EncodeAndDecodeValuesInRange(start, range, prefix_length, 10);
+ }
+}
+
+// Test *some* values that require 10 extension bytes.
+TEST_P(HpackVarintRoundTripTest, ValidateTenExtensionBytes) {
+ if (!decode_64_bits_) {
+ return;
+ }
+
+ for (int prefix_length = 3; prefix_length <= 8; ++prefix_length) {
+ const uint64_t start = HiValueOfExtensionBytes(9, prefix_length) + 1;
+ const uint64_t range = std::numeric_limits<uint64_t>::max() - start;
+
+ EncodeAndDecodeValuesInRange(start, range, prefix_length, 11);
+ }
+}
+
+// Test the value one larger than the largest that can be decoded.
+TEST_P(HpackVarintRoundTripTest, ValueTooLarge) {
+ // New implementation can decode any integer that HpackVarintEncoder can
+ // encode.
+ if (decode_64_bits_) {
+ return;
+ }
+
+ for (prefix_length_ = 3; prefix_length_ <= 8; ++prefix_length_) {
+ const uint64_t too_large = (1 << 28) + (1 << prefix_length_) - 1;
+ const uint32_t expected_offset = 6;
+ HpackBlockBuilder bb;
+ bb.AppendHighBitsAndVarint(0, prefix_length_, too_large);
+ buffer_ = bb.buffer();
+
+ // The validator is called after each of the several times that the input
+ // DecodeBuffer is decoded, each with a different segmentation of the input.
+ // Validate that decoder_.value() matches the expected value.
+ bool validated = false;
+ Validator validator = [&validated, expected_offset](
+ const DecodeBuffer& db,
+ DecodeStatus status) -> AssertionResult {
+ validated = true;
+ VERIFY_EQ(DecodeStatus::kDecodeError, status);
+ VERIFY_EQ(expected_offset, db.Offset());
+ return AssertionSuccess();
+ };
+
+ // StartDecoding, above, requires the DecodeBuffer be non-empty so that it
+ // can call Start with the prefix byte.
+ bool return_non_zero_on_first = true;
+ DecodeBuffer b(buffer_);
+ EXPECT_TRUE(
+ DecodeAndValidateSeveralWays(&b, return_non_zero_on_first, validator));
+ EXPECT_EQ(expected_offset, b.Offset());
+ EXPECT_TRUE(validated);
+ }
+}
+
+} // namespace
+} // namespace test
+} // namespace http2
diff --git a/chromium/net/third_party/quiche/src/http2/http2_constants.cc b/chromium/net/third_party/quiche/src/http2/http2_constants.cc
new file mode 100644
index 00000000000..d4d59a555c7
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/http2_constants.cc
@@ -0,0 +1,150 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/http2/http2_constants.h"
+
+#include "base/logging.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string_piece.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string_utils.h"
+
+namespace http2 {
+
+Http2String Http2FrameTypeToString(Http2FrameType v) {
+ switch (v) {
+ case Http2FrameType::DATA:
+ return "DATA";
+ case Http2FrameType::HEADERS:
+ return "HEADERS";
+ case Http2FrameType::PRIORITY:
+ return "PRIORITY";
+ case Http2FrameType::RST_STREAM:
+ return "RST_STREAM";
+ case Http2FrameType::SETTINGS:
+ return "SETTINGS";
+ case Http2FrameType::PUSH_PROMISE:
+ return "PUSH_PROMISE";
+ case Http2FrameType::PING:
+ return "PING";
+ case Http2FrameType::GOAWAY:
+ return "GOAWAY";
+ case Http2FrameType::WINDOW_UPDATE:
+ return "WINDOW_UPDATE";
+ case Http2FrameType::CONTINUATION:
+ return "CONTINUATION";
+ case Http2FrameType::ALTSVC:
+ return "ALTSVC";
+ }
+ return Http2StrCat("UnknownFrameType(", static_cast<int>(v), ")");
+}
+
+Http2String Http2FrameTypeToString(uint8_t v) {
+ return Http2FrameTypeToString(static_cast<Http2FrameType>(v));
+}
+
+Http2String Http2FrameFlagsToString(Http2FrameType type, uint8_t flags) {
+ Http2String s;
+ // Closure to append flag name |v| to the Http2String |s|,
+ // and to clear |bit| from |flags|.
+ auto append_and_clear = [&s, &flags](Http2StringPiece v, uint8_t bit) {
+ if (!s.empty()) {
+ s.push_back('|');
+ }
+ Http2StrAppend(&s, v);
+ flags ^= bit;
+ };
+ if (flags & 0x01) {
+ if (type == Http2FrameType::DATA || type == Http2FrameType::HEADERS) {
+ append_and_clear("END_STREAM", Http2FrameFlag::END_STREAM);
+ } else if (type == Http2FrameType::SETTINGS ||
+ type == Http2FrameType::PING) {
+ append_and_clear("ACK", Http2FrameFlag::ACK);
+ }
+ }
+ if (flags & 0x04) {
+ if (type == Http2FrameType::HEADERS ||
+ type == Http2FrameType::PUSH_PROMISE ||
+ type == Http2FrameType::CONTINUATION) {
+ append_and_clear("END_HEADERS", Http2FrameFlag::END_HEADERS);
+ }
+ }
+ if (flags & 0x08) {
+ if (type == Http2FrameType::DATA || type == Http2FrameType::HEADERS ||
+ type == Http2FrameType::PUSH_PROMISE) {
+ append_and_clear("PADDED", Http2FrameFlag::PADDED);
+ }
+ }
+ if (flags & 0x20) {
+ if (type == Http2FrameType::HEADERS) {
+ append_and_clear("PRIORITY", Http2FrameFlag::PRIORITY);
+ }
+ }
+ if (flags != 0) {
+ append_and_clear(Http2StringPrintf("0x%02x", flags), flags);
+ }
+ DCHECK_EQ(0, flags);
+ return s;
+}
+Http2String Http2FrameFlagsToString(uint8_t type, uint8_t flags) {
+ return Http2FrameFlagsToString(static_cast<Http2FrameType>(type), flags);
+}
+
+Http2String Http2ErrorCodeToString(uint32_t v) {
+ switch (v) {
+ case 0x0:
+ return "NO_ERROR";
+ case 0x1:
+ return "PROTOCOL_ERROR";
+ case 0x2:
+ return "INTERNAL_ERROR";
+ case 0x3:
+ return "FLOW_CONTROL_ERROR";
+ case 0x4:
+ return "SETTINGS_TIMEOUT";
+ case 0x5:
+ return "STREAM_CLOSED";
+ case 0x6:
+ return "FRAME_SIZE_ERROR";
+ case 0x7:
+ return "REFUSED_STREAM";
+ case 0x8:
+ return "CANCEL";
+ case 0x9:
+ return "COMPRESSION_ERROR";
+ case 0xa:
+ return "CONNECT_ERROR";
+ case 0xb:
+ return "ENHANCE_YOUR_CALM";
+ case 0xc:
+ return "INADEQUATE_SECURITY";
+ case 0xd:
+ return "HTTP_1_1_REQUIRED";
+ }
+ return Http2StrCat("UnknownErrorCode(0x", Http2Hex(v), ")");
+}
+Http2String Http2ErrorCodeToString(Http2ErrorCode v) {
+ return Http2ErrorCodeToString(static_cast<uint32_t>(v));
+}
+
+Http2String Http2SettingsParameterToString(uint32_t v) {
+ switch (v) {
+ case 0x1:
+ return "HEADER_TABLE_SIZE";
+ case 0x2:
+ return "ENABLE_PUSH";
+ case 0x3:
+ return "MAX_CONCURRENT_STREAMS";
+ case 0x4:
+ return "INITIAL_WINDOW_SIZE";
+ case 0x5:
+ return "MAX_FRAME_SIZE";
+ case 0x6:
+ return "MAX_HEADER_LIST_SIZE";
+ }
+ return Http2StrCat("UnknownSettingsParameter(0x", Http2Hex(v), ")");
+}
+Http2String Http2SettingsParameterToString(Http2SettingsParameter v) {
+ return Http2SettingsParameterToString(static_cast<uint32_t>(v));
+}
+
+} // namespace http2
diff --git a/chromium/net/third_party/quiche/src/http2/http2_constants.h b/chromium/net/third_party/quiche/src/http2/http2_constants.h
new file mode 100644
index 00000000000..93920069c5a
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/http2_constants.h
@@ -0,0 +1,261 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_HTTP2_HTTP2_CONSTANTS_H_
+#define QUICHE_HTTP2_HTTP2_CONSTANTS_H_
+
+// Constants from the HTTP/2 spec, RFC 7540, and associated helper functions.
+
+#include <cstdint>
+#include <iosfwd>
+#include <ostream>
+
+#include "net/third_party/quiche/src/http2/platform/api/http2_export.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string.h"
+
+namespace http2 {
+
+// TODO(jamessynge): create http2_simple_types for types similar to
+// SpdyStreamId, but not for structures like Http2FrameHeader. Then will be
+// able to move these stream id functions there.
+constexpr uint32_t UInt31Mask() {
+ return 0x7fffffff;
+}
+constexpr uint32_t StreamIdMask() {
+ return UInt31Mask();
+}
+
+// The value used to identify types of frames. Upper case to match the RFC.
+// The comments indicate which flags are valid for that frame type.
+// ALTSVC is defined in http://httpwg.org/http-extensions/alt-svc.html
+// (not yet final standard as of March 2016, but close).
+enum class Http2FrameType : uint8_t {
+ DATA = 0, // END_STREAM | PADDED
+ HEADERS = 1, // END_STREAM | END_HEADERS | PADDED | PRIORITY
+ PRIORITY = 2, //
+ RST_STREAM = 3, //
+ SETTINGS = 4, // ACK
+ PUSH_PROMISE = 5, // END_HEADERS | PADDED
+ PING = 6, // ACK
+ GOAWAY = 7, //
+ WINDOW_UPDATE = 8, //
+ CONTINUATION = 9, // END_HEADERS
+ ALTSVC = 10, //
+};
+
+// Is the frame type known/supported?
+inline bool IsSupportedHttp2FrameType(uint32_t v) {
+ return v <= static_cast<uint32_t>(Http2FrameType::ALTSVC);
+}
+inline bool IsSupportedHttp2FrameType(Http2FrameType v) {
+ return IsSupportedHttp2FrameType(static_cast<uint32_t>(v));
+}
+
+// The return type is 'Http2String' so that they can generate a unique string
+// for each unsupported value. Since these are just used for debugging/error
+// messages, that isn't a cost to we need to worry about. The same applies to
+// the functions later in this file.
+HTTP2_EXPORT_PRIVATE Http2String Http2FrameTypeToString(Http2FrameType v);
+HTTP2_EXPORT_PRIVATE Http2String Http2FrameTypeToString(uint8_t v);
+HTTP2_EXPORT_PRIVATE inline std::ostream& operator<<(std::ostream& out,
+ Http2FrameType v) {
+ return out << Http2FrameTypeToString(v);
+}
+
+// Flags that appear in supported frame types. These are treated as bit masks.
+// The comments indicate for which frame types the flag is valid.
+enum Http2FrameFlag {
+ END_STREAM = 0x01, // DATA, HEADERS
+ ACK = 0x01, // SETTINGS, PING
+ END_HEADERS = 0x04, // HEADERS, PUSH_PROMISE, CONTINUATION
+ PADDED = 0x08, // DATA, HEADERS, PUSH_PROMISE
+ PRIORITY = 0x20, // HEADERS
+};
+
+// Formats zero or more flags for the specified type of frame. Returns an
+// empty string if flags==0.
+HTTP2_EXPORT_PRIVATE Http2String Http2FrameFlagsToString(Http2FrameType type,
+ uint8_t flags);
+HTTP2_EXPORT_PRIVATE Http2String Http2FrameFlagsToString(uint8_t type,
+ uint8_t flags);
+
+// Error codes for GOAWAY and RST_STREAM frames.
+enum class Http2ErrorCode : uint32_t {
+ // The associated condition is not a result of an error. For example, a GOAWAY
+ // might include this code to indicate graceful shutdown of a connection.
+ HTTP2_NO_ERROR = 0x0,
+
+ // The endpoint detected an unspecific protocol error. This error is for use
+ // when a more specific error code is not available.
+ PROTOCOL_ERROR = 0x1,
+
+ // The endpoint encountered an unexpected internal error.
+ INTERNAL_ERROR = 0x2,
+
+ // The endpoint detected that its peer violated the flow-control protocol.
+ FLOW_CONTROL_ERROR = 0x3,
+
+ // The endpoint sent a SETTINGS frame but did not receive a response in a
+ // timely manner. See Section 6.5.3 ("Settings Synchronization").
+ SETTINGS_TIMEOUT = 0x4,
+
+ // The endpoint received a frame after a stream was half-closed.
+ STREAM_CLOSED = 0x5,
+
+ // The endpoint received a frame with an invalid size.
+ FRAME_SIZE_ERROR = 0x6,
+
+ // The endpoint refused the stream prior to performing any application
+ // processing (see Section 8.1.4 for details).
+ REFUSED_STREAM = 0x7,
+
+ // Used by the endpoint to indicate that the stream is no longer needed.
+ CANCEL = 0x8,
+
+ // The endpoint is unable to maintain the header compression context
+ // for the connection.
+ COMPRESSION_ERROR = 0x9,
+
+ // The connection established in response to a CONNECT request (Section 8.3)
+ // was reset or abnormally closed.
+ CONNECT_ERROR = 0xa,
+
+ // The endpoint detected that its peer is exhibiting a behavior that might
+ // be generating excessive load.
+ ENHANCE_YOUR_CALM = 0xb,
+
+ // The underlying transport has properties that do not meet minimum
+ // security requirements (see Section 9.2).
+ INADEQUATE_SECURITY = 0xc,
+
+ // The endpoint requires that HTTP/1.1 be used instead of HTTP/2.
+ HTTP_1_1_REQUIRED = 0xd,
+};
+
+// Is the error code supported? (So far that means it is in RFC 7540.)
+inline bool IsSupportedHttp2ErrorCode(uint32_t v) {
+ return v <= static_cast<uint32_t>(Http2ErrorCode::HTTP_1_1_REQUIRED);
+}
+inline bool IsSupportedHttp2ErrorCode(Http2ErrorCode v) {
+ return IsSupportedHttp2ErrorCode(static_cast<uint32_t>(v));
+}
+
+// Format the specified error code.
+HTTP2_EXPORT_PRIVATE Http2String Http2ErrorCodeToString(uint32_t v);
+HTTP2_EXPORT_PRIVATE Http2String Http2ErrorCodeToString(Http2ErrorCode v);
+HTTP2_EXPORT_PRIVATE inline std::ostream& operator<<(std::ostream& out,
+ Http2ErrorCode v) {
+ return out << Http2ErrorCodeToString(v);
+}
+
+// Supported parameters in SETTINGS frames; so far just those in RFC 7540.
+enum class Http2SettingsParameter : uint16_t {
+ // Allows the sender to inform the remote endpoint of the maximum size of the
+ // header compression table used to decode header blocks, in octets. The
+ // encoder can select any size equal to or less than this value by using
+ // signaling specific to the header compression format inside a header block
+ // (see [COMPRESSION]). The initial value is 4,096 octets.
+ HEADER_TABLE_SIZE = 0x1,
+
+ // This setting can be used to disable server push (Section 8.2). An endpoint
+ // MUST NOT send a PUSH_PROMISE frame if it receives this parameter set to a
+ // value of 0. An endpoint that has both set this parameter to 0 and had it
+ // acknowledged MUST treat the receipt of a PUSH_PROMISE frame as a connection
+ // error (Section 5.4.1) of type PROTOCOL_ERROR.
+ //
+ // The initial value is 1, which indicates that server push is permitted. Any
+ // value other than 0 or 1 MUST be treated as a connection error (Section
+ // 5.4.1) of type PROTOCOL_ERROR.
+ ENABLE_PUSH = 0x2,
+
+ // Indicates the maximum number of concurrent streams that the sender will
+ // allow. This limit is directional: it applies to the number of streams that
+ // the sender permits the receiver to create. Initially, there is no limit to
+ // this value. It is recommended that this value be no smaller than 100, so as
+ // to not unnecessarily limit parallelism.
+ //
+ // A value of 0 for MAX_CONCURRENT_STREAMS SHOULD NOT be treated as
+ // special by endpoints. A zero value does prevent the creation of new
+ // streams; however, this can also happen for any limit that is exhausted with
+ // active streams. Servers SHOULD only set a zero value for short durations;
+ // if a server does not wish to accept requests, closing the connection is
+ // more appropriate.
+ MAX_CONCURRENT_STREAMS = 0x3,
+
+ // Indicates the sender's initial window size (in octets) for stream-level
+ // flow control. The initial value is 2^16-1 (65,535) octets.
+ //
+ // This setting affects the window size of all streams (see Section 6.9.2).
+ //
+ // Values above the maximum flow-control window size of 2^31-1 MUST be treated
+ // as a connection error (Section 5.4.1) of type FLOW_CONTROL_ERROR.
+ INITIAL_WINDOW_SIZE = 0x4,
+
+ // Indicates the size of the largest frame payload that the sender is willing
+ // to receive, in octets.
+ //
+ // The initial value is 2^14 (16,384) octets. The value advertised by an
+ // endpoint MUST be between this initial value and the maximum allowed frame
+ // size (2^24-1 or 16,777,215 octets), inclusive. Values outside this range
+ // MUST be treated as a connection error (Section 5.4.1) of type
+ // PROTOCOL_ERROR.
+ MAX_FRAME_SIZE = 0x5,
+
+ // This advisory setting informs a peer of the maximum size of header list
+ // that the sender is prepared to accept, in octets. The value is based on the
+ // uncompressed size of header fields, including the length of the name and
+ // value in octets plus an overhead of 32 octets for each header field.
+ //
+ // For any given request, a lower limit than what is advertised MAY be
+ // enforced. The initial value of this setting is unlimited.
+ MAX_HEADER_LIST_SIZE = 0x6,
+};
+
+// Is the settings parameter supported (so far that means it is in RFC 7540)?
+inline bool IsSupportedHttp2SettingsParameter(uint32_t v) {
+ return 0 < v && v <= static_cast<uint32_t>(
+ Http2SettingsParameter::MAX_HEADER_LIST_SIZE);
+}
+inline bool IsSupportedHttp2SettingsParameter(Http2SettingsParameter v) {
+ return IsSupportedHttp2SettingsParameter(static_cast<uint32_t>(v));
+}
+
+// Format the specified settings parameter.
+HTTP2_EXPORT_PRIVATE Http2String Http2SettingsParameterToString(uint32_t v);
+HTTP2_EXPORT_PRIVATE Http2String
+Http2SettingsParameterToString(Http2SettingsParameter v);
+inline std::ostream& operator<<(std::ostream& out, Http2SettingsParameter v) {
+ return out << Http2SettingsParameterToString(v);
+}
+
+// Information about the initial, minimum and maximum value of settings (not
+// applicable to all settings parameters).
+class Http2SettingsInfo {
+ public:
+ // Default value for HEADER_TABLE_SIZE.
+ static constexpr uint32_t DefaultHeaderTableSize() { return 4096; }
+
+ // Default value for ENABLE_PUSH.
+ static constexpr bool DefaultEnablePush() { return true; }
+
+ // Default value for INITIAL_WINDOW_SIZE.
+ static constexpr uint32_t DefaultInitialWindowSize() { return 65535; }
+
+ // Maximum value for INITIAL_WINDOW_SIZE, and for the connection flow control
+ // window, and for each stream flow control window.
+ static constexpr uint32_t MaximumWindowSize() { return UInt31Mask(); }
+
+ // Default value for MAX_FRAME_SIZE.
+ static constexpr uint32_t DefaultMaxFrameSize() { return 16384; }
+
+ // Minimum value for MAX_FRAME_SIZE.
+ static constexpr uint32_t MinimumMaxFrameSize() { return 16384; }
+
+ // Maximum value for MAX_FRAME_SIZE.
+ static constexpr uint32_t MaximumMaxFrameSize() { return (1 << 24) - 1; }
+};
+
+} // namespace http2
+
+#endif // QUICHE_HTTP2_HTTP2_CONSTANTS_H_
diff --git a/chromium/net/third_party/quiche/src/http2/http2_constants_test.cc b/chromium/net/third_party/quiche/src/http2/http2_constants_test.cc
new file mode 100644
index 00000000000..223082adafe
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/http2_constants_test.cc
@@ -0,0 +1,271 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/http2/http2_constants.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace http2 {
+namespace test {
+namespace {
+
+class Http2ConstantsTest : public testing::Test {};
+
+TEST(Http2ConstantsTest, Http2FrameType) {
+ EXPECT_EQ(Http2FrameType::DATA, static_cast<Http2FrameType>(0));
+ EXPECT_EQ(Http2FrameType::HEADERS, static_cast<Http2FrameType>(1));
+ EXPECT_EQ(Http2FrameType::PRIORITY, static_cast<Http2FrameType>(2));
+ EXPECT_EQ(Http2FrameType::RST_STREAM, static_cast<Http2FrameType>(3));
+ EXPECT_EQ(Http2FrameType::SETTINGS, static_cast<Http2FrameType>(4));
+ EXPECT_EQ(Http2FrameType::PUSH_PROMISE, static_cast<Http2FrameType>(5));
+ EXPECT_EQ(Http2FrameType::PING, static_cast<Http2FrameType>(6));
+ EXPECT_EQ(Http2FrameType::GOAWAY, static_cast<Http2FrameType>(7));
+ EXPECT_EQ(Http2FrameType::WINDOW_UPDATE, static_cast<Http2FrameType>(8));
+ EXPECT_EQ(Http2FrameType::CONTINUATION, static_cast<Http2FrameType>(9));
+ EXPECT_EQ(Http2FrameType::ALTSVC, static_cast<Http2FrameType>(10));
+}
+
+TEST(Http2ConstantsTest, Http2FrameTypeToString) {
+ EXPECT_EQ("DATA", Http2FrameTypeToString(Http2FrameType::DATA));
+ EXPECT_EQ("HEADERS", Http2FrameTypeToString(Http2FrameType::HEADERS));
+ EXPECT_EQ("PRIORITY", Http2FrameTypeToString(Http2FrameType::PRIORITY));
+ EXPECT_EQ("RST_STREAM", Http2FrameTypeToString(Http2FrameType::RST_STREAM));
+ EXPECT_EQ("SETTINGS", Http2FrameTypeToString(Http2FrameType::SETTINGS));
+ EXPECT_EQ("PUSH_PROMISE",
+ Http2FrameTypeToString(Http2FrameType::PUSH_PROMISE));
+ EXPECT_EQ("PING", Http2FrameTypeToString(Http2FrameType::PING));
+ EXPECT_EQ("GOAWAY", Http2FrameTypeToString(Http2FrameType::GOAWAY));
+ EXPECT_EQ("WINDOW_UPDATE",
+ Http2FrameTypeToString(Http2FrameType::WINDOW_UPDATE));
+ EXPECT_EQ("CONTINUATION",
+ Http2FrameTypeToString(Http2FrameType::CONTINUATION));
+ EXPECT_EQ("ALTSVC", Http2FrameTypeToString(Http2FrameType::ALTSVC));
+
+ EXPECT_EQ("DATA", Http2FrameTypeToString(0));
+ EXPECT_EQ("HEADERS", Http2FrameTypeToString(1));
+ EXPECT_EQ("PRIORITY", Http2FrameTypeToString(2));
+ EXPECT_EQ("RST_STREAM", Http2FrameTypeToString(3));
+ EXPECT_EQ("SETTINGS", Http2FrameTypeToString(4));
+ EXPECT_EQ("PUSH_PROMISE", Http2FrameTypeToString(5));
+ EXPECT_EQ("PING", Http2FrameTypeToString(6));
+ EXPECT_EQ("GOAWAY", Http2FrameTypeToString(7));
+ EXPECT_EQ("WINDOW_UPDATE", Http2FrameTypeToString(8));
+ EXPECT_EQ("CONTINUATION", Http2FrameTypeToString(9));
+ EXPECT_EQ("ALTSVC", Http2FrameTypeToString(10));
+
+ EXPECT_EQ("UnknownFrameType(99)", Http2FrameTypeToString(99));
+}
+
+TEST(Http2ConstantsTest, Http2FrameFlag) {
+ EXPECT_EQ(Http2FrameFlag::END_STREAM, static_cast<Http2FrameFlag>(0x01));
+ EXPECT_EQ(Http2FrameFlag::ACK, static_cast<Http2FrameFlag>(0x01));
+ EXPECT_EQ(Http2FrameFlag::END_HEADERS, static_cast<Http2FrameFlag>(0x04));
+ EXPECT_EQ(Http2FrameFlag::PADDED, static_cast<Http2FrameFlag>(0x08));
+ EXPECT_EQ(Http2FrameFlag::PRIORITY, static_cast<Http2FrameFlag>(0x20));
+
+ EXPECT_EQ(Http2FrameFlag::END_STREAM, 0x01);
+ EXPECT_EQ(Http2FrameFlag::ACK, 0x01);
+ EXPECT_EQ(Http2FrameFlag::END_HEADERS, 0x04);
+ EXPECT_EQ(Http2FrameFlag::PADDED, 0x08);
+ EXPECT_EQ(Http2FrameFlag::PRIORITY, 0x20);
+}
+
+TEST(Http2ConstantsTest, Http2FrameFlagsToString) {
+ // Single flags...
+
+ // 0b00000001
+ EXPECT_EQ("END_STREAM", Http2FrameFlagsToString(Http2FrameType::DATA,
+ Http2FrameFlag::END_STREAM));
+ EXPECT_EQ("END_STREAM",
+ Http2FrameFlagsToString(Http2FrameType::HEADERS, 0x01));
+ EXPECT_EQ("ACK", Http2FrameFlagsToString(Http2FrameType::SETTINGS,
+ Http2FrameFlag::ACK));
+ EXPECT_EQ("ACK", Http2FrameFlagsToString(Http2FrameType::PING, 0x01));
+
+ // 0b00000010
+ EXPECT_EQ("0x02", Http2FrameFlagsToString(0xff, 0x02));
+
+ // 0b00000100
+ EXPECT_EQ("END_HEADERS",
+ Http2FrameFlagsToString(Http2FrameType::HEADERS,
+ Http2FrameFlag::END_HEADERS));
+ EXPECT_EQ("END_HEADERS",
+ Http2FrameFlagsToString(Http2FrameType::PUSH_PROMISE, 0x04));
+ EXPECT_EQ("END_HEADERS", Http2FrameFlagsToString(0x09, 0x04));
+ EXPECT_EQ("0x04", Http2FrameFlagsToString(0xff, 0x04));
+
+ // 0b00001000
+ EXPECT_EQ("PADDED", Http2FrameFlagsToString(Http2FrameType::DATA,
+ Http2FrameFlag::PADDED));
+ EXPECT_EQ("PADDED", Http2FrameFlagsToString(Http2FrameType::HEADERS, 0x08));
+ EXPECT_EQ("PADDED", Http2FrameFlagsToString(0x05, 0x08));
+ EXPECT_EQ("0x08", Http2FrameFlagsToString(0xff, Http2FrameFlag::PADDED));
+
+ // 0b00010000
+ EXPECT_EQ("0x10", Http2FrameFlagsToString(Http2FrameType::SETTINGS, 0x10));
+
+ // 0b00100000
+ EXPECT_EQ("PRIORITY", Http2FrameFlagsToString(Http2FrameType::HEADERS, 0x20));
+ EXPECT_EQ("0x20",
+ Http2FrameFlagsToString(Http2FrameType::PUSH_PROMISE, 0x20));
+
+ // 0b01000000
+ EXPECT_EQ("0x40", Http2FrameFlagsToString(0xff, 0x40));
+
+ // 0b10000000
+ EXPECT_EQ("0x80", Http2FrameFlagsToString(0xff, 0x80));
+
+ // Combined flags...
+
+ EXPECT_EQ("END_STREAM|PADDED|0xf6",
+ Http2FrameFlagsToString(Http2FrameType::DATA, 0xff));
+ EXPECT_EQ("END_STREAM|END_HEADERS|PADDED|PRIORITY|0xd2",
+ Http2FrameFlagsToString(Http2FrameType::HEADERS, 0xff));
+ EXPECT_EQ("0xff", Http2FrameFlagsToString(Http2FrameType::PRIORITY, 0xff));
+ EXPECT_EQ("0xff", Http2FrameFlagsToString(Http2FrameType::RST_STREAM, 0xff));
+ EXPECT_EQ("ACK|0xfe",
+ Http2FrameFlagsToString(Http2FrameType::SETTINGS, 0xff));
+ EXPECT_EQ("END_HEADERS|PADDED|0xf3",
+ Http2FrameFlagsToString(Http2FrameType::PUSH_PROMISE, 0xff));
+ EXPECT_EQ("ACK|0xfe", Http2FrameFlagsToString(Http2FrameType::PING, 0xff));
+ EXPECT_EQ("0xff", Http2FrameFlagsToString(Http2FrameType::GOAWAY, 0xff));
+ EXPECT_EQ("0xff",
+ Http2FrameFlagsToString(Http2FrameType::WINDOW_UPDATE, 0xff));
+ EXPECT_EQ("END_HEADERS|0xfb",
+ Http2FrameFlagsToString(Http2FrameType::CONTINUATION, 0xff));
+ EXPECT_EQ("0xff", Http2FrameFlagsToString(Http2FrameType::ALTSVC, 0xff));
+ EXPECT_EQ("0xff", Http2FrameFlagsToString(0xff, 0xff));
+}
+
+TEST(Http2ConstantsTest, Http2ErrorCode) {
+ EXPECT_EQ(Http2ErrorCode::HTTP2_NO_ERROR, static_cast<Http2ErrorCode>(0x0));
+ EXPECT_EQ(Http2ErrorCode::PROTOCOL_ERROR, static_cast<Http2ErrorCode>(0x1));
+ EXPECT_EQ(Http2ErrorCode::INTERNAL_ERROR, static_cast<Http2ErrorCode>(0x2));
+ EXPECT_EQ(Http2ErrorCode::FLOW_CONTROL_ERROR,
+ static_cast<Http2ErrorCode>(0x3));
+ EXPECT_EQ(Http2ErrorCode::SETTINGS_TIMEOUT, static_cast<Http2ErrorCode>(0x4));
+ EXPECT_EQ(Http2ErrorCode::STREAM_CLOSED, static_cast<Http2ErrorCode>(0x5));
+ EXPECT_EQ(Http2ErrorCode::FRAME_SIZE_ERROR, static_cast<Http2ErrorCode>(0x6));
+ EXPECT_EQ(Http2ErrorCode::REFUSED_STREAM, static_cast<Http2ErrorCode>(0x7));
+ EXPECT_EQ(Http2ErrorCode::CANCEL, static_cast<Http2ErrorCode>(0x8));
+ EXPECT_EQ(Http2ErrorCode::COMPRESSION_ERROR,
+ static_cast<Http2ErrorCode>(0x9));
+ EXPECT_EQ(Http2ErrorCode::CONNECT_ERROR, static_cast<Http2ErrorCode>(0xa));
+ EXPECT_EQ(Http2ErrorCode::ENHANCE_YOUR_CALM,
+ static_cast<Http2ErrorCode>(0xb));
+ EXPECT_EQ(Http2ErrorCode::INADEQUATE_SECURITY,
+ static_cast<Http2ErrorCode>(0xc));
+ EXPECT_EQ(Http2ErrorCode::HTTP_1_1_REQUIRED,
+ static_cast<Http2ErrorCode>(0xd));
+}
+
+TEST(Http2ConstantsTest, Http2ErrorCodeToString) {
+ EXPECT_EQ("NO_ERROR", Http2ErrorCodeToString(Http2ErrorCode::HTTP2_NO_ERROR));
+ EXPECT_EQ("NO_ERROR", Http2ErrorCodeToString(0x0));
+ EXPECT_EQ("PROTOCOL_ERROR",
+ Http2ErrorCodeToString(Http2ErrorCode::PROTOCOL_ERROR));
+ EXPECT_EQ("PROTOCOL_ERROR", Http2ErrorCodeToString(0x1));
+ EXPECT_EQ("INTERNAL_ERROR",
+ Http2ErrorCodeToString(Http2ErrorCode::INTERNAL_ERROR));
+ EXPECT_EQ("INTERNAL_ERROR", Http2ErrorCodeToString(0x2));
+ EXPECT_EQ("FLOW_CONTROL_ERROR",
+ Http2ErrorCodeToString(Http2ErrorCode::FLOW_CONTROL_ERROR));
+ EXPECT_EQ("FLOW_CONTROL_ERROR", Http2ErrorCodeToString(0x3));
+ EXPECT_EQ("SETTINGS_TIMEOUT",
+ Http2ErrorCodeToString(Http2ErrorCode::SETTINGS_TIMEOUT));
+ EXPECT_EQ("SETTINGS_TIMEOUT", Http2ErrorCodeToString(0x4));
+ EXPECT_EQ("STREAM_CLOSED",
+ Http2ErrorCodeToString(Http2ErrorCode::STREAM_CLOSED));
+ EXPECT_EQ("STREAM_CLOSED", Http2ErrorCodeToString(0x5));
+ EXPECT_EQ("FRAME_SIZE_ERROR",
+ Http2ErrorCodeToString(Http2ErrorCode::FRAME_SIZE_ERROR));
+ EXPECT_EQ("FRAME_SIZE_ERROR", Http2ErrorCodeToString(0x6));
+ EXPECT_EQ("REFUSED_STREAM",
+ Http2ErrorCodeToString(Http2ErrorCode::REFUSED_STREAM));
+ EXPECT_EQ("REFUSED_STREAM", Http2ErrorCodeToString(0x7));
+ EXPECT_EQ("CANCEL", Http2ErrorCodeToString(Http2ErrorCode::CANCEL));
+ EXPECT_EQ("CANCEL", Http2ErrorCodeToString(0x8));
+ EXPECT_EQ("COMPRESSION_ERROR",
+ Http2ErrorCodeToString(Http2ErrorCode::COMPRESSION_ERROR));
+ EXPECT_EQ("COMPRESSION_ERROR", Http2ErrorCodeToString(0x9));
+ EXPECT_EQ("CONNECT_ERROR",
+ Http2ErrorCodeToString(Http2ErrorCode::CONNECT_ERROR));
+ EXPECT_EQ("CONNECT_ERROR", Http2ErrorCodeToString(0xa));
+ EXPECT_EQ("ENHANCE_YOUR_CALM",
+ Http2ErrorCodeToString(Http2ErrorCode::ENHANCE_YOUR_CALM));
+ EXPECT_EQ("ENHANCE_YOUR_CALM", Http2ErrorCodeToString(0xb));
+ EXPECT_EQ("INADEQUATE_SECURITY",
+ Http2ErrorCodeToString(Http2ErrorCode::INADEQUATE_SECURITY));
+ EXPECT_EQ("INADEQUATE_SECURITY", Http2ErrorCodeToString(0xc));
+ EXPECT_EQ("HTTP_1_1_REQUIRED",
+ Http2ErrorCodeToString(Http2ErrorCode::HTTP_1_1_REQUIRED));
+ EXPECT_EQ("HTTP_1_1_REQUIRED", Http2ErrorCodeToString(0xd));
+
+ EXPECT_EQ("UnknownErrorCode(0x123)", Http2ErrorCodeToString(0x123));
+}
+
+TEST(Http2ConstantsTest, Http2SettingsParameter) {
+ EXPECT_EQ(Http2SettingsParameter::HEADER_TABLE_SIZE,
+ static_cast<Http2SettingsParameter>(0x1));
+ EXPECT_EQ(Http2SettingsParameter::ENABLE_PUSH,
+ static_cast<Http2SettingsParameter>(0x2));
+ EXPECT_EQ(Http2SettingsParameter::MAX_CONCURRENT_STREAMS,
+ static_cast<Http2SettingsParameter>(0x3));
+ EXPECT_EQ(Http2SettingsParameter::INITIAL_WINDOW_SIZE,
+ static_cast<Http2SettingsParameter>(0x4));
+ EXPECT_EQ(Http2SettingsParameter::MAX_FRAME_SIZE,
+ static_cast<Http2SettingsParameter>(0x5));
+ EXPECT_EQ(Http2SettingsParameter::MAX_HEADER_LIST_SIZE,
+ static_cast<Http2SettingsParameter>(0x6));
+
+ EXPECT_TRUE(IsSupportedHttp2SettingsParameter(
+ Http2SettingsParameter::HEADER_TABLE_SIZE));
+ EXPECT_TRUE(
+ IsSupportedHttp2SettingsParameter(Http2SettingsParameter::ENABLE_PUSH));
+ EXPECT_TRUE(IsSupportedHttp2SettingsParameter(
+ Http2SettingsParameter::MAX_CONCURRENT_STREAMS));
+ EXPECT_TRUE(IsSupportedHttp2SettingsParameter(
+ Http2SettingsParameter::INITIAL_WINDOW_SIZE));
+ EXPECT_TRUE(IsSupportedHttp2SettingsParameter(
+ Http2SettingsParameter::MAX_FRAME_SIZE));
+ EXPECT_TRUE(IsSupportedHttp2SettingsParameter(
+ Http2SettingsParameter::MAX_HEADER_LIST_SIZE));
+
+ EXPECT_FALSE(IsSupportedHttp2SettingsParameter(
+ static_cast<Http2SettingsParameter>(0)));
+ EXPECT_FALSE(IsSupportedHttp2SettingsParameter(
+ static_cast<Http2SettingsParameter>(7)));
+}
+
+TEST(Http2ConstantsTest, Http2SettingsParameterToString) {
+ EXPECT_EQ("HEADER_TABLE_SIZE",
+ Http2SettingsParameterToString(
+ Http2SettingsParameter::HEADER_TABLE_SIZE));
+ EXPECT_EQ("HEADER_TABLE_SIZE", Http2SettingsParameterToString(0x1));
+ EXPECT_EQ("ENABLE_PUSH", Http2SettingsParameterToString(
+ Http2SettingsParameter::ENABLE_PUSH));
+ EXPECT_EQ("ENABLE_PUSH", Http2SettingsParameterToString(0x2));
+ EXPECT_EQ("MAX_CONCURRENT_STREAMS",
+ Http2SettingsParameterToString(
+ Http2SettingsParameter::MAX_CONCURRENT_STREAMS));
+ EXPECT_EQ("MAX_CONCURRENT_STREAMS", Http2SettingsParameterToString(0x3));
+ EXPECT_EQ("INITIAL_WINDOW_SIZE",
+ Http2SettingsParameterToString(
+ Http2SettingsParameter::INITIAL_WINDOW_SIZE));
+ EXPECT_EQ("INITIAL_WINDOW_SIZE", Http2SettingsParameterToString(0x4));
+ EXPECT_EQ("MAX_FRAME_SIZE", Http2SettingsParameterToString(
+ Http2SettingsParameter::MAX_FRAME_SIZE));
+ EXPECT_EQ("MAX_FRAME_SIZE", Http2SettingsParameterToString(0x5));
+ EXPECT_EQ("MAX_HEADER_LIST_SIZE",
+ Http2SettingsParameterToString(
+ Http2SettingsParameter::MAX_HEADER_LIST_SIZE));
+ EXPECT_EQ("MAX_HEADER_LIST_SIZE", Http2SettingsParameterToString(0x6));
+
+ EXPECT_EQ("UnknownSettingsParameter(0x123)",
+ Http2SettingsParameterToString(0x123));
+}
+
+} // namespace
+} // namespace test
+} // namespace http2
diff --git a/chromium/net/third_party/quiche/src/http2/http2_constants_test_util.cc b/chromium/net/third_party/quiche/src/http2/http2_constants_test_util.cc
new file mode 100644
index 00000000000..e729890b5b0
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/http2_constants_test_util.cc
@@ -0,0 +1,84 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/http2/http2_constants_test_util.h"
+
+namespace http2 {
+namespace test {
+
+std::vector<Http2ErrorCode> AllHttp2ErrorCodes() {
+ // clang-format off
+ return {
+ Http2ErrorCode::HTTP2_NO_ERROR,
+ Http2ErrorCode::PROTOCOL_ERROR,
+ Http2ErrorCode::INTERNAL_ERROR,
+ Http2ErrorCode::FLOW_CONTROL_ERROR,
+ Http2ErrorCode::SETTINGS_TIMEOUT,
+ Http2ErrorCode::STREAM_CLOSED,
+ Http2ErrorCode::FRAME_SIZE_ERROR,
+ Http2ErrorCode::REFUSED_STREAM,
+ Http2ErrorCode::CANCEL,
+ Http2ErrorCode::COMPRESSION_ERROR,
+ Http2ErrorCode::CONNECT_ERROR,
+ Http2ErrorCode::ENHANCE_YOUR_CALM,
+ Http2ErrorCode::INADEQUATE_SECURITY,
+ Http2ErrorCode::HTTP_1_1_REQUIRED,
+ };
+ // clang-format on
+}
+
+std::vector<Http2SettingsParameter> AllHttp2SettingsParameters() {
+ // clang-format off
+ return {
+ Http2SettingsParameter::HEADER_TABLE_SIZE,
+ Http2SettingsParameter::ENABLE_PUSH,
+ Http2SettingsParameter::MAX_CONCURRENT_STREAMS,
+ Http2SettingsParameter::INITIAL_WINDOW_SIZE,
+ Http2SettingsParameter::MAX_FRAME_SIZE,
+ Http2SettingsParameter::MAX_HEADER_LIST_SIZE,
+ };
+ // clang-format on
+}
+
+// Returns a mask of flags supported for the specified frame type. Returns
+// zero for unknown frame types.
+uint8_t KnownFlagsMaskForFrameType(Http2FrameType type) {
+ switch (type) {
+ case Http2FrameType::DATA:
+ return Http2FrameFlag::END_STREAM | Http2FrameFlag::PADDED;
+ case Http2FrameType::HEADERS:
+ return Http2FrameFlag::END_STREAM | Http2FrameFlag::END_HEADERS |
+ Http2FrameFlag::PADDED | Http2FrameFlag::PRIORITY;
+ case Http2FrameType::PRIORITY:
+ return 0x00;
+ case Http2FrameType::RST_STREAM:
+ return 0x00;
+ case Http2FrameType::SETTINGS:
+ return Http2FrameFlag::ACK;
+ case Http2FrameType::PUSH_PROMISE:
+ return Http2FrameFlag::END_HEADERS | Http2FrameFlag::PADDED;
+ case Http2FrameType::PING:
+ return Http2FrameFlag::ACK;
+ case Http2FrameType::GOAWAY:
+ return 0x00;
+ case Http2FrameType::WINDOW_UPDATE:
+ return 0x00;
+ case Http2FrameType::CONTINUATION:
+ return Http2FrameFlag::END_HEADERS;
+ case Http2FrameType::ALTSVC:
+ return 0x00;
+ default:
+ return 0x00;
+ }
+}
+
+uint8_t InvalidFlagMaskForFrameType(Http2FrameType type) {
+ if (IsSupportedHttp2FrameType(type)) {
+ return ~KnownFlagsMaskForFrameType(type);
+ }
+ return 0x00;
+}
+
+} // namespace test
+} // namespace http2
diff --git a/chromium/net/third_party/quiche/src/http2/http2_constants_test_util.h b/chromium/net/third_party/quiche/src/http2/http2_constants_test_util.h
new file mode 100644
index 00000000000..6ddc1cd4adf
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/http2_constants_test_util.h
@@ -0,0 +1,34 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_HTTP2_HTTP2_CONSTANTS_TEST_UTIL_H_
+#define QUICHE_HTTP2_HTTP2_CONSTANTS_TEST_UTIL_H_
+
+#include <cstdint>
+#include <vector>
+
+#include "net/third_party/quiche/src/http2/http2_constants.h"
+
+namespace http2 {
+namespace test {
+
+// Returns a vector of all supported RST_STREAM and GOAWAY error codes.
+std::vector<Http2ErrorCode> AllHttp2ErrorCodes();
+
+// Returns a vector of all supported parameters in SETTINGS frames.
+std::vector<Http2SettingsParameter> AllHttp2SettingsParameters();
+
+// Returns a mask of flags supported for the specified frame type. Returns
+// zero for unknown frame types.
+uint8_t KnownFlagsMaskForFrameType(Http2FrameType type);
+
+// Returns a mask of flag bits known to be invalid for the frame type.
+// For unknown frame types, the mask is zero; i.e., we don't know that any
+// are invalid.
+uint8_t InvalidFlagMaskForFrameType(Http2FrameType type);
+
+} // namespace test
+} // namespace http2
+
+#endif // QUICHE_HTTP2_HTTP2_CONSTANTS_TEST_UTIL_H_
diff --git a/chromium/net/third_party/quiche/src/http2/http2_structures.cc b/chromium/net/third_party/quiche/src/http2/http2_structures.cc
new file mode 100644
index 00000000000..7dbaf30aedc
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/http2_structures.cc
@@ -0,0 +1,132 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/http2/http2_structures.h"
+
+#include <cstring> // For std::memcmp
+#include <sstream>
+
+#include "net/third_party/quiche/src/http2/platform/api/http2_string_utils.h"
+
+namespace http2 {
+
+// Http2FrameHeader:
+
+bool Http2FrameHeader::IsProbableHttpResponse() const {
+ return (payload_length == 0x485454 && // "HTT"
+ static_cast<char>(type) == 'P' && // "P"
+ flags == '/'); // "/"
+}
+
+Http2String Http2FrameHeader::ToString() const {
+ return Http2StrCat("length=", payload_length,
+ ", type=", Http2FrameTypeToString(type),
+ ", flags=", FlagsToString(), ", stream=", stream_id);
+}
+
+Http2String Http2FrameHeader::FlagsToString() const {
+ return Http2FrameFlagsToString(type, flags);
+}
+
+bool operator==(const Http2FrameHeader& a, const Http2FrameHeader& b) {
+ return a.payload_length == b.payload_length && a.stream_id == b.stream_id &&
+ a.type == b.type && a.flags == b.flags;
+}
+
+std::ostream& operator<<(std::ostream& out, const Http2FrameHeader& v) {
+ return out << v.ToString();
+}
+
+// Http2PriorityFields:
+
+bool operator==(const Http2PriorityFields& a, const Http2PriorityFields& b) {
+ return a.stream_dependency == b.stream_dependency && a.weight == b.weight;
+}
+
+Http2String Http2PriorityFields::ToString() const {
+ std::stringstream ss;
+ ss << "E=" << (is_exclusive ? "true" : "false")
+ << ", stream=" << stream_dependency
+ << ", weight=" << static_cast<uint32_t>(weight);
+ return ss.str();
+}
+
+std::ostream& operator<<(std::ostream& out, const Http2PriorityFields& v) {
+ return out << v.ToString();
+}
+
+// Http2RstStreamFields:
+
+bool operator==(const Http2RstStreamFields& a, const Http2RstStreamFields& b) {
+ return a.error_code == b.error_code;
+}
+
+std::ostream& operator<<(std::ostream& out, const Http2RstStreamFields& v) {
+ return out << "error_code=" << v.error_code;
+}
+
+// Http2SettingFields:
+
+bool operator==(const Http2SettingFields& a, const Http2SettingFields& b) {
+ return a.parameter == b.parameter && a.value == b.value;
+}
+std::ostream& operator<<(std::ostream& out, const Http2SettingFields& v) {
+ return out << "parameter=" << v.parameter << ", value=" << v.value;
+}
+
+// Http2PushPromiseFields:
+
+bool operator==(const Http2PushPromiseFields& a,
+ const Http2PushPromiseFields& b) {
+ return a.promised_stream_id == b.promised_stream_id;
+}
+
+std::ostream& operator<<(std::ostream& out, const Http2PushPromiseFields& v) {
+ return out << "promised_stream_id=" << v.promised_stream_id;
+}
+
+// Http2PingFields:
+
+bool operator==(const Http2PingFields& a, const Http2PingFields& b) {
+ static_assert((sizeof a.opaque_bytes) == Http2PingFields::EncodedSize(),
+ "Why not the same size?");
+ return 0 ==
+ std::memcmp(a.opaque_bytes, b.opaque_bytes, sizeof a.opaque_bytes);
+}
+
+std::ostream& operator<<(std::ostream& out, const Http2PingFields& v) {
+ return out << "opaque_bytes=0x"
+ << Http2HexEncode(v.opaque_bytes, sizeof v.opaque_bytes);
+}
+
+// Http2GoAwayFields:
+
+bool operator==(const Http2GoAwayFields& a, const Http2GoAwayFields& b) {
+ return a.last_stream_id == b.last_stream_id && a.error_code == b.error_code;
+}
+std::ostream& operator<<(std::ostream& out, const Http2GoAwayFields& v) {
+ return out << "last_stream_id=" << v.last_stream_id
+ << ", error_code=" << v.error_code;
+}
+
+// Http2WindowUpdateFields:
+
+bool operator==(const Http2WindowUpdateFields& a,
+ const Http2WindowUpdateFields& b) {
+ return a.window_size_increment == b.window_size_increment;
+}
+std::ostream& operator<<(std::ostream& out, const Http2WindowUpdateFields& v) {
+ return out << "window_size_increment=" << v.window_size_increment;
+}
+
+// Http2AltSvcFields:
+
+bool operator==(const Http2AltSvcFields& a, const Http2AltSvcFields& b) {
+ return a.origin_length == b.origin_length;
+}
+std::ostream& operator<<(std::ostream& out, const Http2AltSvcFields& v) {
+ return out << "origin_length=" << v.origin_length;
+}
+
+} // namespace http2
diff --git a/chromium/net/third_party/quiche/src/http2/http2_structures.h b/chromium/net/third_party/quiche/src/http2/http2_structures.h
new file mode 100644
index 00000000000..01c011e1f9c
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/http2_structures.h
@@ -0,0 +1,325 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_HTTP2_HTTP2_STRUCTURES_H_
+#define QUICHE_HTTP2_HTTP2_STRUCTURES_H_
+
+// Defines structs for various fixed sized structures in HTTP/2.
+//
+// Those structs with multiple fields have constructors that take arguments in
+// the same order as their encoding (which may be different from their order
+// in the struct). For single field structs, use aggregate initialization if
+// desired, e.g.:
+//
+// Http2RstStreamFields var{Http2ErrorCode::ENHANCE_YOUR_CALM};
+// or:
+// SomeFunc(Http2RstStreamFields{Http2ErrorCode::ENHANCE_YOUR_CALM});
+//
+// Each struct includes a static method EncodedSize which returns the number
+// of bytes of the encoding.
+//
+// With the exception of Http2FrameHeader, all the types are named
+// Http2<X>Fields, where X is the title-case form of the frame which always
+// includes the fields; the "always" is to cover the case of the PRIORITY frame;
+// its fields optionally appear in the HEADERS frame, but the struct is called
+// Http2PriorityFields.
+
+#include <stddef.h>
+
+#include <cstdint>
+#include <ostream>
+
+#include "base/logging.h"
+#include "net/third_party/quiche/src/http2/http2_constants.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_export.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string.h"
+
+namespace http2 {
+
+struct HTTP2_EXPORT_PRIVATE Http2FrameHeader {
+ Http2FrameHeader() {}
+ Http2FrameHeader(uint32_t payload_length,
+ Http2FrameType type,
+ uint8_t flags,
+ uint32_t stream_id)
+ : payload_length(payload_length),
+ stream_id(stream_id),
+ type(type),
+ flags(static_cast<Http2FrameFlag>(flags)) {
+ DCHECK_LT(payload_length, static_cast<uint32_t>(1 << 24))
+ << "Payload Length is only a 24 bit field\n"
+ << ToString();
+ }
+
+ static constexpr size_t EncodedSize() { return 9; }
+
+ // Keep the current value of those flags that are in
+ // valid_flags, and clear all the others.
+ void RetainFlags(uint8_t valid_flags) {
+ flags = static_cast<Http2FrameFlag>(flags & valid_flags);
+ }
+
+ // Returns true if any of the flags in flag_mask are set,
+ // otherwise false.
+ bool HasAnyFlags(uint8_t flag_mask) const { return 0 != (flags & flag_mask); }
+
+ // Is the END_STREAM flag set?
+ bool IsEndStream() const {
+ DCHECK(type == Http2FrameType::DATA || type == Http2FrameType::HEADERS)
+ << ToString();
+ return (flags & Http2FrameFlag::END_STREAM) != 0;
+ }
+
+ // Is the ACK flag set?
+ bool IsAck() const {
+ DCHECK(type == Http2FrameType::SETTINGS || type == Http2FrameType::PING)
+ << ToString();
+ return (flags & Http2FrameFlag::ACK) != 0;
+ }
+
+ // Is the END_HEADERS flag set?
+ bool IsEndHeaders() const {
+ DCHECK(type == Http2FrameType::HEADERS ||
+ type == Http2FrameType::PUSH_PROMISE ||
+ type == Http2FrameType::CONTINUATION)
+ << ToString();
+ return (flags & Http2FrameFlag::END_HEADERS) != 0;
+ }
+
+ // Is the PADDED flag set?
+ bool IsPadded() const {
+ DCHECK(type == Http2FrameType::DATA || type == Http2FrameType::HEADERS ||
+ type == Http2FrameType::PUSH_PROMISE)
+ << ToString();
+ return (flags & Http2FrameFlag::PADDED) != 0;
+ }
+
+ // Is the PRIORITY flag set?
+ bool HasPriority() const {
+ DCHECK_EQ(type, Http2FrameType::HEADERS) << ToString();
+ return (flags & Http2FrameFlag::PRIORITY) != 0;
+ }
+
+ // Does the encoding of this header start with "HTTP/", indicating that it
+ // might be from a non-HTTP/2 server.
+ bool IsProbableHttpResponse() const;
+
+ // Produce strings useful for debugging/logging messages.
+ Http2String ToString() const;
+ Http2String FlagsToString() const;
+
+ // 24 bit length of the payload after the header, including any padding.
+ // First field in encoding.
+ uint32_t payload_length; // 24 bits
+
+ // 31 bit stream id, with high bit (32nd bit) reserved (must be zero),
+ // and is cleared during decoding.
+ // Fourth field in encoding.
+ uint32_t stream_id;
+
+ // Type of the frame.
+ // Second field in encoding.
+ Http2FrameType type;
+
+ // Flag bits, with interpretations that depend upon the frame type.
+ // Flag bits not used by the frame type are cleared.
+ // Third field in encoding.
+ Http2FrameFlag flags;
+};
+
+HTTP2_EXPORT_PRIVATE bool operator==(const Http2FrameHeader& a,
+ const Http2FrameHeader& b);
+HTTP2_EXPORT_PRIVATE inline bool operator!=(const Http2FrameHeader& a,
+ const Http2FrameHeader& b) {
+ return !(a == b);
+}
+HTTP2_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& out,
+ const Http2FrameHeader& v);
+
+// Http2PriorityFields:
+
+struct HTTP2_EXPORT_PRIVATE Http2PriorityFields {
+ Http2PriorityFields() {}
+ Http2PriorityFields(uint32_t stream_dependency,
+ uint32_t weight,
+ bool is_exclusive)
+ : stream_dependency(stream_dependency),
+ weight(weight),
+ is_exclusive(is_exclusive) {
+ // Can't have the high-bit set in the stream id because we need to use
+ // that for the EXCLUSIVE flag bit.
+ DCHECK_EQ(stream_dependency, stream_dependency & StreamIdMask())
+ << "Stream Dependency is only a 31-bit field.\n"
+ << ToString();
+ DCHECK_LE(1u, weight) << "Weight is too small.";
+ DCHECK_LE(weight, 256u) << "Weight is too large.";
+ }
+ static constexpr size_t EncodedSize() { return 5; }
+
+ // Produce strings useful for debugging/logging messages.
+ Http2String ToString() const;
+
+ // A 31-bit stream identifier for the stream that this stream depends on.
+ uint32_t stream_dependency;
+
+ // Weight (1 to 256) is encoded as a byte in the range 0 to 255, so we
+ // add one when decoding, and store it in a field larger than a byte.
+ uint32_t weight;
+
+ // A single-bit flag indicating that the stream dependency is exclusive;
+ // extracted from high bit of stream dependency field during decoding.
+ bool is_exclusive;
+};
+
+HTTP2_EXPORT_PRIVATE bool operator==(const Http2PriorityFields& a,
+ const Http2PriorityFields& b);
+HTTP2_EXPORT_PRIVATE inline bool operator!=(const Http2PriorityFields& a,
+ const Http2PriorityFields& b) {
+ return !(a == b);
+}
+HTTP2_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& out,
+ const Http2PriorityFields& v);
+
+// Http2RstStreamFields:
+
+struct Http2RstStreamFields {
+ static constexpr size_t EncodedSize() { return 4; }
+ bool IsSupportedErrorCode() const {
+ return IsSupportedHttp2ErrorCode(error_code);
+ }
+
+ Http2ErrorCode error_code;
+};
+
+HTTP2_EXPORT_PRIVATE bool operator==(const Http2RstStreamFields& a,
+ const Http2RstStreamFields& b);
+HTTP2_EXPORT_PRIVATE inline bool operator!=(const Http2RstStreamFields& a,
+ const Http2RstStreamFields& b) {
+ return !(a == b);
+}
+HTTP2_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& out,
+ const Http2RstStreamFields& v);
+
+// Http2SettingFields:
+
+struct Http2SettingFields {
+ Http2SettingFields() {}
+ Http2SettingFields(Http2SettingsParameter parameter, uint32_t value)
+ : parameter(parameter), value(value) {}
+ static constexpr size_t EncodedSize() { return 6; }
+ bool IsSupportedParameter() const {
+ return IsSupportedHttp2SettingsParameter(parameter);
+ }
+
+ Http2SettingsParameter parameter;
+ uint32_t value;
+};
+
+HTTP2_EXPORT_PRIVATE bool operator==(const Http2SettingFields& a,
+ const Http2SettingFields& b);
+HTTP2_EXPORT_PRIVATE inline bool operator!=(const Http2SettingFields& a,
+ const Http2SettingFields& b) {
+ return !(a == b);
+}
+HTTP2_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& out,
+ const Http2SettingFields& v);
+
+// Http2PushPromiseFields:
+
+struct Http2PushPromiseFields {
+ static constexpr size_t EncodedSize() { return 4; }
+
+ uint32_t promised_stream_id;
+};
+
+HTTP2_EXPORT_PRIVATE bool operator==(const Http2PushPromiseFields& a,
+ const Http2PushPromiseFields& b);
+HTTP2_EXPORT_PRIVATE inline bool operator!=(const Http2PushPromiseFields& a,
+ const Http2PushPromiseFields& b) {
+ return !(a == b);
+}
+HTTP2_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& out,
+ const Http2PushPromiseFields& v);
+
+// Http2PingFields:
+
+struct Http2PingFields {
+ static constexpr size_t EncodedSize() { return 8; }
+
+ uint8_t opaque_bytes[8];
+};
+
+HTTP2_EXPORT_PRIVATE bool operator==(const Http2PingFields& a,
+ const Http2PingFields& b);
+HTTP2_EXPORT_PRIVATE inline bool operator!=(const Http2PingFields& a,
+ const Http2PingFields& b) {
+ return !(a == b);
+}
+HTTP2_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& out,
+ const Http2PingFields& v);
+
+// Http2GoAwayFields:
+
+struct Http2GoAwayFields {
+ Http2GoAwayFields() {}
+ Http2GoAwayFields(uint32_t last_stream_id, Http2ErrorCode error_code)
+ : last_stream_id(last_stream_id), error_code(error_code) {}
+ static constexpr size_t EncodedSize() { return 8; }
+ bool IsSupportedErrorCode() const {
+ return IsSupportedHttp2ErrorCode(error_code);
+ }
+
+ uint32_t last_stream_id;
+ Http2ErrorCode error_code;
+};
+
+HTTP2_EXPORT_PRIVATE bool operator==(const Http2GoAwayFields& a,
+ const Http2GoAwayFields& b);
+HTTP2_EXPORT_PRIVATE inline bool operator!=(const Http2GoAwayFields& a,
+ const Http2GoAwayFields& b) {
+ return !(a == b);
+}
+HTTP2_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& out,
+ const Http2GoAwayFields& v);
+
+// Http2WindowUpdateFields:
+
+struct Http2WindowUpdateFields {
+ static constexpr size_t EncodedSize() { return 4; }
+
+ // 31-bit, unsigned increase in the window size (only positive values are
+ // allowed). The high-bit is reserved for the future.
+ uint32_t window_size_increment;
+};
+
+HTTP2_EXPORT_PRIVATE bool operator==(const Http2WindowUpdateFields& a,
+ const Http2WindowUpdateFields& b);
+HTTP2_EXPORT_PRIVATE inline bool operator!=(const Http2WindowUpdateFields& a,
+ const Http2WindowUpdateFields& b) {
+ return !(a == b);
+}
+HTTP2_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& out,
+ const Http2WindowUpdateFields& v);
+
+// Http2AltSvcFields:
+
+struct Http2AltSvcFields {
+ static constexpr size_t EncodedSize() { return 2; }
+
+ // This is the one fixed size portion of the ALTSVC payload.
+ uint16_t origin_length;
+};
+
+HTTP2_EXPORT_PRIVATE bool operator==(const Http2AltSvcFields& a,
+ const Http2AltSvcFields& b);
+HTTP2_EXPORT_PRIVATE inline bool operator!=(const Http2AltSvcFields& a,
+ const Http2AltSvcFields& b) {
+ return !(a == b);
+}
+HTTP2_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& out,
+ const Http2AltSvcFields& v);
+
+} // namespace http2
+
+#endif // QUICHE_HTTP2_HTTP2_STRUCTURES_H_
diff --git a/chromium/net/third_party/quiche/src/http2/http2_structures_test.cc b/chromium/net/third_party/quiche/src/http2/http2_structures_test.cc
new file mode 100644
index 00000000000..30ebb3660ea
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/http2_structures_test.cc
@@ -0,0 +1,537 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/http2/http2_structures.h"
+
+// Tests are focused on Http2FrameHeader because it has by far the most
+// methods of any of the structures.
+// Note that EXPECT.*DEATH tests are slow (a fork is probably involved).
+
+// And in case you're wondering, yes, these are ridiculously thorough tests,
+// but believe it or not, I've found stupid bugs this way.
+
+#include <memory>
+#include <ostream>
+#include <sstream>
+#include <tuple>
+#include <type_traits>
+#include <vector>
+
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/http2/http2_structures_test_util.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string_utils.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_test_helpers.h"
+#include "net/third_party/quiche/src/http2/test_tools/http2_random.h"
+
+using ::testing::AssertionResult;
+using ::testing::AssertionSuccess;
+using ::testing::Combine;
+using ::testing::HasSubstr;
+using ::testing::MatchesRegex;
+using ::testing::Not;
+using ::testing::Values;
+using ::testing::ValuesIn;
+
+namespace http2 {
+namespace test {
+namespace {
+
+template <typename E>
+E IncrementEnum(E e) {
+ using I = typename std::underlying_type<E>::type;
+ return static_cast<E>(1 + static_cast<I>(e));
+}
+
+template <class T>
+AssertionResult VerifyRandomCalls() {
+ T t1;
+ Http2Random seq1;
+ Randomize(&t1, &seq1);
+
+ T t2;
+ Http2Random seq2(seq1.Key());
+ Randomize(&t2, &seq2);
+
+ // The two Randomize calls should have made the same number of calls into
+ // the Http2Random implementations.
+ VERIFY_EQ(seq1.Rand64(), seq2.Rand64());
+
+ // And because Http2Random implementation is returning the same sequence, and
+ // Randomize should have been consistent in applying those results, the two
+ // Ts should have the same value.
+ VERIFY_EQ(t1, t2);
+
+ Randomize(&t2, &seq2);
+ VERIFY_NE(t1, t2);
+
+ Randomize(&t1, &seq1);
+ VERIFY_EQ(t1, t2);
+
+ VERIFY_EQ(seq1.Rand64(), seq2.Rand64());
+
+ return AssertionSuccess();
+}
+
+#if GTEST_HAS_DEATH_TEST && !defined(NDEBUG)
+std::vector<Http2FrameType> ValidFrameTypes() {
+ std::vector<Http2FrameType> valid_types{Http2FrameType::DATA};
+ while (valid_types.back() != Http2FrameType::ALTSVC) {
+ valid_types.push_back(IncrementEnum(valid_types.back()));
+ }
+ return valid_types;
+}
+#endif // GTEST_HAS_DEATH_TEST && !defined(NDEBUG)
+
+TEST(Http2FrameHeaderTest, Constructor) {
+ Http2Random random;
+ uint8_t frame_type = 0;
+ do {
+ // Only the payload length is DCHECK'd in the constructor, so we need to
+ // make sure it is a "uint24".
+ uint32_t payload_length = random.Rand32() & 0xffffff;
+ Http2FrameType type = static_cast<Http2FrameType>(frame_type);
+ uint8_t flags = random.Rand8();
+ uint32_t stream_id = random.Rand32();
+
+ Http2FrameHeader v(payload_length, type, flags, stream_id);
+
+ EXPECT_EQ(payload_length, v.payload_length);
+ EXPECT_EQ(type, v.type);
+ EXPECT_EQ(flags, v.flags);
+ EXPECT_EQ(stream_id, v.stream_id);
+ } while (frame_type++ == 255);
+
+#if GTEST_HAS_DEATH_TEST && !defined(NDEBUG)
+ EXPECT_DEBUG_DEATH(Http2FrameHeader(0x01000000, Http2FrameType::DATA, 0, 1),
+ "payload_length");
+#endif // GTEST_HAS_DEATH_TEST && !defined(NDEBUG)
+}
+
+TEST(Http2FrameHeaderTest, Eq) {
+ Http2Random random;
+ uint32_t payload_length = random.Rand32() & 0xffffff;
+ Http2FrameType type = static_cast<Http2FrameType>(random.Rand8());
+
+ uint8_t flags = random.Rand8();
+ uint32_t stream_id = random.Rand32();
+
+ Http2FrameHeader v(payload_length, type, flags, stream_id);
+
+ EXPECT_EQ(payload_length, v.payload_length);
+ EXPECT_EQ(type, v.type);
+ EXPECT_EQ(flags, v.flags);
+ EXPECT_EQ(stream_id, v.stream_id);
+
+ Http2FrameHeader u(0, type, ~flags, stream_id);
+
+ EXPECT_NE(u, v);
+ EXPECT_NE(v, u);
+ EXPECT_FALSE(u == v);
+ EXPECT_FALSE(v == u);
+ EXPECT_TRUE(u != v);
+ EXPECT_TRUE(v != u);
+
+ u = v;
+
+ EXPECT_EQ(u, v);
+ EXPECT_EQ(v, u);
+ EXPECT_TRUE(u == v);
+ EXPECT_TRUE(v == u);
+ EXPECT_FALSE(u != v);
+ EXPECT_FALSE(v != u);
+
+ EXPECT_TRUE(VerifyRandomCalls<Http2FrameHeader>());
+}
+
+#if GTEST_HAS_DEATH_TEST && !defined(NDEBUG)
+// The tests of the valid frame types include EXPECT_DEBUG_DEATH, which is
+// quite slow, so using value parameterized tests in order to allow sharding.
+class Http2FrameHeaderTypeAndFlagTest
+ : public ::testing::TestWithParam<
+ std::tuple<Http2FrameType, Http2FrameFlag>> {
+ protected:
+ Http2FrameHeaderTypeAndFlagTest()
+ : type_(std::get<0>(GetParam())), flags_(std::get<1>(GetParam())) {
+ LOG(INFO) << "Frame type: " << type_;
+ LOG(INFO) << "Frame flags: " << Http2FrameFlagsToString(type_, flags_);
+ }
+
+ const Http2FrameType type_;
+ const Http2FrameFlag flags_;
+};
+
+class IsEndStreamTest : public Http2FrameHeaderTypeAndFlagTest {};
+INSTANTIATE_TEST_CASE_P(IsEndStream,
+ IsEndStreamTest,
+ Combine(ValuesIn(ValidFrameTypes()),
+ Values(~Http2FrameFlag::END_STREAM, 0xff)));
+TEST_P(IsEndStreamTest, IsEndStream) {
+ const bool is_set =
+ (flags_ & Http2FrameFlag::END_STREAM) == Http2FrameFlag::END_STREAM;
+ Http2String flags_string;
+ Http2FrameHeader v(0, type_, flags_, 0);
+ switch (type_) {
+ case Http2FrameType::DATA:
+ case Http2FrameType::HEADERS:
+ EXPECT_EQ(is_set, v.IsEndStream()) << v;
+ flags_string = v.FlagsToString();
+ if (is_set) {
+ EXPECT_THAT(flags_string, MatchesRegex(".*\\|?END_STREAM\\|.*"));
+ } else {
+ EXPECT_THAT(flags_string, Not(HasSubstr("END_STREAM")));
+ }
+ v.RetainFlags(Http2FrameFlag::END_STREAM);
+ EXPECT_EQ(is_set, v.IsEndStream()) << v;
+ {
+ std::stringstream s;
+ s << v;
+ EXPECT_EQ(v.ToString(), s.str());
+ if (is_set) {
+ EXPECT_THAT(s.str(), HasSubstr("flags=END_STREAM,"));
+ } else {
+ EXPECT_THAT(s.str(), HasSubstr("flags=,"));
+ }
+ }
+ break;
+ default:
+ EXPECT_DEBUG_DEATH(v.IsEndStream(), "DATA.*HEADERS") << v;
+ }
+}
+
+class IsACKTest : public Http2FrameHeaderTypeAndFlagTest {};
+INSTANTIATE_TEST_CASE_P(IsAck,
+ IsACKTest,
+ Combine(ValuesIn(ValidFrameTypes()),
+ Values(~Http2FrameFlag::ACK, 0xff)));
+TEST_P(IsACKTest, IsAck) {
+ const bool is_set = (flags_ & Http2FrameFlag::ACK) == Http2FrameFlag::ACK;
+ Http2String flags_string;
+ Http2FrameHeader v(0, type_, flags_, 0);
+ switch (type_) {
+ case Http2FrameType::SETTINGS:
+ case Http2FrameType::PING:
+ EXPECT_EQ(is_set, v.IsAck()) << v;
+ flags_string = v.FlagsToString();
+ if (is_set) {
+ EXPECT_THAT(flags_string, MatchesRegex(".*\\|?ACK\\|.*"));
+ } else {
+ EXPECT_THAT(flags_string, Not(HasSubstr("ACK")));
+ }
+ v.RetainFlags(Http2FrameFlag::ACK);
+ EXPECT_EQ(is_set, v.IsAck()) << v;
+ {
+ std::stringstream s;
+ s << v;
+ EXPECT_EQ(v.ToString(), s.str());
+ if (is_set) {
+ EXPECT_THAT(s.str(), HasSubstr("flags=ACK,"));
+ } else {
+ EXPECT_THAT(s.str(), HasSubstr("flags=,"));
+ }
+ }
+ break;
+ default:
+ EXPECT_DEBUG_DEATH(v.IsAck(), "SETTINGS.*PING") << v;
+ }
+}
+
+class IsEndHeadersTest : public Http2FrameHeaderTypeAndFlagTest {};
+INSTANTIATE_TEST_CASE_P(IsEndHeaders,
+ IsEndHeadersTest,
+ Combine(ValuesIn(ValidFrameTypes()),
+ Values(~Http2FrameFlag::END_HEADERS, 0xff)));
+TEST_P(IsEndHeadersTest, IsEndHeaders) {
+ const bool is_set =
+ (flags_ & Http2FrameFlag::END_HEADERS) == Http2FrameFlag::END_HEADERS;
+ Http2String flags_string;
+ Http2FrameHeader v(0, type_, flags_, 0);
+ switch (type_) {
+ case Http2FrameType::HEADERS:
+ case Http2FrameType::PUSH_PROMISE:
+ case Http2FrameType::CONTINUATION:
+ EXPECT_EQ(is_set, v.IsEndHeaders()) << v;
+ flags_string = v.FlagsToString();
+ if (is_set) {
+ EXPECT_THAT(flags_string, MatchesRegex(".*\\|?END_HEADERS\\|.*"));
+ } else {
+ EXPECT_THAT(flags_string, Not(HasSubstr("END_HEADERS")));
+ }
+ v.RetainFlags(Http2FrameFlag::END_HEADERS);
+ EXPECT_EQ(is_set, v.IsEndHeaders()) << v;
+ {
+ std::stringstream s;
+ s << v;
+ EXPECT_EQ(v.ToString(), s.str());
+ if (is_set) {
+ EXPECT_THAT(s.str(), HasSubstr("flags=END_HEADERS,"));
+ } else {
+ EXPECT_THAT(s.str(), HasSubstr("flags=,"));
+ }
+ }
+ break;
+ default:
+ EXPECT_DEBUG_DEATH(v.IsEndHeaders(),
+ "HEADERS.*PUSH_PROMISE.*CONTINUATION")
+ << v;
+ }
+}
+
+class IsPaddedTest : public Http2FrameHeaderTypeAndFlagTest {};
+INSTANTIATE_TEST_CASE_P(IsPadded,
+ IsPaddedTest,
+ Combine(ValuesIn(ValidFrameTypes()),
+ Values(~Http2FrameFlag::PADDED, 0xff)));
+TEST_P(IsPaddedTest, IsPadded) {
+ const bool is_set =
+ (flags_ & Http2FrameFlag::PADDED) == Http2FrameFlag::PADDED;
+ Http2String flags_string;
+ Http2FrameHeader v(0, type_, flags_, 0);
+ switch (type_) {
+ case Http2FrameType::DATA:
+ case Http2FrameType::HEADERS:
+ case Http2FrameType::PUSH_PROMISE:
+ EXPECT_EQ(is_set, v.IsPadded()) << v;
+ flags_string = v.FlagsToString();
+ if (is_set) {
+ EXPECT_THAT(flags_string, MatchesRegex(".*\\|?PADDED\\|.*"));
+ } else {
+ EXPECT_THAT(flags_string, Not(HasSubstr("PADDED")));
+ }
+ v.RetainFlags(Http2FrameFlag::PADDED);
+ EXPECT_EQ(is_set, v.IsPadded()) << v;
+ {
+ std::stringstream s;
+ s << v;
+ EXPECT_EQ(v.ToString(), s.str());
+ if (is_set) {
+ EXPECT_THAT(s.str(), HasSubstr("flags=PADDED,"));
+ } else {
+ EXPECT_THAT(s.str(), HasSubstr("flags=,"));
+ }
+ }
+ break;
+ default:
+ EXPECT_DEBUG_DEATH(v.IsPadded(), "DATA.*HEADERS.*PUSH_PROMISE") << v;
+ }
+}
+
+class HasPriorityTest : public Http2FrameHeaderTypeAndFlagTest {};
+INSTANTIATE_TEST_CASE_P(HasPriority,
+ HasPriorityTest,
+ Combine(ValuesIn(ValidFrameTypes()),
+ Values(~Http2FrameFlag::PRIORITY, 0xff)));
+TEST_P(HasPriorityTest, HasPriority) {
+ const bool is_set =
+ (flags_ & Http2FrameFlag::PRIORITY) == Http2FrameFlag::PRIORITY;
+ Http2String flags_string;
+ Http2FrameHeader v(0, type_, flags_, 0);
+ switch (type_) {
+ case Http2FrameType::HEADERS:
+ EXPECT_EQ(is_set, v.HasPriority()) << v;
+ flags_string = v.FlagsToString();
+ if (is_set) {
+ EXPECT_THAT(flags_string, MatchesRegex(".*\\|?PRIORITY\\|.*"));
+ } else {
+ EXPECT_THAT(flags_string, Not(HasSubstr("PRIORITY")));
+ }
+ v.RetainFlags(Http2FrameFlag::PRIORITY);
+ EXPECT_EQ(is_set, v.HasPriority()) << v;
+ {
+ std::stringstream s;
+ s << v;
+ EXPECT_EQ(v.ToString(), s.str());
+ if (is_set) {
+ EXPECT_THAT(s.str(), HasSubstr("flags=PRIORITY,"));
+ } else {
+ EXPECT_THAT(s.str(), HasSubstr("flags=,"));
+ }
+ }
+ break;
+ default:
+ EXPECT_DEBUG_DEATH(v.HasPriority(), "HEADERS") << v;
+ }
+}
+
+TEST(Http2PriorityFieldsTest, Constructor) {
+ Http2Random random;
+ uint32_t stream_dependency = random.Rand32() & StreamIdMask();
+ uint32_t weight = 1 + random.Rand8();
+ bool is_exclusive = random.OneIn(2);
+
+ Http2PriorityFields v(stream_dependency, weight, is_exclusive);
+
+ EXPECT_EQ(stream_dependency, v.stream_dependency);
+ EXPECT_EQ(weight, v.weight);
+ EXPECT_EQ(is_exclusive, v.is_exclusive);
+
+ // The high-bit must not be set on the stream id.
+ EXPECT_DEBUG_DEATH(
+ Http2PriorityFields(stream_dependency | 0x80000000, weight, is_exclusive),
+ "31-bit");
+
+ // The weight must be in the range 1-256.
+ EXPECT_DEBUG_DEATH(Http2PriorityFields(stream_dependency, 0, is_exclusive),
+ "too small");
+ EXPECT_DEBUG_DEATH(
+ Http2PriorityFields(stream_dependency, weight + 256, is_exclusive),
+ "too large");
+
+ EXPECT_TRUE(VerifyRandomCalls<Http2PriorityFields>());
+}
+#endif // GTEST_HAS_DEATH_TEST && !defined(NDEBUG)
+
+TEST(Http2RstStreamFieldsTest, IsSupported) {
+ Http2RstStreamFields v{Http2ErrorCode::HTTP2_NO_ERROR};
+ EXPECT_TRUE(v.IsSupportedErrorCode()) << v;
+
+ Http2RstStreamFields u{static_cast<Http2ErrorCode>(~0)};
+ EXPECT_FALSE(u.IsSupportedErrorCode()) << v;
+
+ EXPECT_TRUE(VerifyRandomCalls<Http2RstStreamFields>());
+}
+
+TEST(Http2SettingFieldsTest, Misc) {
+ Http2Random random;
+ Http2SettingsParameter parameter =
+ static_cast<Http2SettingsParameter>(random.Rand16());
+ uint32_t value = random.Rand32();
+
+ Http2SettingFields v(parameter, value);
+
+ EXPECT_EQ(v, v);
+ EXPECT_EQ(parameter, v.parameter);
+ EXPECT_EQ(value, v.value);
+
+ if (static_cast<uint16_t>(parameter) < 7) {
+ EXPECT_TRUE(v.IsSupportedParameter()) << v;
+ } else {
+ EXPECT_FALSE(v.IsSupportedParameter()) << v;
+ }
+
+ Http2SettingFields u(parameter, ~value);
+ EXPECT_NE(v, u);
+ EXPECT_EQ(v.parameter, u.parameter);
+ EXPECT_NE(v.value, u.value);
+
+ Http2SettingFields w(IncrementEnum(parameter), value);
+ EXPECT_NE(v, w);
+ EXPECT_NE(v.parameter, w.parameter);
+ EXPECT_EQ(v.value, w.value);
+
+ Http2SettingFields x(Http2SettingsParameter::MAX_FRAME_SIZE, 123);
+ std::stringstream s;
+ s << x;
+ EXPECT_EQ("parameter=MAX_FRAME_SIZE, value=123", s.str());
+
+ EXPECT_TRUE(VerifyRandomCalls<Http2SettingFields>());
+}
+
+TEST(Http2PushPromiseTest, Misc) {
+ Http2Random random;
+ uint32_t promised_stream_id = random.Rand32() & StreamIdMask();
+
+ Http2PushPromiseFields v{promised_stream_id};
+ EXPECT_EQ(promised_stream_id, v.promised_stream_id);
+ EXPECT_EQ(v, v);
+
+ std::stringstream s;
+ s << v;
+ EXPECT_EQ(Http2StrCat("promised_stream_id=", promised_stream_id), s.str());
+
+ // High-bit is reserved, but not used, so we can set it.
+ promised_stream_id |= 0x80000000;
+ Http2PushPromiseFields w{promised_stream_id};
+ EXPECT_EQ(w, w);
+ EXPECT_NE(v, w);
+
+ v.promised_stream_id = promised_stream_id;
+ EXPECT_EQ(v, w);
+
+ EXPECT_TRUE(VerifyRandomCalls<Http2PushPromiseFields>());
+}
+
+TEST(Http2PingFieldsTest, Misc) {
+ Http2PingFields v{{'8', ' ', 'b', 'y', 't', 'e', 's', '\0'}};
+ std::stringstream s;
+ s << v;
+ EXPECT_EQ("opaque_bytes=0x3820627974657300", s.str());
+
+ EXPECT_TRUE(VerifyRandomCalls<Http2PingFields>());
+}
+
+TEST(Http2GoAwayFieldsTest, Misc) {
+ Http2Random random;
+ uint32_t last_stream_id = random.Rand32() & StreamIdMask();
+ Http2ErrorCode error_code = static_cast<Http2ErrorCode>(random.Rand32());
+
+ Http2GoAwayFields v(last_stream_id, error_code);
+ EXPECT_EQ(v, v);
+ EXPECT_EQ(last_stream_id, v.last_stream_id);
+ EXPECT_EQ(error_code, v.error_code);
+
+ if (static_cast<uint32_t>(error_code) < 14) {
+ EXPECT_TRUE(v.IsSupportedErrorCode()) << v;
+ } else {
+ EXPECT_FALSE(v.IsSupportedErrorCode()) << v;
+ }
+
+ Http2GoAwayFields u(~last_stream_id, error_code);
+ EXPECT_NE(v, u);
+ EXPECT_NE(v.last_stream_id, u.last_stream_id);
+ EXPECT_EQ(v.error_code, u.error_code);
+
+ EXPECT_TRUE(VerifyRandomCalls<Http2GoAwayFields>());
+}
+
+TEST(Http2WindowUpdateTest, Misc) {
+ Http2Random random;
+ uint32_t window_size_increment = random.Rand32() & UInt31Mask();
+
+ Http2WindowUpdateFields v{window_size_increment};
+ EXPECT_EQ(window_size_increment, v.window_size_increment);
+ EXPECT_EQ(v, v);
+
+ std::stringstream s;
+ s << v;
+ EXPECT_EQ(Http2StrCat("window_size_increment=", window_size_increment),
+ s.str());
+
+ // High-bit is reserved, but not used, so we can set it.
+ window_size_increment |= 0x80000000;
+ Http2WindowUpdateFields w{window_size_increment};
+ EXPECT_EQ(w, w);
+ EXPECT_NE(v, w);
+
+ v.window_size_increment = window_size_increment;
+ EXPECT_EQ(v, w);
+
+ EXPECT_TRUE(VerifyRandomCalls<Http2WindowUpdateFields>());
+}
+
+TEST(Http2AltSvcTest, Misc) {
+ Http2Random random;
+ uint16_t origin_length = random.Rand16();
+
+ Http2AltSvcFields v{origin_length};
+ EXPECT_EQ(origin_length, v.origin_length);
+ EXPECT_EQ(v, v);
+
+ std::stringstream s;
+ s << v;
+ EXPECT_EQ(Http2StrCat("origin_length=", origin_length), s.str());
+
+ Http2AltSvcFields w{++origin_length};
+ EXPECT_EQ(w, w);
+ EXPECT_NE(v, w);
+
+ v.origin_length = w.origin_length;
+ EXPECT_EQ(v, w);
+
+ EXPECT_TRUE(VerifyRandomCalls<Http2AltSvcFields>());
+}
+
+} // namespace
+} // namespace test
+} // namespace http2
diff --git a/chromium/net/third_party/quiche/src/http2/http2_structures_test_util.cc b/chromium/net/third_party/quiche/src/http2/http2_structures_test_util.cc
new file mode 100644
index 00000000000..4a1a74d8ee9
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/http2_structures_test_util.cc
@@ -0,0 +1,109 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/http2/http2_structures_test_util.h"
+
+#include <cstdint>
+
+#include "net/third_party/quiche/src/http2/http2_constants.h"
+#include "net/third_party/quiche/src/http2/http2_constants_test_util.h"
+#include "net/third_party/quiche/src/http2/http2_structures.h"
+#include "net/third_party/quiche/src/http2/test_tools/http2_random.h"
+
+namespace http2 {
+namespace test {
+
+void Randomize(Http2FrameHeader* p, Http2Random* rng) {
+ p->payload_length = rng->Rand32() & 0xffffff;
+ p->type = static_cast<Http2FrameType>(rng->Rand8());
+ p->flags = static_cast<Http2FrameFlag>(rng->Rand8());
+ p->stream_id = rng->Rand32() & StreamIdMask();
+}
+void Randomize(Http2PriorityFields* p, Http2Random* rng) {
+ p->stream_dependency = rng->Rand32() & StreamIdMask();
+ p->weight = rng->Rand8() + 1;
+ p->is_exclusive = rng->OneIn(2);
+}
+void Randomize(Http2RstStreamFields* p, Http2Random* rng) {
+ p->error_code = static_cast<Http2ErrorCode>(rng->Rand32());
+}
+void Randomize(Http2SettingFields* p, Http2Random* rng) {
+ p->parameter = static_cast<Http2SettingsParameter>(rng->Rand16());
+ p->value = rng->Rand32();
+}
+void Randomize(Http2PushPromiseFields* p, Http2Random* rng) {
+ p->promised_stream_id = rng->Rand32() & StreamIdMask();
+}
+void Randomize(Http2PingFields* p, Http2Random* rng) {
+ for (int ndx = 0; ndx < 8; ++ndx) {
+ p->opaque_bytes[ndx] = rng->Rand8();
+ }
+}
+void Randomize(Http2GoAwayFields* p, Http2Random* rng) {
+ p->last_stream_id = rng->Rand32() & StreamIdMask();
+ p->error_code = static_cast<Http2ErrorCode>(rng->Rand32());
+}
+void Randomize(Http2WindowUpdateFields* p, Http2Random* rng) {
+ p->window_size_increment = rng->Rand32() & 0x7fffffff;
+}
+void Randomize(Http2AltSvcFields* p, Http2Random* rng) {
+ p->origin_length = rng->Rand16();
+}
+
+void ScrubFlagsOfHeader(Http2FrameHeader* header) {
+ uint8_t invalid_mask = InvalidFlagMaskForFrameType(header->type);
+ uint8_t keep_mask = ~invalid_mask;
+ header->RetainFlags(keep_mask);
+}
+
+bool FrameIsPadded(const Http2FrameHeader& header) {
+ switch (header.type) {
+ case Http2FrameType::DATA:
+ case Http2FrameType::HEADERS:
+ case Http2FrameType::PUSH_PROMISE:
+ return header.IsPadded();
+ default:
+ return false;
+ }
+}
+
+bool FrameHasPriority(const Http2FrameHeader& header) {
+ switch (header.type) {
+ case Http2FrameType::HEADERS:
+ return header.HasPriority();
+ case Http2FrameType::PRIORITY:
+ return true;
+ default:
+ return false;
+ }
+}
+
+bool FrameCanHavePayload(const Http2FrameHeader& header) {
+ switch (header.type) {
+ case Http2FrameType::DATA:
+ case Http2FrameType::HEADERS:
+ case Http2FrameType::PUSH_PROMISE:
+ case Http2FrameType::CONTINUATION:
+ case Http2FrameType::PING:
+ case Http2FrameType::GOAWAY:
+ case Http2FrameType::ALTSVC:
+ return true;
+ default:
+ return false;
+ }
+}
+
+bool FrameCanHaveHpackPayload(const Http2FrameHeader& header) {
+ switch (header.type) {
+ case Http2FrameType::HEADERS:
+ case Http2FrameType::PUSH_PROMISE:
+ case Http2FrameType::CONTINUATION:
+ return true;
+ default:
+ return false;
+ }
+}
+
+} // namespace test
+} // namespace http2
diff --git a/chromium/net/third_party/quiche/src/http2/http2_structures_test_util.h b/chromium/net/third_party/quiche/src/http2/http2_structures_test_util.h
new file mode 100644
index 00000000000..86fbf3f20af
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/http2_structures_test_util.h
@@ -0,0 +1,59 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_HTTP2_HTTP2_STRUCTURES_TEST_UTIL_H_
+#define QUICHE_HTTP2_HTTP2_STRUCTURES_TEST_UTIL_H_
+
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/http2/http2_structures.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string.h"
+#include "net/third_party/quiche/src/http2/test_tools/http2_random.h"
+#include "net/third_party/quiche/src/http2/tools/http2_frame_builder.h"
+
+namespace http2 {
+namespace test {
+
+template <class S>
+Http2String SerializeStructure(const S& s) {
+ Http2FrameBuilder fb;
+ fb.Append(s);
+ EXPECT_EQ(S::EncodedSize(), fb.size());
+ return fb.buffer();
+}
+
+// Randomize the members of out, in a manner that yields encodeable contents
+// (e.g. a "uint24" field has only the low 24 bits set).
+void Randomize(Http2FrameHeader* out, Http2Random* rng);
+void Randomize(Http2PriorityFields* out, Http2Random* rng);
+void Randomize(Http2RstStreamFields* out, Http2Random* rng);
+void Randomize(Http2SettingFields* out, Http2Random* rng);
+void Randomize(Http2PushPromiseFields* out, Http2Random* rng);
+void Randomize(Http2PingFields* out, Http2Random* rng);
+void Randomize(Http2GoAwayFields* out, Http2Random* rng);
+void Randomize(Http2WindowUpdateFields* out, Http2Random* rng);
+void Randomize(Http2AltSvcFields* out, Http2Random* rng);
+
+// Clear bits of header->flags that are known to be invalid for the
+// type. For unknown frame types, no change is made.
+void ScrubFlagsOfHeader(Http2FrameHeader* header);
+
+// Is the frame with this header padded? Only true for known/supported frame
+// types.
+bool FrameIsPadded(const Http2FrameHeader& header);
+
+// Does the frame with this header have Http2PriorityFields?
+bool FrameHasPriority(const Http2FrameHeader& header);
+
+// Does the frame with this header have a variable length payload (including
+// empty) payload (e.g. DATA or HEADERS)? Really a test of the frame type.
+bool FrameCanHavePayload(const Http2FrameHeader& header);
+
+// Does the frame with this header have a variable length HPACK payload
+// (including empty) payload (e.g. HEADERS)? Really a test of the frame type.
+bool FrameCanHaveHpackPayload(const Http2FrameHeader& header);
+
+} // namespace test
+} // namespace http2
+
+#endif // QUICHE_HTTP2_HTTP2_STRUCTURES_TEST_UTIL_H_
diff --git a/chromium/net/third_party/quiche/src/http2/platform/api/http2_arraysize.h b/chromium/net/third_party/quiche/src/http2/platform/api/http2_arraysize.h
new file mode 100644
index 00000000000..417e53b4c78
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/platform/api/http2_arraysize.h
@@ -0,0 +1,12 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_HTTP2_PLATFORM_API_HTTP2_ARRAYSIZE_H_
+#define QUICHE_HTTP2_PLATFORM_API_HTTP2_ARRAYSIZE_H_
+
+#include "net/http2/platform/impl/http2_arraysize_impl.h"
+
+#define HTTP2_ARRAYSIZE(x) HTTP2_ARRAYSIZE_IMPL(x)
+
+#endif // QUICHE_HTTP2_PLATFORM_API_HTTP2_ARRAYSIZE_H_
diff --git a/chromium/net/third_party/quiche/src/http2/platform/api/http2_bug_tracker.h b/chromium/net/third_party/quiche/src/http2/platform/api/http2_bug_tracker.h
new file mode 100644
index 00000000000..93ad67717f1
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/platform/api/http2_bug_tracker.h
@@ -0,0 +1,15 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_HTTP2_PLATFORM_API_HTTP2_BUG_TRACKER_H_
+#define QUICHE_HTTP2_PLATFORM_API_HTTP2_BUG_TRACKER_H_
+
+#include "net/http2/platform/impl/http2_bug_tracker_impl.h"
+
+#define HTTP2_BUG HTTP2_BUG_IMPL
+#define HTTP2_BUG_IF HTTP2_BUG_IF_IMPL
+#define FLAGS_http2_always_log_bugs_for_tests \
+ FLAGS_http2_always_log_bugs_for_tests_IMPL
+
+#endif // QUICHE_HTTP2_PLATFORM_API_HTTP2_BUG_TRACKER_H_
diff --git a/chromium/net/third_party/quiche/src/http2/platform/api/http2_containers.h b/chromium/net/third_party/quiche/src/http2/platform/api/http2_containers.h
new file mode 100644
index 00000000000..e748e7bcd91
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/platform/api/http2_containers.h
@@ -0,0 +1,17 @@
+#ifndef QUICHE_HTTP2_PLATFORM_API_HTTP2_CONTAINERS_H_
+#define QUICHE_HTTP2_PLATFORM_API_HTTP2_CONTAINERS_H_
+
+#include "net/http2/platform/impl/http2_containers_impl.h"
+
+namespace http2 {
+
+// Represents a double-ended queue which may be backed by a list or a flat
+// circular buffer.
+//
+// DOES NOT GUARANTEE POINTER OR ITERATOR STABILITY!
+template <typename T>
+using Http2Deque = Http2DequeImpl<T>;
+
+} // namespace http2
+
+#endif // QUICHE_HTTP2_PLATFORM_API_HTTP2_CONTAINERS_H_
diff --git a/chromium/net/third_party/quiche/src/http2/platform/api/http2_estimate_memory_usage.h b/chromium/net/third_party/quiche/src/http2/platform/api/http2_estimate_memory_usage.h
new file mode 100644
index 00000000000..fd405faa460
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/platform/api/http2_estimate_memory_usage.h
@@ -0,0 +1,21 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_HTTP2_PLATFORM_API_HTTP2_ESTIMATE_MEMORY_USAGE_H_
+#define QUICHE_HTTP2_PLATFORM_API_HTTP2_ESTIMATE_MEMORY_USAGE_H_
+
+#include <cstddef>
+
+#include "net/http2/platform/impl/http2_estimate_memory_usage_impl.h"
+
+namespace http2 {
+
+template <class T>
+size_t Http2EstimateMemoryUsage(const T& object) {
+ return Http2EstimateMemoryUsageImpl(object);
+}
+
+} // namespace http2
+
+#endif // QUICHE_HTTP2_PLATFORM_API_HTTP2_ESTIMATE_MEMORY_USAGE_H_
diff --git a/chromium/net/third_party/quiche/src/http2/platform/api/http2_export.h b/chromium/net/third_party/quiche/src/http2/platform/api/http2_export.h
new file mode 100644
index 00000000000..e262f744782
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/platform/api/http2_export.h
@@ -0,0 +1,10 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_HTTP2_PLATFORM_API_HTTP2_EXPORT_H_
+#define QUICHE_HTTP2_PLATFORM_API_HTTP2_EXPORT_H_
+
+#include "net/http2/platform/impl/http2_export_impl.h"
+
+#endif // QUICHE_HTTP2_PLATFORM_API_HTTP2_EXPORT_H_
diff --git a/chromium/net/third_party/quiche/src/http2/platform/api/http2_flag_utils.h b/chromium/net/third_party/quiche/src/http2/platform/api/http2_flag_utils.h
new file mode 100644
index 00000000000..4303c437d0a
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/platform/api/http2_flag_utils.h
@@ -0,0 +1,12 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_HTTP2_PLATFORM_API_HTTP2_FLAG_UTILS_H_
+#define QUICHE_HTTP2_PLATFORM_API_HTTP2_FLAG_UTILS_H_
+
+#include "net/http2/platform/impl/http2_flag_utils_impl.h"
+
+#define HTTP2_RELOADABLE_FLAG_COUNT HTTP2_RELOADABLE_FLAG_COUNT_IMPL
+
+#endif // QUICHE_HTTP2_PLATFORM_API_HTTP2_FLAG_UTILS_H_
diff --git a/chromium/net/third_party/quiche/src/http2/platform/api/http2_flags.h b/chromium/net/third_party/quiche/src/http2/platform/api/http2_flags.h
new file mode 100644
index 00000000000..08f95da687f
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/platform/api/http2_flags.h
@@ -0,0 +1,14 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_HTTP2_PLATFORM_API_HTTP2_FLAGS_H_
+#define QUICHE_HTTP2_PLATFORM_API_HTTP2_FLAGS_H_
+
+#include "net/http2/platform/impl/http2_flags_impl.h"
+
+#define GetHttp2ReloadableFlag(flag) GetHttp2ReloadableFlagImpl(flag)
+#define SetHttp2ReloadableFlag(flag, value) \
+ SetHttp2ReloadableFlagImpl(flag, value)
+
+#endif // QUICHE_HTTP2_PLATFORM_API_HTTP2_FLAGS_H_
diff --git a/chromium/net/third_party/quiche/src/http2/platform/api/http2_macros.h b/chromium/net/third_party/quiche/src/http2/platform/api/http2_macros.h
new file mode 100644
index 00000000000..0be5e89b4ec
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/platform/api/http2_macros.h
@@ -0,0 +1,10 @@
+#ifndef QUICHE_HTTP2_PLATFORM_API_HTTP2_MACROS_H_
+#define QUICHE_HTTP2_PLATFORM_API_HTTP2_MACROS_H_
+
+#include "net/http2/platform/impl/http2_macros_impl.h"
+
+#define HTTP2_FALLTHROUGH HTTP2_FALLTHROUGH_IMPL
+#define HTTP2_UNREACHABLE() HTTP2_UNREACHABLE_IMPL()
+#define HTTP2_DIE_IF_NULL(ptr) HTTP2_DIE_IF_NULL_IMPL(ptr)
+
+#endif // QUICHE_HTTP2_PLATFORM_API_HTTP2_MACROS_H_
diff --git a/chromium/net/third_party/quiche/src/http2/platform/api/http2_mock_log.h b/chromium/net/third_party/quiche/src/http2/platform/api/http2_mock_log.h
new file mode 100644
index 00000000000..3d16837d518
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/platform/api/http2_mock_log.h
@@ -0,0 +1,18 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_HTTP2_PLATFORM_API_HTTP2_MOCK_LOG_H_
+#define QUICHE_HTTP2_PLATFORM_API_HTTP2_MOCK_LOG_H_
+
+#include "net/http2/platform/impl/http2_mock_log_impl.h"
+
+using Http2MockLog = Http2MockLogImpl;
+#define CREATE_HTTP2_MOCK_LOG(log) CREATE_HTTP2_MOCK_LOG_IMPL(log)
+
+#define EXPECT_HTTP2_LOG_CALL(log) EXPECT_HTTP2_LOG_CALL_IMPL(log)
+
+#define EXPECT_HTTP2_LOG_CALL_CONTAINS(log, level, content) \
+ EXPECT_HTTP2_LOG_CALL_CONTAINS_IMPL(log, level, content)
+
+#endif // QUICHE_HTTP2_PLATFORM_API_HTTP2_MOCK_LOG_H_
diff --git a/chromium/net/third_party/quiche/src/http2/platform/api/http2_optional.h b/chromium/net/third_party/quiche/src/http2/platform/api/http2_optional.h
new file mode 100644
index 00000000000..64ce5f5f478
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/platform/api/http2_optional.h
@@ -0,0 +1,19 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_HTTP2_PLATFORM_API_HTTP2_OPTIONAL_H_
+#define QUICHE_HTTP2_PLATFORM_API_HTTP2_OPTIONAL_H_
+
+#include <utility>
+
+#include "net/http2/platform/impl/http2_optional_impl.h"
+
+namespace http2 {
+
+template <typename T>
+using Http2Optional = Http2OptionalImpl<T>;
+
+} // namespace http2
+
+#endif // QUICHE_HTTP2_PLATFORM_API_HTTP2_OPTIONAL_H_
diff --git a/chromium/net/third_party/quiche/src/http2/platform/api/http2_ptr_util.h b/chromium/net/third_party/quiche/src/http2/platform/api/http2_ptr_util.h
new file mode 100644
index 00000000000..2530e7ccf58
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/platform/api/http2_ptr_util.h
@@ -0,0 +1,22 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_HTTP2_PLATFORM_API_HTTP2_PTR_UTIL_H_
+#define QUICHE_HTTP2_PLATFORM_API_HTTP2_PTR_UTIL_H_
+
+#include <memory>
+#include <utility>
+
+#include "net/http2/platform/impl/http2_ptr_util_impl.h"
+
+namespace http2 {
+
+template <typename T, typename... Args>
+std::unique_ptr<T> Http2MakeUnique(Args&&... args) {
+ return Http2MakeUniqueImpl<T>(std::forward<Args>(args)...);
+}
+
+} // namespace http2
+
+#endif // QUICHE_HTTP2_PLATFORM_API_HTTP2_PTR_UTIL_H_
diff --git a/chromium/net/third_party/quiche/src/http2/platform/api/http2_reconstruct_object.h b/chromium/net/third_party/quiche/src/http2/platform/api/http2_reconstruct_object.h
new file mode 100644
index 00000000000..ddcb312157e
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/platform/api/http2_reconstruct_object.h
@@ -0,0 +1,34 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_HTTP2_PLATFORM_API_HTTP2_RECONSTRUCT_OBJECT_H_
+#define QUICHE_HTTP2_PLATFORM_API_HTTP2_RECONSTRUCT_OBJECT_H_
+
+#include <utility>
+
+#include "net/http2/platform/impl/http2_reconstruct_object_impl.h"
+
+namespace http2 {
+namespace test {
+
+class Http2Random;
+
+// Reconstruct an object so that it is initialized as when it was first
+// constructed. Runs the destructor to handle objects that might own resources,
+// and runs the constructor with the provided arguments, if any.
+template <class T, class... Args>
+void Http2ReconstructObject(T* ptr, Http2Random* rng, Args&&... args) {
+ Http2ReconstructObjectImpl(ptr, rng, std::forward<Args>(args)...);
+}
+
+// This version applies default-initialization to the object.
+template <class T>
+void Http2DefaultReconstructObject(T* ptr, Http2Random* rng) {
+ Http2DefaultReconstructObjectImpl(ptr, rng);
+}
+
+} // namespace test
+} // namespace http2
+
+#endif // QUICHE_HTTP2_PLATFORM_API_HTTP2_RECONSTRUCT_OBJECT_H_
diff --git a/chromium/net/third_party/quiche/src/http2/platform/api/http2_string.h b/chromium/net/third_party/quiche/src/http2/platform/api/http2_string.h
new file mode 100644
index 00000000000..25f9e593cc1
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/platform/api/http2_string.h
@@ -0,0 +1,16 @@
+// Copyright (c) 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_HTTP2_PLATFORM_API_HTTP2_STRING_H_
+#define QUICHE_HTTP2_PLATFORM_API_HTTP2_STRING_H_
+
+#include "net/http2/platform/impl/http2_string_impl.h"
+
+namespace http2 {
+
+using Http2String = Http2StringImpl;
+
+} // namespace http2
+
+#endif // QUICHE_HTTP2_PLATFORM_API_HTTP2_STRING_H_
diff --git a/chromium/net/third_party/quiche/src/http2/platform/api/http2_string_piece.h b/chromium/net/third_party/quiche/src/http2/platform/api/http2_string_piece.h
new file mode 100644
index 00000000000..92fb3ef393e
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/platform/api/http2_string_piece.h
@@ -0,0 +1,16 @@
+// Copyright (c) 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_HTTP2_PLATFORM_API_HTTP2_STRING_PIECE_H_
+#define QUICHE_HTTP2_PLATFORM_API_HTTP2_STRING_PIECE_H_
+
+#include "net/http2/platform/impl/http2_string_piece_impl.h"
+
+namespace http2 {
+
+using Http2StringPiece = Http2StringPieceImpl;
+
+} // namespace http2
+
+#endif // QUICHE_HTTP2_PLATFORM_API_HTTP2_STRING_PIECE_H_
diff --git a/chromium/net/third_party/quiche/src/http2/platform/api/http2_string_utils.h b/chromium/net/third_party/quiche/src/http2/platform/api/http2_string_utils.h
new file mode 100644
index 00000000000..ba4056051ee
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/platform/api/http2_string_utils.h
@@ -0,0 +1,56 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_HTTP2_PLATFORM_API_HTTP2_STRING_UTILS_H_
+#define QUICHE_HTTP2_PLATFORM_API_HTTP2_STRING_UTILS_H_
+
+#include <type_traits>
+#include <utility>
+
+#include "net/third_party/quiche/src/http2/platform/api/http2_string.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string_piece.h"
+#include "net/http2/platform/impl/http2_string_utils_impl.h"
+
+namespace http2 {
+
+template <typename... Args>
+inline Http2String Http2StrCat(const Args&... args) {
+ return Http2StrCatImpl(std::forward<const Args&>(args)...);
+}
+
+template <typename... Args>
+inline void Http2StrAppend(Http2String* output, const Args&... args) {
+ Http2StrAppendImpl(output, std::forward<const Args&>(args)...);
+}
+
+template <typename... Args>
+inline Http2String Http2StringPrintf(const Args&... args) {
+ return Http2StringPrintfImpl(std::forward<const Args&>(args)...);
+}
+
+inline Http2String Http2HexEncode(const void* bytes, size_t size) {
+ return Http2HexEncodeImpl(bytes, size);
+}
+
+inline Http2String Http2HexDecode(Http2StringPiece data) {
+ return Http2HexDecodeImpl(data);
+}
+
+inline Http2String Http2HexDump(Http2StringPiece data) {
+ return Http2HexDumpImpl(data);
+}
+
+inline Http2String Http2HexEscape(Http2StringPiece data) {
+ return Http2HexEscapeImpl(data);
+}
+
+template <typename Number>
+inline Http2String Http2Hex(Number number) {
+ static_assert(std::is_integral<Number>::value, "Number has to be an int");
+ return Http2HexImpl(number);
+}
+
+} // namespace http2
+
+#endif // QUICHE_HTTP2_PLATFORM_API_HTTP2_STRING_UTILS_H_
diff --git a/chromium/net/third_party/quiche/src/http2/platform/api/http2_string_utils_test.cc b/chromium/net/third_party/quiche/src/http2/platform/api/http2_string_utils_test.cc
new file mode 100644
index 00000000000..254f9d7d460
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/platform/api/http2_string_utils_test.cc
@@ -0,0 +1,178 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/http2/platform/api/http2_string_utils.h"
+
+#include <cstdint>
+
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string_piece.h"
+
+namespace http2 {
+namespace test {
+namespace {
+
+TEST(Http2StringUtilsTest, Http2StrCat) {
+ // No arguments.
+ EXPECT_EQ("", Http2StrCat());
+
+ // Single string-like argument.
+ const char kFoo[] = "foo";
+ const Http2String string_foo(kFoo);
+ const Http2StringPiece stringpiece_foo(string_foo);
+ EXPECT_EQ("foo", Http2StrCat(kFoo));
+ EXPECT_EQ("foo", Http2StrCat(string_foo));
+ EXPECT_EQ("foo", Http2StrCat(stringpiece_foo));
+
+ // Two string-like arguments.
+ const char kBar[] = "bar";
+ const Http2StringPiece stringpiece_bar(kBar);
+ const Http2String string_bar(kBar);
+ EXPECT_EQ("foobar", Http2StrCat(kFoo, kBar));
+ EXPECT_EQ("foobar", Http2StrCat(kFoo, string_bar));
+ EXPECT_EQ("foobar", Http2StrCat(kFoo, stringpiece_bar));
+ EXPECT_EQ("foobar", Http2StrCat(string_foo, kBar));
+ EXPECT_EQ("foobar", Http2StrCat(string_foo, string_bar));
+ EXPECT_EQ("foobar", Http2StrCat(string_foo, stringpiece_bar));
+ EXPECT_EQ("foobar", Http2StrCat(stringpiece_foo, kBar));
+ EXPECT_EQ("foobar", Http2StrCat(stringpiece_foo, string_bar));
+ EXPECT_EQ("foobar", Http2StrCat(stringpiece_foo, stringpiece_bar));
+
+ // Many-many arguments.
+ EXPECT_EQ(
+ "foobarbazquxquuxquuzcorgegraultgarplywaldofredplughxyzzythud",
+ Http2StrCat("foo", "bar", "baz", "qux", "quux", "quuz", "corge", "grault",
+ "garply", "waldo", "fred", "plugh", "xyzzy", "thud"));
+
+ // Numerical arguments.
+ const int16_t i = 1;
+ const uint64_t u = 8;
+ const double d = 3.1415;
+
+ EXPECT_EQ("1 8", Http2StrCat(i, " ", u));
+ EXPECT_EQ("3.14151181", Http2StrCat(d, i, i, u, i));
+ EXPECT_EQ("i: 1, u: 8, d: 3.1415",
+ Http2StrCat("i: ", i, ", u: ", u, ", d: ", d));
+
+ // Boolean arguments.
+ const bool t = true;
+ const bool f = false;
+
+ EXPECT_EQ("1", Http2StrCat(t));
+ EXPECT_EQ("0", Http2StrCat(f));
+ EXPECT_EQ("0110", Http2StrCat(f, t, t, f));
+
+ // Mixed string-like, numerical, and Boolean arguments.
+ EXPECT_EQ("foo1foo081bar3.14151",
+ Http2StrCat(kFoo, i, string_foo, f, u, t, stringpiece_bar, d, t));
+ EXPECT_EQ("3.141511bar18bar13.14150",
+ Http2StrCat(d, t, t, string_bar, i, u, kBar, t, d, f));
+}
+
+TEST(Http2StringUtilsTest, Http2StrAppend) {
+ // No arguments on empty string.
+ Http2String output;
+ Http2StrAppend(&output);
+ EXPECT_TRUE(output.empty());
+
+ // Single string-like argument.
+ const char kFoo[] = "foo";
+ const Http2String string_foo(kFoo);
+ const Http2StringPiece stringpiece_foo(string_foo);
+ Http2StrAppend(&output, kFoo);
+ EXPECT_EQ("foo", output);
+ Http2StrAppend(&output, string_foo);
+ EXPECT_EQ("foofoo", output);
+ Http2StrAppend(&output, stringpiece_foo);
+ EXPECT_EQ("foofoofoo", output);
+
+ // No arguments on non-empty string.
+ Http2StrAppend(&output);
+ EXPECT_EQ("foofoofoo", output);
+
+ output.clear();
+
+ // Two string-like arguments.
+ const char kBar[] = "bar";
+ const Http2StringPiece stringpiece_bar(kBar);
+ const Http2String string_bar(kBar);
+ Http2StrAppend(&output, kFoo, kBar);
+ EXPECT_EQ("foobar", output);
+ Http2StrAppend(&output, kFoo, string_bar);
+ EXPECT_EQ("foobarfoobar", output);
+ Http2StrAppend(&output, kFoo, stringpiece_bar);
+ EXPECT_EQ("foobarfoobarfoobar", output);
+ Http2StrAppend(&output, string_foo, kBar);
+ EXPECT_EQ("foobarfoobarfoobarfoobar", output);
+
+ output.clear();
+
+ Http2StrAppend(&output, string_foo, string_bar);
+ EXPECT_EQ("foobar", output);
+ Http2StrAppend(&output, string_foo, stringpiece_bar);
+ EXPECT_EQ("foobarfoobar", output);
+ Http2StrAppend(&output, stringpiece_foo, kBar);
+ EXPECT_EQ("foobarfoobarfoobar", output);
+ Http2StrAppend(&output, stringpiece_foo, string_bar);
+ EXPECT_EQ("foobarfoobarfoobarfoobar", output);
+
+ output.clear();
+
+ Http2StrAppend(&output, stringpiece_foo, stringpiece_bar);
+ EXPECT_EQ("foobar", output);
+
+ // Many-many arguments.
+ Http2StrAppend(&output, "foo", "bar", "baz", "qux", "quux", "quuz", "corge",
+ "grault", "garply", "waldo", "fred", "plugh", "xyzzy", "thud");
+ EXPECT_EQ(
+ "foobarfoobarbazquxquuxquuzcorgegraultgarplywaldofredplughxyzzythud",
+ output);
+
+ output.clear();
+
+ // Numerical arguments.
+ const int16_t i = 1;
+ const uint64_t u = 8;
+ const double d = 3.1415;
+
+ Http2StrAppend(&output, i, " ", u);
+ EXPECT_EQ("1 8", output);
+ Http2StrAppend(&output, d, i, i, u, i);
+ EXPECT_EQ("1 83.14151181", output);
+ Http2StrAppend(&output, "i: ", i, ", u: ", u, ", d: ", d);
+ EXPECT_EQ("1 83.14151181i: 1, u: 8, d: 3.1415", output);
+
+ output.clear();
+
+ // Boolean arguments.
+ const bool t = true;
+ const bool f = false;
+
+ Http2StrAppend(&output, t);
+ EXPECT_EQ("1", output);
+ Http2StrAppend(&output, f);
+ EXPECT_EQ("10", output);
+ Http2StrAppend(&output, f, t, t, f);
+ EXPECT_EQ("100110", output);
+
+ output.clear();
+
+ // Mixed string-like, numerical, and Boolean arguments.
+ Http2StrAppend(&output, kFoo, i, string_foo, f, u, t, stringpiece_bar, d, t);
+ EXPECT_EQ("foo1foo081bar3.14151", output);
+ Http2StrAppend(&output, d, t, t, string_bar, i, u, kBar, t, d, f);
+ EXPECT_EQ("foo1foo081bar3.141513.141511bar18bar13.14150", output);
+}
+
+TEST(Http2StringUtilsTest, Http2StringPrintf) {
+ EXPECT_EQ("", Http2StringPrintf("%s", ""));
+ EXPECT_EQ("foobar", Http2StringPrintf("%sbar", "foo"));
+ EXPECT_EQ("foobar", Http2StringPrintf("%s%s", "foo", "bar"));
+ EXPECT_EQ("foo: 1, bar: 2.0",
+ Http2StringPrintf("foo: %d, bar: %.1f", 1, 2.0));
+}
+
+} // namespace
+} // namespace test
+} // namespace http2
diff --git a/chromium/net/third_party/quiche/src/http2/platform/api/http2_test_helpers.h b/chromium/net/third_party/quiche/src/http2/platform/api/http2_test_helpers.h
new file mode 100644
index 00000000000..a54f4ab1b7e
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/platform/api/http2_test_helpers.h
@@ -0,0 +1,16 @@
+#ifndef QUICHE_HTTP2_PLATFORM_API_HTTP2_TEST_HELPERS_H_
+#define QUICHE_HTTP2_PLATFORM_API_HTTP2_TEST_HELPERS_H_
+
+// Provides VERIFY_* macros, similar to EXPECT_* and ASSERT_*, but they return
+// an AssertionResult if the condition is not satisfied.
+#include "net/http2/platform/impl/http2_test_helpers_impl.h"
+
+#include "testing/gtest/include/gtest/gtest.h" // For AssertionSuccess
+
+#define VERIFY_AND_RETURN_SUCCESS(expression) \
+ { \
+ VERIFY_SUCCESS(expression); \
+ return ::testing::AssertionSuccess(); \
+ }
+
+#endif // QUICHE_HTTP2_PLATFORM_API_HTTP2_TEST_HELPERS_H_
diff --git a/chromium/net/third_party/quiche/src/http2/test_tools/frame_parts.cc b/chromium/net/third_party/quiche/src/http2/test_tools/frame_parts.cc
new file mode 100644
index 00000000000..3d0453e9294
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/test_tools/frame_parts.cc
@@ -0,0 +1,526 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/http2/test_tools/frame_parts.h"
+
+#include <type_traits>
+
+#include "base/logging.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/http2/http2_structures_test_util.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string_utils.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_test_helpers.h"
+
+using ::testing::AssertionFailure;
+using ::testing::AssertionResult;
+using ::testing::AssertionSuccess;
+using ::testing::ContainerEq;
+
+namespace http2 {
+namespace test {
+namespace {
+
+static_assert(std::is_base_of<Http2FrameDecoderListener, FrameParts>::value &&
+ !std::is_abstract<FrameParts>::value,
+ "FrameParts needs to implement all of the methods of "
+ "Http2FrameDecoderListener");
+
+// Compare two optional variables of the same type.
+// TODO(jamessynge): Maybe create a ::testing::Matcher for this.
+template <class T>
+AssertionResult VerifyOptionalEq(const T& opt_a, const T& opt_b) {
+ if (opt_a) {
+ if (opt_b) {
+ VERIFY_EQ(opt_a.value(), opt_b.value());
+ } else {
+ return AssertionFailure()
+ << "opt_b is not set; opt_a.value()=" << opt_a.value();
+ }
+ } else if (opt_b) {
+ return AssertionFailure()
+ << "opt_a is not set; opt_b.value()=" << opt_b.value();
+ }
+ return AssertionSuccess();
+}
+
+} // namespace
+
+FrameParts::FrameParts(const Http2FrameHeader& header) : frame_header_(header) {
+ VLOG(1) << "FrameParts, header: " << frame_header_;
+}
+
+FrameParts::FrameParts(const Http2FrameHeader& header, Http2StringPiece payload)
+ : FrameParts(header) {
+ VLOG(1) << "FrameParts with payload.size() = " << payload.size();
+ this->payload_.append(payload.data(), payload.size());
+ opt_payload_length_ = payload.size();
+}
+FrameParts::FrameParts(const Http2FrameHeader& header,
+ Http2StringPiece payload,
+ size_t total_pad_length)
+ : FrameParts(header, payload) {
+ VLOG(1) << "FrameParts with total_pad_length=" << total_pad_length;
+ SetTotalPadLength(total_pad_length);
+}
+
+FrameParts::FrameParts(const FrameParts& header) = default;
+
+FrameParts::~FrameParts() = default;
+
+AssertionResult FrameParts::VerifyEquals(const FrameParts& that) const {
+#define COMMON_MESSAGE "\n this: " << *this << "\n that: " << that
+
+ VERIFY_EQ(frame_header_, that.frame_header_) << COMMON_MESSAGE;
+ VERIFY_EQ(payload_, that.payload_) << COMMON_MESSAGE;
+ VERIFY_EQ(padding_, that.padding_) << COMMON_MESSAGE;
+ VERIFY_EQ(altsvc_origin_, that.altsvc_origin_) << COMMON_MESSAGE;
+ VERIFY_EQ(altsvc_value_, that.altsvc_value_) << COMMON_MESSAGE;
+ VERIFY_THAT(settings_, ContainerEq(that.settings_)) << COMMON_MESSAGE;
+
+#define VERIFY_OPTIONAL_FIELD(field_name) \
+ VERIFY_SUCCESS(VerifyOptionalEq(field_name, that.field_name))
+
+ VERIFY_OPTIONAL_FIELD(opt_altsvc_origin_length_) << COMMON_MESSAGE;
+ VERIFY_OPTIONAL_FIELD(opt_altsvc_value_length_) << COMMON_MESSAGE;
+ VERIFY_OPTIONAL_FIELD(opt_goaway_) << COMMON_MESSAGE;
+ VERIFY_OPTIONAL_FIELD(opt_missing_length_) << COMMON_MESSAGE;
+ VERIFY_OPTIONAL_FIELD(opt_pad_length_) << COMMON_MESSAGE;
+ VERIFY_OPTIONAL_FIELD(opt_ping_) << COMMON_MESSAGE;
+ VERIFY_OPTIONAL_FIELD(opt_priority_) << COMMON_MESSAGE;
+ VERIFY_OPTIONAL_FIELD(opt_push_promise_) << COMMON_MESSAGE;
+ VERIFY_OPTIONAL_FIELD(opt_rst_stream_error_code_) << COMMON_MESSAGE;
+ VERIFY_OPTIONAL_FIELD(opt_window_update_increment_) << COMMON_MESSAGE;
+
+#undef VERIFY_OPTIONAL_FIELD
+
+ return AssertionSuccess();
+}
+
+void FrameParts::SetTotalPadLength(size_t total_pad_length) {
+ opt_pad_length_.reset();
+ padding_.clear();
+ if (total_pad_length > 0) {
+ ASSERT_LE(total_pad_length, 256u);
+ ASSERT_TRUE(frame_header_.IsPadded());
+ opt_pad_length_ = total_pad_length - 1;
+ char zero = 0;
+ padding_.append(opt_pad_length_.value(), zero);
+ }
+
+ if (opt_pad_length_) {
+ VLOG(1) << "SetTotalPadLength: pad_length=" << opt_pad_length_.value();
+ } else {
+ VLOG(1) << "SetTotalPadLength: has no pad length";
+ }
+}
+
+void FrameParts::SetAltSvcExpected(Http2StringPiece origin,
+ Http2StringPiece value) {
+ altsvc_origin_.append(origin.data(), origin.size());
+ altsvc_value_.append(value.data(), value.size());
+ opt_altsvc_origin_length_ = origin.size();
+ opt_altsvc_value_length_ = value.size();
+}
+
+bool FrameParts::OnFrameHeader(const Http2FrameHeader& header) {
+ ADD_FAILURE() << "OnFrameHeader: " << *this;
+ return true;
+}
+
+void FrameParts::OnDataStart(const Http2FrameHeader& header) {
+ VLOG(1) << "OnDataStart: " << header;
+ ASSERT_TRUE(StartFrameOfType(header, Http2FrameType::DATA)) << *this;
+ opt_payload_length_ = header.payload_length;
+}
+
+void FrameParts::OnDataPayload(const char* data, size_t len) {
+ VLOG(1) << "OnDataPayload: len=" << len
+ << "; frame_header_: " << frame_header_;
+ ASSERT_TRUE(InFrameOfType(Http2FrameType::DATA)) << *this;
+ ASSERT_TRUE(AppendString(Http2StringPiece(data, len), &payload_,
+ &opt_payload_length_));
+}
+
+void FrameParts::OnDataEnd() {
+ VLOG(1) << "OnDataEnd; frame_header_: " << frame_header_;
+ ASSERT_TRUE(EndFrameOfType(Http2FrameType::DATA)) << *this;
+}
+
+void FrameParts::OnHeadersStart(const Http2FrameHeader& header) {
+ VLOG(1) << "OnHeadersStart: " << header;
+ ASSERT_TRUE(StartFrameOfType(header, Http2FrameType::HEADERS)) << *this;
+ opt_payload_length_ = header.payload_length;
+}
+
+void FrameParts::OnHeadersPriority(const Http2PriorityFields& priority) {
+ VLOG(1) << "OnHeadersPriority: priority: " << priority
+ << "; frame_header_: " << frame_header_;
+ ASSERT_TRUE(InFrameOfType(Http2FrameType::HEADERS)) << *this;
+ ASSERT_FALSE(opt_priority_);
+ opt_priority_ = priority;
+ ASSERT_TRUE(opt_payload_length_);
+ opt_payload_length_ =
+ opt_payload_length_.value() - Http2PriorityFields::EncodedSize();
+}
+
+void FrameParts::OnHpackFragment(const char* data, size_t len) {
+ VLOG(1) << "OnHpackFragment: len=" << len
+ << "; frame_header_: " << frame_header_;
+ ASSERT_TRUE(got_start_callback_);
+ ASSERT_FALSE(got_end_callback_);
+ ASSERT_TRUE(FrameCanHaveHpackPayload(frame_header_)) << *this;
+ ASSERT_TRUE(AppendString(Http2StringPiece(data, len), &payload_,
+ &opt_payload_length_));
+}
+
+void FrameParts::OnHeadersEnd() {
+ VLOG(1) << "OnHeadersEnd; frame_header_: " << frame_header_;
+ ASSERT_TRUE(EndFrameOfType(Http2FrameType::HEADERS)) << *this;
+}
+
+void FrameParts::OnPriorityFrame(const Http2FrameHeader& header,
+ const Http2PriorityFields& priority) {
+ VLOG(1) << "OnPriorityFrame: " << header << "; priority: " << priority;
+ ASSERT_TRUE(StartFrameOfType(header, Http2FrameType::PRIORITY)) << *this;
+ ASSERT_FALSE(opt_priority_);
+ opt_priority_ = priority;
+ ASSERT_TRUE(EndFrameOfType(Http2FrameType::PRIORITY)) << *this;
+}
+
+void FrameParts::OnContinuationStart(const Http2FrameHeader& header) {
+ VLOG(1) << "OnContinuationStart: " << header;
+ ASSERT_TRUE(StartFrameOfType(header, Http2FrameType::CONTINUATION)) << *this;
+ opt_payload_length_ = header.payload_length;
+}
+
+void FrameParts::OnContinuationEnd() {
+ VLOG(1) << "OnContinuationEnd; frame_header_: " << frame_header_;
+ ASSERT_TRUE(EndFrameOfType(Http2FrameType::CONTINUATION)) << *this;
+}
+
+void FrameParts::OnPadLength(size_t trailing_length) {
+ VLOG(1) << "OnPadLength: trailing_length=" << trailing_length;
+ ASSERT_TRUE(InPaddedFrame()) << *this;
+ ASSERT_FALSE(opt_pad_length_);
+ ASSERT_TRUE(opt_payload_length_);
+ size_t total_padding_length = trailing_length + 1;
+ ASSERT_GE(opt_payload_length_.value(), total_padding_length);
+ opt_payload_length_ = opt_payload_length_.value() - total_padding_length;
+ opt_pad_length_ = trailing_length;
+}
+
+void FrameParts::OnPadding(const char* pad, size_t skipped_length) {
+ VLOG(1) << "OnPadding: skipped_length=" << skipped_length;
+ ASSERT_TRUE(InPaddedFrame()) << *this;
+ ASSERT_TRUE(opt_pad_length_);
+ ASSERT_TRUE(AppendString(Http2StringPiece(pad, skipped_length), &padding_,
+ &opt_pad_length_));
+}
+
+void FrameParts::OnRstStream(const Http2FrameHeader& header,
+ Http2ErrorCode error_code) {
+ VLOG(1) << "OnRstStream: " << header << "; code=" << error_code;
+ ASSERT_TRUE(StartFrameOfType(header, Http2FrameType::RST_STREAM)) << *this;
+ ASSERT_FALSE(opt_rst_stream_error_code_);
+ opt_rst_stream_error_code_ = error_code;
+ ASSERT_TRUE(EndFrameOfType(Http2FrameType::RST_STREAM)) << *this;
+}
+
+void FrameParts::OnSettingsStart(const Http2FrameHeader& header) {
+ VLOG(1) << "OnSettingsStart: " << header;
+ ASSERT_TRUE(StartFrameOfType(header, Http2FrameType::SETTINGS)) << *this;
+ ASSERT_EQ(0u, settings_.size());
+ ASSERT_FALSE(header.IsAck()) << header;
+}
+
+void FrameParts::OnSetting(const Http2SettingFields& setting_fields) {
+ VLOG(1) << "OnSetting: " << setting_fields;
+ ASSERT_TRUE(InFrameOfType(Http2FrameType::SETTINGS)) << *this;
+ settings_.push_back(setting_fields);
+}
+
+void FrameParts::OnSettingsEnd() {
+ VLOG(1) << "OnSettingsEnd; frame_header_: " << frame_header_;
+ ASSERT_TRUE(EndFrameOfType(Http2FrameType::SETTINGS)) << *this;
+}
+
+void FrameParts::OnSettingsAck(const Http2FrameHeader& header) {
+ VLOG(1) << "OnSettingsAck: " << header;
+ ASSERT_TRUE(StartFrameOfType(header, Http2FrameType::SETTINGS)) << *this;
+ ASSERT_EQ(0u, settings_.size());
+ ASSERT_TRUE(header.IsAck());
+ ASSERT_TRUE(EndFrameOfType(Http2FrameType::SETTINGS)) << *this;
+}
+
+void FrameParts::OnPushPromiseStart(const Http2FrameHeader& header,
+ const Http2PushPromiseFields& promise,
+ size_t total_padding_length) {
+ VLOG(1) << "OnPushPromiseStart header: " << header << "; promise: " << promise
+ << "; total_padding_length: " << total_padding_length;
+ ASSERT_TRUE(StartFrameOfType(header, Http2FrameType::PUSH_PROMISE)) << *this;
+ ASSERT_GE(header.payload_length, Http2PushPromiseFields::EncodedSize());
+ opt_payload_length_ =
+ header.payload_length - Http2PushPromiseFields::EncodedSize();
+ ASSERT_FALSE(opt_push_promise_);
+ opt_push_promise_ = promise;
+ if (total_padding_length > 0) {
+ ASSERT_GE(opt_payload_length_.value(), total_padding_length);
+ OnPadLength(total_padding_length - 1);
+ } else {
+ ASSERT_FALSE(header.IsPadded());
+ }
+}
+
+void FrameParts::OnPushPromiseEnd() {
+ VLOG(1) << "OnPushPromiseEnd; frame_header_: " << frame_header_;
+ ASSERT_TRUE(EndFrameOfType(Http2FrameType::PUSH_PROMISE)) << *this;
+}
+
+void FrameParts::OnPing(const Http2FrameHeader& header,
+ const Http2PingFields& ping) {
+ VLOG(1) << "OnPing header: " << header << " ping: " << ping;
+ ASSERT_TRUE(StartFrameOfType(header, Http2FrameType::PING)) << *this;
+ ASSERT_FALSE(header.IsAck());
+ ASSERT_FALSE(opt_ping_);
+ opt_ping_ = ping;
+ ASSERT_TRUE(EndFrameOfType(Http2FrameType::PING)) << *this;
+}
+
+void FrameParts::OnPingAck(const Http2FrameHeader& header,
+ const Http2PingFields& ping) {
+ VLOG(1) << "OnPingAck header: " << header << " ping: " << ping;
+ ASSERT_TRUE(StartFrameOfType(header, Http2FrameType::PING)) << *this;
+ ASSERT_TRUE(header.IsAck());
+ ASSERT_FALSE(opt_ping_);
+ opt_ping_ = ping;
+ ASSERT_TRUE(EndFrameOfType(Http2FrameType::PING)) << *this;
+}
+
+void FrameParts::OnGoAwayStart(const Http2FrameHeader& header,
+ const Http2GoAwayFields& goaway) {
+ VLOG(1) << "OnGoAwayStart: " << goaway;
+ ASSERT_TRUE(StartFrameOfType(header, Http2FrameType::GOAWAY)) << *this;
+ ASSERT_FALSE(opt_goaway_);
+ opt_goaway_ = goaway;
+ opt_payload_length_ =
+ header.payload_length - Http2GoAwayFields::EncodedSize();
+}
+
+void FrameParts::OnGoAwayOpaqueData(const char* data, size_t len) {
+ VLOG(1) << "OnGoAwayOpaqueData: len=" << len;
+ ASSERT_TRUE(InFrameOfType(Http2FrameType::GOAWAY)) << *this;
+ ASSERT_TRUE(AppendString(Http2StringPiece(data, len), &payload_,
+ &opt_payload_length_));
+}
+
+void FrameParts::OnGoAwayEnd() {
+ VLOG(1) << "OnGoAwayEnd; frame_header_: " << frame_header_;
+ ASSERT_TRUE(EndFrameOfType(Http2FrameType::GOAWAY)) << *this;
+}
+
+void FrameParts::OnWindowUpdate(const Http2FrameHeader& header,
+ uint32_t increment) {
+ VLOG(1) << "OnWindowUpdate header: " << header
+ << " increment=" << increment;
+ ASSERT_TRUE(StartFrameOfType(header, Http2FrameType::WINDOW_UPDATE)) << *this;
+ ASSERT_FALSE(opt_window_update_increment_);
+ opt_window_update_increment_ = increment;
+ ASSERT_TRUE(EndFrameOfType(Http2FrameType::WINDOW_UPDATE)) << *this;
+}
+
+void FrameParts::OnAltSvcStart(const Http2FrameHeader& header,
+ size_t origin_length,
+ size_t value_length) {
+ VLOG(1) << "OnAltSvcStart: " << header
+ << " origin_length: " << origin_length
+ << " value_length: " << value_length;
+ ASSERT_TRUE(StartFrameOfType(header, Http2FrameType::ALTSVC)) << *this;
+ ASSERT_FALSE(opt_altsvc_origin_length_);
+ opt_altsvc_origin_length_ = origin_length;
+ ASSERT_FALSE(opt_altsvc_value_length_);
+ opt_altsvc_value_length_ = value_length;
+}
+
+void FrameParts::OnAltSvcOriginData(const char* data, size_t len) {
+ VLOG(1) << "OnAltSvcOriginData: len=" << len;
+ ASSERT_TRUE(InFrameOfType(Http2FrameType::ALTSVC)) << *this;
+ ASSERT_TRUE(AppendString(Http2StringPiece(data, len), &altsvc_origin_,
+ &opt_altsvc_origin_length_));
+}
+
+void FrameParts::OnAltSvcValueData(const char* data, size_t len) {
+ VLOG(1) << "OnAltSvcValueData: len=" << len;
+ ASSERT_TRUE(InFrameOfType(Http2FrameType::ALTSVC)) << *this;
+ ASSERT_TRUE(AppendString(Http2StringPiece(data, len), &altsvc_value_,
+ &opt_altsvc_value_length_));
+}
+
+void FrameParts::OnAltSvcEnd() {
+ VLOG(1) << "OnAltSvcEnd; frame_header_: " << frame_header_;
+ ASSERT_TRUE(EndFrameOfType(Http2FrameType::ALTSVC)) << *this;
+}
+
+void FrameParts::OnUnknownStart(const Http2FrameHeader& header) {
+ VLOG(1) << "OnUnknownStart: " << header;
+ ASSERT_FALSE(IsSupportedHttp2FrameType(header.type)) << header;
+ ASSERT_FALSE(got_start_callback_);
+ ASSERT_EQ(frame_header_, header);
+ got_start_callback_ = true;
+ opt_payload_length_ = header.payload_length;
+}
+
+void FrameParts::OnUnknownPayload(const char* data, size_t len) {
+ VLOG(1) << "OnUnknownPayload: len=" << len;
+ ASSERT_FALSE(IsSupportedHttp2FrameType(frame_header_.type)) << *this;
+ ASSERT_TRUE(got_start_callback_);
+ ASSERT_FALSE(got_end_callback_);
+ ASSERT_TRUE(AppendString(Http2StringPiece(data, len), &payload_,
+ &opt_payload_length_));
+}
+
+void FrameParts::OnUnknownEnd() {
+ VLOG(1) << "OnUnknownEnd; frame_header_: " << frame_header_;
+ ASSERT_FALSE(IsSupportedHttp2FrameType(frame_header_.type)) << *this;
+ ASSERT_TRUE(got_start_callback_);
+ ASSERT_FALSE(got_end_callback_);
+ got_end_callback_ = true;
+}
+
+void FrameParts::OnPaddingTooLong(const Http2FrameHeader& header,
+ size_t missing_length) {
+ VLOG(1) << "OnPaddingTooLong: " << header
+ << "; missing_length: " << missing_length;
+ ASSERT_EQ(frame_header_, header);
+ ASSERT_FALSE(got_end_callback_);
+ ASSERT_TRUE(FrameIsPadded(header));
+ ASSERT_FALSE(opt_pad_length_);
+ ASSERT_FALSE(opt_missing_length_);
+ opt_missing_length_ = missing_length;
+ got_start_callback_ = true;
+ got_end_callback_ = true;
+}
+
+void FrameParts::OnFrameSizeError(const Http2FrameHeader& header) {
+ VLOG(1) << "OnFrameSizeError: " << header;
+ ASSERT_EQ(frame_header_, header);
+ ASSERT_FALSE(got_end_callback_);
+ ASSERT_FALSE(has_frame_size_error_);
+ has_frame_size_error_ = true;
+ got_end_callback_ = true;
+}
+
+void FrameParts::OutputTo(std::ostream& out) const {
+ out << "FrameParts{\n frame_header_: " << frame_header_ << "\n";
+ if (!payload_.empty()) {
+ out << " payload_=\"" << Http2HexEscape(payload_) << "\"\n";
+ }
+ if (!padding_.empty()) {
+ out << " padding_=\"" << Http2HexEscape(padding_) << "\"\n";
+ }
+ if (!altsvc_origin_.empty()) {
+ out << " altsvc_origin_=\"" << Http2HexEscape(altsvc_origin_) << "\"\n";
+ }
+ if (!altsvc_value_.empty()) {
+ out << " altsvc_value_=\"" << Http2HexEscape(altsvc_value_) << "\"\n";
+ }
+ if (opt_priority_) {
+ out << " priority=" << opt_priority_.value() << "\n";
+ }
+ if (opt_rst_stream_error_code_) {
+ out << " rst_stream=" << opt_rst_stream_error_code_.value() << "\n";
+ }
+ if (opt_push_promise_) {
+ out << " push_promise=" << opt_push_promise_.value() << "\n";
+ }
+ if (opt_ping_) {
+ out << " ping=" << opt_ping_.value() << "\n";
+ }
+ if (opt_goaway_) {
+ out << " goaway=" << opt_goaway_.value() << "\n";
+ }
+ if (opt_window_update_increment_) {
+ out << " window_update=" << opt_window_update_increment_.value() << "\n";
+ }
+ if (opt_payload_length_) {
+ out << " payload_length=" << opt_payload_length_.value() << "\n";
+ }
+ if (opt_pad_length_) {
+ out << " pad_length=" << opt_pad_length_.value() << "\n";
+ }
+ if (opt_missing_length_) {
+ out << " missing_length=" << opt_missing_length_.value() << "\n";
+ }
+ if (opt_altsvc_origin_length_) {
+ out << " origin_length=" << opt_altsvc_origin_length_.value() << "\n";
+ }
+ if (opt_altsvc_value_length_) {
+ out << " value_length=" << opt_altsvc_value_length_.value() << "\n";
+ }
+ if (has_frame_size_error_) {
+ out << " has_frame_size_error\n";
+ }
+ if (got_start_callback_) {
+ out << " got_start_callback\n";
+ }
+ if (got_end_callback_) {
+ out << " got_end_callback\n";
+ }
+ for (size_t ndx = 0; ndx < settings_.size(); ++ndx) {
+ out << " setting[" << ndx << "]=" << settings_[ndx];
+ }
+ out << "}";
+}
+
+AssertionResult FrameParts::StartFrameOfType(
+ const Http2FrameHeader& header,
+ Http2FrameType expected_frame_type) {
+ VERIFY_EQ(header.type, expected_frame_type);
+ VERIFY_FALSE(got_start_callback_);
+ VERIFY_FALSE(got_end_callback_);
+ VERIFY_EQ(frame_header_, header);
+ got_start_callback_ = true;
+ return AssertionSuccess();
+}
+
+AssertionResult FrameParts::InFrameOfType(Http2FrameType expected_frame_type) {
+ VERIFY_TRUE(got_start_callback_);
+ VERIFY_FALSE(got_end_callback_);
+ VERIFY_EQ(frame_header_.type, expected_frame_type);
+ return AssertionSuccess();
+}
+
+AssertionResult FrameParts::EndFrameOfType(Http2FrameType expected_frame_type) {
+ VERIFY_SUCCESS(InFrameOfType(expected_frame_type));
+ got_end_callback_ = true;
+ return AssertionSuccess();
+}
+
+AssertionResult FrameParts::InPaddedFrame() {
+ VERIFY_TRUE(got_start_callback_);
+ VERIFY_FALSE(got_end_callback_);
+ VERIFY_TRUE(FrameIsPadded(frame_header_));
+ return AssertionSuccess();
+}
+
+AssertionResult FrameParts::AppendString(Http2StringPiece source,
+ Http2String* target,
+ Http2Optional<size_t>* opt_length) {
+ target->append(source.data(), source.size());
+ if (opt_length != nullptr) {
+ VERIFY_TRUE(*opt_length) << "Length is not set yet\n" << *this;
+ VERIFY_LE(target->size(), opt_length->value())
+ << "String too large; source.size() = " << source.size() << "\n"
+ << *this;
+ }
+ return ::testing::AssertionSuccess();
+}
+
+std::ostream& operator<<(std::ostream& out, const FrameParts& v) {
+ v.OutputTo(out);
+ return out;
+}
+
+} // namespace test
+} // namespace http2
diff --git a/chromium/net/third_party/quiche/src/http2/test_tools/frame_parts.h b/chromium/net/third_party/quiche/src/http2/test_tools/frame_parts.h
new file mode 100644
index 00000000000..b531174fa1d
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/test_tools/frame_parts.h
@@ -0,0 +1,249 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_HTTP2_TEST_TOOLS_FRAME_PARTS_H_
+#define QUICHE_HTTP2_TEST_TOOLS_FRAME_PARTS_H_
+
+// FrameParts implements Http2FrameDecoderListener, recording the callbacks
+// during the decoding of a single frame. It is also used for comparing the
+// info that a test expects to be recorded during the decoding of a frame
+// with the actual recorded value (i.e. by providing a comparator).
+
+#include <stddef.h>
+
+#include <cstdint>
+#include <vector>
+
+#include "base/logging.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener.h"
+#include "net/third_party/quiche/src/http2/http2_constants.h"
+#include "net/third_party/quiche/src/http2/http2_structures.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_optional.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string_piece.h"
+
+namespace http2 {
+namespace test {
+
+class FrameParts : public Http2FrameDecoderListener {
+ public:
+ // The first callback for every type of frame includes the frame header; this
+ // is the only constructor used during decoding of a frame.
+ explicit FrameParts(const Http2FrameHeader& header);
+
+ // For use in tests where the expected frame has a variable size payload.
+ FrameParts(const Http2FrameHeader& header, Http2StringPiece payload);
+
+ // For use in tests where the expected frame has a variable size payload
+ // and may be padded.
+ FrameParts(const Http2FrameHeader& header,
+ Http2StringPiece payload,
+ size_t total_pad_length);
+
+ // Copy constructor.
+ FrameParts(const FrameParts& header);
+
+ ~FrameParts() override;
+
+ // Returns AssertionSuccess() if they're equal, else AssertionFailure()
+ // with info about the difference.
+ ::testing::AssertionResult VerifyEquals(const FrameParts& other) const;
+
+ // Format this FrameParts object.
+ void OutputTo(std::ostream& out) const;
+
+ // Set the total padding length (0 to 256).
+ void SetTotalPadLength(size_t total_pad_length);
+
+ // Set the origin and value expected in an ALTSVC frame.
+ void SetAltSvcExpected(Http2StringPiece origin, Http2StringPiece value);
+
+ // Http2FrameDecoderListener methods:
+ bool OnFrameHeader(const Http2FrameHeader& header) override;
+ void OnDataStart(const Http2FrameHeader& header) override;
+ void OnDataPayload(const char* data, size_t len) override;
+ void OnDataEnd() override;
+ void OnHeadersStart(const Http2FrameHeader& header) override;
+ void OnHeadersPriority(const Http2PriorityFields& priority) override;
+ void OnHpackFragment(const char* data, size_t len) override;
+ void OnHeadersEnd() override;
+ void OnPriorityFrame(const Http2FrameHeader& header,
+ const Http2PriorityFields& priority) override;
+ void OnContinuationStart(const Http2FrameHeader& header) override;
+ void OnContinuationEnd() override;
+ void OnPadLength(size_t trailing_length) override;
+ void OnPadding(const char* pad, size_t skipped_length) override;
+ void OnRstStream(const Http2FrameHeader& header,
+ Http2ErrorCode error_code) override;
+ void OnSettingsStart(const Http2FrameHeader& header) override;
+ void OnSetting(const Http2SettingFields& setting_fields) override;
+ void OnSettingsEnd() override;
+ void OnSettingsAck(const Http2FrameHeader& header) override;
+ void OnPushPromiseStart(const Http2FrameHeader& header,
+ const Http2PushPromiseFields& promise,
+ size_t total_padding_length) override;
+ void OnPushPromiseEnd() override;
+ void OnPing(const Http2FrameHeader& header,
+ const Http2PingFields& ping) override;
+ void OnPingAck(const Http2FrameHeader& header,
+ const Http2PingFields& ping) override;
+ void OnGoAwayStart(const Http2FrameHeader& header,
+ const Http2GoAwayFields& goaway) override;
+ void OnGoAwayOpaqueData(const char* data, size_t len) override;
+ void OnGoAwayEnd() override;
+ void OnWindowUpdate(const Http2FrameHeader& header,
+ uint32_t increment) override;
+ void OnAltSvcStart(const Http2FrameHeader& header,
+ size_t origin_length,
+ size_t value_length) override;
+ void OnAltSvcOriginData(const char* data, size_t len) override;
+ void OnAltSvcValueData(const char* data, size_t len) override;
+ void OnAltSvcEnd() override;
+ void OnUnknownStart(const Http2FrameHeader& header) override;
+ void OnUnknownPayload(const char* data, size_t len) override;
+ void OnUnknownEnd() override;
+ void OnPaddingTooLong(const Http2FrameHeader& header,
+ size_t missing_length) override;
+ void OnFrameSizeError(const Http2FrameHeader& header) override;
+
+ void AppendSetting(const Http2SettingFields& setting_fields) {
+ settings_.push_back(setting_fields);
+ }
+
+ const Http2FrameHeader& GetFrameHeader() const { return frame_header_; }
+
+ Http2Optional<Http2PriorityFields> GetOptPriority() const {
+ return opt_priority_;
+ }
+ Http2Optional<Http2ErrorCode> GetOptRstStreamErrorCode() const {
+ return opt_rst_stream_error_code_;
+ }
+ Http2Optional<Http2PushPromiseFields> GetOptPushPromise() const {
+ return opt_push_promise_;
+ }
+ Http2Optional<Http2PingFields> GetOptPing() const { return opt_ping_; }
+ Http2Optional<Http2GoAwayFields> GetOptGoaway() const { return opt_goaway_; }
+ Http2Optional<size_t> GetOptPadLength() const { return opt_pad_length_; }
+ Http2Optional<size_t> GetOptPayloadLength() const {
+ return opt_payload_length_;
+ }
+ Http2Optional<size_t> GetOptMissingLength() const {
+ return opt_missing_length_;
+ }
+ Http2Optional<size_t> GetOptAltsvcOriginLength() const {
+ return opt_altsvc_origin_length_;
+ }
+ Http2Optional<size_t> GetOptAltsvcValueLength() const {
+ return opt_altsvc_value_length_;
+ }
+ Http2Optional<size_t> GetOptWindowUpdateIncrement() const {
+ return opt_window_update_increment_;
+ }
+ bool GetHasFrameSizeError() const { return has_frame_size_error_; }
+
+ void SetOptPriority(Http2Optional<Http2PriorityFields> opt_priority) {
+ opt_priority_ = opt_priority;
+ }
+ void SetOptRstStreamErrorCode(
+ Http2Optional<Http2ErrorCode> opt_rst_stream_error_code) {
+ opt_rst_stream_error_code_ = opt_rst_stream_error_code;
+ }
+ void SetOptPushPromise(
+ Http2Optional<Http2PushPromiseFields> opt_push_promise) {
+ opt_push_promise_ = opt_push_promise;
+ }
+ void SetOptPing(Http2Optional<Http2PingFields> opt_ping) {
+ opt_ping_ = opt_ping;
+ }
+ void SetOptGoaway(Http2Optional<Http2GoAwayFields> opt_goaway) {
+ opt_goaway_ = opt_goaway;
+ }
+ void SetOptPadLength(Http2Optional<size_t> opt_pad_length) {
+ opt_pad_length_ = opt_pad_length;
+ }
+ void SetOptPayloadLength(Http2Optional<size_t> opt_payload_length) {
+ opt_payload_length_ = opt_payload_length;
+ }
+ void SetOptMissingLength(Http2Optional<size_t> opt_missing_length) {
+ opt_missing_length_ = opt_missing_length;
+ }
+ void SetOptAltsvcOriginLength(
+ Http2Optional<size_t> opt_altsvc_origin_length) {
+ opt_altsvc_origin_length_ = opt_altsvc_origin_length;
+ }
+ void SetOptAltsvcValueLength(Http2Optional<size_t> opt_altsvc_value_length) {
+ opt_altsvc_value_length_ = opt_altsvc_value_length;
+ }
+ void SetOptWindowUpdateIncrement(
+ Http2Optional<size_t> opt_window_update_increment) {
+ opt_window_update_increment_ = opt_window_update_increment;
+ }
+
+ void SetHasFrameSizeError(bool has_frame_size_error) {
+ has_frame_size_error_ = has_frame_size_error;
+ }
+
+ private:
+ // ASSERT during an On* method that we're handling a frame of type
+ // expected_frame_type, and have not already received other On* methods
+ // (i.e. got_start_callback is false).
+ ::testing::AssertionResult StartFrameOfType(
+ const Http2FrameHeader& header,
+ Http2FrameType expected_frame_type);
+
+ // ASSERT that StartFrameOfType has already been called with
+ // expected_frame_type (i.e. got_start_callback has been called), and that
+ // EndFrameOfType has not yet been called (i.e. got_end_callback is false).
+ ::testing::AssertionResult InFrameOfType(Http2FrameType expected_frame_type);
+
+ // ASSERT that we're InFrameOfType, and then sets got_end_callback=true.
+ ::testing::AssertionResult EndFrameOfType(Http2FrameType expected_frame_type);
+
+ // ASSERT that we're in the middle of processing a frame that is padded.
+ ::testing::AssertionResult InPaddedFrame();
+
+ // Append source to target. If opt_length is not nullptr, then verifies that
+ // the optional has a value (i.e. that the necessary On*Start method has been
+ // called), and that target is not longer than opt_length->value().
+ ::testing::AssertionResult AppendString(Http2StringPiece source,
+ Http2String* target,
+ Http2Optional<size_t>* opt_length);
+
+ const Http2FrameHeader frame_header_;
+
+ Http2String payload_;
+ Http2String padding_;
+ Http2String altsvc_origin_;
+ Http2String altsvc_value_;
+
+ Http2Optional<Http2PriorityFields> opt_priority_;
+ Http2Optional<Http2ErrorCode> opt_rst_stream_error_code_;
+ Http2Optional<Http2PushPromiseFields> opt_push_promise_;
+ Http2Optional<Http2PingFields> opt_ping_;
+ Http2Optional<Http2GoAwayFields> opt_goaway_;
+
+ Http2Optional<size_t> opt_pad_length_;
+ Http2Optional<size_t> opt_payload_length_;
+ Http2Optional<size_t> opt_missing_length_;
+ Http2Optional<size_t> opt_altsvc_origin_length_;
+ Http2Optional<size_t> opt_altsvc_value_length_;
+
+ Http2Optional<size_t> opt_window_update_increment_;
+
+ bool has_frame_size_error_ = false;
+
+ std::vector<Http2SettingFields> settings_;
+
+ // These booleans are not checked by CompareCollectedFrames.
+ bool got_start_callback_ = false;
+ bool got_end_callback_ = false;
+};
+
+std::ostream& operator<<(std::ostream& out, const FrameParts& v);
+
+} // namespace test
+} // namespace http2
+
+#endif // QUICHE_HTTP2_TEST_TOOLS_FRAME_PARTS_H_
diff --git a/chromium/net/third_party/quiche/src/http2/test_tools/frame_parts_collector.cc b/chromium/net/third_party/quiche/src/http2/test_tools/frame_parts_collector.cc
new file mode 100644
index 00000000000..be4d986259f
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/test_tools/frame_parts_collector.cc
@@ -0,0 +1,113 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/http2/test_tools/frame_parts_collector.h"
+
+#include <utility>
+
+#include "base/logging.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/http2/http2_structures_test_util.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_ptr_util.h"
+
+namespace http2 {
+namespace test {
+
+FramePartsCollector::FramePartsCollector() = default;
+FramePartsCollector::~FramePartsCollector() = default;
+
+void FramePartsCollector::Reset() {
+ current_frame_.reset();
+ collected_frames_.clear();
+ expected_header_set_ = false;
+}
+
+const FrameParts* FramePartsCollector::frame(size_t n) const {
+ if (n < size()) {
+ return collected_frames_.at(n).get();
+ }
+ CHECK(n == size());
+ return current_frame();
+}
+
+void FramePartsCollector::ExpectFrameHeader(const Http2FrameHeader& header) {
+ EXPECT_FALSE(IsInProgress());
+ EXPECT_FALSE(expected_header_set_)
+ << "expected_header_: " << expected_header_;
+ expected_header_ = header;
+ expected_header_set_ = true;
+ // OnFrameHeader is called before the flags are scrubbed, but the other
+ // methods are called after, so scrub the invalid flags from expected_header_.
+ ScrubFlagsOfHeader(&expected_header_);
+}
+
+void FramePartsCollector::TestExpectedHeader(const Http2FrameHeader& header) {
+ if (expected_header_set_) {
+ EXPECT_EQ(header, expected_header_);
+ expected_header_set_ = false;
+ }
+}
+
+Http2FrameDecoderListener* FramePartsCollector::StartFrame(
+ const Http2FrameHeader& header) {
+ TestExpectedHeader(header);
+ EXPECT_FALSE(IsInProgress());
+ if (current_frame_ == nullptr) {
+ current_frame_ = Http2MakeUnique<FrameParts>(header);
+ }
+ return current_frame();
+}
+
+Http2FrameDecoderListener* FramePartsCollector::StartAndEndFrame(
+ const Http2FrameHeader& header) {
+ TestExpectedHeader(header);
+ EXPECT_FALSE(IsInProgress());
+ if (current_frame_ == nullptr) {
+ current_frame_ = Http2MakeUnique<FrameParts>(header);
+ }
+ Http2FrameDecoderListener* result = current_frame();
+ collected_frames_.push_back(std::move(current_frame_));
+ return result;
+}
+
+Http2FrameDecoderListener* FramePartsCollector::CurrentFrame() {
+ EXPECT_TRUE(IsInProgress());
+ if (current_frame_ == nullptr) {
+ return &failing_listener_;
+ }
+ return current_frame();
+}
+
+Http2FrameDecoderListener* FramePartsCollector::EndFrame() {
+ EXPECT_TRUE(IsInProgress());
+ if (current_frame_ == nullptr) {
+ return &failing_listener_;
+ }
+ Http2FrameDecoderListener* result = current_frame();
+ collected_frames_.push_back(std::move(current_frame_));
+ return result;
+}
+
+Http2FrameDecoderListener* FramePartsCollector::FrameError(
+ const Http2FrameHeader& header) {
+ TestExpectedHeader(header);
+ if (current_frame_ == nullptr) {
+ // The decoder may detect an error before making any calls to the listener
+ // regarding the frame, in which case current_frame_==nullptr and we need
+ // to create a FrameParts instance.
+ current_frame_ = Http2MakeUnique<FrameParts>(header);
+ } else {
+ // Similarly, the decoder may have made calls to the listener regarding the
+ // frame before detecting the error; for example, the DATA payload decoder
+ // calls OnDataStart before it can detect padding errors, hence before it
+ // can call OnPaddingTooLong.
+ EXPECT_EQ(header, current_frame_->GetFrameHeader());
+ }
+ Http2FrameDecoderListener* result = current_frame();
+ collected_frames_.push_back(std::move(current_frame_));
+ return result;
+}
+
+} // namespace test
+} // namespace http2
diff --git a/chromium/net/third_party/quiche/src/http2/test_tools/frame_parts_collector.h b/chromium/net/third_party/quiche/src/http2/test_tools/frame_parts_collector.h
new file mode 100644
index 00000000000..a35740a9f25
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/test_tools/frame_parts_collector.h
@@ -0,0 +1,111 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_HTTP2_TEST_TOOLS_FRAME_PARTS_COLLECTOR_H_
+#define QUICHE_HTTP2_TEST_TOOLS_FRAME_PARTS_COLLECTOR_H_
+
+// FramePartsCollector is a base class for Http2FrameDecoderListener
+// implementations that create one FrameParts instance for each decoded frame.
+
+#include <stddef.h>
+
+#include <memory>
+#include <vector>
+
+#include "net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener.h"
+#include "net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener_test_util.h"
+#include "net/third_party/quiche/src/http2/http2_structures.h"
+#include "net/third_party/quiche/src/http2/test_tools/frame_parts.h"
+
+namespace http2 {
+namespace test {
+
+class FramePartsCollector : public FailingHttp2FrameDecoderListener {
+ public:
+ FramePartsCollector();
+ ~FramePartsCollector() override;
+
+ // Toss out the collected data.
+ void Reset();
+
+ // Returns true if has started recording the info for a frame and has not yet
+ // finished doing so.
+ bool IsInProgress() const { return current_frame_ != nullptr; }
+
+ // Returns the FrameParts instance into which we're currently recording
+ // callback info if IsInProgress, else nullptr.
+ const FrameParts* current_frame() const { return current_frame_.get(); }
+
+ // Returns the number of completely collected FrameParts instances.
+ size_t size() const { return collected_frames_.size(); }
+
+ // Returns the n'th frame, where 0 is the oldest of the collected frames,
+ // and n==size() is the frame currently being collected, if there is one.
+ // Returns nullptr if the requested index is not valid.
+ const FrameParts* frame(size_t n) const;
+
+ protected:
+ // In support of OnFrameHeader, set the header that we expect to be used in
+ // the next call.
+ // TODO(jamessynge): Remove ExpectFrameHeader et al. once done with supporting
+ // SpdyFramer's exact states.
+ void ExpectFrameHeader(const Http2FrameHeader& header);
+
+ // For use in implementing On*Start methods of Http2FrameDecoderListener,
+ // returns a FrameParts instance, which will be newly created if
+ // IsInProgress==false (which the caller should ensure), else will be the
+ // current_frame(); never returns nullptr.
+ // If called when IsInProgress==true, a test failure will be recorded.
+ Http2FrameDecoderListener* StartFrame(const Http2FrameHeader& header);
+
+ // For use in implementing On* callbacks, such as OnPingAck, that are the only
+ // call expected for the frame being decoded; not for On*Start methods.
+ // Returns a FrameParts instance, which will be newly created if
+ // IsInProgress==false (which the caller should ensure), else will be the
+ // current_frame(); never returns nullptr.
+ // If called when IsInProgress==true, a test failure will be recorded.
+ Http2FrameDecoderListener* StartAndEndFrame(const Http2FrameHeader& header);
+
+ // If IsInProgress==true, returns the FrameParts into which the current
+ // frame is being recorded; else records a test failure and returns
+ // failing_listener_, which will record a test failure when any of its
+ // On* methods is called.
+ Http2FrameDecoderListener* CurrentFrame();
+
+ // For use in implementing On*End methods, pushes the current frame onto
+ // the vector of completed frames, and returns a pointer to it for recording
+ // the info in the final call. If IsInProgress==false, records a test failure
+ // and returns failing_listener_, which will record a test failure when any
+ // of its On* methods is called.
+ Http2FrameDecoderListener* EndFrame();
+
+ // For use in implementing OnPaddingTooLong and OnFrameSizeError, is
+ // equivalent to EndFrame() if IsInProgress==true, else equivalent to
+ // StartAndEndFrame().
+ Http2FrameDecoderListener* FrameError(const Http2FrameHeader& header);
+
+ private:
+ // Returns the mutable FrameParts instance into which we're currently
+ // recording callback info if IsInProgress, else nullptr.
+ FrameParts* current_frame() { return current_frame_.get(); }
+
+ // If expected header is set, verify that it matches the header param.
+ // TODO(jamessynge): Remove TestExpectedHeader et al. once done
+ // with supporting SpdyFramer's exact states.
+ void TestExpectedHeader(const Http2FrameHeader& header);
+
+ std::unique_ptr<FrameParts> current_frame_;
+ std::vector<std::unique_ptr<FrameParts>> collected_frames_;
+ FailingHttp2FrameDecoderListener failing_listener_;
+
+ // TODO(jamessynge): Remove expected_header_ et al. once done with supporting
+ // SpdyFramer's exact states.
+ Http2FrameHeader expected_header_;
+ bool expected_header_set_ = false;
+};
+
+} // namespace test
+} // namespace http2
+
+#endif // QUICHE_HTTP2_TEST_TOOLS_FRAME_PARTS_COLLECTOR_H_
diff --git a/chromium/net/third_party/quiche/src/http2/test_tools/frame_parts_collector_listener.cc b/chromium/net/third_party/quiche/src/http2/test_tools/frame_parts_collector_listener.cc
new file mode 100644
index 00000000000..8d9da78707b
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/test_tools/frame_parts_collector_listener.cc
@@ -0,0 +1,230 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/http2/test_tools/frame_parts_collector_listener.h"
+
+#include "base/logging.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace http2 {
+namespace test {
+
+bool FramePartsCollectorListener::OnFrameHeader(
+ const Http2FrameHeader& header) {
+ VLOG(1) << "OnFrameHeader: " << header;
+ ExpectFrameHeader(header);
+ return true;
+}
+
+void FramePartsCollectorListener::OnDataStart(const Http2FrameHeader& header) {
+ VLOG(1) << "OnDataStart: " << header;
+ StartFrame(header)->OnDataStart(header);
+}
+
+void FramePartsCollectorListener::OnDataPayload(const char* data, size_t len) {
+ VLOG(1) << "OnDataPayload: len=" << len;
+ CurrentFrame()->OnDataPayload(data, len);
+}
+
+void FramePartsCollectorListener::OnDataEnd() {
+ VLOG(1) << "OnDataEnd";
+ EndFrame()->OnDataEnd();
+}
+
+void FramePartsCollectorListener::OnHeadersStart(
+ const Http2FrameHeader& header) {
+ VLOG(1) << "OnHeadersStart: " << header;
+ StartFrame(header)->OnHeadersStart(header);
+}
+
+void FramePartsCollectorListener::OnHeadersPriority(
+ const Http2PriorityFields& priority) {
+ VLOG(1) << "OnHeadersPriority: " << priority;
+ CurrentFrame()->OnHeadersPriority(priority);
+}
+
+void FramePartsCollectorListener::OnHpackFragment(const char* data,
+ size_t len) {
+ VLOG(1) << "OnHpackFragment: len=" << len;
+ CurrentFrame()->OnHpackFragment(data, len);
+}
+
+void FramePartsCollectorListener::OnHeadersEnd() {
+ VLOG(1) << "OnHeadersEnd";
+ EndFrame()->OnHeadersEnd();
+}
+
+void FramePartsCollectorListener::OnPriorityFrame(
+ const Http2FrameHeader& header,
+ const Http2PriorityFields& priority_fields) {
+ VLOG(1) << "OnPriority: " << header << "; " << priority_fields;
+ StartAndEndFrame(header)->OnPriorityFrame(header, priority_fields);
+}
+
+void FramePartsCollectorListener::OnContinuationStart(
+ const Http2FrameHeader& header) {
+ VLOG(1) << "OnContinuationStart: " << header;
+ StartFrame(header)->OnContinuationStart(header);
+}
+
+void FramePartsCollectorListener::OnContinuationEnd() {
+ VLOG(1) << "OnContinuationEnd";
+ EndFrame()->OnContinuationEnd();
+}
+
+void FramePartsCollectorListener::OnPadLength(size_t pad_length) {
+ VLOG(1) << "OnPadLength: " << pad_length;
+ CurrentFrame()->OnPadLength(pad_length);
+}
+
+void FramePartsCollectorListener::OnPadding(const char* padding,
+ size_t skipped_length) {
+ VLOG(1) << "OnPadding: " << skipped_length;
+ CurrentFrame()->OnPadding(padding, skipped_length);
+}
+
+void FramePartsCollectorListener::OnRstStream(const Http2FrameHeader& header,
+ Http2ErrorCode error_code) {
+ VLOG(1) << "OnRstStream: " << header << "; error_code=" << error_code;
+ StartAndEndFrame(header)->OnRstStream(header, error_code);
+}
+
+void FramePartsCollectorListener::OnSettingsStart(
+ const Http2FrameHeader& header) {
+ VLOG(1) << "OnSettingsStart: " << header;
+ EXPECT_EQ(Http2FrameType::SETTINGS, header.type) << header;
+ EXPECT_EQ(Http2FrameFlag(), header.flags) << header;
+ StartFrame(header)->OnSettingsStart(header);
+}
+
+void FramePartsCollectorListener::OnSetting(
+ const Http2SettingFields& setting_fields) {
+ VLOG(1) << "Http2SettingFields: setting_fields=" << setting_fields;
+ CurrentFrame()->OnSetting(setting_fields);
+}
+
+void FramePartsCollectorListener::OnSettingsEnd() {
+ VLOG(1) << "OnSettingsEnd";
+ EndFrame()->OnSettingsEnd();
+}
+
+void FramePartsCollectorListener::OnSettingsAck(
+ const Http2FrameHeader& header) {
+ VLOG(1) << "OnSettingsAck: " << header;
+ StartAndEndFrame(header)->OnSettingsAck(header);
+}
+
+void FramePartsCollectorListener::OnPushPromiseStart(
+ const Http2FrameHeader& header,
+ const Http2PushPromiseFields& promise,
+ size_t total_padding_length) {
+ VLOG(1) << "OnPushPromiseStart header: " << header << " promise: " << promise
+ << " total_padding_length: " << total_padding_length;
+ EXPECT_EQ(Http2FrameType::PUSH_PROMISE, header.type);
+ StartFrame(header)->OnPushPromiseStart(header, promise, total_padding_length);
+}
+
+void FramePartsCollectorListener::OnPushPromiseEnd() {
+ VLOG(1) << "OnPushPromiseEnd";
+ EndFrame()->OnPushPromiseEnd();
+}
+
+void FramePartsCollectorListener::OnPing(const Http2FrameHeader& header,
+ const Http2PingFields& ping) {
+ VLOG(1) << "OnPing: " << header << "; " << ping;
+ StartAndEndFrame(header)->OnPing(header, ping);
+}
+
+void FramePartsCollectorListener::OnPingAck(const Http2FrameHeader& header,
+ const Http2PingFields& ping) {
+ VLOG(1) << "OnPingAck: " << header << "; " << ping;
+ StartAndEndFrame(header)->OnPingAck(header, ping);
+}
+
+void FramePartsCollectorListener::OnGoAwayStart(
+ const Http2FrameHeader& header,
+ const Http2GoAwayFields& goaway) {
+ VLOG(1) << "OnGoAwayStart header: " << header << "; goaway: " << goaway;
+ StartFrame(header)->OnGoAwayStart(header, goaway);
+}
+
+void FramePartsCollectorListener::OnGoAwayOpaqueData(const char* data,
+ size_t len) {
+ VLOG(1) << "OnGoAwayOpaqueData: len=" << len;
+ CurrentFrame()->OnGoAwayOpaqueData(data, len);
+}
+
+void FramePartsCollectorListener::OnGoAwayEnd() {
+ VLOG(1) << "OnGoAwayEnd";
+ EndFrame()->OnGoAwayEnd();
+}
+
+void FramePartsCollectorListener::OnWindowUpdate(
+ const Http2FrameHeader& header,
+ uint32_t window_size_increment) {
+ VLOG(1) << "OnWindowUpdate: " << header
+ << "; window_size_increment=" << window_size_increment;
+ EXPECT_EQ(Http2FrameType::WINDOW_UPDATE, header.type);
+ StartAndEndFrame(header)->OnWindowUpdate(header, window_size_increment);
+}
+
+void FramePartsCollectorListener::OnAltSvcStart(const Http2FrameHeader& header,
+ size_t origin_length,
+ size_t value_length) {
+ VLOG(1) << "OnAltSvcStart header: " << header
+ << "; origin_length=" << origin_length
+ << "; value_length=" << value_length;
+ StartFrame(header)->OnAltSvcStart(header, origin_length, value_length);
+}
+
+void FramePartsCollectorListener::OnAltSvcOriginData(const char* data,
+ size_t len) {
+ VLOG(1) << "OnAltSvcOriginData: len=" << len;
+ CurrentFrame()->OnAltSvcOriginData(data, len);
+}
+
+void FramePartsCollectorListener::OnAltSvcValueData(const char* data,
+ size_t len) {
+ VLOG(1) << "OnAltSvcValueData: len=" << len;
+ CurrentFrame()->OnAltSvcValueData(data, len);
+}
+
+void FramePartsCollectorListener::OnAltSvcEnd() {
+ VLOG(1) << "OnAltSvcEnd";
+ EndFrame()->OnAltSvcEnd();
+}
+
+void FramePartsCollectorListener::OnUnknownStart(
+ const Http2FrameHeader& header) {
+ VLOG(1) << "OnUnknownStart: " << header;
+ StartFrame(header)->OnUnknownStart(header);
+}
+
+void FramePartsCollectorListener::OnUnknownPayload(const char* data,
+ size_t len) {
+ VLOG(1) << "OnUnknownPayload: len=" << len;
+ CurrentFrame()->OnUnknownPayload(data, len);
+}
+
+void FramePartsCollectorListener::OnUnknownEnd() {
+ VLOG(1) << "OnUnknownEnd";
+ EndFrame()->OnUnknownEnd();
+}
+
+void FramePartsCollectorListener::OnPaddingTooLong(
+ const Http2FrameHeader& header,
+ size_t missing_length) {
+ VLOG(1) << "OnPaddingTooLong: " << header
+ << " missing_length: " << missing_length;
+ EndFrame()->OnPaddingTooLong(header, missing_length);
+}
+
+void FramePartsCollectorListener::OnFrameSizeError(
+ const Http2FrameHeader& header) {
+ VLOG(1) << "OnFrameSizeError: " << header;
+ FrameError(header)->OnFrameSizeError(header);
+}
+
+} // namespace test
+} // namespace http2
diff --git a/chromium/net/third_party/quiche/src/http2/test_tools/frame_parts_collector_listener.h b/chromium/net/third_party/quiche/src/http2/test_tools/frame_parts_collector_listener.h
new file mode 100644
index 00000000000..07a82aaa806
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/test_tools/frame_parts_collector_listener.h
@@ -0,0 +1,85 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_HTTP2_TEST_TOOLS_FRAME_PARTS_COLLECTOR_LISTENER_H_
+#define QUICHE_HTTP2_TEST_TOOLS_FRAME_PARTS_COLLECTOR_LISTENER_H_
+
+// FramePartsCollectorListener extends FramePartsCollector with an
+// implementation of every method of Http2FrameDecoderListener; it is
+// essentially the union of all the Listener classes in the tests of the
+// payload decoders (i.e. in ./payload_decoders/*_test.cc files), with the
+// addition of the OnFrameHeader method.
+// FramePartsCollectorListener supports tests of Http2FrameDecoder.
+
+#include <stddef.h>
+
+#include <cstdint>
+
+#include "net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener.h"
+#include "net/third_party/quiche/src/http2/http2_constants.h"
+#include "net/third_party/quiche/src/http2/http2_structures.h"
+#include "net/third_party/quiche/src/http2/test_tools/frame_parts_collector.h"
+
+namespace http2 {
+namespace test {
+
+class FramePartsCollectorListener : public FramePartsCollector {
+ public:
+ FramePartsCollectorListener() {}
+ ~FramePartsCollectorListener() override {}
+
+ // TODO(jamessynge): Remove OnFrameHeader once done with supporting
+ // SpdyFramer's exact states.
+ bool OnFrameHeader(const Http2FrameHeader& header) override;
+ void OnDataStart(const Http2FrameHeader& header) override;
+ void OnDataPayload(const char* data, size_t len) override;
+ void OnDataEnd() override;
+ void OnHeadersStart(const Http2FrameHeader& header) override;
+ void OnHeadersPriority(const Http2PriorityFields& priority) override;
+ void OnHpackFragment(const char* data, size_t len) override;
+ void OnHeadersEnd() override;
+ void OnPriorityFrame(const Http2FrameHeader& header,
+ const Http2PriorityFields& priority_fields) override;
+ void OnContinuationStart(const Http2FrameHeader& header) override;
+ void OnContinuationEnd() override;
+ void OnPadLength(size_t pad_length) override;
+ void OnPadding(const char* padding, size_t skipped_length) override;
+ void OnRstStream(const Http2FrameHeader& header,
+ Http2ErrorCode error_code) override;
+ void OnSettingsStart(const Http2FrameHeader& header) override;
+ void OnSetting(const Http2SettingFields& setting_fields) override;
+ void OnSettingsEnd() override;
+ void OnSettingsAck(const Http2FrameHeader& header) override;
+ void OnPushPromiseStart(const Http2FrameHeader& header,
+ const Http2PushPromiseFields& promise,
+ size_t total_padding_length) override;
+ void OnPushPromiseEnd() override;
+ void OnPing(const Http2FrameHeader& header,
+ const Http2PingFields& ping) override;
+ void OnPingAck(const Http2FrameHeader& header,
+ const Http2PingFields& ping) override;
+ void OnGoAwayStart(const Http2FrameHeader& header,
+ const Http2GoAwayFields& goaway) override;
+ void OnGoAwayOpaqueData(const char* data, size_t len) override;
+ void OnGoAwayEnd() override;
+ void OnWindowUpdate(const Http2FrameHeader& header,
+ uint32_t window_size_increment) override;
+ void OnAltSvcStart(const Http2FrameHeader& header,
+ size_t origin_length,
+ size_t value_length) override;
+ void OnAltSvcOriginData(const char* data, size_t len) override;
+ void OnAltSvcValueData(const char* data, size_t len) override;
+ void OnAltSvcEnd() override;
+ void OnUnknownStart(const Http2FrameHeader& header) override;
+ void OnUnknownPayload(const char* data, size_t len) override;
+ void OnUnknownEnd() override;
+ void OnPaddingTooLong(const Http2FrameHeader& header,
+ size_t missing_length) override;
+ void OnFrameSizeError(const Http2FrameHeader& header) override;
+};
+
+} // namespace test
+} // namespace http2
+
+#endif // QUICHE_HTTP2_TEST_TOOLS_FRAME_PARTS_COLLECTOR_LISTENER_H_
diff --git a/chromium/net/third_party/quiche/src/http2/test_tools/http2_random.cc b/chromium/net/third_party/quiche/src/http2/test_tools/http2_random.cc
new file mode 100644
index 00000000000..fc577f467fe
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/test_tools/http2_random.cc
@@ -0,0 +1,72 @@
+#include "net/third_party/quiche/src/http2/test_tools/http2_random.h"
+
+#include "base/logging.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string_utils.h"
+#include "third_party/boringssl/src/include/openssl/chacha.h"
+#include "third_party/boringssl/src/include/openssl/rand.h"
+
+static const uint8_t kZeroNonce[12] = {0};
+
+namespace http2 {
+namespace test {
+
+Http2Random::Http2Random() {
+ RAND_bytes(key_, sizeof(key_));
+
+ LOG(INFO) << "Initialized test RNG with the following key: " << Key();
+}
+
+Http2Random::Http2Random(Http2StringPiece key) {
+ Http2String decoded_key = Http2HexDecode(key);
+ CHECK_EQ(sizeof(key_), decoded_key.size());
+ memcpy(key_, decoded_key.data(), sizeof(key_));
+}
+
+Http2String Http2Random::Key() const {
+ return Http2HexEncode(key_, sizeof(key_));
+}
+
+void Http2Random::FillRandom(void* buffer, size_t buffer_size) {
+ memset(buffer, 0, buffer_size);
+ uint8_t* buffer_u8 = reinterpret_cast<uint8_t*>(buffer);
+ CRYPTO_chacha_20(buffer_u8, buffer_u8, buffer_size, key_, kZeroNonce,
+ counter_++);
+}
+
+Http2String Http2Random::RandString(int length) {
+ Http2String result;
+ result.resize(length);
+ FillRandom(&result[0], length);
+ return result;
+}
+
+uint64_t Http2Random::Rand64() {
+ union {
+ uint64_t number;
+ uint8_t bytes[sizeof(uint64_t)];
+ } result;
+ FillRandom(result.bytes, sizeof(result.bytes));
+ return result.number;
+}
+
+double Http2Random::RandDouble() {
+ union {
+ double f;
+ uint64_t i;
+ } value;
+ value.i = (1023ull << 52ull) | (Rand64() & 0xfffffffffffffu);
+ return value.f - 1.0;
+}
+
+Http2String Http2Random::RandStringWithAlphabet(int length,
+ Http2StringPiece alphabet) {
+ Http2String result;
+ result.resize(length);
+ for (int i = 0; i < length; i++) {
+ result[i] = alphabet[Uniform(alphabet.size())];
+ }
+ return result;
+}
+
+} // namespace test
+} // namespace http2
diff --git a/chromium/net/third_party/quiche/src/http2/test_tools/http2_random.h b/chromium/net/third_party/quiche/src/http2/test_tools/http2_random.h
new file mode 100644
index 00000000000..def60f8665d
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/test_tools/http2_random.h
@@ -0,0 +1,88 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_HTTP2_TEST_TOOLS_HTTP2_RANDOM_H_
+#define QUICHE_HTTP2_TEST_TOOLS_HTTP2_RANDOM_H_
+
+#include <cmath>
+#include <cstdint>
+#include <limits>
+#include <random>
+
+#include "net/third_party/quiche/src/http2/platform/api/http2_string.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string_piece.h"
+
+namespace http2 {
+namespace test {
+
+// The random number generator used for unit tests. Since the algorithm is
+// deterministic and fixed, this can be used to reproduce flakes in the unit
+// tests caused by specific random values.
+class Http2Random {
+ public:
+ Http2Random();
+
+ Http2Random(const Http2Random&) = delete;
+ Http2Random& operator=(const Http2Random&) = delete;
+
+ // Reproducible random number generation: by using the same key, the same
+ // sequence of results is obtained.
+ explicit Http2Random(Http2StringPiece key);
+ Http2String Key() const;
+
+ void FillRandom(void* buffer, size_t buffer_size);
+ Http2String RandString(int length);
+
+ // Returns a random 64-bit value.
+ uint64_t Rand64();
+
+ // Return a uniformly distrubted random number in [0, n).
+ uint32_t Uniform(uint32_t n) { return Rand64() % n; }
+ // Return a uniformly distrubted random number in [lo, hi).
+ uint64_t UniformInRange(uint64_t lo, uint64_t hi) {
+ return lo + Rand64() % (hi - lo);
+ }
+ // Return an integer of logarithmically random scale.
+ uint32_t Skewed(uint32_t max_log) {
+ const uint32_t base = Rand32() % (max_log + 1);
+ const uint32_t mask = ((base < 32) ? (1u << base) : 0u) - 1u;
+ return Rand32() & mask;
+ }
+ // Return a random number in [0, max] range that skews low.
+ uint64_t RandomSizeSkewedLow(uint64_t max) {
+ return std::round(max * std::pow(RandDouble(), 2));
+ }
+
+ // Returns a random double between 0 and 1.
+ double RandDouble();
+ float RandFloat() { return RandDouble(); }
+
+ // Has 1/n chance of returning true.
+ bool OneIn(int n) { return Uniform(n) == 0; }
+
+ uint8_t Rand8() { return Rand64(); }
+ uint16_t Rand16() { return Rand64(); }
+ uint32_t Rand32() { return Rand64(); }
+
+ // Return a random string consisting of the characters from the specified
+ // alphabet.
+ Http2String RandStringWithAlphabet(int length, Http2StringPiece alphabet);
+
+ // STL UniformRandomNumberGenerator implementation.
+ using result_type = uint64_t;
+ static constexpr result_type min() { return 0; }
+ static constexpr result_type max() {
+ return std::numeric_limits<result_type>::max();
+ }
+ result_type operator()() { return Rand64(); }
+
+ private:
+ uint8_t key_[32];
+ uint32_t counter_ = 0;
+};
+
+} // namespace test
+} // namespace http2
+
+#endif // QUICHE_HTTP2_TEST_TOOLS_HTTP2_RANDOM_H_
diff --git a/chromium/net/third_party/quiche/src/http2/test_tools/http2_random_test.cc b/chromium/net/third_party/quiche/src/http2/test_tools/http2_random_test.cc
new file mode 100644
index 00000000000..a1b74f64a7b
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/test_tools/http2_random_test.cc
@@ -0,0 +1,94 @@
+#include "net/third_party/quiche/src/http2/test_tools/http2_random.h"
+
+#include <set>
+
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace http2 {
+namespace test {
+namespace {
+
+TEST(Http2RandomTest, ProducesDifferentNumbers) {
+ Http2Random random;
+ uint64_t value1 = random.Rand64();
+ uint64_t value2 = random.Rand64();
+ uint64_t value3 = random.Rand64();
+
+ EXPECT_NE(value1, value2);
+ EXPECT_NE(value2, value3);
+ EXPECT_NE(value3, value1);
+}
+
+TEST(Http2RandomTest, StartsWithDifferentKeys) {
+ Http2Random random1;
+ Http2Random random2;
+
+ EXPECT_NE(random1.Key(), random2.Key());
+ EXPECT_NE(random1.Rand64(), random2.Rand64());
+ EXPECT_NE(random1.Rand64(), random2.Rand64());
+ EXPECT_NE(random1.Rand64(), random2.Rand64());
+}
+
+TEST(Http2RandomTest, ReproducibleRandom) {
+ Http2Random random;
+ uint64_t value1 = random.Rand64();
+ uint64_t value2 = random.Rand64();
+
+ Http2Random clone_random(random.Key());
+ EXPECT_EQ(clone_random.Key(), random.Key());
+ EXPECT_EQ(value1, clone_random.Rand64());
+ EXPECT_EQ(value2, clone_random.Rand64());
+}
+
+TEST(Http2RandomTest, STLShuffle) {
+ Http2Random random;
+ const Http2String original = "abcdefghijklmonpqrsuvwxyz";
+
+ Http2String shuffled = original;
+ std::shuffle(shuffled.begin(), shuffled.end(), random);
+ EXPECT_NE(original, shuffled);
+}
+
+TEST(Http2RandomTest, RandFloat) {
+ Http2Random random;
+ for (int i = 0; i < 10000; i++) {
+ float value = random.RandFloat();
+ ASSERT_GE(value, 0.f);
+ ASSERT_LE(value, 1.f);
+ }
+}
+
+TEST(Http2RandomTest, RandStringWithAlphabet) {
+ Http2Random random;
+ Http2String str = random.RandStringWithAlphabet(1000, "xyz");
+ EXPECT_EQ(1000u, str.size());
+
+ std::set<char> characters(str.begin(), str.end());
+ EXPECT_THAT(characters, testing::ElementsAre('x', 'y', 'z'));
+}
+
+TEST(Http2RandomTest, SkewedLow) {
+ Http2Random random;
+ constexpr size_t kMax = 1234;
+ for (int i = 0; i < 10000; i++) {
+ size_t value = random.RandomSizeSkewedLow(kMax);
+ ASSERT_GE(value, 0u);
+ ASSERT_LE(value, kMax);
+ }
+}
+
+// Checks that SkewedLow() generates full range. This is required, since in
+// some unit tests would infinitely loop.
+TEST(Http2RandomTest, SkewedLowFullRange) {
+ Http2Random random;
+ std::set<size_t> values;
+ for (int i = 0; i < 1000; i++) {
+ values.insert(random.RandomSizeSkewedLow(3));
+ }
+ EXPECT_THAT(values, testing::ElementsAre(0, 1, 2, 3));
+}
+
+} // namespace
+} // namespace test
+} // namespace http2
diff --git a/chromium/net/third_party/quiche/src/http2/tools/http2_frame_builder.cc b/chromium/net/third_party/quiche/src/http2/tools/http2_frame_builder.cc
new file mode 100644
index 00000000000..1dfcdeb0396
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/tools/http2_frame_builder.cc
@@ -0,0 +1,181 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/http2/tools/http2_frame_builder.h"
+
+#ifdef WIN32
+#include <winsock2.h> // for htonl() functions
+#else
+#include <arpa/inet.h>
+#include <netinet/in.h> // for htonl, htons
+#endif
+
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string_utils.h"
+
+namespace http2 {
+namespace test {
+
+Http2FrameBuilder::Http2FrameBuilder(Http2FrameType type,
+ uint8_t flags,
+ uint32_t stream_id) {
+ AppendUInt24(0); // Frame payload length, unknown so far.
+ Append(type);
+ AppendUInt8(flags);
+ AppendUInt31(stream_id);
+}
+
+Http2FrameBuilder::Http2FrameBuilder(const Http2FrameHeader& v) {
+ Append(v);
+}
+
+void Http2FrameBuilder::Append(Http2StringPiece s) {
+ Http2StrAppend(&buffer_, s);
+}
+
+void Http2FrameBuilder::AppendBytes(const void* data, uint32_t num_bytes) {
+ Append(Http2StringPiece(static_cast<const char*>(data), num_bytes));
+}
+
+void Http2FrameBuilder::AppendZeroes(size_t num_zero_bytes) {
+ char zero = 0;
+ buffer_.append(num_zero_bytes, zero);
+}
+
+void Http2FrameBuilder::AppendUInt8(uint8_t value) {
+ AppendBytes(&value, 1);
+}
+
+void Http2FrameBuilder::AppendUInt16(uint16_t value) {
+ value = htons(value);
+ AppendBytes(&value, 2);
+}
+
+void Http2FrameBuilder::AppendUInt24(uint32_t value) {
+ // Doesn't make sense to try to append a larger value, as that doesn't
+ // simulate something an encoder could do (i.e. the other 8 bits simply aren't
+ // there to be occupied).
+ EXPECT_EQ(value, value & 0xffffff);
+ value = htonl(value);
+ AppendBytes(reinterpret_cast<char*>(&value) + 1, 3);
+}
+
+void Http2FrameBuilder::AppendUInt31(uint32_t value) {
+ // If you want to test the high-bit being set, call AppendUInt32 instead.
+ uint32_t tmp = value & StreamIdMask();
+ EXPECT_EQ(value, value & StreamIdMask())
+ << "High-bit of uint32_t should be clear.";
+ value = htonl(tmp);
+ AppendBytes(&value, 4);
+}
+
+void Http2FrameBuilder::AppendUInt32(uint32_t value) {
+ value = htonl(value);
+ AppendBytes(&value, sizeof(value));
+}
+
+void Http2FrameBuilder::Append(Http2ErrorCode error_code) {
+ AppendUInt32(static_cast<uint32_t>(error_code));
+}
+
+void Http2FrameBuilder::Append(Http2FrameType type) {
+ AppendUInt8(static_cast<uint8_t>(type));
+}
+
+void Http2FrameBuilder::Append(Http2SettingsParameter parameter) {
+ AppendUInt16(static_cast<uint16_t>(parameter));
+}
+
+void Http2FrameBuilder::Append(const Http2FrameHeader& v) {
+ AppendUInt24(v.payload_length);
+ Append(v.type);
+ AppendUInt8(v.flags);
+ AppendUInt31(v.stream_id);
+}
+
+void Http2FrameBuilder::Append(const Http2PriorityFields& v) {
+ // The EXCLUSIVE flag is the high-bit of the 32-bit stream dependency field.
+ uint32_t tmp = v.stream_dependency & StreamIdMask();
+ EXPECT_EQ(tmp, v.stream_dependency);
+ if (v.is_exclusive) {
+ tmp |= 0x80000000;
+ }
+ AppendUInt32(tmp);
+
+ // The PRIORITY frame's weight field is logically in the range [1, 256],
+ // but is encoded as a byte in the range [0, 255].
+ ASSERT_LE(1u, v.weight);
+ ASSERT_LE(v.weight, 256u);
+ AppendUInt8(v.weight - 1);
+}
+
+void Http2FrameBuilder::Append(const Http2RstStreamFields& v) {
+ Append(v.error_code);
+}
+
+void Http2FrameBuilder::Append(const Http2SettingFields& v) {
+ Append(v.parameter);
+ AppendUInt32(v.value);
+}
+
+void Http2FrameBuilder::Append(const Http2PushPromiseFields& v) {
+ AppendUInt31(v.promised_stream_id);
+}
+
+void Http2FrameBuilder::Append(const Http2PingFields& v) {
+ AppendBytes(v.opaque_bytes, sizeof Http2PingFields::opaque_bytes);
+}
+
+void Http2FrameBuilder::Append(const Http2GoAwayFields& v) {
+ AppendUInt31(v.last_stream_id);
+ Append(v.error_code);
+}
+
+void Http2FrameBuilder::Append(const Http2WindowUpdateFields& v) {
+ EXPECT_NE(0u, v.window_size_increment) << "Increment must be non-zero.";
+ AppendUInt31(v.window_size_increment);
+}
+
+void Http2FrameBuilder::Append(const Http2AltSvcFields& v) {
+ AppendUInt16(v.origin_length);
+}
+
+// Methods for changing existing buffer contents.
+
+void Http2FrameBuilder::WriteAt(Http2StringPiece s, size_t offset) {
+ ASSERT_LE(offset, buffer_.size());
+ size_t len = offset + s.size();
+ if (len > buffer_.size()) {
+ buffer_.resize(len);
+ }
+ for (size_t ndx = 0; ndx < s.size(); ++ndx) {
+ buffer_[offset + ndx] = s[ndx];
+ }
+}
+
+void Http2FrameBuilder::WriteBytesAt(const void* data,
+ uint32_t num_bytes,
+ size_t offset) {
+ WriteAt(Http2StringPiece(static_cast<const char*>(data), num_bytes), offset);
+}
+
+void Http2FrameBuilder::WriteUInt24At(uint32_t value, size_t offset) {
+ ASSERT_LT(value, static_cast<uint32_t>(1 << 24));
+ value = htonl(value);
+ WriteBytesAt(reinterpret_cast<char*>(&value) + 1, sizeof(value) - 1, offset);
+}
+
+void Http2FrameBuilder::SetPayloadLength(uint32_t payload_length) {
+ WriteUInt24At(payload_length, 0);
+}
+
+size_t Http2FrameBuilder::SetPayloadLength() {
+ EXPECT_GE(size(), Http2FrameHeader::EncodedSize());
+ uint32_t payload_length = size() - Http2FrameHeader::EncodedSize();
+ SetPayloadLength(payload_length);
+ return payload_length;
+}
+
+} // namespace test
+} // namespace http2
diff --git a/chromium/net/third_party/quiche/src/http2/tools/http2_frame_builder.h b/chromium/net/third_party/quiche/src/http2/tools/http2_frame_builder.h
new file mode 100644
index 00000000000..c36cb56e48a
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/tools/http2_frame_builder.h
@@ -0,0 +1,101 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_HTTP2_TOOLS_HTTP2_FRAME_BUILDER_H_
+#define QUICHE_HTTP2_TOOLS_HTTP2_FRAME_BUILDER_H_
+
+// Http2FrameBuilder builds wire-format HTTP/2 frames (or fragments thereof)
+// from components.
+//
+// For now, this is only intended for use in tests, and thus has EXPECT* in the
+// code. If desired to use it in an encoder, it will need optimization work,
+// especially w.r.t memory mgmt, and the EXPECT* will need to be removed or
+// replaced with DCHECKs.
+
+#include <stddef.h> // for size_t
+
+#include <cstdint>
+
+#include "net/third_party/quiche/src/http2/http2_constants.h"
+#include "net/third_party/quiche/src/http2/http2_structures.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string_piece.h"
+
+namespace http2 {
+namespace test {
+
+class Http2FrameBuilder {
+ public:
+ Http2FrameBuilder(Http2FrameType type, uint8_t flags, uint32_t stream_id);
+ explicit Http2FrameBuilder(const Http2FrameHeader& v);
+ Http2FrameBuilder() {}
+ ~Http2FrameBuilder() {}
+
+ size_t size() const { return buffer_.size(); }
+ const Http2String& buffer() const { return buffer_; }
+
+ //----------------------------------------------------------------------------
+ // Methods for appending to the end of the buffer.
+
+ // Append a sequence of bytes from various sources.
+ void Append(Http2StringPiece s);
+ void AppendBytes(const void* data, uint32_t num_bytes);
+
+ // Append an array of type T[N] to the string. Intended for tests with arrays
+ // initialized from literals, such as:
+ // const char kData[] = {0x12, 0x23, ...};
+ // builder.AppendBytes(kData);
+ template <typename T, size_t N>
+ void AppendBytes(T (&buf)[N]) {
+ AppendBytes(buf, N * sizeof(buf[0]));
+ }
+
+ // Support for appending padding. Does not read or write the Pad Length field.
+ void AppendZeroes(size_t num_zero_bytes);
+
+ // Append various sizes of unsigned integers.
+ void AppendUInt8(uint8_t value);
+ void AppendUInt16(uint16_t value);
+ void AppendUInt24(uint32_t value);
+ void AppendUInt31(uint32_t value);
+ void AppendUInt32(uint32_t value);
+
+ // Append various enums.
+ void Append(Http2ErrorCode error_code);
+ void Append(Http2FrameType type);
+ void Append(Http2SettingsParameter parameter);
+
+ // Append various structures.
+ void Append(const Http2FrameHeader& v);
+ void Append(const Http2PriorityFields& v);
+ void Append(const Http2RstStreamFields& v);
+ void Append(const Http2SettingFields& v);
+ void Append(const Http2PushPromiseFields& v);
+ void Append(const Http2PingFields& v);
+ void Append(const Http2GoAwayFields& v);
+ void Append(const Http2WindowUpdateFields& v);
+ void Append(const Http2AltSvcFields& v);
+
+ // Methods for changing existing buffer contents (mostly focused on updating
+ // the payload length).
+
+ void WriteAt(Http2StringPiece s, size_t offset);
+ void WriteBytesAt(const void* data, uint32_t num_bytes, size_t offset);
+ void WriteUInt24At(uint32_t value, size_t offset);
+
+ // Set the payload length to the specified size.
+ void SetPayloadLength(uint32_t payload_length);
+
+ // Sets the payload length to the size of the buffer minus the size of
+ // the frame header.
+ size_t SetPayloadLength();
+
+ private:
+ Http2String buffer_;
+};
+
+} // namespace test
+} // namespace http2
+
+#endif // QUICHE_HTTP2_TOOLS_HTTP2_FRAME_BUILDER_H_
diff --git a/chromium/net/third_party/quiche/src/http2/tools/random_decoder_test.cc b/chromium/net/third_party/quiche/src/http2/tools/random_decoder_test.cc
new file mode 100644
index 00000000000..623487c6e0c
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/tools/random_decoder_test.cc
@@ -0,0 +1,167 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/http2/tools/random_decoder_test.h"
+
+#include <stddef.h>
+
+#include <algorithm>
+#include <memory>
+
+#include "base/logging.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_status.h"
+#include "net/third_party/quiche/src/http2/http2_constants.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_test_helpers.h"
+
+using ::testing::AssertionFailure;
+using ::testing::AssertionResult;
+using ::testing::AssertionSuccess;
+
+namespace http2 {
+namespace test {
+
+RandomDecoderTest::RandomDecoderTest() = default;
+
+bool RandomDecoderTest::StopDecodeOnDone() {
+ return stop_decode_on_done_;
+}
+
+DecodeStatus RandomDecoderTest::DecodeSegments(DecodeBuffer* original,
+ const SelectSize& select_size) {
+ DecodeStatus status = DecodeStatus::kDecodeInProgress;
+ bool first = true;
+ VLOG(2) << "DecodeSegments: input size=" << original->Remaining();
+ while (first || original->HasData()) {
+ size_t remaining = original->Remaining();
+ size_t size =
+ std::min(remaining, select_size(first, original->Offset(), remaining));
+ DecodeBuffer db(original->cursor(), size);
+ VLOG(2) << "Decoding " << size << " bytes of " << remaining << " remaining";
+ if (first) {
+ first = false;
+ status = StartDecoding(&db);
+ } else {
+ status = ResumeDecoding(&db);
+ }
+ // A decoder MUST consume some input (if any is available), else we could
+ // get stuck in infinite loops.
+ if (db.Offset() == 0 && db.HasData() &&
+ status != DecodeStatus::kDecodeError) {
+ ADD_FAILURE() << "Decoder didn't make any progress; db.FullSize="
+ << db.FullSize()
+ << " original.Offset=" << original->Offset();
+ return DecodeStatus::kDecodeError;
+ }
+ original->AdvanceCursor(db.Offset());
+ switch (status) {
+ case DecodeStatus::kDecodeDone:
+ if (original->Empty() || StopDecodeOnDone()) {
+ return DecodeStatus::kDecodeDone;
+ }
+ continue;
+ case DecodeStatus::kDecodeInProgress:
+ continue;
+ case DecodeStatus::kDecodeError:
+ return DecodeStatus::kDecodeError;
+ }
+ }
+ return status;
+}
+
+// Decode |original| multiple times, with different segmentations, validating
+// after each decode, returning on the first failure.
+AssertionResult RandomDecoderTest::DecodeAndValidateSeveralWays(
+ DecodeBuffer* original,
+ bool return_non_zero_on_first,
+ const Validator& validator) {
+ const uint32_t original_remaining = original->Remaining();
+ VLOG(1) << "DecodeAndValidateSeveralWays - Start, remaining = "
+ << original_remaining;
+ uint32_t first_consumed;
+ {
+ // Fast decode (no stopping unless decoder does so).
+ DecodeBuffer input(original->cursor(), original_remaining);
+ VLOG(2) << "DecodeSegmentsAndValidate with SelectRemaining";
+ VERIFY_SUCCESS(
+ DecodeSegmentsAndValidate(&input, SelectRemaining(), validator))
+ << "\nFailed with SelectRemaining; input.Offset=" << input.Offset()
+ << "; input.Remaining=" << input.Remaining();
+ first_consumed = input.Offset();
+ }
+ if (original_remaining <= 30) {
+ // Decode again, one byte at a time.
+ DecodeBuffer input(original->cursor(), original_remaining);
+ VLOG(2) << "DecodeSegmentsAndValidate with SelectOne";
+ VERIFY_SUCCESS(DecodeSegmentsAndValidate(&input, SelectOne(), validator))
+ << "\nFailed with SelectOne; input.Offset=" << input.Offset()
+ << "; input.Remaining=" << input.Remaining();
+ VERIFY_EQ(first_consumed, input.Offset()) << "\nFailed with SelectOne";
+ }
+ if (original_remaining <= 20) {
+ // Decode again, one or zero bytes at a time.
+ DecodeBuffer input(original->cursor(), original_remaining);
+ VLOG(2) << "DecodeSegmentsAndValidate with SelectZeroAndOne";
+ VERIFY_SUCCESS(DecodeSegmentsAndValidate(
+ &input, SelectZeroAndOne(return_non_zero_on_first), validator))
+ << "\nFailed with SelectZeroAndOne";
+ VERIFY_EQ(first_consumed, input.Offset())
+ << "\nFailed with SelectZeroAndOne; input.Offset=" << input.Offset()
+ << "; input.Remaining=" << input.Remaining();
+ }
+ {
+ // Decode again, with randomly selected segment sizes.
+ DecodeBuffer input(original->cursor(), original_remaining);
+ VLOG(2) << "DecodeSegmentsAndValidate with SelectRandom";
+ VERIFY_SUCCESS(DecodeSegmentsAndValidate(
+ &input, SelectRandom(return_non_zero_on_first), validator))
+ << "\nFailed with SelectRandom; input.Offset=" << input.Offset()
+ << "; input.Remaining=" << input.Remaining();
+ VERIFY_EQ(first_consumed, input.Offset()) << "\nFailed with SelectRandom";
+ }
+ VERIFY_EQ(original_remaining, original->Remaining());
+ original->AdvanceCursor(first_consumed);
+ VLOG(1) << "DecodeAndValidateSeveralWays - SUCCESS";
+ return ::testing::AssertionSuccess();
+}
+
+// static
+RandomDecoderTest::SelectSize RandomDecoderTest::SelectZeroAndOne(
+ bool return_non_zero_on_first) {
+ std::shared_ptr<bool> zero_next(new bool);
+ *zero_next = !return_non_zero_on_first;
+ return [zero_next](bool first, size_t offset, size_t remaining) -> size_t {
+ if (*zero_next) {
+ *zero_next = false;
+ return 0;
+ } else {
+ *zero_next = true;
+ return 1;
+ }
+ };
+}
+
+RandomDecoderTest::SelectSize RandomDecoderTest::SelectRandom(
+ bool return_non_zero_on_first) {
+ return [this, return_non_zero_on_first](bool first, size_t offset,
+ size_t remaining) -> size_t {
+ uint32_t r = random_.Rand32();
+ if (first && return_non_zero_on_first) {
+ CHECK_LT(0u, remaining);
+ if (remaining == 1) {
+ return 1;
+ }
+ return 1 + (r % remaining); // size in range [1, remaining).
+ }
+ return r % (remaining + 1); // size in range [0, remaining].
+ };
+}
+
+uint32_t RandomDecoderTest::RandStreamId() {
+ return random_.Rand32() & StreamIdMask();
+}
+
+} // namespace test
+} // namespace http2
diff --git a/chromium/net/third_party/quiche/src/http2/tools/random_decoder_test.h b/chromium/net/third_party/quiche/src/http2/tools/random_decoder_test.h
new file mode 100644
index 00000000000..36f319605f5
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/tools/random_decoder_test.h
@@ -0,0 +1,258 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_HTTP2_TOOLS_RANDOM_DECODER_TEST_H_
+#define QUICHE_HTTP2_TOOLS_RANDOM_DECODER_TEST_H_
+
+// RandomDecoderTest is a base class for tests of decoding various kinds
+// of HTTP/2 and HPACK encodings.
+
+// TODO(jamessynge): Move more methods into .cc file.
+
+#include <stddef.h>
+
+#include <cstdint>
+#include <functional>
+#include <memory>
+#include <type_traits>
+
+#include "base/logging.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_status.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string_piece.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_test_helpers.h"
+#include "net/third_party/quiche/src/http2/test_tools/http2_random.h"
+
+namespace http2 {
+namespace test {
+
+// Some helpers.
+
+template <typename T, size_t N>
+Http2StringPiece ToStringPiece(T (&data)[N]) {
+ return Http2StringPiece(reinterpret_cast<const char*>(data), N * sizeof(T));
+}
+
+// Overwrite the enum with some random value, probably not a valid value for
+// the enum type, but which fits into its storage.
+template <typename T,
+ typename E = typename std::enable_if<std::is_enum<T>::value>::type>
+void CorruptEnum(T* out, Http2Random* rng) {
+ // Per cppreference.com, if the destination type of a static_cast is
+ // smaller than the source type (i.e. type of r and uint32 below), the
+ // resulting value is the smallest unsigned value equal to the source value
+ // modulo 2^n, where n is the number of bits used to represent the
+ // destination type unsigned U.
+ using underlying_type_T = typename std::underlying_type<T>::type;
+ using unsigned_underlying_type_T =
+ typename std::make_unsigned<underlying_type_T>::type;
+ auto r = static_cast<unsigned_underlying_type_T>(rng->Rand32());
+ *out = static_cast<T>(r);
+}
+
+// Base class for tests of the ability to decode a sequence of bytes with
+// various boundaries between the DecodeBuffers provided to the decoder.
+class RandomDecoderTest : public ::testing::Test {
+ public:
+ // SelectSize returns the size of the next DecodeBuffer to be passed to the
+ // decoder. Note that RandomDecoderTest allows that size to be zero, though
+ // some decoders can't deal with that on the first byte, hence the |first|
+ // parameter.
+ typedef std::function<size_t(bool first, size_t offset, size_t remaining)>
+ SelectSize;
+
+ // Validator returns an AssertionResult so test can do:
+ // EXPECT_THAT(DecodeAndValidate(..., validator));
+ typedef ::testing::AssertionResult AssertionResult;
+ typedef std::function<AssertionResult(const DecodeBuffer& input,
+ DecodeStatus status)>
+ Validator;
+ typedef std::function<AssertionResult()> NoArgValidator;
+
+ RandomDecoderTest();
+
+ protected:
+ // TODO(jamessynge): Modify StartDecoding, etc. to (somehow) return
+ // AssertionResult so that the VERIFY_* methods exported from
+ // gunit_helpers.h can be widely used.
+
+ // Start decoding; call allows sub-class to Reset the decoder, or deal with
+ // the first byte if that is done in a unique fashion. Might be called with
+ // a zero byte buffer.
+ virtual DecodeStatus StartDecoding(DecodeBuffer* db) = 0;
+
+ // Resume decoding of the input after a prior call to StartDecoding, and
+ // possibly many calls to ResumeDecoding.
+ virtual DecodeStatus ResumeDecoding(DecodeBuffer* db) = 0;
+
+ // Return true if a decode status of kDecodeDone indicates that
+ // decoding should stop.
+ virtual bool StopDecodeOnDone();
+
+ // Decode buffer |original| until we run out of input, or kDecodeDone is
+ // returned by the decoder AND StopDecodeOnDone() returns true. Segments
+ // (i.e. cuts up) the original DecodeBuffer into (potentially) smaller buffers
+ // by calling |select_size| to decide how large each buffer should be.
+ // We do this to test the ability to deal with arbitrary boundaries, as might
+ // happen in transport.
+ // Returns the final DecodeStatus.
+ DecodeStatus DecodeSegments(DecodeBuffer* original,
+ const SelectSize& select_size);
+
+ // Decode buffer |original| until we run out of input, or kDecodeDone is
+ // returned by the decoder AND StopDecodeOnDone() returns true. Segments
+ // (i.e. cuts up) the original DecodeBuffer into (potentially) smaller buffers
+ // by calling |select_size| to decide how large each buffer should be.
+ // We do this to test the ability to deal with arbitrary boundaries, as might
+ // happen in transport.
+ // Invokes |validator| with the final decode status and the original decode
+ // buffer, with the cursor advanced as far as has been consumed by the decoder
+ // and returns validator's result.
+ ::testing::AssertionResult DecodeSegmentsAndValidate(
+ DecodeBuffer* original,
+ const SelectSize& select_size,
+ const Validator& validator) {
+ DecodeStatus status = DecodeSegments(original, select_size);
+ VERIFY_AND_RETURN_SUCCESS(validator(*original, status));
+ }
+
+ // Returns a SelectSize function for fast decoding, i.e. passing all that
+ // is available to the decoder.
+ static SelectSize SelectRemaining() {
+ return [](bool first, size_t offset, size_t remaining) -> size_t {
+ return remaining;
+ };
+ }
+
+ // Returns a SelectSize function for decoding a single byte at a time.
+ static SelectSize SelectOne() {
+ return
+ [](bool first, size_t offset, size_t remaining) -> size_t { return 1; };
+ }
+
+ // Returns a SelectSize function for decoding a single byte at a time, where
+ // zero byte buffers are also allowed. Alternates between zero and one.
+ static SelectSize SelectZeroAndOne(bool return_non_zero_on_first);
+
+ // Returns a SelectSize function for decoding random sized segments.
+ SelectSize SelectRandom(bool return_non_zero_on_first);
+
+ // Decode |original| multiple times, with different segmentations of the
+ // decode buffer, validating after each decode, and confirming that they
+ // each decode the same amount. Returns on the first failure, else returns
+ // success.
+ AssertionResult DecodeAndValidateSeveralWays(DecodeBuffer* original,
+ bool return_non_zero_on_first,
+ const Validator& validator);
+
+ static Validator ToValidator(std::nullptr_t) {
+ return [](const DecodeBuffer& input, DecodeStatus status) {
+ return ::testing::AssertionSuccess();
+ };
+ }
+
+ static Validator ToValidator(const Validator& validator) {
+ if (validator == nullptr) {
+ return ToValidator(nullptr);
+ }
+ return validator;
+ }
+
+ static Validator ToValidator(const NoArgValidator& validator) {
+ if (validator == nullptr) {
+ return ToValidator(nullptr);
+ }
+ return [validator](const DecodeBuffer& input, DecodeStatus status) {
+ return validator();
+ };
+ }
+
+ // Wraps a validator with another validator
+ // that first checks that the DecodeStatus is kDecodeDone and
+ // that the DecodeBuffer is empty.
+ // TODO(jamessynge): Replace this overload with the next, as using this method
+ // usually means that the wrapped function doesn't need to be passed the
+ // DecodeBuffer nor the DecodeStatus.
+ static Validator ValidateDoneAndEmpty(const Validator& wrapped) {
+ return [wrapped](const DecodeBuffer& input,
+ DecodeStatus status) -> AssertionResult {
+ VERIFY_EQ(status, DecodeStatus::kDecodeDone);
+ VERIFY_EQ(0u, input.Remaining()) << "\nOffset=" << input.Offset();
+ if (wrapped) {
+ return wrapped(input, status);
+ }
+ return ::testing::AssertionSuccess();
+ };
+ }
+ static Validator ValidateDoneAndEmpty(NoArgValidator wrapped) {
+ return [wrapped](const DecodeBuffer& input,
+ DecodeStatus status) -> AssertionResult {
+ VERIFY_EQ(status, DecodeStatus::kDecodeDone);
+ VERIFY_EQ(0u, input.Remaining()) << "\nOffset=" << input.Offset();
+ if (wrapped) {
+ return wrapped();
+ }
+ return ::testing::AssertionSuccess();
+ };
+ }
+ static Validator ValidateDoneAndEmpty() {
+ NoArgValidator validator;
+ return ValidateDoneAndEmpty(validator);
+ }
+
+ // Wraps a validator with another validator
+ // that first checks that the DecodeStatus is kDecodeDone and
+ // that the DecodeBuffer has the expected offset.
+ // TODO(jamessynge): Replace this overload with the next, as using this method
+ // usually means that the wrapped function doesn't need to be passed the
+ // DecodeBuffer nor the DecodeStatus.
+ static Validator ValidateDoneAndOffset(uint32_t offset,
+ const Validator& wrapped) {
+ return [wrapped, offset](const DecodeBuffer& input,
+ DecodeStatus status) -> AssertionResult {
+ VERIFY_EQ(status, DecodeStatus::kDecodeDone);
+ VERIFY_EQ(offset, input.Offset()) << "\nRemaining=" << input.Remaining();
+ if (wrapped) {
+ return wrapped(input, status);
+ }
+ return ::testing::AssertionSuccess();
+ };
+ }
+ static Validator ValidateDoneAndOffset(uint32_t offset,
+ NoArgValidator wrapped) {
+ return [wrapped, offset](const DecodeBuffer& input,
+ DecodeStatus status) -> AssertionResult {
+ VERIFY_EQ(status, DecodeStatus::kDecodeDone);
+ VERIFY_EQ(offset, input.Offset()) << "\nRemaining=" << input.Remaining();
+ if (wrapped) {
+ return wrapped();
+ }
+ return ::testing::AssertionSuccess();
+ };
+ }
+ static Validator ValidateDoneAndOffset(uint32_t offset) {
+ NoArgValidator validator;
+ return ValidateDoneAndOffset(offset, validator);
+ }
+
+ // Expose |random_| as Http2Random so callers don't have to care about which
+ // sub-class of Http2Random is used, nor can they rely on the specific
+ // sub-class that RandomDecoderTest uses.
+ Http2Random& Random() { return random_; }
+ Http2Random* RandomPtr() { return &random_; }
+
+ uint32_t RandStreamId();
+
+ bool stop_decode_on_done_ = true;
+
+ private:
+ Http2Random random_;
+};
+
+} // namespace test
+} // namespace http2
+
+#endif // QUICHE_HTTP2_TOOLS_RANDOM_DECODER_TEST_H_
diff --git a/chromium/net/third_party/quiche/src/http2/tools/random_util.cc b/chromium/net/third_party/quiche/src/http2/tools/random_util.cc
new file mode 100644
index 00000000000..82c3edd4063
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/tools/random_util.cc
@@ -0,0 +1,39 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/http2/tools/random_util.h"
+
+#include <cmath>
+
+namespace http2 {
+namespace test {
+
+// Here "word" means something that starts with a lower-case letter, and has
+// zero or more additional characters that are numbers or lower-case letters.
+Http2String GenerateHttp2HeaderName(size_t len, Http2Random* rng) {
+ Http2StringPiece alpha_lc = "abcdefghijklmnopqrstuvwxyz";
+ // If the name is short, just make it one word.
+ if (len < 8) {
+ return rng->RandStringWithAlphabet(len, alpha_lc);
+ }
+ // If the name is longer, ensure it starts with a word, and after that may
+ // have any character in alphanumdash_lc. 4 is arbitrary, could be as low
+ // as 1.
+ Http2StringPiece alphanumdash_lc = "abcdefghijklmnopqrstuvwxyz0123456789-";
+ return rng->RandStringWithAlphabet(4, alpha_lc) +
+ rng->RandStringWithAlphabet(len - 4, alphanumdash_lc);
+}
+
+Http2String GenerateWebSafeString(size_t len, Http2Random* rng) {
+ static const char* kWebsafe64 =
+ "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_";
+ return rng->RandStringWithAlphabet(len, kWebsafe64);
+}
+
+Http2String GenerateWebSafeString(size_t lo, size_t hi, Http2Random* rng) {
+ return GenerateWebSafeString(rng->UniformInRange(lo, hi), rng);
+}
+
+} // namespace test
+} // namespace http2
diff --git a/chromium/net/third_party/quiche/src/http2/tools/random_util.h b/chromium/net/third_party/quiche/src/http2/tools/random_util.h
new file mode 100644
index 00000000000..a2107b7c7c9
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/http2/tools/random_util.h
@@ -0,0 +1,29 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_HTTP2_TOOLS_RANDOM_UTIL_H_
+#define QUICHE_HTTP2_TOOLS_RANDOM_UTIL_H_
+
+#include <stddef.h>
+
+#include "net/third_party/quiche/src/http2/platform/api/http2_string.h"
+#include "net/third_party/quiche/src/http2/test_tools/http2_random.h"
+
+namespace http2 {
+namespace test {
+
+// Generate a string with the allowed character set for HTTP/2 / HPACK header
+// names.
+Http2String GenerateHttp2HeaderName(size_t len, Http2Random* rng);
+
+// Generate a string with the web-safe string character set of specified len.
+Http2String GenerateWebSafeString(size_t len, Http2Random* rng);
+
+// Generate a string with the web-safe string character set of length [lo, hi).
+Http2String GenerateWebSafeString(size_t lo, size_t hi, Http2Random* rng);
+
+} // namespace test
+} // namespace http2
+
+#endif // QUICHE_HTTP2_TOOLS_RANDOM_UTIL_H_
diff --git a/chromium/net/third_party/quiche/src/spdy/core/array_output_buffer.cc b/chromium/net/third_party/quiche/src/spdy/core/array_output_buffer.cc
new file mode 100644
index 00000000000..ea82eb0006f
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/spdy/core/array_output_buffer.cc
@@ -0,0 +1,23 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/spdy/core/array_output_buffer.h"
+
+namespace spdy {
+
+void ArrayOutputBuffer::Next(char** data, int* size) {
+ *data = current_;
+ *size = capacity_ > 0 ? capacity_ : 0;
+}
+
+void ArrayOutputBuffer::AdvanceWritePtr(int64_t count) {
+ current_ += count;
+ capacity_ -= count;
+}
+
+uint64_t ArrayOutputBuffer::BytesFree() const {
+ return capacity_;
+}
+
+} // namespace spdy
diff --git a/chromium/net/third_party/quiche/src/spdy/core/array_output_buffer.h b/chromium/net/third_party/quiche/src/spdy/core/array_output_buffer.h
new file mode 100644
index 00000000000..72303f18aad
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/spdy/core/array_output_buffer.h
@@ -0,0 +1,45 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_SPDY_CORE_ARRAY_OUTPUT_BUFFER_H_
+#define QUICHE_SPDY_CORE_ARRAY_OUTPUT_BUFFER_H_
+
+#include <cstddef>
+#include "net/third_party/quiche/src/spdy/core/zero_copy_output_buffer.h"
+
+namespace spdy {
+
+class ArrayOutputBuffer : public ZeroCopyOutputBuffer {
+ public:
+ // |buffer| is pointed to the output to write to, and |size| is the capacity
+ // of the output.
+ ArrayOutputBuffer(char* buffer, int64_t size)
+ : current_(buffer), begin_(buffer), capacity_(size) {}
+ ~ArrayOutputBuffer() override {}
+
+ ArrayOutputBuffer(const ArrayOutputBuffer&) = delete;
+ ArrayOutputBuffer& operator=(const ArrayOutputBuffer&) = delete;
+
+ void Next(char** data, int* size) override;
+ void AdvanceWritePtr(int64_t count) override;
+ uint64_t BytesFree() const override;
+
+ size_t Size() const { return current_ - begin_; }
+ char* Begin() const { return begin_; }
+
+ // Resets the buffer to its original state.
+ void Reset() {
+ capacity_ += Size();
+ current_ = begin_;
+ }
+
+ private:
+ char* current_ = nullptr;
+ char* begin_ = nullptr;
+ uint64_t capacity_ = 0;
+};
+
+} // namespace spdy
+
+#endif // QUICHE_SPDY_CORE_ARRAY_OUTPUT_BUFFER_H_
diff --git a/chromium/net/third_party/quiche/src/spdy/core/array_output_buffer_test.cc b/chromium/net/third_party/quiche/src/spdy/core/array_output_buffer_test.cc
new file mode 100644
index 00000000000..369778d8537
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/spdy/core/array_output_buffer_test.cc
@@ -0,0 +1,50 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/spdy/core/array_output_buffer.h"
+
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace spdy {
+namespace test {
+
+// This test verifies that ArrayOutputBuffer is initialized properly.
+TEST(ArrayOutputBufferTest, InitializedFromArray) {
+ char array[100];
+ ArrayOutputBuffer buffer(array, sizeof(array));
+ EXPECT_EQ(sizeof(array), buffer.BytesFree());
+ EXPECT_EQ(0u, buffer.Size());
+ EXPECT_EQ(array, buffer.Begin());
+}
+
+// This test verifies that Reset() causes an ArrayOutputBuffer's capacity and
+// size to be reset to the initial state.
+TEST(ArrayOutputBufferTest, WriteAndReset) {
+ char array[100];
+ ArrayOutputBuffer buffer(array, sizeof(array));
+
+ // Let's write some bytes.
+ char* dst;
+ int size;
+ buffer.Next(&dst, &size);
+ ASSERT_GT(size, 1);
+ ASSERT_NE(nullptr, dst);
+ const int64_t written = size / 2;
+ memset(dst, 'x', written);
+ buffer.AdvanceWritePtr(written);
+
+ // The buffer should be partially used.
+ EXPECT_EQ(static_cast<uint64_t>(size) - written, buffer.BytesFree());
+ EXPECT_EQ(static_cast<uint64_t>(written), buffer.Size());
+
+ buffer.Reset();
+
+ // After a reset, the buffer should regain its full capacity.
+ EXPECT_EQ(sizeof(array), buffer.BytesFree());
+ EXPECT_EQ(0u, buffer.Size());
+}
+
+} // namespace test
+} // namespace spdy
diff --git a/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_constants.cc b/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_constants.cc
new file mode 100644
index 00000000000..9c1498a9d2e
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_constants.cc
@@ -0,0 +1,386 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/spdy/core/hpack/hpack_constants.h"
+
+#include <cstddef>
+#include <memory>
+#include <vector>
+
+#include "base/logging.h"
+#include "net/third_party/quiche/src/spdy/core/hpack/hpack_huffman_table.h"
+#include "net/third_party/quiche/src/spdy/core/hpack/hpack_static_table.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_arraysize.h"
+
+namespace spdy {
+
+// Produced by applying the python program [1] with tables provided by [2]
+// (inserted into the source of the python program) and copy-paste them into
+// this file.
+//
+// [1] net/tools/build_hpack_constants.py in Chromium
+// [2] http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-08
+
+// HpackHuffmanSymbol entries are initialized as {code, length, id}.
+// Codes are specified in the |length| most-significant bits of |code|.
+const std::vector<HpackHuffmanSymbol>& HpackHuffmanCodeVector() {
+ static const auto* kHpackHuffmanCode = new std::vector<HpackHuffmanSymbol>{
+ {0xffc00000ul, 13, 0}, // 11111111|11000
+ {0xffffb000ul, 23, 1}, // 11111111|11111111|1011000
+ {0xfffffe20ul, 28, 2}, // 11111111|11111111|11111110|0010
+ {0xfffffe30ul, 28, 3}, // 11111111|11111111|11111110|0011
+ {0xfffffe40ul, 28, 4}, // 11111111|11111111|11111110|0100
+ {0xfffffe50ul, 28, 5}, // 11111111|11111111|11111110|0101
+ {0xfffffe60ul, 28, 6}, // 11111111|11111111|11111110|0110
+ {0xfffffe70ul, 28, 7}, // 11111111|11111111|11111110|0111
+ {0xfffffe80ul, 28, 8}, // 11111111|11111111|11111110|1000
+ {0xffffea00ul, 24, 9}, // 11111111|11111111|11101010
+ {0xfffffff0ul, 30, 10}, // 11111111|11111111|11111111|111100
+ {0xfffffe90ul, 28, 11}, // 11111111|11111111|11111110|1001
+ {0xfffffea0ul, 28, 12}, // 11111111|11111111|11111110|1010
+ {0xfffffff4ul, 30, 13}, // 11111111|11111111|11111111|111101
+ {0xfffffeb0ul, 28, 14}, // 11111111|11111111|11111110|1011
+ {0xfffffec0ul, 28, 15}, // 11111111|11111111|11111110|1100
+ {0xfffffed0ul, 28, 16}, // 11111111|11111111|11111110|1101
+ {0xfffffee0ul, 28, 17}, // 11111111|11111111|11111110|1110
+ {0xfffffef0ul, 28, 18}, // 11111111|11111111|11111110|1111
+ {0xffffff00ul, 28, 19}, // 11111111|11111111|11111111|0000
+ {0xffffff10ul, 28, 20}, // 11111111|11111111|11111111|0001
+ {0xffffff20ul, 28, 21}, // 11111111|11111111|11111111|0010
+ {0xfffffff8ul, 30, 22}, // 11111111|11111111|11111111|111110
+ {0xffffff30ul, 28, 23}, // 11111111|11111111|11111111|0011
+ {0xffffff40ul, 28, 24}, // 11111111|11111111|11111111|0100
+ {0xffffff50ul, 28, 25}, // 11111111|11111111|11111111|0101
+ {0xffffff60ul, 28, 26}, // 11111111|11111111|11111111|0110
+ {0xffffff70ul, 28, 27}, // 11111111|11111111|11111111|0111
+ {0xffffff80ul, 28, 28}, // 11111111|11111111|11111111|1000
+ {0xffffff90ul, 28, 29}, // 11111111|11111111|11111111|1001
+ {0xffffffa0ul, 28, 30}, // 11111111|11111111|11111111|1010
+ {0xffffffb0ul, 28, 31}, // 11111111|11111111|11111111|1011
+ {0x50000000ul, 6, 32}, // ' ' 010100
+ {0xfe000000ul, 10, 33}, // '!' 11111110|00
+ {0xfe400000ul, 10, 34}, // '"' 11111110|01
+ {0xffa00000ul, 12, 35}, // '#' 11111111|1010
+ {0xffc80000ul, 13, 36}, // '$' 11111111|11001
+ {0x54000000ul, 6, 37}, // '%' 010101
+ {0xf8000000ul, 8, 38}, // '&' 11111000
+ {0xff400000ul, 11, 39}, // ''' 11111111|010
+ {0xfe800000ul, 10, 40}, // '(' 11111110|10
+ {0xfec00000ul, 10, 41}, // ')' 11111110|11
+ {0xf9000000ul, 8, 42}, // '*' 11111001
+ {0xff600000ul, 11, 43}, // '+' 11111111|011
+ {0xfa000000ul, 8, 44}, // ',' 11111010
+ {0x58000000ul, 6, 45}, // '-' 010110
+ {0x5c000000ul, 6, 46}, // '.' 010111
+ {0x60000000ul, 6, 47}, // '/' 011000
+ {0x00000000ul, 5, 48}, // '0' 00000
+ {0x08000000ul, 5, 49}, // '1' 00001
+ {0x10000000ul, 5, 50}, // '2' 00010
+ {0x64000000ul, 6, 51}, // '3' 011001
+ {0x68000000ul, 6, 52}, // '4' 011010
+ {0x6c000000ul, 6, 53}, // '5' 011011
+ {0x70000000ul, 6, 54}, // '6' 011100
+ {0x74000000ul, 6, 55}, // '7' 011101
+ {0x78000000ul, 6, 56}, // '8' 011110
+ {0x7c000000ul, 6, 57}, // '9' 011111
+ {0xb8000000ul, 7, 58}, // ':' 1011100
+ {0xfb000000ul, 8, 59}, // ';' 11111011
+ {0xfff80000ul, 15, 60}, // '<' 11111111|1111100
+ {0x80000000ul, 6, 61}, // '=' 100000
+ {0xffb00000ul, 12, 62}, // '>' 11111111|1011
+ {0xff000000ul, 10, 63}, // '?' 11111111|00
+ {0xffd00000ul, 13, 64}, // '@' 11111111|11010
+ {0x84000000ul, 6, 65}, // 'A' 100001
+ {0xba000000ul, 7, 66}, // 'B' 1011101
+ {0xbc000000ul, 7, 67}, // 'C' 1011110
+ {0xbe000000ul, 7, 68}, // 'D' 1011111
+ {0xc0000000ul, 7, 69}, // 'E' 1100000
+ {0xc2000000ul, 7, 70}, // 'F' 1100001
+ {0xc4000000ul, 7, 71}, // 'G' 1100010
+ {0xc6000000ul, 7, 72}, // 'H' 1100011
+ {0xc8000000ul, 7, 73}, // 'I' 1100100
+ {0xca000000ul, 7, 74}, // 'J' 1100101
+ {0xcc000000ul, 7, 75}, // 'K' 1100110
+ {0xce000000ul, 7, 76}, // 'L' 1100111
+ {0xd0000000ul, 7, 77}, // 'M' 1101000
+ {0xd2000000ul, 7, 78}, // 'N' 1101001
+ {0xd4000000ul, 7, 79}, // 'O' 1101010
+ {0xd6000000ul, 7, 80}, // 'P' 1101011
+ {0xd8000000ul, 7, 81}, // 'Q' 1101100
+ {0xda000000ul, 7, 82}, // 'R' 1101101
+ {0xdc000000ul, 7, 83}, // 'S' 1101110
+ {0xde000000ul, 7, 84}, // 'T' 1101111
+ {0xe0000000ul, 7, 85}, // 'U' 1110000
+ {0xe2000000ul, 7, 86}, // 'V' 1110001
+ {0xe4000000ul, 7, 87}, // 'W' 1110010
+ {0xfc000000ul, 8, 88}, // 'X' 11111100
+ {0xe6000000ul, 7, 89}, // 'Y' 1110011
+ {0xfd000000ul, 8, 90}, // 'Z' 11111101
+ {0xffd80000ul, 13, 91}, // '[' 11111111|11011
+ {0xfffe0000ul, 19, 92}, // '\' 11111111|11111110|000
+ {0xffe00000ul, 13, 93}, // ']' 11111111|11100
+ {0xfff00000ul, 14, 94}, // '^' 11111111|111100
+ {0x88000000ul, 6, 95}, // '_' 100010
+ {0xfffa0000ul, 15, 96}, // '`' 11111111|1111101
+ {0x18000000ul, 5, 97}, // 'a' 00011
+ {0x8c000000ul, 6, 98}, // 'b' 100011
+ {0x20000000ul, 5, 99}, // 'c' 00100
+ {0x90000000ul, 6, 100}, // 'd' 100100
+ {0x28000000ul, 5, 101}, // 'e' 00101
+ {0x94000000ul, 6, 102}, // 'f' 100101
+ {0x98000000ul, 6, 103}, // 'g' 100110
+ {0x9c000000ul, 6, 104}, // 'h' 100111
+ {0x30000000ul, 5, 105}, // 'i' 00110
+ {0xe8000000ul, 7, 106}, // 'j' 1110100
+ {0xea000000ul, 7, 107}, // 'k' 1110101
+ {0xa0000000ul, 6, 108}, // 'l' 101000
+ {0xa4000000ul, 6, 109}, // 'm' 101001
+ {0xa8000000ul, 6, 110}, // 'n' 101010
+ {0x38000000ul, 5, 111}, // 'o' 00111
+ {0xac000000ul, 6, 112}, // 'p' 101011
+ {0xec000000ul, 7, 113}, // 'q' 1110110
+ {0xb0000000ul, 6, 114}, // 'r' 101100
+ {0x40000000ul, 5, 115}, // 's' 01000
+ {0x48000000ul, 5, 116}, // 't' 01001
+ {0xb4000000ul, 6, 117}, // 'u' 101101
+ {0xee000000ul, 7, 118}, // 'v' 1110111
+ {0xf0000000ul, 7, 119}, // 'w' 1111000
+ {0xf2000000ul, 7, 120}, // 'x' 1111001
+ {0xf4000000ul, 7, 121}, // 'y' 1111010
+ {0xf6000000ul, 7, 122}, // 'z' 1111011
+ {0xfffc0000ul, 15, 123}, // '{' 11111111|1111110
+ {0xff800000ul, 11, 124}, // '|' 11111111|100
+ {0xfff40000ul, 14, 125}, // '}' 11111111|111101
+ {0xffe80000ul, 13, 126}, // '~' 11111111|11101
+ {0xffffffc0ul, 28, 127}, // 11111111|11111111|11111111|1100
+ {0xfffe6000ul, 20, 128}, // 11111111|11111110|0110
+ {0xffff4800ul, 22, 129}, // 11111111|11111111|010010
+ {0xfffe7000ul, 20, 130}, // 11111111|11111110|0111
+ {0xfffe8000ul, 20, 131}, // 11111111|11111110|1000
+ {0xffff4c00ul, 22, 132}, // 11111111|11111111|010011
+ {0xffff5000ul, 22, 133}, // 11111111|11111111|010100
+ {0xffff5400ul, 22, 134}, // 11111111|11111111|010101
+ {0xffffb200ul, 23, 135}, // 11111111|11111111|1011001
+ {0xffff5800ul, 22, 136}, // 11111111|11111111|010110
+ {0xffffb400ul, 23, 137}, // 11111111|11111111|1011010
+ {0xffffb600ul, 23, 138}, // 11111111|11111111|1011011
+ {0xffffb800ul, 23, 139}, // 11111111|11111111|1011100
+ {0xffffba00ul, 23, 140}, // 11111111|11111111|1011101
+ {0xffffbc00ul, 23, 141}, // 11111111|11111111|1011110
+ {0xffffeb00ul, 24, 142}, // 11111111|11111111|11101011
+ {0xffffbe00ul, 23, 143}, // 11111111|11111111|1011111
+ {0xffffec00ul, 24, 144}, // 11111111|11111111|11101100
+ {0xffffed00ul, 24, 145}, // 11111111|11111111|11101101
+ {0xffff5c00ul, 22, 146}, // 11111111|11111111|010111
+ {0xffffc000ul, 23, 147}, // 11111111|11111111|1100000
+ {0xffffee00ul, 24, 148}, // 11111111|11111111|11101110
+ {0xffffc200ul, 23, 149}, // 11111111|11111111|1100001
+ {0xffffc400ul, 23, 150}, // 11111111|11111111|1100010
+ {0xffffc600ul, 23, 151}, // 11111111|11111111|1100011
+ {0xffffc800ul, 23, 152}, // 11111111|11111111|1100100
+ {0xfffee000ul, 21, 153}, // 11111111|11111110|11100
+ {0xffff6000ul, 22, 154}, // 11111111|11111111|011000
+ {0xffffca00ul, 23, 155}, // 11111111|11111111|1100101
+ {0xffff6400ul, 22, 156}, // 11111111|11111111|011001
+ {0xffffcc00ul, 23, 157}, // 11111111|11111111|1100110
+ {0xffffce00ul, 23, 158}, // 11111111|11111111|1100111
+ {0xffffef00ul, 24, 159}, // 11111111|11111111|11101111
+ {0xffff6800ul, 22, 160}, // 11111111|11111111|011010
+ {0xfffee800ul, 21, 161}, // 11111111|11111110|11101
+ {0xfffe9000ul, 20, 162}, // 11111111|11111110|1001
+ {0xffff6c00ul, 22, 163}, // 11111111|11111111|011011
+ {0xffff7000ul, 22, 164}, // 11111111|11111111|011100
+ {0xffffd000ul, 23, 165}, // 11111111|11111111|1101000
+ {0xffffd200ul, 23, 166}, // 11111111|11111111|1101001
+ {0xfffef000ul, 21, 167}, // 11111111|11111110|11110
+ {0xffffd400ul, 23, 168}, // 11111111|11111111|1101010
+ {0xffff7400ul, 22, 169}, // 11111111|11111111|011101
+ {0xffff7800ul, 22, 170}, // 11111111|11111111|011110
+ {0xfffff000ul, 24, 171}, // 11111111|11111111|11110000
+ {0xfffef800ul, 21, 172}, // 11111111|11111110|11111
+ {0xffff7c00ul, 22, 173}, // 11111111|11111111|011111
+ {0xffffd600ul, 23, 174}, // 11111111|11111111|1101011
+ {0xffffd800ul, 23, 175}, // 11111111|11111111|1101100
+ {0xffff0000ul, 21, 176}, // 11111111|11111111|00000
+ {0xffff0800ul, 21, 177}, // 11111111|11111111|00001
+ {0xffff8000ul, 22, 178}, // 11111111|11111111|100000
+ {0xffff1000ul, 21, 179}, // 11111111|11111111|00010
+ {0xffffda00ul, 23, 180}, // 11111111|11111111|1101101
+ {0xffff8400ul, 22, 181}, // 11111111|11111111|100001
+ {0xffffdc00ul, 23, 182}, // 11111111|11111111|1101110
+ {0xffffde00ul, 23, 183}, // 11111111|11111111|1101111
+ {0xfffea000ul, 20, 184}, // 11111111|11111110|1010
+ {0xffff8800ul, 22, 185}, // 11111111|11111111|100010
+ {0xffff8c00ul, 22, 186}, // 11111111|11111111|100011
+ {0xffff9000ul, 22, 187}, // 11111111|11111111|100100
+ {0xffffe000ul, 23, 188}, // 11111111|11111111|1110000
+ {0xffff9400ul, 22, 189}, // 11111111|11111111|100101
+ {0xffff9800ul, 22, 190}, // 11111111|11111111|100110
+ {0xffffe200ul, 23, 191}, // 11111111|11111111|1110001
+ {0xfffff800ul, 26, 192}, // 11111111|11111111|11111000|00
+ {0xfffff840ul, 26, 193}, // 11111111|11111111|11111000|01
+ {0xfffeb000ul, 20, 194}, // 11111111|11111110|1011
+ {0xfffe2000ul, 19, 195}, // 11111111|11111110|001
+ {0xffff9c00ul, 22, 196}, // 11111111|11111111|100111
+ {0xffffe400ul, 23, 197}, // 11111111|11111111|1110010
+ {0xffffa000ul, 22, 198}, // 11111111|11111111|101000
+ {0xfffff600ul, 25, 199}, // 11111111|11111111|11110110|0
+ {0xfffff880ul, 26, 200}, // 11111111|11111111|11111000|10
+ {0xfffff8c0ul, 26, 201}, // 11111111|11111111|11111000|11
+ {0xfffff900ul, 26, 202}, // 11111111|11111111|11111001|00
+ {0xfffffbc0ul, 27, 203}, // 11111111|11111111|11111011|110
+ {0xfffffbe0ul, 27, 204}, // 11111111|11111111|11111011|111
+ {0xfffff940ul, 26, 205}, // 11111111|11111111|11111001|01
+ {0xfffff100ul, 24, 206}, // 11111111|11111111|11110001
+ {0xfffff680ul, 25, 207}, // 11111111|11111111|11110110|1
+ {0xfffe4000ul, 19, 208}, // 11111111|11111110|010
+ {0xffff1800ul, 21, 209}, // 11111111|11111111|00011
+ {0xfffff980ul, 26, 210}, // 11111111|11111111|11111001|10
+ {0xfffffc00ul, 27, 211}, // 11111111|11111111|11111100|000
+ {0xfffffc20ul, 27, 212}, // 11111111|11111111|11111100|001
+ {0xfffff9c0ul, 26, 213}, // 11111111|11111111|11111001|11
+ {0xfffffc40ul, 27, 214}, // 11111111|11111111|11111100|010
+ {0xfffff200ul, 24, 215}, // 11111111|11111111|11110010
+ {0xffff2000ul, 21, 216}, // 11111111|11111111|00100
+ {0xffff2800ul, 21, 217}, // 11111111|11111111|00101
+ {0xfffffa00ul, 26, 218}, // 11111111|11111111|11111010|00
+ {0xfffffa40ul, 26, 219}, // 11111111|11111111|11111010|01
+ {0xffffffd0ul, 28, 220}, // 11111111|11111111|11111111|1101
+ {0xfffffc60ul, 27, 221}, // 11111111|11111111|11111100|011
+ {0xfffffc80ul, 27, 222}, // 11111111|11111111|11111100|100
+ {0xfffffca0ul, 27, 223}, // 11111111|11111111|11111100|101
+ {0xfffec000ul, 20, 224}, // 11111111|11111110|1100
+ {0xfffff300ul, 24, 225}, // 11111111|11111111|11110011
+ {0xfffed000ul, 20, 226}, // 11111111|11111110|1101
+ {0xffff3000ul, 21, 227}, // 11111111|11111111|00110
+ {0xffffa400ul, 22, 228}, // 11111111|11111111|101001
+ {0xffff3800ul, 21, 229}, // 11111111|11111111|00111
+ {0xffff4000ul, 21, 230}, // 11111111|11111111|01000
+ {0xffffe600ul, 23, 231}, // 11111111|11111111|1110011
+ {0xffffa800ul, 22, 232}, // 11111111|11111111|101010
+ {0xffffac00ul, 22, 233}, // 11111111|11111111|101011
+ {0xfffff700ul, 25, 234}, // 11111111|11111111|11110111|0
+ {0xfffff780ul, 25, 235}, // 11111111|11111111|11110111|1
+ {0xfffff400ul, 24, 236}, // 11111111|11111111|11110100
+ {0xfffff500ul, 24, 237}, // 11111111|11111111|11110101
+ {0xfffffa80ul, 26, 238}, // 11111111|11111111|11111010|10
+ {0xffffe800ul, 23, 239}, // 11111111|11111111|1110100
+ {0xfffffac0ul, 26, 240}, // 11111111|11111111|11111010|11
+ {0xfffffcc0ul, 27, 241}, // 11111111|11111111|11111100|110
+ {0xfffffb00ul, 26, 242}, // 11111111|11111111|11111011|00
+ {0xfffffb40ul, 26, 243}, // 11111111|11111111|11111011|01
+ {0xfffffce0ul, 27, 244}, // 11111111|11111111|11111100|111
+ {0xfffffd00ul, 27, 245}, // 11111111|11111111|11111101|000
+ {0xfffffd20ul, 27, 246}, // 11111111|11111111|11111101|001
+ {0xfffffd40ul, 27, 247}, // 11111111|11111111|11111101|010
+ {0xfffffd60ul, 27, 248}, // 11111111|11111111|11111101|011
+ {0xffffffe0ul, 28, 249}, // 11111111|11111111|11111111|1110
+ {0xfffffd80ul, 27, 250}, // 11111111|11111111|11111101|100
+ {0xfffffda0ul, 27, 251}, // 11111111|11111111|11111101|101
+ {0xfffffdc0ul, 27, 252}, // 11111111|11111111|11111101|110
+ {0xfffffde0ul, 27, 253}, // 11111111|11111111|11111101|111
+ {0xfffffe00ul, 27, 254}, // 11111111|11111111|11111110|000
+ {0xfffffb80ul, 26, 255}, // 11111111|11111111|11111011|10
+ {0xfffffffcul, 30, 256}, // EOS 11111111|11111111|11111111|111111
+ };
+ return *kHpackHuffmanCode;
+}
+
+// The "constructor" for a HpackStaticEntry that computes the lengths at
+// compile time.
+#define STATIC_ENTRY(name, value) \
+ { name, SPDY_ARRAYSIZE(name) - 1, value, SPDY_ARRAYSIZE(value) - 1 }
+
+const std::vector<HpackStaticEntry>& HpackStaticTableVector() {
+ static const auto* kHpackStaticTable = new std::vector<HpackStaticEntry>{
+ STATIC_ENTRY(":authority", ""), // 1
+ STATIC_ENTRY(":method", "GET"), // 2
+ STATIC_ENTRY(":method", "POST"), // 3
+ STATIC_ENTRY(":path", "/"), // 4
+ STATIC_ENTRY(":path", "/index.html"), // 5
+ STATIC_ENTRY(":scheme", "http"), // 6
+ STATIC_ENTRY(":scheme", "https"), // 7
+ STATIC_ENTRY(":status", "200"), // 8
+ STATIC_ENTRY(":status", "204"), // 9
+ STATIC_ENTRY(":status", "206"), // 10
+ STATIC_ENTRY(":status", "304"), // 11
+ STATIC_ENTRY(":status", "400"), // 12
+ STATIC_ENTRY(":status", "404"), // 13
+ STATIC_ENTRY(":status", "500"), // 14
+ STATIC_ENTRY("accept-charset", ""), // 15
+ STATIC_ENTRY("accept-encoding", "gzip, deflate"), // 16
+ STATIC_ENTRY("accept-language", ""), // 17
+ STATIC_ENTRY("accept-ranges", ""), // 18
+ STATIC_ENTRY("accept", ""), // 19
+ STATIC_ENTRY("access-control-allow-origin", ""), // 20
+ STATIC_ENTRY("age", ""), // 21
+ STATIC_ENTRY("allow", ""), // 22
+ STATIC_ENTRY("authorization", ""), // 23
+ STATIC_ENTRY("cache-control", ""), // 24
+ STATIC_ENTRY("content-disposition", ""), // 25
+ STATIC_ENTRY("content-encoding", ""), // 26
+ STATIC_ENTRY("content-language", ""), // 27
+ STATIC_ENTRY("content-length", ""), // 28
+ STATIC_ENTRY("content-location", ""), // 29
+ STATIC_ENTRY("content-range", ""), // 30
+ STATIC_ENTRY("content-type", ""), // 31
+ STATIC_ENTRY("cookie", ""), // 32
+ STATIC_ENTRY("date", ""), // 33
+ STATIC_ENTRY("etag", ""), // 34
+ STATIC_ENTRY("expect", ""), // 35
+ STATIC_ENTRY("expires", ""), // 36
+ STATIC_ENTRY("from", ""), // 37
+ STATIC_ENTRY("host", ""), // 38
+ STATIC_ENTRY("if-match", ""), // 39
+ STATIC_ENTRY("if-modified-since", ""), // 40
+ STATIC_ENTRY("if-none-match", ""), // 41
+ STATIC_ENTRY("if-range", ""), // 42
+ STATIC_ENTRY("if-unmodified-since", ""), // 43
+ STATIC_ENTRY("last-modified", ""), // 44
+ STATIC_ENTRY("link", ""), // 45
+ STATIC_ENTRY("location", ""), // 46
+ STATIC_ENTRY("max-forwards", ""), // 47
+ STATIC_ENTRY("proxy-authenticate", ""), // 48
+ STATIC_ENTRY("proxy-authorization", ""), // 49
+ STATIC_ENTRY("range", ""), // 50
+ STATIC_ENTRY("referer", ""), // 51
+ STATIC_ENTRY("refresh", ""), // 52
+ STATIC_ENTRY("retry-after", ""), // 53
+ STATIC_ENTRY("server", ""), // 54
+ STATIC_ENTRY("set-cookie", ""), // 55
+ STATIC_ENTRY("strict-transport-security", ""), // 56
+ STATIC_ENTRY("transfer-encoding", ""), // 57
+ STATIC_ENTRY("user-agent", ""), // 58
+ STATIC_ENTRY("vary", ""), // 59
+ STATIC_ENTRY("via", ""), // 60
+ STATIC_ENTRY("www-authenticate", ""), // 61
+ };
+ return *kHpackStaticTable;
+}
+
+#undef STATIC_ENTRY
+
+const HpackHuffmanTable& ObtainHpackHuffmanTable() {
+ static const HpackHuffmanTable* const shared_huffman_table = []() {
+ auto* table = new HpackHuffmanTable();
+ CHECK(table->Initialize(HpackHuffmanCodeVector().data(),
+ HpackHuffmanCodeVector().size()));
+ CHECK(table->IsInitialized());
+ return table;
+ }();
+ return *shared_huffman_table;
+}
+
+const HpackStaticTable& ObtainHpackStaticTable() {
+ static const HpackStaticTable* const shared_static_table = []() {
+ auto* table = new HpackStaticTable();
+ table->Initialize(HpackStaticTableVector().data(),
+ HpackStaticTableVector().size());
+ CHECK(table->IsInitialized());
+ return table;
+ }();
+ return *shared_static_table;
+}
+
+} // namespace spdy
diff --git a/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_constants.h b/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_constants.h
new file mode 100644
index 00000000000..3f4026bebcf
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_constants.h
@@ -0,0 +1,95 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_SPDY_CORE_HPACK_HPACK_CONSTANTS_H_
+#define QUICHE_SPDY_CORE_HPACK_HPACK_CONSTANTS_H_
+
+#include <cstddef>
+#include <cstdint>
+#include <vector>
+
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_export.h"
+
+// All section references below are to
+// http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-08
+
+namespace spdy {
+
+// An HpackPrefix signifies |bits| stored in the top |bit_size| bits
+// of an octet.
+struct HpackPrefix {
+ uint8_t bits;
+ size_t bit_size;
+};
+
+// Represents a symbol and its Huffman code (stored in most-significant bits).
+struct HpackHuffmanSymbol {
+ uint32_t code;
+ uint8_t length;
+ uint16_t id;
+};
+
+// An entry in the static table. Must be a POD in order to avoid static
+// initializers, i.e. no user-defined constructors or destructors.
+struct HpackStaticEntry {
+ const char* const name;
+ const size_t name_len;
+ const char* const value;
+ const size_t value_len;
+};
+
+class HpackHuffmanTable;
+class HpackStaticTable;
+
+// Defined in RFC 7540, 6.5.2.
+const uint32_t kDefaultHeaderTableSizeSetting = 4096;
+
+// RFC 7541, 5.2: Flag for a string literal that is stored unmodified (i.e.,
+// without Huffman encoding).
+const HpackPrefix kStringLiteralIdentityEncoded = {0x0, 1};
+
+// RFC 7541, 5.2: Flag for a Huffman-coded string literal.
+const HpackPrefix kStringLiteralHuffmanEncoded = {0x1, 1};
+
+// RFC 7541, 6.1: Opcode for an indexed header field.
+const HpackPrefix kIndexedOpcode = {0b1, 1};
+
+// RFC 7541, 6.2.1: Opcode for a literal header field with incremental indexing.
+const HpackPrefix kLiteralIncrementalIndexOpcode = {0b01, 2};
+
+// RFC 7541, 6.2.2: Opcode for a literal header field without indexing.
+const HpackPrefix kLiteralNoIndexOpcode = {0b0000, 4};
+
+// RFC 7541, 6.2.3: Opcode for a literal header field which is never indexed.
+// Currently unused.
+// const HpackPrefix kLiteralNeverIndexOpcode = {0b0001, 4};
+
+// RFC 7541, 6.3: Opcode for maximum header table size update. Begins a
+// varint-encoded table size with a 5-bit prefix.
+const HpackPrefix kHeaderTableSizeUpdateOpcode = {0b001, 3};
+
+// Symbol code table from RFC 7541, "Appendix C. Huffman Code".
+SPDY_EXPORT_PRIVATE const std::vector<HpackHuffmanSymbol>&
+HpackHuffmanCodeVector();
+
+// Static table from RFC 7541, "Appendix B. Static Table Definition".
+SPDY_EXPORT_PRIVATE const std::vector<HpackStaticEntry>&
+HpackStaticTableVector();
+
+// Returns a HpackHuffmanTable instance initialized with |kHpackHuffmanCode|.
+// The instance is read-only, has static lifetime, and is safe to share amoung
+// threads. This function is thread-safe.
+SPDY_EXPORT_PRIVATE const HpackHuffmanTable& ObtainHpackHuffmanTable();
+
+// Returns a HpackStaticTable instance initialized with |kHpackStaticTable|.
+// The instance is read-only, has static lifetime, and is safe to share amoung
+// threads. This function is thread-safe.
+SPDY_EXPORT_PRIVATE const HpackStaticTable& ObtainHpackStaticTable();
+
+// Pseudo-headers start with a colon. (HTTP2 8.1.2.1., HPACK 3.1.)
+const char kPseudoHeaderPrefix = ':';
+
+} // namespace spdy
+
+#endif // QUICHE_SPDY_CORE_HPACK_HPACK_CONSTANTS_H_
diff --git a/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_decoder_adapter.cc b/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_decoder_adapter.cc
new file mode 100644
index 00000000000..309af39a06b
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_decoder_adapter.cc
@@ -0,0 +1,201 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/spdy/core/hpack/hpack_decoder_adapter.h"
+
+#include "base/logging.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_status.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_estimate_memory_usage.h"
+
+using ::http2::DecodeBuffer;
+using ::http2::HpackEntryType;
+using ::http2::HpackString;
+
+namespace spdy {
+namespace {
+const size_t kMaxDecodeBufferSizeBytes = 32 * 1024; // 32 KB
+} // namespace
+
+HpackDecoderAdapter::HpackDecoderAdapter()
+ : hpack_decoder_(&listener_adapter_, kMaxDecodeBufferSizeBytes),
+ max_decode_buffer_size_bytes_(kMaxDecodeBufferSizeBytes),
+ header_block_started_(false) {}
+
+HpackDecoderAdapter::~HpackDecoderAdapter() = default;
+
+void HpackDecoderAdapter::ApplyHeaderTableSizeSetting(size_t size_setting) {
+ DVLOG(2) << "HpackDecoderAdapter::ApplyHeaderTableSizeSetting";
+ hpack_decoder_.ApplyHeaderTableSizeSetting(size_setting);
+}
+
+void HpackDecoderAdapter::HandleControlFrameHeadersStart(
+ SpdyHeadersHandlerInterface* handler) {
+ DVLOG(2) << "HpackDecoderAdapter::HandleControlFrameHeadersStart";
+ DCHECK(!header_block_started_);
+ listener_adapter_.set_handler(handler);
+}
+
+bool HpackDecoderAdapter::HandleControlFrameHeadersData(
+ const char* headers_data,
+ size_t headers_data_length) {
+ DVLOG(2) << "HpackDecoderAdapter::HandleControlFrameHeadersData: len="
+ << headers_data_length;
+ if (!header_block_started_) {
+ // Initialize the decoding process here rather than in
+ // HandleControlFrameHeadersStart because that method is not always called.
+ header_block_started_ = true;
+ if (!hpack_decoder_.StartDecodingBlock()) {
+ header_block_started_ = false;
+ return false;
+ }
+ }
+
+ // Sometimes we get a call with headers_data==nullptr and
+ // headers_data_length==0, in which case we need to avoid creating
+ // a DecodeBuffer, which would otherwise complain.
+ if (headers_data_length > 0) {
+ DCHECK_NE(headers_data, nullptr);
+ if (headers_data_length > max_decode_buffer_size_bytes_) {
+ DVLOG(1) << "max_decode_buffer_size_bytes_ < headers_data_length: "
+ << max_decode_buffer_size_bytes_ << " < " << headers_data_length;
+ return false;
+ }
+ listener_adapter_.AddToTotalHpackBytes(headers_data_length);
+ http2::DecodeBuffer db(headers_data, headers_data_length);
+ bool ok = hpack_decoder_.DecodeFragment(&db);
+ DCHECK(!ok || db.Empty()) << "Remaining=" << db.Remaining();
+ return ok;
+ }
+ return true;
+}
+
+bool HpackDecoderAdapter::HandleControlFrameHeadersComplete(
+ size_t* compressed_len) {
+ DVLOG(2) << "HpackDecoderAdapter::HandleControlFrameHeadersComplete";
+ if (compressed_len != nullptr) {
+ *compressed_len = listener_adapter_.total_hpack_bytes();
+ }
+ if (!hpack_decoder_.EndDecodingBlock()) {
+ DVLOG(3) << "EndDecodingBlock returned false";
+ return false;
+ }
+ header_block_started_ = false;
+ return true;
+}
+
+const SpdyHeaderBlock& HpackDecoderAdapter::decoded_block() const {
+ return listener_adapter_.decoded_block();
+}
+
+void HpackDecoderAdapter::SetHeaderTableDebugVisitor(
+ std::unique_ptr<HpackHeaderTable::DebugVisitorInterface> visitor) {
+ DVLOG(2) << "HpackDecoderAdapter::SetHeaderTableDebugVisitor";
+ if (visitor != nullptr) {
+ listener_adapter_.SetHeaderTableDebugVisitor(std::move(visitor));
+ hpack_decoder_.set_tables_debug_listener(&listener_adapter_);
+ } else {
+ hpack_decoder_.set_tables_debug_listener(nullptr);
+ listener_adapter_.SetHeaderTableDebugVisitor(nullptr);
+ }
+}
+
+void HpackDecoderAdapter::set_max_decode_buffer_size_bytes(
+ size_t max_decode_buffer_size_bytes) {
+ DVLOG(2) << "HpackDecoderAdapter::set_max_decode_buffer_size_bytes";
+ max_decode_buffer_size_bytes_ = max_decode_buffer_size_bytes;
+ hpack_decoder_.set_max_string_size_bytes(max_decode_buffer_size_bytes);
+}
+
+size_t HpackDecoderAdapter::EstimateMemoryUsage() const {
+ return SpdyEstimateMemoryUsage(hpack_decoder_);
+}
+
+HpackDecoderAdapter::ListenerAdapter::ListenerAdapter() : handler_(nullptr) {}
+HpackDecoderAdapter::ListenerAdapter::~ListenerAdapter() = default;
+
+void HpackDecoderAdapter::ListenerAdapter::set_handler(
+ SpdyHeadersHandlerInterface* handler) {
+ handler_ = handler;
+}
+
+void HpackDecoderAdapter::ListenerAdapter::SetHeaderTableDebugVisitor(
+ std::unique_ptr<HpackHeaderTable::DebugVisitorInterface> visitor) {
+ visitor_ = std::move(visitor);
+}
+
+void HpackDecoderAdapter::ListenerAdapter::OnHeaderListStart() {
+ DVLOG(2) << "HpackDecoderAdapter::ListenerAdapter::OnHeaderListStart";
+ total_hpack_bytes_ = 0;
+ total_uncompressed_bytes_ = 0;
+ decoded_block_.clear();
+ if (handler_ != nullptr) {
+ handler_->OnHeaderBlockStart();
+ }
+}
+
+void HpackDecoderAdapter::ListenerAdapter::OnHeader(HpackEntryType entry_type,
+ const HpackString& name,
+ const HpackString& value) {
+ DVLOG(2) << "HpackDecoderAdapter::ListenerAdapter::OnHeader:\n name: " << name
+ << "\n value: " << value;
+ total_uncompressed_bytes_ += name.size() + value.size();
+ if (handler_ == nullptr) {
+ DVLOG(3) << "Adding to decoded_block";
+ decoded_block_.AppendValueOrAddHeader(name.ToStringPiece(),
+ value.ToStringPiece());
+ } else {
+ DVLOG(3) << "Passing to handler";
+ handler_->OnHeader(name.ToStringPiece(), value.ToStringPiece());
+ }
+}
+
+void HpackDecoderAdapter::ListenerAdapter::OnHeaderListEnd() {
+ DVLOG(2) << "HpackDecoderAdapter::ListenerAdapter::OnHeaderListEnd";
+ // We don't clear the SpdyHeaderBlock here to allow access to it until the
+ // next HPACK block is decoded.
+ if (handler_ != nullptr) {
+ handler_->OnHeaderBlockEnd(total_uncompressed_bytes_, total_hpack_bytes_);
+ handler_ = nullptr;
+ }
+}
+
+void HpackDecoderAdapter::ListenerAdapter::OnHeaderErrorDetected(
+ SpdyStringPiece error_message) {
+ VLOG(1) << error_message;
+}
+
+int64_t HpackDecoderAdapter::ListenerAdapter::OnEntryInserted(
+ const http2::HpackStringPair& sp,
+ size_t insert_count) {
+ DVLOG(2) << "HpackDecoderAdapter::ListenerAdapter::OnEntryInserted: " << sp
+ << ", insert_count=" << insert_count;
+ if (visitor_ == nullptr) {
+ return 0;
+ }
+ HpackEntry entry(sp.name.ToStringPiece(), sp.value.ToStringPiece(),
+ /*is_static*/ false, insert_count);
+ int64_t time_added = visitor_->OnNewEntry(entry);
+ DVLOG(2)
+ << "HpackDecoderAdapter::ListenerAdapter::OnEntryInserted: time_added="
+ << time_added;
+ return time_added;
+}
+
+void HpackDecoderAdapter::ListenerAdapter::OnUseEntry(
+ const http2::HpackStringPair& sp,
+ size_t insert_count,
+ int64_t time_added) {
+ DVLOG(2) << "HpackDecoderAdapter::ListenerAdapter::OnUseEntry: " << sp
+ << ", insert_count=" << insert_count
+ << ", time_added=" << time_added;
+ if (visitor_ != nullptr) {
+ HpackEntry entry(sp.name.ToStringPiece(), sp.value.ToStringPiece(),
+ /*is_static*/ false, insert_count);
+ entry.set_time_added(time_added);
+ visitor_->OnUseEntry(entry);
+ }
+}
+
+} // namespace spdy
diff --git a/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_decoder_adapter.h b/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_decoder_adapter.h
new file mode 100644
index 00000000000..b497fe7a47c
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_decoder_adapter.h
@@ -0,0 +1,159 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_SPDY_CORE_HPACK_HPACK_DECODER_ADAPTER_H_
+#define QUICHE_SPDY_CORE_HPACK_HPACK_DECODER_ADAPTER_H_
+
+// HpackDecoderAdapter uses http2::HpackDecoder to decode HPACK blocks into
+// HTTP/2 header lists as outlined in http://tools.ietf.org/html/rfc7541.
+
+#include <stddef.h>
+
+#include <cstdint>
+#include <memory>
+
+#include "base/macros.h"
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder.h"
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_listener.h"
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_tables.h"
+#include "net/third_party/quiche/src/http2/hpack/hpack_string.h"
+#include "net/third_party/quiche/src/http2/hpack/http2_hpack_constants.h"
+#include "net/third_party/quiche/src/spdy/core/hpack/hpack_header_table.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_header_block.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_headers_handler_interface.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_export.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_string_piece.h"
+
+namespace spdy {
+namespace test {
+class HpackDecoderAdapterPeer;
+} // namespace test
+
+class SPDY_EXPORT_PRIVATE HpackDecoderAdapter {
+ public:
+ friend test::HpackDecoderAdapterPeer;
+ HpackDecoderAdapter();
+ HpackDecoderAdapter(const HpackDecoderAdapter&) = delete;
+ HpackDecoderAdapter& operator=(const HpackDecoderAdapter&) = delete;
+ ~HpackDecoderAdapter();
+
+ // Called upon acknowledgement of SETTINGS_HEADER_TABLE_SIZE.
+ void ApplyHeaderTableSizeSetting(size_t size_setting);
+
+ // If a SpdyHeadersHandlerInterface is provided, the decoder will emit
+ // headers to it rather than accumulating them in a SpdyHeaderBlock.
+ // Does not take ownership of the handler, but does use the pointer until
+ // the current HPACK block is completely decoded.
+ void HandleControlFrameHeadersStart(SpdyHeadersHandlerInterface* handler);
+
+ // Called as HPACK block fragments arrive. Returns false if an error occurred
+ // while decoding the block. Does not take ownership of headers_data.
+ bool HandleControlFrameHeadersData(const char* headers_data,
+ size_t headers_data_length);
+
+ // Called after a HPACK block has been completely delivered via
+ // HandleControlFrameHeadersData(). Returns false if an error occurred.
+ // |compressed_len| if non-null will be set to the size of the encoded
+ // buffered block that was accumulated in HandleControlFrameHeadersData(),
+ // to support subsequent calculation of compression percentage.
+ // Discards the handler supplied at the start of decoding the block.
+ // TODO(jamessynge): Determine if compressed_len is needed; it is used to
+ // produce UUMA stat Net.SpdyHpackDecompressionPercentage, but only for
+ // deprecated SPDY3.
+ bool HandleControlFrameHeadersComplete(size_t* compressed_len);
+
+ // Accessor for the most recently decoded headers block. Valid until the next
+ // call to HandleControlFrameHeadersData().
+ // TODO(birenroy): Remove this method when all users of HpackDecoder specify
+ // a SpdyHeadersHandlerInterface.
+ const SpdyHeaderBlock& decoded_block() const;
+
+ void SetHeaderTableDebugVisitor(
+ std::unique_ptr<HpackHeaderTable::DebugVisitorInterface> visitor);
+
+ // Set how much encoded data this decoder is willing to buffer.
+ // TODO(jamessynge): Resolve definition of this value, as it is currently
+ // too tied to a single implementation. We probably want to limit one or more
+ // of these: individual name or value strings, header entries, the entire
+ // header list, or the HPACK block; we probably shouldn't care about the size
+ // of individual transport buffers.
+ void set_max_decode_buffer_size_bytes(size_t max_decode_buffer_size_bytes);
+
+ size_t EstimateMemoryUsage() const;
+
+ private:
+ class SPDY_EXPORT_PRIVATE ListenerAdapter
+ : public http2::HpackDecoderListener,
+ public http2::HpackDecoderTablesDebugListener {
+ public:
+ ListenerAdapter();
+ ~ListenerAdapter() override;
+
+ // If a SpdyHeadersHandlerInterface is provided, the decoder will emit
+ // headers to it rather than accumulating them in a SpdyHeaderBlock.
+ // Does not take ownership of the handler, but does use the pointer until
+ // the current HPACK block is completely decoded.
+ void set_handler(SpdyHeadersHandlerInterface* handler);
+ const SpdyHeaderBlock& decoded_block() const { return decoded_block_; }
+
+ void SetHeaderTableDebugVisitor(
+ std::unique_ptr<HpackHeaderTable::DebugVisitorInterface> visitor);
+
+ // Override the HpackDecoderListener methods:
+ void OnHeaderListStart() override;
+ void OnHeader(http2::HpackEntryType entry_type,
+ const http2::HpackString& name,
+ const http2::HpackString& value) override;
+ void OnHeaderListEnd() override;
+ void OnHeaderErrorDetected(SpdyStringPiece error_message) override;
+
+ // Override the HpackDecoderTablesDebugListener methods:
+ int64_t OnEntryInserted(const http2::HpackStringPair& entry,
+ size_t insert_count) override;
+ void OnUseEntry(const http2::HpackStringPair& entry,
+ size_t insert_count,
+ int64_t insert_time) override;
+
+ void AddToTotalHpackBytes(size_t delta) { total_hpack_bytes_ += delta; }
+ size_t total_hpack_bytes() const { return total_hpack_bytes_; }
+
+ private:
+ // If the caller doesn't provide a handler, the header list is stored in
+ // this SpdyHeaderBlock.
+ SpdyHeaderBlock decoded_block_;
+
+ // If non-NULL, handles decoded headers. Not owned.
+ SpdyHeadersHandlerInterface* handler_;
+
+ // Total bytes that have been received as input (i.e. HPACK encoded)
+ // in the current HPACK block.
+ size_t total_hpack_bytes_;
+
+ // Total bytes of the name and value strings in the current HPACK block.
+ size_t total_uncompressed_bytes_;
+
+ // visitor_ is used by a QUIC experiment regarding HPACK; remove
+ // when the experiment is done.
+ std::unique_ptr<HpackHeaderTable::DebugVisitorInterface> visitor_;
+ };
+
+ // Converts calls to HpackDecoderListener into calls to
+ // SpdyHeadersHandlerInterface.
+ ListenerAdapter listener_adapter_;
+
+ // The actual decoder.
+ http2::HpackDecoder hpack_decoder_;
+
+ // How much encoded data this decoder is willing to buffer.
+ size_t max_decode_buffer_size_bytes_;
+
+ // Flag to keep track of having seen the header block start. Needed at the
+ // moment because HandleControlFrameHeadersStart won't be called if a handler
+ // is not being provided by the caller.
+ bool header_block_started_;
+};
+
+} // namespace spdy
+
+#endif // QUICHE_SPDY_CORE_HPACK_HPACK_DECODER_ADAPTER_H_
diff --git a/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_decoder_adapter_test.cc b/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_decoder_adapter_test.cc
new file mode 100644
index 00000000000..765c2eac7ac
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_decoder_adapter_test.cc
@@ -0,0 +1,1097 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/spdy/core/hpack/hpack_decoder_adapter.h"
+
+// Tests of HpackDecoderAdapter.
+
+#include <stdint.h>
+
+#include <tuple>
+#include <utility>
+#include <vector>
+
+#include "base/logging.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_state.h"
+#include "net/third_party/quiche/src/http2/hpack/decoder/hpack_decoder_tables.h"
+#include "net/third_party/quiche/src/http2/hpack/tools/hpack_block_builder.h"
+#include "net/third_party/quiche/src/http2/test_tools/http2_random.h"
+#include "net/third_party/quiche/src/spdy/core/hpack/hpack_constants.h"
+#include "net/third_party/quiche/src/spdy/core/hpack/hpack_encoder.h"
+#include "net/third_party/quiche/src/spdy/core/hpack/hpack_output_stream.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_test_utils.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_arraysize.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_string.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_string_utils.h"
+
+using ::http2::HpackEntryType;
+using ::http2::HpackString;
+using ::http2::HpackStringPair;
+using ::http2::test::HpackBlockBuilder;
+using ::http2::test::HpackDecoderPeer;
+using ::testing::ElementsAre;
+using ::testing::Pair;
+
+namespace http2 {
+namespace test {
+
+class HpackDecoderStatePeer {
+ public:
+ static HpackDecoderTables* GetDecoderTables(HpackDecoderState* state) {
+ return &state->decoder_tables_;
+ }
+};
+
+class HpackDecoderPeer {
+ public:
+ static HpackDecoderState* GetDecoderState(HpackDecoder* decoder) {
+ return &decoder->decoder_state_;
+ }
+ static HpackDecoderTables* GetDecoderTables(HpackDecoder* decoder) {
+ return HpackDecoderStatePeer::GetDecoderTables(GetDecoderState(decoder));
+ }
+};
+
+} // namespace test
+} // namespace http2
+
+namespace spdy {
+namespace test {
+
+class HpackDecoderAdapterPeer {
+ public:
+ explicit HpackDecoderAdapterPeer(HpackDecoderAdapter* decoder)
+ : decoder_(decoder) {}
+
+ void HandleHeaderRepresentation(SpdyStringPiece name, SpdyStringPiece value) {
+ decoder_->listener_adapter_.OnHeader(HpackEntryType::kIndexedLiteralHeader,
+ HpackString(name), HpackString(value));
+ }
+
+ http2::HpackDecoderTables* GetDecoderTables() {
+ return HpackDecoderPeer::GetDecoderTables(&decoder_->hpack_decoder_);
+ }
+
+ const HpackStringPair* GetTableEntry(uint32_t index) {
+ return GetDecoderTables()->Lookup(index);
+ }
+
+ size_t current_header_table_size() {
+ return GetDecoderTables()->current_header_table_size();
+ }
+
+ size_t header_table_size_limit() {
+ return GetDecoderTables()->header_table_size_limit();
+ }
+
+ void set_header_table_size_limit(size_t size) {
+ return GetDecoderTables()->DynamicTableSizeUpdate(size);
+ }
+
+ private:
+ HpackDecoderAdapter* decoder_;
+};
+
+class HpackEncoderPeer {
+ public:
+ static void CookieToCrumbs(const HpackEncoder::Representation& cookie,
+ HpackEncoder::Representations* crumbs_out) {
+ HpackEncoder::CookieToCrumbs(cookie, crumbs_out);
+ }
+};
+
+namespace {
+
+const bool kNoCheckDecodedSize = false;
+const char* kCookieKey = "cookie";
+
+// Is HandleControlFrameHeadersStart to be called, and with what value?
+enum StartChoice { START_WITH_HANDLER, START_WITHOUT_HANDLER, NO_START };
+
+class HpackDecoderAdapterTest
+ : public ::testing::TestWithParam<std::tuple<StartChoice, bool>> {
+ protected:
+ HpackDecoderAdapterTest() : decoder_(), decoder_peer_(&decoder_) {}
+
+ void SetUp() override {
+ std::tie(start_choice_, randomly_split_input_buffer_) = GetParam();
+ }
+
+ void HandleControlFrameHeadersStart() {
+ bytes_passed_in_ = 0;
+ switch (start_choice_) {
+ case START_WITH_HANDLER:
+ decoder_.HandleControlFrameHeadersStart(&handler_);
+ break;
+ case START_WITHOUT_HANDLER:
+ decoder_.HandleControlFrameHeadersStart(nullptr);
+ break;
+ case NO_START:
+ break;
+ }
+ }
+
+ bool HandleControlFrameHeadersData(SpdyStringPiece str) {
+ VLOG(3) << "HandleControlFrameHeadersData:\n" << SpdyHexDump(str);
+ bytes_passed_in_ += str.size();
+ return decoder_.HandleControlFrameHeadersData(str.data(), str.size());
+ }
+
+ bool HandleControlFrameHeadersComplete(size_t* size) {
+ bool rc = decoder_.HandleControlFrameHeadersComplete(size);
+ if (size != nullptr) {
+ EXPECT_EQ(*size, bytes_passed_in_);
+ }
+ return rc;
+ }
+
+ bool DecodeHeaderBlock(SpdyStringPiece str, bool check_decoded_size = true) {
+ // Don't call this again if HandleControlFrameHeadersData failed previously.
+ EXPECT_FALSE(decode_has_failed_);
+ HandleControlFrameHeadersStart();
+ if (randomly_split_input_buffer_) {
+ do {
+ // Decode some fragment of the remaining bytes.
+ size_t bytes = str.size();
+ if (!str.empty()) {
+ bytes = random_.Uniform(str.size()) + 1;
+ }
+ EXPECT_LE(bytes, str.size());
+ if (!HandleControlFrameHeadersData(str.substr(0, bytes))) {
+ decode_has_failed_ = true;
+ return false;
+ }
+ str.remove_prefix(bytes);
+ } while (!str.empty());
+ } else if (!HandleControlFrameHeadersData(str)) {
+ decode_has_failed_ = true;
+ return false;
+ }
+ // Want to get out the number of compressed bytes that were decoded,
+ // so pass in a pointer if no handler.
+ size_t total_hpack_bytes = 0;
+ if (start_choice_ == START_WITH_HANDLER) {
+ if (!HandleControlFrameHeadersComplete(nullptr)) {
+ decode_has_failed_ = true;
+ return false;
+ }
+ total_hpack_bytes = handler_.compressed_header_bytes_parsed();
+ } else {
+ if (!HandleControlFrameHeadersComplete(&total_hpack_bytes)) {
+ decode_has_failed_ = true;
+ return false;
+ }
+ }
+ EXPECT_EQ(total_hpack_bytes, bytes_passed_in_);
+ if (check_decoded_size && start_choice_ == START_WITH_HANDLER) {
+ EXPECT_EQ(handler_.header_bytes_parsed(), SizeOfHeaders(decoded_block()));
+ }
+ return true;
+ }
+
+ bool EncodeAndDecodeDynamicTableSizeUpdates(size_t first, size_t second) {
+ HpackBlockBuilder hbb;
+ hbb.AppendDynamicTableSizeUpdate(first);
+ if (second != first) {
+ hbb.AppendDynamicTableSizeUpdate(second);
+ }
+ return DecodeHeaderBlock(hbb.buffer());
+ }
+
+ const SpdyHeaderBlock& decoded_block() const {
+ if (start_choice_ == START_WITH_HANDLER) {
+ return handler_.decoded_block();
+ } else {
+ return decoder_.decoded_block();
+ }
+ }
+
+ static size_t SizeOfHeaders(const SpdyHeaderBlock& headers) {
+ size_t size = 0;
+ for (const auto& kv : headers) {
+ if (kv.first == kCookieKey) {
+ HpackEncoder::Representations crumbs;
+ HpackEncoderPeer::CookieToCrumbs(kv, &crumbs);
+ for (const auto& crumb : crumbs) {
+ size += crumb.first.size() + crumb.second.size();
+ }
+ } else {
+ size += kv.first.size() + kv.second.size();
+ }
+ }
+ return size;
+ }
+
+ const SpdyHeaderBlock& DecodeBlockExpectingSuccess(SpdyStringPiece str) {
+ EXPECT_TRUE(DecodeHeaderBlock(str));
+ return decoded_block();
+ }
+
+ void expectEntry(size_t index,
+ size_t size,
+ const SpdyString& name,
+ const SpdyString& value) {
+ const HpackStringPair* entry = decoder_peer_.GetTableEntry(index);
+ EXPECT_EQ(name, entry->name) << "index " << index;
+ EXPECT_EQ(value, entry->value);
+ EXPECT_EQ(size, entry->size());
+ }
+
+ SpdyHeaderBlock MakeHeaderBlock(
+ const std::vector<std::pair<SpdyString, SpdyString>>& headers) {
+ SpdyHeaderBlock result;
+ for (const auto& kv : headers) {
+ result.AppendValueOrAddHeader(kv.first, kv.second);
+ }
+ return result;
+ }
+
+ http2::test::Http2Random random_;
+ HpackDecoderAdapter decoder_;
+ test::HpackDecoderAdapterPeer decoder_peer_;
+ TestHeadersHandler handler_;
+ StartChoice start_choice_;
+ bool randomly_split_input_buffer_;
+ bool decode_has_failed_ = false;
+ size_t bytes_passed_in_;
+};
+
+INSTANTIATE_TEST_CASE_P(
+ NoHandler,
+ HpackDecoderAdapterTest,
+ ::testing::Combine(::testing::Values(START_WITHOUT_HANDLER, NO_START),
+ ::testing::Bool()));
+
+INSTANTIATE_TEST_CASE_P(
+ WithHandler,
+ HpackDecoderAdapterTest,
+ ::testing::Combine(::testing::Values(START_WITH_HANDLER),
+ ::testing::Bool()));
+
+TEST_P(HpackDecoderAdapterTest,
+ AddHeaderDataWithHandleControlFrameHeadersData) {
+ // The hpack decode buffer size is limited in size. This test verifies that
+ // adding encoded data under that limit is accepted, and data that exceeds the
+ // limit is rejected.
+ HandleControlFrameHeadersStart();
+ const size_t kMaxBufferSizeBytes = 50;
+ const SpdyString a_value = SpdyString(49, 'x');
+ decoder_.set_max_decode_buffer_size_bytes(kMaxBufferSizeBytes);
+ HpackBlockBuilder hbb;
+ hbb.AppendLiteralNameAndValue(HpackEntryType::kNeverIndexedLiteralHeader,
+ false, "a", false, a_value);
+ const SpdyString& s = hbb.buffer();
+ EXPECT_GT(s.size(), kMaxBufferSizeBytes);
+
+ // Any one in input buffer must not exceed kMaxBufferSizeBytes.
+ EXPECT_TRUE(HandleControlFrameHeadersData(s.substr(0, s.size() / 2)));
+ EXPECT_TRUE(HandleControlFrameHeadersData(s.substr(s.size() / 2)));
+
+ EXPECT_FALSE(HandleControlFrameHeadersData(s));
+ SpdyHeaderBlock expected_block = MakeHeaderBlock({{"a", a_value}});
+ EXPECT_EQ(expected_block, decoded_block());
+}
+
+TEST_P(HpackDecoderAdapterTest, NameTooLong) {
+ // Verify that a name longer than the allowed size generates an error.
+ const size_t kMaxBufferSizeBytes = 50;
+ const SpdyString name = SpdyString(2 * kMaxBufferSizeBytes, 'x');
+ const SpdyString value = "abc";
+
+ decoder_.set_max_decode_buffer_size_bytes(kMaxBufferSizeBytes);
+
+ HpackBlockBuilder hbb;
+ hbb.AppendLiteralNameAndValue(HpackEntryType::kNeverIndexedLiteralHeader,
+ false, name, false, value);
+
+ const size_t fragment_size = (3 * kMaxBufferSizeBytes) / 2;
+ const SpdyString fragment = hbb.buffer().substr(0, fragment_size);
+
+ HandleControlFrameHeadersStart();
+ EXPECT_FALSE(HandleControlFrameHeadersData(fragment));
+}
+
+TEST_P(HpackDecoderAdapterTest, HeaderTooLongToBuffer) {
+ // Verify that a header longer than the allowed size generates an error if
+ // it isn't all in one input buffer.
+ const SpdyString name = "some-key";
+ const SpdyString value = "some-value";
+ const size_t kMaxBufferSizeBytes = name.size() + value.size() - 2;
+ decoder_.set_max_decode_buffer_size_bytes(kMaxBufferSizeBytes);
+
+ HpackBlockBuilder hbb;
+ hbb.AppendLiteralNameAndValue(HpackEntryType::kNeverIndexedLiteralHeader,
+ false, name, false, value);
+ const size_t fragment_size = hbb.size() - 1;
+ const SpdyString fragment = hbb.buffer().substr(0, fragment_size);
+
+ HandleControlFrameHeadersStart();
+ EXPECT_FALSE(HandleControlFrameHeadersData(fragment));
+}
+
+// Decode with incomplete data in buffer.
+TEST_P(HpackDecoderAdapterTest, DecodeWithIncompleteData) {
+ HandleControlFrameHeadersStart();
+
+ // No need to wait for more data.
+ EXPECT_TRUE(HandleControlFrameHeadersData("\x82\x85\x82"));
+ std::vector<std::pair<SpdyString, SpdyString>> expected_headers = {
+ {":method", "GET"}, {":path", "/index.html"}, {":method", "GET"}};
+
+ SpdyHeaderBlock expected_block1 = MakeHeaderBlock(expected_headers);
+ EXPECT_EQ(expected_block1, decoded_block());
+
+ // Full and partial headers, won't add partial to the headers.
+ EXPECT_TRUE(
+ HandleControlFrameHeadersData("\x40\x03goo"
+ "\x03gar\xbe\x40\x04spam"));
+ expected_headers.push_back({"goo", "gar"});
+ expected_headers.push_back({"goo", "gar"});
+
+ SpdyHeaderBlock expected_block2 = MakeHeaderBlock(expected_headers);
+ EXPECT_EQ(expected_block2, decoded_block());
+
+ // Add the needed data.
+ EXPECT_TRUE(HandleControlFrameHeadersData("\x04gggs"));
+
+ size_t size = 0;
+ EXPECT_TRUE(HandleControlFrameHeadersComplete(&size));
+ EXPECT_EQ(24u, size);
+
+ expected_headers.push_back({"spam", "gggs"});
+
+ SpdyHeaderBlock expected_block3 = MakeHeaderBlock(expected_headers);
+ EXPECT_EQ(expected_block3, decoded_block());
+}
+
+TEST_P(HpackDecoderAdapterTest, HandleHeaderRepresentation) {
+ // Make sure the decoder is properly initialized.
+ HandleControlFrameHeadersStart();
+ HandleControlFrameHeadersData("");
+
+ // All cookie crumbs are joined.
+ decoder_peer_.HandleHeaderRepresentation("cookie", " part 1");
+ decoder_peer_.HandleHeaderRepresentation("cookie", "part 2 ");
+ decoder_peer_.HandleHeaderRepresentation("cookie", "part3");
+
+ // Already-delimited headers are passed through.
+ decoder_peer_.HandleHeaderRepresentation("passed-through",
+ SpdyString("foo\0baz", 7));
+
+ // Other headers are joined on \0. Case matters.
+ decoder_peer_.HandleHeaderRepresentation("joined", "not joined");
+ decoder_peer_.HandleHeaderRepresentation("joineD", "value 1");
+ decoder_peer_.HandleHeaderRepresentation("joineD", "value 2");
+
+ // Empty headers remain empty.
+ decoder_peer_.HandleHeaderRepresentation("empty", "");
+
+ // Joined empty headers work as expected.
+ decoder_peer_.HandleHeaderRepresentation("empty-joined", "");
+ decoder_peer_.HandleHeaderRepresentation("empty-joined", "foo");
+ decoder_peer_.HandleHeaderRepresentation("empty-joined", "");
+ decoder_peer_.HandleHeaderRepresentation("empty-joined", "");
+
+ // Non-contiguous cookie crumb.
+ decoder_peer_.HandleHeaderRepresentation("cookie", " fin!");
+
+ // Finish and emit all headers.
+ decoder_.HandleControlFrameHeadersComplete(nullptr);
+
+ // Resulting decoded headers are in the same order as the inputs.
+ EXPECT_THAT(
+ decoded_block(),
+ ElementsAre(Pair("cookie", " part 1; part 2 ; part3; fin!"),
+ Pair("passed-through", SpdyStringPiece("foo\0baz", 7)),
+ Pair("joined", "not joined"),
+ Pair("joineD", SpdyStringPiece("value 1\0value 2", 15)),
+ Pair("empty", ""),
+ Pair("empty-joined", SpdyStringPiece("\0foo\0\0", 6))));
+}
+
+// Decoding indexed static table field should work.
+TEST_P(HpackDecoderAdapterTest, IndexedHeaderStatic) {
+ // Reference static table entries #2 and #5.
+ const SpdyHeaderBlock& header_set1 = DecodeBlockExpectingSuccess("\x82\x85");
+ SpdyHeaderBlock expected_header_set1;
+ expected_header_set1[":method"] = "GET";
+ expected_header_set1[":path"] = "/index.html";
+ EXPECT_EQ(expected_header_set1, header_set1);
+
+ // Reference static table entry #2.
+ const SpdyHeaderBlock& header_set2 = DecodeBlockExpectingSuccess("\x82");
+ SpdyHeaderBlock expected_header_set2;
+ expected_header_set2[":method"] = "GET";
+ EXPECT_EQ(expected_header_set2, header_set2);
+}
+
+TEST_P(HpackDecoderAdapterTest, IndexedHeaderDynamic) {
+ // First header block: add an entry to header table.
+ const SpdyHeaderBlock& header_set1 = DecodeBlockExpectingSuccess(
+ "\x40\x03"
+ "foo"
+ "\x03"
+ "bar");
+ SpdyHeaderBlock expected_header_set1;
+ expected_header_set1["foo"] = "bar";
+ EXPECT_EQ(expected_header_set1, header_set1);
+
+ // Second header block: add another entry to header table.
+ const SpdyHeaderBlock& header_set2 = DecodeBlockExpectingSuccess(
+ "\xbe\x40\x04"
+ "spam"
+ "\x04"
+ "eggs");
+ SpdyHeaderBlock expected_header_set2;
+ expected_header_set2["foo"] = "bar";
+ expected_header_set2["spam"] = "eggs";
+ EXPECT_EQ(expected_header_set2, header_set2);
+
+ // Third header block: refer to most recently added entry.
+ const SpdyHeaderBlock& header_set3 = DecodeBlockExpectingSuccess("\xbe");
+ SpdyHeaderBlock expected_header_set3;
+ expected_header_set3["spam"] = "eggs";
+ EXPECT_EQ(expected_header_set3, header_set3);
+}
+
+// Test a too-large indexed header.
+TEST_P(HpackDecoderAdapterTest, InvalidIndexedHeader) {
+ // High-bit set, and a prefix of one more than the number of static entries.
+ EXPECT_FALSE(DecodeHeaderBlock("\xbe"));
+}
+
+TEST_P(HpackDecoderAdapterTest, ContextUpdateMaximumSize) {
+ EXPECT_EQ(kDefaultHeaderTableSizeSetting,
+ decoder_peer_.header_table_size_limit());
+ SpdyString input;
+ {
+ // Maximum-size update with size 126. Succeeds.
+ HpackOutputStream output_stream;
+ output_stream.AppendPrefix(kHeaderTableSizeUpdateOpcode);
+ output_stream.AppendUint32(126);
+
+ output_stream.TakeString(&input);
+ EXPECT_TRUE(DecodeHeaderBlock(SpdyStringPiece(input)));
+ EXPECT_EQ(126u, decoder_peer_.header_table_size_limit());
+ }
+ {
+ // Maximum-size update with kDefaultHeaderTableSizeSetting. Succeeds.
+ HpackOutputStream output_stream;
+ output_stream.AppendPrefix(kHeaderTableSizeUpdateOpcode);
+ output_stream.AppendUint32(kDefaultHeaderTableSizeSetting);
+
+ output_stream.TakeString(&input);
+ EXPECT_TRUE(DecodeHeaderBlock(SpdyStringPiece(input)));
+ EXPECT_EQ(kDefaultHeaderTableSizeSetting,
+ decoder_peer_.header_table_size_limit());
+ }
+ {
+ // Maximum-size update with kDefaultHeaderTableSizeSetting + 1. Fails.
+ HpackOutputStream output_stream;
+ output_stream.AppendPrefix(kHeaderTableSizeUpdateOpcode);
+ output_stream.AppendUint32(kDefaultHeaderTableSizeSetting + 1);
+
+ output_stream.TakeString(&input);
+ EXPECT_FALSE(DecodeHeaderBlock(SpdyStringPiece(input)));
+ EXPECT_EQ(kDefaultHeaderTableSizeSetting,
+ decoder_peer_.header_table_size_limit());
+ }
+}
+
+// Two HeaderTableSizeUpdates may appear at the beginning of the block
+TEST_P(HpackDecoderAdapterTest, TwoTableSizeUpdates) {
+ SpdyString input;
+ {
+ // Should accept two table size updates, update to second one
+ HpackOutputStream output_stream;
+ output_stream.AppendPrefix(kHeaderTableSizeUpdateOpcode);
+ output_stream.AppendUint32(0);
+ output_stream.AppendPrefix(kHeaderTableSizeUpdateOpcode);
+ output_stream.AppendUint32(122);
+
+ output_stream.TakeString(&input);
+ EXPECT_TRUE(DecodeHeaderBlock(SpdyStringPiece(input)));
+ EXPECT_EQ(122u, decoder_peer_.header_table_size_limit());
+ }
+}
+
+// Three HeaderTableSizeUpdates should result in an error
+TEST_P(HpackDecoderAdapterTest, ThreeTableSizeUpdatesError) {
+ SpdyString input;
+ {
+ // Should reject three table size updates, update to second one
+ HpackOutputStream output_stream;
+ output_stream.AppendPrefix(kHeaderTableSizeUpdateOpcode);
+ output_stream.AppendUint32(5);
+ output_stream.AppendPrefix(kHeaderTableSizeUpdateOpcode);
+ output_stream.AppendUint32(10);
+ output_stream.AppendPrefix(kHeaderTableSizeUpdateOpcode);
+ output_stream.AppendUint32(15);
+
+ output_stream.TakeString(&input);
+
+ EXPECT_FALSE(DecodeHeaderBlock(SpdyStringPiece(input)));
+ EXPECT_EQ(10u, decoder_peer_.header_table_size_limit());
+ }
+}
+
+// HeaderTableSizeUpdates may only appear at the beginning of the block
+// Any other updates should result in an error
+TEST_P(HpackDecoderAdapterTest, TableSizeUpdateSecondError) {
+ SpdyString input;
+ {
+ // Should reject a table size update appearing after a different entry
+ // The table size should remain as the default
+ HpackOutputStream output_stream;
+ output_stream.AppendBytes("\x82\x85");
+ output_stream.AppendPrefix(kHeaderTableSizeUpdateOpcode);
+ output_stream.AppendUint32(123);
+
+ output_stream.TakeString(&input);
+
+ EXPECT_FALSE(DecodeHeaderBlock(SpdyStringPiece(input)));
+ EXPECT_EQ(kDefaultHeaderTableSizeSetting,
+ decoder_peer_.header_table_size_limit());
+ }
+}
+
+// HeaderTableSizeUpdates may only appear at the beginning of the block
+// Any other updates should result in an error
+TEST_P(HpackDecoderAdapterTest, TableSizeUpdateFirstThirdError) {
+ SpdyString input;
+ {
+ // Should reject the second table size update
+ // if a different entry appears after the first update
+ // The table size should update to the first but not the second
+ HpackOutputStream output_stream;
+ output_stream.AppendPrefix(kHeaderTableSizeUpdateOpcode);
+ output_stream.AppendUint32(60);
+ output_stream.AppendBytes("\x82\x85");
+ output_stream.AppendPrefix(kHeaderTableSizeUpdateOpcode);
+ output_stream.AppendUint32(125);
+
+ output_stream.TakeString(&input);
+
+ EXPECT_FALSE(DecodeHeaderBlock(SpdyStringPiece(input)));
+ EXPECT_EQ(60u, decoder_peer_.header_table_size_limit());
+ }
+}
+
+// Decoding two valid encoded literal headers with no indexing should
+// work.
+TEST_P(HpackDecoderAdapterTest, LiteralHeaderNoIndexing) {
+ // First header with indexed name, second header with string literal
+ // name.
+ const char input[] = "\x04\x0c/sample/path\x00\x06:path2\x0e/sample/path/2";
+ const SpdyHeaderBlock& header_set = DecodeBlockExpectingSuccess(
+ SpdyStringPiece(input, SPDY_ARRAYSIZE(input) - 1));
+
+ SpdyHeaderBlock expected_header_set;
+ expected_header_set[":path"] = "/sample/path";
+ expected_header_set[":path2"] = "/sample/path/2";
+ EXPECT_EQ(expected_header_set, header_set);
+}
+
+// Decoding two valid encoded literal headers with incremental
+// indexing and string literal names should work.
+TEST_P(HpackDecoderAdapterTest, LiteralHeaderIncrementalIndexing) {
+ const char input[] = "\x44\x0c/sample/path\x40\x06:path2\x0e/sample/path/2";
+ const SpdyHeaderBlock& header_set = DecodeBlockExpectingSuccess(
+ SpdyStringPiece(input, SPDY_ARRAYSIZE(input) - 1));
+
+ SpdyHeaderBlock expected_header_set;
+ expected_header_set[":path"] = "/sample/path";
+ expected_header_set[":path2"] = "/sample/path/2";
+ EXPECT_EQ(expected_header_set, header_set);
+}
+
+TEST_P(HpackDecoderAdapterTest, LiteralHeaderWithIndexingInvalidNameIndex) {
+ decoder_.ApplyHeaderTableSizeSetting(0);
+ EXPECT_TRUE(EncodeAndDecodeDynamicTableSizeUpdates(0, 0));
+
+ // Name is the last static index. Works.
+ EXPECT_TRUE(DecodeHeaderBlock(SpdyStringPiece("\x7d\x03ooo")));
+ // Name is one beyond the last static index. Fails.
+ EXPECT_FALSE(DecodeHeaderBlock(SpdyStringPiece("\x7e\x03ooo")));
+}
+
+TEST_P(HpackDecoderAdapterTest, LiteralHeaderNoIndexingInvalidNameIndex) {
+ // Name is the last static index. Works.
+ EXPECT_TRUE(DecodeHeaderBlock(SpdyStringPiece("\x0f\x2e\x03ooo")));
+ // Name is one beyond the last static index. Fails.
+ EXPECT_FALSE(DecodeHeaderBlock(SpdyStringPiece("\x0f\x2f\x03ooo")));
+}
+
+TEST_P(HpackDecoderAdapterTest, LiteralHeaderNeverIndexedInvalidNameIndex) {
+ // Name is the last static index. Works.
+ EXPECT_TRUE(DecodeHeaderBlock(SpdyStringPiece("\x1f\x2e\x03ooo")));
+ // Name is one beyond the last static index. Fails.
+ EXPECT_FALSE(DecodeHeaderBlock(SpdyStringPiece("\x1f\x2f\x03ooo")));
+}
+
+TEST_P(HpackDecoderAdapterTest, TruncatedIndex) {
+ // Indexed Header, varint for index requires multiple bytes,
+ // but only one provided.
+ EXPECT_FALSE(DecodeHeaderBlock("\xff"));
+}
+
+TEST_P(HpackDecoderAdapterTest, TruncatedHuffmanLiteral) {
+ // Literal value, Huffman encoded, but with the last byte missing (i.e.
+ // drop the final ff shown below).
+ //
+ // 41 | == Literal indexed ==
+ // | Indexed name (idx = 1)
+ // | :authority
+ // 8c | Literal value (len = 12)
+ // | Huffman encoded:
+ // f1e3 c2e5 f23a 6ba0 ab90 f4ff | .....:k.....
+ // | Decoded:
+ // | www.example.com
+ // | -> :authority: www.example.com
+
+ SpdyString first = SpdyHexDecode("418cf1e3c2e5f23a6ba0ab90f4ff");
+ EXPECT_TRUE(DecodeHeaderBlock(first));
+ first.pop_back();
+ EXPECT_FALSE(DecodeHeaderBlock(first));
+}
+
+TEST_P(HpackDecoderAdapterTest, HuffmanEOSError) {
+ // Literal value, Huffman encoded, but with an additional ff byte at the end
+ // of the string, i.e. an EOS that is longer than permitted.
+ //
+ // 41 | == Literal indexed ==
+ // | Indexed name (idx = 1)
+ // | :authority
+ // 8d | Literal value (len = 13)
+ // | Huffman encoded:
+ // f1e3 c2e5 f23a 6ba0 ab90 f4ff | .....:k.....
+ // | Decoded:
+ // | www.example.com
+ // | -> :authority: www.example.com
+
+ SpdyString first = SpdyHexDecode("418cf1e3c2e5f23a6ba0ab90f4ff");
+ EXPECT_TRUE(DecodeHeaderBlock(first));
+ first = SpdyHexDecode("418df1e3c2e5f23a6ba0ab90f4ffff");
+ EXPECT_FALSE(DecodeHeaderBlock(first));
+}
+
+// Round-tripping the header set from RFC 7541 C.3.1 should work.
+// http://httpwg.org/specs/rfc7541.html#rfc.section.C.3.1
+TEST_P(HpackDecoderAdapterTest, BasicC31) {
+ HpackEncoder encoder(ObtainHpackHuffmanTable());
+
+ SpdyHeaderBlock expected_header_set;
+ expected_header_set[":method"] = "GET";
+ expected_header_set[":scheme"] = "http";
+ expected_header_set[":path"] = "/";
+ expected_header_set[":authority"] = "www.example.com";
+
+ SpdyString encoded_header_set;
+ EXPECT_TRUE(
+ encoder.EncodeHeaderSet(expected_header_set, &encoded_header_set));
+
+ EXPECT_TRUE(DecodeHeaderBlock(encoded_header_set));
+ EXPECT_EQ(expected_header_set, decoded_block());
+}
+
+// RFC 7541, Section C.4: Request Examples with Huffman Coding
+// http://httpwg.org/specs/rfc7541.html#rfc.section.C.4
+TEST_P(HpackDecoderAdapterTest, SectionC4RequestHuffmanExamples) {
+ // TODO(jamessynge): Use http2/hpack/tools/hpack_example.h to parse the
+ // example directly, instead of having it as a comment.
+ //
+ // 82 | == Indexed - Add ==
+ // | idx = 2
+ // | -> :method: GET
+ // 86 | == Indexed - Add ==
+ // | idx = 6
+ // | -> :scheme: http
+ // 84 | == Indexed - Add ==
+ // | idx = 4
+ // | -> :path: /
+ // 41 | == Literal indexed ==
+ // | Indexed name (idx = 1)
+ // | :authority
+ // 8c | Literal value (len = 12)
+ // | Huffman encoded:
+ // f1e3 c2e5 f23a 6ba0 ab90 f4ff | .....:k.....
+ // | Decoded:
+ // | www.example.com
+ // | -> :authority: www.example.com
+ SpdyString first = SpdyHexDecode("828684418cf1e3c2e5f23a6ba0ab90f4ff");
+ const SpdyHeaderBlock& first_header_set = DecodeBlockExpectingSuccess(first);
+
+ EXPECT_THAT(first_header_set,
+ ElementsAre(
+ // clang-format off
+ Pair(":method", "GET"),
+ Pair(":scheme", "http"),
+ Pair(":path", "/"),
+ Pair(":authority", "www.example.com")));
+ // clang-format on
+
+ expectEntry(62, 57, ":authority", "www.example.com");
+ EXPECT_EQ(57u, decoder_peer_.current_header_table_size());
+
+ // 82 | == Indexed - Add ==
+ // | idx = 2
+ // | -> :method: GET
+ // 86 | == Indexed - Add ==
+ // | idx = 6
+ // | -> :scheme: http
+ // 84 | == Indexed - Add ==
+ // | idx = 4
+ // | -> :path: /
+ // be | == Indexed - Add ==
+ // | idx = 62
+ // | -> :authority: www.example.com
+ // 58 | == Literal indexed ==
+ // | Indexed name (idx = 24)
+ // | cache-control
+ // 86 | Literal value (len = 8)
+ // | Huffman encoded:
+ // a8eb 1064 9cbf | ...d..
+ // | Decoded:
+ // | no-cache
+ // | -> cache-control: no-cache
+
+ SpdyString second = SpdyHexDecode("828684be5886a8eb10649cbf");
+ const SpdyHeaderBlock& second_header_set =
+ DecodeBlockExpectingSuccess(second);
+
+ EXPECT_THAT(second_header_set,
+ ElementsAre(
+ // clang-format off
+ Pair(":method", "GET"),
+ Pair(":scheme", "http"),
+ Pair(":path", "/"),
+ Pair(":authority", "www.example.com"),
+ Pair("cache-control", "no-cache")));
+ // clang-format on
+
+ expectEntry(62, 53, "cache-control", "no-cache");
+ expectEntry(63, 57, ":authority", "www.example.com");
+ EXPECT_EQ(110u, decoder_peer_.current_header_table_size());
+
+ // 82 | == Indexed - Add ==
+ // | idx = 2
+ // | -> :method: GET
+ // 87 | == Indexed - Add ==
+ // | idx = 7
+ // | -> :scheme: https
+ // 85 | == Indexed - Add ==
+ // | idx = 5
+ // | -> :path: /index.html
+ // bf | == Indexed - Add ==
+ // | idx = 63
+ // | -> :authority: www.example.com
+ // 40 | == Literal indexed ==
+ // 88 | Literal name (len = 10)
+ // | Huffman encoded:
+ // 25a8 49e9 5ba9 7d7f | %.I.[.}.
+ // | Decoded:
+ // | custom-key
+ // 89 | Literal value (len = 12)
+ // | Huffman encoded:
+ // 25a8 49e9 5bb8 e8b4 bf | %.I.[....
+ // | Decoded:
+ // | custom-value
+ // | -> custom-key: custom-value
+ SpdyString third =
+ SpdyHexDecode("828785bf408825a849e95ba97d7f8925a849e95bb8e8b4bf");
+ const SpdyHeaderBlock& third_header_set = DecodeBlockExpectingSuccess(third);
+
+ EXPECT_THAT(
+ third_header_set,
+ ElementsAre(
+ // clang-format off
+ Pair(":method", "GET"),
+ Pair(":scheme", "https"),
+ Pair(":path", "/index.html"),
+ Pair(":authority", "www.example.com"),
+ Pair("custom-key", "custom-value")));
+ // clang-format on
+
+ expectEntry(62, 54, "custom-key", "custom-value");
+ expectEntry(63, 53, "cache-control", "no-cache");
+ expectEntry(64, 57, ":authority", "www.example.com");
+ EXPECT_EQ(164u, decoder_peer_.current_header_table_size());
+}
+
+// RFC 7541, Section C.6: Response Examples with Huffman Coding
+// http://httpwg.org/specs/rfc7541.html#rfc.section.C.6
+TEST_P(HpackDecoderAdapterTest, SectionC6ResponseHuffmanExamples) {
+ // The example is based on a maximum dynamic table size of 256,
+ // which allows for testing dynamic table evictions.
+ decoder_peer_.set_header_table_size_limit(256);
+
+ // 48 | == Literal indexed ==
+ // | Indexed name (idx = 8)
+ // | :status
+ // 82 | Literal value (len = 3)
+ // | Huffman encoded:
+ // 6402 | d.
+ // | Decoded:
+ // | 302
+ // | -> :status: 302
+ // 58 | == Literal indexed ==
+ // | Indexed name (idx = 24)
+ // | cache-control
+ // 85 | Literal value (len = 7)
+ // | Huffman encoded:
+ // aec3 771a 4b | ..w.K
+ // | Decoded:
+ // | private
+ // | -> cache-control: private
+ // 61 | == Literal indexed ==
+ // | Indexed name (idx = 33)
+ // | date
+ // 96 | Literal value (len = 29)
+ // | Huffman encoded:
+ // d07a be94 1054 d444 a820 0595 040b 8166 | .z...T.D. .....f
+ // e082 a62d 1bff | ...-..
+ // | Decoded:
+ // | Mon, 21 Oct 2013 20:13:21
+ // | GMT
+ // | -> date: Mon, 21 Oct 2013
+ // | 20:13:21 GMT
+ // 6e | == Literal indexed ==
+ // | Indexed name (idx = 46)
+ // | location
+ // 91 | Literal value (len = 23)
+ // | Huffman encoded:
+ // 9d29 ad17 1863 c78f 0b97 c8e9 ae82 ae43 | .)...c.........C
+ // d3 | .
+ // | Decoded:
+ // | https://www.example.com
+ // | -> location: https://www.e
+ // | xample.com
+
+ SpdyString first = SpdyHexDecode(
+ "488264025885aec3771a4b6196d07abe"
+ "941054d444a8200595040b8166e082a6"
+ "2d1bff6e919d29ad171863c78f0b97c8"
+ "e9ae82ae43d3");
+ const SpdyHeaderBlock& first_header_set = DecodeBlockExpectingSuccess(first);
+
+ EXPECT_THAT(first_header_set,
+ ElementsAre(
+ // clang-format off
+ Pair(":status", "302"),
+ Pair("cache-control", "private"),
+ Pair("date", "Mon, 21 Oct 2013 20:13:21 GMT"),
+ Pair("location", "https://www.example.com")));
+ // clang-format on
+
+ expectEntry(62, 63, "location", "https://www.example.com");
+ expectEntry(63, 65, "date", "Mon, 21 Oct 2013 20:13:21 GMT");
+ expectEntry(64, 52, "cache-control", "private");
+ expectEntry(65, 42, ":status", "302");
+ EXPECT_EQ(222u, decoder_peer_.current_header_table_size());
+
+ // 48 | == Literal indexed ==
+ // | Indexed name (idx = 8)
+ // | :status
+ // 83 | Literal value (len = 3)
+ // | Huffman encoded:
+ // 640e ff | d..
+ // | Decoded:
+ // | 307
+ // | - evict: :status: 302
+ // | -> :status: 307
+ // c1 | == Indexed - Add ==
+ // | idx = 65
+ // | -> cache-control: private
+ // c0 | == Indexed - Add ==
+ // | idx = 64
+ // | -> date: Mon, 21 Oct 2013
+ // | 20:13:21 GMT
+ // bf | == Indexed - Add ==
+ // | idx = 63
+ // | -> location:
+ // | https://www.example.com
+ SpdyString second = SpdyHexDecode("4883640effc1c0bf");
+ const SpdyHeaderBlock& second_header_set =
+ DecodeBlockExpectingSuccess(second);
+
+ EXPECT_THAT(second_header_set,
+ ElementsAre(
+ // clang-format off
+ Pair(":status", "307"),
+ Pair("cache-control", "private"),
+ Pair("date", "Mon, 21 Oct 2013 20:13:21 GMT"),
+ Pair("location", "https://www.example.com")));
+ // clang-format on
+
+ expectEntry(62, 42, ":status", "307");
+ expectEntry(63, 63, "location", "https://www.example.com");
+ expectEntry(64, 65, "date", "Mon, 21 Oct 2013 20:13:21 GMT");
+ expectEntry(65, 52, "cache-control", "private");
+ EXPECT_EQ(222u, decoder_peer_.current_header_table_size());
+
+ // 88 | == Indexed - Add ==
+ // | idx = 8
+ // | -> :status: 200
+ // c1 | == Indexed - Add ==
+ // | idx = 65
+ // | -> cache-control: private
+ // 61 | == Literal indexed ==
+ // | Indexed name (idx = 33)
+ // | date
+ // 96 | Literal value (len = 22)
+ // | Huffman encoded:
+ // d07a be94 1054 d444 a820 0595 040b 8166 | .z...T.D. .....f
+ // e084 a62d 1bff | ...-..
+ // | Decoded:
+ // | Mon, 21 Oct 2013 20:13:22
+ // | GMT
+ // | - evict: cache-control:
+ // | private
+ // | -> date: Mon, 21 Oct 2013
+ // | 20:13:22 GMT
+ // c0 | == Indexed - Add ==
+ // | idx = 64
+ // | -> location:
+ // | https://www.example.com
+ // 5a | == Literal indexed ==
+ // | Indexed name (idx = 26)
+ // | content-encoding
+ // 83 | Literal value (len = 3)
+ // | Huffman encoded:
+ // 9bd9 ab | ...
+ // | Decoded:
+ // | gzip
+ // | - evict: date: Mon, 21 Oct
+ // | 2013 20:13:21 GMT
+ // | -> content-encoding: gzip
+ // 77 | == Literal indexed ==
+ // | Indexed name (idx = 55)
+ // | set-cookie
+ // ad | Literal value (len = 45)
+ // | Huffman encoded:
+ // 94e7 821d d7f2 e6c7 b335 dfdf cd5b 3960 | .........5...[9`
+ // d5af 2708 7f36 72c1 ab27 0fb5 291f 9587 | ..'..6r..'..)...
+ // 3160 65c0 03ed 4ee5 b106 3d50 07 | 1`e...N...=P.
+ // | Decoded:
+ // | foo=ASDJKHQKBZXOQWEOPIUAXQ
+ // | WEOIU; max-age=3600; versi
+ // | on=1
+ // | - evict: location:
+ // | https://www.example.com
+ // | - evict: :status: 307
+ // | -> set-cookie: foo=ASDJKHQ
+ // | KBZXOQWEOPIUAXQWEOIU;
+ // | max-age=3600; version=1
+ SpdyString third = SpdyHexDecode(
+ "88c16196d07abe941054d444a8200595"
+ "040b8166e084a62d1bffc05a839bd9ab"
+ "77ad94e7821dd7f2e6c7b335dfdfcd5b"
+ "3960d5af27087f3672c1ab270fb5291f"
+ "9587316065c003ed4ee5b1063d5007");
+ const SpdyHeaderBlock& third_header_set = DecodeBlockExpectingSuccess(third);
+
+ EXPECT_THAT(third_header_set,
+ ElementsAre(
+ // clang-format off
+ Pair(":status", "200"),
+ Pair("cache-control", "private"),
+ Pair("date", "Mon, 21 Oct 2013 20:13:22 GMT"),
+ Pair("location", "https://www.example.com"),
+ Pair("content-encoding", "gzip"),
+ Pair("set-cookie", "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU;"
+ " max-age=3600; version=1")));
+ // clang-format on
+
+ expectEntry(62, 98, "set-cookie",
+ "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU;"
+ " max-age=3600; version=1");
+ expectEntry(63, 52, "content-encoding", "gzip");
+ expectEntry(64, 65, "date", "Mon, 21 Oct 2013 20:13:22 GMT");
+ EXPECT_EQ(215u, decoder_peer_.current_header_table_size());
+}
+
+// Regression test: Found that entries with dynamic indexed names and literal
+// values caused "use after free" MSAN failures if the name was evicted as it
+// was being re-used.
+TEST_P(HpackDecoderAdapterTest, ReuseNameOfEvictedEntry) {
+ // Each entry is measured as 32 bytes plus the sum of the lengths of the name
+ // and the value. Set the size big enough for at most one entry, and a fairly
+ // small one at that (31 ASCII characters).
+ decoder_.ApplyHeaderTableSizeSetting(63);
+
+ HpackBlockBuilder hbb;
+ hbb.AppendDynamicTableSizeUpdate(0);
+ hbb.AppendDynamicTableSizeUpdate(63);
+
+ const SpdyStringPiece name("some-name");
+ const SpdyStringPiece value1("some-value");
+ const SpdyStringPiece value2("another-value");
+ const SpdyStringPiece value3("yet-another-value");
+
+ // Add an entry that will become the first in the dynamic table, entry 62.
+ hbb.AppendLiteralNameAndValue(HpackEntryType::kIndexedLiteralHeader, false,
+ name, false, value1);
+
+ // Confirm that entry has been added by re-using it.
+ hbb.AppendIndexedHeader(62);
+
+ // Add another entry referring to the name of the first. This will evict the
+ // first.
+ hbb.AppendNameIndexAndLiteralValue(HpackEntryType::kIndexedLiteralHeader, 62,
+ false, value2);
+
+ // Confirm that entry has been added by re-using it.
+ hbb.AppendIndexedHeader(62);
+
+ // Add another entry referring to the name of the second. This will evict the
+ // second.
+ hbb.AppendNameIndexAndLiteralValue(HpackEntryType::kIndexedLiteralHeader, 62,
+ false, value3);
+
+ // Confirm that entry has been added by re-using it.
+ hbb.AppendIndexedHeader(62);
+
+ // Can't have DecodeHeaderBlock do the default check for size of the decoded
+ // data because SpdyHeaderBlock will join multiple headers with the same
+ // name into a single entry, thus we won't see repeated occurrences of the
+ // name, instead seeing separators between values.
+ EXPECT_TRUE(DecodeHeaderBlock(hbb.buffer(), kNoCheckDecodedSize));
+
+ SpdyHeaderBlock expected_header_set;
+ expected_header_set.AppendValueOrAddHeader(name, value1);
+ expected_header_set.AppendValueOrAddHeader(name, value1);
+ expected_header_set.AppendValueOrAddHeader(name, value2);
+ expected_header_set.AppendValueOrAddHeader(name, value2);
+ expected_header_set.AppendValueOrAddHeader(name, value3);
+ expected_header_set.AppendValueOrAddHeader(name, value3);
+
+ // SpdyHeaderBlock stores these 6 strings as '\0' separated values.
+ // Make sure that is what happened.
+ SpdyString joined_values = expected_header_set[name].as_string();
+ EXPECT_EQ(joined_values.size(),
+ 2 * value1.size() + 2 * value2.size() + 2 * value3.size() + 5);
+
+ EXPECT_EQ(expected_header_set, decoded_block());
+
+ if (start_choice_ == START_WITH_HANDLER) {
+ EXPECT_EQ(handler_.header_bytes_parsed(),
+ 6 * name.size() + 2 * value1.size() + 2 * value2.size() +
+ 2 * value3.size());
+ }
+}
+
+// Regression test for https://crbug.com/747395.
+TEST_P(HpackDecoderAdapterTest, Cookies) {
+ SpdyHeaderBlock expected_header_set;
+ expected_header_set["cookie"] = "foo; bar";
+
+ EXPECT_TRUE(DecodeHeaderBlock(SpdyHexDecode("608294e76003626172")));
+ EXPECT_EQ(expected_header_set, decoded_block());
+}
+
+} // namespace
+} // namespace test
+} // namespace spdy
diff --git a/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_encoder.cc b/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_encoder.cc
new file mode 100644
index 00000000000..a9981b95a73
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_encoder.cc
@@ -0,0 +1,365 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/spdy/core/hpack/hpack_encoder.h"
+
+#include <algorithm>
+#include <limits>
+
+#include "base/logging.h"
+#include "net/third_party/quiche/src/spdy/core/hpack/hpack_constants.h"
+#include "net/third_party/quiche/src/spdy/core/hpack/hpack_header_table.h"
+#include "net/third_party/quiche/src/spdy/core/hpack/hpack_huffman_table.h"
+#include "net/third_party/quiche/src/spdy/core/hpack/hpack_output_stream.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_estimate_memory_usage.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_ptr_util.h"
+
+namespace spdy {
+
+class HpackEncoder::RepresentationIterator {
+ public:
+ // |pseudo_headers| and |regular_headers| must outlive the iterator.
+ RepresentationIterator(const Representations& pseudo_headers,
+ const Representations& regular_headers)
+ : pseudo_begin_(pseudo_headers.begin()),
+ pseudo_end_(pseudo_headers.end()),
+ regular_begin_(regular_headers.begin()),
+ regular_end_(regular_headers.end()) {}
+
+ // |headers| must outlive the iterator.
+ explicit RepresentationIterator(const Representations& headers)
+ : pseudo_begin_(headers.begin()),
+ pseudo_end_(headers.end()),
+ regular_begin_(headers.end()),
+ regular_end_(headers.end()) {}
+
+ bool HasNext() {
+ return pseudo_begin_ != pseudo_end_ || regular_begin_ != regular_end_;
+ }
+
+ const Representation Next() {
+ if (pseudo_begin_ != pseudo_end_) {
+ return *pseudo_begin_++;
+ } else {
+ return *regular_begin_++;
+ }
+ }
+
+ private:
+ Representations::const_iterator pseudo_begin_;
+ Representations::const_iterator pseudo_end_;
+ Representations::const_iterator regular_begin_;
+ Representations::const_iterator regular_end_;
+};
+
+namespace {
+
+// The default header listener.
+void NoOpListener(SpdyStringPiece /*name*/, SpdyStringPiece /*value*/) {}
+
+// The default HPACK indexing policy.
+bool DefaultPolicy(SpdyStringPiece name, SpdyStringPiece /* value */) {
+ if (name.empty()) {
+ return false;
+ }
+ // :authority is always present and rarely changes, and has moderate
+ // length, therefore it makes a lot of sense to index (insert in the
+ // dynamic table).
+ if (name[0] == kPseudoHeaderPrefix) {
+ return name == ":authority";
+ }
+ return true;
+}
+
+} // namespace
+
+HpackEncoder::HpackEncoder(const HpackHuffmanTable& table)
+ : output_stream_(),
+ huffman_table_(table),
+ min_table_size_setting_received_(std::numeric_limits<size_t>::max()),
+ listener_(NoOpListener),
+ should_index_(DefaultPolicy),
+ enable_compression_(true),
+ should_emit_table_size_(false) {}
+
+HpackEncoder::~HpackEncoder() = default;
+
+bool HpackEncoder::EncodeHeaderSet(const SpdyHeaderBlock& header_set,
+ SpdyString* output) {
+ // Separate header set into pseudo-headers and regular headers.
+ Representations pseudo_headers;
+ Representations regular_headers;
+ bool found_cookie = false;
+ for (const auto& header : header_set) {
+ if (!found_cookie && header.first == "cookie") {
+ // Note that there can only be one "cookie" header, because header_set is
+ // a map.
+ found_cookie = true;
+ CookieToCrumbs(header, &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);
+ EncodeRepresentations(&iter, output);
+ }
+ return true;
+}
+
+void HpackEncoder::ApplyHeaderTableSizeSetting(size_t size_setting) {
+ if (size_setting == header_table_.settings_size_bound()) {
+ return;
+ }
+ if (size_setting < header_table_.settings_size_bound()) {
+ min_table_size_setting_received_ =
+ std::min(size_setting, min_table_size_setting_received_);
+ }
+ header_table_.SetSettingsHeaderTableSize(size_setting);
+ should_emit_table_size_ = true;
+}
+
+size_t HpackEncoder::EstimateMemoryUsage() const {
+ // |huffman_table_| is a singleton. It's accounted for in spdy_session_pool.cc
+ return SpdyEstimateMemoryUsage(header_table_) +
+ SpdyEstimateMemoryUsage(output_stream_);
+}
+
+void HpackEncoder::EncodeRepresentations(RepresentationIterator* iter,
+ SpdyString* output) {
+ MaybeEmitTableSize();
+ while (iter->HasNext()) {
+ const auto header = iter->Next();
+ listener_(header.first, header.second);
+ if (enable_compression_) {
+ const HpackEntry* entry =
+ header_table_.GetByNameAndValue(header.first, header.second);
+ if (entry != nullptr) {
+ EmitIndex(entry);
+ } else if (should_index_(header.first, header.second)) {
+ EmitIndexedLiteral(header);
+ } else {
+ EmitNonIndexedLiteral(header);
+ }
+ } else {
+ EmitNonIndexedLiteral(header);
+ }
+ }
+
+ output_stream_.TakeString(output);
+}
+
+void HpackEncoder::EmitIndex(const HpackEntry* entry) {
+ DVLOG(2) << "Emitting index " << header_table_.IndexOf(entry);
+ output_stream_.AppendPrefix(kIndexedOpcode);
+ output_stream_.AppendUint32(header_table_.IndexOf(entry));
+}
+
+void HpackEncoder::EmitIndexedLiteral(const Representation& representation) {
+ DVLOG(2) << "Emitting indexed literal: (" << representation.first << ", "
+ << representation.second << ")";
+ output_stream_.AppendPrefix(kLiteralIncrementalIndexOpcode);
+ EmitLiteral(representation);
+ header_table_.TryAddEntry(representation.first, representation.second);
+}
+
+void HpackEncoder::EmitNonIndexedLiteral(const Representation& representation) {
+ DVLOG(2) << "Emitting nonindexed literal: (" << representation.first << ", "
+ << representation.second << ")";
+ output_stream_.AppendPrefix(kLiteralNoIndexOpcode);
+ output_stream_.AppendUint32(0);
+ EmitString(representation.first);
+ EmitString(representation.second);
+}
+
+void HpackEncoder::EmitLiteral(const Representation& representation) {
+ const HpackEntry* name_entry = header_table_.GetByName(representation.first);
+ if (name_entry != nullptr) {
+ output_stream_.AppendUint32(header_table_.IndexOf(name_entry));
+ } else {
+ output_stream_.AppendUint32(0);
+ EmitString(representation.first);
+ }
+ EmitString(representation.second);
+}
+
+void HpackEncoder::EmitString(SpdyStringPiece str) {
+ size_t encoded_size =
+ enable_compression_ ? huffman_table_.EncodedSize(str) : str.size();
+ if (encoded_size < str.size()) {
+ DVLOG(2) << "Emitted Huffman-encoded string of length " << encoded_size;
+ output_stream_.AppendPrefix(kStringLiteralHuffmanEncoded);
+ output_stream_.AppendUint32(encoded_size);
+ huffman_table_.EncodeString(str, &output_stream_);
+ } else {
+ DVLOG(2) << "Emitted literal string of length " << str.size();
+ output_stream_.AppendPrefix(kStringLiteralIdentityEncoded);
+ output_stream_.AppendUint32(str.size());
+ output_stream_.AppendBytes(str);
+ }
+}
+
+void HpackEncoder::MaybeEmitTableSize() {
+ if (!should_emit_table_size_) {
+ return;
+ }
+ const size_t current_size = CurrentHeaderTableSizeSetting();
+ DVLOG(1) << "MaybeEmitTableSize current_size=" << current_size;
+ DVLOG(1) << "MaybeEmitTableSize min_table_size_setting_received_="
+ << min_table_size_setting_received_;
+ if (min_table_size_setting_received_ < current_size) {
+ output_stream_.AppendPrefix(kHeaderTableSizeUpdateOpcode);
+ output_stream_.AppendUint32(min_table_size_setting_received_);
+ }
+ output_stream_.AppendPrefix(kHeaderTableSizeUpdateOpcode);
+ output_stream_.AppendUint32(current_size);
+ min_table_size_setting_received_ = std::numeric_limits<size_t>::max();
+ should_emit_table_size_ = false;
+}
+
+// static
+void HpackEncoder::CookieToCrumbs(const Representation& cookie,
+ Representations* out) {
+ // See Section 8.1.2.5. "Compressing the Cookie Header Field" in the HTTP/2
+ // specification at https://tools.ietf.org/html/draft-ietf-httpbis-http2-14.
+ // Cookie values are split into individually-encoded HPACK representations.
+ SpdyStringPiece cookie_value = cookie.second;
+ // Consume leading and trailing whitespace if present.
+ SpdyStringPiece::size_type first = cookie_value.find_first_not_of(" \t");
+ SpdyStringPiece::size_type last = cookie_value.find_last_not_of(" \t");
+ if (first == SpdyStringPiece::npos) {
+ cookie_value = SpdyStringPiece();
+ } else {
+ cookie_value = cookie_value.substr(first, (last - first) + 1);
+ }
+ for (size_t pos = 0;;) {
+ size_t end = cookie_value.find(";", pos);
+
+ if (end == SpdyStringPiece::npos) {
+ out->push_back(std::make_pair(cookie.first, cookie_value.substr(pos)));
+ break;
+ }
+ out->push_back(
+ std::make_pair(cookie.first, cookie_value.substr(pos, end - pos)));
+
+ // Consume next space if present.
+ pos = end + 1;
+ if (pos != cookie_value.size() && cookie_value[pos] == ' ') {
+ pos++;
+ }
+ }
+}
+
+// static
+void HpackEncoder::DecomposeRepresentation(const Representation& header_field,
+ Representations* out) {
+ size_t pos = 0;
+ size_t end = 0;
+ while (end != SpdyStringPiece::npos) {
+ end = header_field.second.find('\0', pos);
+ out->push_back(std::make_pair(
+ header_field.first,
+ header_field.second.substr(
+ pos, end == SpdyStringPiece::npos ? end : end - pos)));
+ pos = end + 1;
+ }
+}
+
+// static
+void HpackEncoder::GatherRepresentation(const Representation& header_field,
+ Representations* out) {
+ out->push_back(std::make_pair(header_field.first, header_field.second));
+}
+
+// Iteratively encodes a SpdyHeaderBlock.
+class HpackEncoder::Encoderator : public ProgressiveEncoder {
+ public:
+ Encoderator(const SpdyHeaderBlock& header_set, HpackEncoder* encoder);
+
+ // Encoderator is neither copyable nor movable.
+ Encoderator(const Encoderator&) = delete;
+ Encoderator& operator=(const Encoderator&) = delete;
+
+ // Returns true iff more remains to encode.
+ bool HasNext() const override { return has_next_; }
+
+ // Encodes up to max_encoded_bytes of the current header block into the
+ // given output string.
+ void Next(size_t max_encoded_bytes, SpdyString* output) override;
+
+ private:
+ HpackEncoder* encoder_;
+ std::unique_ptr<RepresentationIterator> header_it_;
+ Representations pseudo_headers_;
+ Representations regular_headers_;
+ bool has_next_;
+};
+
+HpackEncoder::Encoderator::Encoderator(const SpdyHeaderBlock& header_set,
+ HpackEncoder* encoder)
+ : encoder_(encoder), has_next_(true) {
+ // Separate header set into pseudo-headers and regular headers.
+ const bool use_compression = encoder_->enable_compression_;
+ bool found_cookie = false;
+ for (const auto& header : header_set) {
+ if (!found_cookie && header.first == "cookie") {
+ // Note that there can only be one "cookie" header, because header_set
+ // is a map.
+ found_cookie = true;
+ CookieToCrumbs(header, &regular_headers_);
+ } else if (!header.first.empty() &&
+ header.first[0] == kPseudoHeaderPrefix) {
+ use_compression ? DecomposeRepresentation(header, &pseudo_headers_)
+ : GatherRepresentation(header, &pseudo_headers_);
+ } else {
+ use_compression ? DecomposeRepresentation(header, &regular_headers_)
+ : GatherRepresentation(header, &regular_headers_);
+ }
+ }
+ header_it_ =
+ SpdyMakeUnique<RepresentationIterator>(pseudo_headers_, regular_headers_);
+
+ encoder_->MaybeEmitTableSize();
+}
+
+void HpackEncoder::Encoderator::Next(size_t max_encoded_bytes,
+ SpdyString* output) {
+ SPDY_BUG_IF(!has_next_)
+ << "Encoderator::Next called with nothing left to encode.";
+ const bool use_compression = encoder_->enable_compression_;
+
+ // Encode up to max_encoded_bytes of headers.
+ while (header_it_->HasNext() &&
+ encoder_->output_stream_.size() <= max_encoded_bytes) {
+ const Representation header = header_it_->Next();
+ encoder_->listener_(header.first, header.second);
+ if (use_compression) {
+ const HpackEntry* entry = encoder_->header_table_.GetByNameAndValue(
+ header.first, header.second);
+ if (entry != nullptr) {
+ encoder_->EmitIndex(entry);
+ } else if (encoder_->should_index_(header.first, header.second)) {
+ encoder_->EmitIndexedLiteral(header);
+ } else {
+ encoder_->EmitNonIndexedLiteral(header);
+ }
+ } else {
+ encoder_->EmitNonIndexedLiteral(header);
+ }
+ }
+
+ has_next_ = encoder_->output_stream_.size() > max_encoded_bytes;
+ encoder_->output_stream_.BoundedTakeString(max_encoded_bytes, output);
+}
+
+std::unique_ptr<HpackEncoder::ProgressiveEncoder> HpackEncoder::EncodeHeaderSet(
+ const SpdyHeaderBlock& header_set) {
+ return SpdyMakeUnique<Encoderator>(header_set, this);
+}
+
+} // namespace spdy
diff --git a/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_encoder.h b/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_encoder.h
new file mode 100644
index 00000000000..486d53693e2
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_encoder.h
@@ -0,0 +1,152 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_SPDY_CORE_HPACK_HPACK_ENCODER_H_
+#define QUICHE_SPDY_CORE_HPACK_HPACK_ENCODER_H_
+
+#include <stddef.h>
+
+#include <functional>
+#include <map>
+#include <memory>
+#include <utility>
+#include <vector>
+
+#include "base/macros.h"
+#include "net/third_party/quiche/src/spdy/core/hpack/hpack_header_table.h"
+#include "net/third_party/quiche/src/spdy/core/hpack/hpack_output_stream.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_protocol.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_export.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_string.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_string_piece.h"
+
+// An HpackEncoder encodes header sets as outlined in
+// http://tools.ietf.org/html/rfc7541.
+
+namespace spdy {
+
+class HpackHuffmanTable;
+
+namespace test {
+class HpackEncoderPeer;
+} // namespace test
+
+class SPDY_EXPORT_PRIVATE HpackEncoder {
+ public:
+ using Representation = std::pair<SpdyStringPiece, SpdyStringPiece>;
+ using Representations = std::vector<Representation>;
+
+ // Callers may provide a HeaderListener to be informed of header name-value
+ // pairs processed by this encoder.
+ using HeaderListener = std::function<void(SpdyStringPiece, SpdyStringPiece)>;
+
+ // An indexing policy should return true if the provided header name-value
+ // pair should be inserted into the HPACK dynamic table.
+ using IndexingPolicy = std::function<bool(SpdyStringPiece, SpdyStringPiece)>;
+
+ // |table| is an initialized HPACK Huffman table, having an
+ // externally-managed lifetime which spans beyond HpackEncoder.
+ explicit HpackEncoder(const HpackHuffmanTable& table);
+ HpackEncoder(const HpackEncoder&) = delete;
+ HpackEncoder& operator=(const HpackEncoder&) = delete;
+ ~HpackEncoder();
+
+ // Encodes the given header set into the given string. Returns
+ // whether or not the encoding was successful.
+ bool EncodeHeaderSet(const SpdyHeaderBlock& header_set, SpdyString* output);
+
+ class SPDY_EXPORT_PRIVATE ProgressiveEncoder {
+ public:
+ virtual ~ProgressiveEncoder() {}
+
+ // Returns true iff more remains to encode.
+ virtual bool HasNext() const = 0;
+
+ // Encodes up to max_encoded_bytes of the current header block into the
+ // given output string.
+ virtual void Next(size_t max_encoded_bytes, SpdyString* output) = 0;
+ };
+
+ // Returns a ProgressiveEncoder which must be outlived by both the given
+ // SpdyHeaderBlock and this object.
+ std::unique_ptr<ProgressiveEncoder> EncodeHeaderSet(
+ const SpdyHeaderBlock& header_set);
+
+ // Called upon a change to SETTINGS_HEADER_TABLE_SIZE. Specifically, this
+ // is to be called after receiving (and sending an acknowledgement for) a
+ // SETTINGS_HEADER_TABLE_SIZE update from the remote decoding endpoint.
+ void ApplyHeaderTableSizeSetting(size_t size_setting);
+
+ size_t CurrentHeaderTableSizeSetting() const {
+ return header_table_.settings_size_bound();
+ }
+
+ // This HpackEncoder will use |policy| to determine whether to insert header
+ // name-value pairs into the dynamic table.
+ void SetIndexingPolicy(IndexingPolicy policy) { should_index_ = policy; }
+
+ // |listener| will be invoked for each header name-value pair processed by
+ // this encoder.
+ void SetHeaderListener(HeaderListener listener) { listener_ = listener; }
+
+ void SetHeaderTableDebugVisitor(
+ std::unique_ptr<HpackHeaderTable::DebugVisitorInterface> visitor) {
+ header_table_.set_debug_visitor(std::move(visitor));
+ }
+
+ void DisableCompression() { enable_compression_ = false; }
+
+ // Returns the estimate of dynamically allocated memory in bytes.
+ size_t EstimateMemoryUsage() const;
+
+ private:
+ friend class test::HpackEncoderPeer;
+
+ class RepresentationIterator;
+ class Encoderator;
+
+ // Encodes a sequence of header name-value pairs as a single header block.
+ void EncodeRepresentations(RepresentationIterator* iter, SpdyString* output);
+
+ // Emits a static/dynamic indexed representation (Section 7.1).
+ void EmitIndex(const HpackEntry* entry);
+
+ // Emits a literal representation (Section 7.2).
+ void EmitIndexedLiteral(const Representation& representation);
+ void EmitNonIndexedLiteral(const Representation& representation);
+ void EmitLiteral(const Representation& representation);
+
+ // Emits a Huffman or identity string (whichever is smaller).
+ void EmitString(SpdyStringPiece str);
+
+ // Emits the current dynamic table size if the table size was recently
+ // updated and we have not yet emitted it (Section 6.3).
+ void MaybeEmitTableSize();
+
+ // Crumbles a cookie header into ";" delimited crumbs.
+ static void CookieToCrumbs(const Representation& cookie,
+ Representations* crumbs_out);
+
+ // Crumbles other header field values at \0 delimiters.
+ static void DecomposeRepresentation(const Representation& header_field,
+ Representations* out);
+
+ // Gathers headers without crumbling. Used when compression is not enabled.
+ static void GatherRepresentation(const Representation& header_field,
+ Representations* out);
+
+ HpackHeaderTable header_table_;
+ HpackOutputStream output_stream_;
+
+ const HpackHuffmanTable& huffman_table_;
+ size_t min_table_size_setting_received_;
+ HeaderListener listener_;
+ IndexingPolicy should_index_;
+ bool enable_compression_;
+ bool should_emit_table_size_;
+};
+
+} // namespace spdy
+
+#endif // QUICHE_SPDY_CORE_HPACK_HPACK_ENCODER_H_
diff --git a/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_encoder_test.cc b/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_encoder_test.cc
new file mode 100644
index 00000000000..8ead1c278b1
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_encoder_test.cc
@@ -0,0 +1,586 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/spdy/core/hpack/hpack_encoder.h"
+
+#include <cstdint>
+#include <map>
+
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/http2/test_tools/http2_random.h"
+#include "net/third_party/quiche/src/spdy/core/hpack/hpack_huffman_table.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_unsafe_arena.h"
+
+namespace spdy {
+
+namespace test {
+
+class HpackHeaderTablePeer {
+ public:
+ explicit HpackHeaderTablePeer(HpackHeaderTable* table) : table_(table) {}
+
+ HpackHeaderTable::EntryTable* dynamic_entries() {
+ return &table_->dynamic_entries_;
+ }
+
+ private:
+ HpackHeaderTable* table_;
+};
+
+class HpackEncoderPeer {
+ public:
+ typedef HpackEncoder::Representation Representation;
+ typedef HpackEncoder::Representations Representations;
+
+ explicit HpackEncoderPeer(HpackEncoder* encoder) : encoder_(encoder) {}
+
+ bool compression_enabled() const { return encoder_->enable_compression_; }
+ HpackHeaderTable* table() { return &encoder_->header_table_; }
+ HpackHeaderTablePeer table_peer() { return HpackHeaderTablePeer(table()); }
+ const HpackHuffmanTable& huffman_table() const {
+ return encoder_->huffman_table_;
+ }
+ void EmitString(SpdyStringPiece str) { encoder_->EmitString(str); }
+ void TakeString(SpdyString* out) { encoder_->output_stream_.TakeString(out); }
+ static void CookieToCrumbs(SpdyStringPiece cookie,
+ std::vector<SpdyStringPiece>* out) {
+ Representations tmp;
+ HpackEncoder::CookieToCrumbs(std::make_pair("", cookie), &tmp);
+
+ out->clear();
+ for (size_t i = 0; i != tmp.size(); ++i) {
+ out->push_back(tmp[i].second);
+ }
+ }
+ static void DecomposeRepresentation(SpdyStringPiece value,
+ std::vector<SpdyStringPiece>* out) {
+ Representations tmp;
+ HpackEncoder::DecomposeRepresentation(std::make_pair("foobar", value),
+ &tmp);
+
+ out->clear();
+ for (size_t i = 0; i != tmp.size(); ++i) {
+ out->push_back(tmp[i].second);
+ }
+ }
+
+ // TODO(dahollings): Remove or clean up these methods when deprecating
+ // non-incremental encoding path.
+ static bool EncodeHeaderSet(HpackEncoder* encoder,
+ const SpdyHeaderBlock& header_set,
+ SpdyString* output,
+ bool use_incremental) {
+ if (use_incremental) {
+ return EncodeIncremental(encoder, header_set, output);
+ } else {
+ return encoder->EncodeHeaderSet(header_set, output);
+ }
+ }
+
+ static bool EncodeIncremental(HpackEncoder* encoder,
+ const SpdyHeaderBlock& header_set,
+ SpdyString* output) {
+ std::unique_ptr<HpackEncoder::ProgressiveEncoder> encoderator =
+ encoder->EncodeHeaderSet(header_set);
+ SpdyString output_buffer;
+ http2::test::Http2Random random;
+ encoderator->Next(random.UniformInRange(0, 16), &output_buffer);
+ while (encoderator->HasNext()) {
+ SpdyString second_buffer;
+ encoderator->Next(random.UniformInRange(0, 16), &second_buffer);
+ output_buffer.append(second_buffer);
+ }
+ *output = std::move(output_buffer);
+ return true;
+ }
+
+ private:
+ HpackEncoder* encoder_;
+};
+
+} // namespace test
+
+namespace {
+
+using testing::ElementsAre;
+using testing::Pair;
+
+class HpackEncoderTest : public ::testing::TestWithParam<bool> {
+ protected:
+ typedef test::HpackEncoderPeer::Representations Representations;
+
+ HpackEncoderTest()
+ : encoder_(ObtainHpackHuffmanTable()),
+ peer_(&encoder_),
+ static_(peer_.table()->GetByIndex(1)),
+ headers_storage_(1024 /* block size */) {}
+
+ void SetUp() override {
+ use_incremental_ = GetParam();
+
+ // Populate dynamic entries into the table fixture. For simplicity each
+ // entry has name.size() + value.size() == 10.
+ key_1_ = peer_.table()->TryAddEntry("key1", "value1");
+ key_2_ = peer_.table()->TryAddEntry("key2", "value2");
+ cookie_a_ = peer_.table()->TryAddEntry("cookie", "a=bb");
+ cookie_c_ = peer_.table()->TryAddEntry("cookie", "c=dd");
+
+ // No further insertions may occur without evictions.
+ peer_.table()->SetMaxSize(peer_.table()->size());
+ }
+
+ void SaveHeaders(SpdyStringPiece name, SpdyStringPiece value) {
+ SpdyStringPiece n(headers_storage_.Memdup(name.data(), name.size()),
+ name.size());
+ SpdyStringPiece v(headers_storage_.Memdup(value.data(), value.size()),
+ value.size());
+ headers_observed_.push_back(std::make_pair(n, v));
+ }
+
+ void ExpectIndex(size_t index) {
+ expected_.AppendPrefix(kIndexedOpcode);
+ expected_.AppendUint32(index);
+ }
+ void ExpectIndexedLiteral(const HpackEntry* key_entry,
+ SpdyStringPiece value) {
+ expected_.AppendPrefix(kLiteralIncrementalIndexOpcode);
+ expected_.AppendUint32(IndexOf(key_entry));
+ ExpectString(&expected_, value);
+ }
+ void ExpectIndexedLiteral(SpdyStringPiece name, SpdyStringPiece value) {
+ expected_.AppendPrefix(kLiteralIncrementalIndexOpcode);
+ expected_.AppendUint32(0);
+ ExpectString(&expected_, name);
+ ExpectString(&expected_, value);
+ }
+ void ExpectNonIndexedLiteral(SpdyStringPiece name, SpdyStringPiece value) {
+ expected_.AppendPrefix(kLiteralNoIndexOpcode);
+ expected_.AppendUint32(0);
+ ExpectString(&expected_, name);
+ ExpectString(&expected_, value);
+ }
+ void ExpectString(HpackOutputStream* stream, SpdyStringPiece str) {
+ const HpackHuffmanTable& huffman_table = peer_.huffman_table();
+ size_t encoded_size = peer_.compression_enabled()
+ ? huffman_table.EncodedSize(str)
+ : str.size();
+ if (encoded_size < str.size()) {
+ expected_.AppendPrefix(kStringLiteralHuffmanEncoded);
+ expected_.AppendUint32(encoded_size);
+ huffman_table.EncodeString(str, stream);
+ } else {
+ expected_.AppendPrefix(kStringLiteralIdentityEncoded);
+ expected_.AppendUint32(str.size());
+ expected_.AppendBytes(str);
+ }
+ }
+ void ExpectHeaderTableSizeUpdate(uint32_t size) {
+ expected_.AppendPrefix(kHeaderTableSizeUpdateOpcode);
+ expected_.AppendUint32(size);
+ }
+ void CompareWithExpectedEncoding(const SpdyHeaderBlock& header_set) {
+ SpdyString expected_out, actual_out;
+ expected_.TakeString(&expected_out);
+ EXPECT_TRUE(test::HpackEncoderPeer::EncodeHeaderSet(
+ &encoder_, header_set, &actual_out, use_incremental_));
+ EXPECT_EQ(expected_out, actual_out);
+ }
+ size_t IndexOf(const HpackEntry* entry) {
+ return peer_.table()->IndexOf(entry);
+ }
+
+ HpackEncoder encoder_;
+ test::HpackEncoderPeer peer_;
+
+ const HpackEntry* static_;
+ const HpackEntry* key_1_;
+ const HpackEntry* key_2_;
+ const HpackEntry* cookie_a_;
+ const HpackEntry* cookie_c_;
+
+ SpdyUnsafeArena headers_storage_;
+ std::vector<std::pair<SpdyStringPiece, SpdyStringPiece>> headers_observed_;
+
+ HpackOutputStream expected_;
+ bool use_incremental_;
+};
+
+INSTANTIATE_TEST_CASE_P(HpackEncoderTests, HpackEncoderTest, ::testing::Bool());
+
+TEST_P(HpackEncoderTest, SingleDynamicIndex) {
+ encoder_.SetHeaderListener(
+ [this](SpdyStringPiece name, SpdyStringPiece value) {
+ this->SaveHeaders(name, value);
+ });
+
+ ExpectIndex(IndexOf(key_2_));
+
+ SpdyHeaderBlock headers;
+ headers[key_2_->name()] = key_2_->value();
+ CompareWithExpectedEncoding(headers);
+ EXPECT_THAT(headers_observed_,
+ ElementsAre(Pair(key_2_->name(), key_2_->value())));
+}
+
+TEST_P(HpackEncoderTest, SingleStaticIndex) {
+ ExpectIndex(IndexOf(static_));
+
+ SpdyHeaderBlock headers;
+ headers[static_->name()] = static_->value();
+ CompareWithExpectedEncoding(headers);
+}
+
+TEST_P(HpackEncoderTest, SingleStaticIndexTooLarge) {
+ peer_.table()->SetMaxSize(1); // Also evicts all fixtures.
+ ExpectIndex(IndexOf(static_));
+
+ SpdyHeaderBlock headers;
+ headers[static_->name()] = static_->value();
+ CompareWithExpectedEncoding(headers);
+
+ EXPECT_EQ(0u, peer_.table_peer().dynamic_entries()->size());
+}
+
+TEST_P(HpackEncoderTest, SingleLiteralWithIndexName) {
+ ExpectIndexedLiteral(key_2_, "value3");
+
+ SpdyHeaderBlock headers;
+ headers[key_2_->name()] = "value3";
+ CompareWithExpectedEncoding(headers);
+
+ // A new entry was inserted and added to the reference set.
+ HpackEntry* new_entry = &peer_.table_peer().dynamic_entries()->front();
+ EXPECT_EQ(new_entry->name(), key_2_->name());
+ EXPECT_EQ(new_entry->value(), "value3");
+}
+
+TEST_P(HpackEncoderTest, SingleLiteralWithLiteralName) {
+ ExpectIndexedLiteral("key3", "value3");
+
+ SpdyHeaderBlock headers;
+ headers["key3"] = "value3";
+ CompareWithExpectedEncoding(headers);
+
+ HpackEntry* new_entry = &peer_.table_peer().dynamic_entries()->front();
+ EXPECT_EQ(new_entry->name(), "key3");
+ EXPECT_EQ(new_entry->value(), "value3");
+}
+
+TEST_P(HpackEncoderTest, SingleLiteralTooLarge) {
+ peer_.table()->SetMaxSize(1); // Also evicts all fixtures.
+
+ ExpectIndexedLiteral("key3", "value3");
+
+ // A header overflowing the header table is still emitted.
+ // The header table is empty.
+ SpdyHeaderBlock headers;
+ headers["key3"] = "value3";
+ CompareWithExpectedEncoding(headers);
+
+ EXPECT_EQ(0u, peer_.table_peer().dynamic_entries()->size());
+}
+
+TEST_P(HpackEncoderTest, EmitThanEvict) {
+ // |key_1_| is toggled and placed into the reference set,
+ // and then immediately evicted by "key3".
+ ExpectIndex(IndexOf(key_1_));
+ ExpectIndexedLiteral("key3", "value3");
+
+ SpdyHeaderBlock headers;
+ headers[key_1_->name()] = key_1_->value();
+ headers["key3"] = "value3";
+ CompareWithExpectedEncoding(headers);
+}
+
+TEST_P(HpackEncoderTest, CookieHeaderIsCrumbled) {
+ ExpectIndex(IndexOf(cookie_a_));
+ ExpectIndex(IndexOf(cookie_c_));
+ ExpectIndexedLiteral(peer_.table()->GetByName("cookie"), "e=ff");
+
+ SpdyHeaderBlock headers;
+ headers["cookie"] = "a=bb; c=dd; e=ff";
+ CompareWithExpectedEncoding(headers);
+}
+
+TEST_P(HpackEncoderTest, StringsDynamicallySelectHuffmanCoding) {
+ // Compactable string. Uses Huffman coding.
+ peer_.EmitString("feedbeef");
+ expected_.AppendPrefix(kStringLiteralHuffmanEncoded);
+ expected_.AppendUint32(6);
+ expected_.AppendBytes("\x94\xA5\x92\x32\x96_");
+
+ // Non-compactable. Uses identity coding.
+ peer_.EmitString("@@@@@@");
+ expected_.AppendPrefix(kStringLiteralIdentityEncoded);
+ expected_.AppendUint32(6);
+ expected_.AppendBytes("@@@@@@");
+
+ SpdyString expected_out, actual_out;
+ expected_.TakeString(&expected_out);
+ peer_.TakeString(&actual_out);
+ EXPECT_EQ(expected_out, actual_out);
+}
+
+TEST_P(HpackEncoderTest, EncodingWithoutCompression) {
+ encoder_.SetHeaderListener(
+ [this](SpdyStringPiece name, SpdyStringPiece value) {
+ this->SaveHeaders(name, value);
+ });
+ encoder_.DisableCompression();
+
+ ExpectNonIndexedLiteral(":path", "/index.html");
+ ExpectNonIndexedLiteral("cookie", "foo=bar");
+ ExpectNonIndexedLiteral("cookie", "baz=bing");
+ ExpectNonIndexedLiteral("hello", "goodbye");
+
+ SpdyHeaderBlock headers;
+ headers[":path"] = "/index.html";
+ headers["cookie"] = "foo=bar; baz=bing";
+ headers["hello"] = "goodbye";
+
+ CompareWithExpectedEncoding(headers);
+
+ EXPECT_THAT(
+ headers_observed_,
+ ElementsAre(Pair(":path", "/index.html"), Pair("cookie", "foo=bar"),
+ Pair("cookie", "baz=bing"), Pair("hello", "goodbye")));
+}
+
+TEST_P(HpackEncoderTest, MultipleEncodingPasses) {
+ encoder_.SetHeaderListener(
+ [this](SpdyStringPiece name, SpdyStringPiece value) {
+ this->SaveHeaders(name, value);
+ });
+
+ // Pass 1.
+ {
+ SpdyHeaderBlock headers;
+ headers["key1"] = "value1";
+ headers["cookie"] = "a=bb";
+
+ ExpectIndex(IndexOf(key_1_));
+ ExpectIndex(IndexOf(cookie_a_));
+ CompareWithExpectedEncoding(headers);
+ }
+ // Header table is:
+ // 65: key1: value1
+ // 64: key2: value2
+ // 63: cookie: a=bb
+ // 62: cookie: c=dd
+ // Pass 2.
+ {
+ SpdyHeaderBlock headers;
+ headers["key2"] = "value2";
+ headers["cookie"] = "c=dd; e=ff";
+
+ // "key2: value2"
+ ExpectIndex(64);
+ // "cookie: c=dd"
+ ExpectIndex(62);
+ // This cookie evicts |key1| from the dynamic table.
+ ExpectIndexedLiteral(peer_.table()->GetByName("cookie"), "e=ff");
+
+ CompareWithExpectedEncoding(headers);
+ }
+ // Header table is:
+ // 65: key2: value2
+ // 64: cookie: a=bb
+ // 63: cookie: c=dd
+ // 62: cookie: e=ff
+ // Pass 3.
+ {
+ SpdyHeaderBlock headers;
+ headers["key2"] = "value2";
+ headers["cookie"] = "a=bb; b=cc; c=dd";
+
+ // "key2: value2"
+ ExpectIndex(65);
+ // "cookie: a=bb"
+ ExpectIndex(64);
+ // This cookie evicts |key2| from the dynamic table.
+ ExpectIndexedLiteral(peer_.table()->GetByName("cookie"), "b=cc");
+ // "cookie: c=dd"
+ ExpectIndex(64);
+
+ CompareWithExpectedEncoding(headers);
+ }
+
+ // clang-format off
+ EXPECT_THAT(headers_observed_,
+ ElementsAre(Pair("key1", "value1"),
+ Pair("cookie", "a=bb"),
+ Pair("key2", "value2"),
+ Pair("cookie", "c=dd"),
+ Pair("cookie", "e=ff"),
+ Pair("key2", "value2"),
+ Pair("cookie", "a=bb"),
+ Pair("cookie", "b=cc"),
+ Pair("cookie", "c=dd")));
+ // clang-format on
+}
+
+TEST_P(HpackEncoderTest, PseudoHeadersFirst) {
+ SpdyHeaderBlock headers;
+ // A pseudo-header that should not be indexed.
+ headers[":path"] = "/spam/eggs.html";
+ // A pseudo-header to be indexed.
+ headers[":authority"] = "www.example.com";
+ // A regular header which precedes ":" alphabetically, should still be encoded
+ // after pseudo-headers.
+ headers["-foo"] = "bar";
+ headers["foo"] = "bar";
+ headers["cookie"] = "c=dd";
+
+ // Headers are indexed in the order in which they were added.
+ // This entry pushes "cookie: a=bb" back to 63.
+ ExpectNonIndexedLiteral(":path", "/spam/eggs.html");
+ ExpectIndexedLiteral(peer_.table()->GetByName(":authority"),
+ "www.example.com");
+ ExpectIndexedLiteral("-foo", "bar");
+ ExpectIndexedLiteral("foo", "bar");
+ ExpectIndexedLiteral(peer_.table()->GetByName("cookie"), "c=dd");
+ CompareWithExpectedEncoding(headers);
+}
+
+TEST_P(HpackEncoderTest, CookieToCrumbs) {
+ test::HpackEncoderPeer peer(nullptr);
+ std::vector<SpdyStringPiece> out;
+
+ // Leading and trailing whitespace is consumed. A space after ';' is consumed.
+ // All other spaces remain. ';' at beginning and end of string produce empty
+ // crumbs.
+ // See section 8.1.3.4 "Compressing the Cookie Header Field" in the HTTP/2
+ // specification at http://tools.ietf.org/html/draft-ietf-httpbis-http2-11
+ peer.CookieToCrumbs(" foo=1;bar=2 ; bar=3; bing=4; ", &out);
+ EXPECT_THAT(out, ElementsAre("foo=1", "bar=2 ", "bar=3", " bing=4", ""));
+
+ peer.CookieToCrumbs(";;foo = bar ;; ;baz =bing", &out);
+ EXPECT_THAT(out, ElementsAre("", "", "foo = bar ", "", "", "baz =bing"));
+
+ peer.CookieToCrumbs("baz=bing; foo=bar; baz=bing", &out);
+ EXPECT_THAT(out, ElementsAre("baz=bing", "foo=bar", "baz=bing"));
+
+ peer.CookieToCrumbs("baz=bing", &out);
+ EXPECT_THAT(out, ElementsAre("baz=bing"));
+
+ peer.CookieToCrumbs("", &out);
+ EXPECT_THAT(out, ElementsAre(""));
+
+ peer.CookieToCrumbs("foo;bar; baz;baz;bing;", &out);
+ EXPECT_THAT(out, ElementsAre("foo", "bar", "baz", "baz", "bing", ""));
+
+ peer.CookieToCrumbs(" \t foo=1;bar=2 ; bar=3;\t ", &out);
+ EXPECT_THAT(out, ElementsAre("foo=1", "bar=2 ", "bar=3", ""));
+
+ peer.CookieToCrumbs(" \t foo=1;bar=2 ; bar=3 \t ", &out);
+ EXPECT_THAT(out, ElementsAre("foo=1", "bar=2 ", "bar=3"));
+}
+
+TEST_P(HpackEncoderTest, DecomposeRepresentation) {
+ test::HpackEncoderPeer peer(nullptr);
+ std::vector<SpdyStringPiece> out;
+
+ peer.DecomposeRepresentation("", &out);
+ EXPECT_THAT(out, ElementsAre(""));
+
+ peer.DecomposeRepresentation("foobar", &out);
+ EXPECT_THAT(out, ElementsAre("foobar"));
+
+ peer.DecomposeRepresentation(SpdyStringPiece("foo\0bar", 7), &out);
+ EXPECT_THAT(out, ElementsAre("foo", "bar"));
+
+ peer.DecomposeRepresentation(SpdyStringPiece("\0foo\0bar", 8), &out);
+ EXPECT_THAT(out, ElementsAre("", "foo", "bar"));
+
+ peer.DecomposeRepresentation(SpdyStringPiece("foo\0bar\0", 8), &out);
+ EXPECT_THAT(out, ElementsAre("foo", "bar", ""));
+
+ peer.DecomposeRepresentation(SpdyStringPiece("\0foo\0bar\0", 9), &out);
+ EXPECT_THAT(out, ElementsAre("", "foo", "bar", ""));
+}
+
+// Test that encoded headers do not have \0-delimited multiple values, as this
+// became disallowed in HTTP/2 draft-14.
+TEST_P(HpackEncoderTest, CrumbleNullByteDelimitedValue) {
+ SpdyHeaderBlock headers;
+ // A header field to be crumbled: "spam: foo\0bar".
+ headers["spam"] = SpdyString("foo\0bar", 7);
+
+ ExpectIndexedLiteral("spam", "foo");
+ expected_.AppendPrefix(kLiteralIncrementalIndexOpcode);
+ expected_.AppendUint32(62);
+ expected_.AppendPrefix(kStringLiteralIdentityEncoded);
+ expected_.AppendUint32(3);
+ expected_.AppendBytes("bar");
+ CompareWithExpectedEncoding(headers);
+}
+
+TEST_P(HpackEncoderTest, HeaderTableSizeUpdate) {
+ encoder_.ApplyHeaderTableSizeSetting(1024);
+ ExpectHeaderTableSizeUpdate(1024);
+ ExpectIndexedLiteral("key3", "value3");
+
+ SpdyHeaderBlock headers;
+ headers["key3"] = "value3";
+ CompareWithExpectedEncoding(headers);
+
+ HpackEntry* new_entry = &peer_.table_peer().dynamic_entries()->front();
+ EXPECT_EQ(new_entry->name(), "key3");
+ EXPECT_EQ(new_entry->value(), "value3");
+}
+
+TEST_P(HpackEncoderTest, HeaderTableSizeUpdateWithMin) {
+ const size_t starting_size = peer_.table()->settings_size_bound();
+ encoder_.ApplyHeaderTableSizeSetting(starting_size - 2);
+ encoder_.ApplyHeaderTableSizeSetting(starting_size - 1);
+ // We must encode the low watermark, so the peer knows to evict entries
+ // if necessary.
+ ExpectHeaderTableSizeUpdate(starting_size - 2);
+ ExpectHeaderTableSizeUpdate(starting_size - 1);
+ ExpectIndexedLiteral("key3", "value3");
+
+ SpdyHeaderBlock headers;
+ headers["key3"] = "value3";
+ CompareWithExpectedEncoding(headers);
+
+ HpackEntry* new_entry = &peer_.table_peer().dynamic_entries()->front();
+ EXPECT_EQ(new_entry->name(), "key3");
+ EXPECT_EQ(new_entry->value(), "value3");
+}
+
+TEST_P(HpackEncoderTest, HeaderTableSizeUpdateWithExistingSize) {
+ encoder_.ApplyHeaderTableSizeSetting(peer_.table()->settings_size_bound());
+ // No encoded size update.
+ ExpectIndexedLiteral("key3", "value3");
+
+ SpdyHeaderBlock headers;
+ headers["key3"] = "value3";
+ CompareWithExpectedEncoding(headers);
+
+ HpackEntry* new_entry = &peer_.table_peer().dynamic_entries()->front();
+ EXPECT_EQ(new_entry->name(), "key3");
+ EXPECT_EQ(new_entry->value(), "value3");
+}
+
+TEST_P(HpackEncoderTest, HeaderTableSizeUpdatesWithGreaterSize) {
+ const size_t starting_size = peer_.table()->settings_size_bound();
+ encoder_.ApplyHeaderTableSizeSetting(starting_size + 1);
+ encoder_.ApplyHeaderTableSizeSetting(starting_size + 2);
+ // Only a single size update to the final size.
+ ExpectHeaderTableSizeUpdate(starting_size + 2);
+ ExpectIndexedLiteral("key3", "value3");
+
+ SpdyHeaderBlock headers;
+ headers["key3"] = "value3";
+ CompareWithExpectedEncoding(headers);
+
+ HpackEntry* new_entry = &peer_.table_peer().dynamic_entries()->front();
+ EXPECT_EQ(new_entry->name(), "key3");
+ EXPECT_EQ(new_entry->value(), "value3");
+}
+
+} // namespace
+
+} // namespace spdy
diff --git a/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_entry.cc b/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_entry.cc
new file mode 100644
index 00000000000..90d53e65d73
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_entry.cc
@@ -0,0 +1,89 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/spdy/core/hpack/hpack_entry.h"
+
+#include "base/logging.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_estimate_memory_usage.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_string_utils.h"
+
+namespace spdy {
+
+const size_t HpackEntry::kSizeOverhead = 32;
+
+HpackEntry::HpackEntry(SpdyStringPiece name,
+ SpdyStringPiece value,
+ bool is_static,
+ size_t insertion_index)
+ : name_(name.data(), name.size()),
+ value_(value.data(), value.size()),
+ name_ref_(name_),
+ value_ref_(value_),
+ insertion_index_(insertion_index),
+ type_(is_static ? STATIC : DYNAMIC),
+ time_added_(0) {}
+
+HpackEntry::HpackEntry(SpdyStringPiece name, SpdyStringPiece value)
+ : name_ref_(name),
+ value_ref_(value),
+ insertion_index_(0),
+ type_(LOOKUP),
+ time_added_(0) {}
+
+HpackEntry::HpackEntry() : insertion_index_(0), type_(LOOKUP), time_added_(0) {}
+
+HpackEntry::HpackEntry(const HpackEntry& other)
+ : insertion_index_(other.insertion_index_),
+ type_(other.type_),
+ time_added_(0) {
+ if (type_ == LOOKUP) {
+ name_ref_ = other.name_ref_;
+ value_ref_ = other.value_ref_;
+ } else {
+ name_ = other.name_;
+ value_ = other.value_;
+ name_ref_ = SpdyStringPiece(name_.data(), name_.size());
+ value_ref_ = SpdyStringPiece(value_.data(), value_.size());
+ }
+}
+
+HpackEntry& HpackEntry::operator=(const HpackEntry& other) {
+ insertion_index_ = other.insertion_index_;
+ type_ = other.type_;
+ if (type_ == LOOKUP) {
+ name_.clear();
+ value_.clear();
+ name_ref_ = other.name_ref_;
+ value_ref_ = other.value_ref_;
+ return *this;
+ }
+ name_ = other.name_;
+ value_ = other.value_;
+ name_ref_ = SpdyStringPiece(name_.data(), name_.size());
+ value_ref_ = SpdyStringPiece(value_.data(), value_.size());
+ return *this;
+}
+
+HpackEntry::~HpackEntry() = default;
+
+// static
+size_t HpackEntry::Size(SpdyStringPiece name, SpdyStringPiece value) {
+ return name.size() + value.size() + kSizeOverhead;
+}
+size_t HpackEntry::Size() const {
+ return Size(name(), value());
+}
+
+SpdyString HpackEntry::GetDebugString() const {
+ return SpdyStrCat(
+ "{ name: \"", name_ref_, "\", value: \"", value_ref_,
+ "\", index: ", insertion_index_, " ",
+ (IsStatic() ? " static" : (IsLookup() ? " lookup" : " dynamic")), " }");
+}
+
+size_t HpackEntry::EstimateMemoryUsage() const {
+ return SpdyEstimateMemoryUsage(name_) + SpdyEstimateMemoryUsage(value_);
+}
+
+} // namespace spdy
diff --git a/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_entry.h b/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_entry.h
new file mode 100644
index 00000000000..28dee47807a
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_entry.h
@@ -0,0 +1,110 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_SPDY_CORE_HPACK_HPACK_ENTRY_H_
+#define QUICHE_SPDY_CORE_HPACK_HPACK_ENTRY_H_
+
+#include <cstddef>
+#include <cstdint>
+
+#include "base/macros.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_export.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_string.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_string_piece.h"
+
+// All section references below are to
+// http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-08
+
+namespace spdy {
+
+// A structure for an entry in the static table (3.3.1)
+// and the header table (3.3.2).
+class SPDY_EXPORT_PRIVATE HpackEntry {
+ public:
+ // The constant amount added to name().size() and value().size() to
+ // get the size of an HpackEntry as defined in 5.1.
+ static const size_t kSizeOverhead;
+
+ // Creates an entry. Preconditions:
+ // - |is_static| captures whether this entry is a member of the static
+ // or dynamic header table.
+ // - |insertion_index| is this entry's index in the total set of entries ever
+ // inserted into the header table (including static entries).
+ //
+ // The combination of |is_static| and |insertion_index| allows an
+ // HpackEntryTable to determine the index of an HpackEntry in O(1) time.
+ // Copies |name| and |value|.
+ HpackEntry(SpdyStringPiece name,
+ SpdyStringPiece value,
+ bool is_static,
+ size_t insertion_index);
+
+ // Create a 'lookup' entry (only) suitable for querying a HpackEntrySet. The
+ // instance InsertionIndex() always returns 0 and IsLookup() returns true.
+ // The memory backing |name| and |value| must outlive this object.
+ HpackEntry(SpdyStringPiece name, SpdyStringPiece value);
+
+ HpackEntry(const HpackEntry& other);
+ HpackEntry& operator=(const HpackEntry& other);
+
+ // Creates an entry with empty name and value. Only defined so that
+ // entries can be stored in STL containers.
+ HpackEntry();
+
+ ~HpackEntry();
+
+ SpdyStringPiece name() const { return name_ref_; }
+ SpdyStringPiece value() const { return value_ref_; }
+
+ // Returns whether this entry is a member of the static (as opposed to
+ // dynamic) table.
+ bool IsStatic() const { return type_ == STATIC; }
+
+ // Returns whether this entry is a lookup-only entry.
+ bool IsLookup() const { return type_ == LOOKUP; }
+
+ // Used to compute the entry's index in the header table.
+ size_t InsertionIndex() const { return insertion_index_; }
+
+ // Returns the size of an entry as defined in 5.1.
+ static size_t Size(SpdyStringPiece name, SpdyStringPiece value);
+ size_t Size() const;
+
+ SpdyString GetDebugString() const;
+
+ int64_t time_added() const { return time_added_; }
+ void set_time_added(int64_t now) { time_added_ = now; }
+
+ // Returns the estimate of dynamically allocated memory in bytes.
+ size_t EstimateMemoryUsage() const;
+
+ private:
+ enum EntryType {
+ LOOKUP,
+ DYNAMIC,
+ STATIC,
+ };
+
+ // These members are not used for LOOKUP entries.
+ SpdyString name_;
+ SpdyString value_;
+
+ // These members are always valid. For DYNAMIC and STATIC entries, they
+ // always point to |name_| and |value_|.
+ SpdyStringPiece name_ref_;
+ SpdyStringPiece value_ref_;
+
+ // The entry's index in the total set of entries ever inserted into the header
+ // table.
+ size_t insertion_index_;
+
+ EntryType type_;
+
+ // For HpackHeaderTable::DebugVisitorInterface
+ int64_t time_added_;
+};
+
+} // namespace spdy
+
+#endif // QUICHE_SPDY_CORE_HPACK_HPACK_ENTRY_H_
diff --git a/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_entry_test.cc b/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_entry_test.cc
new file mode 100644
index 00000000000..507c851d136
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_entry_test.cc
@@ -0,0 +1,122 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/spdy/core/hpack/hpack_entry.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace spdy {
+
+namespace {
+
+class HpackEntryTest : public ::testing::Test {
+ protected:
+ HpackEntryTest()
+ : name_("header-name"),
+ value_("header value"),
+ total_insertions_(0),
+ table_size_(0) {}
+
+ // These builders maintain the same external table invariants that a "real"
+ // table (ie HpackHeaderTable) would.
+ HpackEntry StaticEntry() {
+ return HpackEntry(name_, value_, true, total_insertions_++);
+ }
+ HpackEntry DynamicEntry() {
+ ++table_size_;
+ size_t index = total_insertions_++;
+ return HpackEntry(name_, value_, false, index);
+ }
+ void DropEntry() { --table_size_; }
+
+ size_t IndexOf(const HpackEntry& entry) const {
+ if (entry.IsStatic()) {
+ return 1 + entry.InsertionIndex() + table_size_;
+ } else {
+ return total_insertions_ - entry.InsertionIndex();
+ }
+ }
+
+ size_t Size() {
+ return name_.size() + value_.size() + HpackEntry::kSizeOverhead;
+ }
+
+ SpdyString name_, value_;
+
+ private:
+ // Referenced by HpackEntry instances.
+ size_t total_insertions_;
+ size_t table_size_;
+};
+
+TEST_F(HpackEntryTest, StaticConstructor) {
+ HpackEntry entry(StaticEntry());
+
+ EXPECT_EQ(name_, entry.name());
+ EXPECT_EQ(value_, entry.value());
+ EXPECT_TRUE(entry.IsStatic());
+ EXPECT_EQ(1u, IndexOf(entry));
+ EXPECT_EQ(Size(), entry.Size());
+}
+
+TEST_F(HpackEntryTest, DynamicConstructor) {
+ HpackEntry entry(DynamicEntry());
+
+ EXPECT_EQ(name_, entry.name());
+ EXPECT_EQ(value_, entry.value());
+ EXPECT_FALSE(entry.IsStatic());
+ EXPECT_EQ(1u, IndexOf(entry));
+ EXPECT_EQ(Size(), entry.Size());
+}
+
+TEST_F(HpackEntryTest, LookupConstructor) {
+ HpackEntry entry(name_, value_);
+
+ EXPECT_EQ(name_, entry.name());
+ EXPECT_EQ(value_, entry.value());
+ EXPECT_FALSE(entry.IsStatic());
+ EXPECT_EQ(0u, IndexOf(entry));
+ EXPECT_EQ(Size(), entry.Size());
+}
+
+TEST_F(HpackEntryTest, DefaultConstructor) {
+ HpackEntry entry;
+
+ EXPECT_TRUE(entry.name().empty());
+ EXPECT_TRUE(entry.value().empty());
+ EXPECT_EQ(HpackEntry::kSizeOverhead, entry.Size());
+}
+
+TEST_F(HpackEntryTest, IndexUpdate) {
+ HpackEntry static1(StaticEntry());
+ HpackEntry static2(StaticEntry());
+
+ EXPECT_EQ(1u, IndexOf(static1));
+ EXPECT_EQ(2u, IndexOf(static2));
+
+ HpackEntry dynamic1(DynamicEntry());
+ HpackEntry dynamic2(DynamicEntry());
+
+ EXPECT_EQ(1u, IndexOf(dynamic2));
+ EXPECT_EQ(2u, IndexOf(dynamic1));
+ EXPECT_EQ(3u, IndexOf(static1));
+ EXPECT_EQ(4u, IndexOf(static2));
+
+ DropEntry(); // Drops |dynamic1|.
+
+ EXPECT_EQ(1u, IndexOf(dynamic2));
+ EXPECT_EQ(2u, IndexOf(static1));
+ EXPECT_EQ(3u, IndexOf(static2));
+
+ HpackEntry dynamic3(DynamicEntry());
+
+ EXPECT_EQ(1u, IndexOf(dynamic3));
+ EXPECT_EQ(2u, IndexOf(dynamic2));
+ EXPECT_EQ(3u, IndexOf(static1));
+ EXPECT_EQ(4u, IndexOf(static2));
+}
+
+} // namespace
+
+} // namespace spdy
diff --git a/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_header_table.cc b/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_header_table.cc
new file mode 100644
index 00000000000..dbe565f1c27
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_header_table.cc
@@ -0,0 +1,274 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/spdy/core/hpack/hpack_header_table.h"
+
+#include <algorithm>
+
+#include "base/logging.h"
+#include "net/third_party/quiche/src/spdy/core/hpack/hpack_constants.h"
+#include "net/third_party/quiche/src/spdy/core/hpack/hpack_static_table.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_containers.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_estimate_memory_usage.h"
+
+namespace spdy {
+
+size_t HpackHeaderTable::EntryHasher::operator()(
+ const HpackEntry* entry) const {
+ return SpdyHashStringPair(entry->name(), entry->value());
+}
+
+bool HpackHeaderTable::EntriesEq::operator()(const HpackEntry* lhs,
+ const HpackEntry* rhs) const {
+ if (lhs == nullptr) {
+ return rhs == nullptr;
+ }
+ if (rhs == nullptr) {
+ return false;
+ }
+ return lhs->name() == rhs->name() && lhs->value() == rhs->value();
+}
+
+HpackHeaderTable::HpackHeaderTable()
+ : static_entries_(ObtainHpackStaticTable().GetStaticEntries()),
+ static_index_(ObtainHpackStaticTable().GetStaticIndex()),
+ static_name_index_(ObtainHpackStaticTable().GetStaticNameIndex()),
+ settings_size_bound_(kDefaultHeaderTableSizeSetting),
+ size_(0),
+ max_size_(kDefaultHeaderTableSizeSetting),
+ total_insertions_(static_entries_.size()) {}
+
+HpackHeaderTable::~HpackHeaderTable() = default;
+
+const HpackEntry* HpackHeaderTable::GetByIndex(size_t index) {
+ if (index == 0) {
+ return nullptr;
+ }
+ index -= 1;
+ if (index < static_entries_.size()) {
+ return &static_entries_[index];
+ }
+ index -= static_entries_.size();
+ if (index < dynamic_entries_.size()) {
+ const HpackEntry* result = &dynamic_entries_[index];
+ if (debug_visitor_ != nullptr) {
+ debug_visitor_->OnUseEntry(*result);
+ }
+ return result;
+ }
+ return nullptr;
+}
+
+const HpackEntry* HpackHeaderTable::GetByName(SpdyStringPiece name) {
+ {
+ auto it = static_name_index_.find(name);
+ if (it != static_name_index_.end()) {
+ return it->second;
+ }
+ }
+ {
+ NameToEntryMap::const_iterator it = dynamic_name_index_.find(name);
+ if (it != dynamic_name_index_.end()) {
+ const HpackEntry* result = it->second;
+ if (debug_visitor_ != nullptr) {
+ debug_visitor_->OnUseEntry(*result);
+ }
+ return result;
+ }
+ }
+ return nullptr;
+}
+
+const HpackEntry* HpackHeaderTable::GetByNameAndValue(SpdyStringPiece name,
+ SpdyStringPiece value) {
+ HpackEntry query(name, value);
+ {
+ auto it = static_index_.find(&query);
+ if (it != static_index_.end()) {
+ return *it;
+ }
+ }
+ {
+ auto it = dynamic_index_.find(&query);
+ if (it != dynamic_index_.end()) {
+ const HpackEntry* result = *it;
+ if (debug_visitor_ != nullptr) {
+ debug_visitor_->OnUseEntry(*result);
+ }
+ return result;
+ }
+ }
+ return nullptr;
+}
+
+size_t HpackHeaderTable::IndexOf(const HpackEntry* entry) const {
+ if (entry->IsLookup()) {
+ return 0;
+ } else if (entry->IsStatic()) {
+ return 1 + entry->InsertionIndex();
+ } else {
+ return total_insertions_ - entry->InsertionIndex() + static_entries_.size();
+ }
+}
+
+void HpackHeaderTable::SetMaxSize(size_t max_size) {
+ CHECK_LE(max_size, settings_size_bound_);
+
+ max_size_ = max_size;
+ if (size_ > max_size_) {
+ Evict(EvictionCountToReclaim(size_ - max_size_));
+ CHECK_LE(size_, max_size_);
+ }
+}
+
+void HpackHeaderTable::SetSettingsHeaderTableSize(size_t settings_size) {
+ settings_size_bound_ = settings_size;
+ SetMaxSize(settings_size_bound_);
+}
+
+void HpackHeaderTable::EvictionSet(SpdyStringPiece name,
+ SpdyStringPiece value,
+ EntryTable::iterator* begin_out,
+ EntryTable::iterator* end_out) {
+ size_t eviction_count = EvictionCountForEntry(name, value);
+ *begin_out = dynamic_entries_.end() - eviction_count;
+ *end_out = dynamic_entries_.end();
+}
+
+size_t HpackHeaderTable::EvictionCountForEntry(SpdyStringPiece name,
+ SpdyStringPiece value) const {
+ size_t available_size = max_size_ - size_;
+ size_t entry_size = HpackEntry::Size(name, value);
+
+ if (entry_size <= available_size) {
+ // No evictions are required.
+ return 0;
+ }
+ return EvictionCountToReclaim(entry_size - available_size);
+}
+
+size_t HpackHeaderTable::EvictionCountToReclaim(size_t reclaim_size) const {
+ size_t count = 0;
+ for (auto it = dynamic_entries_.rbegin();
+ it != dynamic_entries_.rend() && reclaim_size != 0; ++it, ++count) {
+ reclaim_size -= std::min(reclaim_size, it->Size());
+ }
+ return count;
+}
+
+void HpackHeaderTable::Evict(size_t count) {
+ for (size_t i = 0; i != count; ++i) {
+ CHECK(!dynamic_entries_.empty());
+ HpackEntry* entry = &dynamic_entries_.back();
+
+ size_ -= entry->Size();
+ auto it = dynamic_index_.find(entry);
+ DCHECK(it != dynamic_index_.end());
+ // Only remove an entry from the index if its insertion index matches;
+ // otherwise, the index refers to another entry with the same name and
+ // value.
+ if ((*it)->InsertionIndex() == entry->InsertionIndex()) {
+ dynamic_index_.erase(it);
+ }
+ auto name_it = dynamic_name_index_.find(entry->name());
+ DCHECK(name_it != dynamic_name_index_.end());
+ // Only remove an entry from the literal index if its insertion index
+ /// matches; otherwise, the index refers to another entry with the same
+ // name.
+ if (name_it->second->InsertionIndex() == entry->InsertionIndex()) {
+ dynamic_name_index_.erase(name_it);
+ }
+ dynamic_entries_.pop_back();
+ }
+}
+
+const HpackEntry* HpackHeaderTable::TryAddEntry(SpdyStringPiece name,
+ SpdyStringPiece value) {
+ Evict(EvictionCountForEntry(name, value));
+
+ size_t entry_size = HpackEntry::Size(name, value);
+ if (entry_size > (max_size_ - size_)) {
+ // Entire table has been emptied, but there's still insufficient room.
+ DCHECK(dynamic_entries_.empty());
+ DCHECK_EQ(0u, size_);
+ return nullptr;
+ }
+ dynamic_entries_.push_front(HpackEntry(name, value,
+ false, // is_static
+ total_insertions_));
+ HpackEntry* new_entry = &dynamic_entries_.front();
+ auto index_result = dynamic_index_.insert(new_entry);
+ if (!index_result.second) {
+ // An entry with the same name and value already exists in the dynamic
+ // index. We should replace it with the newly added entry.
+ DVLOG(1) << "Found existing entry: "
+ << (*index_result.first)->GetDebugString()
+ << " replacing with: " << new_entry->GetDebugString();
+ DCHECK_GT(new_entry->InsertionIndex(),
+ (*index_result.first)->InsertionIndex());
+ dynamic_index_.erase(index_result.first);
+ CHECK(dynamic_index_.insert(new_entry).second);
+ }
+
+ auto name_result =
+ dynamic_name_index_.insert(std::make_pair(new_entry->name(), new_entry));
+ if (!name_result.second) {
+ // An entry with the same name already exists in the dynamic index. We
+ // should replace it with the newly added entry.
+ DVLOG(1) << "Found existing entry: "
+ << name_result.first->second->GetDebugString()
+ << " replacing with: " << new_entry->GetDebugString();
+ DCHECK_GT(new_entry->InsertionIndex(),
+ name_result.first->second->InsertionIndex());
+ dynamic_name_index_.erase(name_result.first);
+ auto insert_result = dynamic_name_index_.insert(
+ std::make_pair(new_entry->name(), new_entry));
+ CHECK(insert_result.second);
+ }
+
+ size_ += entry_size;
+ ++total_insertions_;
+ if (debug_visitor_ != nullptr) {
+ // Call |debug_visitor_->OnNewEntry()| to get the current time.
+ HpackEntry& entry = dynamic_entries_.front();
+ entry.set_time_added(debug_visitor_->OnNewEntry(entry));
+ DVLOG(2) << "HpackHeaderTable::OnNewEntry: name=" << entry.name()
+ << ", value=" << entry.value()
+ << ", insert_index=" << entry.InsertionIndex()
+ << ", time_added=" << entry.time_added();
+ }
+
+ return &dynamic_entries_.front();
+}
+
+void HpackHeaderTable::DebugLogTableState() const {
+ DVLOG(2) << "Dynamic table:";
+ for (auto it = dynamic_entries_.begin(); it != dynamic_entries_.end(); ++it) {
+ DVLOG(2) << " " << it->GetDebugString();
+ }
+ DVLOG(2) << "Full Static Index:";
+ for (const auto* entry : static_index_) {
+ DVLOG(2) << " " << entry->GetDebugString();
+ }
+ DVLOG(2) << "Full Static Name Index:";
+ for (const auto it : static_name_index_) {
+ DVLOG(2) << " " << it.first << ": " << it.second->GetDebugString();
+ }
+ DVLOG(2) << "Full Dynamic Index:";
+ for (const auto* entry : dynamic_index_) {
+ DVLOG(2) << " " << entry->GetDebugString();
+ }
+ DVLOG(2) << "Full Dynamic Name Index:";
+ for (const auto it : dynamic_name_index_) {
+ DVLOG(2) << " " << it.first << ": " << it.second->GetDebugString();
+ }
+}
+
+size_t HpackHeaderTable::EstimateMemoryUsage() const {
+ return SpdyEstimateMemoryUsage(dynamic_entries_) +
+ SpdyEstimateMemoryUsage(dynamic_index_) +
+ SpdyEstimateMemoryUsage(dynamic_name_index_);
+}
+
+} // namespace spdy
diff --git a/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_header_table.h b/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_header_table.h
new file mode 100644
index 00000000000..e85edb7896b
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_header_table.h
@@ -0,0 +1,180 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_SPDY_CORE_HPACK_HPACK_HEADER_TABLE_H_
+#define QUICHE_SPDY_CORE_HPACK_HPACK_HEADER_TABLE_H_
+
+#include <cstddef>
+#include <cstdint>
+#include <deque>
+#include <memory>
+
+#include "base/macros.h"
+#include "net/third_party/quiche/src/spdy/core/hpack/hpack_entry.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_containers.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_export.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_macros.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_string_piece.h"
+
+// All section references below are to http://tools.ietf.org/html/rfc7541.
+
+namespace spdy {
+
+namespace test {
+class HpackHeaderTablePeer;
+} // namespace test
+
+// A data structure for the static table (2.3.1) and the dynamic table (2.3.2).
+class SPDY_EXPORT_PRIVATE HpackHeaderTable {
+ public:
+ friend class test::HpackHeaderTablePeer;
+
+ // Debug visitor my be used to extract debug/internal information
+ // about the HpackHeaderTable as it operates.
+ //
+ // Most HpackHeaderTable implementations do not need to bother with
+ // this interface at all.
+ class DebugVisitorInterface {
+ public:
+ virtual ~DebugVisitorInterface() {}
+
+ // |OnNewEntry()| and |OnUseEntry()| can be used together to
+ // gather data about the distribution of time intervals between
+ // creation and reference of entries in the dynamic table. The
+ // data is desired to sanity check a proposed extension to HPACK
+ // for QUIC that would eliminate inter-stream head of line
+ // blocking (due to standard HPACK). The visitor should return
+ // the current time from |OnNewEntry()|, which will be passed
+ // to |OnUseEntry()| each time that particular entry is used to
+ // emit an indexed representation.
+ virtual int64_t OnNewEntry(const HpackEntry& entry) = 0;
+ virtual void OnUseEntry(const HpackEntry& entry) = 0;
+ };
+
+ // HpackHeaderTable takes advantage of the deque property that references
+ // remain valid, so long as insertions & deletions are at the head & tail.
+ // This precludes the use of base::circular_deque.
+ //
+ // If this changes (we want to change to circular_deque or we start to drop
+ // entries from the middle of the table), this should to be a std::list, in
+ // which case |*_index_| can be trivially extended to map to list iterators.
+ using EntryTable = std::deque<HpackEntry>;
+
+ struct SPDY_EXPORT_PRIVATE EntryHasher {
+ size_t operator()(const HpackEntry* entry) const;
+ };
+ struct SPDY_EXPORT_PRIVATE EntriesEq {
+ bool operator()(const HpackEntry* lhs, const HpackEntry* rhs) const;
+ };
+ using UnorderedEntrySet = SpdyHashSet<HpackEntry*, EntryHasher, EntriesEq>;
+ using NameToEntryMap =
+ SpdyHashMap<SpdyStringPiece, const HpackEntry*, SpdyStringPieceHash>;
+
+ HpackHeaderTable();
+ HpackHeaderTable(const HpackHeaderTable&) = delete;
+ HpackHeaderTable& operator=(const HpackHeaderTable&) = delete;
+
+ ~HpackHeaderTable();
+
+ // Last-acknowledged value of SETTINGS_HEADER_TABLE_SIZE.
+ size_t settings_size_bound() const { return settings_size_bound_; }
+
+ // Current and maximum estimated byte size of the table, as described in
+ // 4.1. Notably, this is /not/ the number of entries in the table.
+ size_t size() const { return size_; }
+ size_t max_size() const { return max_size_; }
+
+ // Returns the entry matching the index, or NULL.
+ const HpackEntry* GetByIndex(size_t index);
+
+ // Returns the lowest-value entry having |name|, or NULL.
+ const HpackEntry* GetByName(SpdyStringPiece name);
+
+ // Returns the lowest-index matching entry, or NULL.
+ const HpackEntry* GetByNameAndValue(SpdyStringPiece name,
+ SpdyStringPiece value);
+
+ // Returns the index of an entry within this header table.
+ size_t IndexOf(const HpackEntry* entry) const;
+
+ // Sets the maximum size of the header table, evicting entries if
+ // necessary as described in 5.2.
+ void SetMaxSize(size_t max_size);
+
+ // Sets the SETTINGS_HEADER_TABLE_SIZE bound of the table. Will call
+ // SetMaxSize() as needed to preserve max_size() <= settings_size_bound().
+ void SetSettingsHeaderTableSize(size_t settings_size);
+
+ // Determine the set of entries which would be evicted by the insertion
+ // of |name| & |value| into the table, as per section 4.4. No eviction
+ // actually occurs. The set is returned via range [begin_out, end_out).
+ void EvictionSet(SpdyStringPiece name,
+ SpdyStringPiece value,
+ EntryTable::iterator* begin_out,
+ EntryTable::iterator* end_out);
+
+ // Adds an entry for the representation, evicting entries as needed. |name|
+ // and |value| must not be owned by an entry which could be evicted. The
+ // added HpackEntry is returned, or NULL is returned if all entries were
+ // evicted and the empty table is of insufficent size for the representation.
+ const HpackEntry* TryAddEntry(SpdyStringPiece name, SpdyStringPiece value);
+
+ void DebugLogTableState() const SPDY_UNUSED;
+
+ void set_debug_visitor(std::unique_ptr<DebugVisitorInterface> visitor) {
+ debug_visitor_ = std::move(visitor);
+ }
+
+ // Returns the estimate of dynamically allocated memory in bytes.
+ size_t EstimateMemoryUsage() const;
+
+ private:
+ // Returns number of evictions required to enter |name| & |value|.
+ size_t EvictionCountForEntry(SpdyStringPiece name,
+ SpdyStringPiece value) const;
+
+ // Returns number of evictions required to reclaim |reclaim_size| table size.
+ size_t EvictionCountToReclaim(size_t reclaim_size) const;
+
+ // Evicts |count| oldest entries from the table.
+ void Evict(size_t count);
+
+ // |static_entries_|, |static_index_|, and |static_name_index_| are owned by
+ // HpackStaticTable singleton.
+
+ // Tracks HpackEntries by index.
+ const EntryTable& static_entries_;
+ EntryTable dynamic_entries_;
+
+ // Tracks the unique HpackEntry for a given header name and value.
+ const UnorderedEntrySet& static_index_;
+
+ // Tracks the first static entry for each name in the static table.
+ const NameToEntryMap& static_name_index_;
+
+ // Tracks the most recently inserted HpackEntry for a given header name and
+ // value.
+ UnorderedEntrySet dynamic_index_;
+
+ // Tracks the most recently inserted HpackEntry for a given header name.
+ NameToEntryMap dynamic_name_index_;
+
+ // Last acknowledged value for SETTINGS_HEADER_TABLE_SIZE.
+ size_t settings_size_bound_;
+
+ // Estimated current and maximum byte size of the table.
+ // |max_size_| <= |settings_size_bound_|
+ size_t size_;
+ size_t max_size_;
+
+ // Total number of table insertions which have occurred. Referenced by
+ // IndexOf() for determination of an HpackEntry's table index.
+ size_t total_insertions_;
+
+ std::unique_ptr<DebugVisitorInterface> debug_visitor_;
+};
+
+} // namespace spdy
+
+#endif // QUICHE_SPDY_CORE_HPACK_HPACK_HEADER_TABLE_H_
diff --git a/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_header_table_test.cc b/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_header_table_test.cc
new file mode 100644
index 00000000000..b032aba435c
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_header_table_test.cc
@@ -0,0 +1,446 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/spdy/core/hpack/hpack_header_table.h"
+
+#include <algorithm>
+#include <cstdint>
+#include <set>
+#include <vector>
+
+#include "base/macros.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/spdy/core/hpack/hpack_constants.h"
+#include "net/third_party/quiche/src/spdy/core/hpack/hpack_entry.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_string.h"
+
+namespace spdy {
+
+using std::distance;
+
+namespace test {
+
+class HpackHeaderTablePeer {
+ public:
+ explicit HpackHeaderTablePeer(HpackHeaderTable* table) : table_(table) {}
+
+ const HpackHeaderTable::EntryTable& dynamic_entries() {
+ return table_->dynamic_entries_;
+ }
+ const HpackHeaderTable::EntryTable& static_entries() {
+ return table_->static_entries_;
+ }
+ size_t index_size() {
+ return table_->static_index_.size() + table_->dynamic_index_.size();
+ }
+ std::vector<HpackEntry*> EvictionSet(SpdyStringPiece name,
+ SpdyStringPiece value) {
+ HpackHeaderTable::EntryTable::iterator begin, end;
+ table_->EvictionSet(name, value, &begin, &end);
+ std::vector<HpackEntry*> result;
+ for (; begin != end; ++begin) {
+ result.push_back(&(*begin));
+ }
+ return result;
+ }
+ size_t total_insertions() { return table_->total_insertions_; }
+ size_t dynamic_entries_count() { return table_->dynamic_entries_.size(); }
+ size_t EvictionCountForEntry(SpdyStringPiece name, SpdyStringPiece value) {
+ return table_->EvictionCountForEntry(name, value);
+ }
+ size_t EvictionCountToReclaim(size_t reclaim_size) {
+ return table_->EvictionCountToReclaim(reclaim_size);
+ }
+ void Evict(size_t count) { return table_->Evict(count); }
+
+ void AddDynamicEntry(SpdyStringPiece name, SpdyStringPiece value) {
+ table_->dynamic_entries_.push_back(
+ HpackEntry(name, value, false, table_->total_insertions_++));
+ }
+
+ private:
+ HpackHeaderTable* table_;
+};
+
+} // namespace test
+
+namespace {
+
+class HpackHeaderTableTest : public ::testing::Test {
+ protected:
+ typedef std::vector<HpackEntry> HpackEntryVector;
+
+ HpackHeaderTableTest() : table_(), peer_(&table_) {}
+
+ // Returns an entry whose Size() is equal to the given one.
+ static HpackEntry MakeEntryOfSize(uint32_t size) {
+ EXPECT_GE(size, HpackEntry::kSizeOverhead);
+ SpdyString name((size - HpackEntry::kSizeOverhead) / 2, 'n');
+ SpdyString value(size - HpackEntry::kSizeOverhead - name.size(), 'v');
+ HpackEntry entry(name, value, false, 0);
+ EXPECT_EQ(size, entry.Size());
+ return entry;
+ }
+
+ // Returns a vector of entries whose total size is equal to the given
+ // one.
+ static HpackEntryVector MakeEntriesOfTotalSize(uint32_t total_size) {
+ EXPECT_GE(total_size, HpackEntry::kSizeOverhead);
+ uint32_t entry_size = HpackEntry::kSizeOverhead;
+ uint32_t remaining_size = total_size;
+ HpackEntryVector entries;
+ while (remaining_size > 0) {
+ EXPECT_LE(entry_size, remaining_size);
+ entries.push_back(MakeEntryOfSize(entry_size));
+ remaining_size -= entry_size;
+ entry_size = std::min(remaining_size, entry_size + 32);
+ }
+ return entries;
+ }
+
+ // Adds the given vector of entries to the given header table,
+ // expecting no eviction to happen.
+ void AddEntriesExpectNoEviction(const HpackEntryVector& entries) {
+ for (auto it = entries.begin(); it != entries.end(); ++it) {
+ HpackHeaderTable::EntryTable::iterator begin, end;
+
+ table_.EvictionSet(it->name(), it->value(), &begin, &end);
+ EXPECT_EQ(0, distance(begin, end));
+
+ const HpackEntry* entry = table_.TryAddEntry(it->name(), it->value());
+ EXPECT_NE(entry, static_cast<HpackEntry*>(nullptr));
+ }
+
+ for (size_t i = 0; i != entries.size(); ++i) {
+ // Static table has 61 entries, dynamic entries follow those.
+ size_t index = 61 + entries.size() - i;
+ const HpackEntry* entry = table_.GetByIndex(index);
+ EXPECT_EQ(entries[i].name(), entry->name());
+ EXPECT_EQ(entries[i].value(), entry->value());
+ EXPECT_EQ(index, table_.IndexOf(entry));
+ }
+ }
+
+ HpackEntry DynamicEntry(const SpdyString& name, const SpdyString& value) {
+ peer_.AddDynamicEntry(name, value);
+ return peer_.dynamic_entries().back();
+ }
+
+ HpackHeaderTable table_;
+ test::HpackHeaderTablePeer peer_;
+};
+
+TEST_F(HpackHeaderTableTest, StaticTableInitialization) {
+ EXPECT_EQ(0u, table_.size());
+ EXPECT_EQ(kDefaultHeaderTableSizeSetting, table_.max_size());
+ EXPECT_EQ(kDefaultHeaderTableSizeSetting, table_.settings_size_bound());
+
+ EXPECT_EQ(0u, peer_.dynamic_entries_count());
+ EXPECT_EQ(peer_.static_entries().size(), peer_.total_insertions());
+
+ // Static entries have been populated and inserted into the table & index.
+ EXPECT_NE(0u, peer_.static_entries().size());
+ EXPECT_EQ(peer_.index_size(), peer_.static_entries().size());
+ for (size_t i = 0; i != peer_.static_entries().size(); ++i) {
+ const HpackEntry* entry = &peer_.static_entries()[i];
+
+ EXPECT_TRUE(entry->IsStatic());
+ EXPECT_EQ(entry, table_.GetByIndex(i + 1));
+ EXPECT_EQ(entry, table_.GetByNameAndValue(entry->name(), entry->value()));
+ }
+}
+
+TEST_F(HpackHeaderTableTest, BasicDynamicEntryInsertionAndEviction) {
+ size_t static_count = peer_.total_insertions();
+ const HpackEntry* first_static_entry = table_.GetByIndex(1);
+
+ EXPECT_EQ(1u, table_.IndexOf(first_static_entry));
+
+ const HpackEntry* entry = table_.TryAddEntry("header-key", "Header Value");
+ EXPECT_EQ("header-key", entry->name());
+ EXPECT_EQ("Header Value", entry->value());
+ EXPECT_FALSE(entry->IsStatic());
+
+ // Table counts were updated appropriately.
+ EXPECT_EQ(entry->Size(), table_.size());
+ EXPECT_EQ(1u, peer_.dynamic_entries_count());
+ EXPECT_EQ(peer_.dynamic_entries().size(), peer_.dynamic_entries_count());
+ EXPECT_EQ(static_count + 1, peer_.total_insertions());
+ EXPECT_EQ(static_count + 1, peer_.index_size());
+
+ // Index() of entries reflects the insertion.
+ EXPECT_EQ(1u, table_.IndexOf(first_static_entry));
+ // Static table has 61 entries.
+ EXPECT_EQ(62u, table_.IndexOf(entry));
+ EXPECT_EQ(first_static_entry, table_.GetByIndex(1));
+ EXPECT_EQ(entry, table_.GetByIndex(62));
+
+ // Evict |entry|. Table counts are again updated appropriately.
+ peer_.Evict(1);
+ EXPECT_EQ(0u, table_.size());
+ EXPECT_EQ(0u, peer_.dynamic_entries_count());
+ EXPECT_EQ(peer_.dynamic_entries().size(), peer_.dynamic_entries_count());
+ EXPECT_EQ(static_count + 1, peer_.total_insertions());
+ EXPECT_EQ(static_count, peer_.index_size());
+
+ // Index() of |first_static_entry| reflects the eviction.
+ EXPECT_EQ(1u, table_.IndexOf(first_static_entry));
+ EXPECT_EQ(first_static_entry, table_.GetByIndex(1));
+}
+
+TEST_F(HpackHeaderTableTest, EntryIndexing) {
+ const HpackEntry* first_static_entry = table_.GetByIndex(1);
+
+ // Static entries are queryable by name & value.
+ EXPECT_EQ(first_static_entry, table_.GetByName(first_static_entry->name()));
+ EXPECT_EQ(first_static_entry,
+ table_.GetByNameAndValue(first_static_entry->name(),
+ first_static_entry->value()));
+
+ // Create a mix of entries which duplicate names, and names & values of both
+ // dynamic and static entries.
+ const HpackEntry* entry1 = table_.TryAddEntry(first_static_entry->name(),
+ first_static_entry->value());
+ const HpackEntry* entry2 =
+ table_.TryAddEntry(first_static_entry->name(), "Value Four");
+ const HpackEntry* entry3 = table_.TryAddEntry("key-1", "Value One");
+ const HpackEntry* entry4 = table_.TryAddEntry("key-2", "Value Three");
+ const HpackEntry* entry5 = table_.TryAddEntry("key-1", "Value Two");
+ const HpackEntry* entry6 = table_.TryAddEntry("key-2", "Value Three");
+ const HpackEntry* entry7 = table_.TryAddEntry("key-2", "Value Four");
+
+ // Entries are queryable under their current index.
+ EXPECT_EQ(entry7, table_.GetByIndex(62));
+ EXPECT_EQ(entry6, table_.GetByIndex(63));
+ EXPECT_EQ(entry5, table_.GetByIndex(64));
+ EXPECT_EQ(entry4, table_.GetByIndex(65));
+ EXPECT_EQ(entry3, table_.GetByIndex(66));
+ EXPECT_EQ(entry2, table_.GetByIndex(67));
+ EXPECT_EQ(entry1, table_.GetByIndex(68));
+ EXPECT_EQ(first_static_entry, table_.GetByIndex(1));
+
+ // Querying by name returns the most recently added matching entry.
+ EXPECT_EQ(entry5, table_.GetByName("key-1"));
+ EXPECT_EQ(entry7, table_.GetByName("key-2"));
+ EXPECT_EQ(entry2->name(),
+ table_.GetByName(first_static_entry->name())->name());
+ EXPECT_EQ(nullptr, table_.GetByName("not-present"));
+
+ // Querying by name & value returns the lowest-index matching entry among
+ // static entries, and the highest-index one among dynamic entries.
+ EXPECT_EQ(entry3, table_.GetByNameAndValue("key-1", "Value One"));
+ EXPECT_EQ(entry5, table_.GetByNameAndValue("key-1", "Value Two"));
+ EXPECT_EQ(entry6, table_.GetByNameAndValue("key-2", "Value Three"));
+ EXPECT_EQ(entry7, table_.GetByNameAndValue("key-2", "Value Four"));
+ EXPECT_EQ(first_static_entry,
+ table_.GetByNameAndValue(first_static_entry->name(),
+ first_static_entry->value()));
+ EXPECT_EQ(entry2,
+ table_.GetByNameAndValue(first_static_entry->name(), "Value Four"));
+ EXPECT_EQ(nullptr, table_.GetByNameAndValue("key-1", "Not Present"));
+ EXPECT_EQ(nullptr, table_.GetByNameAndValue("not-present", "Value One"));
+
+ // Evict |entry1|. Queries for its name & value now return the static entry.
+ // |entry2| remains queryable.
+ peer_.Evict(1);
+ EXPECT_EQ(first_static_entry,
+ table_.GetByNameAndValue(first_static_entry->name(),
+ first_static_entry->value()));
+ EXPECT_EQ(entry2,
+ table_.GetByNameAndValue(first_static_entry->name(), "Value Four"));
+
+ // Evict |entry2|. Queries by its name & value are not found.
+ peer_.Evict(1);
+ EXPECT_EQ(nullptr,
+ table_.GetByNameAndValue(first_static_entry->name(), "Value Four"));
+}
+
+TEST_F(HpackHeaderTableTest, SetSizes) {
+ SpdyString key = "key", value = "value";
+ const HpackEntry* entry1 = table_.TryAddEntry(key, value);
+ const HpackEntry* entry2 = table_.TryAddEntry(key, value);
+ const HpackEntry* entry3 = table_.TryAddEntry(key, value);
+
+ // Set exactly large enough. No Evictions.
+ size_t max_size = entry1->Size() + entry2->Size() + entry3->Size();
+ table_.SetMaxSize(max_size);
+ EXPECT_EQ(3u, peer_.dynamic_entries().size());
+
+ // Set just too small. One eviction.
+ max_size = entry1->Size() + entry2->Size() + entry3->Size() - 1;
+ table_.SetMaxSize(max_size);
+ EXPECT_EQ(2u, peer_.dynamic_entries().size());
+
+ // Changing SETTINGS_HEADER_TABLE_SIZE.
+ EXPECT_EQ(kDefaultHeaderTableSizeSetting, table_.settings_size_bound());
+ // In production, the size passed to SetSettingsHeaderTableSize is never
+ // larger than table_.settings_size_bound().
+ table_.SetSettingsHeaderTableSize(kDefaultHeaderTableSizeSetting * 3 + 1);
+ EXPECT_EQ(kDefaultHeaderTableSizeSetting * 3 + 1, table_.max_size());
+
+ // SETTINGS_HEADER_TABLE_SIZE upper-bounds |table_.max_size()|,
+ // and will force evictions.
+ max_size = entry3->Size() - 1;
+ table_.SetSettingsHeaderTableSize(max_size);
+ EXPECT_EQ(max_size, table_.max_size());
+ EXPECT_EQ(max_size, table_.settings_size_bound());
+ EXPECT_EQ(0u, peer_.dynamic_entries().size());
+}
+
+TEST_F(HpackHeaderTableTest, EvictionCountForEntry) {
+ SpdyString key = "key", value = "value";
+ const HpackEntry* entry1 = table_.TryAddEntry(key, value);
+ const HpackEntry* entry2 = table_.TryAddEntry(key, value);
+ size_t entry3_size = HpackEntry::Size(key, value);
+
+ // Just enough capacity for third entry.
+ table_.SetMaxSize(entry1->Size() + entry2->Size() + entry3_size);
+ EXPECT_EQ(0u, peer_.EvictionCountForEntry(key, value));
+ EXPECT_EQ(1u, peer_.EvictionCountForEntry(key, value + "x"));
+
+ // No extra capacity. Third entry would force evictions.
+ table_.SetMaxSize(entry1->Size() + entry2->Size());
+ EXPECT_EQ(1u, peer_.EvictionCountForEntry(key, value));
+ EXPECT_EQ(2u, peer_.EvictionCountForEntry(key, value + "x"));
+}
+
+TEST_F(HpackHeaderTableTest, EvictionCountToReclaim) {
+ SpdyString key = "key", value = "value";
+ const HpackEntry* entry1 = table_.TryAddEntry(key, value);
+ const HpackEntry* entry2 = table_.TryAddEntry(key, value);
+
+ EXPECT_EQ(1u, peer_.EvictionCountToReclaim(1));
+ EXPECT_EQ(1u, peer_.EvictionCountToReclaim(entry1->Size()));
+ EXPECT_EQ(2u, peer_.EvictionCountToReclaim(entry1->Size() + 1));
+ EXPECT_EQ(2u, peer_.EvictionCountToReclaim(entry1->Size() + entry2->Size()));
+}
+
+// Fill a header table with entries. Make sure the entries are in
+// reverse order in the header table.
+TEST_F(HpackHeaderTableTest, TryAddEntryBasic) {
+ EXPECT_EQ(0u, table_.size());
+ EXPECT_EQ(table_.settings_size_bound(), table_.max_size());
+
+ HpackEntryVector entries = MakeEntriesOfTotalSize(table_.max_size());
+
+ // Most of the checks are in AddEntriesExpectNoEviction().
+ AddEntriesExpectNoEviction(entries);
+ EXPECT_EQ(table_.max_size(), table_.size());
+ EXPECT_EQ(table_.settings_size_bound(), table_.size());
+}
+
+// Fill a header table with entries, and then ramp the table's max
+// size down to evict an entry one at a time. Make sure the eviction
+// happens as expected.
+TEST_F(HpackHeaderTableTest, SetMaxSize) {
+ HpackEntryVector entries =
+ MakeEntriesOfTotalSize(kDefaultHeaderTableSizeSetting / 2);
+ AddEntriesExpectNoEviction(entries);
+
+ for (auto it = entries.begin(); it != entries.end(); ++it) {
+ size_t expected_count = distance(it, entries.end());
+ EXPECT_EQ(expected_count, peer_.dynamic_entries().size());
+
+ table_.SetMaxSize(table_.size() + 1);
+ EXPECT_EQ(expected_count, peer_.dynamic_entries().size());
+
+ table_.SetMaxSize(table_.size());
+ EXPECT_EQ(expected_count, peer_.dynamic_entries().size());
+
+ --expected_count;
+ table_.SetMaxSize(table_.size() - 1);
+ EXPECT_EQ(expected_count, peer_.dynamic_entries().size());
+ }
+ EXPECT_EQ(0u, table_.size());
+}
+
+// Fill a header table with entries, and then add an entry just big
+// enough to cause eviction of all but one entry. Make sure the
+// eviction happens as expected and the long entry is inserted into
+// the table.
+TEST_F(HpackHeaderTableTest, TryAddEntryEviction) {
+ HpackEntryVector entries = MakeEntriesOfTotalSize(table_.max_size());
+ AddEntriesExpectNoEviction(entries);
+
+ const HpackEntry* survivor_entry = table_.GetByIndex(61 + 1);
+ HpackEntry long_entry =
+ MakeEntryOfSize(table_.max_size() - survivor_entry->Size());
+
+ // All dynamic entries but the first are to be evicted.
+ EXPECT_EQ(peer_.dynamic_entries().size() - 1,
+ peer_.EvictionSet(long_entry.name(), long_entry.value()).size());
+
+ const HpackEntry* new_entry =
+ table_.TryAddEntry(long_entry.name(), long_entry.value());
+ EXPECT_EQ(62u, table_.IndexOf(new_entry));
+ EXPECT_EQ(2u, peer_.dynamic_entries().size());
+ EXPECT_EQ(table_.GetByIndex(63), survivor_entry);
+ EXPECT_EQ(table_.GetByIndex(62), new_entry);
+}
+
+// Fill a header table with entries, and then add an entry bigger than
+// the entire table. Make sure no entry remains in the table.
+TEST_F(HpackHeaderTableTest, TryAddTooLargeEntry) {
+ HpackEntryVector entries = MakeEntriesOfTotalSize(table_.max_size());
+ AddEntriesExpectNoEviction(entries);
+
+ const HpackEntry long_entry = MakeEntryOfSize(table_.max_size() + 1);
+
+ // All entries are to be evicted.
+ EXPECT_EQ(peer_.dynamic_entries().size(),
+ peer_.EvictionSet(long_entry.name(), long_entry.value()).size());
+
+ const HpackEntry* new_entry =
+ table_.TryAddEntry(long_entry.name(), long_entry.value());
+ EXPECT_EQ(new_entry, static_cast<HpackEntry*>(nullptr));
+ EXPECT_EQ(0u, peer_.dynamic_entries().size());
+}
+
+TEST_F(HpackHeaderTableTest, EntryNamesDiffer) {
+ HpackEntry entry1("header", "value");
+ HpackEntry entry2("HEADER", "value");
+
+ HpackHeaderTable::EntryHasher hasher;
+ EXPECT_NE(hasher(&entry1), hasher(&entry2));
+
+ HpackHeaderTable::EntriesEq eq;
+ EXPECT_FALSE(eq(&entry1, &entry2));
+}
+
+TEST_F(HpackHeaderTableTest, EntryValuesDiffer) {
+ HpackEntry entry1("header", "value");
+ HpackEntry entry2("header", "VALUE");
+
+ HpackHeaderTable::EntryHasher hasher;
+ EXPECT_NE(hasher(&entry1), hasher(&entry2));
+
+ HpackHeaderTable::EntriesEq eq;
+ EXPECT_FALSE(eq(&entry1, &entry2));
+}
+
+TEST_F(HpackHeaderTableTest, EntriesEqual) {
+ HpackEntry entry1(DynamicEntry("name", "value"));
+ HpackEntry entry2(DynamicEntry("name", "value"));
+
+ HpackHeaderTable::EntryHasher hasher;
+ EXPECT_EQ(hasher(&entry1), hasher(&entry2));
+
+ HpackHeaderTable::EntriesEq eq;
+ EXPECT_TRUE(eq(&entry1, &entry2));
+}
+
+TEST_F(HpackHeaderTableTest, StaticAndDynamicEntriesEqual) {
+ HpackEntry entry1("name", "value");
+ HpackEntry entry2(DynamicEntry("name", "value"));
+
+ HpackHeaderTable::EntryHasher hasher;
+ EXPECT_EQ(hasher(&entry1), hasher(&entry2));
+
+ HpackHeaderTable::EntriesEq eq;
+ EXPECT_TRUE(eq(&entry1, &entry2));
+}
+
+} // namespace
+
+} // namespace spdy
diff --git a/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_huffman_table.cc b/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_huffman_table.cc
new file mode 100644
index 00000000000..c917767aa07
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_huffman_table.cc
@@ -0,0 +1,151 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/spdy/core/hpack/hpack_huffman_table.h"
+
+#include <algorithm>
+#include <cmath>
+#include <limits>
+#include <memory>
+
+#include "base/logging.h"
+#include "net/third_party/quiche/src/spdy/core/hpack/hpack_output_stream.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_estimate_memory_usage.h"
+
+namespace spdy {
+
+namespace {
+
+bool SymbolLengthAndIdCompare(const HpackHuffmanSymbol& a,
+ const HpackHuffmanSymbol& b) {
+ if (a.length == b.length) {
+ return a.id < b.id;
+ }
+ return a.length < b.length;
+}
+bool SymbolIdCompare(const HpackHuffmanSymbol& a, const HpackHuffmanSymbol& b) {
+ return a.id < b.id;
+}
+
+} // namespace
+
+HpackHuffmanTable::HpackHuffmanTable() : pad_bits_(0), failed_symbol_id_(0) {}
+
+HpackHuffmanTable::~HpackHuffmanTable() = default;
+
+bool HpackHuffmanTable::Initialize(const HpackHuffmanSymbol* input_symbols,
+ size_t symbol_count) {
+ CHECK(!IsInitialized());
+ DCHECK_LE(symbol_count, std::numeric_limits<uint16_t>::max());
+
+ std::vector<Symbol> symbols(symbol_count);
+ // Validate symbol id sequence, and copy into |symbols|.
+ for (uint16_t i = 0; i < symbol_count; i++) {
+ if (i != input_symbols[i].id) {
+ failed_symbol_id_ = i;
+ return false;
+ }
+ symbols[i] = input_symbols[i];
+ }
+ // Order on length and ID ascending, to verify symbol codes are canonical.
+ std::sort(symbols.begin(), symbols.end(), SymbolLengthAndIdCompare);
+ if (symbols[0].code != 0) {
+ failed_symbol_id_ = 0;
+ return false;
+ }
+ for (size_t i = 1; i != symbols.size(); i++) {
+ unsigned code_shift = 32 - symbols[i - 1].length;
+ uint32_t code = symbols[i - 1].code + (1 << code_shift);
+
+ if (code != symbols[i].code) {
+ failed_symbol_id_ = symbols[i].id;
+ return false;
+ }
+ if (code < symbols[i - 1].code) {
+ // An integer overflow occurred. This implies the input
+ // lengths do not represent a valid Huffman code.
+ failed_symbol_id_ = symbols[i].id;
+ return false;
+ }
+ }
+ if (symbols.back().length < 8) {
+ // At least one code (such as an EOS symbol) must be 8 bits or longer.
+ // Without this, some inputs will not be encodable in a whole number
+ // of bytes.
+ return false;
+ }
+ pad_bits_ = static_cast<uint8_t>(symbols.back().code >> 24);
+
+ // Order on symbol ID ascending.
+ std::sort(symbols.begin(), symbols.end(), SymbolIdCompare);
+ BuildEncodeTable(symbols);
+ return true;
+}
+
+void HpackHuffmanTable::BuildEncodeTable(const std::vector<Symbol>& symbols) {
+ for (size_t i = 0; i != symbols.size(); i++) {
+ const Symbol& symbol = symbols[i];
+ CHECK_EQ(i, symbol.id);
+ code_by_id_.push_back(symbol.code);
+ length_by_id_.push_back(symbol.length);
+ }
+}
+
+bool HpackHuffmanTable::IsInitialized() const {
+ return !code_by_id_.empty();
+}
+
+void HpackHuffmanTable::EncodeString(SpdyStringPiece in,
+ HpackOutputStream* out) const {
+ size_t bit_remnant = 0;
+ for (size_t i = 0; i != in.size(); i++) {
+ uint16_t symbol_id = static_cast<uint8_t>(in[i]);
+ CHECK_GT(code_by_id_.size(), symbol_id);
+
+ // Load, and shift code to low bits.
+ unsigned length = length_by_id_[symbol_id];
+ uint32_t code = code_by_id_[symbol_id] >> (32 - length);
+
+ bit_remnant = (bit_remnant + length) % 8;
+
+ if (length > 24) {
+ out->AppendBits(static_cast<uint8_t>(code >> 24), length - 24);
+ length = 24;
+ }
+ if (length > 16) {
+ out->AppendBits(static_cast<uint8_t>(code >> 16), length - 16);
+ length = 16;
+ }
+ if (length > 8) {
+ out->AppendBits(static_cast<uint8_t>(code >> 8), length - 8);
+ length = 8;
+ }
+ out->AppendBits(static_cast<uint8_t>(code), length);
+ }
+ if (bit_remnant != 0) {
+ // Pad current byte as required.
+ out->AppendBits(pad_bits_ >> bit_remnant, 8 - bit_remnant);
+ }
+}
+
+size_t HpackHuffmanTable::EncodedSize(SpdyStringPiece in) const {
+ size_t bit_count = 0;
+ for (size_t i = 0; i != in.size(); i++) {
+ uint16_t symbol_id = static_cast<uint8_t>(in[i]);
+ CHECK_GT(code_by_id_.size(), symbol_id);
+
+ bit_count += length_by_id_[symbol_id];
+ }
+ if (bit_count % 8 != 0) {
+ bit_count += 8 - bit_count % 8;
+ }
+ return bit_count / 8;
+}
+
+size_t HpackHuffmanTable::EstimateMemoryUsage() const {
+ return SpdyEstimateMemoryUsage(code_by_id_) +
+ SpdyEstimateMemoryUsage(length_by_id_);
+}
+
+} // namespace spdy
diff --git a/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_huffman_table.h b/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_huffman_table.h
new file mode 100644
index 00000000000..80d9e5262b7
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_huffman_table.h
@@ -0,0 +1,76 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_SPDY_CORE_HPACK_HPACK_HUFFMAN_TABLE_H_
+#define QUICHE_SPDY_CORE_HPACK_HPACK_HUFFMAN_TABLE_H_
+
+#include <cstddef>
+#include <cstdint>
+#include <vector>
+
+#include "net/third_party/quiche/src/spdy/core/hpack/hpack_constants.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_export.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_string.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_string_piece.h"
+
+namespace spdy {
+
+namespace test {
+class HpackHuffmanTablePeer;
+} // namespace test
+
+class HpackOutputStream;
+
+// HpackHuffmanTable encodes string literals using a constructed canonical
+// Huffman code. Once initialized, an instance is read only and may be accessed
+// only through its const interface.
+class SPDY_EXPORT_PRIVATE HpackHuffmanTable {
+ public:
+ friend class test::HpackHuffmanTablePeer;
+
+ typedef HpackHuffmanSymbol Symbol;
+
+ HpackHuffmanTable();
+ ~HpackHuffmanTable();
+
+ // Prepares HpackHuffmanTable to encode the canonical Huffman code as
+ // determined by the given symbols. Must be called exactly once.
+ // Returns false if the input symbols define an invalid coding, and true
+ // otherwise. Symbols must be presented in ascending ID order with no gaps,
+ // and |symbol_count| must fit in a uint16_t.
+ bool Initialize(const Symbol* input_symbols, size_t symbol_count);
+
+ // Returns whether Initialize() has been successfully called.
+ bool IsInitialized() const;
+
+ // Encodes the input string to the output stream using the table's Huffman
+ // context.
+ void EncodeString(SpdyStringPiece in, HpackOutputStream* out) const;
+
+ // Returns the encoded size of the input string.
+ size_t EncodedSize(SpdyStringPiece in) const;
+
+ // Returns the estimate of dynamically allocated memory in bytes.
+ size_t EstimateMemoryUsage() const;
+
+ private:
+ // Expects symbols ordered on ID ascending.
+ void BuildEncodeTable(const std::vector<Symbol>& symbols);
+
+ // Symbol code and code length, in ascending symbol ID order.
+ // Codes are stored in the most-significant bits of the word.
+ std::vector<uint32_t> code_by_id_;
+ std::vector<uint8_t> length_by_id_;
+
+ // The first 8 bits of the longest code. Applied when generating padding bits.
+ uint8_t pad_bits_;
+
+ // If initialization fails, preserve the symbol ID which failed validation
+ // for examination in tests.
+ uint16_t failed_symbol_id_;
+};
+
+} // namespace spdy
+
+#endif // QUICHE_SPDY_CORE_HPACK_HPACK_HUFFMAN_TABLE_H_
diff --git a/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_huffman_table_test.cc b/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_huffman_table_test.cc
new file mode 100644
index 00000000000..f30926c35e6
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_huffman_table_test.cc
@@ -0,0 +1,309 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/spdy/core/hpack/hpack_huffman_table.h"
+
+#include <utility>
+
+#include "base/macros.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/http2/hpack/huffman/hpack_huffman_decoder.h"
+#include "net/third_party/quiche/src/spdy/core/hpack/hpack_constants.h"
+#include "net/third_party/quiche/src/spdy/core/hpack/hpack_output_stream.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_arraysize.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_string_utils.h"
+
+namespace spdy {
+
+namespace test {
+
+class HpackHuffmanTablePeer {
+ public:
+ explicit HpackHuffmanTablePeer(const HpackHuffmanTable& table)
+ : table_(table) {}
+
+ const std::vector<uint32_t>& code_by_id() const { return table_.code_by_id_; }
+ const std::vector<uint8_t>& length_by_id() const {
+ return table_.length_by_id_;
+ }
+ uint8_t pad_bits() const { return table_.pad_bits_; }
+ uint16_t failed_symbol_id() const { return table_.failed_symbol_id_; }
+
+ private:
+ const HpackHuffmanTable& table_;
+};
+
+namespace {
+
+// Tests of the ability to encode some canonical Huffman code,
+// not just the one defined in the RFC 7541.
+class GenericHuffmanTableTest : public ::testing::Test {
+ protected:
+ GenericHuffmanTableTest() : table_(), peer_(table_) {}
+
+ SpdyString EncodeString(SpdyStringPiece input) {
+ SpdyString result;
+ HpackOutputStream output_stream;
+ table_.EncodeString(input, &output_stream);
+
+ output_stream.TakeString(&result);
+ // Verify EncodedSize() agrees with EncodeString().
+ EXPECT_EQ(result.size(), table_.EncodedSize(input));
+ return result;
+ }
+
+ HpackHuffmanTable table_;
+ HpackHuffmanTablePeer peer_;
+};
+
+TEST_F(GenericHuffmanTableTest, InitializeEdgeCases) {
+ {
+ // Verify eight symbols can be encoded with 3 bits per symbol.
+ HpackHuffmanSymbol code[] = {{0b00000000000000000000000000000000, 3, 0},
+ {0b00100000000000000000000000000000, 3, 1},
+ {0b01000000000000000000000000000000, 3, 2},
+ {0b01100000000000000000000000000000, 3, 3},
+ {0b10000000000000000000000000000000, 3, 4},
+ {0b10100000000000000000000000000000, 3, 5},
+ {0b11000000000000000000000000000000, 3, 6},
+ {0b11100000000000000000000000000000, 8, 7}};
+ HpackHuffmanTable table;
+ EXPECT_TRUE(table.Initialize(code, SPDY_ARRAYSIZE(code)));
+ }
+ {
+ // But using 2 bits with one symbol overflows the code.
+ HpackHuffmanSymbol code[] = {
+ {0b01000000000000000000000000000000, 3, 0},
+ {0b01100000000000000000000000000000, 3, 1},
+ {0b00000000000000000000000000000000, 2, 2},
+ {0b10000000000000000000000000000000, 3, 3},
+ {0b10100000000000000000000000000000, 3, 4},
+ {0b11000000000000000000000000000000, 3, 5},
+ {0b11100000000000000000000000000000, 3, 6},
+ {0b00000000000000000000000000000000, 8, 7}}; // Overflow.
+ HpackHuffmanTable table;
+ EXPECT_FALSE(table.Initialize(code, SPDY_ARRAYSIZE(code)));
+ EXPECT_EQ(7, HpackHuffmanTablePeer(table).failed_symbol_id());
+ }
+ {
+ // Verify four symbols can be encoded with incremental bits per symbol.
+ HpackHuffmanSymbol code[] = {{0b00000000000000000000000000000000, 1, 0},
+ {0b10000000000000000000000000000000, 2, 1},
+ {0b11000000000000000000000000000000, 3, 2},
+ {0b11100000000000000000000000000000, 8, 3}};
+ HpackHuffmanTable table;
+ EXPECT_TRUE(table.Initialize(code, SPDY_ARRAYSIZE(code)));
+ }
+ {
+ // But repeating a length overflows the code.
+ HpackHuffmanSymbol code[] = {
+ {0b00000000000000000000000000000000, 1, 0},
+ {0b10000000000000000000000000000000, 2, 1},
+ {0b11000000000000000000000000000000, 2, 2},
+ {0b00000000000000000000000000000000, 8, 3}}; // Overflow.
+ HpackHuffmanTable table;
+ EXPECT_FALSE(table.Initialize(code, SPDY_ARRAYSIZE(code)));
+ EXPECT_EQ(3, HpackHuffmanTablePeer(table).failed_symbol_id());
+ }
+ {
+ // Symbol IDs must be assigned sequentially with no gaps.
+ HpackHuffmanSymbol code[] = {
+ {0b00000000000000000000000000000000, 1, 0},
+ {0b10000000000000000000000000000000, 2, 1},
+ {0b11000000000000000000000000000000, 3, 1}, // Repeat.
+ {0b11100000000000000000000000000000, 8, 3}};
+ HpackHuffmanTable table;
+ EXPECT_FALSE(table.Initialize(code, SPDY_ARRAYSIZE(code)));
+ EXPECT_EQ(2, HpackHuffmanTablePeer(table).failed_symbol_id());
+ }
+ {
+ // Canonical codes must begin with zero.
+ HpackHuffmanSymbol code[] = {{0b10000000000000000000000000000000, 4, 0},
+ {0b10010000000000000000000000000000, 4, 1},
+ {0b10100000000000000000000000000000, 4, 2},
+ {0b10110000000000000000000000000000, 8, 3}};
+ HpackHuffmanTable table;
+ EXPECT_FALSE(table.Initialize(code, SPDY_ARRAYSIZE(code)));
+ EXPECT_EQ(0, HpackHuffmanTablePeer(table).failed_symbol_id());
+ }
+ {
+ // Codes must match the expected canonical sequence.
+ HpackHuffmanSymbol code[] = {
+ {0b00000000000000000000000000000000, 2, 0},
+ {0b01000000000000000000000000000000, 2, 1},
+ {0b11000000000000000000000000000000, 2, 2}, // Code not canonical.
+ {0b10000000000000000000000000000000, 8, 3}};
+ HpackHuffmanTable table;
+ EXPECT_FALSE(table.Initialize(code, SPDY_ARRAYSIZE(code)));
+ EXPECT_EQ(2, HpackHuffmanTablePeer(table).failed_symbol_id());
+ }
+ {
+ // At least one code must have a length of 8 bits (to ensure pad-ability).
+ HpackHuffmanSymbol code[] = {{0b00000000000000000000000000000000, 1, 0},
+ {0b10000000000000000000000000000000, 2, 1},
+ {0b11000000000000000000000000000000, 3, 2},
+ {0b11100000000000000000000000000000, 7, 3}};
+ HpackHuffmanTable table;
+ EXPECT_FALSE(table.Initialize(code, SPDY_ARRAYSIZE(code)));
+ }
+}
+
+TEST_F(GenericHuffmanTableTest, ValidateInternalsWithSmallCode) {
+ HpackHuffmanSymbol code[] = {
+ {0b01100000000000000000000000000000, 4, 0}, // 3rd.
+ {0b01110000000000000000000000000000, 4, 1}, // 4th.
+ {0b00000000000000000000000000000000, 2, 2}, // 1st assigned code.
+ {0b01000000000000000000000000000000, 3, 3}, // 2nd.
+ {0b10000000000000000000000000000000, 5, 4}, // 5th.
+ {0b10001000000000000000000000000000, 5, 5}, // 6th.
+ {0b10011000000000000000000000000000, 8, 6}, // 8th.
+ {0b10010000000000000000000000000000, 5, 7}}; // 7th.
+ EXPECT_TRUE(table_.Initialize(code, SPDY_ARRAYSIZE(code)));
+
+ ASSERT_EQ(SPDY_ARRAYSIZE(code), peer_.code_by_id().size());
+ ASSERT_EQ(SPDY_ARRAYSIZE(code), peer_.length_by_id().size());
+ for (size_t i = 0; i < SPDY_ARRAYSIZE(code); ++i) {
+ EXPECT_EQ(code[i].code, peer_.code_by_id()[i]);
+ EXPECT_EQ(code[i].length, peer_.length_by_id()[i]);
+ }
+
+ EXPECT_EQ(0b10011000, peer_.pad_bits());
+
+ char input_storage[] = {2, 3, 2, 7, 4};
+ SpdyStringPiece input(input_storage, SPDY_ARRAYSIZE(input_storage));
+ // By symbol: (2) 00 (3) 010 (2) 00 (7) 10010 (4) 10000 (6 as pad) 1001100.
+ char expect_storage[] = {0b00010001, 0b00101000, 0b01001100};
+ SpdyStringPiece expect(expect_storage, SPDY_ARRAYSIZE(expect_storage));
+ EXPECT_EQ(expect, EncodeString(input));
+}
+
+// Tests of the ability to encode the HPACK Huffman Code, defined in:
+// https://httpwg.github.io/specs/rfc7541.html#huffman.code
+class HpackHuffmanTableTest : public GenericHuffmanTableTest {
+ protected:
+ void SetUp() override {
+ EXPECT_TRUE(table_.Initialize(HpackHuffmanCodeVector().data(),
+ HpackHuffmanCodeVector().size()));
+ EXPECT_TRUE(table_.IsInitialized());
+ }
+
+ // Use http2::HpackHuffmanDecoder for roundtrip tests.
+ void DecodeString(const SpdyString& encoded, SpdyString* out) {
+ http2::HpackHuffmanDecoder decoder;
+ out->clear();
+ EXPECT_TRUE(decoder.Decode(encoded, out));
+ }
+};
+
+TEST_F(HpackHuffmanTableTest, InitializeHpackCode) {
+ EXPECT_EQ(peer_.pad_bits(), 0b11111111); // First 8 bits of EOS.
+}
+
+TEST_F(HpackHuffmanTableTest, SpecRequestExamples) {
+ SpdyString buffer;
+ SpdyString test_table[] = {
+ SpdyHexDecode("f1e3c2e5f23a6ba0ab90f4ff"),
+ "www.example.com",
+ SpdyHexDecode("a8eb10649cbf"),
+ "no-cache",
+ SpdyHexDecode("25a849e95ba97d7f"),
+ "custom-key",
+ SpdyHexDecode("25a849e95bb8e8b4bf"),
+ "custom-value",
+ };
+ // Round-trip each test example.
+ for (size_t i = 0; i != SPDY_ARRAYSIZE(test_table); i += 2) {
+ const SpdyString& encodedFixture(test_table[i]);
+ const SpdyString& decodedFixture(test_table[i + 1]);
+ DecodeString(encodedFixture, &buffer);
+ EXPECT_EQ(decodedFixture, buffer);
+ buffer = EncodeString(decodedFixture);
+ EXPECT_EQ(encodedFixture, buffer);
+ }
+}
+
+TEST_F(HpackHuffmanTableTest, SpecResponseExamples) {
+ SpdyString buffer;
+ SpdyString test_table[] = {
+ SpdyHexDecode("6402"),
+ "302",
+ SpdyHexDecode("aec3771a4b"),
+ "private",
+ SpdyHexDecode("d07abe941054d444a8200595040b8166"
+ "e082a62d1bff"),
+ "Mon, 21 Oct 2013 20:13:21 GMT",
+ SpdyHexDecode("9d29ad171863c78f0b97c8e9ae82ae43"
+ "d3"),
+ "https://www.example.com",
+ SpdyHexDecode("94e7821dd7f2e6c7b335dfdfcd5b3960"
+ "d5af27087f3672c1ab270fb5291f9587"
+ "316065c003ed4ee5b1063d5007"),
+ "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1",
+ };
+ // Round-trip each test example.
+ for (size_t i = 0; i != SPDY_ARRAYSIZE(test_table); i += 2) {
+ const SpdyString& encodedFixture(test_table[i]);
+ const SpdyString& decodedFixture(test_table[i + 1]);
+ DecodeString(encodedFixture, &buffer);
+ EXPECT_EQ(decodedFixture, buffer);
+ buffer = EncodeString(decodedFixture);
+ EXPECT_EQ(encodedFixture, buffer);
+ }
+}
+
+TEST_F(HpackHuffmanTableTest, RoundTripIndividualSymbols) {
+ for (size_t i = 0; i != 256; i++) {
+ char c = static_cast<char>(i);
+ char storage[3] = {c, c, c};
+ SpdyStringPiece input(storage, SPDY_ARRAYSIZE(storage));
+ SpdyString buffer_in = EncodeString(input);
+ SpdyString buffer_out;
+ DecodeString(buffer_in, &buffer_out);
+ EXPECT_EQ(input, buffer_out);
+ }
+}
+
+TEST_F(HpackHuffmanTableTest, RoundTripSymbolSequence) {
+ char storage[512];
+ for (size_t i = 0; i != 256; i++) {
+ storage[i] = static_cast<char>(i);
+ storage[511 - i] = static_cast<char>(i);
+ }
+ SpdyStringPiece input(storage, SPDY_ARRAYSIZE(storage));
+ SpdyString buffer_in = EncodeString(input);
+ SpdyString buffer_out;
+ DecodeString(buffer_in, &buffer_out);
+ EXPECT_EQ(input, buffer_out);
+}
+
+TEST_F(HpackHuffmanTableTest, EncodedSizeAgreesWithEncodeString) {
+ SpdyString test_table[] = {
+ "",
+ "Mon, 21 Oct 2013 20:13:21 GMT",
+ "https://www.example.com",
+ "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1",
+ SpdyString(1, '\0'),
+ SpdyString("foo\0bar", 7),
+ SpdyString(256, '\0'),
+ };
+ for (size_t i = 0; i != 256; ++i) {
+ // Expand last |test_table| entry to cover all codes.
+ test_table[SPDY_ARRAYSIZE(test_table) - 1][i] = static_cast<char>(i);
+ }
+
+ HpackOutputStream output_stream;
+ SpdyString encoding;
+ for (size_t i = 0; i != SPDY_ARRAYSIZE(test_table); ++i) {
+ table_.EncodeString(test_table[i], &output_stream);
+ output_stream.TakeString(&encoding);
+ EXPECT_EQ(encoding.size(), table_.EncodedSize(test_table[i]));
+ }
+}
+
+} // namespace
+
+} // namespace test
+
+} // namespace spdy
diff --git a/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_output_stream.cc b/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_output_stream.cc
new file mode 100644
index 00000000000..30b566294a5
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_output_stream.cc
@@ -0,0 +1,97 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/spdy/core/hpack/hpack_output_stream.h"
+
+#include <utility>
+
+#include "base/logging.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_estimate_memory_usage.h"
+
+namespace spdy {
+
+HpackOutputStream::HpackOutputStream() : bit_offset_(0) {}
+
+HpackOutputStream::~HpackOutputStream() = default;
+
+void HpackOutputStream::AppendBits(uint8_t bits, size_t bit_size) {
+ DCHECK_GT(bit_size, 0u);
+ DCHECK_LE(bit_size, 8u);
+ DCHECK_EQ(bits >> bit_size, 0);
+ size_t new_bit_offset = bit_offset_ + bit_size;
+ if (bit_offset_ == 0) {
+ // Buffer ends on a byte boundary.
+ DCHECK_LE(bit_size, 8u);
+ buffer_.append(1, bits << (8 - bit_size));
+ } else if (new_bit_offset <= 8) {
+ // Buffer does not end on a byte boundary but the given bits fit
+ // in the remainder of the last byte.
+ buffer_.back() |= bits << (8 - new_bit_offset);
+ } else {
+ // Buffer does not end on a byte boundary and the given bits do
+ // not fit in the remainder of the last byte.
+ buffer_.back() |= bits >> (new_bit_offset - 8);
+ buffer_.append(1, bits << (16 - new_bit_offset));
+ }
+ bit_offset_ = new_bit_offset % 8;
+}
+
+void HpackOutputStream::AppendPrefix(HpackPrefix prefix) {
+ AppendBits(prefix.bits, prefix.bit_size);
+}
+
+void HpackOutputStream::AppendBytes(SpdyStringPiece buffer) {
+ DCHECK_EQ(bit_offset_, 0u);
+ buffer_.append(buffer.data(), buffer.size());
+}
+
+void HpackOutputStream::AppendUint32(uint32_t I) {
+ // The algorithm below is adapted from the pseudocode in 6.1.
+ size_t N = 8 - bit_offset_;
+ uint8_t max_first_byte = static_cast<uint8_t>((1 << N) - 1);
+ if (I < max_first_byte) {
+ AppendBits(static_cast<uint8_t>(I), N);
+ } else {
+ AppendBits(max_first_byte, N);
+ I -= max_first_byte;
+ while ((I & ~0x7f) != 0) {
+ buffer_.append(1, (I & 0x7f) | 0x80);
+ I >>= 7;
+ }
+ AppendBits(static_cast<uint8_t>(I), 8);
+ }
+}
+
+void HpackOutputStream::TakeString(SpdyString* output) {
+ // This must hold, since all public functions cause the buffer to
+ // end on a byte boundary.
+ DCHECK_EQ(bit_offset_, 0u);
+ buffer_.swap(*output);
+ buffer_.clear();
+ bit_offset_ = 0;
+}
+
+void HpackOutputStream::BoundedTakeString(size_t max_size, SpdyString* output) {
+ if (buffer_.size() > max_size) {
+ // Save off overflow bytes to temporary string (causes a copy).
+ SpdyString overflow(buffer_.data() + max_size, buffer_.size() - max_size);
+
+ // Resize buffer down to the given limit.
+ buffer_.resize(max_size);
+
+ // Give buffer to output string.
+ *output = std::move(buffer_);
+
+ // Reset to contain overflow.
+ buffer_ = std::move(overflow);
+ } else {
+ TakeString(output);
+ }
+}
+
+size_t HpackOutputStream::EstimateMemoryUsage() const {
+ return SpdyEstimateMemoryUsage(buffer_);
+}
+
+} // namespace spdy
diff --git a/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_output_stream.h b/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_output_stream.h
new file mode 100644
index 00000000000..450803ff471
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_output_stream.h
@@ -0,0 +1,76 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_SPDY_CORE_HPACK_HPACK_OUTPUT_STREAM_H_
+#define QUICHE_SPDY_CORE_HPACK_HPACK_OUTPUT_STREAM_H_
+
+#include <cstdint>
+#include <map>
+
+#include "base/macros.h"
+#include "net/third_party/quiche/src/spdy/core/hpack/hpack_constants.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_export.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_string.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_string_piece.h"
+
+// All section references below are to
+// http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-08
+
+namespace spdy {
+
+// An HpackOutputStream handles all the low-level details of encoding
+// header fields.
+class SPDY_EXPORT_PRIVATE HpackOutputStream {
+ public:
+ HpackOutputStream();
+ HpackOutputStream(const HpackOutputStream&) = delete;
+ HpackOutputStream& operator=(const HpackOutputStream&) = delete;
+ ~HpackOutputStream();
+
+ // Appends the lower |bit_size| bits of |bits| to the internal buffer.
+ //
+ // |bit_size| must be > 0 and <= 8. |bits| must not have any bits
+ // set other than the lower |bit_size| bits.
+ void AppendBits(uint8_t bits, size_t bit_size);
+
+ // Simply forwards to AppendBits(prefix.bits, prefix.bit-size).
+ void AppendPrefix(HpackPrefix prefix);
+
+ // Directly appends |buffer|.
+ void AppendBytes(SpdyStringPiece buffer);
+
+ // Appends the given integer using the representation described in
+ // 6.1. If the internal buffer ends on a byte boundary, the prefix
+ // length N is taken to be 8; otherwise, it is taken to be the
+ // number of bits to the next byte boundary.
+ //
+ // It is guaranteed that the internal buffer will end on a byte
+ // boundary after this function is called.
+ void AppendUint32(uint32_t I);
+
+ // Swaps the internal buffer with |output|, then resets state.
+ void TakeString(SpdyString* output);
+
+ // Gives up to |max_size| bytes of the internal buffer to |output|. Resets
+ // internal state with the overflow.
+ void BoundedTakeString(size_t max_size, SpdyString* output);
+
+ // Size in bytes of stream's internal buffer.
+ size_t size() const { return buffer_.size(); }
+
+ // Returns the estimate of dynamically allocated memory in bytes.
+ size_t EstimateMemoryUsage() const;
+
+ private:
+ // The internal bit buffer.
+ SpdyString buffer_;
+
+ // If 0, the buffer ends on a byte boundary. If non-zero, the buffer
+ // ends on the nth most significant bit. Guaranteed to be < 8.
+ size_t bit_offset_;
+};
+
+} // namespace spdy
+
+#endif // QUICHE_SPDY_CORE_HPACK_HPACK_OUTPUT_STREAM_H_
diff --git a/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_output_stream_test.cc b/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_output_stream_test.cc
new file mode 100644
index 00000000000..823c41bf0c3
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_output_stream_test.cc
@@ -0,0 +1,276 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/spdy/core/hpack/hpack_output_stream.h"
+
+#include <cstddef>
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace spdy {
+
+namespace {
+
+// Make sure that AppendBits() appends bits starting from the most
+// significant bit, and that it can handle crossing a byte boundary.
+TEST(HpackOutputStreamTest, AppendBits) {
+ HpackOutputStream output_stream;
+ SpdyString expected_str;
+
+ output_stream.AppendBits(0x1, 1);
+ expected_str.append(1, 0x00);
+ expected_str.back() |= (0x1 << 7);
+
+ output_stream.AppendBits(0x0, 1);
+
+ output_stream.AppendBits(0x3, 2);
+ *expected_str.rbegin() |= (0x3 << 4);
+
+ output_stream.AppendBits(0x0, 2);
+
+ // Byte-crossing append.
+ output_stream.AppendBits(0x7, 3);
+ *expected_str.rbegin() |= (0x7 >> 1);
+ expected_str.append(1, 0x00);
+ expected_str.back() |= (0x7 << 7);
+
+ output_stream.AppendBits(0x0, 7);
+
+ SpdyString str;
+ output_stream.TakeString(&str);
+ EXPECT_EQ(expected_str, str);
+}
+
+// Utility function to return I as a string encoded with an N-bit
+// prefix.
+SpdyString EncodeUint32(uint8_t N, uint32_t I) {
+ HpackOutputStream output_stream;
+ if (N < 8) {
+ output_stream.AppendBits(0x00, 8 - N);
+ }
+ output_stream.AppendUint32(I);
+ SpdyString str;
+ output_stream.TakeString(&str);
+ return str;
+}
+
+// The {Number}ByteIntegersEightBitPrefix tests below test that
+// certain integers are encoded correctly with an 8-bit prefix in
+// exactly {Number} bytes.
+
+TEST(HpackOutputStreamTest, OneByteIntegersEightBitPrefix) {
+ // Minimum.
+ EXPECT_EQ(SpdyString("\x00", 1), EncodeUint32(8, 0x00));
+ EXPECT_EQ("\x7f", EncodeUint32(8, 0x7f));
+ // Maximum.
+ EXPECT_EQ("\xfe", EncodeUint32(8, 0xfe));
+}
+
+TEST(HpackOutputStreamTest, TwoByteIntegersEightBitPrefix) {
+ // Minimum.
+ EXPECT_EQ(SpdyString("\xff\x00", 2), EncodeUint32(8, 0xff));
+ EXPECT_EQ("\xff\x01", EncodeUint32(8, 0x0100));
+ // Maximum.
+ EXPECT_EQ("\xff\x7f", EncodeUint32(8, 0x017e));
+}
+
+TEST(HpackOutputStreamTest, ThreeByteIntegersEightBitPrefix) {
+ // Minimum.
+ EXPECT_EQ("\xff\x80\x01", EncodeUint32(8, 0x017f));
+ EXPECT_EQ("\xff\x80\x1e", EncodeUint32(8, 0x0fff));
+ // Maximum.
+ EXPECT_EQ("\xff\xff\x7f", EncodeUint32(8, 0x40fe));
+}
+
+TEST(HpackOutputStreamTest, FourByteIntegersEightBitPrefix) {
+ // Minimum.
+ EXPECT_EQ("\xff\x80\x80\x01", EncodeUint32(8, 0x40ff));
+ EXPECT_EQ("\xff\x80\xfe\x03", EncodeUint32(8, 0xffff));
+ // Maximum.
+ EXPECT_EQ("\xff\xff\xff\x7f", EncodeUint32(8, 0x002000fe));
+}
+
+TEST(HpackOutputStreamTest, FiveByteIntegersEightBitPrefix) {
+ // Minimum.
+ EXPECT_EQ("\xff\x80\x80\x80\x01", EncodeUint32(8, 0x002000ff));
+ EXPECT_EQ("\xff\x80\xfe\xff\x07", EncodeUint32(8, 0x00ffffff));
+ // Maximum.
+ EXPECT_EQ("\xff\xff\xff\xff\x7f", EncodeUint32(8, 0x100000fe));
+}
+
+TEST(HpackOutputStreamTest, SixByteIntegersEightBitPrefix) {
+ // Minimum.
+ EXPECT_EQ("\xff\x80\x80\x80\x80\x01", EncodeUint32(8, 0x100000ff));
+ // Maximum.
+ EXPECT_EQ("\xff\x80\xfe\xff\xff\x0f", EncodeUint32(8, 0xffffffff));
+}
+
+// The {Number}ByteIntegersOneToSevenBitPrefix tests below test that
+// certain integers are encoded correctly with an N-bit prefix in
+// exactly {Number} bytes for N in {1, 2, ..., 7}.
+
+TEST(HpackOutputStreamTest, OneByteIntegersOneToSevenBitPrefixes) {
+ // Minimums.
+ EXPECT_EQ(SpdyString("\x00", 1), EncodeUint32(7, 0x00));
+ EXPECT_EQ(SpdyString("\x00", 1), EncodeUint32(6, 0x00));
+ EXPECT_EQ(SpdyString("\x00", 1), EncodeUint32(5, 0x00));
+ EXPECT_EQ(SpdyString("\x00", 1), EncodeUint32(4, 0x00));
+ EXPECT_EQ(SpdyString("\x00", 1), EncodeUint32(3, 0x00));
+ EXPECT_EQ(SpdyString("\x00", 1), EncodeUint32(2, 0x00));
+ EXPECT_EQ(SpdyString("\x00", 1), EncodeUint32(1, 0x00));
+
+ // Maximums.
+ EXPECT_EQ("\x7e", EncodeUint32(7, 0x7e));
+ EXPECT_EQ("\x3e", EncodeUint32(6, 0x3e));
+ EXPECT_EQ("\x1e", EncodeUint32(5, 0x1e));
+ EXPECT_EQ("\x0e", EncodeUint32(4, 0x0e));
+ EXPECT_EQ("\x06", EncodeUint32(3, 0x06));
+ EXPECT_EQ("\x02", EncodeUint32(2, 0x02));
+ EXPECT_EQ(SpdyString("\x00", 1), EncodeUint32(1, 0x00));
+}
+
+TEST(HpackOutputStreamTest, TwoByteIntegersOneToSevenBitPrefixes) {
+ // Minimums.
+ EXPECT_EQ(SpdyString("\x7f\x00", 2), EncodeUint32(7, 0x7f));
+ EXPECT_EQ(SpdyString("\x3f\x00", 2), EncodeUint32(6, 0x3f));
+ EXPECT_EQ(SpdyString("\x1f\x00", 2), EncodeUint32(5, 0x1f));
+ EXPECT_EQ(SpdyString("\x0f\x00", 2), EncodeUint32(4, 0x0f));
+ EXPECT_EQ(SpdyString("\x07\x00", 2), EncodeUint32(3, 0x07));
+ EXPECT_EQ(SpdyString("\x03\x00", 2), EncodeUint32(2, 0x03));
+ EXPECT_EQ(SpdyString("\x01\x00", 2), EncodeUint32(1, 0x01));
+
+ // Maximums.
+ EXPECT_EQ("\x7f\x7f", EncodeUint32(7, 0xfe));
+ EXPECT_EQ("\x3f\x7f", EncodeUint32(6, 0xbe));
+ EXPECT_EQ("\x1f\x7f", EncodeUint32(5, 0x9e));
+ EXPECT_EQ("\x0f\x7f", EncodeUint32(4, 0x8e));
+ EXPECT_EQ("\x07\x7f", EncodeUint32(3, 0x86));
+ EXPECT_EQ("\x03\x7f", EncodeUint32(2, 0x82));
+ EXPECT_EQ("\x01\x7f", EncodeUint32(1, 0x80));
+}
+
+TEST(HpackOutputStreamTest, ThreeByteIntegersOneToSevenBitPrefixes) {
+ // Minimums.
+ EXPECT_EQ("\x7f\x80\x01", EncodeUint32(7, 0xff));
+ EXPECT_EQ("\x3f\x80\x01", EncodeUint32(6, 0xbf));
+ EXPECT_EQ("\x1f\x80\x01", EncodeUint32(5, 0x9f));
+ EXPECT_EQ("\x0f\x80\x01", EncodeUint32(4, 0x8f));
+ EXPECT_EQ("\x07\x80\x01", EncodeUint32(3, 0x87));
+ EXPECT_EQ("\x03\x80\x01", EncodeUint32(2, 0x83));
+ EXPECT_EQ("\x01\x80\x01", EncodeUint32(1, 0x81));
+
+ // Maximums.
+ EXPECT_EQ("\x7f\xff\x7f", EncodeUint32(7, 0x407e));
+ EXPECT_EQ("\x3f\xff\x7f", EncodeUint32(6, 0x403e));
+ EXPECT_EQ("\x1f\xff\x7f", EncodeUint32(5, 0x401e));
+ EXPECT_EQ("\x0f\xff\x7f", EncodeUint32(4, 0x400e));
+ EXPECT_EQ("\x07\xff\x7f", EncodeUint32(3, 0x4006));
+ EXPECT_EQ("\x03\xff\x7f", EncodeUint32(2, 0x4002));
+ EXPECT_EQ("\x01\xff\x7f", EncodeUint32(1, 0x4000));
+}
+
+TEST(HpackOutputStreamTest, FourByteIntegersOneToSevenBitPrefixes) {
+ // Minimums.
+ EXPECT_EQ("\x7f\x80\x80\x01", EncodeUint32(7, 0x407f));
+ EXPECT_EQ("\x3f\x80\x80\x01", EncodeUint32(6, 0x403f));
+ EXPECT_EQ("\x1f\x80\x80\x01", EncodeUint32(5, 0x401f));
+ EXPECT_EQ("\x0f\x80\x80\x01", EncodeUint32(4, 0x400f));
+ EXPECT_EQ("\x07\x80\x80\x01", EncodeUint32(3, 0x4007));
+ EXPECT_EQ("\x03\x80\x80\x01", EncodeUint32(2, 0x4003));
+ EXPECT_EQ("\x01\x80\x80\x01", EncodeUint32(1, 0x4001));
+
+ // Maximums.
+ EXPECT_EQ("\x7f\xff\xff\x7f", EncodeUint32(7, 0x20007e));
+ EXPECT_EQ("\x3f\xff\xff\x7f", EncodeUint32(6, 0x20003e));
+ EXPECT_EQ("\x1f\xff\xff\x7f", EncodeUint32(5, 0x20001e));
+ EXPECT_EQ("\x0f\xff\xff\x7f", EncodeUint32(4, 0x20000e));
+ EXPECT_EQ("\x07\xff\xff\x7f", EncodeUint32(3, 0x200006));
+ EXPECT_EQ("\x03\xff\xff\x7f", EncodeUint32(2, 0x200002));
+ EXPECT_EQ("\x01\xff\xff\x7f", EncodeUint32(1, 0x200000));
+}
+
+TEST(HpackOutputStreamTest, FiveByteIntegersOneToSevenBitPrefixes) {
+ // Minimums.
+ EXPECT_EQ("\x7f\x80\x80\x80\x01", EncodeUint32(7, 0x20007f));
+ EXPECT_EQ("\x3f\x80\x80\x80\x01", EncodeUint32(6, 0x20003f));
+ EXPECT_EQ("\x1f\x80\x80\x80\x01", EncodeUint32(5, 0x20001f));
+ EXPECT_EQ("\x0f\x80\x80\x80\x01", EncodeUint32(4, 0x20000f));
+ EXPECT_EQ("\x07\x80\x80\x80\x01", EncodeUint32(3, 0x200007));
+ EXPECT_EQ("\x03\x80\x80\x80\x01", EncodeUint32(2, 0x200003));
+ EXPECT_EQ("\x01\x80\x80\x80\x01", EncodeUint32(1, 0x200001));
+
+ // Maximums.
+ EXPECT_EQ("\x7f\xff\xff\xff\x7f", EncodeUint32(7, 0x1000007e));
+ EXPECT_EQ("\x3f\xff\xff\xff\x7f", EncodeUint32(6, 0x1000003e));
+ EXPECT_EQ("\x1f\xff\xff\xff\x7f", EncodeUint32(5, 0x1000001e));
+ EXPECT_EQ("\x0f\xff\xff\xff\x7f", EncodeUint32(4, 0x1000000e));
+ EXPECT_EQ("\x07\xff\xff\xff\x7f", EncodeUint32(3, 0x10000006));
+ EXPECT_EQ("\x03\xff\xff\xff\x7f", EncodeUint32(2, 0x10000002));
+ EXPECT_EQ("\x01\xff\xff\xff\x7f", EncodeUint32(1, 0x10000000));
+}
+
+TEST(HpackOutputStreamTest, SixByteIntegersOneToSevenBitPrefixes) {
+ // Minimums.
+ EXPECT_EQ("\x7f\x80\x80\x80\x80\x01", EncodeUint32(7, 0x1000007f));
+ EXPECT_EQ("\x3f\x80\x80\x80\x80\x01", EncodeUint32(6, 0x1000003f));
+ EXPECT_EQ("\x1f\x80\x80\x80\x80\x01", EncodeUint32(5, 0x1000001f));
+ EXPECT_EQ("\x0f\x80\x80\x80\x80\x01", EncodeUint32(4, 0x1000000f));
+ EXPECT_EQ("\x07\x80\x80\x80\x80\x01", EncodeUint32(3, 0x10000007));
+ EXPECT_EQ("\x03\x80\x80\x80\x80\x01", EncodeUint32(2, 0x10000003));
+ EXPECT_EQ("\x01\x80\x80\x80\x80\x01", EncodeUint32(1, 0x10000001));
+
+ // Maximums.
+ EXPECT_EQ("\x7f\x80\xff\xff\xff\x0f", EncodeUint32(7, 0xffffffff));
+ EXPECT_EQ("\x3f\xc0\xff\xff\xff\x0f", EncodeUint32(6, 0xffffffff));
+ EXPECT_EQ("\x1f\xe0\xff\xff\xff\x0f", EncodeUint32(5, 0xffffffff));
+ EXPECT_EQ("\x0f\xf0\xff\xff\xff\x0f", EncodeUint32(4, 0xffffffff));
+ EXPECT_EQ("\x07\xf8\xff\xff\xff\x0f", EncodeUint32(3, 0xffffffff));
+ EXPECT_EQ("\x03\xfc\xff\xff\xff\x0f", EncodeUint32(2, 0xffffffff));
+ EXPECT_EQ("\x01\xfe\xff\xff\xff\x0f", EncodeUint32(1, 0xffffffff));
+}
+
+// Test that encoding an integer with an N-bit prefix preserves the
+// upper (8-N) bits of the first byte.
+TEST(HpackOutputStreamTest, AppendUint32PreservesUpperBits) {
+ HpackOutputStream output_stream;
+ output_stream.AppendBits(0x7f, 7);
+ output_stream.AppendUint32(0x01);
+ SpdyString str;
+ output_stream.TakeString(&str);
+ EXPECT_EQ(SpdyString("\xff\x00", 2), str);
+}
+
+TEST(HpackOutputStreamTest, AppendBytes) {
+ HpackOutputStream output_stream;
+
+ output_stream.AppendBytes("buffer1");
+ output_stream.AppendBytes("buffer2");
+
+ SpdyString str;
+ output_stream.TakeString(&str);
+ EXPECT_EQ("buffer1buffer2", str);
+}
+
+TEST(HpackOutputStreamTest, BoundedTakeString) {
+ HpackOutputStream output_stream;
+
+ output_stream.AppendBytes("buffer12");
+ output_stream.AppendBytes("buffer456");
+
+ SpdyString str;
+ output_stream.BoundedTakeString(9, &str);
+ EXPECT_EQ("buffer12b", str);
+
+ output_stream.AppendBits(0x7f, 7);
+ output_stream.AppendUint32(0x11);
+ output_stream.BoundedTakeString(9, &str);
+ EXPECT_EQ("uffer456\xff", str);
+
+ output_stream.BoundedTakeString(9, &str);
+ EXPECT_EQ("\x10", str);
+}
+
+} // namespace
+
+} // namespace spdy
diff --git a/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_round_trip_test.cc b/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_round_trip_test.cc
new file mode 100644
index 00000000000..4b3a8481cb2
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_round_trip_test.cc
@@ -0,0 +1,227 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <cmath>
+#include <cstdint>
+#include <ctime>
+#include <vector>
+
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/http2/test_tools/http2_random.h"
+#include "net/third_party/quiche/src/spdy/core/hpack/hpack_constants.h"
+#include "net/third_party/quiche/src/spdy/core/hpack/hpack_decoder_adapter.h"
+#include "net/third_party/quiche/src/spdy/core/hpack/hpack_encoder.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_test_utils.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_string.h"
+
+namespace spdy {
+namespace test {
+
+namespace {
+
+// Supports testing with the input split at every byte boundary.
+enum InputSizeParam { ALL_INPUT, ONE_BYTE, ZERO_THEN_ONE_BYTE };
+
+class HpackRoundTripTest : public ::testing::TestWithParam<InputSizeParam> {
+ protected:
+ HpackRoundTripTest() : encoder_(ObtainHpackHuffmanTable()), decoder_() {}
+
+ void SetUp() override {
+ // Use a small table size to tickle eviction handling.
+ encoder_.ApplyHeaderTableSizeSetting(256);
+ decoder_.ApplyHeaderTableSizeSetting(256);
+ }
+
+ bool RoundTrip(const SpdyHeaderBlock& header_set) {
+ SpdyString encoded;
+ encoder_.EncodeHeaderSet(header_set, &encoded);
+
+ bool success = true;
+ if (GetParam() == ALL_INPUT) {
+ // Pass all the input to the decoder at once.
+ success = decoder_.HandleControlFrameHeadersData(encoded.data(),
+ encoded.size());
+ } else if (GetParam() == ONE_BYTE) {
+ // Pass the input to the decoder one byte at a time.
+ const char* data = encoded.data();
+ for (size_t ndx = 0; ndx < encoded.size() && success; ++ndx) {
+ success = decoder_.HandleControlFrameHeadersData(data + ndx, 1);
+ }
+ } else if (GetParam() == ZERO_THEN_ONE_BYTE) {
+ // Pass the input to the decoder one byte at a time, but before each
+ // byte pass an empty buffer.
+ const char* data = encoded.data();
+ for (size_t ndx = 0; ndx < encoded.size() && success; ++ndx) {
+ success = (decoder_.HandleControlFrameHeadersData(data + ndx, 0) &&
+ decoder_.HandleControlFrameHeadersData(data + ndx, 1));
+ }
+ } else {
+ ADD_FAILURE() << "Unknown param: " << GetParam();
+ }
+
+ if (success) {
+ success = decoder_.HandleControlFrameHeadersComplete(nullptr);
+ }
+
+ EXPECT_EQ(header_set, decoder_.decoded_block());
+ return success;
+ }
+
+ size_t SampleExponential(size_t mean, size_t sanity_bound) {
+ return std::min<size_t>(-std::log(random_.RandDouble()) * mean,
+ sanity_bound);
+ }
+
+ http2::test::Http2Random random_;
+ HpackEncoder encoder_;
+ HpackDecoderAdapter decoder_;
+};
+
+INSTANTIATE_TEST_CASE_P(Tests,
+ HpackRoundTripTest,
+ ::testing::Values(ALL_INPUT,
+ ONE_BYTE,
+ ZERO_THEN_ONE_BYTE));
+
+TEST_P(HpackRoundTripTest, ResponseFixtures) {
+ {
+ SpdyHeaderBlock headers;
+ headers[":status"] = "302";
+ headers["cache-control"] = "private";
+ headers["date"] = "Mon, 21 Oct 2013 20:13:21 GMT";
+ headers["location"] = "https://www.example.com";
+ EXPECT_TRUE(RoundTrip(headers));
+ }
+ {
+ SpdyHeaderBlock headers;
+ headers[":status"] = "200";
+ headers["cache-control"] = "private";
+ headers["date"] = "Mon, 21 Oct 2013 20:13:21 GMT";
+ headers["location"] = "https://www.example.com";
+ EXPECT_TRUE(RoundTrip(headers));
+ }
+ {
+ SpdyHeaderBlock headers;
+ headers[":status"] = "200";
+ headers["cache-control"] = "private";
+ headers["content-encoding"] = "gzip";
+ headers["date"] = "Mon, 21 Oct 2013 20:13:22 GMT";
+ headers["location"] = "https://www.example.com";
+ headers["set-cookie"] =
+ "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU;"
+ " max-age=3600; version=1";
+ headers["multivalue"] = SpdyString("foo\0bar", 7);
+ EXPECT_TRUE(RoundTrip(headers));
+ }
+}
+
+TEST_P(HpackRoundTripTest, RequestFixtures) {
+ {
+ SpdyHeaderBlock headers;
+ headers[":authority"] = "www.example.com";
+ headers[":method"] = "GET";
+ headers[":path"] = "/";
+ headers[":scheme"] = "http";
+ headers["cookie"] = "baz=bing; foo=bar";
+ EXPECT_TRUE(RoundTrip(headers));
+ }
+ {
+ SpdyHeaderBlock headers;
+ headers[":authority"] = "www.example.com";
+ headers[":method"] = "GET";
+ headers[":path"] = "/";
+ headers[":scheme"] = "http";
+ headers["cache-control"] = "no-cache";
+ headers["cookie"] = "foo=bar; spam=eggs";
+ EXPECT_TRUE(RoundTrip(headers));
+ }
+ {
+ SpdyHeaderBlock headers;
+ headers[":authority"] = "www.example.com";
+ headers[":method"] = "GET";
+ headers[":path"] = "/index.html";
+ headers[":scheme"] = "https";
+ headers["custom-key"] = "custom-value";
+ headers["cookie"] = "baz=bing; fizzle=fazzle; garbage";
+ headers["multivalue"] = SpdyString("foo\0bar", 7);
+ EXPECT_TRUE(RoundTrip(headers));
+ }
+}
+
+TEST_P(HpackRoundTripTest, RandomizedExamples) {
+ // Grow vectors of names & values, which are seeded with fixtures and then
+ // expanded with dynamically generated data. Samples are taken using the
+ // exponential distribution.
+ std::vector<SpdyString> pseudo_header_names, random_header_names;
+ pseudo_header_names.push_back(":authority");
+ pseudo_header_names.push_back(":path");
+ pseudo_header_names.push_back(":status");
+
+ // TODO(jgraettinger): Enable "cookie" as a name fixture. Crumbs may be
+ // reconstructed in any order, which breaks the simple validation used here.
+
+ std::vector<SpdyString> values;
+ values.push_back("/");
+ values.push_back("/index.html");
+ values.push_back("200");
+ values.push_back("404");
+ values.push_back("");
+ values.push_back("baz=bing; foo=bar; garbage");
+ values.push_back("baz=bing; fizzle=fazzle; garbage");
+
+ for (size_t i = 0; i != 2000; ++i) {
+ SpdyHeaderBlock headers;
+
+ // Choose a random number of headers to add, and of these a random subset
+ // will be HTTP/2 pseudo headers.
+ size_t header_count = 1 + SampleExponential(7, 50);
+ size_t pseudo_header_count =
+ std::min(header_count, 1 + SampleExponential(7, 50));
+ EXPECT_LE(pseudo_header_count, header_count);
+ for (size_t j = 0; j != header_count; ++j) {
+ SpdyString name, value;
+ // Pseudo headers must be added before regular headers.
+ if (j < pseudo_header_count) {
+ // Choose one of the defined pseudo headers at random.
+ size_t name_index = random_.Uniform(pseudo_header_names.size());
+ name = pseudo_header_names[name_index];
+ } else {
+ // Randomly reuse an existing header name, or generate a new one.
+ size_t name_index = SampleExponential(20, 200);
+ if (name_index >= random_header_names.size()) {
+ name = random_.RandString(1 + SampleExponential(5, 30));
+ // A regular header cannot begin with the pseudo header prefix ":".
+ if (name[0] == ':') {
+ name[0] = 'x';
+ }
+ random_header_names.push_back(name);
+ } else {
+ name = random_header_names[name_index];
+ }
+ }
+
+ // Randomly reuse an existing value, or generate a new one.
+ size_t value_index = SampleExponential(20, 200);
+ if (value_index >= values.size()) {
+ SpdyString newvalue = random_.RandString(1 + SampleExponential(15, 75));
+ // Currently order is not preserved in the encoder. In particular,
+ // when a value is decomposed at \0 delimiters, its parts might get
+ // encoded out of order if some but not all of them already exist in
+ // the header table. For now, avoid \0 bytes in values.
+ std::replace(newvalue.begin(), newvalue.end(), '\x00', '\x01');
+ values.push_back(newvalue);
+ value = values.back();
+ } else {
+ value = values[value_index];
+ }
+ headers[name] = value;
+ }
+ EXPECT_TRUE(RoundTrip(headers));
+ }
+}
+
+} // namespace
+
+} // namespace test
+} // namespace spdy
diff --git a/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_static_table.cc b/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_static_table.cc
new file mode 100644
index 00000000000..14e282ec5ff
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_static_table.cc
@@ -0,0 +1,50 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/spdy/core/hpack/hpack_static_table.h"
+
+#include "base/logging.h"
+#include "net/third_party/quiche/src/spdy/core/hpack/hpack_constants.h"
+#include "net/third_party/quiche/src/spdy/core/hpack/hpack_entry.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_estimate_memory_usage.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_string_piece.h"
+
+namespace spdy {
+
+HpackStaticTable::HpackStaticTable() = default;
+
+HpackStaticTable::~HpackStaticTable() = default;
+
+void HpackStaticTable::Initialize(const HpackStaticEntry* static_entry_table,
+ size_t static_entry_count) {
+ CHECK(!IsInitialized());
+
+ int total_insertions = 0;
+ for (const HpackStaticEntry* it = static_entry_table;
+ it != static_entry_table + static_entry_count; ++it) {
+ static_entries_.push_back(
+ HpackEntry(SpdyStringPiece(it->name, it->name_len),
+ SpdyStringPiece(it->value, it->value_len),
+ true, // is_static
+ total_insertions));
+ HpackEntry* entry = &static_entries_.back();
+ CHECK(static_index_.insert(entry).second);
+ // Multiple static entries may have the same name, so inserts may fail.
+ static_name_index_.insert(std::make_pair(entry->name(), entry));
+
+ ++total_insertions;
+ }
+}
+
+bool HpackStaticTable::IsInitialized() const {
+ return !static_entries_.empty();
+}
+
+size_t HpackStaticTable::EstimateMemoryUsage() const {
+ return SpdyEstimateMemoryUsage(static_entries_) +
+ SpdyEstimateMemoryUsage(static_index_) +
+ SpdyEstimateMemoryUsage(static_name_index_);
+}
+
+} // namespace spdy
diff --git a/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_static_table.h b/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_static_table.h
new file mode 100644
index 00000000000..f354a120ca5
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_static_table.h
@@ -0,0 +1,54 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_SPDY_CORE_HPACK_HPACK_STATIC_TABLE_H_
+#define QUICHE_SPDY_CORE_HPACK_HPACK_STATIC_TABLE_H_
+
+#include "net/third_party/quiche/src/spdy/core/hpack/hpack_header_table.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_export.h"
+
+namespace spdy {
+
+struct HpackStaticEntry;
+
+// HpackStaticTable provides |static_entries_| and |static_index_| for HPACK
+// encoding and decoding contexts. Once initialized, an instance is read only
+// and may be accessed only through its const interface. Such an instance may
+// be shared accross multiple HPACK contexts.
+class SPDY_EXPORT_PRIVATE HpackStaticTable {
+ public:
+ HpackStaticTable();
+ ~HpackStaticTable();
+
+ // Prepares HpackStaticTable by filling up static_entries_ and static_index_
+ // from an array of struct HpackStaticEntry. Must be called exactly once.
+ void Initialize(const HpackStaticEntry* static_entry_table,
+ size_t static_entry_count);
+
+ // Returns whether Initialize() has been called.
+ bool IsInitialized() const;
+
+ // Accessors.
+ const HpackHeaderTable::EntryTable& GetStaticEntries() const {
+ return static_entries_;
+ }
+ const HpackHeaderTable::UnorderedEntrySet& GetStaticIndex() const {
+ return static_index_;
+ }
+ const HpackHeaderTable::NameToEntryMap& GetStaticNameIndex() const {
+ return static_name_index_;
+ }
+
+ // Returns the estimate of dynamically allocated memory in bytes.
+ size_t EstimateMemoryUsage() const;
+
+ private:
+ HpackHeaderTable::EntryTable static_entries_;
+ HpackHeaderTable::UnorderedEntrySet static_index_;
+ HpackHeaderTable::NameToEntryMap static_name_index_;
+};
+
+} // namespace spdy
+
+#endif // QUICHE_SPDY_CORE_HPACK_HPACK_STATIC_TABLE_H_
diff --git a/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_static_table_test.cc b/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_static_table_test.cc
new file mode 100644
index 00000000000..4550be018a6
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/spdy/core/hpack/hpack_static_table_test.cc
@@ -0,0 +1,60 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/spdy/core/hpack/hpack_static_table.h"
+
+#include <set>
+#include <vector>
+
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/spdy/core/hpack/hpack_constants.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_string_piece.h"
+
+namespace spdy {
+
+namespace test {
+
+namespace {
+
+class HpackStaticTableTest : public ::testing::Test {
+ protected:
+ HpackStaticTableTest() : table_() {}
+
+ HpackStaticTable table_;
+};
+
+// Check that an initialized instance has the right number of entries.
+TEST_F(HpackStaticTableTest, Initialize) {
+ EXPECT_FALSE(table_.IsInitialized());
+ table_.Initialize(HpackStaticTableVector().data(),
+ HpackStaticTableVector().size());
+ EXPECT_TRUE(table_.IsInitialized());
+
+ HpackHeaderTable::EntryTable static_entries = table_.GetStaticEntries();
+ EXPECT_EQ(HpackStaticTableVector().size(), static_entries.size());
+
+ HpackHeaderTable::UnorderedEntrySet static_index = table_.GetStaticIndex();
+ EXPECT_EQ(HpackStaticTableVector().size(), static_index.size());
+
+ HpackHeaderTable::NameToEntryMap static_name_index =
+ table_.GetStaticNameIndex();
+ std::set<SpdyStringPiece> names;
+ for (auto* entry : static_index) {
+ names.insert(entry->name());
+ }
+ EXPECT_EQ(names.size(), static_name_index.size());
+}
+
+// Test that ObtainHpackStaticTable returns the same instance every time.
+TEST_F(HpackStaticTableTest, IsSingleton) {
+ const HpackStaticTable* static_table_one = &ObtainHpackStaticTable();
+ const HpackStaticTable* static_table_two = &ObtainHpackStaticTable();
+ EXPECT_EQ(static_table_one, static_table_two);
+}
+
+} // namespace
+
+} // namespace test
+
+} // namespace spdy
diff --git a/chromium/net/third_party/quiche/src/spdy/core/http2_frame_decoder_adapter.cc b/chromium/net/third_party/quiche/src/spdy/core/http2_frame_decoder_adapter.cc
new file mode 100644
index 00000000000..c4e728d2334
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/spdy/core/http2_frame_decoder_adapter.cc
@@ -0,0 +1,1022 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/spdy/core/http2_frame_decoder_adapter.h"
+
+// Logging policy: If an error in the input is detected, VLOG(n) is used so that
+// the option exists to debug the situation. Otherwise, this code mostly uses
+// DVLOG so that the logging does not slow down production code when things are
+// working OK.
+
+#include <stddef.h>
+
+#include <cstdint>
+#include <cstring>
+#include <utility>
+
+#include "base/logging.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_buffer.h"
+#include "net/third_party/quiche/src/http2/decoder/decode_status.h"
+#include "net/third_party/quiche/src/http2/decoder/http2_frame_decoder.h"
+#include "net/third_party/quiche/src/http2/decoder/http2_frame_decoder_listener.h"
+#include "net/third_party/quiche/src/http2/http2_constants.h"
+#include "net/third_party/quiche/src/http2/http2_structures.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_string.h"
+#include "net/third_party/quiche/src/spdy/core/hpack/hpack_decoder_adapter.h"
+#include "net/third_party/quiche/src/spdy/core/hpack/hpack_header_table.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_alt_svc_wire_format.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_header_block.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_headers_handler_interface.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_protocol.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_bug_tracker.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_endianness_util.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_estimate_memory_usage.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_flags.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_ptr_util.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_string_utils.h"
+
+using ::spdy::ExtensionVisitorInterface;
+using ::spdy::HpackDecoderAdapter;
+using ::spdy::HpackHeaderTable;
+using ::spdy::ParseErrorCode;
+using ::spdy::ParseFrameType;
+using ::spdy::SpdyAltSvcWireFormat;
+using ::spdy::SpdyErrorCode;
+using ::spdy::SpdyEstimateMemoryUsage;
+using ::spdy::SpdyFramerDebugVisitorInterface;
+using ::spdy::SpdyFramerVisitorInterface;
+using ::spdy::SpdyFrameType;
+using ::spdy::SpdyHeadersHandlerInterface;
+using ::spdy::SpdyKnownSettingsId;
+using ::spdy::SpdyMakeUnique;
+using ::spdy::SpdySettingsId;
+
+namespace http2 {
+namespace {
+
+const bool kHasPriorityFields = true;
+const bool kNotHasPriorityFields = false;
+
+bool IsPaddable(Http2FrameType type) {
+ return type == Http2FrameType::DATA || type == Http2FrameType::HEADERS ||
+ type == Http2FrameType::PUSH_PROMISE;
+}
+
+SpdyFrameType ToSpdyFrameType(Http2FrameType type) {
+ return ParseFrameType(static_cast<uint8_t>(type));
+}
+
+uint64_t ToSpdyPingId(const Http2PingFields& ping) {
+ uint64_t v;
+ std::memcpy(&v, ping.opaque_bytes, Http2PingFields::EncodedSize());
+ return spdy::SpdyNetToHost64(v);
+}
+
+// Overwrites the fields of the header with invalid values, for the purpose
+// of identifying reading of unset fields. Only takes effect for debug builds.
+// In Address Sanatizer builds, it also marks the fields as un-readable.
+void CorruptFrameHeader(Http2FrameHeader* header) {
+#ifndef NDEBUG
+ // Beyond a valid payload length, which is 2^24 - 1.
+ header->payload_length = 0x1010dead;
+ // An unsupported frame type.
+ header->type = Http2FrameType(0x80);
+ DCHECK(!IsSupportedHttp2FrameType(header->type));
+ // Frame flag bits that aren't used by any supported frame type.
+ header->flags = Http2FrameFlag(0xd2);
+ // A stream id with the reserved high-bit (R in the RFC) set.
+ // 2129510127 when the high-bit is cleared.
+ header->stream_id = 0xfeedbeef;
+#endif
+}
+
+} // namespace
+
+const char* Http2DecoderAdapter::StateToString(int state) {
+ switch (state) {
+ case SPDY_ERROR:
+ return "ERROR";
+ case SPDY_FRAME_COMPLETE:
+ return "FRAME_COMPLETE";
+ case SPDY_READY_FOR_FRAME:
+ return "READY_FOR_FRAME";
+ case SPDY_READING_COMMON_HEADER:
+ return "READING_COMMON_HEADER";
+ case SPDY_CONTROL_FRAME_PAYLOAD:
+ return "CONTROL_FRAME_PAYLOAD";
+ case SPDY_READ_DATA_FRAME_PADDING_LENGTH:
+ return "SPDY_READ_DATA_FRAME_PADDING_LENGTH";
+ case SPDY_CONSUME_PADDING:
+ return "SPDY_CONSUME_PADDING";
+ case SPDY_IGNORE_REMAINING_PAYLOAD:
+ return "IGNORE_REMAINING_PAYLOAD";
+ case SPDY_FORWARD_STREAM_FRAME:
+ return "FORWARD_STREAM_FRAME";
+ case SPDY_CONTROL_FRAME_BEFORE_HEADER_BLOCK:
+ return "SPDY_CONTROL_FRAME_BEFORE_HEADER_BLOCK";
+ case SPDY_CONTROL_FRAME_HEADER_BLOCK:
+ return "SPDY_CONTROL_FRAME_HEADER_BLOCK";
+ case SPDY_GOAWAY_FRAME_PAYLOAD:
+ return "SPDY_GOAWAY_FRAME_PAYLOAD";
+ case SPDY_SETTINGS_FRAME_HEADER:
+ return "SPDY_SETTINGS_FRAME_HEADER";
+ case SPDY_SETTINGS_FRAME_PAYLOAD:
+ return "SPDY_SETTINGS_FRAME_PAYLOAD";
+ case SPDY_ALTSVC_FRAME_PAYLOAD:
+ return "SPDY_ALTSVC_FRAME_PAYLOAD";
+ }
+ return "UNKNOWN_STATE";
+}
+
+const char* Http2DecoderAdapter::SpdyFramerErrorToString(
+ SpdyFramerError spdy_framer_error) {
+ switch (spdy_framer_error) {
+ case SPDY_NO_ERROR:
+ return "NO_ERROR";
+ case SPDY_INVALID_STREAM_ID:
+ return "INVALID_STREAM_ID";
+ case SPDY_INVALID_CONTROL_FRAME:
+ return "INVALID_CONTROL_FRAME";
+ case SPDY_CONTROL_PAYLOAD_TOO_LARGE:
+ return "CONTROL_PAYLOAD_TOO_LARGE";
+ case SPDY_ZLIB_INIT_FAILURE:
+ return "ZLIB_INIT_FAILURE";
+ case SPDY_UNSUPPORTED_VERSION:
+ return "UNSUPPORTED_VERSION";
+ case SPDY_DECOMPRESS_FAILURE:
+ return "DECOMPRESS_FAILURE";
+ case SPDY_COMPRESS_FAILURE:
+ return "COMPRESS_FAILURE";
+ case SPDY_GOAWAY_FRAME_CORRUPT:
+ return "GOAWAY_FRAME_CORRUPT";
+ case SPDY_RST_STREAM_FRAME_CORRUPT:
+ return "RST_STREAM_FRAME_CORRUPT";
+ case SPDY_INVALID_PADDING:
+ return "INVALID_PADDING";
+ case SPDY_INVALID_DATA_FRAME_FLAGS:
+ return "INVALID_DATA_FRAME_FLAGS";
+ case SPDY_INVALID_CONTROL_FRAME_FLAGS:
+ return "INVALID_CONTROL_FRAME_FLAGS";
+ case SPDY_UNEXPECTED_FRAME:
+ return "UNEXPECTED_FRAME";
+ case SPDY_INTERNAL_FRAMER_ERROR:
+ return "INTERNAL_FRAMER_ERROR";
+ case SPDY_INVALID_CONTROL_FRAME_SIZE:
+ return "INVALID_CONTROL_FRAME_SIZE";
+ case SPDY_OVERSIZED_PAYLOAD:
+ return "OVERSIZED_PAYLOAD";
+ case LAST_ERROR:
+ return "UNKNOWN_ERROR";
+ }
+ return "UNKNOWN_ERROR";
+}
+
+Http2DecoderAdapter::Http2DecoderAdapter() {
+ DVLOG(1) << "Http2DecoderAdapter ctor";
+ ResetInternal();
+}
+
+Http2DecoderAdapter::~Http2DecoderAdapter() = default;
+
+void Http2DecoderAdapter::set_visitor(SpdyFramerVisitorInterface* visitor) {
+ visitor_ = visitor;
+}
+
+void Http2DecoderAdapter::set_debug_visitor(
+ SpdyFramerDebugVisitorInterface* debug_visitor) {
+ debug_visitor_ = debug_visitor;
+}
+
+void Http2DecoderAdapter::set_process_single_input_frame(bool v) {
+ process_single_input_frame_ = v;
+}
+
+void Http2DecoderAdapter::set_extension_visitor(
+ ExtensionVisitorInterface* visitor) {
+ extension_ = visitor;
+}
+
+// Passes the call on to the HPACK decoder.
+void Http2DecoderAdapter::SetDecoderHeaderTableDebugVisitor(
+ std::unique_ptr<HpackHeaderTable::DebugVisitorInterface> visitor) {
+ GetHpackDecoder()->SetHeaderTableDebugVisitor(std::move(visitor));
+}
+
+size_t Http2DecoderAdapter::ProcessInput(const char* data, size_t len) {
+ size_t limit = recv_frame_size_limit_;
+ frame_decoder_->set_maximum_payload_size(limit);
+
+ size_t total_processed = 0;
+ while (len > 0 && spdy_state_ != SPDY_ERROR) {
+ // Process one at a time so that we update the adapter's internal
+ // state appropriately.
+ const size_t processed = ProcessInputFrame(data, len);
+
+ // We had some data, and weren't in an error state, so should have
+ // processed/consumed at least one byte of it, even if we then ended up
+ // in an error state.
+ DCHECK(processed > 0) << "processed=" << processed
+ << " spdy_state_=" << spdy_state_
+ << " spdy_framer_error_=" << spdy_framer_error_;
+
+ data += processed;
+ len -= processed;
+ total_processed += processed;
+ if (process_single_input_frame() || processed == 0) {
+ break;
+ }
+ }
+ return total_processed;
+}
+
+void Http2DecoderAdapter::Reset() {
+ ResetInternal();
+}
+
+Http2DecoderAdapter::SpdyState Http2DecoderAdapter::state() const {
+ return spdy_state_;
+}
+
+Http2DecoderAdapter::SpdyFramerError Http2DecoderAdapter::spdy_framer_error()
+ const {
+ return spdy_framer_error_;
+}
+
+bool Http2DecoderAdapter::probable_http_response() const {
+ return latched_probable_http_response_;
+}
+
+size_t Http2DecoderAdapter::EstimateMemoryUsage() const {
+ // Skip |frame_decoder_|, |frame_header_| and |hpack_first_frame_header_| as
+ // they don't allocate.
+ return SpdyEstimateMemoryUsage(alt_svc_origin_) +
+ SpdyEstimateMemoryUsage(alt_svc_value_);
+}
+
+// ===========================================================================
+// Implementations of the methods declared by Http2FrameDecoderListener.
+
+// Called once the common frame header has been decoded for any frame.
+// This function is largely based on Http2DecoderAdapter::ValidateFrameHeader
+// and some parts of Http2DecoderAdapter::ProcessCommonHeader.
+bool Http2DecoderAdapter::OnFrameHeader(const Http2FrameHeader& header) {
+ DVLOG(1) << "OnFrameHeader: " << header;
+ decoded_frame_header_ = true;
+ if (!latched_probable_http_response_) {
+ latched_probable_http_response_ = header.IsProbableHttpResponse();
+ }
+ const uint8_t raw_frame_type = static_cast<uint8_t>(header.type);
+ visitor()->OnCommonHeader(header.stream_id, header.payload_length,
+ raw_frame_type, header.flags);
+ if (has_expected_frame_type_ && header.type != expected_frame_type_) {
+ // Report an unexpected frame error and close the connection if we
+ // expect a known frame type (probably CONTINUATION) and receive an
+ // unknown frame.
+ VLOG(1) << "The framer was expecting to receive a " << expected_frame_type_
+ << " frame, but instead received an unknown frame of type "
+ << header.type;
+ SetSpdyErrorAndNotify(SpdyFramerError::SPDY_UNEXPECTED_FRAME);
+ return false;
+ }
+ if (!IsSupportedHttp2FrameType(header.type)) {
+ if (extension_ != nullptr) {
+ // Unknown frames will be passed to the registered extension.
+ return true;
+ }
+ // In HTTP2 we ignore unknown frame types for extensibility, as long as
+ // the rest of the control frame header is valid.
+ // We rely on the visitor to check validity of stream_id.
+ bool valid_stream =
+ visitor()->OnUnknownFrame(header.stream_id, raw_frame_type);
+ if (!valid_stream) {
+ // Report an invalid frame error if the stream_id is not valid.
+ VLOG(1) << "Unknown control frame type " << header.type
+ << " received on invalid stream " << header.stream_id;
+ SetSpdyErrorAndNotify(SpdyFramerError::SPDY_INVALID_CONTROL_FRAME);
+ return false;
+ } else {
+ DVLOG(1) << "Ignoring unknown frame type " << header.type;
+ return true;
+ }
+ }
+
+ SpdyFrameType frame_type = ToSpdyFrameType(header.type);
+ if (!IsValidHTTP2FrameStreamId(header.stream_id, frame_type)) {
+ VLOG(1) << "The framer received an invalid streamID of " << header.stream_id
+ << " for a frame of type " << header.type;
+ SetSpdyErrorAndNotify(SpdyFramerError::SPDY_INVALID_STREAM_ID);
+ return false;
+ }
+
+ if (has_expected_frame_type_ && header.type != expected_frame_type_) {
+ VLOG(1) << "Expected frame type " << expected_frame_type_ << ", not "
+ << header.type;
+ SetSpdyErrorAndNotify(SpdyFramerError::SPDY_UNEXPECTED_FRAME);
+ return false;
+ }
+
+ if (!has_expected_frame_type_ &&
+ header.type == Http2FrameType::CONTINUATION) {
+ VLOG(1) << "Got CONTINUATION frame when not expected.";
+ SetSpdyErrorAndNotify(SpdyFramerError::SPDY_UNEXPECTED_FRAME);
+ return false;
+ }
+
+ if (header.type == Http2FrameType::DATA) {
+ // For some reason SpdyFramer still rejects invalid DATA frame flags.
+ uint8_t valid_flags = Http2FrameFlag::PADDED | Http2FrameFlag::END_STREAM;
+ if (header.HasAnyFlags(~valid_flags)) {
+ SetSpdyErrorAndNotify(SpdyFramerError::SPDY_INVALID_DATA_FRAME_FLAGS);
+ return false;
+ }
+ }
+
+ return true;
+}
+
+void Http2DecoderAdapter::OnDataStart(const Http2FrameHeader& header) {
+ DVLOG(1) << "OnDataStart: " << header;
+
+ if (IsOkToStartFrame(header) && HasRequiredStreamId(header)) {
+ frame_header_ = header;
+ has_frame_header_ = true;
+ visitor()->OnDataFrameHeader(header.stream_id, header.payload_length,
+ header.IsEndStream());
+ }
+}
+
+void Http2DecoderAdapter::OnDataPayload(const char* data, size_t len) {
+ DVLOG(1) << "OnDataPayload: len=" << len;
+ DCHECK(has_frame_header_);
+ DCHECK_EQ(frame_header_.type, Http2FrameType::DATA);
+ visitor()->OnStreamFrameData(frame_header().stream_id, data, len);
+}
+
+void Http2DecoderAdapter::OnDataEnd() {
+ DVLOG(1) << "OnDataEnd";
+ DCHECK(has_frame_header_);
+ DCHECK_EQ(frame_header_.type, Http2FrameType::DATA);
+ if (frame_header().IsEndStream()) {
+ visitor()->OnStreamEnd(frame_header().stream_id);
+ }
+ opt_pad_length_.reset();
+}
+
+void Http2DecoderAdapter::OnHeadersStart(const Http2FrameHeader& header) {
+ DVLOG(1) << "OnHeadersStart: " << header;
+ if (IsOkToStartFrame(header) && HasRequiredStreamId(header)) {
+ frame_header_ = header;
+ has_frame_header_ = true;
+ if (header.HasPriority()) {
+ // Once we've got the priority fields, then we can report the arrival
+ // of this HEADERS frame.
+ on_headers_called_ = false;
+ return;
+ }
+ on_headers_called_ = true;
+ ReportReceiveCompressedFrame(header);
+ visitor()->OnHeaders(header.stream_id, kNotHasPriorityFields,
+ 0, // priority
+ 0, // parent_stream_id
+ false, // exclusive
+ header.IsEndStream(), header.IsEndHeaders());
+ CommonStartHpackBlock();
+ }
+}
+
+void Http2DecoderAdapter::OnHeadersPriority(
+ const Http2PriorityFields& priority) {
+ DVLOG(1) << "OnHeadersPriority: " << priority;
+ DCHECK(has_frame_header_);
+ DCHECK_EQ(frame_type(), Http2FrameType::HEADERS) << frame_header_;
+ DCHECK(frame_header_.HasPriority());
+ DCHECK(!on_headers_called_);
+ on_headers_called_ = true;
+ ReportReceiveCompressedFrame(frame_header_);
+ visitor()->OnHeaders(frame_header_.stream_id, kHasPriorityFields,
+ priority.weight, priority.stream_dependency,
+ priority.is_exclusive, frame_header_.IsEndStream(),
+ frame_header_.IsEndHeaders());
+ CommonStartHpackBlock();
+}
+
+void Http2DecoderAdapter::OnHpackFragment(const char* data, size_t len) {
+ DVLOG(1) << "OnHpackFragment: len=" << len;
+ on_hpack_fragment_called_ = true;
+ if (!GetHpackDecoder()->HandleControlFrameHeadersData(data, len)) {
+ SetSpdyErrorAndNotify(SpdyFramerError::SPDY_DECOMPRESS_FAILURE);
+ return;
+ }
+}
+
+void Http2DecoderAdapter::OnHeadersEnd() {
+ DVLOG(1) << "OnHeadersEnd";
+ CommonHpackFragmentEnd();
+ opt_pad_length_.reset();
+}
+
+void Http2DecoderAdapter::OnPriorityFrame(const Http2FrameHeader& header,
+ const Http2PriorityFields& priority) {
+ DVLOG(1) << "OnPriorityFrame: " << header << "; priority: " << priority;
+ if (IsOkToStartFrame(header) && HasRequiredStreamId(header)) {
+ visitor()->OnPriority(header.stream_id, priority.stream_dependency,
+ priority.weight, priority.is_exclusive);
+ }
+}
+
+void Http2DecoderAdapter::OnContinuationStart(const Http2FrameHeader& header) {
+ DVLOG(1) << "OnContinuationStart: " << header;
+ if (IsOkToStartFrame(header) && HasRequiredStreamId(header)) {
+ DCHECK(has_hpack_first_frame_header_);
+ if (header.stream_id != hpack_first_frame_header_.stream_id) {
+ SetSpdyErrorAndNotify(SpdyFramerError::SPDY_UNEXPECTED_FRAME);
+ return;
+ }
+ frame_header_ = header;
+ has_frame_header_ = true;
+ ReportReceiveCompressedFrame(header);
+ visitor()->OnContinuation(header.stream_id, header.IsEndHeaders());
+ }
+}
+
+void Http2DecoderAdapter::OnContinuationEnd() {
+ DVLOG(1) << "OnContinuationEnd";
+ CommonHpackFragmentEnd();
+}
+
+void Http2DecoderAdapter::OnPadLength(size_t trailing_length) {
+ DVLOG(1) << "OnPadLength: " << trailing_length;
+ opt_pad_length_ = trailing_length;
+ DCHECK_LT(trailing_length, 256u);
+ if (frame_header_.type == Http2FrameType::DATA) {
+ visitor()->OnStreamPadLength(stream_id(), trailing_length);
+ }
+}
+
+void Http2DecoderAdapter::OnPadding(const char* padding,
+ size_t skipped_length) {
+ DVLOG(1) << "OnPadding: " << skipped_length;
+ if (frame_header_.type == Http2FrameType::DATA) {
+ visitor()->OnStreamPadding(stream_id(), skipped_length);
+ } else {
+ MaybeAnnounceEmptyFirstHpackFragment();
+ }
+}
+
+void Http2DecoderAdapter::OnRstStream(const Http2FrameHeader& header,
+ Http2ErrorCode http2_error_code) {
+ DVLOG(1) << "OnRstStream: " << header << "; code=" << http2_error_code;
+ if (IsOkToStartFrame(header) && HasRequiredStreamId(header)) {
+ SpdyErrorCode error_code =
+ ParseErrorCode(static_cast<uint32_t>(http2_error_code));
+ visitor()->OnRstStream(header.stream_id, error_code);
+ }
+}
+
+void Http2DecoderAdapter::OnSettingsStart(const Http2FrameHeader& header) {
+ DVLOG(1) << "OnSettingsStart: " << header;
+ if (IsOkToStartFrame(header) && HasRequiredStreamIdZero(header)) {
+ frame_header_ = header;
+ has_frame_header_ = true;
+ visitor()->OnSettings();
+ }
+}
+
+void Http2DecoderAdapter::OnSetting(const Http2SettingFields& setting_fields) {
+ DVLOG(1) << "OnSetting: " << setting_fields;
+ const auto parameter = static_cast<SpdySettingsId>(setting_fields.parameter);
+ visitor()->OnSetting(parameter, setting_fields.value);
+ if (extension_ != nullptr) {
+ extension_->OnSetting(parameter, setting_fields.value);
+ }
+}
+
+void Http2DecoderAdapter::OnSettingsEnd() {
+ DVLOG(1) << "OnSettingsEnd";
+ visitor()->OnSettingsEnd();
+}
+
+void Http2DecoderAdapter::OnSettingsAck(const Http2FrameHeader& header) {
+ DVLOG(1) << "OnSettingsAck: " << header;
+ if (IsOkToStartFrame(header) && HasRequiredStreamIdZero(header)) {
+ visitor()->OnSettingsAck();
+ }
+}
+
+void Http2DecoderAdapter::OnPushPromiseStart(
+ const Http2FrameHeader& header,
+ const Http2PushPromiseFields& promise,
+ size_t total_padding_length) {
+ DVLOG(1) << "OnPushPromiseStart: " << header << "; promise: " << promise
+ << "; total_padding_length: " << total_padding_length;
+ if (IsOkToStartFrame(header) && HasRequiredStreamId(header)) {
+ if (promise.promised_stream_id == 0) {
+ SetSpdyErrorAndNotify(SpdyFramerError::SPDY_INVALID_CONTROL_FRAME);
+ return;
+ }
+ frame_header_ = header;
+ has_frame_header_ = true;
+ ReportReceiveCompressedFrame(header);
+ visitor()->OnPushPromise(header.stream_id, promise.promised_stream_id,
+ header.IsEndHeaders());
+ CommonStartHpackBlock();
+ }
+}
+
+void Http2DecoderAdapter::OnPushPromiseEnd() {
+ DVLOG(1) << "OnPushPromiseEnd";
+ CommonHpackFragmentEnd();
+ opt_pad_length_.reset();
+}
+
+void Http2DecoderAdapter::OnPing(const Http2FrameHeader& header,
+ const Http2PingFields& ping) {
+ DVLOG(1) << "OnPing: " << header << "; ping: " << ping;
+ if (IsOkToStartFrame(header) && HasRequiredStreamIdZero(header)) {
+ visitor()->OnPing(ToSpdyPingId(ping), false);
+ }
+}
+
+void Http2DecoderAdapter::OnPingAck(const Http2FrameHeader& header,
+ const Http2PingFields& ping) {
+ DVLOG(1) << "OnPingAck: " << header << "; ping: " << ping;
+ if (IsOkToStartFrame(header) && HasRequiredStreamIdZero(header)) {
+ visitor()->OnPing(ToSpdyPingId(ping), true);
+ }
+}
+
+void Http2DecoderAdapter::OnGoAwayStart(const Http2FrameHeader& header,
+ const Http2GoAwayFields& goaway) {
+ DVLOG(1) << "OnGoAwayStart: " << header << "; goaway: " << goaway;
+ if (IsOkToStartFrame(header) && HasRequiredStreamIdZero(header)) {
+ frame_header_ = header;
+ has_frame_header_ = true;
+ SpdyErrorCode error_code =
+ ParseErrorCode(static_cast<uint32_t>(goaway.error_code));
+ visitor()->OnGoAway(goaway.last_stream_id, error_code);
+ }
+}
+
+void Http2DecoderAdapter::OnGoAwayOpaqueData(const char* data, size_t len) {
+ DVLOG(1) << "OnGoAwayOpaqueData: len=" << len;
+ visitor()->OnGoAwayFrameData(data, len);
+}
+
+void Http2DecoderAdapter::OnGoAwayEnd() {
+ DVLOG(1) << "OnGoAwayEnd";
+ visitor()->OnGoAwayFrameData(nullptr, 0);
+}
+
+void Http2DecoderAdapter::OnWindowUpdate(const Http2FrameHeader& header,
+ uint32_t increment) {
+ DVLOG(1) << "OnWindowUpdate: " << header << "; increment=" << increment;
+ if (IsOkToStartFrame(header)) {
+ visitor()->OnWindowUpdate(header.stream_id, increment);
+ }
+}
+
+// Per RFC7838, an ALTSVC frame on stream 0 with origin_length == 0, or one on
+// a stream other than stream 0 with origin_length != 0 MUST be ignored. All
+// frames are decoded by Http2DecoderAdapter, and it is left to the consumer
+// (listener) to implement this behavior.
+void Http2DecoderAdapter::OnAltSvcStart(const Http2FrameHeader& header,
+ size_t origin_length,
+ size_t value_length) {
+ DVLOG(1) << "OnAltSvcStart: " << header
+ << "; origin_length: " << origin_length
+ << "; value_length: " << value_length;
+ if (!IsOkToStartFrame(header)) {
+ return;
+ }
+ frame_header_ = header;
+ has_frame_header_ = true;
+ alt_svc_origin_.clear();
+ alt_svc_value_.clear();
+}
+
+void Http2DecoderAdapter::OnAltSvcOriginData(const char* data, size_t len) {
+ DVLOG(1) << "OnAltSvcOriginData: len=" << len;
+ alt_svc_origin_.append(data, len);
+}
+
+// Called when decoding the Alt-Svc-Field-Value of an ALTSVC;
+// the field is uninterpreted.
+void Http2DecoderAdapter::OnAltSvcValueData(const char* data, size_t len) {
+ DVLOG(1) << "OnAltSvcValueData: len=" << len;
+ alt_svc_value_.append(data, len);
+}
+
+void Http2DecoderAdapter::OnAltSvcEnd() {
+ DVLOG(1) << "OnAltSvcEnd: origin.size(): " << alt_svc_origin_.size()
+ << "; value.size(): " << alt_svc_value_.size();
+ SpdyAltSvcWireFormat::AlternativeServiceVector altsvc_vector;
+ if (!SpdyAltSvcWireFormat::ParseHeaderFieldValue(alt_svc_value_,
+ &altsvc_vector)) {
+ DLOG(ERROR) << "SpdyAltSvcWireFormat::ParseHeaderFieldValue failed.";
+ SetSpdyErrorAndNotify(SpdyFramerError::SPDY_INVALID_CONTROL_FRAME);
+ return;
+ }
+ visitor()->OnAltSvc(frame_header_.stream_id, alt_svc_origin_, altsvc_vector);
+ // We assume that ALTSVC frames are rare, so get rid of the storage.
+ alt_svc_origin_.clear();
+ alt_svc_origin_.shrink_to_fit();
+ alt_svc_value_.clear();
+ alt_svc_value_.shrink_to_fit();
+}
+
+// Except for BLOCKED frames, all other unknown frames are either dropped or
+// passed to a registered extension.
+void Http2DecoderAdapter::OnUnknownStart(const Http2FrameHeader& header) {
+ DVLOG(1) << "OnUnknownStart: " << header;
+ if (IsOkToStartFrame(header)) {
+ if (extension_ != nullptr) {
+ const uint8_t type = static_cast<uint8_t>(header.type);
+ const uint8_t flags = static_cast<uint8_t>(header.flags);
+ handling_extension_payload_ = extension_->OnFrameHeader(
+ header.stream_id, header.payload_length, type, flags);
+ }
+ }
+}
+
+void Http2DecoderAdapter::OnUnknownPayload(const char* data, size_t len) {
+ if (handling_extension_payload_) {
+ extension_->OnFramePayload(data, len);
+ } else {
+ DVLOG(1) << "OnUnknownPayload: len=" << len;
+ }
+}
+
+void Http2DecoderAdapter::OnUnknownEnd() {
+ DVLOG(1) << "OnUnknownEnd";
+ handling_extension_payload_ = false;
+}
+
+void Http2DecoderAdapter::OnPaddingTooLong(const Http2FrameHeader& header,
+ size_t missing_length) {
+ DVLOG(1) << "OnPaddingTooLong: " << header
+ << "; missing_length: " << missing_length;
+ if (header.type == Http2FrameType::DATA) {
+ if (header.payload_length == 0) {
+ DCHECK_EQ(1u, missing_length);
+ SetSpdyErrorAndNotify(SpdyFramerError::SPDY_INVALID_DATA_FRAME_FLAGS);
+ return;
+ }
+ visitor()->OnStreamPadding(header.stream_id, 1);
+ }
+ SetSpdyErrorAndNotify(SpdyFramerError::SPDY_INVALID_PADDING);
+}
+
+void Http2DecoderAdapter::OnFrameSizeError(const Http2FrameHeader& header) {
+ DVLOG(1) << "OnFrameSizeError: " << header;
+ size_t recv_limit = recv_frame_size_limit_;
+ if (header.payload_length > recv_limit) {
+ SetSpdyErrorAndNotify(SpdyFramerError::SPDY_OVERSIZED_PAYLOAD);
+ return;
+ }
+ if (header.type != Http2FrameType::DATA &&
+ header.payload_length > recv_limit) {
+ SetSpdyErrorAndNotify(SpdyFramerError::SPDY_CONTROL_PAYLOAD_TOO_LARGE);
+ return;
+ }
+ switch (header.type) {
+ case Http2FrameType::GOAWAY:
+ case Http2FrameType::ALTSVC:
+ SetSpdyErrorAndNotify(SpdyFramerError::SPDY_INVALID_CONTROL_FRAME);
+ break;
+ default:
+ SetSpdyErrorAndNotify(SpdyFramerError::SPDY_INVALID_CONTROL_FRAME_SIZE);
+ }
+}
+
+// Decodes the input up to the next frame boundary (i.e. at most one frame),
+// stopping early if an error is detected.
+size_t Http2DecoderAdapter::ProcessInputFrame(const char* data, size_t len) {
+ DCHECK_NE(spdy_state_, SpdyState::SPDY_ERROR);
+ DecodeBuffer db(data, len);
+ DecodeStatus status = frame_decoder_->DecodeFrame(&db);
+ if (spdy_state_ != SpdyState::SPDY_ERROR) {
+ DetermineSpdyState(status);
+ } else {
+ VLOG(1) << "ProcessInputFrame spdy_framer_error_="
+ << SpdyFramerErrorToString(spdy_framer_error_);
+ if (spdy_framer_error_ == SpdyFramerError::SPDY_INVALID_PADDING &&
+ has_frame_header_ && frame_type() != Http2FrameType::DATA) {
+ // spdy_framer_test checks that all of the available frame payload
+ // has been consumed, so do that.
+ size_t total = remaining_total_payload();
+ if (total <= frame_header().payload_length) {
+ size_t avail = db.MinLengthRemaining(total);
+ VLOG(1) << "Skipping past " << avail << " bytes, of " << total
+ << " total remaining in the frame's payload.";
+ db.AdvanceCursor(avail);
+ } else {
+ SPDY_BUG << "Total remaining (" << total
+ << ") should not be greater than the payload length; "
+ << frame_header();
+ }
+ }
+ }
+ return db.Offset();
+}
+
+// After decoding, determine the next SpdyState. Only called if the current
+// state is NOT SpdyState::SPDY_ERROR (i.e. if none of the callback methods
+// detected an error condition), because otherwise we assume that the callback
+// method has set spdy_framer_error_ appropriately.
+void Http2DecoderAdapter::DetermineSpdyState(DecodeStatus status) {
+ DCHECK_EQ(spdy_framer_error_, SPDY_NO_ERROR);
+ DCHECK(!HasError()) << spdy_framer_error_;
+ switch (status) {
+ case DecodeStatus::kDecodeDone:
+ DVLOG(1) << "ProcessInputFrame -> DecodeStatus::kDecodeDone";
+ ResetBetweenFrames();
+ break;
+ case DecodeStatus::kDecodeInProgress:
+ DVLOG(1) << "ProcessInputFrame -> DecodeStatus::kDecodeInProgress";
+ if (decoded_frame_header_) {
+ if (IsDiscardingPayload()) {
+ set_spdy_state(SpdyState::SPDY_IGNORE_REMAINING_PAYLOAD);
+ } else if (has_frame_header_ && frame_type() == Http2FrameType::DATA) {
+ if (IsReadingPaddingLength()) {
+ set_spdy_state(SpdyState::SPDY_READ_DATA_FRAME_PADDING_LENGTH);
+ } else if (IsSkippingPadding()) {
+ set_spdy_state(SpdyState::SPDY_CONSUME_PADDING);
+ } else {
+ set_spdy_state(SpdyState::SPDY_FORWARD_STREAM_FRAME);
+ }
+ } else {
+ set_spdy_state(SpdyState::SPDY_CONTROL_FRAME_PAYLOAD);
+ }
+ } else {
+ set_spdy_state(SpdyState::SPDY_READING_COMMON_HEADER);
+ }
+ break;
+ case DecodeStatus::kDecodeError:
+ VLOG(1) << "ProcessInputFrame -> DecodeStatus::kDecodeError";
+ if (IsDiscardingPayload()) {
+ if (remaining_total_payload() == 0) {
+ // Push the Http2FrameDecoder out of state kDiscardPayload now
+ // since doing so requires no input.
+ DecodeBuffer tmp("", 0);
+ DecodeStatus status = frame_decoder_->DecodeFrame(&tmp);
+ if (status != DecodeStatus::kDecodeDone) {
+ SPDY_BUG << "Expected to be done decoding the frame, not "
+ << status;
+ SetSpdyErrorAndNotify(SPDY_INTERNAL_FRAMER_ERROR);
+ } else if (spdy_framer_error_ != SPDY_NO_ERROR) {
+ SPDY_BUG << "Expected to have no error, not "
+ << SpdyFramerErrorToString(spdy_framer_error_);
+ } else {
+ ResetBetweenFrames();
+ }
+ } else {
+ set_spdy_state(SpdyState::SPDY_IGNORE_REMAINING_PAYLOAD);
+ }
+ } else {
+ SetSpdyErrorAndNotify(SPDY_INVALID_CONTROL_FRAME);
+ }
+ break;
+ }
+}
+
+void Http2DecoderAdapter::ResetBetweenFrames() {
+ CorruptFrameHeader(&frame_header_);
+ decoded_frame_header_ = false;
+ has_frame_header_ = false;
+ set_spdy_state(SpdyState::SPDY_READY_FOR_FRAME);
+}
+
+// ResetInternal is called from the constructor, and during tests, but not
+// otherwise (i.e. not between every frame).
+void Http2DecoderAdapter::ResetInternal() {
+ set_spdy_state(SpdyState::SPDY_READY_FOR_FRAME);
+ spdy_framer_error_ = SpdyFramerError::SPDY_NO_ERROR;
+
+ decoded_frame_header_ = false;
+ has_frame_header_ = false;
+ on_headers_called_ = false;
+ on_hpack_fragment_called_ = false;
+ latched_probable_http_response_ = false;
+ has_expected_frame_type_ = false;
+
+ CorruptFrameHeader(&frame_header_);
+ CorruptFrameHeader(&hpack_first_frame_header_);
+
+ frame_decoder_ = SpdyMakeUnique<Http2FrameDecoder>(this);
+ hpack_decoder_ = nullptr;
+}
+
+void Http2DecoderAdapter::set_spdy_state(SpdyState v) {
+ DVLOG(2) << "set_spdy_state(" << StateToString(v) << ")";
+ spdy_state_ = v;
+}
+
+void Http2DecoderAdapter::SetSpdyErrorAndNotify(SpdyFramerError error) {
+ if (HasError()) {
+ DCHECK_EQ(spdy_state_, SpdyState::SPDY_ERROR);
+ } else {
+ VLOG(2) << "SetSpdyErrorAndNotify(" << SpdyFramerErrorToString(error)
+ << ")";
+ DCHECK_NE(error, SpdyFramerError::SPDY_NO_ERROR);
+ spdy_framer_error_ = error;
+ set_spdy_state(SpdyState::SPDY_ERROR);
+ frame_decoder_->set_listener(&no_op_listener_);
+ visitor()->OnError(error);
+ }
+}
+
+bool Http2DecoderAdapter::HasError() const {
+ if (spdy_state_ == SpdyState::SPDY_ERROR) {
+ DCHECK_NE(spdy_framer_error(), SpdyFramerError::SPDY_NO_ERROR);
+ return true;
+ } else {
+ DCHECK_EQ(spdy_framer_error(), SpdyFramerError::SPDY_NO_ERROR);
+ return false;
+ }
+}
+
+const Http2FrameHeader& Http2DecoderAdapter::frame_header() const {
+ DCHECK(has_frame_header_);
+ return frame_header_;
+}
+
+uint32_t Http2DecoderAdapter::stream_id() const {
+ return frame_header().stream_id;
+}
+
+Http2FrameType Http2DecoderAdapter::frame_type() const {
+ return frame_header().type;
+}
+
+size_t Http2DecoderAdapter::remaining_total_payload() const {
+ DCHECK(has_frame_header_);
+ size_t remaining = frame_decoder_->remaining_payload();
+ if (IsPaddable(frame_type()) && frame_header_.IsPadded()) {
+ remaining += frame_decoder_->remaining_padding();
+ }
+ return remaining;
+}
+
+bool Http2DecoderAdapter::IsReadingPaddingLength() {
+ bool result = frame_header_.IsPadded() && !opt_pad_length_;
+ DVLOG(2) << "Http2DecoderAdapter::IsReadingPaddingLength: " << result;
+ return result;
+}
+bool Http2DecoderAdapter::IsSkippingPadding() {
+ bool result = frame_header_.IsPadded() && opt_pad_length_ &&
+ frame_decoder_->remaining_payload() == 0 &&
+ frame_decoder_->remaining_padding() > 0;
+ DVLOG(2) << "Http2DecoderAdapter::IsSkippingPadding: " << result;
+ return result;
+}
+bool Http2DecoderAdapter::IsDiscardingPayload() {
+ bool result = decoded_frame_header_ && frame_decoder_->IsDiscardingPayload();
+ DVLOG(2) << "Http2DecoderAdapter::IsDiscardingPayload: " << result;
+ return result;
+}
+// Called from OnXyz or OnXyzStart methods to decide whether it is OK to
+// handle the callback.
+bool Http2DecoderAdapter::IsOkToStartFrame(const Http2FrameHeader& header) {
+ DVLOG(3) << "IsOkToStartFrame";
+ if (HasError()) {
+ VLOG(2) << "HasError()";
+ return false;
+ }
+ DCHECK(!has_frame_header_);
+ if (has_expected_frame_type_ && header.type != expected_frame_type_) {
+ VLOG(1) << "Expected frame type " << expected_frame_type_ << ", not "
+ << header.type;
+ SetSpdyErrorAndNotify(SpdyFramerError::SPDY_UNEXPECTED_FRAME);
+ return false;
+ }
+
+ return true;
+}
+
+bool Http2DecoderAdapter::HasRequiredStreamId(uint32_t stream_id) {
+ DVLOG(3) << "HasRequiredStreamId: " << stream_id;
+ if (HasError()) {
+ VLOG(2) << "HasError()";
+ return false;
+ }
+ if (stream_id != 0) {
+ return true;
+ }
+ VLOG(1) << "Stream Id is required, but zero provided";
+ SetSpdyErrorAndNotify(SpdyFramerError::SPDY_INVALID_STREAM_ID);
+ return false;
+}
+
+bool Http2DecoderAdapter::HasRequiredStreamId(const Http2FrameHeader& header) {
+ return HasRequiredStreamId(header.stream_id);
+}
+
+bool Http2DecoderAdapter::HasRequiredStreamIdZero(uint32_t stream_id) {
+ DVLOG(3) << "HasRequiredStreamIdZero: " << stream_id;
+ if (HasError()) {
+ VLOG(2) << "HasError()";
+ return false;
+ }
+ if (stream_id == 0) {
+ return true;
+ }
+ VLOG(1) << "Stream Id was not zero, as required: " << stream_id;
+ SetSpdyErrorAndNotify(SpdyFramerError::SPDY_INVALID_STREAM_ID);
+ return false;
+}
+
+bool Http2DecoderAdapter::HasRequiredStreamIdZero(
+ const Http2FrameHeader& header) {
+ return HasRequiredStreamIdZero(header.stream_id);
+}
+
+void Http2DecoderAdapter::ReportReceiveCompressedFrame(
+ const Http2FrameHeader& header) {
+ if (debug_visitor() != nullptr) {
+ size_t total = header.payload_length + Http2FrameHeader::EncodedSize();
+ debug_visitor()->OnReceiveCompressedFrame(
+ header.stream_id, ToSpdyFrameType(header.type), total);
+ }
+}
+
+HpackDecoderAdapter* Http2DecoderAdapter::GetHpackDecoder() {
+ if (hpack_decoder_ == nullptr) {
+ hpack_decoder_ = SpdyMakeUnique<HpackDecoderAdapter>();
+ }
+ return hpack_decoder_.get();
+}
+
+void Http2DecoderAdapter::CommonStartHpackBlock() {
+ DVLOG(1) << "CommonStartHpackBlock";
+ DCHECK(!has_hpack_first_frame_header_);
+ if (!frame_header_.IsEndHeaders()) {
+ hpack_first_frame_header_ = frame_header_;
+ has_hpack_first_frame_header_ = true;
+ } else {
+ CorruptFrameHeader(&hpack_first_frame_header_);
+ }
+ on_hpack_fragment_called_ = false;
+ SpdyHeadersHandlerInterface* handler =
+ visitor()->OnHeaderFrameStart(stream_id());
+ if (handler == nullptr) {
+ SPDY_BUG << "visitor_->OnHeaderFrameStart returned nullptr";
+ SetSpdyErrorAndNotify(SpdyFramerError::SPDY_INTERNAL_FRAMER_ERROR);
+ return;
+ }
+ GetHpackDecoder()->HandleControlFrameHeadersStart(handler);
+}
+
+// SpdyFramer calls HandleControlFrameHeadersData even if there are zero
+// fragment bytes in the first frame, so do the same.
+void Http2DecoderAdapter::MaybeAnnounceEmptyFirstHpackFragment() {
+ if (!on_hpack_fragment_called_) {
+ OnHpackFragment(nullptr, 0);
+ DCHECK(on_hpack_fragment_called_);
+ }
+}
+
+void Http2DecoderAdapter::CommonHpackFragmentEnd() {
+ DVLOG(1) << "CommonHpackFragmentEnd: stream_id=" << stream_id();
+ if (HasError()) {
+ VLOG(1) << "HasError(), returning";
+ return;
+ }
+ DCHECK(has_frame_header_);
+ MaybeAnnounceEmptyFirstHpackFragment();
+ if (frame_header_.IsEndHeaders()) {
+ DCHECK_EQ(has_hpack_first_frame_header_,
+ frame_type() == Http2FrameType::CONTINUATION)
+ << frame_header();
+ has_expected_frame_type_ = false;
+ if (GetHpackDecoder()->HandleControlFrameHeadersComplete(nullptr)) {
+ visitor()->OnHeaderFrameEnd(stream_id());
+ } else {
+ SetSpdyErrorAndNotify(SpdyFramerError::SPDY_DECOMPRESS_FAILURE);
+ return;
+ }
+ const Http2FrameHeader& first = frame_type() == Http2FrameType::CONTINUATION
+ ? hpack_first_frame_header_
+ : frame_header_;
+ if (first.type == Http2FrameType::HEADERS && first.IsEndStream()) {
+ visitor()->OnStreamEnd(first.stream_id);
+ }
+ has_hpack_first_frame_header_ = false;
+ CorruptFrameHeader(&hpack_first_frame_header_);
+ } else {
+ DCHECK(has_hpack_first_frame_header_);
+ has_expected_frame_type_ = true;
+ expected_frame_type_ = Http2FrameType::CONTINUATION;
+ }
+}
+
+} // namespace http2
+
+namespace spdy {
+
+bool SpdyFramerVisitorInterface::OnGoAwayFrameData(const char* goaway_data,
+ size_t len) {
+ return true;
+}
+
+} // namespace spdy
diff --git a/chromium/net/third_party/quiche/src/spdy/core/http2_frame_decoder_adapter.h b/chromium/net/third_party/quiche/src/spdy/core/http2_frame_decoder_adapter.h
new file mode 100644
index 00000000000..02deafdf909
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/spdy/core/http2_frame_decoder_adapter.h
@@ -0,0 +1,514 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_SPDY_CORE_HTTP2_FRAME_DECODER_ADAPTER_H_
+#define QUICHE_SPDY_CORE_HTTP2_FRAME_DECODER_ADAPTER_H_
+
+#include <stddef.h>
+
+#include <cstdint>
+#include <memory>
+
+#include "net/third_party/quiche/src/http2/decoder/http2_frame_decoder.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_optional.h"
+#include "net/third_party/quiche/src/spdy/core/hpack/hpack_decoder_adapter.h"
+#include "net/third_party/quiche/src/spdy/core/hpack/hpack_header_table.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_alt_svc_wire_format.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_headers_handler_interface.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_protocol.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_string_piece.h"
+
+namespace spdy {
+
+class SpdyFramerVisitorInterface;
+class ExtensionVisitorInterface;
+
+} // namespace spdy
+
+// TODO(dahollings): Perform various renames/moves suggested in cl/164660364.
+
+namespace http2 {
+
+// Adapts SpdyFramer interface to use Http2FrameDecoder.
+class SPDY_EXPORT_PRIVATE Http2DecoderAdapter
+ : public http2::Http2FrameDecoderListener {
+ public:
+ // HTTP2 states.
+ enum SpdyState {
+ SPDY_ERROR,
+ SPDY_READY_FOR_FRAME, // Framer is ready for reading the next frame.
+ SPDY_FRAME_COMPLETE, // Framer has finished reading a frame, need to reset.
+ SPDY_READING_COMMON_HEADER,
+ SPDY_CONTROL_FRAME_PAYLOAD,
+ SPDY_READ_DATA_FRAME_PADDING_LENGTH,
+ SPDY_CONSUME_PADDING,
+ SPDY_IGNORE_REMAINING_PAYLOAD,
+ SPDY_FORWARD_STREAM_FRAME,
+ SPDY_CONTROL_FRAME_BEFORE_HEADER_BLOCK,
+ SPDY_CONTROL_FRAME_HEADER_BLOCK,
+ SPDY_GOAWAY_FRAME_PAYLOAD,
+ SPDY_SETTINGS_FRAME_HEADER,
+ SPDY_SETTINGS_FRAME_PAYLOAD,
+ SPDY_ALTSVC_FRAME_PAYLOAD,
+ SPDY_EXTENSION_FRAME_PAYLOAD,
+ };
+
+ // Framer error codes.
+ enum SpdyFramerError {
+ SPDY_NO_ERROR,
+ SPDY_INVALID_STREAM_ID, // Stream ID is invalid
+ SPDY_INVALID_CONTROL_FRAME, // Control frame is mal-formatted.
+ SPDY_CONTROL_PAYLOAD_TOO_LARGE, // Control frame payload was too large.
+ SPDY_ZLIB_INIT_FAILURE, // The Zlib library could not initialize.
+ SPDY_UNSUPPORTED_VERSION, // Control frame has unsupported version.
+ SPDY_DECOMPRESS_FAILURE, // There was an error decompressing.
+ SPDY_COMPRESS_FAILURE, // There was an error compressing.
+ SPDY_GOAWAY_FRAME_CORRUPT, // GOAWAY frame could not be parsed.
+ SPDY_RST_STREAM_FRAME_CORRUPT, // RST_STREAM frame could not be parsed.
+ SPDY_INVALID_PADDING, // HEADERS or DATA frame padding invalid
+ SPDY_INVALID_DATA_FRAME_FLAGS, // Data frame has invalid flags.
+ SPDY_INVALID_CONTROL_FRAME_FLAGS, // Control frame has invalid flags.
+ SPDY_UNEXPECTED_FRAME, // Frame received out of order.
+ SPDY_INTERNAL_FRAMER_ERROR, // SpdyFramer was used incorrectly.
+ SPDY_INVALID_CONTROL_FRAME_SIZE, // Control frame not sized to spec
+ SPDY_OVERSIZED_PAYLOAD, // Payload size was too large
+
+ LAST_ERROR, // Must be the last entry in the enum.
+ };
+
+ // For debugging.
+ static const char* StateToString(int state);
+ static const char* SpdyFramerErrorToString(SpdyFramerError spdy_framer_error);
+
+ Http2DecoderAdapter();
+ ~Http2DecoderAdapter() override;
+
+ // Set callbacks to be called from the framer. A visitor must be set, or
+ // else the framer will likely crash. It is acceptable for the visitor
+ // to do nothing. If this is called multiple times, only the last visitor
+ // will be used.
+ void set_visitor(spdy::SpdyFramerVisitorInterface* visitor);
+ spdy::SpdyFramerVisitorInterface* visitor() const { return visitor_; }
+
+ // Set extension callbacks to be called from the framer or decoder. Optional.
+ // If called multiple times, only the last visitor will be used.
+ void set_extension_visitor(spdy::ExtensionVisitorInterface* visitor);
+
+ // Set debug callbacks to be called from the framer. The debug visitor is
+ // completely optional and need not be set in order for normal operation.
+ // If this is called multiple times, only the last visitor will be used.
+ void set_debug_visitor(spdy::SpdyFramerDebugVisitorInterface* debug_visitor);
+ spdy::SpdyFramerDebugVisitorInterface* debug_visitor() const {
+ return debug_visitor_;
+ }
+
+ // Set debug callbacks to be called from the HPACK decoder.
+ void SetDecoderHeaderTableDebugVisitor(
+ std::unique_ptr<spdy::HpackHeaderTable::DebugVisitorInterface> visitor);
+
+ // Sets whether or not ProcessInput returns after finishing a frame, or
+ // continues processing additional frames. Normally ProcessInput processes
+ // all input, but this method enables the caller (and visitor) to work with
+ // a single frame at a time (or that portion of the frame which is provided
+ // as input). Reset() does not change the value of this flag.
+ void set_process_single_input_frame(bool v);
+ bool process_single_input_frame() const {
+ return process_single_input_frame_;
+ }
+
+ // Decode the |len| bytes of encoded HTTP/2 starting at |*data|. Returns
+ // the number of bytes consumed. It is safe to pass more bytes in than
+ // may be consumed. Should process (or otherwise buffer) as much as
+ // available, unless process_single_input_frame is true.
+ size_t ProcessInput(const char* data, size_t len);
+
+ // Reset the decoder (used just for tests at this time).
+ void Reset();
+
+ // Current state of the decoder.
+ SpdyState state() const;
+
+ // Current error code (NO_ERROR if state != ERROR).
+ SpdyFramerError spdy_framer_error() const;
+
+ // Has any frame header looked like the start of an HTTP/1.1 (or earlier)
+ // response? Used to detect if a backend/server that we sent a request to
+ // has responded with an HTTP/1.1 (or earlier) response.
+ bool probable_http_response() const;
+
+ // Returns the estimate of dynamically allocated memory in bytes.
+ size_t EstimateMemoryUsage() const;
+
+ spdy::HpackDecoderAdapter* GetHpackDecoder();
+
+ bool HasError() const;
+
+ private:
+ bool OnFrameHeader(const Http2FrameHeader& header) override;
+ void OnDataStart(const Http2FrameHeader& header) override;
+ void OnDataPayload(const char* data, size_t len) override;
+ void OnDataEnd() override;
+ void OnHeadersStart(const Http2FrameHeader& header) override;
+ void OnHeadersPriority(const Http2PriorityFields& priority) override;
+ void OnHpackFragment(const char* data, size_t len) override;
+ void OnHeadersEnd() override;
+ void OnPriorityFrame(const Http2FrameHeader& header,
+ const Http2PriorityFields& priority) override;
+ void OnContinuationStart(const Http2FrameHeader& header) override;
+ void OnContinuationEnd() override;
+ void OnPadLength(size_t trailing_length) override;
+ void OnPadding(const char* padding, size_t skipped_length) override;
+ void OnRstStream(const Http2FrameHeader& header,
+ Http2ErrorCode http2_error_code) override;
+ void OnSettingsStart(const Http2FrameHeader& header) override;
+ void OnSetting(const Http2SettingFields& setting_fields) override;
+ void OnSettingsEnd() override;
+ void OnSettingsAck(const Http2FrameHeader& header) override;
+ void OnPushPromiseStart(const Http2FrameHeader& header,
+ const Http2PushPromiseFields& promise,
+ size_t total_padding_length) override;
+ void OnPushPromiseEnd() override;
+ void OnPing(const Http2FrameHeader& header,
+ const Http2PingFields& ping) override;
+ void OnPingAck(const Http2FrameHeader& header,
+ const Http2PingFields& ping) override;
+ void OnGoAwayStart(const Http2FrameHeader& header,
+ const Http2GoAwayFields& goaway) override;
+ void OnGoAwayOpaqueData(const char* data, size_t len) override;
+ void OnGoAwayEnd() override;
+ void OnWindowUpdate(const Http2FrameHeader& header,
+ uint32_t increment) override;
+ void OnAltSvcStart(const Http2FrameHeader& header,
+ size_t origin_length,
+ size_t value_length) override;
+ void OnAltSvcOriginData(const char* data, size_t len) override;
+ void OnAltSvcValueData(const char* data, size_t len) override;
+ void OnAltSvcEnd() override;
+ void OnUnknownStart(const Http2FrameHeader& header) override;
+ void OnUnknownPayload(const char* data, size_t len) override;
+ void OnUnknownEnd() override;
+ void OnPaddingTooLong(const Http2FrameHeader& header,
+ size_t missing_length) override;
+ void OnFrameSizeError(const Http2FrameHeader& header) override;
+
+ size_t ProcessInputFrame(const char* data, size_t len);
+
+ void DetermineSpdyState(DecodeStatus status);
+ void ResetBetweenFrames();
+
+ // ResetInternal is called from the constructor, and during tests, but not
+ // otherwise (i.e. not between every frame).
+ void ResetInternal();
+
+ void set_spdy_state(SpdyState v);
+
+ void SetSpdyErrorAndNotify(SpdyFramerError error);
+
+ const Http2FrameHeader& frame_header() const;
+
+ uint32_t stream_id() const;
+ Http2FrameType frame_type() const;
+
+ size_t remaining_total_payload() const;
+
+ bool IsReadingPaddingLength();
+ bool IsSkippingPadding();
+ bool IsDiscardingPayload();
+ // Called from OnXyz or OnXyzStart methods to decide whether it is OK to
+ // handle the callback.
+ bool IsOkToStartFrame(const Http2FrameHeader& header);
+ bool HasRequiredStreamId(uint32_t stream_id);
+
+ bool HasRequiredStreamId(const Http2FrameHeader& header);
+
+ bool HasRequiredStreamIdZero(uint32_t stream_id);
+
+ bool HasRequiredStreamIdZero(const Http2FrameHeader& header);
+
+ void ReportReceiveCompressedFrame(const Http2FrameHeader& header);
+
+ void CommonStartHpackBlock();
+
+ // SpdyFramer calls HandleControlFrameHeadersData even if there are zero
+ // fragment bytes in the first frame, so do the same.
+ void MaybeAnnounceEmptyFirstHpackFragment();
+ void CommonHpackFragmentEnd();
+
+ // The most recently decoded frame header; invalid after we reached the end
+ // of that frame.
+ Http2FrameHeader frame_header_;
+
+ // If decoding an HPACK block that is split across multiple frames, this holds
+ // the frame header of the HEADERS or PUSH_PROMISE that started the block.
+ Http2FrameHeader hpack_first_frame_header_;
+
+ // Amount of trailing padding. Currently used just as an indicator of whether
+ // OnPadLength has been called.
+ Http2Optional<size_t> opt_pad_length_;
+
+ // Temporary buffers for the AltSvc fields.
+ Http2String alt_svc_origin_;
+ Http2String alt_svc_value_;
+
+ // Listener used if we transition to an error state; the listener ignores all
+ // the callbacks.
+ Http2FrameDecoderNoOpListener no_op_listener_;
+
+ spdy::SpdyFramerVisitorInterface* visitor_ = nullptr;
+ spdy::SpdyFramerDebugVisitorInterface* debug_visitor_ = nullptr;
+
+ // If non-null, unknown frames and settings are passed to the extension.
+ spdy::ExtensionVisitorInterface* extension_ = nullptr;
+
+ // The HPACK decoder to be used for this adapter. User is responsible for
+ // clearing if the adapter is to be used for another connection.
+ std::unique_ptr<spdy::HpackDecoderAdapter> hpack_decoder_ = nullptr;
+
+ // The HTTP/2 frame decoder. Accessed via a unique_ptr to allow replacement
+ // (e.g. in tests) when Reset() is called.
+ std::unique_ptr<Http2FrameDecoder> frame_decoder_;
+
+ // Next frame type expected. Currently only used for CONTINUATION frames,
+ // but could be used for detecting whether the first frame is a SETTINGS
+ // frame.
+ // TODO(jamessyng): Provide means to indicate that decoder should require
+ // SETTINGS frame as the first frame.
+ Http2FrameType expected_frame_type_;
+
+ // Attempt to duplicate the SpdyState and SpdyFramerError values that
+ // SpdyFramer sets. Values determined by getting tests to pass.
+ SpdyState spdy_state_;
+ SpdyFramerError spdy_framer_error_;
+
+ // The limit on the size of received HTTP/2 payloads as specified in the
+ // SETTINGS_MAX_FRAME_SIZE advertised to peer.
+ size_t recv_frame_size_limit_ = spdy::kHttp2DefaultFramePayloadLimit;
+
+ // Has OnFrameHeader been called?
+ bool decoded_frame_header_ = false;
+
+ // Have we recorded an Http2FrameHeader for the current frame?
+ // We only do so if the decoder will make multiple callbacks for
+ // the frame; for example, for PING frames we don't make record
+ // the frame header, but for ALTSVC we do.
+ bool has_frame_header_ = false;
+
+ // Have we recorded an Http2FrameHeader for the current HPACK block?
+ // True only for multi-frame HPACK blocks.
+ bool has_hpack_first_frame_header_ = false;
+
+ // Has OnHeaders() already been called for current HEADERS block? Only
+ // meaningful between OnHeadersStart and OnHeadersPriority.
+ bool on_headers_called_;
+
+ // Has OnHpackFragment() already been called for current HPACK block?
+ // SpdyFramer will pass an empty buffer to the HPACK decoder if a HEADERS
+ // or PUSH_PROMISE has no HPACK data in it (e.g. a HEADERS frame with only
+ // padding). Detect that condition and replicate the behavior using this
+ // field.
+ bool on_hpack_fragment_called_;
+
+ // Have we seen a frame header that appears to be an HTTP/1 response?
+ bool latched_probable_http_response_ = false;
+
+ // Is expected_frame_type_ set?
+ bool has_expected_frame_type_ = false;
+
+ // Is the current frame payload destined for |extension_|?
+ bool handling_extension_payload_ = false;
+
+ bool process_single_input_frame_ = false;
+};
+
+} // namespace http2
+
+namespace spdy {
+
+// Http2DecoderAdapter will use the given visitor implementing this
+// interface to deliver event callbacks as frames are decoded.
+//
+// Control frames that contain HTTP2 header blocks (HEADER, and PUSH_PROMISE)
+// are processed in fashion that allows the decompressed header block to be
+// delivered in chunks to the visitor.
+// The following steps are followed:
+// 1. OnHeaders, or OnPushPromise is called.
+// 2. OnHeaderFrameStart is called; visitor is expected to return an instance
+// of SpdyHeadersHandlerInterface that will receive the header key-value
+// pairs.
+// 3. OnHeaderFrameEnd is called, indicating that the full header block has
+// been delivered for the control frame.
+// During step 2, if the visitor is not interested in accepting the header data,
+// it should return a no-op implementation of SpdyHeadersHandlerInterface.
+class SPDY_EXPORT_PRIVATE SpdyFramerVisitorInterface {
+ public:
+ virtual ~SpdyFramerVisitorInterface() {}
+
+ // Called if an error is detected in the SpdyFrame protocol.
+ virtual void OnError(http2::Http2DecoderAdapter::SpdyFramerError error) = 0;
+
+ // Called when the common header for a frame is received. Validating the
+ // common header occurs in later processing.
+ virtual void OnCommonHeader(SpdyStreamId /*stream_id*/,
+ size_t /*length*/,
+ uint8_t /*type*/,
+ uint8_t /*flags*/) {}
+
+ // Called when a data frame header is received. The frame's data
+ // payload will be provided via subsequent calls to
+ // OnStreamFrameData().
+ virtual void OnDataFrameHeader(SpdyStreamId stream_id,
+ size_t length,
+ bool fin) = 0;
+
+ // Called when data is received.
+ // |stream_id| The stream receiving data.
+ // |data| A buffer containing the data received.
+ // |len| The length of the data buffer.
+ virtual void OnStreamFrameData(SpdyStreamId stream_id,
+ const char* data,
+ size_t len) = 0;
+
+ // Called when the other side has finished sending data on this stream.
+ // |stream_id| The stream that was receiving data.
+ virtual void OnStreamEnd(SpdyStreamId stream_id) = 0;
+
+ // Called when padding length field is received on a DATA frame.
+ // |stream_id| The stream receiving data.
+ // |value| The value of the padding length field.
+ virtual void OnStreamPadLength(SpdyStreamId stream_id, size_t value) {}
+
+ // Called when padding is received (the trailing octets, not pad_len field) on
+ // a DATA frame.
+ // |stream_id| The stream receiving data.
+ // |len| The number of padding octets.
+ virtual void OnStreamPadding(SpdyStreamId stream_id, size_t len) = 0;
+
+ // Called just before processing the payload of a frame containing header
+ // data. Should return an implementation of SpdyHeadersHandlerInterface that
+ // will receive headers for stream |stream_id|. The caller will not take
+ // ownership of the headers handler. The same instance should remain live
+ // and be returned for all header frames comprising a logical header block
+ // (i.e. until OnHeaderFrameEnd() is called).
+ virtual SpdyHeadersHandlerInterface* OnHeaderFrameStart(
+ SpdyStreamId stream_id) = 0;
+
+ // Called after processing the payload of a frame containing header data.
+ virtual void OnHeaderFrameEnd(SpdyStreamId stream_id) = 0;
+
+ // Called when a RST_STREAM frame has been parsed.
+ virtual void OnRstStream(SpdyStreamId stream_id,
+ SpdyErrorCode error_code) = 0;
+
+ // Called when a SETTINGS frame is received.
+ virtual void OnSettings() {}
+
+ // Called when a complete setting within a SETTINGS frame has been parsed.
+ // Note that |id| may or may not be a SETTINGS ID defined in the HTTP/2 spec.
+ virtual void OnSetting(SpdySettingsId id, uint32_t value) = 0;
+
+ // Called when a SETTINGS frame is received with the ACK flag set.
+ virtual void OnSettingsAck() {}
+
+ // Called before and after parsing SETTINGS id and value tuples.
+ virtual void OnSettingsEnd() = 0;
+
+ // Called when a PING frame has been parsed.
+ virtual void OnPing(SpdyPingId unique_id, bool is_ack) = 0;
+
+ // Called when a GOAWAY frame has been parsed.
+ virtual void OnGoAway(SpdyStreamId last_accepted_stream_id,
+ SpdyErrorCode error_code) = 0;
+
+ // Called when a HEADERS frame is received.
+ // Note that header block data is not included. See OnHeaderFrameStart().
+ // |stream_id| The stream receiving the header.
+ // |has_priority| Whether or not the headers frame included a priority value,
+ // and stream dependency info.
+ // |weight| If |has_priority| is true, then weight (in the range [1, 256])
+ // for the receiving stream, otherwise 0.
+ // |parent_stream_id| If |has_priority| is true the parent stream of the
+ // receiving stream, else 0.
+ // |exclusive| If |has_priority| is true the exclusivity of dependence on the
+ // parent stream, else false.
+ // |fin| Whether FIN flag is set in frame headers.
+ // |end| False if HEADERs frame is to be followed by a CONTINUATION frame,
+ // or true if not.
+ virtual void OnHeaders(SpdyStreamId stream_id,
+ bool has_priority,
+ int weight,
+ SpdyStreamId parent_stream_id,
+ bool exclusive,
+ bool fin,
+ bool end) = 0;
+
+ // Called when a WINDOW_UPDATE frame has been parsed.
+ virtual void OnWindowUpdate(SpdyStreamId stream_id,
+ int delta_window_size) = 0;
+
+ // Called when a goaway frame opaque data is available.
+ // |goaway_data| A buffer containing the opaque GOAWAY data chunk received.
+ // |len| The length of the header data buffer. A length of zero indicates
+ // that the header data block has been completely sent.
+ // When this function returns true the visitor indicates that it accepted
+ // all of the data. Returning false indicates that that an error has
+ // occurred while processing the data. Default implementation returns true.
+ virtual bool OnGoAwayFrameData(const char* goaway_data, size_t len);
+
+ // Called when a PUSH_PROMISE frame is received.
+ // Note that header block data is not included. See OnHeaderFrameStart().
+ virtual void OnPushPromise(SpdyStreamId stream_id,
+ SpdyStreamId promised_stream_id,
+ bool end) = 0;
+
+ // Called when a CONTINUATION frame is received.
+ // Note that header block data is not included. See OnHeaderFrameStart().
+ virtual void OnContinuation(SpdyStreamId stream_id, bool end) = 0;
+
+ // Called when an ALTSVC frame has been parsed.
+ virtual void OnAltSvc(
+ SpdyStreamId /*stream_id*/,
+ SpdyStringPiece /*origin*/,
+ const SpdyAltSvcWireFormat::AlternativeServiceVector& /*altsvc_vector*/) {
+ }
+
+ // Called when a PRIORITY frame is received.
+ // |stream_id| The stream to update the priority of.
+ // |parent_stream_id| The parent stream of |stream_id|.
+ // |weight| Stream weight, in the range [1, 256].
+ // |exclusive| Whether |stream_id| should be an only child of
+ // |parent_stream_id|.
+ virtual void OnPriority(SpdyStreamId stream_id,
+ SpdyStreamId parent_stream_id,
+ int weight,
+ bool exclusive) = 0;
+
+ // Called when a frame type we don't recognize is received.
+ // Return true if this appears to be a valid extension frame, false otherwise.
+ // We distinguish between extension frames and nonsense by checking
+ // whether the stream id is valid.
+ virtual bool OnUnknownFrame(SpdyStreamId stream_id, uint8_t frame_type) = 0;
+};
+
+class SPDY_EXPORT_PRIVATE ExtensionVisitorInterface {
+ public:
+ virtual ~ExtensionVisitorInterface() {}
+
+ // Called when SETTINGS are received, including non-standard SETTINGS.
+ virtual void OnSetting(SpdySettingsId id, uint32_t value) = 0;
+
+ // Called when non-standard frames are received.
+ virtual bool OnFrameHeader(SpdyStreamId stream_id,
+ size_t length,
+ uint8_t type,
+ uint8_t flags) = 0;
+
+ // The payload for a single frame may be delivered as multiple calls to
+ // OnFramePayload. Since the length field is passed in OnFrameHeader, there is
+ // no explicit indication of the end of the frame payload.
+ virtual void OnFramePayload(const char* data, size_t len) = 0;
+};
+
+} // namespace spdy
+
+#endif // QUICHE_SPDY_CORE_HTTP2_FRAME_DECODER_ADAPTER_H_
diff --git a/chromium/net/third_party/quiche/src/spdy/core/mock_spdy_framer_visitor.cc b/chromium/net/third_party/quiche/src/spdy/core/mock_spdy_framer_visitor.cc
new file mode 100644
index 00000000000..053c25518e5
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/spdy/core/mock_spdy_framer_visitor.cc
@@ -0,0 +1,19 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/spdy/core/mock_spdy_framer_visitor.h"
+
+namespace spdy {
+
+namespace test {
+
+MockSpdyFramerVisitor::MockSpdyFramerVisitor() {
+ DelegateHeaderHandling();
+}
+
+MockSpdyFramerVisitor::~MockSpdyFramerVisitor() = default;
+
+} // namespace test
+
+} // namespace spdy
diff --git a/chromium/net/third_party/quiche/src/spdy/core/mock_spdy_framer_visitor.h b/chromium/net/third_party/quiche/src/spdy/core/mock_spdy_framer_visitor.h
new file mode 100644
index 00000000000..124efe584f4
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/spdy/core/mock_spdy_framer_visitor.h
@@ -0,0 +1,103 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_SPDY_CORE_MOCK_SPDY_FRAMER_VISITOR_H_
+#define QUICHE_SPDY_CORE_MOCK_SPDY_FRAMER_VISITOR_H_
+
+#include <cstdint>
+#include <memory>
+
+#include "testing/gmock/include/gmock/gmock.h"
+#include "net/third_party/quiche/src/spdy/core/http2_frame_decoder_adapter.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_test_utils.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_ptr_util.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_string_piece.h"
+
+namespace spdy {
+
+namespace test {
+
+class MockSpdyFramerVisitor : public SpdyFramerVisitorInterface {
+ public:
+ MockSpdyFramerVisitor();
+ ~MockSpdyFramerVisitor() override;
+
+ MOCK_METHOD1(OnError,
+ void(http2::Http2DecoderAdapter::SpdyFramerError error));
+ MOCK_METHOD3(OnDataFrameHeader,
+ void(SpdyStreamId stream_id, size_t length, bool fin));
+ MOCK_METHOD3(OnStreamFrameData,
+ void(SpdyStreamId stream_id, const char* data, size_t len));
+ MOCK_METHOD1(OnStreamEnd, void(SpdyStreamId stream_id));
+ MOCK_METHOD2(OnStreamPadLength, void(SpdyStreamId stream_id, size_t value));
+ MOCK_METHOD2(OnStreamPadding, void(SpdyStreamId stream_id, size_t len));
+ MOCK_METHOD1(OnHeaderFrameStart,
+ SpdyHeadersHandlerInterface*(SpdyStreamId stream_id));
+ MOCK_METHOD1(OnHeaderFrameEnd, void(SpdyStreamId stream_id));
+ MOCK_METHOD2(OnRstStream,
+ void(SpdyStreamId stream_id, SpdyErrorCode error_code));
+ MOCK_METHOD0(OnSettings, void());
+ MOCK_METHOD2(OnSetting, void(SpdySettingsId id, uint32_t value));
+ MOCK_METHOD2(OnPing, void(SpdyPingId unique_id, bool is_ack));
+ MOCK_METHOD0(OnSettingsEnd, void());
+ MOCK_METHOD2(OnGoAway,
+ void(SpdyStreamId last_accepted_stream_id,
+ SpdyErrorCode error_code));
+ MOCK_METHOD7(OnHeaders,
+ void(SpdyStreamId stream_id,
+ bool has_priority,
+ int weight,
+ SpdyStreamId parent_stream_id,
+ bool exclusive,
+ bool fin,
+ bool end));
+ MOCK_METHOD2(OnWindowUpdate,
+ void(SpdyStreamId stream_id, int delta_window_size));
+ MOCK_METHOD3(OnPushPromise,
+ void(SpdyStreamId stream_id,
+ SpdyStreamId promised_stream_id,
+ bool end));
+ MOCK_METHOD2(OnContinuation, void(SpdyStreamId stream_id, bool end));
+ MOCK_METHOD3(OnAltSvc,
+ void(SpdyStreamId stream_id,
+ SpdyStringPiece origin,
+ const SpdyAltSvcWireFormat::AlternativeServiceVector&
+ altsvc_vector));
+ MOCK_METHOD4(OnPriority,
+ void(SpdyStreamId stream_id,
+ SpdyStreamId parent_stream_id,
+ int weight,
+ bool exclusive));
+ MOCK_METHOD2(OnUnknownFrame,
+ bool(SpdyStreamId stream_id, uint8_t frame_type));
+
+ void DelegateHeaderHandling() {
+ ON_CALL(*this, OnHeaderFrameStart(testing::_))
+ .WillByDefault(testing::Invoke(
+ this, &MockSpdyFramerVisitor::ReturnTestHeadersHandler));
+ ON_CALL(*this, OnHeaderFrameEnd(testing::_))
+ .WillByDefault(testing::Invoke(
+ this, &MockSpdyFramerVisitor::ResetTestHeadersHandler));
+ }
+
+ SpdyHeadersHandlerInterface* ReturnTestHeadersHandler(
+ SpdyStreamId /* stream_id */) {
+ if (headers_handler_ == nullptr) {
+ headers_handler_ = SpdyMakeUnique<TestHeadersHandler>();
+ }
+ return headers_handler_.get();
+ }
+
+ void ResetTestHeadersHandler(SpdyStreamId /* stream_id */) {
+ headers_handler_.reset();
+ }
+
+ std::unique_ptr<SpdyHeadersHandlerInterface> headers_handler_;
+};
+
+} // namespace test
+
+} // namespace spdy
+
+#endif // QUICHE_SPDY_CORE_MOCK_SPDY_FRAMER_VISITOR_H_
diff --git a/chromium/net/third_party/quiche/src/spdy/core/priority_write_scheduler.h b/chromium/net/third_party/quiche/src/spdy/core/priority_write_scheduler.h
new file mode 100644
index 00000000000..d5c3c44f3af
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/spdy/core/priority_write_scheduler.h
@@ -0,0 +1,322 @@
+// Copyright (c) 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_SPDY_CORE_PRIORITY_WRITE_SCHEDULER_H_
+#define QUICHE_SPDY_CORE_PRIORITY_WRITE_SCHEDULER_H_
+
+#include <algorithm>
+#include <cstddef>
+#include <cstdint>
+#include <tuple>
+#include <unordered_map>
+#include <utility>
+#include <vector>
+
+#include "base/logging.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_containers.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_protocol.h"
+#include "net/third_party/quiche/src/spdy/core/write_scheduler.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_bug_tracker.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_macros.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_string.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_string_utils.h"
+
+namespace spdy {
+
+namespace test {
+template <typename StreamIdType>
+class PriorityWriteSchedulerPeer;
+}
+
+// WriteScheduler implementation that manages the order in which streams are
+// written using the SPDY priority scheme described at:
+// https://www.chromium.org/spdy/spdy-protocol/spdy-protocol-draft3-1#TOC-2.3.3-Stream-priority
+//
+// Internally, PriorityWriteScheduler consists of 8 PriorityInfo objects, one
+// for each priority value. Each PriorityInfo contains a list of streams of
+// that priority that are ready to write, as well as a timestamp of the last
+// I/O event that occurred for a stream of that priority.
+//
+// DO NOT USE. Deprecated.
+template <typename StreamIdType>
+class PriorityWriteScheduler : public WriteScheduler<StreamIdType> {
+ public:
+ using typename WriteScheduler<StreamIdType>::StreamPrecedenceType;
+
+ // Creates scheduler with no streams.
+ PriorityWriteScheduler() = default;
+
+ void RegisterStream(StreamIdType stream_id,
+ const StreamPrecedenceType& precedence) override {
+ // TODO(mpw): verify |precedence.is_spdy3_priority() == true| once
+ // Http2PriorityWriteScheduler enabled for HTTP/2.
+
+ // parent_id not used here, but may as well validate it. However,
+ // parent_id may legitimately not be registered yet--see b/15676312.
+ StreamIdType parent_id = precedence.parent_id();
+ SPDY_DVLOG_IF(
+ 1, parent_id != kHttp2RootStreamId && !StreamRegistered(parent_id))
+ << "Parent stream " << parent_id << " not registered";
+
+ if (stream_id == kHttp2RootStreamId) {
+ SPDY_BUG << "Stream " << kHttp2RootStreamId << " already registered";
+ return;
+ }
+ StreamInfo stream_info = {precedence.spdy3_priority(), stream_id, false};
+ bool inserted =
+ stream_infos_.insert(std::make_pair(stream_id, stream_info)).second;
+ SPDY_BUG_IF(!inserted) << "Stream " << stream_id << " already registered";
+ }
+
+ void UnregisterStream(StreamIdType stream_id) override {
+ auto it = stream_infos_.find(stream_id);
+ if (it == stream_infos_.end()) {
+ SPDY_BUG << "Stream " << stream_id << " not registered";
+ return;
+ }
+ StreamInfo& stream_info = it->second;
+ if (stream_info.ready) {
+ bool erased =
+ Erase(&priority_infos_[stream_info.priority].ready_list, stream_info);
+ DCHECK(erased);
+ }
+ stream_infos_.erase(it);
+ }
+
+ bool StreamRegistered(StreamIdType stream_id) const override {
+ return stream_infos_.find(stream_id) != stream_infos_.end();
+ }
+
+ StreamPrecedenceType GetStreamPrecedence(
+ StreamIdType stream_id) const override {
+ auto it = stream_infos_.find(stream_id);
+ if (it == stream_infos_.end()) {
+ DVLOG(1) << "Stream " << stream_id << " not registered";
+ return StreamPrecedenceType(kV3LowestPriority);
+ }
+ return StreamPrecedenceType(it->second.priority);
+ }
+
+ void UpdateStreamPrecedence(StreamIdType stream_id,
+ const StreamPrecedenceType& precedence) override {
+ // TODO(mpw): verify |precedence.is_spdy3_priority() == true| once
+ // Http2PriorityWriteScheduler enabled for HTTP/2.
+
+ // parent_id not used here, but may as well validate it. However,
+ // parent_id may legitimately not be registered yet--see b/15676312.
+ StreamIdType parent_id = precedence.parent_id();
+ SPDY_DVLOG_IF(
+ 1, parent_id != kHttp2RootStreamId && !StreamRegistered(parent_id))
+ << "Parent stream " << parent_id << " not registered";
+
+ auto it = stream_infos_.find(stream_id);
+ if (it == stream_infos_.end()) {
+ // TODO(mpw): add to stream_infos_ on demand--see b/15676312.
+ DVLOG(1) << "Stream " << stream_id << " not registered";
+ return;
+ }
+ StreamInfo& stream_info = it->second;
+ SpdyPriority new_priority = precedence.spdy3_priority();
+ if (stream_info.priority == new_priority) {
+ return;
+ }
+ if (stream_info.ready) {
+ bool erased =
+ Erase(&priority_infos_[stream_info.priority].ready_list, stream_info);
+ DCHECK(erased);
+ priority_infos_[new_priority].ready_list.push_back(&stream_info);
+ ++num_ready_streams_;
+ }
+ stream_info.priority = new_priority;
+ }
+
+ std::vector<StreamIdType> GetStreamChildren(
+ StreamIdType stream_id) const override {
+ return std::vector<StreamIdType>();
+ }
+
+ void RecordStreamEventTime(StreamIdType stream_id,
+ int64_t now_in_usec) override {
+ auto it = stream_infos_.find(stream_id);
+ if (it == stream_infos_.end()) {
+ SPDY_BUG << "Stream " << stream_id << " not registered";
+ return;
+ }
+ PriorityInfo& priority_info = priority_infos_[it->second.priority];
+ priority_info.last_event_time_usec =
+ std::max(priority_info.last_event_time_usec, now_in_usec);
+ }
+
+ int64_t GetLatestEventWithPrecedence(StreamIdType stream_id) const override {
+ auto it = stream_infos_.find(stream_id);
+ if (it == stream_infos_.end()) {
+ SPDY_BUG << "Stream " << stream_id << " not registered";
+ return 0;
+ }
+ int64_t last_event_time_usec = 0;
+ const StreamInfo& stream_info = it->second;
+ for (SpdyPriority p = kV3HighestPriority; p < stream_info.priority; ++p) {
+ last_event_time_usec = std::max(last_event_time_usec,
+ priority_infos_[p].last_event_time_usec);
+ }
+ return last_event_time_usec;
+ }
+
+ StreamIdType PopNextReadyStream() override {
+ return std::get<0>(PopNextReadyStreamAndPrecedence());
+ }
+
+ // Returns the next ready stream and its precedence.
+ std::tuple<StreamIdType, StreamPrecedenceType>
+ PopNextReadyStreamAndPrecedence() override {
+ for (SpdyPriority p = kV3HighestPriority; p <= kV3LowestPriority; ++p) {
+ ReadyList& ready_list = priority_infos_[p].ready_list;
+ if (!ready_list.empty()) {
+ StreamInfo* info = ready_list.front();
+ ready_list.pop_front();
+ --num_ready_streams_;
+
+ DCHECK(stream_infos_.find(info->stream_id) != stream_infos_.end());
+ info->ready = false;
+ return std::make_tuple(info->stream_id,
+ StreamPrecedenceType(info->priority));
+ }
+ }
+ SPDY_BUG << "No ready streams available";
+ return std::make_tuple(0, StreamPrecedenceType(kV3LowestPriority));
+ }
+
+ bool ShouldYield(StreamIdType stream_id) const override {
+ auto it = stream_infos_.find(stream_id);
+ if (it == stream_infos_.end()) {
+ SPDY_BUG << "Stream " << stream_id << " not registered";
+ return false;
+ }
+
+ // If there's a higher priority stream, this stream should yield.
+ const StreamInfo& stream_info = it->second;
+ for (SpdyPriority p = kV3HighestPriority; p < stream_info.priority; ++p) {
+ if (!priority_infos_[p].ready_list.empty()) {
+ return true;
+ }
+ }
+
+ // If this priority level is empty, or this stream is the next up, there's
+ // no need to yield.
+ const auto& ready_list = priority_infos_[it->second.priority].ready_list;
+ if (ready_list.empty() || ready_list.front()->stream_id == stream_id) {
+ return false;
+ }
+
+ // There are other streams in this priority level which take precedence.
+ // Yield.
+ return true;
+ }
+
+ void MarkStreamReady(StreamIdType stream_id, bool add_to_front) override {
+ auto it = stream_infos_.find(stream_id);
+ if (it == stream_infos_.end()) {
+ SPDY_BUG << "Stream " << stream_id << " not registered";
+ return;
+ }
+ StreamInfo& stream_info = it->second;
+ if (stream_info.ready) {
+ return;
+ }
+ ReadyList& ready_list = priority_infos_[stream_info.priority].ready_list;
+ if (add_to_front) {
+ ready_list.push_front(&stream_info);
+ } else {
+ ready_list.push_back(&stream_info);
+ }
+ ++num_ready_streams_;
+ stream_info.ready = true;
+ }
+
+ void MarkStreamNotReady(StreamIdType stream_id) override {
+ auto it = stream_infos_.find(stream_id);
+ if (it == stream_infos_.end()) {
+ SPDY_BUG << "Stream " << stream_id << " not registered";
+ return;
+ }
+ StreamInfo& stream_info = it->second;
+ if (!stream_info.ready) {
+ return;
+ }
+ bool erased =
+ Erase(&priority_infos_[stream_info.priority].ready_list, stream_info);
+ DCHECK(erased);
+ stream_info.ready = false;
+ }
+
+ // Returns true iff the number of ready streams is non-zero.
+ bool HasReadyStreams() const override { return num_ready_streams_ > 0; }
+
+ // Returns the number of ready streams.
+ size_t NumReadyStreams() const override { return num_ready_streams_; }
+
+ SpdyString DebugString() const override {
+ return SpdyStrCat(
+ "PriorityWriteScheduler {num_streams=", stream_infos_.size(),
+ " num_ready_streams=", NumReadyStreams(), "}");
+ }
+
+ // Returns true if a stream is ready.
+ bool IsStreamReady(StreamIdType stream_id) const {
+ auto it = stream_infos_.find(stream_id);
+ if (it == stream_infos_.end()) {
+ DLOG(INFO) << "Stream " << stream_id << " not registered";
+ return false;
+ }
+ return it->second.ready;
+ }
+
+ private:
+ friend class test::PriorityWriteSchedulerPeer<StreamIdType>;
+
+ // State kept for all registered streams. All ready streams have ready = true
+ // and should be present in priority_infos_[priority].ready_list.
+ struct StreamInfo {
+ SpdyPriority priority;
+ StreamIdType stream_id;
+ bool ready;
+ };
+
+ // O(1) size lookup, O(1) insert at front or back (amortized).
+ using ReadyList = http2::Http2Deque<StreamInfo*>;
+
+ // State kept for each priority level.
+ struct PriorityInfo {
+ // IDs of streams that are ready to write.
+ ReadyList ready_list;
+ // Time of latest write event for stream of this priority, in microseconds.
+ int64_t last_event_time_usec = 0;
+ };
+
+ typedef std::unordered_map<StreamIdType, StreamInfo> StreamInfoMap;
+
+ // Erases first occurrence (which should be the only one) of |info| in
+ // |ready_list|, returning true if found (and erased), or false otherwise.
+ // Decrements |num_ready_streams_| if an entry is erased.
+ bool Erase(ReadyList* ready_list, const StreamInfo& info) {
+ auto it = std::find(ready_list->begin(), ready_list->end(), &info);
+ if (it == ready_list->end()) {
+ return false;
+ }
+ ready_list->erase(it);
+ --num_ready_streams_;
+ return true;
+ }
+
+ // Number of ready streams.
+ size_t num_ready_streams_ = 0;
+ // Per-priority state, including ready lists.
+ PriorityInfo priority_infos_[kV3LowestPriority + 1];
+ // StreamInfos for all registered streams.
+ StreamInfoMap stream_infos_;
+};
+
+} // namespace spdy
+
+#endif // QUICHE_SPDY_CORE_PRIORITY_WRITE_SCHEDULER_H_
diff --git a/chromium/net/third_party/quiche/src/spdy/core/priority_write_scheduler_test.cc b/chromium/net/third_party/quiche/src/spdy/core/priority_write_scheduler_test.cc
new file mode 100644
index 00000000000..a285cb2e4bd
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/spdy/core/priority_write_scheduler_test.cc
@@ -0,0 +1,371 @@
+// Copyright (c) 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/spdy/core/priority_write_scheduler.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_protocol.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_test_utils.h"
+
+namespace spdy {
+namespace test {
+
+template <typename StreamIdType>
+class PriorityWriteSchedulerPeer {
+ public:
+ explicit PriorityWriteSchedulerPeer(
+ PriorityWriteScheduler<StreamIdType>* scheduler)
+ : scheduler_(scheduler) {}
+
+ size_t NumReadyStreams(SpdyPriority priority) const {
+ return scheduler_->priority_infos_[priority].ready_list.size();
+ }
+
+ private:
+ PriorityWriteScheduler<StreamIdType>* scheduler_;
+};
+
+namespace {
+
+class PriorityWriteSchedulerTest : public ::testing::Test {
+ public:
+ PriorityWriteSchedulerTest() : peer_(&scheduler_) {}
+
+ PriorityWriteScheduler<SpdyStreamId> scheduler_;
+ PriorityWriteSchedulerPeer<SpdyStreamId> peer_;
+};
+
+TEST_F(PriorityWriteSchedulerTest, RegisterUnregisterStreams) {
+ EXPECT_FALSE(scheduler_.HasReadyStreams());
+ EXPECT_FALSE(scheduler_.StreamRegistered(1));
+ scheduler_.RegisterStream(1, SpdyStreamPrecedence(1));
+ EXPECT_TRUE(scheduler_.StreamRegistered(1));
+
+ // Root stream counts as already registered.
+ EXPECT_SPDY_BUG(
+ scheduler_.RegisterStream(kHttp2RootStreamId, SpdyStreamPrecedence(1)),
+ "Stream 0 already registered");
+
+ // Try redundant registrations.
+ EXPECT_SPDY_BUG(scheduler_.RegisterStream(1, SpdyStreamPrecedence(1)),
+ "Stream 1 already registered");
+ EXPECT_SPDY_BUG(scheduler_.RegisterStream(1, SpdyStreamPrecedence(2)),
+ "Stream 1 already registered");
+
+ scheduler_.RegisterStream(2, SpdyStreamPrecedence(3));
+
+ // Verify registration != ready.
+ EXPECT_FALSE(scheduler_.HasReadyStreams());
+
+ scheduler_.UnregisterStream(1);
+ scheduler_.UnregisterStream(2);
+
+ // Try redundant unregistration.
+ EXPECT_SPDY_BUG(scheduler_.UnregisterStream(1), "Stream 1 not registered");
+ EXPECT_SPDY_BUG(scheduler_.UnregisterStream(2), "Stream 2 not registered");
+}
+
+TEST_F(PriorityWriteSchedulerTest, RegisterStreamWithHttp2StreamDependency) {
+ EXPECT_FALSE(scheduler_.HasReadyStreams());
+ EXPECT_FALSE(scheduler_.StreamRegistered(1));
+ scheduler_.RegisterStream(
+ 1, SpdyStreamPrecedence(kHttp2RootStreamId, 123, false));
+ EXPECT_TRUE(scheduler_.StreamRegistered(1));
+ EXPECT_TRUE(scheduler_.GetStreamPrecedence(1).is_spdy3_priority());
+ EXPECT_EQ(3, scheduler_.GetStreamPrecedence(1).spdy3_priority());
+ EXPECT_FALSE(scheduler_.HasReadyStreams());
+
+ EXPECT_SPDY_BUG(scheduler_.RegisterStream(
+ 1, SpdyStreamPrecedence(kHttp2RootStreamId, 256, false)),
+ "Stream 1 already registered");
+ EXPECT_TRUE(scheduler_.GetStreamPrecedence(1).is_spdy3_priority());
+ EXPECT_EQ(3, scheduler_.GetStreamPrecedence(1).spdy3_priority());
+
+ // Registering stream with a non-existent parent stream is permissible, per
+ // b/15676312, but parent stream will always be reset to 0.
+ scheduler_.RegisterStream(2, SpdyStreamPrecedence(3, 123, false));
+ EXPECT_TRUE(scheduler_.StreamRegistered(2));
+ EXPECT_FALSE(scheduler_.StreamRegistered(3));
+ EXPECT_EQ(kHttp2RootStreamId, scheduler_.GetStreamPrecedence(2).parent_id());
+}
+
+TEST_F(PriorityWriteSchedulerTest, GetStreamPrecedence) {
+ // Unknown streams tolerated due to b/15676312. However, return lowest
+ // priority.
+ EXPECT_EQ(kV3LowestPriority,
+ scheduler_.GetStreamPrecedence(1).spdy3_priority());
+
+ scheduler_.RegisterStream(1, SpdyStreamPrecedence(3));
+ EXPECT_TRUE(scheduler_.GetStreamPrecedence(1).is_spdy3_priority());
+ EXPECT_EQ(3, scheduler_.GetStreamPrecedence(1).spdy3_priority());
+
+ // Redundant registration shouldn't change stream priority.
+ EXPECT_SPDY_BUG(scheduler_.RegisterStream(1, SpdyStreamPrecedence(4)),
+ "Stream 1 already registered");
+ EXPECT_EQ(3, scheduler_.GetStreamPrecedence(1).spdy3_priority());
+
+ scheduler_.UpdateStreamPrecedence(1, SpdyStreamPrecedence(5));
+ EXPECT_EQ(5, scheduler_.GetStreamPrecedence(1).spdy3_priority());
+
+ // Toggling ready state shouldn't change stream priority.
+ scheduler_.MarkStreamReady(1, true);
+ EXPECT_EQ(5, scheduler_.GetStreamPrecedence(1).spdy3_priority());
+
+ // Test changing priority of ready stream.
+ EXPECT_EQ(1u, peer_.NumReadyStreams(5));
+ scheduler_.UpdateStreamPrecedence(1, SpdyStreamPrecedence(6));
+ EXPECT_EQ(6, scheduler_.GetStreamPrecedence(1).spdy3_priority());
+ EXPECT_EQ(0u, peer_.NumReadyStreams(5));
+ EXPECT_EQ(1u, peer_.NumReadyStreams(6));
+
+ EXPECT_EQ(1u, scheduler_.PopNextReadyStream());
+ EXPECT_EQ(6, scheduler_.GetStreamPrecedence(1).spdy3_priority());
+
+ scheduler_.UnregisterStream(1);
+ EXPECT_EQ(kV3LowestPriority,
+ scheduler_.GetStreamPrecedence(1).spdy3_priority());
+}
+
+TEST_F(PriorityWriteSchedulerTest, PopNextReadyStreamAndPrecedence) {
+ scheduler_.RegisterStream(1, SpdyStreamPrecedence(3));
+ scheduler_.MarkStreamReady(1, true);
+ EXPECT_EQ(std::make_tuple(1u, SpdyStreamPrecedence(3)),
+ scheduler_.PopNextReadyStreamAndPrecedence());
+ scheduler_.UnregisterStream(1);
+}
+
+TEST_F(PriorityWriteSchedulerTest, UpdateStreamPrecedence) {
+ // For the moment, updating stream precedence on a non-registered stream
+ // should have no effect. In the future, it will lazily cause the stream to
+ // be registered (b/15676312).
+ EXPECT_EQ(kV3LowestPriority,
+ scheduler_.GetStreamPrecedence(3).spdy3_priority());
+ EXPECT_FALSE(scheduler_.StreamRegistered(3));
+ scheduler_.UpdateStreamPrecedence(3, SpdyStreamPrecedence(1));
+ EXPECT_FALSE(scheduler_.StreamRegistered(3));
+ EXPECT_EQ(kV3LowestPriority,
+ scheduler_.GetStreamPrecedence(3).spdy3_priority());
+
+ scheduler_.RegisterStream(3, SpdyStreamPrecedence(1));
+ EXPECT_EQ(1, scheduler_.GetStreamPrecedence(3).spdy3_priority());
+ scheduler_.UpdateStreamPrecedence(3, SpdyStreamPrecedence(2));
+ EXPECT_EQ(2, scheduler_.GetStreamPrecedence(3).spdy3_priority());
+
+ // Updating priority of stream to current priority value is valid, but has no
+ // effect.
+ scheduler_.UpdateStreamPrecedence(3, SpdyStreamPrecedence(2));
+ EXPECT_EQ(2, scheduler_.GetStreamPrecedence(3).spdy3_priority());
+
+ // Even though stream 4 is marked ready after stream 5, it should be returned
+ // first by PopNextReadyStream() since it has higher priority.
+ scheduler_.RegisterStream(4, SpdyStreamPrecedence(1));
+ scheduler_.MarkStreamReady(3, false); // priority 2
+ EXPECT_TRUE(scheduler_.IsStreamReady(3));
+ scheduler_.MarkStreamReady(4, false); // priority 1
+ EXPECT_TRUE(scheduler_.IsStreamReady(4));
+ EXPECT_EQ(4u, scheduler_.PopNextReadyStream());
+ EXPECT_FALSE(scheduler_.IsStreamReady(4));
+ EXPECT_EQ(3u, scheduler_.PopNextReadyStream());
+ EXPECT_FALSE(scheduler_.IsStreamReady(3));
+
+ // Verify that lowering priority of stream 4 causes it to be returned later
+ // by PopNextReadyStream().
+ scheduler_.MarkStreamReady(3, false); // priority 2
+ scheduler_.MarkStreamReady(4, false); // priority 1
+ scheduler_.UpdateStreamPrecedence(4, SpdyStreamPrecedence(3));
+ EXPECT_EQ(3u, scheduler_.PopNextReadyStream());
+ EXPECT_EQ(4u, scheduler_.PopNextReadyStream());
+
+ scheduler_.UnregisterStream(3);
+}
+
+TEST_F(PriorityWriteSchedulerTest,
+ UpdateStreamPrecedenceWithHttp2StreamDependency) {
+ // Unknown streams tolerated due to b/15676312, but should have no effect.
+ scheduler_.UpdateStreamPrecedence(3, SpdyStreamPrecedence(0, 100, false));
+ EXPECT_FALSE(scheduler_.StreamRegistered(3));
+
+ scheduler_.RegisterStream(3, SpdyStreamPrecedence(3));
+ scheduler_.UpdateStreamPrecedence(3, SpdyStreamPrecedence(0, 100, false));
+ EXPECT_TRUE(scheduler_.GetStreamPrecedence(3).is_spdy3_priority());
+ EXPECT_EQ(4, scheduler_.GetStreamPrecedence(3).spdy3_priority());
+
+ scheduler_.UnregisterStream(3);
+ scheduler_.UpdateStreamPrecedence(3, SpdyStreamPrecedence(0, 100, false));
+ EXPECT_FALSE(scheduler_.StreamRegistered(3));
+}
+
+TEST_F(PriorityWriteSchedulerTest, MarkStreamReadyBack) {
+ EXPECT_FALSE(scheduler_.HasReadyStreams());
+ EXPECT_SPDY_BUG(scheduler_.MarkStreamReady(1, false),
+ "Stream 1 not registered");
+ EXPECT_FALSE(scheduler_.HasReadyStreams());
+ EXPECT_SPDY_BUG(EXPECT_EQ(0u, scheduler_.PopNextReadyStream()),
+ "No ready streams available");
+
+ // Add a bunch of ready streams to tail of per-priority lists.
+ // Expected order: (P2) 4, (P3) 1, 2, 3, (P5) 5.
+ scheduler_.RegisterStream(1, SpdyStreamPrecedence(3));
+ scheduler_.MarkStreamReady(1, false);
+ EXPECT_TRUE(scheduler_.HasReadyStreams());
+ scheduler_.RegisterStream(2, SpdyStreamPrecedence(3));
+ scheduler_.MarkStreamReady(2, false);
+ scheduler_.RegisterStream(3, SpdyStreamPrecedence(3));
+ scheduler_.MarkStreamReady(3, false);
+ scheduler_.RegisterStream(4, SpdyStreamPrecedence(2));
+ scheduler_.MarkStreamReady(4, false);
+ scheduler_.RegisterStream(5, SpdyStreamPrecedence(5));
+ scheduler_.MarkStreamReady(5, false);
+
+ EXPECT_EQ(4u, scheduler_.PopNextReadyStream());
+ EXPECT_EQ(1u, scheduler_.PopNextReadyStream());
+ EXPECT_EQ(2u, scheduler_.PopNextReadyStream());
+ EXPECT_EQ(3u, scheduler_.PopNextReadyStream());
+ EXPECT_EQ(5u, scheduler_.PopNextReadyStream());
+ EXPECT_SPDY_BUG(EXPECT_EQ(0u, scheduler_.PopNextReadyStream()),
+ "No ready streams available");
+}
+
+TEST_F(PriorityWriteSchedulerTest, MarkStreamReadyFront) {
+ EXPECT_FALSE(scheduler_.HasReadyStreams());
+ EXPECT_SPDY_BUG(scheduler_.MarkStreamReady(1, true),
+ "Stream 1 not registered");
+ EXPECT_FALSE(scheduler_.HasReadyStreams());
+ EXPECT_SPDY_BUG(EXPECT_EQ(0u, scheduler_.PopNextReadyStream()),
+ "No ready streams available");
+
+ // Add a bunch of ready streams to head of per-priority lists.
+ // Expected order: (P2) 4, (P3) 3, 2, 1, (P5) 5
+ scheduler_.RegisterStream(1, SpdyStreamPrecedence(3));
+ scheduler_.MarkStreamReady(1, true);
+ EXPECT_TRUE(scheduler_.HasReadyStreams());
+ scheduler_.RegisterStream(2, SpdyStreamPrecedence(3));
+ scheduler_.MarkStreamReady(2, true);
+ scheduler_.RegisterStream(3, SpdyStreamPrecedence(3));
+ scheduler_.MarkStreamReady(3, true);
+ scheduler_.RegisterStream(4, SpdyStreamPrecedence(2));
+ scheduler_.MarkStreamReady(4, true);
+ scheduler_.RegisterStream(5, SpdyStreamPrecedence(5));
+ scheduler_.MarkStreamReady(5, true);
+
+ EXPECT_EQ(4u, scheduler_.PopNextReadyStream());
+ EXPECT_EQ(3u, scheduler_.PopNextReadyStream());
+ EXPECT_EQ(2u, scheduler_.PopNextReadyStream());
+ EXPECT_EQ(1u, scheduler_.PopNextReadyStream());
+ EXPECT_EQ(5u, scheduler_.PopNextReadyStream());
+ EXPECT_SPDY_BUG(EXPECT_EQ(0u, scheduler_.PopNextReadyStream()),
+ "No ready streams available");
+}
+
+TEST_F(PriorityWriteSchedulerTest, MarkStreamReadyBackAndFront) {
+ scheduler_.RegisterStream(1, SpdyStreamPrecedence(4));
+ scheduler_.RegisterStream(2, SpdyStreamPrecedence(3));
+ scheduler_.RegisterStream(3, SpdyStreamPrecedence(3));
+ scheduler_.RegisterStream(4, SpdyStreamPrecedence(3));
+ scheduler_.RegisterStream(5, SpdyStreamPrecedence(4));
+ scheduler_.RegisterStream(6, SpdyStreamPrecedence(1));
+
+ // Add a bunch of ready streams to per-priority lists, with variety of adding
+ // at head and tail.
+ // Expected order: (P1) 6, (P3) 4, 2, 3, (P4) 1, 5
+ scheduler_.MarkStreamReady(1, true);
+ scheduler_.MarkStreamReady(2, true);
+ scheduler_.MarkStreamReady(3, false);
+ scheduler_.MarkStreamReady(4, true);
+ scheduler_.MarkStreamReady(5, false);
+ scheduler_.MarkStreamReady(6, true);
+
+ EXPECT_EQ(6u, scheduler_.PopNextReadyStream());
+ EXPECT_EQ(4u, scheduler_.PopNextReadyStream());
+ EXPECT_EQ(2u, scheduler_.PopNextReadyStream());
+ EXPECT_EQ(3u, scheduler_.PopNextReadyStream());
+ EXPECT_EQ(1u, scheduler_.PopNextReadyStream());
+ EXPECT_EQ(5u, scheduler_.PopNextReadyStream());
+ EXPECT_SPDY_BUG(EXPECT_EQ(0u, scheduler_.PopNextReadyStream()),
+ "No ready streams available");
+}
+
+TEST_F(PriorityWriteSchedulerTest, MarkStreamNotReady) {
+ // Verify ready state reflected in NumReadyStreams().
+ scheduler_.RegisterStream(1, SpdyStreamPrecedence(1));
+ EXPECT_EQ(0u, scheduler_.NumReadyStreams());
+ scheduler_.MarkStreamReady(1, false);
+ EXPECT_EQ(1u, scheduler_.NumReadyStreams());
+ scheduler_.MarkStreamNotReady(1);
+ EXPECT_EQ(0u, scheduler_.NumReadyStreams());
+
+ // Empty pop should fail.
+ EXPECT_SPDY_BUG(EXPECT_EQ(0u, scheduler_.PopNextReadyStream()),
+ "No ready streams available");
+
+ // Tolerate redundant marking of a stream as not ready.
+ scheduler_.MarkStreamNotReady(1);
+ EXPECT_EQ(0u, scheduler_.NumReadyStreams());
+
+ // Should only be able to mark registered streams.
+ EXPECT_SPDY_BUG(scheduler_.MarkStreamNotReady(3), "Stream 3 not registered");
+}
+
+TEST_F(PriorityWriteSchedulerTest, UnregisterRemovesStream) {
+ scheduler_.RegisterStream(3, SpdyStreamPrecedence(4));
+ scheduler_.MarkStreamReady(3, false);
+ EXPECT_EQ(1u, scheduler_.NumReadyStreams());
+
+ // Unregistering a stream should remove it from set of ready streams.
+ scheduler_.UnregisterStream(3);
+ EXPECT_EQ(0u, scheduler_.NumReadyStreams());
+ EXPECT_SPDY_BUG(EXPECT_EQ(0u, scheduler_.PopNextReadyStream()),
+ "No ready streams available");
+}
+
+TEST_F(PriorityWriteSchedulerTest, ShouldYield) {
+ scheduler_.RegisterStream(1, SpdyStreamPrecedence(1));
+ scheduler_.RegisterStream(4, SpdyStreamPrecedence(4));
+ scheduler_.RegisterStream(5, SpdyStreamPrecedence(4));
+ scheduler_.RegisterStream(7, SpdyStreamPrecedence(7));
+
+ // Make sure we don't yield when the list is empty.
+ EXPECT_FALSE(scheduler_.ShouldYield(1));
+
+ // Add a low priority stream.
+ scheduler_.MarkStreamReady(4, false);
+ // 4 should not yield to itself.
+ EXPECT_FALSE(scheduler_.ShouldYield(4));
+ // 7 should yield as 4 is blocked and a higher priority.
+ EXPECT_TRUE(scheduler_.ShouldYield(7));
+ // 5 should yield to 4 as they are the same priority.
+ EXPECT_TRUE(scheduler_.ShouldYield(5));
+ // 1 should not yield as 1 is higher priority.
+ EXPECT_FALSE(scheduler_.ShouldYield(1));
+
+ // Add a second stream in that priority class.
+ scheduler_.MarkStreamReady(5, false);
+ // 4 and 5 are both blocked, but 4 is at the front so should not yield.
+ EXPECT_FALSE(scheduler_.ShouldYield(4));
+ EXPECT_TRUE(scheduler_.ShouldYield(5));
+}
+
+TEST_F(PriorityWriteSchedulerTest, GetLatestEventWithPrecedence) {
+ EXPECT_SPDY_BUG(scheduler_.RecordStreamEventTime(3, 5),
+ "Stream 3 not registered");
+ EXPECT_SPDY_BUG(EXPECT_EQ(0, scheduler_.GetLatestEventWithPrecedence(4)),
+ "Stream 4 not registered");
+
+ for (int i = 1; i < 5; ++i) {
+ scheduler_.RegisterStream(i, SpdyStreamPrecedence(i));
+ }
+ for (int i = 1; i < 5; ++i) {
+ EXPECT_EQ(0, scheduler_.GetLatestEventWithPrecedence(i));
+ }
+ for (int i = 1; i < 5; ++i) {
+ scheduler_.RecordStreamEventTime(i, i * 100);
+ }
+ for (int i = 1; i < 5; ++i) {
+ EXPECT_EQ((i - 1) * 100, scheduler_.GetLatestEventWithPrecedence(i));
+ }
+}
+
+} // namespace
+} // namespace test
+} // namespace spdy
diff --git a/chromium/net/third_party/quiche/src/spdy/core/spdy_alt_svc_wire_format.cc b/chromium/net/third_party/quiche/src/spdy/core/spdy_alt_svc_wire_format.cc
new file mode 100644
index 00000000000..15234a6e445
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/spdy/core/spdy_alt_svc_wire_format.cc
@@ -0,0 +1,390 @@
+// Copyright (c) 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/spdy/core/spdy_alt_svc_wire_format.h"
+
+#include <algorithm>
+#include <cctype>
+#include <limits>
+
+#include "base/logging.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_string_utils.h"
+
+namespace spdy {
+
+namespace {
+
+template <class T>
+bool ParsePositiveIntegerImpl(SpdyStringPiece::const_iterator c,
+ SpdyStringPiece::const_iterator end,
+ T* value) {
+ *value = 0;
+ for (; c != end && std::isdigit(*c); ++c) {
+ if (*value > std::numeric_limits<T>::max() / 10) {
+ return false;
+ }
+ *value *= 10;
+ if (*value > std::numeric_limits<T>::max() - (*c - '0')) {
+ return false;
+ }
+ *value += *c - '0';
+ }
+ return (c == end && *value > 0);
+}
+
+} // namespace
+
+SpdyAltSvcWireFormat::AlternativeService::AlternativeService() = default;
+
+SpdyAltSvcWireFormat::AlternativeService::AlternativeService(
+ const SpdyString& protocol_id,
+ const SpdyString& host,
+ uint16_t port,
+ uint32_t max_age,
+ VersionVector version)
+ : protocol_id(protocol_id),
+ host(host),
+ port(port),
+ max_age(max_age),
+ version(std::move(version)) {}
+
+SpdyAltSvcWireFormat::AlternativeService::~AlternativeService() = default;
+
+SpdyAltSvcWireFormat::AlternativeService::AlternativeService(
+ const AlternativeService& other) = default;
+
+// static
+bool SpdyAltSvcWireFormat::ParseHeaderFieldValue(
+ SpdyStringPiece value,
+ AlternativeServiceVector* altsvc_vector) {
+ // Empty value is invalid according to the specification.
+ if (value.empty()) {
+ return false;
+ }
+ altsvc_vector->clear();
+ if (value == SpdyStringPiece("clear")) {
+ return true;
+ }
+ SpdyStringPiece::const_iterator c = value.begin();
+ while (c != value.end()) {
+ // Parse protocol-id.
+ SpdyStringPiece::const_iterator percent_encoded_protocol_id_end =
+ std::find(c, value.end(), '=');
+ SpdyString protocol_id;
+ if (percent_encoded_protocol_id_end == c ||
+ !PercentDecode(c, percent_encoded_protocol_id_end, &protocol_id)) {
+ return false;
+ }
+ // Check for IETF format for advertising QUIC:
+ // hq=":443";quic=51303338;quic=51303334
+ const bool is_ietf_format_quic = (protocol_id == "hq");
+ c = percent_encoded_protocol_id_end;
+ if (c == value.end()) {
+ return false;
+ }
+ // Parse alt-authority.
+ DCHECK_EQ('=', *c);
+ ++c;
+ if (c == value.end() || *c != '"') {
+ return false;
+ }
+ ++c;
+ SpdyStringPiece::const_iterator alt_authority_begin = c;
+ for (; c != value.end() && *c != '"'; ++c) {
+ // Decode backslash encoding.
+ if (*c != '\\') {
+ continue;
+ }
+ ++c;
+ if (c == value.end()) {
+ return false;
+ }
+ }
+ if (c == alt_authority_begin || c == value.end()) {
+ return false;
+ }
+ DCHECK_EQ('"', *c);
+ SpdyString host;
+ uint16_t port;
+ if (!ParseAltAuthority(alt_authority_begin, c, &host, &port)) {
+ return false;
+ }
+ ++c;
+ // Parse parameters.
+ uint32_t max_age = 86400;
+ VersionVector version;
+ SpdyStringPiece::const_iterator parameters_end =
+ std::find(c, value.end(), ',');
+ while (c != parameters_end) {
+ SkipWhiteSpace(&c, parameters_end);
+ if (c == parameters_end) {
+ break;
+ }
+ if (*c != ';') {
+ return false;
+ }
+ ++c;
+ SkipWhiteSpace(&c, parameters_end);
+ if (c == parameters_end) {
+ break;
+ }
+ SpdyString parameter_name;
+ for (; c != parameters_end && *c != '=' && *c != ' ' && *c != '\t'; ++c) {
+ parameter_name.push_back(tolower(*c));
+ }
+ SkipWhiteSpace(&c, parameters_end);
+ if (c == parameters_end || *c != '=') {
+ return false;
+ }
+ ++c;
+ SkipWhiteSpace(&c, parameters_end);
+ SpdyStringPiece::const_iterator parameter_value_begin = c;
+ for (; c != parameters_end && *c != ';' && *c != ' ' && *c != '\t'; ++c) {
+ }
+ if (c == parameter_value_begin) {
+ return false;
+ }
+ if (parameter_name == "ma") {
+ if (!ParsePositiveInteger32(parameter_value_begin, c, &max_age)) {
+ return false;
+ }
+ } else if (!is_ietf_format_quic && parameter_name == "v") {
+ // Version is a comma separated list of positive integers enclosed in
+ // quotation marks. Since it can contain commas, which are not
+ // delineating alternative service entries, |parameters_end| and |c| can
+ // be invalid.
+ if (*parameter_value_begin != '"') {
+ return false;
+ }
+ c = std::find(parameter_value_begin + 1, value.end(), '"');
+ if (c == value.end()) {
+ return false;
+ }
+ ++c;
+ parameters_end = std::find(c, value.end(), ',');
+ SpdyStringPiece::const_iterator v_begin = parameter_value_begin + 1;
+ while (v_begin < c) {
+ SpdyStringPiece::const_iterator v_end = v_begin;
+ while (v_end < c - 1 && *v_end != ',') {
+ ++v_end;
+ }
+ uint16_t v;
+ if (!ParsePositiveInteger16(v_begin, v_end, &v)) {
+ return false;
+ }
+ version.push_back(v);
+ v_begin = v_end + 1;
+ if (v_begin == c - 1) {
+ // List ends in comma.
+ return false;
+ }
+ }
+ } else if (is_ietf_format_quic && parameter_name == "quic") {
+ // IETF format for advertising QUIC. Version is hex encoding of QUIC
+ // version tag. Hex-encoded string should not include leading "0x" or
+ // leading zeros.
+ // Example for advertising QUIC versions "Q038" and "Q034":
+ // hq=":443";quic=51303338;quic=51303334
+ if (*parameter_value_begin == '0') {
+ return false;
+ }
+ // Versions will be stored as the uint32_t hex decoding of the param
+ // value string. Example: QUIC version "Q038", which is advertised as:
+ // hq=":443";quic=51303338
+ // ... will be stored in |versions| as 0x51303338.
+ uint32_t quic_version;
+ if (!SpdyHexDecodeToUInt32(SpdyStringPiece(parameter_value_begin,
+ c - parameter_value_begin),
+ &quic_version) ||
+ quic_version == 0) {
+ return false;
+ }
+ version.push_back(quic_version);
+ }
+ }
+ altsvc_vector->emplace_back(protocol_id, host, port, max_age, version);
+ for (; c != value.end() && (*c == ' ' || *c == '\t' || *c == ','); ++c) {
+ }
+ }
+ return true;
+}
+
+// static
+SpdyString SpdyAltSvcWireFormat::SerializeHeaderFieldValue(
+ const AlternativeServiceVector& altsvc_vector) {
+ if (altsvc_vector.empty()) {
+ return SpdyString("clear");
+ }
+ const char kNibbleToHex[] = "0123456789ABCDEF";
+ SpdyString value;
+ for (const AlternativeService& altsvc : altsvc_vector) {
+ if (!value.empty()) {
+ value.push_back(',');
+ }
+ // Check for IETF format for advertising QUIC.
+ const bool is_ietf_format_quic = (altsvc.protocol_id == "hq");
+ // Percent escape protocol id according to
+ // http://tools.ietf.org/html/rfc7230#section-3.2.6.
+ for (char c : altsvc.protocol_id) {
+ if (isalnum(c)) {
+ value.push_back(c);
+ continue;
+ }
+ switch (c) {
+ case '!':
+ case '#':
+ case '$':
+ case '&':
+ case '\'':
+ case '*':
+ case '+':
+ case '-':
+ case '.':
+ case '^':
+ case '_':
+ case '`':
+ case '|':
+ case '~':
+ value.push_back(c);
+ break;
+ default:
+ value.push_back('%');
+ // Network byte order is big-endian.
+ value.push_back(kNibbleToHex[c >> 4]);
+ value.push_back(kNibbleToHex[c & 0x0f]);
+ break;
+ }
+ }
+ value.push_back('=');
+ value.push_back('"');
+ for (char c : altsvc.host) {
+ if (c == '"' || c == '\\') {
+ value.push_back('\\');
+ }
+ value.push_back(c);
+ }
+ value.append(SpdyStrCat(":", altsvc.port, "\""));
+ if (altsvc.max_age != 86400) {
+ value.append(SpdyStrCat("; ma=", altsvc.max_age));
+ }
+ if (!altsvc.version.empty()) {
+ if (is_ietf_format_quic) {
+ for (uint32_t quic_version : altsvc.version) {
+ value.append("; quic=");
+ value.append(SpdyHexEncodeUInt32AndTrim(quic_version));
+ }
+ } else {
+ value.append("; v=\"");
+ for (auto it = altsvc.version.begin(); it != altsvc.version.end();
+ ++it) {
+ if (it != altsvc.version.begin()) {
+ value.append(",");
+ }
+ value.append(SpdyStrCat(*it));
+ }
+ value.append("\"");
+ }
+ }
+ }
+ return value;
+}
+
+// static
+void SpdyAltSvcWireFormat::SkipWhiteSpace(SpdyStringPiece::const_iterator* c,
+ SpdyStringPiece::const_iterator end) {
+ for (; *c != end && (**c == ' ' || **c == '\t'); ++*c) {
+ }
+}
+
+// static
+bool SpdyAltSvcWireFormat::PercentDecode(SpdyStringPiece::const_iterator c,
+ SpdyStringPiece::const_iterator end,
+ SpdyString* output) {
+ output->clear();
+ for (; c != end; ++c) {
+ if (*c != '%') {
+ output->push_back(*c);
+ continue;
+ }
+ DCHECK_EQ('%', *c);
+ ++c;
+ if (c == end || !std::isxdigit(*c)) {
+ return false;
+ }
+ // Network byte order is big-endian.
+ char decoded = SpdyHexDigitToInt(*c) << 4;
+ ++c;
+ if (c == end || !std::isxdigit(*c)) {
+ return false;
+ }
+ decoded += SpdyHexDigitToInt(*c);
+ output->push_back(decoded);
+ }
+ return true;
+}
+
+// static
+bool SpdyAltSvcWireFormat::ParseAltAuthority(
+ SpdyStringPiece::const_iterator c,
+ SpdyStringPiece::const_iterator end,
+ SpdyString* host,
+ uint16_t* port) {
+ host->clear();
+ if (c == end) {
+ return false;
+ }
+ if (*c == '[') {
+ for (; c != end && *c != ']'; ++c) {
+ if (*c == '"') {
+ // Port is mandatory.
+ return false;
+ }
+ host->push_back(*c);
+ }
+ if (c == end) {
+ return false;
+ }
+ DCHECK_EQ(']', *c);
+ host->push_back(*c);
+ ++c;
+ } else {
+ for (; c != end && *c != ':'; ++c) {
+ if (*c == '"') {
+ // Port is mandatory.
+ return false;
+ }
+ if (*c == '\\') {
+ ++c;
+ if (c == end) {
+ return false;
+ }
+ }
+ host->push_back(*c);
+ }
+ }
+ if (c == end || *c != ':') {
+ return false;
+ }
+ DCHECK_EQ(':', *c);
+ ++c;
+ return ParsePositiveInteger16(c, end, port);
+}
+
+// static
+bool SpdyAltSvcWireFormat::ParsePositiveInteger16(
+ SpdyStringPiece::const_iterator c,
+ SpdyStringPiece::const_iterator end,
+ uint16_t* value) {
+ return ParsePositiveIntegerImpl<uint16_t>(c, end, value);
+}
+
+// static
+bool SpdyAltSvcWireFormat::ParsePositiveInteger32(
+ SpdyStringPiece::const_iterator c,
+ SpdyStringPiece::const_iterator end,
+ uint32_t* value) {
+ return ParsePositiveIntegerImpl<uint32_t>(c, end, value);
+}
+
+} // namespace spdy
diff --git a/chromium/net/third_party/quiche/src/spdy/core/spdy_alt_svc_wire_format.h b/chromium/net/third_party/quiche/src/spdy/core/spdy_alt_svc_wire_format.h
new file mode 100644
index 00000000000..ac834bc851a
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/spdy/core/spdy_alt_svc_wire_format.h
@@ -0,0 +1,88 @@
+// Copyright (c) 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// This file contains data structures and utility functions used for serializing
+// and parsing alternative service header values, common to HTTP/1.1 header
+// fields and HTTP/2 and QUIC ALTSVC frames. See specification at
+// https://httpwg.github.io/http-extensions/alt-svc.html.
+
+#ifndef QUICHE_SPDY_CORE_SPDY_ALT_SVC_WIRE_FORMAT_H_
+#define QUICHE_SPDY_CORE_SPDY_ALT_SVC_WIRE_FORMAT_H_
+
+#include <cstdint>
+#include <vector>
+
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_containers.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_export.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_string.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_string_piece.h"
+
+namespace spdy {
+
+namespace test {
+class SpdyAltSvcWireFormatPeer;
+} // namespace test
+
+class SPDY_EXPORT_PRIVATE SpdyAltSvcWireFormat {
+ public:
+ using VersionVector = SpdyInlinedVector<uint32_t, 8>;
+
+ struct SPDY_EXPORT_PRIVATE AlternativeService {
+ SpdyString protocol_id;
+ SpdyString host;
+
+ // Default is 0: invalid port.
+ uint16_t port = 0;
+ // Default is one day.
+ uint32_t max_age = 86400;
+ // Default is empty: unspecified version.
+ VersionVector version;
+
+ AlternativeService();
+ AlternativeService(const SpdyString& protocol_id,
+ const SpdyString& host,
+ uint16_t port,
+ uint32_t max_age,
+ VersionVector version);
+ AlternativeService(const AlternativeService& other);
+ ~AlternativeService();
+
+ bool operator==(const AlternativeService& other) const {
+ return protocol_id == other.protocol_id && host == other.host &&
+ port == other.port && version == other.version &&
+ max_age == other.max_age;
+ }
+ };
+ // An empty vector means alternative services should be cleared for given
+ // origin. Note that the wire format for this is the string "clear", not an
+ // empty value (which is invalid).
+ typedef std::vector<AlternativeService> AlternativeServiceVector;
+
+ friend class test::SpdyAltSvcWireFormatPeer;
+ static bool ParseHeaderFieldValue(SpdyStringPiece value,
+ AlternativeServiceVector* altsvc_vector);
+ static SpdyString SerializeHeaderFieldValue(
+ const AlternativeServiceVector& altsvc_vector);
+
+ private:
+ static void SkipWhiteSpace(SpdyStringPiece::const_iterator* c,
+ SpdyStringPiece::const_iterator end);
+ static bool PercentDecode(SpdyStringPiece::const_iterator c,
+ SpdyStringPiece::const_iterator end,
+ SpdyString* output);
+ static bool ParseAltAuthority(SpdyStringPiece::const_iterator c,
+ SpdyStringPiece::const_iterator end,
+ SpdyString* host,
+ uint16_t* port);
+ static bool ParsePositiveInteger16(SpdyStringPiece::const_iterator c,
+ SpdyStringPiece::const_iterator end,
+ uint16_t* value);
+ static bool ParsePositiveInteger32(SpdyStringPiece::const_iterator c,
+ SpdyStringPiece::const_iterator end,
+ uint32_t* value);
+};
+
+} // namespace spdy
+
+#endif // QUICHE_SPDY_CORE_SPDY_ALT_SVC_WIRE_FORMAT_H_
diff --git a/chromium/net/third_party/quiche/src/spdy/core/spdy_alt_svc_wire_format_test.cc b/chromium/net/third_party/quiche/src/spdy/core/spdy_alt_svc_wire_format_test.cc
new file mode 100644
index 00000000000..c2595f0b4ec
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/spdy/core/spdy_alt_svc_wire_format_test.cc
@@ -0,0 +1,573 @@
+// Copyright (c) 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/spdy/core/spdy_alt_svc_wire_format.h"
+
+#include "base/logging.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace spdy {
+
+namespace test {
+
+// Expose all private methods of class SpdyAltSvcWireFormat.
+class SpdyAltSvcWireFormatPeer {
+ public:
+ static void SkipWhiteSpace(SpdyStringPiece::const_iterator* c,
+ SpdyStringPiece::const_iterator end) {
+ SpdyAltSvcWireFormat::SkipWhiteSpace(c, end);
+ }
+ static bool PercentDecode(SpdyStringPiece::const_iterator c,
+ SpdyStringPiece::const_iterator end,
+ SpdyString* output) {
+ return SpdyAltSvcWireFormat::PercentDecode(c, end, output);
+ }
+ static bool ParseAltAuthority(SpdyStringPiece::const_iterator c,
+ SpdyStringPiece::const_iterator end,
+ SpdyString* host,
+ uint16_t* port) {
+ return SpdyAltSvcWireFormat::ParseAltAuthority(c, end, host, port);
+ }
+ static bool ParsePositiveInteger16(SpdyStringPiece::const_iterator c,
+ SpdyStringPiece::const_iterator end,
+ uint16_t* max_age) {
+ return SpdyAltSvcWireFormat::ParsePositiveInteger16(c, end, max_age);
+ }
+ static bool ParsePositiveInteger32(SpdyStringPiece::const_iterator c,
+ SpdyStringPiece::const_iterator end,
+ uint32_t* max_age) {
+ return SpdyAltSvcWireFormat::ParsePositiveInteger32(c, end, max_age);
+ }
+};
+
+} // namespace test
+
+namespace {
+
+// Generate header field values, possibly with multiply defined parameters and
+// random case, and corresponding AlternativeService entries.
+void FuzzHeaderFieldValue(
+ int i,
+ SpdyString* header_field_value,
+ SpdyAltSvcWireFormat::AlternativeService* expected_altsvc) {
+ if (!header_field_value->empty()) {
+ header_field_value->push_back(',');
+ }
+ // TODO(b/77515496): use struct of bools instead of int |i| to generate the
+ // header field value.
+ bool is_ietf_format_quic = (i & 1 << 0) != 0;
+ if (i & 1 << 0) {
+ expected_altsvc->protocol_id = "hq";
+ header_field_value->append("hq=\"");
+ } else {
+ expected_altsvc->protocol_id = "a=b%c";
+ header_field_value->append("a%3Db%25c=\"");
+ }
+ if (i & 1 << 1) {
+ expected_altsvc->host = "foo\"bar\\baz";
+ header_field_value->append("foo\\\"bar\\\\baz");
+ } else {
+ expected_altsvc->host = "";
+ }
+ expected_altsvc->port = 42;
+ header_field_value->append(":42\"");
+ if (i & 1 << 2) {
+ header_field_value->append(" ");
+ }
+ if (i & 3 << 3) {
+ expected_altsvc->max_age = 1111;
+ header_field_value->append(";");
+ if (i & 1 << 3) {
+ header_field_value->append(" ");
+ }
+ header_field_value->append("mA=1111");
+ if (i & 2 << 3) {
+ header_field_value->append(" ");
+ }
+ }
+ if (i & 1 << 5) {
+ header_field_value->append("; J=s");
+ }
+ if (i & 1 << 6) {
+ if (is_ietf_format_quic) {
+ if (i & 1 << 7) {
+ expected_altsvc->version.push_back(0x923457e);
+ header_field_value->append("; quic=923457E");
+ } else {
+ expected_altsvc->version.push_back(1);
+ expected_altsvc->version.push_back(0xFFFFFFFF);
+ header_field_value->append("; quic=1; quic=fFfFffFf");
+ }
+ } else {
+ if (i & i << 7) {
+ expected_altsvc->version.push_back(24);
+ header_field_value->append("; v=\"24\"");
+ } else {
+ expected_altsvc->version.push_back(1);
+ expected_altsvc->version.push_back(65535);
+ header_field_value->append("; v=\"1,65535\"");
+ }
+ }
+ }
+ if (i & 1 << 8) {
+ expected_altsvc->max_age = 999999999;
+ header_field_value->append("; Ma=999999999");
+ }
+ if (i & 1 << 9) {
+ header_field_value->append(";");
+ }
+ if (i & 1 << 10) {
+ header_field_value->append(" ");
+ }
+ if (i & 1 << 11) {
+ header_field_value->append(",");
+ }
+ if (i & 1 << 12) {
+ header_field_value->append(" ");
+ }
+}
+
+// Generate AlternativeService entries and corresponding header field values in
+// canonical form, that is, what SerializeHeaderFieldValue() should output.
+void FuzzAlternativeService(int i,
+ SpdyAltSvcWireFormat::AlternativeService* altsvc,
+ SpdyString* expected_header_field_value) {
+ if (!expected_header_field_value->empty()) {
+ expected_header_field_value->push_back(',');
+ }
+ altsvc->protocol_id = "a=b%c";
+ altsvc->port = 42;
+ expected_header_field_value->append("a%3Db%25c=\"");
+ if (i & 1 << 0) {
+ altsvc->host = "foo\"bar\\baz";
+ expected_header_field_value->append("foo\\\"bar\\\\baz");
+ }
+ expected_header_field_value->append(":42\"");
+ if (i & 1 << 1) {
+ altsvc->max_age = 1111;
+ expected_header_field_value->append("; ma=1111");
+ }
+ if (i & 1 << 2) {
+ altsvc->version.push_back(24);
+ altsvc->version.push_back(25);
+ expected_header_field_value->append("; v=\"24,25\"");
+ }
+}
+
+// Tests of public API.
+
+TEST(SpdyAltSvcWireFormatTest, DefaultValues) {
+ SpdyAltSvcWireFormat::AlternativeService altsvc;
+ EXPECT_EQ("", altsvc.protocol_id);
+ EXPECT_EQ("", altsvc.host);
+ EXPECT_EQ(0u, altsvc.port);
+ EXPECT_EQ(86400u, altsvc.max_age);
+ EXPECT_TRUE(altsvc.version.empty());
+}
+
+TEST(SpdyAltSvcWireFormatTest, ParseInvalidEmptyHeaderFieldValue) {
+ SpdyAltSvcWireFormat::AlternativeServiceVector altsvc_vector;
+ ASSERT_FALSE(SpdyAltSvcWireFormat::ParseHeaderFieldValue("", &altsvc_vector));
+}
+
+TEST(SpdyAltSvcWireFormatTest, ParseHeaderFieldValueClear) {
+ SpdyAltSvcWireFormat::AlternativeServiceVector altsvc_vector;
+ ASSERT_TRUE(
+ SpdyAltSvcWireFormat::ParseHeaderFieldValue("clear", &altsvc_vector));
+ EXPECT_EQ(0u, altsvc_vector.size());
+}
+
+// Fuzz test of ParseHeaderFieldValue() with optional whitespaces, ignored
+// parameters, duplicate parameters, trailing space, trailing alternate service
+// separator, etc. Single alternative service at a time.
+TEST(SpdyAltSvcWireFormatTest, ParseHeaderFieldValue) {
+ for (int i = 0; i < 1 << 13; ++i) {
+ SpdyString header_field_value;
+ SpdyAltSvcWireFormat::AlternativeService expected_altsvc;
+ FuzzHeaderFieldValue(i, &header_field_value, &expected_altsvc);
+ SpdyAltSvcWireFormat::AlternativeServiceVector altsvc_vector;
+ ASSERT_TRUE(SpdyAltSvcWireFormat::ParseHeaderFieldValue(header_field_value,
+ &altsvc_vector));
+ ASSERT_EQ(1u, altsvc_vector.size());
+ EXPECT_EQ(expected_altsvc.protocol_id, altsvc_vector[0].protocol_id);
+ EXPECT_EQ(expected_altsvc.host, altsvc_vector[0].host);
+ EXPECT_EQ(expected_altsvc.port, altsvc_vector[0].port);
+ EXPECT_EQ(expected_altsvc.max_age, altsvc_vector[0].max_age);
+ EXPECT_EQ(expected_altsvc.version, altsvc_vector[0].version);
+
+ // Roundtrip test starting with |altsvc_vector|.
+ SpdyString reserialized_header_field_value =
+ SpdyAltSvcWireFormat::SerializeHeaderFieldValue(altsvc_vector);
+ SpdyAltSvcWireFormat::AlternativeServiceVector roundtrip_altsvc_vector;
+ ASSERT_TRUE(SpdyAltSvcWireFormat::ParseHeaderFieldValue(
+ reserialized_header_field_value, &roundtrip_altsvc_vector));
+ ASSERT_EQ(1u, roundtrip_altsvc_vector.size());
+ EXPECT_EQ(expected_altsvc.protocol_id,
+ roundtrip_altsvc_vector[0].protocol_id);
+ EXPECT_EQ(expected_altsvc.host, roundtrip_altsvc_vector[0].host);
+ EXPECT_EQ(expected_altsvc.port, roundtrip_altsvc_vector[0].port);
+ EXPECT_EQ(expected_altsvc.max_age, roundtrip_altsvc_vector[0].max_age);
+ EXPECT_EQ(expected_altsvc.version, roundtrip_altsvc_vector[0].version);
+ }
+}
+
+// Fuzz test of ParseHeaderFieldValue() with optional whitespaces, ignored
+// parameters, duplicate parameters, trailing space, trailing alternate service
+// separator, etc. Possibly multiple alternative service at a time.
+TEST(SpdyAltSvcWireFormatTest, ParseHeaderFieldValueMultiple) {
+ for (int i = 0; i < 1 << 13;) {
+ SpdyString header_field_value;
+ SpdyAltSvcWireFormat::AlternativeServiceVector expected_altsvc_vector;
+ // This will generate almost two hundred header field values with two,
+ // three, four, five, six, and seven alternative services each, and
+ // thousands with a single one.
+ do {
+ SpdyAltSvcWireFormat::AlternativeService expected_altsvc;
+ FuzzHeaderFieldValue(i, &header_field_value, &expected_altsvc);
+ expected_altsvc_vector.push_back(expected_altsvc);
+ ++i;
+ } while (i % 6 < i % 7);
+ SpdyAltSvcWireFormat::AlternativeServiceVector altsvc_vector;
+ ASSERT_TRUE(SpdyAltSvcWireFormat::ParseHeaderFieldValue(header_field_value,
+ &altsvc_vector));
+ ASSERT_EQ(expected_altsvc_vector.size(), altsvc_vector.size());
+ for (unsigned int j = 0; j < altsvc_vector.size(); ++j) {
+ EXPECT_EQ(expected_altsvc_vector[j].protocol_id,
+ altsvc_vector[j].protocol_id);
+ EXPECT_EQ(expected_altsvc_vector[j].host, altsvc_vector[j].host);
+ EXPECT_EQ(expected_altsvc_vector[j].port, altsvc_vector[j].port);
+ EXPECT_EQ(expected_altsvc_vector[j].max_age, altsvc_vector[j].max_age);
+ EXPECT_EQ(expected_altsvc_vector[j].version, altsvc_vector[j].version);
+ }
+
+ // Roundtrip test starting with |altsvc_vector|.
+ SpdyString reserialized_header_field_value =
+ SpdyAltSvcWireFormat::SerializeHeaderFieldValue(altsvc_vector);
+ SpdyAltSvcWireFormat::AlternativeServiceVector roundtrip_altsvc_vector;
+ ASSERT_TRUE(SpdyAltSvcWireFormat::ParseHeaderFieldValue(
+ reserialized_header_field_value, &roundtrip_altsvc_vector));
+ ASSERT_EQ(expected_altsvc_vector.size(), roundtrip_altsvc_vector.size());
+ for (unsigned int j = 0; j < roundtrip_altsvc_vector.size(); ++j) {
+ EXPECT_EQ(expected_altsvc_vector[j].protocol_id,
+ roundtrip_altsvc_vector[j].protocol_id);
+ EXPECT_EQ(expected_altsvc_vector[j].host,
+ roundtrip_altsvc_vector[j].host);
+ EXPECT_EQ(expected_altsvc_vector[j].port,
+ roundtrip_altsvc_vector[j].port);
+ EXPECT_EQ(expected_altsvc_vector[j].max_age,
+ roundtrip_altsvc_vector[j].max_age);
+ EXPECT_EQ(expected_altsvc_vector[j].version,
+ roundtrip_altsvc_vector[j].version);
+ }
+ }
+}
+
+TEST(SpdyAltSvcWireFormatTest, SerializeEmptyHeaderFieldValue) {
+ SpdyAltSvcWireFormat::AlternativeServiceVector altsvc_vector;
+ EXPECT_EQ("clear",
+ SpdyAltSvcWireFormat::SerializeHeaderFieldValue(altsvc_vector));
+}
+
+// Test ParseHeaderFieldValue() and SerializeHeaderFieldValue() on the same pair
+// of |expected_header_field_value| and |altsvc|, with and without hostname and
+// each
+// parameter. Single alternative service at a time.
+TEST(SpdyAltSvcWireFormatTest, RoundTrip) {
+ for (int i = 0; i < 1 << 3; ++i) {
+ SpdyAltSvcWireFormat::AlternativeService altsvc;
+ SpdyString expected_header_field_value;
+ FuzzAlternativeService(i, &altsvc, &expected_header_field_value);
+
+ // Test ParseHeaderFieldValue().
+ SpdyAltSvcWireFormat::AlternativeServiceVector parsed_altsvc_vector;
+ ASSERT_TRUE(SpdyAltSvcWireFormat::ParseHeaderFieldValue(
+ expected_header_field_value, &parsed_altsvc_vector));
+ ASSERT_EQ(1u, parsed_altsvc_vector.size());
+ EXPECT_EQ(altsvc.protocol_id, parsed_altsvc_vector[0].protocol_id);
+ EXPECT_EQ(altsvc.host, parsed_altsvc_vector[0].host);
+ EXPECT_EQ(altsvc.port, parsed_altsvc_vector[0].port);
+ EXPECT_EQ(altsvc.max_age, parsed_altsvc_vector[0].max_age);
+ EXPECT_EQ(altsvc.version, parsed_altsvc_vector[0].version);
+
+ // Test SerializeHeaderFieldValue().
+ SpdyAltSvcWireFormat::AlternativeServiceVector altsvc_vector;
+ altsvc_vector.push_back(altsvc);
+ EXPECT_EQ(expected_header_field_value,
+ SpdyAltSvcWireFormat::SerializeHeaderFieldValue(altsvc_vector));
+ }
+}
+
+// Test ParseHeaderFieldValue() and SerializeHeaderFieldValue() on the same pair
+// of |expected_header_field_value| and |altsvc|, with and without hostname and
+// each
+// parameter. Multiple alternative services at a time.
+TEST(SpdyAltSvcWireFormatTest, RoundTripMultiple) {
+ SpdyAltSvcWireFormat::AlternativeServiceVector altsvc_vector;
+ SpdyString expected_header_field_value;
+ for (int i = 0; i < 1 << 3; ++i) {
+ SpdyAltSvcWireFormat::AlternativeService altsvc;
+ FuzzAlternativeService(i, &altsvc, &expected_header_field_value);
+ altsvc_vector.push_back(altsvc);
+ }
+
+ // Test ParseHeaderFieldValue().
+ SpdyAltSvcWireFormat::AlternativeServiceVector parsed_altsvc_vector;
+ ASSERT_TRUE(SpdyAltSvcWireFormat::ParseHeaderFieldValue(
+ expected_header_field_value, &parsed_altsvc_vector));
+ ASSERT_EQ(altsvc_vector.size(), parsed_altsvc_vector.size());
+ auto expected_it = altsvc_vector.begin();
+ auto parsed_it = parsed_altsvc_vector.begin();
+ for (; expected_it != altsvc_vector.end(); ++expected_it, ++parsed_it) {
+ EXPECT_EQ(expected_it->protocol_id, parsed_it->protocol_id);
+ EXPECT_EQ(expected_it->host, parsed_it->host);
+ EXPECT_EQ(expected_it->port, parsed_it->port);
+ EXPECT_EQ(expected_it->max_age, parsed_it->max_age);
+ EXPECT_EQ(expected_it->version, parsed_it->version);
+ }
+
+ // Test SerializeHeaderFieldValue().
+ EXPECT_EQ(expected_header_field_value,
+ SpdyAltSvcWireFormat::SerializeHeaderFieldValue(altsvc_vector));
+}
+
+// ParseHeaderFieldValue() should return false on malformed field values:
+// invalid percent encoding, unmatched quotation mark, empty port, non-numeric
+// characters in numeric fields.
+TEST(SpdyAltSvcWireFormatTest, ParseHeaderFieldValueInvalid) {
+ SpdyAltSvcWireFormat::AlternativeServiceVector altsvc_vector;
+ const char* invalid_field_value_array[] = {"a%",
+ "a%x",
+ "a%b",
+ "a%9z",
+ "a=",
+ "a=\"",
+ "a=\"b\"",
+ "a=\":\"",
+ "a=\"c:\"",
+ "a=\"c:foo\"",
+ "a=\"c:42foo\"",
+ "a=\"b:42\"bar",
+ "a=\"b:42\" ; m",
+ "a=\"b:42\" ; min-age",
+ "a=\"b:42\" ; ma",
+ "a=\"b:42\" ; ma=",
+ "a=\"b:42\" ; v=\"..\"",
+ "a=\"b:42\" ; ma=ma",
+ "a=\"b:42\" ; ma=123bar",
+ "a=\"b:42\" ; v=24",
+ "a=\"b:42\" ; v=24,25",
+ "a=\"b:42\" ; v=\"-3\"",
+ "a=\"b:42\" ; v=\"1.2\"",
+ "a=\"b:42\" ; v=\"24,\""};
+ for (const char* invalid_field_value : invalid_field_value_array) {
+ EXPECT_FALSE(SpdyAltSvcWireFormat::ParseHeaderFieldValue(
+ invalid_field_value, &altsvc_vector))
+ << invalid_field_value;
+ }
+}
+
+// ParseHeaderFieldValue() should return false on a field values truncated
+// before closing quotation mark, without trying to access memory beyond the end
+// of the input.
+TEST(SpdyAltSvcWireFormatTest, ParseTruncatedHeaderFieldValue) {
+ SpdyAltSvcWireFormat::AlternativeServiceVector altsvc_vector;
+ const char* field_value_array[] = {"a=\":137\"", "a=\"foo:137\"",
+ "a%25=\"foo\\\"bar\\\\baz:137\""};
+ for (const SpdyString& field_value : field_value_array) {
+ for (size_t len = 1; len < field_value.size(); ++len) {
+ EXPECT_FALSE(SpdyAltSvcWireFormat::ParseHeaderFieldValue(
+ field_value.substr(0, len), &altsvc_vector))
+ << len;
+ }
+ }
+}
+
+// Tests of private methods.
+
+// Test SkipWhiteSpace().
+TEST(SpdyAltSvcWireFormatTest, SkipWhiteSpace) {
+ SpdyStringPiece input("a \tb ");
+ SpdyStringPiece::const_iterator c = input.begin();
+ test::SpdyAltSvcWireFormatPeer::SkipWhiteSpace(&c, input.end());
+ ASSERT_EQ(input.begin(), c);
+ ++c;
+ test::SpdyAltSvcWireFormatPeer::SkipWhiteSpace(&c, input.end());
+ ASSERT_EQ(input.begin() + 3, c);
+ ++c;
+ test::SpdyAltSvcWireFormatPeer::SkipWhiteSpace(&c, input.end());
+ ASSERT_EQ(input.end(), c);
+}
+
+// Test PercentDecode() on valid input.
+TEST(SpdyAltSvcWireFormatTest, PercentDecodeValid) {
+ SpdyStringPiece input("");
+ SpdyString output;
+ ASSERT_TRUE(test::SpdyAltSvcWireFormatPeer::PercentDecode(
+ input.begin(), input.end(), &output));
+ EXPECT_EQ("", output);
+
+ input = SpdyStringPiece("foo");
+ output.clear();
+ ASSERT_TRUE(test::SpdyAltSvcWireFormatPeer::PercentDecode(
+ input.begin(), input.end(), &output));
+ EXPECT_EQ("foo", output);
+
+ input = SpdyStringPiece("%2ca%5Cb");
+ output.clear();
+ ASSERT_TRUE(test::SpdyAltSvcWireFormatPeer::PercentDecode(
+ input.begin(), input.end(), &output));
+ EXPECT_EQ(",a\\b", output);
+}
+
+// Test PercentDecode() on invalid input.
+TEST(SpdyAltSvcWireFormatTest, PercentDecodeInvalid) {
+ const char* invalid_input_array[] = {"a%", "a%x", "a%b", "%J22", "%9z"};
+ for (const char* invalid_input : invalid_input_array) {
+ SpdyStringPiece input(invalid_input);
+ SpdyString output;
+ EXPECT_FALSE(test::SpdyAltSvcWireFormatPeer::PercentDecode(
+ input.begin(), input.end(), &output))
+ << input;
+ }
+}
+
+// Test ParseAltAuthority() on valid input.
+TEST(SpdyAltSvcWireFormatTest, ParseAltAuthorityValid) {
+ SpdyStringPiece input(":42");
+ SpdyString host;
+ uint16_t port;
+ ASSERT_TRUE(test::SpdyAltSvcWireFormatPeer::ParseAltAuthority(
+ input.begin(), input.end(), &host, &port));
+ EXPECT_TRUE(host.empty());
+ EXPECT_EQ(42, port);
+
+ input = SpdyStringPiece("foo:137");
+ ASSERT_TRUE(test::SpdyAltSvcWireFormatPeer::ParseAltAuthority(
+ input.begin(), input.end(), &host, &port));
+ EXPECT_EQ("foo", host);
+ EXPECT_EQ(137, port);
+
+ input = SpdyStringPiece("[2003:8:0:16::509d:9615]:443");
+ ASSERT_TRUE(test::SpdyAltSvcWireFormatPeer::ParseAltAuthority(
+ input.begin(), input.end(), &host, &port));
+ EXPECT_EQ("[2003:8:0:16::509d:9615]", host);
+ EXPECT_EQ(443, port);
+}
+
+// Test ParseAltAuthority() on invalid input: empty string, no port, zero port,
+// non-digit characters following port.
+TEST(SpdyAltSvcWireFormatTest, ParseAltAuthorityInvalid) {
+ const char* invalid_input_array[] = {"",
+ ":",
+ "foo:",
+ ":bar",
+ ":0",
+ "foo:0",
+ ":12bar",
+ "foo:23bar",
+ " ",
+ ":12 ",
+ "foo:12 ",
+ "[2003:8:0:16::509d:9615]",
+ "[2003:8:0:16::509d:9615]:",
+ "[2003:8:0:16::509d:9615]foo:443",
+ "[2003:8:0:16::509d:9615:443",
+ "2003:8:0:16::509d:9615]:443"};
+ for (const char* invalid_input : invalid_input_array) {
+ SpdyStringPiece input(invalid_input);
+ SpdyString host;
+ uint16_t port;
+ EXPECT_FALSE(test::SpdyAltSvcWireFormatPeer::ParseAltAuthority(
+ input.begin(), input.end(), &host, &port))
+ << input;
+ }
+}
+
+// Test ParseInteger() on valid input.
+TEST(SpdyAltSvcWireFormatTest, ParseIntegerValid) {
+ SpdyStringPiece input("3");
+ uint16_t value;
+ ASSERT_TRUE(test::SpdyAltSvcWireFormatPeer::ParsePositiveInteger16(
+ input.begin(), input.end(), &value));
+ EXPECT_EQ(3, value);
+
+ input = SpdyStringPiece("1337");
+ ASSERT_TRUE(test::SpdyAltSvcWireFormatPeer::ParsePositiveInteger16(
+ input.begin(), input.end(), &value));
+ EXPECT_EQ(1337, value);
+}
+
+// Test ParseIntegerValid() on invalid input: empty, zero, non-numeric, trailing
+// non-numeric characters.
+TEST(SpdyAltSvcWireFormatTest, ParseIntegerInvalid) {
+ const char* invalid_input_array[] = {"", " ", "a", "0", "00", "1 ", "12b"};
+ for (const char* invalid_input : invalid_input_array) {
+ SpdyStringPiece input(invalid_input);
+ uint16_t value;
+ EXPECT_FALSE(test::SpdyAltSvcWireFormatPeer::ParsePositiveInteger16(
+ input.begin(), input.end(), &value))
+ << input;
+ }
+}
+
+// Test ParseIntegerValid() around overflow limit.
+TEST(SpdyAltSvcWireFormatTest, ParseIntegerOverflow) {
+ // Largest possible uint16_t value.
+ SpdyStringPiece input("65535");
+ uint16_t value16;
+ ASSERT_TRUE(test::SpdyAltSvcWireFormatPeer::ParsePositiveInteger16(
+ input.begin(), input.end(), &value16));
+ EXPECT_EQ(65535, value16);
+
+ // Overflow uint16_t, ParsePositiveInteger16() should return false.
+ input = SpdyStringPiece("65536");
+ ASSERT_FALSE(test::SpdyAltSvcWireFormatPeer::ParsePositiveInteger16(
+ input.begin(), input.end(), &value16));
+
+ // However, even if overflow is not checked for, 65536 overflows to 0, which
+ // returns false anyway. Check for a larger number which overflows to 1.
+ input = SpdyStringPiece("65537");
+ ASSERT_FALSE(test::SpdyAltSvcWireFormatPeer::ParsePositiveInteger16(
+ input.begin(), input.end(), &value16));
+
+ // Largest possible uint32_t value.
+ input = SpdyStringPiece("4294967295");
+ uint32_t value32;
+ ASSERT_TRUE(test::SpdyAltSvcWireFormatPeer::ParsePositiveInteger32(
+ input.begin(), input.end(), &value32));
+ EXPECT_EQ(4294967295, value32);
+
+ // Overflow uint32_t, ParsePositiveInteger32() should return false.
+ input = SpdyStringPiece("4294967296");
+ ASSERT_FALSE(test::SpdyAltSvcWireFormatPeer::ParsePositiveInteger32(
+ input.begin(), input.end(), &value32));
+
+ // However, even if overflow is not checked for, 4294967296 overflows to 0,
+ // which returns false anyway. Check for a larger number which overflows to
+ // 1.
+ input = SpdyStringPiece("4294967297");
+ ASSERT_FALSE(test::SpdyAltSvcWireFormatPeer::ParsePositiveInteger32(
+ input.begin(), input.end(), &value32));
+}
+
+// Test parsing an Alt-Svc entry with IP literal hostname.
+// Regression test for https://crbug.com/664173.
+TEST(SpdyAltSvcWireFormatTest, ParseIPLiteral) {
+ const char* input =
+ "quic=\"[2003:8:0:16::509d:9615]:443\"; v=\"36,35\"; ma=60";
+ SpdyAltSvcWireFormat::AlternativeServiceVector altsvc_vector;
+ ASSERT_TRUE(
+ SpdyAltSvcWireFormat::ParseHeaderFieldValue(input, &altsvc_vector));
+ EXPECT_EQ(1u, altsvc_vector.size());
+ EXPECT_EQ("quic", altsvc_vector[0].protocol_id);
+ EXPECT_EQ("[2003:8:0:16::509d:9615]", altsvc_vector[0].host);
+ EXPECT_EQ(443u, altsvc_vector[0].port);
+ EXPECT_EQ(60u, altsvc_vector[0].max_age);
+ EXPECT_THAT(altsvc_vector[0].version, ::testing::ElementsAre(36, 35));
+}
+
+} // namespace
+
+} // namespace spdy
diff --git a/chromium/net/third_party/quiche/src/spdy/core/spdy_bitmasks.h b/chromium/net/third_party/quiche/src/spdy/core/spdy_bitmasks.h
new file mode 100644
index 00000000000..657bd1761e9
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/spdy/core/spdy_bitmasks.h
@@ -0,0 +1,18 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_SPDY_CORE_SPDY_BITMASKS_H_
+#define QUICHE_SPDY_CORE_SPDY_BITMASKS_H_
+
+namespace spdy {
+
+// StreamId mask from the SpdyHeader
+const unsigned int kStreamIdMask = 0x7fffffff;
+
+// Mask the lower 24 bits.
+const unsigned int kLengthMask = 0xffffff;
+
+} // namespace spdy
+
+#endif // QUICHE_SPDY_CORE_SPDY_BITMASKS_H_
diff --git a/chromium/net/third_party/quiche/src/spdy/core/spdy_deframer_visitor.cc b/chromium/net/third_party/quiche/src/spdy/core/spdy_deframer_visitor.cc
new file mode 100644
index 00000000000..76188e9ce8b
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/spdy/core/spdy_deframer_visitor.cc
@@ -0,0 +1,1028 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/spdy/core/spdy_deframer_visitor.h"
+
+#include <stdlib.h>
+
+#include <algorithm>
+#include <cstdint>
+#include <limits>
+#include <memory>
+
+#include "base/logging.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_macros.h"
+#include "net/third_party/quiche/src/spdy/core/hpack/hpack_constants.h"
+#include "net/third_party/quiche/src/spdy/core/mock_spdy_framer_visitor.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_frame_reader.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_protocol.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_test_utils.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_flags.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_ptr_util.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_string_piece.h"
+
+using ::testing::AssertionFailure;
+using ::testing::AssertionResult;
+using ::testing::AssertionSuccess;
+
+namespace spdy {
+namespace test {
+
+// Specify whether to process headers as request or response in visitor-related
+// params.
+enum class HeaderDirection { REQUEST, RESPONSE };
+
+// Types of HTTP/2 frames, per RFC 7540.
+// TODO(jamessynge): Switch to using http2/http2_constants.h when ready.
+enum Http2FrameType {
+ DATA = 0,
+ HEADERS = 1,
+ PRIORITY = 2,
+ RST_STREAM = 3,
+ SETTINGS = 4,
+ PUSH_PROMISE = 5,
+ PING = 6,
+ GOAWAY = 7,
+ WINDOW_UPDATE = 8,
+ CONTINUATION = 9,
+ ALTSVC = 10,
+
+ // Not a frame type.
+ UNSET = -1,
+ UNKNOWN = -2,
+};
+
+// TODO(jamessynge): Switch to using http2/http2_constants.h when ready.
+const char* Http2FrameTypeToString(Http2FrameType v) {
+ switch (v) {
+ case DATA:
+ return "DATA";
+ case HEADERS:
+ return "HEADERS";
+ case PRIORITY:
+ return "PRIORITY";
+ case RST_STREAM:
+ return "RST_STREAM";
+ case SETTINGS:
+ return "SETTINGS";
+ case PUSH_PROMISE:
+ return "PUSH_PROMISE";
+ case PING:
+ return "PING";
+ case GOAWAY:
+ return "GOAWAY";
+ case WINDOW_UPDATE:
+ return "WINDOW_UPDATE";
+ case CONTINUATION:
+ return "CONTINUATION";
+ case ALTSVC:
+ return "ALTSVC";
+ case UNSET:
+ return "UNSET";
+ case UNKNOWN:
+ return "UNKNOWN";
+ default:
+ return "Invalid Http2FrameType";
+ }
+}
+
+// TODO(jamessynge): Switch to using http2/http2_constants.h when ready.
+inline std::ostream& operator<<(std::ostream& out, Http2FrameType v) {
+ return out << Http2FrameTypeToString(v);
+}
+
+// Flag bits in the flag field of the common header of HTTP/2 frames
+// (see https://httpwg.github.io/specs/rfc7540.html#FrameHeader for details on
+// the fixed 9-octet header structure shared by all frames).
+// Flag bits are only valid for specified frame types.
+// TODO(jamessynge): Switch to using http2/http2_constants.h when ready.
+enum Http2HeaderFlag {
+ NO_FLAGS = 0,
+
+ END_STREAM_FLAG = 0x1,
+ ACK_FLAG = 0x1,
+ END_HEADERS_FLAG = 0x4,
+ PADDED_FLAG = 0x8,
+ PRIORITY_FLAG = 0x20,
+};
+
+// Returns name of frame type.
+// TODO(jamessynge): Switch to using http2/http2_constants.h when ready.
+const char* Http2FrameTypeToString(Http2FrameType v);
+
+void SpdyDeframerVisitorInterface::OnPingAck(
+ std::unique_ptr<SpdyPingIR> frame) {
+ OnPing(std::move(frame));
+}
+
+void SpdyDeframerVisitorInterface::OnSettingsAck(
+ std::unique_ptr<SpdySettingsIR> frame) {
+ OnSettings(std::move(frame), nullptr);
+}
+
+class SpdyTestDeframerImpl : public SpdyTestDeframer,
+ public SpdyHeadersHandlerInterface {
+ public:
+ explicit SpdyTestDeframerImpl(
+ std::unique_ptr<SpdyDeframerVisitorInterface> listener)
+ : listener_(std::move(listener)) {
+ CHECK(listener_ != nullptr);
+ }
+ SpdyTestDeframerImpl(const SpdyTestDeframerImpl&) = delete;
+ SpdyTestDeframerImpl& operator=(const SpdyTestDeframerImpl&) = delete;
+ ~SpdyTestDeframerImpl() override = default;
+
+ bool AtFrameEnd() override;
+
+ // Callbacks defined in SpdyFramerVisitorInterface. These are in the
+ // alphabetical order for ease of navigation, and are not in same order
+ // as in SpdyFramerVisitorInterface.
+ void OnAltSvc(SpdyStreamId stream_id,
+ SpdyStringPiece origin,
+ const SpdyAltSvcWireFormat::AlternativeServiceVector&
+ altsvc_vector) override;
+ void OnContinuation(SpdyStreamId stream_id, bool end) override;
+ SpdyHeadersHandlerInterface* OnHeaderFrameStart(
+ SpdyStreamId stream_id) override;
+ void OnHeaderFrameEnd(SpdyStreamId stream_id) override;
+ void OnDataFrameHeader(SpdyStreamId stream_id,
+ size_t length,
+ bool fin) override;
+ void OnError(http2::Http2DecoderAdapter::SpdyFramerError error) override;
+ void OnGoAway(SpdyStreamId last_accepted_stream_id,
+ SpdyErrorCode error_code) override;
+ bool OnGoAwayFrameData(const char* goaway_data, size_t len) override;
+ void OnHeaders(SpdyStreamId stream_id,
+ bool has_priority,
+ int weight,
+ SpdyStreamId parent_stream_id,
+ bool exclusive,
+ bool fin,
+ bool end) override;
+ void OnPing(SpdyPingId unique_id, bool is_ack) override;
+ void OnPriority(SpdyStreamId stream_id,
+ SpdyStreamId parent_stream_id,
+ int weight,
+ bool exclusive) override;
+ void OnPushPromise(SpdyStreamId stream_id,
+ SpdyStreamId promised_stream_id,
+ bool end) override;
+ void OnRstStream(SpdyStreamId stream_id, SpdyErrorCode error_code) override;
+ void OnSetting(SpdySettingsId id, uint32_t value) override;
+ void OnSettings() override;
+ void OnSettingsAck() override;
+ void OnSettingsEnd() override;
+ void OnStreamFrameData(SpdyStreamId stream_id,
+ const char* data,
+ size_t len) override;
+ void OnStreamEnd(SpdyStreamId stream_id) override;
+ void OnStreamPadLength(SpdyStreamId stream_id, size_t value) override;
+ void OnStreamPadding(SpdyStreamId stream_id, size_t len) override;
+ bool OnUnknownFrame(SpdyStreamId stream_id, uint8_t frame_type) override;
+ void OnWindowUpdate(SpdyStreamId stream_id, int delta_window_size) override;
+
+ // Callbacks defined in SpdyHeadersHandlerInterface.
+
+ void OnHeaderBlockStart() override;
+ void OnHeader(SpdyStringPiece key, SpdyStringPiece value) override;
+ void OnHeaderBlockEnd(size_t header_bytes_parsed,
+ size_t compressed_header_bytes_parsed) override;
+
+ protected:
+ void AtDataEnd();
+ void AtGoAwayEnd();
+ void AtHeadersEnd();
+ void AtPushPromiseEnd();
+
+ // Per-physical frame state.
+ // Frame type of the frame currently being processed.
+ Http2FrameType frame_type_ = UNSET;
+ // Stream id of the frame currently being processed.
+ SpdyStreamId stream_id_;
+ // Did the most recent frame header include the END_HEADERS flag?
+ bool end_ = false;
+ // Did the most recent frame header include the ack flag?
+ bool ack_ = false;
+
+ // Per-HPACK block state. Only valid while processing a HEADERS or
+ // PUSH_PROMISE frame, and its CONTINUATION frames.
+ // Did the most recent HEADERS or PUSH_PROMISE include the END_STREAM flag?
+ // Note that this does not necessarily indicate that the current frame is
+ // the last frame for the stream (may be followed by CONTINUATION frames,
+ // may only half close).
+ bool fin_ = false;
+ bool got_hpack_end_ = false;
+
+ std::unique_ptr<SpdyString> data_;
+
+ // Total length of the data frame.
+ size_t data_len_ = 0;
+
+ // Amount of skipped padding (i.e. total length of padding, including Pad
+ // Length field).
+ size_t padding_len_ = 0;
+
+ std::unique_ptr<SpdyString> goaway_description_;
+ std::unique_ptr<StringPairVector> headers_;
+ std::unique_ptr<SettingVector> settings_;
+ std::unique_ptr<TestHeadersHandler> headers_handler_;
+
+ std::unique_ptr<SpdyGoAwayIR> goaway_ir_;
+ std::unique_ptr<SpdyHeadersIR> headers_ir_;
+ std::unique_ptr<SpdyPushPromiseIR> push_promise_ir_;
+ std::unique_ptr<SpdySettingsIR> settings_ir_;
+
+ private:
+ std::unique_ptr<SpdyDeframerVisitorInterface> listener_;
+};
+
+// static
+std::unique_ptr<SpdyTestDeframer> SpdyTestDeframer::CreateConverter(
+ std::unique_ptr<SpdyDeframerVisitorInterface> listener) {
+ return SpdyMakeUnique<SpdyTestDeframerImpl>(std::move(listener));
+}
+
+void SpdyTestDeframerImpl::AtDataEnd() {
+ DVLOG(1) << "AtDataEnd";
+ CHECK_EQ(data_len_, padding_len_ + data_->size());
+ auto ptr = SpdyMakeUnique<SpdyDataIR>(stream_id_, std::move(*data_));
+ CHECK_EQ(0u, data_->size());
+ data_.reset();
+
+ CHECK_LE(0u, padding_len_);
+ CHECK_LE(padding_len_, 256u);
+ if (padding_len_ > 0) {
+ ptr->set_padding_len(padding_len_);
+ }
+ padding_len_ = 0;
+
+ ptr->set_fin(fin_);
+ listener_->OnData(std::move(ptr));
+ frame_type_ = UNSET;
+ fin_ = false;
+ data_len_ = 0;
+}
+
+void SpdyTestDeframerImpl::AtGoAwayEnd() {
+ DVLOG(1) << "AtDataEnd";
+ CHECK_EQ(frame_type_, GOAWAY);
+ if (HTTP2_DIE_IF_NULL(goaway_description_)->empty()) {
+ listener_->OnGoAway(std::move(goaway_ir_));
+ } else {
+ listener_->OnGoAway(SpdyMakeUnique<SpdyGoAwayIR>(
+ goaway_ir_->last_good_stream_id(), goaway_ir_->error_code(),
+ std::move(*goaway_description_)));
+ CHECK_EQ(0u, goaway_description_->size());
+ }
+ goaway_description_.reset();
+ goaway_ir_.reset();
+ frame_type_ = UNSET;
+}
+
+void SpdyTestDeframerImpl::AtHeadersEnd() {
+ DVLOG(1) << "AtDataEnd";
+ CHECK(frame_type_ == HEADERS || frame_type_ == CONTINUATION)
+ << " frame_type_=" << Http2FrameTypeToString(frame_type_);
+ CHECK(end_) << " frame_type_=" << Http2FrameTypeToString(frame_type_);
+ CHECK(got_hpack_end_);
+
+ CHECK(headers_ir_ != nullptr);
+ CHECK(headers_ != nullptr);
+ CHECK(headers_handler_ != nullptr);
+
+ CHECK_LE(0u, padding_len_);
+ CHECK_LE(padding_len_, 256u);
+ if (padding_len_ > 0) {
+ headers_ir_->set_padding_len(padding_len_);
+ }
+ padding_len_ = 0;
+
+ headers_ir_->set_header_block(headers_handler_->decoded_block().Clone());
+ headers_handler_.reset();
+ listener_->OnHeaders(std::move(headers_ir_), std::move(headers_));
+
+ frame_type_ = UNSET;
+ fin_ = false;
+ end_ = false;
+ got_hpack_end_ = false;
+}
+
+void SpdyTestDeframerImpl::AtPushPromiseEnd() {
+ DVLOG(1) << "AtDataEnd";
+ CHECK(frame_type_ == PUSH_PROMISE || frame_type_ == CONTINUATION)
+ << " frame_type_=" << Http2FrameTypeToString(frame_type_);
+ CHECK(end_) << " frame_type_=" << Http2FrameTypeToString(frame_type_);
+
+ CHECK(push_promise_ir_ != nullptr);
+ CHECK(headers_ != nullptr);
+ CHECK(headers_handler_ != nullptr);
+
+ CHECK_EQ(headers_ir_.get(), nullptr);
+
+ CHECK_LE(0u, padding_len_);
+ CHECK_LE(padding_len_, 256u);
+ if (padding_len_ > 0) {
+ push_promise_ir_->set_padding_len(padding_len_);
+ }
+ padding_len_ = 0;
+
+ push_promise_ir_->set_header_block(headers_handler_->decoded_block().Clone());
+ headers_handler_.reset();
+ listener_->OnPushPromise(std::move(push_promise_ir_), std::move(headers_));
+
+ frame_type_ = UNSET;
+ end_ = false;
+}
+
+bool SpdyTestDeframerImpl::AtFrameEnd() {
+ bool incomplete_logical_header = false;
+ // The caller says that the SpdyFrame has reached the end of the frame,
+ // so if we have any accumulated data, flush it.
+ switch (frame_type_) {
+ case DATA:
+ AtDataEnd();
+ break;
+
+ case GOAWAY:
+ AtGoAwayEnd();
+ break;
+
+ case HEADERS:
+ if (end_) {
+ AtHeadersEnd();
+ } else {
+ incomplete_logical_header = true;
+ }
+ break;
+
+ case PUSH_PROMISE:
+ if (end_) {
+ AtPushPromiseEnd();
+ } else {
+ incomplete_logical_header = true;
+ }
+ break;
+
+ case CONTINUATION:
+ if (end_) {
+ if (headers_ir_) {
+ AtHeadersEnd();
+ } else if (push_promise_ir_) {
+ AtPushPromiseEnd();
+ } else {
+ LOG(FATAL) << "Where is the SpdyFrameIR for the headers!";
+ }
+ } else {
+ incomplete_logical_header = true;
+ }
+ break;
+
+ case UNSET:
+ // Except for the frame types above, the others don't leave any record
+ // in the state of this object. Make sure nothing got left by accident.
+ CHECK_EQ(data_.get(), nullptr);
+ CHECK_EQ(goaway_description_.get(), nullptr);
+ CHECK_EQ(goaway_ir_.get(), nullptr);
+ CHECK_EQ(headers_.get(), nullptr);
+ CHECK_EQ(headers_handler_.get(), nullptr);
+ CHECK_EQ(headers_ir_.get(), nullptr);
+ CHECK_EQ(push_promise_ir_.get(), nullptr);
+ CHECK_EQ(settings_.get(), nullptr);
+ CHECK_EQ(settings_ir_.get(), nullptr);
+ break;
+
+ default:
+ SPDY_BUG << "Expected UNSET, instead frame_type_==" << frame_type_;
+ return false;
+ }
+ frame_type_ = UNSET;
+ stream_id_ = 0;
+ end_ = false;
+ ack_ = false;
+ if (!incomplete_logical_header) {
+ fin_ = false;
+ }
+ return true;
+}
+
+// Overridden methods from SpdyFramerVisitorInterface in alpha order...
+
+void SpdyTestDeframerImpl::OnAltSvc(
+ SpdyStreamId stream_id,
+ SpdyStringPiece origin,
+ const SpdyAltSvcWireFormat::AlternativeServiceVector& altsvc_vector) {
+ DVLOG(1) << "OnAltSvc stream_id: " << stream_id;
+ CHECK_EQ(frame_type_, UNSET)
+ << " frame_type_=" << Http2FrameTypeToString(frame_type_);
+ CHECK_GT(stream_id, 0u);
+ auto ptr = SpdyMakeUnique<SpdyAltSvcIR>(stream_id);
+ ptr->set_origin(SpdyString(origin));
+ for (auto& altsvc : altsvc_vector) {
+ ptr->add_altsvc(altsvc);
+ }
+ listener_->OnAltSvc(std::move(ptr));
+}
+
+// A CONTINUATION frame contains a Header Block Fragment, and immediately
+// follows another frame that contains a Header Block Fragment (HEADERS,
+// PUSH_PROMISE or CONTINUATION). The last such frame has the END flag set.
+// SpdyFramer ensures that the behavior is correct before calling the visitor.
+void SpdyTestDeframerImpl::OnContinuation(SpdyStreamId stream_id, bool end) {
+ DVLOG(1) << "OnContinuation stream_id: " << stream_id;
+ CHECK_EQ(frame_type_, UNSET)
+ << " frame_type_=" << Http2FrameTypeToString(frame_type_);
+ CHECK_GT(stream_id, 0u);
+ CHECK_NE(nullptr, headers_.get());
+ frame_type_ = CONTINUATION;
+
+ stream_id_ = stream_id;
+ end_ = end;
+}
+
+// Note that length includes the padding length (0 to 256, when the optional
+// padding length field is counted). Padding comes after the payload, both
+// for DATA frames and for control frames.
+void SpdyTestDeframerImpl::OnDataFrameHeader(SpdyStreamId stream_id,
+ size_t length,
+ bool fin) {
+ DVLOG(1) << "OnDataFrameHeader stream_id: " << stream_id;
+ CHECK_EQ(frame_type_, UNSET)
+ << " frame_type_=" << Http2FrameTypeToString(frame_type_);
+ CHECK_GT(stream_id, 0u);
+ CHECK_EQ(data_.get(), nullptr);
+ frame_type_ = DATA;
+
+ stream_id_ = stream_id;
+ fin_ = fin;
+ data_len_ = length;
+ data_ = SpdyMakeUnique<SpdyString>();
+}
+
+// The SpdyFramer will not process any more data at this point.
+void SpdyTestDeframerImpl::OnError(
+ http2::Http2DecoderAdapter::SpdyFramerError error) {
+ DVLOG(1) << "SpdyFramer detected an error in the stream: "
+ << http2::Http2DecoderAdapter::SpdyFramerErrorToString(error)
+ << " frame_type_: " << Http2FrameTypeToString(frame_type_);
+ listener_->OnError(error, this);
+}
+
+// Received a GOAWAY frame from the peer. The last stream id it accepted from us
+// is |last_accepted_stream_id|. |status| is a protocol defined error code.
+// The frame may also contain data. After this OnGoAwayFrameData will be called
+// for any non-zero amount of data, and after that it will be called with len==0
+// to indicate the end of the GOAWAY frame.
+void SpdyTestDeframerImpl::OnGoAway(SpdyStreamId last_good_stream_id,
+ SpdyErrorCode error_code) {
+ DVLOG(1) << "OnGoAway last_good_stream_id: " << last_good_stream_id
+ << " error code: " << error_code;
+ CHECK_EQ(frame_type_, UNSET)
+ << " frame_type_=" << Http2FrameTypeToString(frame_type_);
+ frame_type_ = GOAWAY;
+ goaway_ir_ =
+ SpdyMakeUnique<SpdyGoAwayIR>(last_good_stream_id, error_code, "");
+ goaway_description_ = SpdyMakeUnique<SpdyString>();
+}
+
+// If len==0 then we've reached the end of the GOAWAY frame.
+bool SpdyTestDeframerImpl::OnGoAwayFrameData(const char* goaway_data,
+ size_t len) {
+ DVLOG(1) << "OnGoAwayFrameData";
+ CHECK_EQ(frame_type_, GOAWAY)
+ << " frame_type_=" << Http2FrameTypeToString(frame_type_);
+ CHECK(goaway_description_ != nullptr);
+ goaway_description_->append(goaway_data, len);
+ return true;
+}
+
+SpdyHeadersHandlerInterface* SpdyTestDeframerImpl::OnHeaderFrameStart(
+ SpdyStreamId stream_id) {
+ return this;
+}
+
+void SpdyTestDeframerImpl::OnHeaderFrameEnd(SpdyStreamId stream_id) {
+ DVLOG(1) << "OnHeaderFrameEnd stream_id: " << stream_id;
+}
+
+// Received the fixed portion of a HEADERS frame. Called before the variable
+// length (including zero length) Header Block Fragment is processed. If fin
+// is true then there will be no DATA or trailing HEADERS after this HEADERS
+// frame.
+// If end is true, then there will be no CONTINUATION frame(s) following this
+// frame; else if true then there will be CONTINATION frames(s) immediately
+// following this frame, terminated by a CONTINUATION frame with end==true.
+void SpdyTestDeframerImpl::OnHeaders(SpdyStreamId stream_id,
+ bool has_priority,
+ int weight,
+ SpdyStreamId parent_stream_id,
+ bool exclusive,
+ bool fin,
+ bool end) {
+ DVLOG(1) << "OnHeaders stream_id: " << stream_id;
+ CHECK_EQ(frame_type_, UNSET)
+ << " frame_type_=" << Http2FrameTypeToString(frame_type_);
+ CHECK_GT(stream_id, 0u);
+ frame_type_ = HEADERS;
+
+ stream_id_ = stream_id;
+ fin_ = fin;
+ end_ = end;
+
+ headers_ = SpdyMakeUnique<StringPairVector>();
+ headers_handler_ = SpdyMakeUnique<TestHeadersHandler>();
+ headers_ir_ = SpdyMakeUnique<SpdyHeadersIR>(stream_id);
+ headers_ir_->set_fin(fin);
+ if (has_priority) {
+ headers_ir_->set_has_priority(true);
+ headers_ir_->set_weight(weight);
+ headers_ir_->set_parent_stream_id(parent_stream_id);
+ headers_ir_->set_exclusive(exclusive);
+ }
+}
+
+// The HTTP/2 protocol refers to the payload, |unique_id| here, as 8 octets of
+// opaque data that is to be echoed back to the sender, with the ACK bit added.
+// It isn't defined as a counter,
+// or frame id, as the SpdyPingId naming might imply.
+// Responding to a PING is supposed to be at the highest priority.
+void SpdyTestDeframerImpl::OnPing(uint64_t unique_id, bool is_ack) {
+ DVLOG(1) << "OnPing unique_id: " << unique_id
+ << " is_ack: " << (is_ack ? "true" : "false");
+ CHECK_EQ(frame_type_, UNSET)
+ << " frame_type_=" << Http2FrameTypeToString(frame_type_);
+ auto ptr = SpdyMakeUnique<SpdyPingIR>(unique_id);
+ if (is_ack) {
+ ptr->set_is_ack(is_ack);
+ listener_->OnPingAck(std::move(ptr));
+ } else {
+ listener_->OnPing(std::move(ptr));
+ }
+}
+
+void SpdyTestDeframerImpl::OnPriority(SpdyStreamId stream_id,
+ SpdyStreamId parent_stream_id,
+ int weight,
+ bool exclusive) {
+ DVLOG(1) << "OnPriority stream_id: " << stream_id;
+ CHECK_EQ(frame_type_, UNSET)
+ << " frame_type_=" << Http2FrameTypeToString(frame_type_);
+ CHECK_GT(stream_id, 0u);
+
+ listener_->OnPriority(SpdyMakeUnique<SpdyPriorityIR>(
+ stream_id, parent_stream_id, weight, exclusive));
+}
+
+void SpdyTestDeframerImpl::OnPushPromise(SpdyStreamId stream_id,
+ SpdyStreamId promised_stream_id,
+ bool end) {
+ DVLOG(1) << "OnPushPromise stream_id: " << stream_id;
+ CHECK_EQ(frame_type_, UNSET)
+ << " frame_type_=" << Http2FrameTypeToString(frame_type_);
+ CHECK_GT(stream_id, 0u);
+
+ frame_type_ = PUSH_PROMISE;
+ stream_id_ = stream_id;
+ end_ = end;
+
+ headers_ = SpdyMakeUnique<StringPairVector>();
+ headers_handler_ = SpdyMakeUnique<TestHeadersHandler>();
+ push_promise_ir_ =
+ SpdyMakeUnique<SpdyPushPromiseIR>(stream_id, promised_stream_id);
+}
+
+// Closes the specified stream. After this the sender may still send PRIORITY
+// frames for this stream, which we can ignore.
+void SpdyTestDeframerImpl::OnRstStream(SpdyStreamId stream_id,
+ SpdyErrorCode error_code) {
+ DVLOG(1) << "OnRstStream stream_id: " << stream_id
+ << " error code: " << error_code;
+ CHECK_EQ(frame_type_, UNSET)
+ << " frame_type_=" << Http2FrameTypeToString(frame_type_);
+ CHECK_GT(stream_id, 0u);
+
+ listener_->OnRstStream(
+ SpdyMakeUnique<SpdyRstStreamIR>(stream_id, error_code));
+}
+
+// Called for an individual setting. There is no negotiation; the sender is
+// stating the value that the sender is using.
+void SpdyTestDeframerImpl::OnSetting(SpdySettingsId id, uint32_t value) {
+ DVLOG(1) << "OnSetting id: " << id << std::hex << " value: " << value;
+ CHECK_EQ(frame_type_, SETTINGS)
+ << " frame_type_=" << Http2FrameTypeToString(frame_type_);
+ CHECK(settings_ != nullptr);
+ SpdyKnownSettingsId known_id;
+ if (ParseSettingsId(id, &known_id)) {
+ settings_->push_back(std::make_pair(known_id, value));
+ settings_ir_->AddSetting(known_id, value);
+ }
+}
+
+// Called at the start of a SETTINGS frame with setting entries, but not the
+// (required) ACK of a SETTINGS frame. There is no stream_id because
+// the settings apply to the entire connection, not to an individual stream.
+void SpdyTestDeframerImpl::OnSettings() {
+ DVLOG(1) << "OnSettings";
+ CHECK_EQ(frame_type_, UNSET)
+ << " frame_type_=" << Http2FrameTypeToString(frame_type_);
+ CHECK_EQ(nullptr, settings_ir_.get());
+ CHECK_EQ(nullptr, settings_.get());
+ frame_type_ = SETTINGS;
+ ack_ = false;
+
+ settings_ = SpdyMakeUnique<SettingVector>();
+ settings_ir_ = SpdyMakeUnique<SpdySettingsIR>();
+}
+
+void SpdyTestDeframerImpl::OnSettingsAck() {
+ DVLOG(1) << "OnSettingsAck";
+ CHECK_EQ(frame_type_, UNSET)
+ << " frame_type_=" << Http2FrameTypeToString(frame_type_);
+ auto ptr = SpdyMakeUnique<SpdySettingsIR>();
+ ptr->set_is_ack(true);
+ listener_->OnSettingsAck(std::move(ptr));
+}
+
+void SpdyTestDeframerImpl::OnSettingsEnd() {
+ DVLOG(1) << "OnSettingsEnd";
+ CHECK_EQ(frame_type_, SETTINGS)
+ << " frame_type_=" << Http2FrameTypeToString(frame_type_);
+ CHECK(!ack_);
+ CHECK_NE(nullptr, settings_ir_.get());
+ CHECK_NE(nullptr, settings_.get());
+ listener_->OnSettings(std::move(settings_ir_), std::move(settings_));
+ frame_type_ = UNSET;
+}
+
+// Called for a zero length DATA frame with the END_STREAM flag set, or at the
+// end a complete HPACK block (and its padding) that started with a HEADERS
+// frame with the END_STREAM flag set. Doesn't apply to PUSH_PROMISE frames
+// because they don't have END_STREAM flags.
+void SpdyTestDeframerImpl::OnStreamEnd(SpdyStreamId stream_id) {
+ DVLOG(1) << "OnStreamEnd stream_id: " << stream_id;
+ CHECK_EQ(stream_id_, stream_id);
+ CHECK(frame_type_ == DATA || frame_type_ == HEADERS ||
+ frame_type_ == CONTINUATION)
+ << " frame_type_=" << Http2FrameTypeToString(frame_type_);
+ CHECK(fin_);
+}
+
+// The data arg points into the non-padding payload of a DATA frame.
+// This must be a DATA frame (i.e. this method will not be
+// called for HEADERS or CONTINUATION frames).
+// This method may be called multiple times for a single DATA frame, depending
+// upon buffer boundaries.
+void SpdyTestDeframerImpl::OnStreamFrameData(SpdyStreamId stream_id,
+ const char* data,
+ size_t len) {
+ DVLOG(1) << "OnStreamFrameData stream_id: " << stream_id
+ << " len: " << len;
+ CHECK_EQ(stream_id_, stream_id);
+ CHECK_EQ(frame_type_, DATA);
+ data_->append(data, len);
+}
+
+// Called when receiving the padding length field at the start of the DATA frame
+// payload. value will be in the range 0 to 255.
+void SpdyTestDeframerImpl::OnStreamPadLength(SpdyStreamId stream_id,
+ size_t value) {
+ DVLOG(1) << "OnStreamPadding stream_id: " << stream_id
+ << " value: " << value;
+ CHECK(frame_type_ == DATA || frame_type_ == HEADERS ||
+ frame_type_ == PUSH_PROMISE)
+ << " frame_type_=" << Http2FrameTypeToString(frame_type_);
+ CHECK_EQ(stream_id_, stream_id);
+ CHECK_GE(255u, value);
+ // Count the padding length byte against total padding.
+ padding_len_ += 1;
+ CHECK_EQ(1u, padding_len_);
+}
+
+// Called when padding is skipped over at the end of the DATA frame. len will
+// be in the range 1 to 255.
+void SpdyTestDeframerImpl::OnStreamPadding(SpdyStreamId stream_id, size_t len) {
+ DVLOG(1) << "OnStreamPadding stream_id: " << stream_id << " len: " << len;
+ CHECK(frame_type_ == DATA || frame_type_ == HEADERS ||
+ frame_type_ == PUSH_PROMISE)
+ << " frame_type_=" << Http2FrameTypeToString(frame_type_);
+ CHECK_EQ(stream_id_, stream_id);
+ CHECK_LE(1u, len);
+ CHECK_GE(255u, len);
+ padding_len_ += len;
+ CHECK_LE(padding_len_, 256u) << "len=" << len;
+}
+
+// WINDOW_UPDATE is supposed to be hop-by-hop, according to the spec.
+// stream_id is 0 if the update applies to the connection, else stream_id
+// will be the id of a stream previously seen, which maybe half or fully
+// closed.
+void SpdyTestDeframerImpl::OnWindowUpdate(SpdyStreamId stream_id,
+ int delta_window_size) {
+ DVLOG(1) << "OnWindowUpdate stream_id: " << stream_id
+ << " delta_window_size: " << delta_window_size;
+ CHECK_EQ(frame_type_, UNSET)
+ << " frame_type_=" << Http2FrameTypeToString(frame_type_);
+ CHECK_NE(0, delta_window_size);
+
+ listener_->OnWindowUpdate(
+ SpdyMakeUnique<SpdyWindowUpdateIR>(stream_id, delta_window_size));
+}
+
+// Return true to indicate that the stream_id is valid; if not valid then
+// SpdyFramer considers the connection corrupted. Requires keeping track
+// of the set of currently open streams. For now we'll assume that unknown
+// frame types are unsupported.
+bool SpdyTestDeframerImpl::OnUnknownFrame(SpdyStreamId stream_id,
+ uint8_t frame_type) {
+ DVLOG(1) << "OnAltSvc stream_id: " << stream_id;
+ CHECK_EQ(frame_type_, UNSET)
+ << " frame_type_=" << Http2FrameTypeToString(frame_type_);
+ frame_type_ = UNKNOWN;
+
+ stream_id_ = stream_id;
+ return false;
+}
+
+// Callbacks defined in SpdyHeadersHandlerInterface.
+
+void SpdyTestDeframerImpl::OnHeaderBlockStart() {
+ CHECK(frame_type_ == HEADERS || frame_type_ == PUSH_PROMISE)
+ << " frame_type_=" << Http2FrameTypeToString(frame_type_);
+ CHECK(headers_ != nullptr);
+ CHECK_EQ(0u, headers_->size());
+ got_hpack_end_ = false;
+}
+
+void SpdyTestDeframerImpl::OnHeader(SpdyStringPiece key,
+ SpdyStringPiece value) {
+ CHECK(frame_type_ == HEADERS || frame_type_ == CONTINUATION ||
+ frame_type_ == PUSH_PROMISE)
+ << " frame_type_=" << Http2FrameTypeToString(frame_type_);
+ CHECK(!got_hpack_end_);
+ HTTP2_DIE_IF_NULL(headers_)->emplace_back(SpdyString(key), SpdyString(value));
+ HTTP2_DIE_IF_NULL(headers_handler_)->OnHeader(key, value);
+}
+
+void SpdyTestDeframerImpl::OnHeaderBlockEnd(
+ size_t /* header_bytes_parsed */,
+ size_t /* compressed_header_bytes_parsed */) {
+ CHECK(headers_ != nullptr);
+ CHECK(frame_type_ == HEADERS || frame_type_ == CONTINUATION ||
+ frame_type_ == PUSH_PROMISE)
+ << " frame_type_=" << Http2FrameTypeToString(frame_type_);
+ CHECK(end_);
+ CHECK(!got_hpack_end_);
+ got_hpack_end_ = true;
+}
+
+class LoggingSpdyDeframerDelegate : public SpdyDeframerVisitorInterface {
+ public:
+ explicit LoggingSpdyDeframerDelegate(
+ std::unique_ptr<SpdyDeframerVisitorInterface> wrapped)
+ : wrapped_(std::move(wrapped)) {
+ if (!wrapped_) {
+ wrapped_ = SpdyMakeUnique<SpdyDeframerVisitorInterface>();
+ }
+ }
+ ~LoggingSpdyDeframerDelegate() override = default;
+
+ void OnAltSvc(std::unique_ptr<SpdyAltSvcIR> frame) override {
+ DVLOG(1) << "LoggingSpdyDeframerDelegate::OnAltSvc";
+ wrapped_->OnAltSvc(std::move(frame));
+ }
+ void OnData(std::unique_ptr<SpdyDataIR> frame) override {
+ DVLOG(1) << "LoggingSpdyDeframerDelegate::OnData";
+ wrapped_->OnData(std::move(frame));
+ }
+ void OnGoAway(std::unique_ptr<SpdyGoAwayIR> frame) override {
+ DVLOG(1) << "LoggingSpdyDeframerDelegate::OnGoAway";
+ wrapped_->OnGoAway(std::move(frame));
+ }
+
+ // SpdyHeadersIR and SpdyPushPromiseIR each has a SpdyHeaderBlock which
+ // significantly modifies the headers, so the actual header entries (name
+ // and value strings) are provided in a vector.
+ void OnHeaders(std::unique_ptr<SpdyHeadersIR> frame,
+ std::unique_ptr<StringPairVector> headers) override {
+ DVLOG(1) << "LoggingSpdyDeframerDelegate::OnHeaders";
+ wrapped_->OnHeaders(std::move(frame), std::move(headers));
+ }
+
+ void OnPing(std::unique_ptr<SpdyPingIR> frame) override {
+ DVLOG(1) << "LoggingSpdyDeframerDelegate::OnPing";
+ wrapped_->OnPing(std::move(frame));
+ }
+ void OnPingAck(std::unique_ptr<SpdyPingIR> frame) override {
+ DVLOG(1) << "LoggingSpdyDeframerDelegate::OnPingAck";
+ wrapped_->OnPingAck(std::move(frame));
+ }
+
+ void OnPriority(std::unique_ptr<SpdyPriorityIR> frame) override {
+ DVLOG(1) << "LoggingSpdyDeframerDelegate::OnPriority";
+ wrapped_->OnPriority(std::move(frame));
+ }
+
+ // SpdyHeadersIR and SpdyPushPromiseIR each has a SpdyHeaderBlock which
+ // significantly modifies the headers, so the actual header entries (name
+ // and value strings) are provided in a vector.
+ void OnPushPromise(std::unique_ptr<SpdyPushPromiseIR> frame,
+ std::unique_ptr<StringPairVector> headers) override {
+ DVLOG(1) << "LoggingSpdyDeframerDelegate::OnPushPromise";
+ wrapped_->OnPushPromise(std::move(frame), std::move(headers));
+ }
+
+ void OnRstStream(std::unique_ptr<SpdyRstStreamIR> frame) override {
+ DVLOG(1) << "LoggingSpdyDeframerDelegate::OnRstStream";
+ wrapped_->OnRstStream(std::move(frame));
+ }
+
+ // SpdySettingsIR has a map for settings, so loses info about the order of
+ // settings, and whether the same setting appeared more than once, so the
+ // the actual settings (parameter and value) are provided in a vector.
+ void OnSettings(std::unique_ptr<SpdySettingsIR> frame,
+ std::unique_ptr<SettingVector> settings) override {
+ DVLOG(1) << "LoggingSpdyDeframerDelegate::OnSettings";
+ wrapped_->OnSettings(std::move(frame), std::move(settings));
+ }
+
+ // A settings frame with an ACK has no content, but for uniformity passing
+ // a frame with the ACK flag set.
+ void OnSettingsAck(std::unique_ptr<SpdySettingsIR> frame) override {
+ DVLOG(1) << "LoggingSpdyDeframerDelegate::OnSettingsAck";
+ wrapped_->OnSettingsAck(std::move(frame));
+ }
+
+ void OnWindowUpdate(std::unique_ptr<SpdyWindowUpdateIR> frame) override {
+ DVLOG(1) << "LoggingSpdyDeframerDelegate::OnWindowUpdate";
+ wrapped_->OnWindowUpdate(std::move(frame));
+ }
+
+ // The SpdyFramer will not process any more data at this point.
+ void OnError(http2::Http2DecoderAdapter::SpdyFramerError error,
+ SpdyTestDeframer* deframer) override {
+ DVLOG(1) << "LoggingSpdyDeframerDelegate::OnError";
+ wrapped_->OnError(error, deframer);
+ }
+
+ private:
+ std::unique_ptr<SpdyDeframerVisitorInterface> wrapped_;
+};
+
+// static
+std::unique_ptr<SpdyDeframerVisitorInterface>
+SpdyDeframerVisitorInterface::LogBeforeVisiting(
+ std::unique_ptr<SpdyDeframerVisitorInterface> wrapped_listener) {
+ return SpdyMakeUnique<LoggingSpdyDeframerDelegate>(
+ std::move(wrapped_listener));
+}
+
+CollectedFrame::CollectedFrame() = default;
+
+CollectedFrame::CollectedFrame(CollectedFrame&& other)
+ : frame_ir(std::move(other.frame_ir)),
+ headers(std::move(other.headers)),
+ settings(std::move(other.settings)),
+ error_reported(other.error_reported) {}
+
+CollectedFrame::~CollectedFrame() = default;
+
+CollectedFrame& CollectedFrame::operator=(CollectedFrame&& other) {
+ frame_ir = std::move(other.frame_ir);
+ headers = std::move(other.headers);
+ settings = std::move(other.settings);
+ error_reported = other.error_reported;
+ return *this;
+}
+
+AssertionResult CollectedFrame::VerifyHasHeaders(
+ const StringPairVector& expected_headers) const {
+ VERIFY_NE(headers.get(), nullptr);
+ VERIFY_THAT(*headers, ::testing::ContainerEq(expected_headers));
+ return AssertionSuccess();
+}
+
+AssertionResult CollectedFrame::VerifyHasSettings(
+ const SettingVector& expected_settings) const {
+ VERIFY_NE(settings.get(), nullptr);
+ VERIFY_THAT(*settings, testing::ContainerEq(expected_settings));
+ return AssertionSuccess();
+}
+
+DeframerCallbackCollector::DeframerCallbackCollector(
+ std::vector<CollectedFrame>* collected_frames)
+ : collected_frames_(HTTP2_DIE_IF_NULL(collected_frames)) {}
+
+void DeframerCallbackCollector::OnAltSvc(
+ std::unique_ptr<SpdyAltSvcIR> frame_ir) {
+ CollectedFrame cf;
+ cf.frame_ir = std::move(frame_ir);
+ collected_frames_->push_back(std::move(cf));
+}
+void DeframerCallbackCollector::OnData(std::unique_ptr<SpdyDataIR> frame_ir) {
+ CollectedFrame cf;
+ cf.frame_ir = std::move(frame_ir);
+ collected_frames_->push_back(std::move(cf));
+}
+void DeframerCallbackCollector::OnGoAway(
+ std::unique_ptr<SpdyGoAwayIR> frame_ir) {
+ CollectedFrame cf;
+ cf.frame_ir = std::move(frame_ir);
+ collected_frames_->push_back(std::move(cf));
+}
+
+// SpdyHeadersIR and SpdyPushPromiseIR each has a SpdyHeaderBlock which
+// significantly modifies the headers, so the actual header entries (name
+// and value strings) are provided in a vector.
+void DeframerCallbackCollector::OnHeaders(
+ std::unique_ptr<SpdyHeadersIR> frame_ir,
+ std::unique_ptr<StringPairVector> headers) {
+ CollectedFrame cf;
+ cf.frame_ir = std::move(frame_ir);
+ cf.headers = std::move(headers);
+ collected_frames_->push_back(std::move(cf));
+}
+
+void DeframerCallbackCollector::OnPing(std::unique_ptr<SpdyPingIR> frame_ir) {
+ EXPECT_TRUE(frame_ir && !frame_ir->is_ack());
+ CollectedFrame cf;
+ cf.frame_ir = std::move(frame_ir);
+ collected_frames_->push_back(std::move(cf));
+}
+
+void DeframerCallbackCollector::OnPingAck(
+ std::unique_ptr<SpdyPingIR> frame_ir) {
+ EXPECT_TRUE(frame_ir && frame_ir->is_ack());
+ CollectedFrame cf;
+ cf.frame_ir = std::move(frame_ir);
+ collected_frames_->push_back(std::move(cf));
+}
+
+void DeframerCallbackCollector::OnPriority(
+ std::unique_ptr<SpdyPriorityIR> frame_ir) {
+ CollectedFrame cf;
+ cf.frame_ir = std::move(frame_ir);
+ collected_frames_->push_back(std::move(cf));
+}
+
+// SpdyHeadersIR and SpdyPushPromiseIR each has a SpdyHeaderBlock which
+// significantly modifies the headers, so the actual header entries (name
+// and value strings) are provided in a vector.
+void DeframerCallbackCollector::OnPushPromise(
+ std::unique_ptr<SpdyPushPromiseIR> frame_ir,
+ std::unique_ptr<StringPairVector> headers) {
+ CollectedFrame cf;
+ cf.frame_ir = std::move(frame_ir);
+ cf.headers = std::move(headers);
+ collected_frames_->push_back(std::move(cf));
+}
+
+void DeframerCallbackCollector::OnRstStream(
+ std::unique_ptr<SpdyRstStreamIR> frame_ir) {
+ CollectedFrame cf;
+ cf.frame_ir = std::move(frame_ir);
+ collected_frames_->push_back(std::move(cf));
+}
+
+// SpdySettingsIR has a map for settings, so loses info about the order of
+// settings, and whether the same setting appeared more than once, so the
+// the actual settings (parameter and value) are provided in a vector.
+void DeframerCallbackCollector::OnSettings(
+ std::unique_ptr<SpdySettingsIR> frame_ir,
+ std::unique_ptr<SettingVector> settings) {
+ EXPECT_TRUE(frame_ir && !frame_ir->is_ack());
+ CollectedFrame cf;
+ cf.frame_ir = std::move(frame_ir);
+ cf.settings = std::move(settings);
+ collected_frames_->push_back(std::move(cf));
+}
+
+// A settings frame_ir with an ACK has no content, but for uniformity passing
+// a frame_ir with the ACK flag set.
+void DeframerCallbackCollector::OnSettingsAck(
+ std::unique_ptr<SpdySettingsIR> frame_ir) {
+ EXPECT_TRUE(frame_ir && frame_ir->is_ack());
+ CollectedFrame cf;
+ cf.frame_ir = std::move(frame_ir);
+ collected_frames_->push_back(std::move(cf));
+}
+
+void DeframerCallbackCollector::OnWindowUpdate(
+ std::unique_ptr<SpdyWindowUpdateIR> frame_ir) {
+ CollectedFrame cf;
+ cf.frame_ir = std::move(frame_ir);
+ collected_frames_->push_back(std::move(cf));
+}
+
+// The SpdyFramer will not process any more data at this point.
+void DeframerCallbackCollector::OnError(
+ http2::Http2DecoderAdapter::SpdyFramerError error,
+ SpdyTestDeframer* deframer) {
+ CollectedFrame cf;
+ cf.error_reported = true;
+ collected_frames_->push_back(std::move(cf));
+}
+
+} // namespace test
+} // namespace spdy
diff --git a/chromium/net/third_party/quiche/src/spdy/core/spdy_deframer_visitor.h b/chromium/net/third_party/quiche/src/spdy/core/spdy_deframer_visitor.h
new file mode 100644
index 00000000000..50d9987b811
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/spdy/core/spdy_deframer_visitor.h
@@ -0,0 +1,250 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_SPDY_CORE_SPDY_DEFRAMER_VISITOR_H_
+#define QUICHE_SPDY_CORE_SPDY_DEFRAMER_VISITOR_H_
+
+// Supports testing by converting callbacks to SpdyFramerVisitorInterface into
+// callbacks to SpdyDeframerVisitorInterface, whose arguments are generally
+// SpdyFrameIR instances. This enables a test client or test backend to operate
+// at a level between the low-level callbacks of SpdyFramerVisitorInterface and
+// the much higher level of entire messages (i.e. headers, body, trailers).
+// Where possible the converter (SpdyTestDeframer) tries to preserve information
+// that might be useful to tests (e.g. the order of headers or the amount of
+// padding); the design also aims to allow tests to be concise, ideally
+// supporting gMock style EXPECT_CALL(visitor, OnHeaders(...matchers...))
+// without too much boilerplate.
+//
+// Only supports HTTP/2 for the moment.
+//
+// Example of usage:
+//
+// SpdyFramer framer(HTTP2);
+//
+// // Need to call SpdyTestDeframer::AtFrameEnd() after processing each
+// // frame, so tell SpdyFramer to stop after each.
+// framer.set_process_single_input_frame(true);
+//
+// // Need the new OnHeader callbacks.
+// framer.set_use_new_methods_for_test(true);
+//
+// // Create your visitor, a subclass of SpdyDeframerVisitorInterface.
+// // For example, using DeframerCallbackCollector to collect frames:
+// std::vector<CollectedFrame> collected_frames;
+// auto your_visitor = SpdyMakeUnique<DeframerCallbackCollector>(
+// &collected_frames);
+//
+// // Transfer ownership of your visitor to the converter, which ensures that
+// // your visitor stays alive while the converter needs to call it.
+// auto the_deframer = SpdyTestDeframer::CreateConverter(
+// std::move(your_visitor));
+//
+// // Tell the framer to notify SpdyTestDeframer of the decoded frame
+// // details.
+// framer.set_visitor(the_deframer.get());
+//
+// // Process frames.
+// SpdyStringPiece input = ...
+// while (!input.empty() && !framer.HasError()) {
+// size_t consumed = framer.ProcessInput(input.data(), input.size());
+// input.remove_prefix(consumed);
+// if (framer.state() == SpdyFramer::SPDY_READY_FOR_FRAME) {
+// the_deframer->AtFrameEnd();
+// }
+// }
+//
+// // Make sure that the correct frames were received. For example:
+// ASSERT_EQ(collected_frames.size(), 3);
+//
+// SpdyDataIR expected1(7 /*stream_id*/, "Data Payload");
+// expected1.set_padding_len(17);
+// EXPECT_TRUE(collected_frames[0].VerifyEquals(expected1));
+//
+// // Repeat for the other frames.
+//
+// Note that you could also seed the subclass of SpdyDeframerVisitorInterface
+// with the expected frames, which it would pop-off the list as its expectations
+// are met.
+
+#include <cstdint>
+
+#include <memory>
+#include <type_traits>
+#include <utility>
+#include <vector>
+
+#include "base/logging.h"
+#include "base/macros.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/spdy/core/http2_frame_decoder_adapter.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_protocol.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_protocol_test_utils.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_test_utils.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_string.h"
+
+namespace spdy {
+namespace test {
+
+// Non-lossy representation of a SETTINGS frame payload.
+typedef std::vector<std::pair<SpdyKnownSettingsId, uint32_t>> SettingVector;
+
+// StringPairVector is used to record information lost by SpdyHeaderBlock, in
+// particular the order of each header entry, though it doesn't expose the
+// inner details of the HPACK block, such as the type of encoding selected
+// for each header entry, nor dynamic table size changes.
+typedef std::pair<SpdyString, SpdyString> StringPair;
+typedef std::vector<StringPair> StringPairVector;
+
+// Forward decl.
+class SpdyTestDeframer;
+
+// Note that this only roughly captures the frames, as padding bytes are lost,
+// continuation frames are combined with their leading HEADERS or PUSH_PROMISE,
+// the details of the HPACK encoding are lost, leaving
+// only the list of header entries (name and value strings). If really helpful,
+// we could add a SpdyRawDeframerVisitorInterface that gets the HPACK bytes,
+// and receives continuation frames. For more info we'd need to improve
+// SpdyFramerVisitorInterface.
+class SpdyDeframerVisitorInterface {
+ public:
+ virtual ~SpdyDeframerVisitorInterface() {}
+
+ // Wrap a visitor in another SpdyDeframerVisitorInterface that will
+ // DVLOG each call, and will then forward the calls to the wrapped visitor
+ // (if provided; nullptr is OK). Takes ownership of the wrapped visitor.
+ static std::unique_ptr<SpdyDeframerVisitorInterface> LogBeforeVisiting(
+ std::unique_ptr<SpdyDeframerVisitorInterface> wrapped_visitor);
+
+ virtual void OnAltSvc(std::unique_ptr<SpdyAltSvcIR> frame) {}
+ virtual void OnData(std::unique_ptr<SpdyDataIR> frame) {}
+ virtual void OnGoAway(std::unique_ptr<SpdyGoAwayIR> frame) {}
+
+ // SpdyHeadersIR and SpdyPushPromiseIR each has a SpdyHeaderBlock which
+ // significantly modifies the headers, so the actual header entries (name
+ // and value strings) are provided in a vector.
+ virtual void OnHeaders(std::unique_ptr<SpdyHeadersIR> frame,
+ std::unique_ptr<StringPairVector> headers) {}
+
+ virtual void OnPing(std::unique_ptr<SpdyPingIR> frame) {}
+ virtual void OnPingAck(std::unique_ptr<SpdyPingIR> frame);
+ virtual void OnPriority(std::unique_ptr<SpdyPriorityIR> frame) {}
+
+ // SpdyHeadersIR and SpdyPushPromiseIR each has a SpdyHeaderBlock which
+ // significantly modifies the headers, so the actual header entries (name
+ // and value strings) are provided in a vector.
+ virtual void OnPushPromise(std::unique_ptr<SpdyPushPromiseIR> frame,
+ std::unique_ptr<StringPairVector> headers) {}
+
+ virtual void OnRstStream(std::unique_ptr<SpdyRstStreamIR> frame) {}
+
+ // SpdySettingsIR has a map for settings, so loses info about the order of
+ // settings, and whether the same setting appeared more than once, so the
+ // the actual settings (parameter and value) are provided in a vector.
+ virtual void OnSettings(std::unique_ptr<SpdySettingsIR> frame,
+ std::unique_ptr<SettingVector> settings) {}
+
+ // A settings frame with an ACK has no content, but for uniformity passing
+ // a frame with the ACK flag set.
+ virtual void OnSettingsAck(std::unique_ptr<SpdySettingsIR> frame);
+
+ virtual void OnWindowUpdate(std::unique_ptr<SpdyWindowUpdateIR> frame) {}
+
+ // The SpdyFramer will not process any more data at this point.
+ virtual void OnError(http2::Http2DecoderAdapter::SpdyFramerError error,
+ SpdyTestDeframer* deframer) {}
+};
+
+class SpdyTestDeframer : public SpdyFramerVisitorInterface {
+ public:
+ ~SpdyTestDeframer() override {}
+
+ // Creates a SpdyFramerVisitorInterface that builds SpdyFrameIR concrete
+ // instances based on the callbacks it receives; when an entire frame is
+ // decoded/reconstructed it calls the passed in SpdyDeframerVisitorInterface.
+ // Transfers ownership of visitor to the new SpdyTestDeframer, which ensures
+ // that it continues to exist while the SpdyTestDeframer exists.
+ static std::unique_ptr<SpdyTestDeframer> CreateConverter(
+ std::unique_ptr<SpdyDeframerVisitorInterface> visitor);
+
+ // Call to notify the deframer that the SpdyFramer has returned after reaching
+ // the end of decoding a frame. This is used to flush info about some frame
+ // types where we don't get a clear end signal; others are flushed (i.e. the
+ // appropriate call to the SpdyDeframerVisitorInterface method is invoked)
+ // as they're decoded by SpdyFramer and it calls the deframer. See the
+ // example in the comments at the top of this file.
+ virtual bool AtFrameEnd() = 0;
+
+ protected:
+ SpdyTestDeframer() {}
+ SpdyTestDeframer(const SpdyTestDeframer&) = delete;
+ SpdyTestDeframer& operator=(const SpdyTestDeframer&) = delete;
+};
+
+// CollectedFrame holds the result of one call to SpdyDeframerVisitorInterface,
+// as recorded by DeframerCallbackCollector.
+struct CollectedFrame {
+ CollectedFrame();
+ CollectedFrame(CollectedFrame&& other);
+ ~CollectedFrame();
+ CollectedFrame& operator=(CollectedFrame&& other);
+
+ // Compare a SpdyFrameIR sub-class instance, expected_ir, against the
+ // collected SpdyFrameIR.
+ template <class T,
+ typename X =
+ typename std::enable_if<std::is_base_of<SpdyFrameIR, T>::value>>
+ ::testing::AssertionResult VerifyHasFrame(const T& expected_ir) const {
+ return VerifySpdyFrameIREquals(expected_ir, frame_ir.get());
+ }
+
+ // Compare the collected headers against a StringPairVector. Ignores
+ // this->frame_ir.
+ ::testing::AssertionResult VerifyHasHeaders(
+ const StringPairVector& expected_headers) const;
+
+ // Compare the collected settings (parameter and value pairs) against
+ // expected_settings. Ignores this->frame_ir.
+ ::testing::AssertionResult VerifyHasSettings(
+ const SettingVector& expected_settings) const;
+
+ std::unique_ptr<SpdyFrameIR> frame_ir;
+ std::unique_ptr<StringPairVector> headers;
+ std::unique_ptr<SettingVector> settings;
+ bool error_reported = false;
+};
+
+// Creates a CollectedFrame instance for each callback, storing it in the
+// vector provided to the constructor.
+class DeframerCallbackCollector : public SpdyDeframerVisitorInterface {
+ public:
+ explicit DeframerCallbackCollector(
+ std::vector<CollectedFrame>* collected_frames);
+ ~DeframerCallbackCollector() override {}
+
+ void OnAltSvc(std::unique_ptr<SpdyAltSvcIR> frame_ir) override;
+ void OnData(std::unique_ptr<SpdyDataIR> frame_ir) override;
+ void OnGoAway(std::unique_ptr<SpdyGoAwayIR> frame_ir) override;
+ void OnHeaders(std::unique_ptr<SpdyHeadersIR> frame_ir,
+ std::unique_ptr<StringPairVector> headers) override;
+ void OnPing(std::unique_ptr<SpdyPingIR> frame_ir) override;
+ void OnPingAck(std::unique_ptr<SpdyPingIR> frame_ir) override;
+ void OnPriority(std::unique_ptr<SpdyPriorityIR> frame_ir) override;
+ void OnPushPromise(std::unique_ptr<SpdyPushPromiseIR> frame_ir,
+ std::unique_ptr<StringPairVector> headers) override;
+ void OnRstStream(std::unique_ptr<SpdyRstStreamIR> frame_ir) override;
+ void OnSettings(std::unique_ptr<SpdySettingsIR> frame_ir,
+ std::unique_ptr<SettingVector> settings) override;
+ void OnSettingsAck(std::unique_ptr<SpdySettingsIR> frame_ir) override;
+ void OnWindowUpdate(std::unique_ptr<SpdyWindowUpdateIR> frame_ir) override;
+ void OnError(http2::Http2DecoderAdapter::SpdyFramerError error,
+ SpdyTestDeframer* deframer) override;
+
+ private:
+ std::vector<CollectedFrame>* collected_frames_;
+};
+
+} // namespace test
+} // namespace spdy
+
+#endif // QUICHE_SPDY_CORE_SPDY_DEFRAMER_VISITOR_H_
diff --git a/chromium/net/third_party/quiche/src/spdy/core/spdy_deframer_visitor_test.cc b/chromium/net/third_party/quiche/src/spdy/core/spdy_deframer_visitor_test.cc
new file mode 100644
index 00000000000..ede1a20b733
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/spdy/core/spdy_deframer_visitor_test.cc
@@ -0,0 +1,247 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/spdy/core/spdy_deframer_visitor.h"
+
+#include <stdlib.h>
+
+#include <algorithm>
+#include <limits>
+
+#include "base/logging.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/http2/test_tools/http2_random.h"
+#include "net/third_party/quiche/src/spdy/core/hpack/hpack_constants.h"
+#include "net/third_party/quiche/src/spdy/core/mock_spdy_framer_visitor.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_frame_builder.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_frame_reader.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_framer.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_protocol.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_protocol_test_utils.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_test_utils.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_ptr_util.h"
+
+namespace spdy {
+namespace test {
+namespace {
+
+class SpdyDeframerVisitorTest : public ::testing::Test {
+ protected:
+ SpdyDeframerVisitorTest() : encoder_(SpdyFramer::ENABLE_COMPRESSION) {
+ decoder_.set_process_single_input_frame(true);
+ auto collector =
+ SpdyMakeUnique<DeframerCallbackCollector>(&collected_frames_);
+ auto log_and_collect =
+ SpdyDeframerVisitorInterface::LogBeforeVisiting(std::move(collector));
+ deframer_ = SpdyTestDeframer::CreateConverter(std::move(log_and_collect));
+ decoder_.set_visitor(deframer_.get());
+ }
+
+ bool DeframeInput(const char* input, size_t size) {
+ size_t input_remaining = size;
+ while (input_remaining > 0 &&
+ decoder_.spdy_framer_error() ==
+ http2::Http2DecoderAdapter::SPDY_NO_ERROR) {
+ // To make the tests more interesting, we feed random (and small) chunks
+ // into the framer. This simulates getting strange-sized reads from
+ // the socket.
+ const size_t kMaxReadSize = 32;
+ size_t bytes_read =
+ (random_.Uniform(std::min(input_remaining, kMaxReadSize))) + 1;
+ size_t bytes_processed = decoder_.ProcessInput(input, bytes_read);
+ input_remaining -= bytes_processed;
+ input += bytes_processed;
+ if (decoder_.state() ==
+ http2::Http2DecoderAdapter::SPDY_READY_FOR_FRAME) {
+ deframer_->AtFrameEnd();
+ }
+ }
+ return (input_remaining == 0 &&
+ decoder_.spdy_framer_error() ==
+ http2::Http2DecoderAdapter::SPDY_NO_ERROR);
+ }
+
+ SpdyFramer encoder_;
+ http2::Http2DecoderAdapter decoder_;
+ std::vector<CollectedFrame> collected_frames_;
+ std::unique_ptr<SpdyTestDeframer> deframer_;
+
+ private:
+ http2::test::Http2Random random_;
+};
+
+TEST_F(SpdyDeframerVisitorTest, DataFrame) {
+ const char kFrameData[] = {
+ 0x00, 0x00, 0x0d, // Length = 13.
+ 0x00, // DATA
+ 0x08, // PADDED
+ 0x00, 0x00, 0x00, 0x01, // Stream 1
+ 0x07, // Pad length field.
+ 'h', 'e', 'l', 'l', // Data
+ 'o', // More Data
+ 0x00, 0x00, 0x00, 0x00, // Padding
+ 0x00, 0x00, 0x00 // More Padding
+ };
+
+ EXPECT_TRUE(DeframeInput(kFrameData, sizeof kFrameData));
+ ASSERT_EQ(1u, collected_frames_.size());
+ const CollectedFrame& cf0 = collected_frames_[0];
+ ASSERT_NE(cf0.frame_ir, nullptr);
+
+ SpdyDataIR expected_ir(/* stream_id = */ 1, "hello");
+ expected_ir.set_padding_len(8);
+ EXPECT_TRUE(cf0.VerifyHasFrame(expected_ir));
+}
+
+TEST_F(SpdyDeframerVisitorTest, HeaderFrameWithContinuation) {
+ const char kFrameData[] = {
+ 0x00, 0x00, 0x05, // Payload Length: 5
+ 0x01, // Type: HEADERS
+ 0x09, // Flags: PADDED | END_STREAM
+ 0x00, 0x00, 0x00, 0x01, // Stream: 1
+ 0x04, // Padding Length: 4
+ 0x00, 0x00, 0x00, 0x00, // Padding
+ /* Second Frame */
+ 0x00, 0x00, 0x12, // Payload Length: 18
+ 0x09, // Type: CONTINUATION
+ 0x04, // Flags: END_HEADERS
+ 0x00, 0x00, 0x00, 0x01, // Stream: 1
+ 0x00, // Unindexed, literal name & value
+ 0x03, 0x62, 0x61, 0x72, // Name len and name (3, "bar")
+ 0x03, 0x66, 0x6f, 0x6f, // Value len and value (3, "foo")
+ 0x00, // Unindexed, literal name & value
+ 0x03, 0x66, 0x6f, 0x6f, // Name len and name (3, "foo")
+ 0x03, 0x62, 0x61, 0x72, // Value len and value (3, "bar")
+ };
+
+ EXPECT_TRUE(DeframeInput(kFrameData, sizeof kFrameData));
+ ASSERT_EQ(1u, collected_frames_.size());
+ const CollectedFrame& cf0 = collected_frames_[0];
+
+ StringPairVector headers;
+ headers.push_back({"bar", "foo"});
+ headers.push_back({"foo", "bar"});
+
+ EXPECT_TRUE(cf0.VerifyHasHeaders(headers));
+
+ SpdyHeadersIR expected_ir(/* stream_id = */ 1);
+ // Yet again SpdyFramerVisitorInterface is lossy: it doesn't call OnPadding
+ // for HEADERS, just for DATA. Sigh.
+ // expected_ir.set_padding_len(5);
+ expected_ir.set_fin(true);
+ for (const auto& nv : headers) {
+ expected_ir.SetHeader(nv.first, nv.second);
+ }
+
+ EXPECT_TRUE(cf0.VerifyHasFrame(expected_ir));
+
+ // Confirm that mismatches are also detected.
+ headers.push_back({"baz", "bing"});
+ EXPECT_FALSE(cf0.VerifyHasHeaders(headers));
+ EXPECT_TRUE(cf0.VerifyHasFrame(expected_ir));
+
+ headers.pop_back();
+ EXPECT_TRUE(cf0.VerifyHasHeaders(headers));
+ EXPECT_TRUE(cf0.VerifyHasFrame(expected_ir));
+
+ expected_ir.SetHeader("baz", "bing");
+ EXPECT_FALSE(cf0.VerifyHasFrame(expected_ir));
+ EXPECT_TRUE(cf0.VerifyHasHeaders(headers));
+}
+
+TEST_F(SpdyDeframerVisitorTest, PriorityFrame) {
+ const char kFrameData[] = {
+ 0x00, 0x00, 0x05, // Length: 5
+ 0x02, // Type: PRIORITY
+ 0x00, // Flags: none
+ 0x00, 0x00, 0x00, 0x65, // Stream: 101
+ '\x80', 0x00, 0x00, 0x01, // Parent: 1 (Exclusive)
+ 0x10, // Weight: 17
+ };
+
+ EXPECT_TRUE(DeframeInput(kFrameData, sizeof kFrameData));
+ ASSERT_EQ(1u, collected_frames_.size());
+ const CollectedFrame& cf0 = collected_frames_[0];
+
+ SpdyPriorityIR expected_ir(/* stream_id = */ 101,
+ /* parent_stream_id = */ 1, /* weight = */ 17,
+ /* exclusive = */ true);
+ EXPECT_TRUE(cf0.VerifyHasFrame(expected_ir));
+
+ // Confirm that mismatches are also detected.
+ EXPECT_FALSE(cf0.VerifyHasFrame(SpdyPriorityIR(101, 1, 16, true)));
+ EXPECT_FALSE(cf0.VerifyHasFrame(SpdyPriorityIR(101, 50, 17, true)));
+ EXPECT_FALSE(cf0.VerifyHasFrame(SpdyPriorityIR(201, 1, 17, true)));
+ EXPECT_FALSE(cf0.VerifyHasFrame(SpdyPriorityIR(101, 1, 17, false)));
+}
+
+TEST_F(SpdyDeframerVisitorTest, DISABLED_RstStreamFrame) {
+ // TODO(jamessynge): Please implement.
+}
+
+TEST_F(SpdyDeframerVisitorTest, SettingsFrame) {
+ // Settings frame with two entries for the same parameter but with different
+ // values. The last one will be in the decoded SpdySettingsIR, but the vector
+ // of settings will have both, in the same order.
+ const char kFrameData[] = {
+ 0x00, 0x00, 0x0c, // Length
+ 0x04, // Type (SETTINGS)
+ 0x00, // Flags
+ 0x00, 0x00, 0x00, 0x00, // Stream id (must be zero)
+ 0x00, 0x04, // Setting id (SETTINGS_INITIAL_WINDOW_SIZE)
+ 0x0a, 0x0b, 0x0c, 0x0d, // Setting value
+ 0x00, 0x04, // Setting id (SETTINGS_INITIAL_WINDOW_SIZE)
+ 0x00, 0x00, 0x00, '\xff', // Setting value
+ };
+
+ EXPECT_TRUE(DeframeInput(kFrameData, sizeof kFrameData));
+ ASSERT_EQ(1u, collected_frames_.size());
+ const CollectedFrame& cf0 = collected_frames_[0];
+ ASSERT_NE(cf0.frame_ir, nullptr);
+
+ SpdySettingsIR expected_ir;
+ expected_ir.AddSetting(SETTINGS_INITIAL_WINDOW_SIZE, 255);
+ EXPECT_TRUE(cf0.VerifyHasFrame(expected_ir));
+
+ SettingVector expected_settings;
+ expected_settings.push_back({SETTINGS_INITIAL_WINDOW_SIZE, 0x0a0b0c0d});
+ expected_settings.push_back({SETTINGS_INITIAL_WINDOW_SIZE, 255});
+
+ EXPECT_TRUE(cf0.VerifyHasSettings(expected_settings));
+
+ // Confirm that mismatches are also detected.
+ expected_settings.push_back({SETTINGS_INITIAL_WINDOW_SIZE, 65536});
+ EXPECT_FALSE(cf0.VerifyHasSettings(expected_settings));
+
+ expected_ir.AddSetting(SETTINGS_INITIAL_WINDOW_SIZE, 65536);
+ EXPECT_FALSE(cf0.VerifyHasFrame(expected_ir));
+
+ SpdySettingsIR unexpected_ir;
+ unexpected_ir.set_is_ack(true);
+ EXPECT_FALSE(cf0.VerifyHasFrame(unexpected_ir));
+}
+
+TEST_F(SpdyDeframerVisitorTest, DISABLED_PushPromiseFrame) {
+ // TODO(jamessynge): Please implement.
+}
+
+TEST_F(SpdyDeframerVisitorTest, DISABLED_PingFrame) {
+ // TODO(jamessynge): Please implement.
+}
+
+TEST_F(SpdyDeframerVisitorTest, DISABLED_GoAwayFrame) {
+ // TODO(jamessynge): Please implement.
+}
+
+TEST_F(SpdyDeframerVisitorTest, DISABLED_WindowUpdateFrame) {
+ // TODO(jamessynge): Please implement.
+}
+
+TEST_F(SpdyDeframerVisitorTest, DISABLED_AltSvcFrame) {
+ // TODO(jamessynge): Please implement.
+}
+
+} // namespace
+} // namespace test
+} // namespace spdy
diff --git a/chromium/net/third_party/quiche/src/spdy/core/spdy_frame_builder.cc b/chromium/net/third_party/quiche/src/spdy/core/spdy_frame_builder.cc
new file mode 100644
index 00000000000..a056b70f68c
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/spdy/core/spdy_frame_builder.cc
@@ -0,0 +1,183 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/spdy/core/spdy_frame_builder.h"
+
+#include <algorithm>
+#include <cstdint>
+#include <limits>
+#include <new>
+
+#include "base/logging.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_protocol.h"
+#include "net/third_party/quiche/src/spdy/core/zero_copy_output_buffer.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_bug_tracker.h"
+
+namespace spdy {
+
+SpdyFrameBuilder::SpdyFrameBuilder(size_t size)
+ : buffer_(new char[size]), capacity_(size), length_(0), offset_(0) {}
+
+SpdyFrameBuilder::SpdyFrameBuilder(size_t size, ZeroCopyOutputBuffer* output)
+ : buffer_(output == nullptr ? new char[size] : nullptr),
+ output_(output),
+ capacity_(size),
+ length_(0),
+ offset_(0) {}
+
+SpdyFrameBuilder::~SpdyFrameBuilder() = default;
+
+char* SpdyFrameBuilder::GetWritableBuffer(size_t length) {
+ if (!CanWrite(length)) {
+ return nullptr;
+ }
+ return buffer_.get() + offset_ + length_;
+}
+
+char* SpdyFrameBuilder::GetWritableOutput(size_t length,
+ size_t* actual_length) {
+ char* dest = nullptr;
+ int size = 0;
+
+ if (!CanWrite(length)) {
+ return nullptr;
+ }
+ output_->Next(&dest, &size);
+ *actual_length = std::min<size_t>(length, size);
+ return dest;
+}
+
+bool SpdyFrameBuilder::Seek(size_t length) {
+ if (!CanWrite(length)) {
+ return false;
+ }
+ if (output_ == nullptr) {
+ length_ += length;
+ } else {
+ output_->AdvanceWritePtr(length);
+ length_ += length;
+ }
+ return true;
+}
+
+bool SpdyFrameBuilder::BeginNewFrame(SpdyFrameType type,
+ uint8_t flags,
+ SpdyStreamId stream_id) {
+ uint8_t raw_frame_type = SerializeFrameType(type);
+ DCHECK(IsDefinedFrameType(raw_frame_type));
+ DCHECK_EQ(0u, stream_id & ~kStreamIdMask);
+ bool success = true;
+ if (length_ > 0) {
+ SPDY_BUG << "SpdyFrameBuilder doesn't have a clean state when BeginNewFrame"
+ << "is called. Leftover length_ is " << length_;
+ offset_ += length_;
+ length_ = 0;
+ }
+
+ success &= WriteUInt24(capacity_ - offset_ - kFrameHeaderSize);
+ success &= WriteUInt8(raw_frame_type);
+ success &= WriteUInt8(flags);
+ success &= WriteUInt32(stream_id);
+ DCHECK_EQ(kDataFrameMinimumSize, length_);
+ return success;
+}
+
+bool SpdyFrameBuilder::BeginNewFrame(SpdyFrameType type,
+ uint8_t flags,
+ SpdyStreamId stream_id,
+ size_t length) {
+ uint8_t raw_frame_type = SerializeFrameType(type);
+ DCHECK(IsDefinedFrameType(raw_frame_type));
+ DCHECK_EQ(0u, stream_id & ~kStreamIdMask);
+ SPDY_BUG_IF(length > kHttp2DefaultFramePayloadLimit)
+ << "Frame length " << length_ << " is longer than frame size limit.";
+ return BeginNewFrameInternal(raw_frame_type, flags, stream_id, length);
+}
+
+bool SpdyFrameBuilder::BeginNewUncheckedFrame(uint8_t raw_frame_type,
+ uint8_t flags,
+ SpdyStreamId stream_id,
+ size_t length) {
+ return BeginNewFrameInternal(raw_frame_type, flags, stream_id, length);
+}
+
+bool SpdyFrameBuilder::BeginNewFrameInternal(uint8_t raw_frame_type,
+ uint8_t flags,
+ SpdyStreamId stream_id,
+ size_t length) {
+ DCHECK_EQ(length, length & kLengthMask);
+ bool success = true;
+
+ offset_ += length_;
+ length_ = 0;
+
+ success &= WriteUInt24(length);
+ success &= WriteUInt8(raw_frame_type);
+ success &= WriteUInt8(flags);
+ success &= WriteUInt32(stream_id);
+ DCHECK_EQ(kDataFrameMinimumSize, length_);
+ return success;
+}
+
+bool SpdyFrameBuilder::WriteStringPiece32(const SpdyStringPiece value) {
+ if (!WriteUInt32(value.size())) {
+ return false;
+ }
+
+ return WriteBytes(value.data(), value.size());
+}
+
+bool SpdyFrameBuilder::WriteBytes(const void* data, uint32_t data_len) {
+ if (!CanWrite(data_len)) {
+ return false;
+ }
+
+ if (output_ == nullptr) {
+ char* dest = GetWritableBuffer(data_len);
+ memcpy(dest, data, data_len);
+ Seek(data_len);
+ } else {
+ char* dest = nullptr;
+ size_t size = 0;
+ size_t total_written = 0;
+ const char* data_ptr = reinterpret_cast<const char*>(data);
+ while (data_len > 0) {
+ dest = GetWritableOutput(data_len, &size);
+ if (dest == nullptr || size == 0) {
+ // Unable to make progress.
+ return false;
+ }
+ uint32_t to_copy = std::min<uint32_t>(data_len, size);
+ const char* src = data_ptr + total_written;
+ memcpy(dest, src, to_copy);
+ Seek(to_copy);
+ data_len -= to_copy;
+ total_written += to_copy;
+ }
+ }
+ return true;
+}
+
+bool SpdyFrameBuilder::CanWrite(size_t length) const {
+ if (length > kLengthMask) {
+ DCHECK(false);
+ return false;
+ }
+
+ if (output_ == nullptr) {
+ if (offset_ + length_ + length > capacity_) {
+ DLOG(FATAL) << "Requested: " << length << " capacity: " << capacity_
+ << " used: " << offset_ + length_;
+ return false;
+ }
+ } else {
+ if (length > output_->BytesFree()) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+} // namespace spdy
diff --git a/chromium/net/third_party/quiche/src/spdy/core/spdy_frame_builder.h b/chromium/net/third_party/quiche/src/spdy/core/spdy_frame_builder.h
new file mode 100644
index 00000000000..c569c8c9377
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/spdy/core/spdy_frame_builder.h
@@ -0,0 +1,142 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_SPDY_CORE_SPDY_FRAME_BUILDER_H_
+#define QUICHE_SPDY_CORE_SPDY_FRAME_BUILDER_H_
+
+#include <cstddef>
+#include <cstdint>
+#include <memory>
+
+#include "net/third_party/quiche/src/spdy/core/spdy_protocol.h"
+#include "net/third_party/quiche/src/spdy/core/zero_copy_output_buffer.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_bug_tracker.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_endianness_util.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_export.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_string_piece.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_test_utils_prod.h"
+
+namespace spdy {
+
+// This class provides facilities for basic binary value packing
+// into Spdy frames.
+//
+// The SpdyFrameBuilder supports appending primitive values (int, string, etc)
+// to a frame instance. The SpdyFrameBuilder grows its internal memory buffer
+// dynamically to hold the sequence of primitive values. The internal memory
+// buffer is exposed as the "data" of the SpdyFrameBuilder.
+class SPDY_EXPORT_PRIVATE SpdyFrameBuilder {
+ public:
+ // Initializes a SpdyFrameBuilder with a buffer of given size
+ explicit SpdyFrameBuilder(size_t size);
+ // Doesn't take ownership of output.
+ SpdyFrameBuilder(size_t size, ZeroCopyOutputBuffer* output);
+
+ ~SpdyFrameBuilder();
+
+ // Returns the total size of the SpdyFrameBuilder's data, which may include
+ // multiple frames.
+ size_t length() const { return offset_ + length_; }
+
+ // Seeks forward by the given number of bytes. Useful in conjunction with
+ // GetWriteableBuffer() above.
+ bool Seek(size_t length);
+
+ // Populates this frame with a HTTP2 frame prefix using length information
+ // from |capacity_|. The given type must be a control frame type.
+ bool BeginNewFrame(SpdyFrameType type, uint8_t flags, SpdyStreamId stream_id);
+
+ // Populates this frame with a HTTP2 frame prefix with type and length
+ // information. |type| must be a defined frame type.
+ bool BeginNewFrame(SpdyFrameType type,
+ uint8_t flags,
+ SpdyStreamId stream_id,
+ size_t length);
+
+ // Populates this frame with a HTTP2 frame prefix with type and length
+ // information. |raw_frame_type| may be a defined or undefined frame type.
+ bool BeginNewUncheckedFrame(uint8_t raw_frame_type,
+ uint8_t flags,
+ SpdyStreamId stream_id,
+ size_t length);
+
+ // Takes the buffer from the SpdyFrameBuilder.
+ SpdySerializedFrame take() {
+ SPDY_BUG_IF(output_ != nullptr) << "ZeroCopyOutputBuffer is used to build "
+ << "frames. take() shouldn't be called";
+ SPDY_BUG_IF(kMaxFrameSizeLimit < length_)
+ << "Frame length " << length_
+ << " is longer than the maximum possible allowed length.";
+ SpdySerializedFrame rv(buffer_.release(), length(), true);
+ capacity_ = 0;
+ length_ = 0;
+ offset_ = 0;
+ return rv;
+ }
+
+ // Methods for adding to the payload. These values are appended to the end
+ // of the SpdyFrameBuilder payload. Note - binary integers are converted from
+ // host to network form.
+ bool WriteUInt8(uint8_t value) { return WriteBytes(&value, sizeof(value)); }
+ bool WriteUInt16(uint16_t value) {
+ value = SpdyHostToNet16(value);
+ return WriteBytes(&value, sizeof(value));
+ }
+ bool WriteUInt24(uint32_t value) {
+ value = SpdyHostToNet32(value);
+ return WriteBytes(reinterpret_cast<char*>(&value) + 1, sizeof(value) - 1);
+ }
+ bool WriteUInt32(uint32_t value) {
+ value = SpdyHostToNet32(value);
+ return WriteBytes(&value, sizeof(value));
+ }
+ bool WriteUInt64(uint64_t value) {
+ uint32_t upper = SpdyHostToNet32(static_cast<uint32_t>(value >> 32));
+ uint32_t lower = SpdyHostToNet32(static_cast<uint32_t>(value));
+ return (WriteBytes(&upper, sizeof(upper)) &&
+ WriteBytes(&lower, sizeof(lower)));
+ }
+ bool WriteStringPiece32(const SpdyStringPiece value);
+ bool WriteBytes(const void* data, uint32_t data_len);
+
+ private:
+ SPDY_FRIEND_TEST(SpdyFrameBuilderTest, GetWritableBuffer);
+ SPDY_FRIEND_TEST(SpdyFrameBuilderTest, GetWritableOutput);
+ SPDY_FRIEND_TEST(SpdyFrameBuilderTest, GetWritableOutputNegative);
+
+ // Populates this frame with a HTTP2 frame prefix with type and length
+ // information.
+ bool BeginNewFrameInternal(uint8_t raw_frame_type,
+ uint8_t flags,
+ SpdyStreamId stream_id,
+ size_t length);
+
+ // Returns a writeable buffer of given size in bytes, to be appended to the
+ // currently written frame. Does bounds checking on length but does not
+ // increment the underlying iterator. To do so, consumers should subsequently
+ // call Seek().
+ // In general, consumers should use Write*() calls instead of this.
+ // Returns NULL on failure.
+ char* GetWritableBuffer(size_t length);
+ char* GetWritableOutput(size_t desired_length, size_t* actual_length);
+
+ // Checks to make sure that there is an appropriate amount of space for a
+ // write of given size, in bytes.
+ bool CanWrite(size_t length) const;
+
+ // A buffer to be created whenever a new frame needs to be written. Used only
+ // if |output_| is nullptr.
+ std::unique_ptr<char[]> buffer_;
+ // A pre-allocated buffer. If not-null, serialized frame data is written to
+ // this buffer.
+ ZeroCopyOutputBuffer* output_ = nullptr; // Does not own.
+
+ size_t capacity_; // Allocation size of payload, set by constructor.
+ size_t length_; // Length of the latest frame in the buffer.
+ size_t offset_; // Position at which the latest frame begins.
+};
+
+} // namespace spdy
+
+#endif // QUICHE_SPDY_CORE_SPDY_FRAME_BUILDER_H_
diff --git a/chromium/net/third_party/quiche/src/spdy/core/spdy_frame_builder_test.cc b/chromium/net/third_party/quiche/src/spdy/core/spdy_frame_builder_test.cc
new file mode 100644
index 00000000000..97d10f12e93
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/spdy/core/spdy_frame_builder_test.cc
@@ -0,0 +1,68 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/spdy/core/spdy_frame_builder.h"
+
+#include <memory>
+
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/spdy/core/array_output_buffer.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_framer.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_protocol.h"
+
+namespace spdy {
+
+namespace {
+
+const int64_t kSize = 64 * 1024;
+char output_buffer[kSize] = "";
+
+} // namespace
+
+// Verifies that SpdyFrameBuilder::GetWritableBuffer() can be used to build a
+// SpdySerializedFrame.
+TEST(SpdyFrameBuilderTest, GetWritableBuffer) {
+ const size_t kBuilderSize = 10;
+ SpdyFrameBuilder builder(kBuilderSize);
+ char* writable_buffer = builder.GetWritableBuffer(kBuilderSize);
+ memset(writable_buffer, ~1, kBuilderSize);
+ EXPECT_TRUE(builder.Seek(kBuilderSize));
+ SpdySerializedFrame frame(builder.take());
+ char expected[kBuilderSize];
+ memset(expected, ~1, kBuilderSize);
+ EXPECT_EQ(SpdyStringPiece(expected, kBuilderSize),
+ SpdyStringPiece(frame.data(), kBuilderSize));
+}
+
+// Verifies that SpdyFrameBuilder::GetWritableBuffer() can be used to build a
+// SpdySerializedFrame to the output buffer.
+TEST(SpdyFrameBuilderTest, GetWritableOutput) {
+ ArrayOutputBuffer output(output_buffer, kSize);
+ const size_t kBuilderSize = 10;
+ SpdyFrameBuilder builder(kBuilderSize, &output);
+ size_t actual_size = 0;
+ char* writable_buffer = builder.GetWritableOutput(kBuilderSize, &actual_size);
+ memset(writable_buffer, ~1, kBuilderSize);
+ EXPECT_TRUE(builder.Seek(kBuilderSize));
+ SpdySerializedFrame frame(output.Begin(), kBuilderSize, false);
+ char expected[kBuilderSize];
+ memset(expected, ~1, kBuilderSize);
+ EXPECT_EQ(SpdyStringPiece(expected, kBuilderSize),
+ SpdyStringPiece(frame.data(), kBuilderSize));
+}
+
+// Verifies the case that the buffer's capacity is too small.
+TEST(SpdyFrameBuilderTest, GetWritableOutputNegative) {
+ size_t small_cap = 1;
+ ArrayOutputBuffer output(output_buffer, small_cap);
+ const size_t kBuilderSize = 10;
+ SpdyFrameBuilder builder(kBuilderSize, &output);
+ size_t actual_size = 0;
+ char* writable_buffer = builder.GetWritableOutput(kBuilderSize, &actual_size);
+ builder.GetWritableOutput(kBuilderSize, &actual_size);
+ EXPECT_EQ(0u, actual_size);
+ EXPECT_EQ(nullptr, writable_buffer);
+}
+
+} // namespace spdy
diff --git a/chromium/net/third_party/quiche/src/spdy/core/spdy_frame_reader.cc b/chromium/net/third_party/quiche/src/spdy/core/spdy_frame_reader.cc
new file mode 100644
index 00000000000..b9bf4c1c8e8
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/spdy/core/spdy_frame_reader.cc
@@ -0,0 +1,200 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/spdy/core/spdy_frame_reader.h"
+
+#include "net/third_party/quiche/src/spdy/core/spdy_protocol.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_endianness_util.h"
+
+namespace spdy {
+
+SpdyFrameReader::SpdyFrameReader(const char* data, const size_t len)
+ : data_(data), len_(len), ofs_(0) {}
+
+bool SpdyFrameReader::ReadUInt8(uint8_t* result) {
+ // Make sure that we have the whole uint8_t.
+ if (!CanRead(1)) {
+ OnFailure();
+ return false;
+ }
+
+ // Read into result.
+ *result = *reinterpret_cast<const uint8_t*>(data_ + ofs_);
+
+ // Iterate.
+ ofs_ += 1;
+
+ return true;
+}
+
+bool SpdyFrameReader::ReadUInt16(uint16_t* result) {
+ // Make sure that we have the whole uint16_t.
+ if (!CanRead(2)) {
+ OnFailure();
+ return false;
+ }
+
+ // Read into result.
+ *result = SpdyNetToHost16(*(reinterpret_cast<const uint16_t*>(data_ + ofs_)));
+
+ // Iterate.
+ ofs_ += 2;
+
+ return true;
+}
+
+bool SpdyFrameReader::ReadUInt32(uint32_t* result) {
+ // Make sure that we have the whole uint32_t.
+ if (!CanRead(4)) {
+ OnFailure();
+ return false;
+ }
+
+ // Read into result.
+ *result = SpdyNetToHost32(*(reinterpret_cast<const uint32_t*>(data_ + ofs_)));
+
+ // Iterate.
+ ofs_ += 4;
+
+ return true;
+}
+
+bool SpdyFrameReader::ReadUInt64(uint64_t* result) {
+ // Make sure that we have the whole uint64_t.
+ if (!CanRead(8)) {
+ OnFailure();
+ return false;
+ }
+
+ // Read into result. Network byte order is big-endian.
+ uint64_t upper =
+ SpdyNetToHost32(*(reinterpret_cast<const uint32_t*>(data_ + ofs_)));
+ uint64_t lower =
+ SpdyNetToHost32(*(reinterpret_cast<const uint32_t*>(data_ + ofs_ + 4)));
+ *result = (upper << 32) + lower;
+
+ // Iterate.
+ ofs_ += 8;
+
+ return true;
+}
+
+bool SpdyFrameReader::ReadUInt31(uint32_t* result) {
+ bool success = ReadUInt32(result);
+
+ // Zero out highest-order bit.
+ if (success) {
+ *result &= 0x7fffffff;
+ }
+
+ return success;
+}
+
+bool SpdyFrameReader::ReadUInt24(uint32_t* result) {
+ // Make sure that we have the whole uint24_t.
+ if (!CanRead(3)) {
+ OnFailure();
+ return false;
+ }
+
+ // Read into result.
+ *result = 0;
+ memcpy(reinterpret_cast<char*>(result) + 1, data_ + ofs_, 3);
+ *result = SpdyNetToHost32(*result);
+
+ // Iterate.
+ ofs_ += 3;
+
+ return true;
+}
+
+bool SpdyFrameReader::ReadStringPiece16(SpdyStringPiece* result) {
+ // Read resultant length.
+ uint16_t result_len;
+ if (!ReadUInt16(&result_len)) {
+ // OnFailure() already called.
+ return false;
+ }
+
+ // Make sure that we have the whole string.
+ if (!CanRead(result_len)) {
+ OnFailure();
+ return false;
+ }
+
+ // Set result.
+ *result = SpdyStringPiece(data_ + ofs_, result_len);
+
+ // Iterate.
+ ofs_ += result_len;
+
+ return true;
+}
+
+bool SpdyFrameReader::ReadStringPiece32(SpdyStringPiece* result) {
+ // Read resultant length.
+ uint32_t result_len;
+ if (!ReadUInt32(&result_len)) {
+ // OnFailure() already called.
+ return false;
+ }
+
+ // Make sure that we have the whole string.
+ if (!CanRead(result_len)) {
+ OnFailure();
+ return false;
+ }
+
+ // Set result.
+ *result = SpdyStringPiece(data_ + ofs_, result_len);
+
+ // Iterate.
+ ofs_ += result_len;
+
+ return true;
+}
+
+bool SpdyFrameReader::ReadBytes(void* result, size_t size) {
+ // Make sure that we have enough data to read.
+ if (!CanRead(size)) {
+ OnFailure();
+ return false;
+ }
+
+ // Read into result.
+ memcpy(result, data_ + ofs_, size);
+
+ // Iterate.
+ ofs_ += size;
+
+ return true;
+}
+
+bool SpdyFrameReader::Seek(size_t size) {
+ if (!CanRead(size)) {
+ OnFailure();
+ return false;
+ }
+
+ // Iterate.
+ ofs_ += size;
+
+ return true;
+}
+
+bool SpdyFrameReader::IsDoneReading() const {
+ return len_ == ofs_;
+}
+
+bool SpdyFrameReader::CanRead(size_t bytes) const {
+ return bytes <= (len_ - ofs_);
+}
+
+void SpdyFrameReader::OnFailure() {
+ // Set our iterator to the end of the buffer so that further reads fail
+ // immediately.
+ ofs_ = len_;
+}
+
+} // namespace spdy
diff --git a/chromium/net/third_party/quiche/src/spdy/core/spdy_frame_reader.h b/chromium/net/third_party/quiche/src/spdy/core/spdy_frame_reader.h
new file mode 100644
index 00000000000..dc6c0640fdb
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/spdy/core/spdy_frame_reader.h
@@ -0,0 +1,129 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_SPDY_CORE_SPDY_FRAME_READER_H_
+#define QUICHE_SPDY_CORE_SPDY_FRAME_READER_H_
+
+#include <cstdint>
+
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_export.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_string_piece.h"
+
+namespace spdy {
+
+// Used for reading SPDY frames. Though there isn't really anything terribly
+// SPDY-specific here, it's a helper class that's useful when doing SPDY
+// framing.
+//
+// To use, simply construct a SpdyFramerReader using the underlying buffer that
+// you'd like to read fields from, then call one of the Read*() methods to
+// actually do some reading.
+//
+// This class keeps an internal iterator to keep track of what's already been
+// read and each successive Read*() call automatically increments said iterator
+// on success. On failure, internal state of the SpdyFrameReader should not be
+// trusted and it is up to the caller to throw away the failed instance and
+// handle the error as appropriate. None of the Read*() methods should ever be
+// called after failure, as they will also fail immediately.
+class SPDY_EXPORT_PRIVATE SpdyFrameReader {
+ public:
+ // Caller must provide an underlying buffer to work on.
+ SpdyFrameReader(const char* data, const size_t len);
+
+ // Empty destructor.
+ ~SpdyFrameReader() {}
+
+ // Reads an 8-bit unsigned integer into the given output parameter.
+ // Forwards the internal iterator on success.
+ // Returns true on success, false otherwise.
+ bool ReadUInt8(uint8_t* result);
+
+ // Reads a 16-bit unsigned integer into the given output parameter.
+ // Forwards the internal iterator on success.
+ // Returns true on success, false otherwise.
+ bool ReadUInt16(uint16_t* result);
+
+ // Reads a 32-bit unsigned integer into the given output parameter.
+ // Forwards the internal iterator on success.
+ // Returns true on success, false otherwise.
+ bool ReadUInt32(uint32_t* result);
+
+ // Reads a 64-bit unsigned integer into the given output parameter.
+ // Forwards the internal iterator on success.
+ // Returns true on success, false otherwise.
+ bool ReadUInt64(uint64_t* result);
+
+ // Reads a 31-bit unsigned integer into the given output parameter. This is
+ // equivalent to ReadUInt32() above except that the highest-order bit is
+ // discarded.
+ // Forwards the internal iterator (by 4B) on success.
+ // Returns true on success, false otherwise.
+ bool ReadUInt31(uint32_t* result);
+
+ // Reads a 24-bit unsigned integer into the given output parameter.
+ // Forwards the internal iterator (by 3B) on success.
+ // Returns true on success, false otherwise.
+ bool ReadUInt24(uint32_t* result);
+
+ // Reads a string prefixed with 16-bit length into the given output parameter.
+ //
+ // NOTE: Does not copy but rather references strings in the underlying buffer.
+ // This should be kept in mind when handling memory management!
+ //
+ // Forwards the internal iterator on success.
+ // Returns true on success, false otherwise.
+ bool ReadStringPiece16(SpdyStringPiece* result);
+
+ // Reads a string prefixed with 32-bit length into the given output parameter.
+ //
+ // NOTE: Does not copy but rather references strings in the underlying buffer.
+ // This should be kept in mind when handling memory management!
+ //
+ // Forwards the internal iterator on success.
+ // Returns true on success, false otherwise.
+ bool ReadStringPiece32(SpdyStringPiece* result);
+
+ // Reads a given number of bytes into the given buffer. The buffer
+ // must be of adequate size.
+ // Forwards the internal iterator on success.
+ // Returns true on success, false otherwise.
+ bool ReadBytes(void* result, size_t size);
+
+ // Seeks a given number of bytes into the buffer from the current offset.
+ // Equivelant to an empty read.
+ // Forwards the internal iterator.
+ // Returns true on success, false otherwise.
+ bool Seek(size_t size);
+
+ // Rewinds this reader to the beginning of the frame.
+ void Rewind() { ofs_ = 0; }
+
+ // Returns true if the entirety of the underlying buffer has been read via
+ // Read*() calls.
+ bool IsDoneReading() const;
+
+ // Returns the number of bytes that have been consumed by the reader so far.
+ size_t GetBytesConsumed() const { return ofs_; }
+
+ private:
+ // Returns true if the underlying buffer has enough room to read the given
+ // amount of bytes.
+ bool CanRead(size_t bytes) const;
+
+ // To be called when a read fails for any reason.
+ void OnFailure();
+
+ // The data buffer that we're reading from.
+ const char* data_;
+
+ // The length of the data buffer that we're reading from.
+ const size_t len_;
+
+ // The location of the next read from our data buffer.
+ size_t ofs_;
+};
+
+} // namespace spdy
+
+#endif // QUICHE_SPDY_CORE_SPDY_FRAME_READER_H_
diff --git a/chromium/net/third_party/quiche/src/spdy/core/spdy_frame_reader_test.cc b/chromium/net/third_party/quiche/src/spdy/core/spdy_frame_reader_test.cc
new file mode 100644
index 00000000000..8caf60f552e
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/spdy/core/spdy_frame_reader_test.cc
@@ -0,0 +1,247 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/spdy/core/spdy_frame_reader.h"
+
+#include <cstdint>
+
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_arraysize.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_endianness_util.h"
+
+namespace spdy {
+
+TEST(SpdyFrameReaderTest, ReadUInt16) {
+ // Frame data in network byte order.
+ const uint16_t kFrameData[] = {
+ SpdyHostToNet16(1),
+ SpdyHostToNet16(1 << 15),
+ };
+
+ SpdyFrameReader frame_reader(reinterpret_cast<const char*>(kFrameData),
+ sizeof(kFrameData));
+ EXPECT_FALSE(frame_reader.IsDoneReading());
+
+ uint16_t uint16_val;
+ EXPECT_TRUE(frame_reader.ReadUInt16(&uint16_val));
+ EXPECT_FALSE(frame_reader.IsDoneReading());
+ EXPECT_EQ(1, uint16_val);
+
+ EXPECT_TRUE(frame_reader.ReadUInt16(&uint16_val));
+ EXPECT_TRUE(frame_reader.IsDoneReading());
+ EXPECT_EQ(1 << 15, uint16_val);
+}
+
+TEST(SpdyFrameReaderTest, ReadUInt32) {
+ // Frame data in network byte order.
+ const uint32_t kFrameData[] = {
+ SpdyHostToNet32(1),
+ SpdyHostToNet32(0x80000000),
+ };
+
+ SpdyFrameReader frame_reader(reinterpret_cast<const char*>(kFrameData),
+ SPDY_ARRAYSIZE(kFrameData) * sizeof(uint32_t));
+ EXPECT_FALSE(frame_reader.IsDoneReading());
+
+ uint32_t uint32_val;
+ EXPECT_TRUE(frame_reader.ReadUInt32(&uint32_val));
+ EXPECT_FALSE(frame_reader.IsDoneReading());
+ EXPECT_EQ(1u, uint32_val);
+
+ EXPECT_TRUE(frame_reader.ReadUInt32(&uint32_val));
+ EXPECT_TRUE(frame_reader.IsDoneReading());
+ EXPECT_EQ(1u << 31, uint32_val);
+}
+
+TEST(SpdyFrameReaderTest, ReadStringPiece16) {
+ // Frame data in network byte order.
+ const char kFrameData[] = {
+ 0x00, 0x02, // uint16_t(2)
+ 0x48, 0x69, // "Hi"
+ 0x00, 0x10, // uint16_t(16)
+ 0x54, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x2c,
+ 0x20, 0x31, 0x2c, 0x20, 0x32, 0x2c, 0x20, 0x33, // "Testing, 1, 2, 3"
+ };
+
+ SpdyFrameReader frame_reader(kFrameData, SPDY_ARRAYSIZE(kFrameData));
+ EXPECT_FALSE(frame_reader.IsDoneReading());
+
+ SpdyStringPiece stringpiece_val;
+ EXPECT_TRUE(frame_reader.ReadStringPiece16(&stringpiece_val));
+ EXPECT_FALSE(frame_reader.IsDoneReading());
+ EXPECT_EQ(0, stringpiece_val.compare("Hi"));
+
+ EXPECT_TRUE(frame_reader.ReadStringPiece16(&stringpiece_val));
+ EXPECT_TRUE(frame_reader.IsDoneReading());
+ EXPECT_EQ(0, stringpiece_val.compare("Testing, 1, 2, 3"));
+}
+
+TEST(SpdyFrameReaderTest, ReadStringPiece32) {
+ // Frame data in network byte order.
+ const char kFrameData[] = {
+ 0x00, 0x00, 0x00, 0x03, // uint32_t(3)
+ 0x66, 0x6f, 0x6f, // "foo"
+ 0x00, 0x00, 0x00, 0x10, // uint32_t(16)
+ 0x54, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x2c,
+ 0x20, 0x34, 0x2c, 0x20, 0x35, 0x2c, 0x20, 0x36, // "Testing, 4, 5, 6"
+ };
+
+ SpdyFrameReader frame_reader(kFrameData, SPDY_ARRAYSIZE(kFrameData));
+ EXPECT_FALSE(frame_reader.IsDoneReading());
+
+ SpdyStringPiece stringpiece_val;
+ EXPECT_TRUE(frame_reader.ReadStringPiece32(&stringpiece_val));
+ EXPECT_FALSE(frame_reader.IsDoneReading());
+ EXPECT_EQ(0, stringpiece_val.compare("foo"));
+
+ EXPECT_TRUE(frame_reader.ReadStringPiece32(&stringpiece_val));
+ EXPECT_TRUE(frame_reader.IsDoneReading());
+ EXPECT_EQ(0, stringpiece_val.compare("Testing, 4, 5, 6"));
+}
+
+TEST(SpdyFrameReaderTest, ReadUInt16WithBufferTooSmall) {
+ // Frame data in network byte order.
+ const char kFrameData[] = {
+ 0x00, // part of a uint16_t
+ };
+
+ SpdyFrameReader frame_reader(kFrameData, SPDY_ARRAYSIZE(kFrameData));
+ EXPECT_FALSE(frame_reader.IsDoneReading());
+
+ uint16_t uint16_val;
+ EXPECT_FALSE(frame_reader.ReadUInt16(&uint16_val));
+}
+
+TEST(SpdyFrameReaderTest, ReadUInt32WithBufferTooSmall) {
+ // Frame data in network byte order.
+ const char kFrameData[] = {
+ 0x00, 0x00, 0x00, // part of a uint32_t
+ };
+
+ SpdyFrameReader frame_reader(kFrameData, SPDY_ARRAYSIZE(kFrameData));
+ EXPECT_FALSE(frame_reader.IsDoneReading());
+
+ uint32_t uint32_val;
+ EXPECT_FALSE(frame_reader.ReadUInt32(&uint32_val));
+
+ // Also make sure that trying to read a uint16_t, which technically could
+ // work, fails immediately due to previously encountered failed read.
+ uint16_t uint16_val;
+ EXPECT_FALSE(frame_reader.ReadUInt16(&uint16_val));
+}
+
+// Tests ReadStringPiece16() with a buffer too small to fit the entire string.
+TEST(SpdyFrameReaderTest, ReadStringPiece16WithBufferTooSmall) {
+ // Frame data in network byte order.
+ const char kFrameData[] = {
+ 0x00, 0x03, // uint16_t(3)
+ 0x48, 0x69, // "Hi"
+ };
+
+ SpdyFrameReader frame_reader(kFrameData, SPDY_ARRAYSIZE(kFrameData));
+ EXPECT_FALSE(frame_reader.IsDoneReading());
+
+ SpdyStringPiece stringpiece_val;
+ EXPECT_FALSE(frame_reader.ReadStringPiece16(&stringpiece_val));
+
+ // Also make sure that trying to read a uint16_t, which technically could
+ // work, fails immediately due to previously encountered failed read.
+ uint16_t uint16_val;
+ EXPECT_FALSE(frame_reader.ReadUInt16(&uint16_val));
+}
+
+// Tests ReadStringPiece16() with a buffer too small even to fit the length.
+TEST(SpdyFrameReaderTest, ReadStringPiece16WithBufferWayTooSmall) {
+ // Frame data in network byte order.
+ const char kFrameData[] = {
+ 0x00, // part of a uint16_t
+ };
+
+ SpdyFrameReader frame_reader(kFrameData, SPDY_ARRAYSIZE(kFrameData));
+ EXPECT_FALSE(frame_reader.IsDoneReading());
+
+ SpdyStringPiece stringpiece_val;
+ EXPECT_FALSE(frame_reader.ReadStringPiece16(&stringpiece_val));
+
+ // Also make sure that trying to read a uint16_t, which technically could
+ // work, fails immediately due to previously encountered failed read.
+ uint16_t uint16_val;
+ EXPECT_FALSE(frame_reader.ReadUInt16(&uint16_val));
+}
+
+// Tests ReadStringPiece32() with a buffer too small to fit the entire string.
+TEST(SpdyFrameReaderTest, ReadStringPiece32WithBufferTooSmall) {
+ // Frame data in network byte order.
+ const char kFrameData[] = {
+ 0x00, 0x00, 0x00, 0x03, // uint32_t(3)
+ 0x48, 0x69, // "Hi"
+ };
+
+ SpdyFrameReader frame_reader(kFrameData, SPDY_ARRAYSIZE(kFrameData));
+ EXPECT_FALSE(frame_reader.IsDoneReading());
+
+ SpdyStringPiece stringpiece_val;
+ EXPECT_FALSE(frame_reader.ReadStringPiece32(&stringpiece_val));
+
+ // Also make sure that trying to read a uint16_t, which technically could
+ // work, fails immediately due to previously encountered failed read.
+ uint16_t uint16_val;
+ EXPECT_FALSE(frame_reader.ReadUInt16(&uint16_val));
+}
+
+// Tests ReadStringPiece32() with a buffer too small even to fit the length.
+TEST(SpdyFrameReaderTest, ReadStringPiece32WithBufferWayTooSmall) {
+ // Frame data in network byte order.
+ const char kFrameData[] = {
+ 0x00, 0x00, 0x00, // part of a uint32_t
+ };
+
+ SpdyFrameReader frame_reader(kFrameData, SPDY_ARRAYSIZE(kFrameData));
+ EXPECT_FALSE(frame_reader.IsDoneReading());
+
+ SpdyStringPiece stringpiece_val;
+ EXPECT_FALSE(frame_reader.ReadStringPiece32(&stringpiece_val));
+
+ // Also make sure that trying to read a uint16_t, which technically could
+ // work, fails immediately due to previously encountered failed read.
+ uint16_t uint16_val;
+ EXPECT_FALSE(frame_reader.ReadUInt16(&uint16_val));
+}
+
+TEST(SpdyFrameReaderTest, ReadBytes) {
+ // Frame data in network byte order.
+ const char kFrameData[] = {
+ 0x66, 0x6f, 0x6f, // "foo"
+ 0x48, 0x69, // "Hi"
+ };
+
+ SpdyFrameReader frame_reader(kFrameData, SPDY_ARRAYSIZE(kFrameData));
+ EXPECT_FALSE(frame_reader.IsDoneReading());
+
+ char dest1[3] = {};
+ EXPECT_TRUE(frame_reader.ReadBytes(&dest1, SPDY_ARRAYSIZE(dest1)));
+ EXPECT_FALSE(frame_reader.IsDoneReading());
+ EXPECT_EQ("foo", SpdyStringPiece(dest1, SPDY_ARRAYSIZE(dest1)));
+
+ char dest2[2] = {};
+ EXPECT_TRUE(frame_reader.ReadBytes(&dest2, SPDY_ARRAYSIZE(dest2)));
+ EXPECT_TRUE(frame_reader.IsDoneReading());
+ EXPECT_EQ("Hi", SpdyStringPiece(dest2, SPDY_ARRAYSIZE(dest2)));
+}
+
+TEST(SpdyFrameReaderTest, ReadBytesWithBufferTooSmall) {
+ // Frame data in network byte order.
+ const char kFrameData[] = {
+ 0x01,
+ };
+
+ SpdyFrameReader frame_reader(kFrameData, SPDY_ARRAYSIZE(kFrameData));
+ EXPECT_FALSE(frame_reader.IsDoneReading());
+
+ char dest[SPDY_ARRAYSIZE(kFrameData) + 2] = {};
+ EXPECT_FALSE(frame_reader.ReadBytes(&dest, SPDY_ARRAYSIZE(kFrameData) + 1));
+ EXPECT_STREQ("", dest);
+}
+
+} // namespace spdy
diff --git a/chromium/net/third_party/quiche/src/spdy/core/spdy_framer.cc b/chromium/net/third_party/quiche/src/spdy/core/spdy_framer.cc
new file mode 100644
index 00000000000..a9252a1af34
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/spdy/core/spdy_framer.cc
@@ -0,0 +1,1295 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/spdy/core/spdy_framer.h"
+
+#include <algorithm>
+#include <cstdint>
+#include <iterator>
+#include <list>
+#include <new>
+
+#include "base/logging.h"
+#include "net/third_party/quiche/src/http2/platform/api/http2_macros.h"
+#include "net/third_party/quiche/src/spdy/core/hpack/hpack_constants.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_bitmasks.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_frame_builder.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_frame_reader.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_bug_tracker.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_estimate_memory_usage.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_ptr_util.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_string_utils.h"
+
+namespace spdy {
+
+namespace {
+
+// Pack parent stream ID and exclusive flag into the format used by HTTP/2
+// headers and priority frames.
+uint32_t PackStreamDependencyValues(bool exclusive,
+ SpdyStreamId parent_stream_id) {
+ // Make sure the highest-order bit in the parent stream id is zeroed out.
+ uint32_t parent = parent_stream_id & 0x7fffffff;
+ // Set the one-bit exclusivity flag.
+ uint32_t e_bit = exclusive ? 0x80000000 : 0;
+ return parent | e_bit;
+}
+
+// Used to indicate no flags in a HTTP2 flags field.
+const uint8_t kNoFlags = 0;
+
+// Wire size of pad length field.
+const size_t kPadLengthFieldSize = 1;
+
+// The size of one parameter in SETTINGS frame.
+const size_t kOneSettingParameterSize = 6;
+
+size_t GetUncompressedSerializedLength(const SpdyHeaderBlock& headers) {
+ const size_t num_name_value_pairs_size = sizeof(uint32_t);
+ const size_t length_of_name_size = num_name_value_pairs_size;
+ const size_t length_of_value_size = num_name_value_pairs_size;
+
+ size_t total_length = num_name_value_pairs_size;
+ for (const auto& header : headers) {
+ // We add space for the length of the name and the length of the value as
+ // well as the length of the name and the length of the value.
+ total_length += length_of_name_size + header.first.size() +
+ length_of_value_size + header.second.size();
+ }
+ return total_length;
+}
+
+// Serializes the flags octet for a given SpdyHeadersIR.
+uint8_t SerializeHeaderFrameFlags(const SpdyHeadersIR& header_ir,
+ const bool end_headers) {
+ uint8_t flags = 0;
+ if (header_ir.fin()) {
+ flags |= CONTROL_FLAG_FIN;
+ }
+ if (end_headers) {
+ flags |= HEADERS_FLAG_END_HEADERS;
+ }
+ if (header_ir.padded()) {
+ flags |= HEADERS_FLAG_PADDED;
+ }
+ if (header_ir.has_priority()) {
+ flags |= HEADERS_FLAG_PRIORITY;
+ }
+ return flags;
+}
+
+// Serializes the flags octet for a given SpdyPushPromiseIR.
+uint8_t SerializePushPromiseFrameFlags(const SpdyPushPromiseIR& push_promise_ir,
+ const bool end_headers) {
+ uint8_t flags = 0;
+ if (push_promise_ir.padded()) {
+ flags = flags | PUSH_PROMISE_FLAG_PADDED;
+ }
+ if (end_headers) {
+ flags |= PUSH_PROMISE_FLAG_END_PUSH_PROMISE;
+ }
+ return flags;
+}
+
+// Serializes a HEADERS frame from the given SpdyHeadersIR and encoded header
+// block. Does not need or use the SpdyHeaderBlock inside SpdyHeadersIR.
+// Return false if the serialization fails. |encoding| should not be empty.
+bool SerializeHeadersGivenEncoding(const SpdyHeadersIR& headers,
+ const SpdyString& encoding,
+ const bool end_headers,
+ ZeroCopyOutputBuffer* output) {
+ const size_t frame_size =
+ GetHeaderFrameSizeSansBlock(headers) + encoding.size();
+ SpdyFrameBuilder builder(frame_size, output);
+ bool ret = builder.BeginNewFrame(
+ SpdyFrameType::HEADERS, SerializeHeaderFrameFlags(headers, end_headers),
+ headers.stream_id(), frame_size - kFrameHeaderSize);
+ DCHECK_EQ(kFrameHeaderSize, builder.length());
+
+ if (ret && headers.padded()) {
+ ret &= builder.WriteUInt8(headers.padding_payload_len());
+ }
+
+ if (ret && headers.has_priority()) {
+ int weight = ClampHttp2Weight(headers.weight());
+ ret &= builder.WriteUInt32(PackStreamDependencyValues(
+ headers.exclusive(), headers.parent_stream_id()));
+ // Per RFC 7540 section 6.3, serialized weight value is actual value - 1.
+ ret &= builder.WriteUInt8(weight - 1);
+ }
+
+ if (ret) {
+ ret &= builder.WriteBytes(encoding.data(), encoding.size());
+ }
+
+ if (ret && headers.padding_payload_len() > 0) {
+ SpdyString padding(headers.padding_payload_len(), 0);
+ ret &= builder.WriteBytes(padding.data(), padding.length());
+ }
+
+ if (!ret) {
+ DLOG(WARNING) << "Failed to build HEADERS. Not enough space in output";
+ }
+ return ret;
+}
+
+// Serializes a PUSH_PROMISE frame from the given SpdyPushPromiseIR and
+// encoded header block. Does not need or use the SpdyHeaderBlock inside
+// SpdyPushPromiseIR.
+bool SerializePushPromiseGivenEncoding(const SpdyPushPromiseIR& push_promise,
+ const SpdyString& encoding,
+ const bool end_headers,
+ ZeroCopyOutputBuffer* output) {
+ const size_t frame_size =
+ GetPushPromiseFrameSizeSansBlock(push_promise) + encoding.size();
+ SpdyFrameBuilder builder(frame_size, output);
+ bool ok = builder.BeginNewFrame(
+ SpdyFrameType::PUSH_PROMISE,
+ SerializePushPromiseFrameFlags(push_promise, end_headers),
+ push_promise.stream_id(), frame_size - kFrameHeaderSize);
+
+ if (push_promise.padded()) {
+ ok = ok && builder.WriteUInt8(push_promise.padding_payload_len());
+ }
+ ok = ok && builder.WriteUInt32(push_promise.promised_stream_id()) &&
+ builder.WriteBytes(encoding.data(), encoding.size());
+ if (ok && push_promise.padding_payload_len() > 0) {
+ SpdyString padding(push_promise.padding_payload_len(), 0);
+ ok = builder.WriteBytes(padding.data(), padding.length());
+ }
+
+ DLOG_IF(ERROR, !ok) << "Failed to write PUSH_PROMISE encoding, not enough "
+ << "space in output";
+ return ok;
+}
+
+bool WritePayloadWithContinuation(SpdyFrameBuilder* builder,
+ const SpdyString& hpack_encoding,
+ SpdyStreamId stream_id,
+ SpdyFrameType type,
+ int padding_payload_len) {
+ uint8_t end_flag = 0;
+ uint8_t flags = 0;
+ if (type == SpdyFrameType::HEADERS) {
+ end_flag = HEADERS_FLAG_END_HEADERS;
+ } else if (type == SpdyFrameType::PUSH_PROMISE) {
+ end_flag = PUSH_PROMISE_FLAG_END_PUSH_PROMISE;
+ } else {
+ DLOG(FATAL) << "CONTINUATION frames cannot be used with frame type "
+ << FrameTypeToString(type);
+ }
+
+ // Write all the padding payload and as much of the data payload as possible
+ // into the initial frame.
+ size_t bytes_remaining = 0;
+ bytes_remaining = hpack_encoding.size() -
+ std::min(hpack_encoding.size(),
+ kHttp2MaxControlFrameSendSize - builder->length() -
+ padding_payload_len);
+ bool ret = builder->WriteBytes(&hpack_encoding[0],
+ hpack_encoding.size() - bytes_remaining);
+ if (padding_payload_len > 0) {
+ SpdyString padding = SpdyString(padding_payload_len, 0);
+ ret &= builder->WriteBytes(padding.data(), padding.length());
+ }
+
+ // Tack on CONTINUATION frames for the overflow.
+ while (bytes_remaining > 0 && ret) {
+ size_t bytes_to_write =
+ std::min(bytes_remaining,
+ kHttp2MaxControlFrameSendSize - kContinuationFrameMinimumSize);
+ // Write CONTINUATION frame prefix.
+ if (bytes_remaining == bytes_to_write) {
+ flags |= end_flag;
+ }
+ ret &= builder->BeginNewFrame(SpdyFrameType::CONTINUATION, flags, stream_id,
+ bytes_to_write);
+ // Write payload fragment.
+ ret &= builder->WriteBytes(
+ &hpack_encoding[hpack_encoding.size() - bytes_remaining],
+ bytes_to_write);
+ bytes_remaining -= bytes_to_write;
+ }
+ return ret;
+}
+
+void SerializeDataBuilderHelper(const SpdyDataIR& data_ir,
+ uint8_t* flags,
+ int* num_padding_fields,
+ size_t* size_with_padding) {
+ if (data_ir.fin()) {
+ *flags = DATA_FLAG_FIN;
+ }
+
+ if (data_ir.padded()) {
+ *flags = *flags | DATA_FLAG_PADDED;
+ ++*num_padding_fields;
+ }
+
+ *size_with_padding = *num_padding_fields + data_ir.data_len() +
+ data_ir.padding_payload_len() + kDataFrameMinimumSize;
+}
+
+void SerializeDataFrameHeaderWithPaddingLengthFieldBuilderHelper(
+ const SpdyDataIR& data_ir,
+ uint8_t* flags,
+ size_t* frame_size,
+ size_t* num_padding_fields) {
+ *flags = DATA_FLAG_NONE;
+ if (data_ir.fin()) {
+ *flags = DATA_FLAG_FIN;
+ }
+
+ *frame_size = kDataFrameMinimumSize;
+ if (data_ir.padded()) {
+ *flags = *flags | DATA_FLAG_PADDED;
+ ++(*num_padding_fields);
+ *frame_size = *frame_size + *num_padding_fields;
+ }
+}
+
+void SerializeSettingsBuilderHelper(const SpdySettingsIR& settings,
+ uint8_t* flags,
+ const SettingsMap* values,
+ size_t* size) {
+ if (settings.is_ack()) {
+ *flags = *flags | SETTINGS_FLAG_ACK;
+ }
+ *size =
+ kSettingsFrameMinimumSize + (values->size() * kOneSettingParameterSize);
+}
+
+void SerializeAltSvcBuilderHelper(const SpdyAltSvcIR& altsvc_ir,
+ SpdyString* value,
+ size_t* size) {
+ *size = kGetAltSvcFrameMinimumSize;
+ *size = *size + altsvc_ir.origin().length();
+ *value = SpdyAltSvcWireFormat::SerializeHeaderFieldValue(
+ altsvc_ir.altsvc_vector());
+ *size = *size + value->length();
+}
+
+} // namespace
+
+SpdyFramer::SpdyFramer(CompressionOption option)
+ : debug_visitor_(nullptr), compression_option_(option) {
+ static_assert(kHttp2MaxControlFrameSendSize <= kHttp2DefaultFrameSizeLimit,
+ "Our send limit should be at most our receive limit.");
+}
+
+SpdyFramer::~SpdyFramer() = default;
+
+void SpdyFramer::set_debug_visitor(
+ SpdyFramerDebugVisitorInterface* debug_visitor) {
+ debug_visitor_ = debug_visitor;
+}
+
+SpdyFramer::SpdyFrameIterator::SpdyFrameIterator(SpdyFramer* framer)
+ : framer_(framer), is_first_frame_(true), has_next_frame_(true) {}
+
+SpdyFramer::SpdyFrameIterator::~SpdyFrameIterator() = default;
+
+size_t SpdyFramer::SpdyFrameIterator::NextFrame(ZeroCopyOutputBuffer* output) {
+ const SpdyFrameIR& frame_ir = GetIR();
+ if (!has_next_frame_) {
+ SPDY_BUG << "SpdyFramer::SpdyFrameIterator::NextFrame called without "
+ << "a next frame.";
+ return false;
+ }
+
+ const size_t size_without_block =
+ is_first_frame_ ? GetFrameSizeSansBlock() : kContinuationFrameMinimumSize;
+ auto encoding = SpdyMakeUnique<SpdyString>();
+ encoder_->Next(kHttp2MaxControlFrameSendSize - size_without_block,
+ encoding.get());
+ has_next_frame_ = encoder_->HasNext();
+
+ if (framer_->debug_visitor_ != nullptr) {
+ const auto& header_block_frame_ir =
+ static_cast<const SpdyFrameWithHeaderBlockIR&>(frame_ir);
+ const size_t header_list_size =
+ GetUncompressedSerializedLength(header_block_frame_ir.header_block());
+ framer_->debug_visitor_->OnSendCompressedFrame(
+ frame_ir.stream_id(),
+ is_first_frame_ ? frame_ir.frame_type() : SpdyFrameType::CONTINUATION,
+ header_list_size, size_without_block + encoding->size());
+ }
+
+ const size_t free_bytes_before = output->BytesFree();
+ bool ok = false;
+ if (is_first_frame_) {
+ is_first_frame_ = false;
+ ok = SerializeGivenEncoding(*encoding, output);
+ } else {
+ SpdyContinuationIR continuation_ir(frame_ir.stream_id());
+ continuation_ir.take_encoding(std::move(encoding));
+ continuation_ir.set_end_headers(!has_next_frame_);
+ ok = framer_->SerializeContinuation(continuation_ir, output);
+ }
+ return ok ? free_bytes_before - output->BytesFree() : 0;
+}
+
+bool SpdyFramer::SpdyFrameIterator::HasNextFrame() const {
+ return has_next_frame_;
+}
+
+SpdyFramer::SpdyHeaderFrameIterator::SpdyHeaderFrameIterator(
+ SpdyFramer* framer,
+ std::unique_ptr<const SpdyHeadersIR> headers_ir)
+ : SpdyFrameIterator(framer), headers_ir_(std::move(headers_ir)) {
+ SetEncoder(headers_ir_.get());
+}
+
+SpdyFramer::SpdyHeaderFrameIterator::~SpdyHeaderFrameIterator() = default;
+
+const SpdyFrameIR& SpdyFramer::SpdyHeaderFrameIterator::GetIR() const {
+ return *(headers_ir_.get());
+}
+
+size_t SpdyFramer::SpdyHeaderFrameIterator::GetFrameSizeSansBlock() const {
+ return GetHeaderFrameSizeSansBlock(*headers_ir_);
+}
+
+bool SpdyFramer::SpdyHeaderFrameIterator::SerializeGivenEncoding(
+ const SpdyString& encoding,
+ ZeroCopyOutputBuffer* output) const {
+ return SerializeHeadersGivenEncoding(*headers_ir_, encoding,
+ !has_next_frame(), output);
+}
+
+SpdyFramer::SpdyPushPromiseFrameIterator::SpdyPushPromiseFrameIterator(
+ SpdyFramer* framer,
+ std::unique_ptr<const SpdyPushPromiseIR> push_promise_ir)
+ : SpdyFrameIterator(framer), push_promise_ir_(std::move(push_promise_ir)) {
+ SetEncoder(push_promise_ir_.get());
+}
+
+SpdyFramer::SpdyPushPromiseFrameIterator::~SpdyPushPromiseFrameIterator() =
+ default;
+
+const SpdyFrameIR& SpdyFramer::SpdyPushPromiseFrameIterator::GetIR() const {
+ return *(push_promise_ir_.get());
+}
+
+size_t SpdyFramer::SpdyPushPromiseFrameIterator::GetFrameSizeSansBlock() const {
+ return GetPushPromiseFrameSizeSansBlock(*push_promise_ir_);
+}
+
+bool SpdyFramer::SpdyPushPromiseFrameIterator::SerializeGivenEncoding(
+ const SpdyString& encoding,
+ ZeroCopyOutputBuffer* output) const {
+ return SerializePushPromiseGivenEncoding(*push_promise_ir_, encoding,
+ !has_next_frame(), output);
+}
+
+SpdyFramer::SpdyControlFrameIterator::SpdyControlFrameIterator(
+ SpdyFramer* framer,
+ std::unique_ptr<const SpdyFrameIR> frame_ir)
+ : framer_(framer), frame_ir_(std::move(frame_ir)) {}
+
+SpdyFramer::SpdyControlFrameIterator::~SpdyControlFrameIterator() = default;
+
+size_t SpdyFramer::SpdyControlFrameIterator::NextFrame(
+ ZeroCopyOutputBuffer* output) {
+ size_t size_written = framer_->SerializeFrame(*frame_ir_, output);
+ has_next_frame_ = false;
+ return size_written;
+}
+
+bool SpdyFramer::SpdyControlFrameIterator::HasNextFrame() const {
+ return has_next_frame_;
+}
+
+const SpdyFrameIR& SpdyFramer::SpdyControlFrameIterator::GetIR() const {
+ return *(frame_ir_.get());
+}
+
+// TODO(yasong): remove all the static_casts.
+std::unique_ptr<SpdyFrameSequence> SpdyFramer::CreateIterator(
+ SpdyFramer* framer,
+ std::unique_ptr<const SpdyFrameIR> frame_ir) {
+ switch (frame_ir->frame_type()) {
+ case SpdyFrameType::HEADERS: {
+ return SpdyMakeUnique<SpdyHeaderFrameIterator>(
+ framer,
+ SpdyWrapUnique(static_cast<const SpdyHeadersIR*>(frame_ir.release())));
+ }
+ case SpdyFrameType::PUSH_PROMISE: {
+ return SpdyMakeUnique<SpdyPushPromiseFrameIterator>(
+ framer, SpdyWrapUnique(
+ static_cast<const SpdyPushPromiseIR*>(frame_ir.release())));
+ }
+ case SpdyFrameType::DATA: {
+ DVLOG(1) << "Serialize a stream end DATA frame for VTL";
+ HTTP2_FALLTHROUGH;
+ }
+ default: {
+ return SpdyMakeUnique<SpdyControlFrameIterator>(framer,
+ std::move(frame_ir));
+ }
+ }
+}
+
+SpdySerializedFrame SpdyFramer::SerializeData(const SpdyDataIR& data_ir) {
+ uint8_t flags = DATA_FLAG_NONE;
+ int num_padding_fields = 0;
+ size_t size_with_padding = 0;
+ SerializeDataBuilderHelper(data_ir, &flags, &num_padding_fields,
+ &size_with_padding);
+
+ SpdyFrameBuilder builder(size_with_padding);
+ builder.BeginNewFrame(SpdyFrameType::DATA, flags, data_ir.stream_id());
+ if (data_ir.padded()) {
+ builder.WriteUInt8(data_ir.padding_payload_len() & 0xff);
+ }
+ builder.WriteBytes(data_ir.data(), data_ir.data_len());
+ if (data_ir.padding_payload_len() > 0) {
+ SpdyString padding(data_ir.padding_payload_len(), 0);
+ builder.WriteBytes(padding.data(), padding.length());
+ }
+ DCHECK_EQ(size_with_padding, builder.length());
+ return builder.take();
+}
+
+SpdySerializedFrame SpdyFramer::SerializeDataFrameHeaderWithPaddingLengthField(
+ const SpdyDataIR& data_ir) {
+ uint8_t flags = DATA_FLAG_NONE;
+ size_t frame_size = 0;
+ size_t num_padding_fields = 0;
+ SerializeDataFrameHeaderWithPaddingLengthFieldBuilderHelper(
+ data_ir, &flags, &frame_size, &num_padding_fields);
+
+ SpdyFrameBuilder builder(frame_size);
+ builder.BeginNewFrame(
+ SpdyFrameType::DATA, flags, data_ir.stream_id(),
+ num_padding_fields + data_ir.data_len() + data_ir.padding_payload_len());
+ if (data_ir.padded()) {
+ builder.WriteUInt8(data_ir.padding_payload_len() & 0xff);
+ }
+ DCHECK_EQ(frame_size, builder.length());
+ return builder.take();
+}
+
+SpdySerializedFrame SpdyFramer::SerializeRstStream(
+ const SpdyRstStreamIR& rst_stream) const {
+ size_t expected_length = kRstStreamFrameSize;
+ SpdyFrameBuilder builder(expected_length);
+
+ builder.BeginNewFrame(SpdyFrameType::RST_STREAM, 0, rst_stream.stream_id());
+
+ builder.WriteUInt32(rst_stream.error_code());
+
+ DCHECK_EQ(expected_length, builder.length());
+ return builder.take();
+}
+
+SpdySerializedFrame SpdyFramer::SerializeSettings(
+ const SpdySettingsIR& settings) const {
+ uint8_t flags = 0;
+ // Size, in bytes, of this SETTINGS frame.
+ size_t size = 0;
+ const SettingsMap* values = &(settings.values());
+ SerializeSettingsBuilderHelper(settings, &flags, values, &size);
+ SpdyFrameBuilder builder(size);
+ builder.BeginNewFrame(SpdyFrameType::SETTINGS, flags, 0);
+
+ // If this is an ACK, payload should be empty.
+ if (settings.is_ack()) {
+ return builder.take();
+ }
+
+ DCHECK_EQ(kSettingsFrameMinimumSize, builder.length());
+ for (auto it = values->begin(); it != values->end(); ++it) {
+ int setting_id = it->first;
+ DCHECK_GE(setting_id, 0);
+ builder.WriteUInt16(static_cast<SpdySettingsId>(setting_id));
+ builder.WriteUInt32(it->second);
+ }
+ DCHECK_EQ(size, builder.length());
+ return builder.take();
+}
+
+SpdySerializedFrame SpdyFramer::SerializePing(const SpdyPingIR& ping) const {
+ SpdyFrameBuilder builder(kPingFrameSize);
+ uint8_t flags = 0;
+ if (ping.is_ack()) {
+ flags |= PING_FLAG_ACK;
+ }
+ builder.BeginNewFrame(SpdyFrameType::PING, flags, 0);
+ builder.WriteUInt64(ping.id());
+ DCHECK_EQ(kPingFrameSize, builder.length());
+ return builder.take();
+}
+
+SpdySerializedFrame SpdyFramer::SerializeGoAway(
+ const SpdyGoAwayIR& goaway) const {
+ // Compute the output buffer size, take opaque data into account.
+ size_t expected_length = kGoawayFrameMinimumSize;
+ expected_length += goaway.description().size();
+ SpdyFrameBuilder builder(expected_length);
+
+ // Serialize the GOAWAY frame.
+ builder.BeginNewFrame(SpdyFrameType::GOAWAY, 0, 0);
+
+ // GOAWAY frames specify the last good stream id.
+ builder.WriteUInt32(goaway.last_good_stream_id());
+
+ // GOAWAY frames also specify the error code.
+ builder.WriteUInt32(goaway.error_code());
+
+ // GOAWAY frames may also specify opaque data.
+ if (!goaway.description().empty()) {
+ builder.WriteBytes(goaway.description().data(),
+ goaway.description().size());
+ }
+
+ DCHECK_EQ(expected_length, builder.length());
+ return builder.take();
+}
+
+void SpdyFramer::SerializeHeadersBuilderHelper(const SpdyHeadersIR& headers,
+ uint8_t* flags,
+ size_t* size,
+ SpdyString* hpack_encoding,
+ int* weight,
+ size_t* length_field) {
+ if (headers.fin()) {
+ *flags = *flags | CONTROL_FLAG_FIN;
+ }
+ // This will get overwritten if we overflow into a CONTINUATION frame.
+ *flags = *flags | HEADERS_FLAG_END_HEADERS;
+ if (headers.has_priority()) {
+ *flags = *flags | HEADERS_FLAG_PRIORITY;
+ }
+ if (headers.padded()) {
+ *flags = *flags | HEADERS_FLAG_PADDED;
+ }
+
+ *size = kHeadersFrameMinimumSize;
+
+ if (headers.padded()) {
+ *size = *size + kPadLengthFieldSize;
+ *size = *size + headers.padding_payload_len();
+ }
+
+ if (headers.has_priority()) {
+ *weight = ClampHttp2Weight(headers.weight());
+ *size = *size + 5;
+ }
+
+ GetHpackEncoder()->EncodeHeaderSet(headers.header_block(), hpack_encoding);
+ *size = *size + hpack_encoding->size();
+ if (*size > kHttp2MaxControlFrameSendSize) {
+ *size = *size + GetNumberRequiredContinuationFrames(*size) *
+ kContinuationFrameMinimumSize;
+ *flags = *flags & ~HEADERS_FLAG_END_HEADERS;
+ }
+ // Compute frame length field.
+ if (headers.padded()) {
+ *length_field = *length_field + kPadLengthFieldSize;
+ }
+ if (headers.has_priority()) {
+ *length_field = *length_field + 4; // Dependency field.
+ *length_field = *length_field + 1; // Weight field.
+ }
+ *length_field = *length_field + headers.padding_payload_len();
+ *length_field = *length_field + hpack_encoding->size();
+ // If the HEADERS frame with payload would exceed the max frame size, then
+ // WritePayloadWithContinuation() will serialize CONTINUATION frames as
+ // necessary.
+ *length_field =
+ std::min(*length_field, kHttp2MaxControlFrameSendSize - kFrameHeaderSize);
+}
+
+SpdySerializedFrame SpdyFramer::SerializeHeaders(const SpdyHeadersIR& headers) {
+ uint8_t flags = 0;
+ // The size of this frame, including padding (if there is any) and
+ // variable-length header block.
+ size_t size = 0;
+ SpdyString hpack_encoding;
+ int weight = 0;
+ size_t length_field = 0;
+ SerializeHeadersBuilderHelper(headers, &flags, &size, &hpack_encoding,
+ &weight, &length_field);
+
+ SpdyFrameBuilder builder(size);
+ builder.BeginNewFrame(SpdyFrameType::HEADERS, flags, headers.stream_id(),
+ length_field);
+
+ DCHECK_EQ(kHeadersFrameMinimumSize, builder.length());
+
+ int padding_payload_len = 0;
+ if (headers.padded()) {
+ builder.WriteUInt8(headers.padding_payload_len());
+ padding_payload_len = headers.padding_payload_len();
+ }
+ if (headers.has_priority()) {
+ builder.WriteUInt32(PackStreamDependencyValues(headers.exclusive(),
+ headers.parent_stream_id()));
+ // Per RFC 7540 section 6.3, serialized weight value is actual value - 1.
+ builder.WriteUInt8(weight - 1);
+ }
+ WritePayloadWithContinuation(&builder, hpack_encoding, headers.stream_id(),
+ SpdyFrameType::HEADERS, padding_payload_len);
+
+ if (debug_visitor_) {
+ const size_t header_list_size =
+ GetUncompressedSerializedLength(headers.header_block());
+ debug_visitor_->OnSendCompressedFrame(headers.stream_id(),
+ SpdyFrameType::HEADERS,
+ header_list_size, builder.length());
+ }
+
+ return builder.take();
+}
+
+SpdySerializedFrame SpdyFramer::SerializeWindowUpdate(
+ const SpdyWindowUpdateIR& window_update) {
+ SpdyFrameBuilder builder(kWindowUpdateFrameSize);
+ builder.BeginNewFrame(SpdyFrameType::WINDOW_UPDATE, kNoFlags,
+ window_update.stream_id());
+ builder.WriteUInt32(window_update.delta());
+ DCHECK_EQ(kWindowUpdateFrameSize, builder.length());
+ return builder.take();
+}
+
+void SpdyFramer::SerializePushPromiseBuilderHelper(
+ const SpdyPushPromiseIR& push_promise,
+ uint8_t* flags,
+ SpdyString* hpack_encoding,
+ size_t* size) {
+ *flags = 0;
+ // This will get overwritten if we overflow into a CONTINUATION frame.
+ *flags = *flags | PUSH_PROMISE_FLAG_END_PUSH_PROMISE;
+ // The size of this frame, including variable-length name-value block.
+ *size = kPushPromiseFrameMinimumSize;
+
+ if (push_promise.padded()) {
+ *flags = *flags | PUSH_PROMISE_FLAG_PADDED;
+ *size = *size + kPadLengthFieldSize;
+ *size = *size + push_promise.padding_payload_len();
+ }
+
+ GetHpackEncoder()->EncodeHeaderSet(push_promise.header_block(),
+ hpack_encoding);
+ *size = *size + hpack_encoding->size();
+ if (*size > kHttp2MaxControlFrameSendSize) {
+ *size = *size + GetNumberRequiredContinuationFrames(*size) *
+ kContinuationFrameMinimumSize;
+ *flags = *flags & ~PUSH_PROMISE_FLAG_END_PUSH_PROMISE;
+ }
+}
+
+SpdySerializedFrame SpdyFramer::SerializePushPromise(
+ const SpdyPushPromiseIR& push_promise) {
+ uint8_t flags = 0;
+ size_t size = 0;
+ SpdyString hpack_encoding;
+ SerializePushPromiseBuilderHelper(push_promise, &flags, &hpack_encoding,
+ &size);
+
+ SpdyFrameBuilder builder(size);
+ size_t length =
+ std::min(size, kHttp2MaxControlFrameSendSize) - kFrameHeaderSize;
+ builder.BeginNewFrame(SpdyFrameType::PUSH_PROMISE, flags,
+ push_promise.stream_id(), length);
+ int padding_payload_len = 0;
+ if (push_promise.padded()) {
+ builder.WriteUInt8(push_promise.padding_payload_len());
+ builder.WriteUInt32(push_promise.promised_stream_id());
+ DCHECK_EQ(kPushPromiseFrameMinimumSize + kPadLengthFieldSize,
+ builder.length());
+
+ padding_payload_len = push_promise.padding_payload_len();
+ } else {
+ builder.WriteUInt32(push_promise.promised_stream_id());
+ DCHECK_EQ(kPushPromiseFrameMinimumSize, builder.length());
+ }
+
+ WritePayloadWithContinuation(
+ &builder, hpack_encoding, push_promise.stream_id(),
+ SpdyFrameType::PUSH_PROMISE, padding_payload_len);
+
+ if (debug_visitor_) {
+ const size_t header_list_size =
+ GetUncompressedSerializedLength(push_promise.header_block());
+ debug_visitor_->OnSendCompressedFrame(push_promise.stream_id(),
+ SpdyFrameType::PUSH_PROMISE,
+ header_list_size, builder.length());
+ }
+
+ return builder.take();
+}
+
+SpdySerializedFrame SpdyFramer::SerializeContinuation(
+ const SpdyContinuationIR& continuation) const {
+ const SpdyString& encoding = continuation.encoding();
+ size_t frame_size = kContinuationFrameMinimumSize + encoding.size();
+ SpdyFrameBuilder builder(frame_size);
+ uint8_t flags = continuation.end_headers() ? HEADERS_FLAG_END_HEADERS : 0;
+ builder.BeginNewFrame(SpdyFrameType::CONTINUATION, flags,
+ continuation.stream_id());
+ DCHECK_EQ(kFrameHeaderSize, builder.length());
+
+ builder.WriteBytes(encoding.data(), encoding.size());
+ return builder.take();
+}
+
+SpdySerializedFrame SpdyFramer::SerializeAltSvc(const SpdyAltSvcIR& altsvc_ir) {
+ SpdyString value;
+ size_t size = 0;
+ SerializeAltSvcBuilderHelper(altsvc_ir, &value, &size);
+ SpdyFrameBuilder builder(size);
+ builder.BeginNewFrame(SpdyFrameType::ALTSVC, kNoFlags, altsvc_ir.stream_id());
+
+ builder.WriteUInt16(altsvc_ir.origin().length());
+ builder.WriteBytes(altsvc_ir.origin().data(), altsvc_ir.origin().length());
+ builder.WriteBytes(value.data(), value.length());
+ DCHECK_LT(kGetAltSvcFrameMinimumSize, builder.length());
+ return builder.take();
+}
+
+SpdySerializedFrame SpdyFramer::SerializePriority(
+ const SpdyPriorityIR& priority) const {
+ SpdyFrameBuilder builder(kPriorityFrameSize);
+ builder.BeginNewFrame(SpdyFrameType::PRIORITY, kNoFlags,
+ priority.stream_id());
+
+ builder.WriteUInt32(PackStreamDependencyValues(priority.exclusive(),
+ priority.parent_stream_id()));
+ // Per RFC 7540 section 6.3, serialized weight value is actual value - 1.
+ builder.WriteUInt8(priority.weight() - 1);
+ DCHECK_EQ(kPriorityFrameSize, builder.length());
+ return builder.take();
+}
+
+SpdySerializedFrame SpdyFramer::SerializeUnknown(
+ const SpdyUnknownIR& unknown) const {
+ const size_t total_size = kFrameHeaderSize + unknown.payload().size();
+ SpdyFrameBuilder builder(total_size);
+ builder.BeginNewUncheckedFrame(unknown.type(), unknown.flags(),
+ unknown.stream_id(), unknown.length());
+ builder.WriteBytes(unknown.payload().data(), unknown.payload().size());
+ return builder.take();
+}
+
+namespace {
+
+class FrameSerializationVisitor : public SpdyFrameVisitor {
+ public:
+ explicit FrameSerializationVisitor(SpdyFramer* framer)
+ : framer_(framer), frame_() {}
+ ~FrameSerializationVisitor() override = default;
+
+ SpdySerializedFrame ReleaseSerializedFrame() { return std::move(frame_); }
+
+ void VisitData(const SpdyDataIR& data) override {
+ frame_ = framer_->SerializeData(data);
+ }
+ void VisitRstStream(const SpdyRstStreamIR& rst_stream) override {
+ frame_ = framer_->SerializeRstStream(rst_stream);
+ }
+ void VisitSettings(const SpdySettingsIR& settings) override {
+ frame_ = framer_->SerializeSettings(settings);
+ }
+ void VisitPing(const SpdyPingIR& ping) override {
+ frame_ = framer_->SerializePing(ping);
+ }
+ void VisitGoAway(const SpdyGoAwayIR& goaway) override {
+ frame_ = framer_->SerializeGoAway(goaway);
+ }
+ void VisitHeaders(const SpdyHeadersIR& headers) override {
+ frame_ = framer_->SerializeHeaders(headers);
+ }
+ void VisitWindowUpdate(const SpdyWindowUpdateIR& window_update) override {
+ frame_ = framer_->SerializeWindowUpdate(window_update);
+ }
+ void VisitPushPromise(const SpdyPushPromiseIR& push_promise) override {
+ frame_ = framer_->SerializePushPromise(push_promise);
+ }
+ void VisitContinuation(const SpdyContinuationIR& continuation) override {
+ frame_ = framer_->SerializeContinuation(continuation);
+ }
+ void VisitAltSvc(const SpdyAltSvcIR& altsvc) override {
+ frame_ = framer_->SerializeAltSvc(altsvc);
+ }
+ void VisitPriority(const SpdyPriorityIR& priority) override {
+ frame_ = framer_->SerializePriority(priority);
+ }
+ void VisitUnknown(const SpdyUnknownIR& unknown) override {
+ frame_ = framer_->SerializeUnknown(unknown);
+ }
+
+ private:
+ SpdyFramer* framer_;
+ SpdySerializedFrame frame_;
+};
+
+// TODO(diannahu): Use also in frame serialization.
+class FlagsSerializationVisitor : public SpdyFrameVisitor {
+ public:
+ void VisitData(const SpdyDataIR& data) override {
+ flags_ = DATA_FLAG_NONE;
+ if (data.fin()) {
+ flags_ |= DATA_FLAG_FIN;
+ }
+ if (data.padded()) {
+ flags_ |= DATA_FLAG_PADDED;
+ }
+ }
+
+ void VisitRstStream(const SpdyRstStreamIR& rst_stream) override {
+ flags_ = kNoFlags;
+ }
+
+ void VisitSettings(const SpdySettingsIR& settings) override {
+ flags_ = kNoFlags;
+ if (settings.is_ack()) {
+ flags_ |= SETTINGS_FLAG_ACK;
+ }
+ }
+
+ void VisitPing(const SpdyPingIR& ping) override {
+ flags_ = kNoFlags;
+ if (ping.is_ack()) {
+ flags_ |= PING_FLAG_ACK;
+ }
+ }
+
+ void VisitGoAway(const SpdyGoAwayIR& goaway) override { flags_ = kNoFlags; }
+
+ // TODO(diannahu): The END_HEADERS flag is incorrect for HEADERS that require
+ // CONTINUATION frames.
+ void VisitHeaders(const SpdyHeadersIR& headers) override {
+ flags_ = HEADERS_FLAG_END_HEADERS;
+ if (headers.fin()) {
+ flags_ |= CONTROL_FLAG_FIN;
+ }
+ if (headers.padded()) {
+ flags_ |= HEADERS_FLAG_PADDED;
+ }
+ if (headers.has_priority()) {
+ flags_ |= HEADERS_FLAG_PRIORITY;
+ }
+ }
+
+ void VisitWindowUpdate(const SpdyWindowUpdateIR& window_update) override {
+ flags_ = kNoFlags;
+ }
+
+ // TODO(diannahu): The END_PUSH_PROMISE flag is incorrect for PUSH_PROMISEs
+ // that require CONTINUATION frames.
+ void VisitPushPromise(const SpdyPushPromiseIR& push_promise) override {
+ flags_ = PUSH_PROMISE_FLAG_END_PUSH_PROMISE;
+ if (push_promise.padded()) {
+ flags_ |= PUSH_PROMISE_FLAG_PADDED;
+ }
+ }
+
+ // TODO(diannahu): The END_HEADERS flag is incorrect for CONTINUATIONs that
+ // require CONTINUATION frames.
+ void VisitContinuation(const SpdyContinuationIR& continuation) override {
+ flags_ = HEADERS_FLAG_END_HEADERS;
+ }
+
+ void VisitAltSvc(const SpdyAltSvcIR& altsvc) override { flags_ = kNoFlags; }
+
+ void VisitPriority(const SpdyPriorityIR& priority) override {
+ flags_ = kNoFlags;
+ }
+
+ uint8_t flags() const { return flags_; }
+
+ private:
+ uint8_t flags_ = kNoFlags;
+};
+
+} // namespace
+
+SpdySerializedFrame SpdyFramer::SerializeFrame(const SpdyFrameIR& frame) {
+ FrameSerializationVisitor visitor(this);
+ frame.Visit(&visitor);
+ return visitor.ReleaseSerializedFrame();
+}
+
+uint8_t SpdyFramer::GetSerializedFlags(const SpdyFrameIR& frame) {
+ FlagsSerializationVisitor visitor;
+ frame.Visit(&visitor);
+ return visitor.flags();
+}
+
+bool SpdyFramer::SerializeData(const SpdyDataIR& data_ir,
+ ZeroCopyOutputBuffer* output) const {
+ uint8_t flags = DATA_FLAG_NONE;
+ int num_padding_fields = 0;
+ size_t size_with_padding = 0;
+ SerializeDataBuilderHelper(data_ir, &flags, &num_padding_fields,
+ &size_with_padding);
+ SpdyFrameBuilder builder(size_with_padding, output);
+
+ bool ok =
+ builder.BeginNewFrame(SpdyFrameType::DATA, flags, data_ir.stream_id());
+
+ if (data_ir.padded()) {
+ ok = ok && builder.WriteUInt8(data_ir.padding_payload_len() & 0xff);
+ }
+
+ ok = ok && builder.WriteBytes(data_ir.data(), data_ir.data_len());
+ if (data_ir.padding_payload_len() > 0) {
+ SpdyString padding;
+ padding = SpdyString(data_ir.padding_payload_len(), 0);
+ ok = ok && builder.WriteBytes(padding.data(), padding.length());
+ }
+ DCHECK_EQ(size_with_padding, builder.length());
+ return ok;
+}
+
+bool SpdyFramer::SerializeDataFrameHeaderWithPaddingLengthField(
+ const SpdyDataIR& data_ir,
+ ZeroCopyOutputBuffer* output) const {
+ uint8_t flags = DATA_FLAG_NONE;
+ size_t frame_size = 0;
+ size_t num_padding_fields = 0;
+ SerializeDataFrameHeaderWithPaddingLengthFieldBuilderHelper(
+ data_ir, &flags, &frame_size, &num_padding_fields);
+
+ SpdyFrameBuilder builder(frame_size, output);
+ bool ok = true;
+ ok = ok &&
+ builder.BeginNewFrame(SpdyFrameType::DATA, flags, data_ir.stream_id(),
+ num_padding_fields + data_ir.data_len() +
+ data_ir.padding_payload_len());
+ if (data_ir.padded()) {
+ ok = ok && builder.WriteUInt8(data_ir.padding_payload_len() & 0xff);
+ }
+ DCHECK_EQ(frame_size, builder.length());
+ return ok;
+}
+
+bool SpdyFramer::SerializeRstStream(const SpdyRstStreamIR& rst_stream,
+ ZeroCopyOutputBuffer* output) const {
+ size_t expected_length = kRstStreamFrameSize;
+ SpdyFrameBuilder builder(expected_length, output);
+ bool ok = builder.BeginNewFrame(SpdyFrameType::RST_STREAM, 0,
+ rst_stream.stream_id());
+ ok = ok && builder.WriteUInt32(rst_stream.error_code());
+
+ DCHECK_EQ(expected_length, builder.length());
+ return ok;
+}
+
+bool SpdyFramer::SerializeSettings(const SpdySettingsIR& settings,
+ ZeroCopyOutputBuffer* output) const {
+ uint8_t flags = 0;
+ // Size, in bytes, of this SETTINGS frame.
+ size_t size = 0;
+ const SettingsMap* values = &(settings.values());
+ SerializeSettingsBuilderHelper(settings, &flags, values, &size);
+ SpdyFrameBuilder builder(size, output);
+ bool ok = builder.BeginNewFrame(SpdyFrameType::SETTINGS, flags, 0);
+
+ // If this is an ACK, payload should be empty.
+ if (settings.is_ack()) {
+ return ok;
+ }
+
+ DCHECK_EQ(kSettingsFrameMinimumSize, builder.length());
+ for (auto it = values->begin(); it != values->end(); ++it) {
+ int setting_id = it->first;
+ DCHECK_GE(setting_id, 0);
+ ok = ok && builder.WriteUInt16(static_cast<SpdySettingsId>(setting_id)) &&
+ builder.WriteUInt32(it->second);
+ }
+ DCHECK_EQ(size, builder.length());
+ return ok;
+}
+
+bool SpdyFramer::SerializePing(const SpdyPingIR& ping,
+ ZeroCopyOutputBuffer* output) const {
+ SpdyFrameBuilder builder(kPingFrameSize, output);
+ uint8_t flags = 0;
+ if (ping.is_ack()) {
+ flags |= PING_FLAG_ACK;
+ }
+ bool ok = builder.BeginNewFrame(SpdyFrameType::PING, flags, 0);
+ ok = ok && builder.WriteUInt64(ping.id());
+ DCHECK_EQ(kPingFrameSize, builder.length());
+ return ok;
+}
+
+bool SpdyFramer::SerializeGoAway(const SpdyGoAwayIR& goaway,
+ ZeroCopyOutputBuffer* output) const {
+ // Compute the output buffer size, take opaque data into account.
+ size_t expected_length = kGoawayFrameMinimumSize;
+ expected_length += goaway.description().size();
+ SpdyFrameBuilder builder(expected_length, output);
+
+ // Serialize the GOAWAY frame.
+ bool ok = builder.BeginNewFrame(SpdyFrameType::GOAWAY, 0, 0);
+
+ // GOAWAY frames specify the last good stream id.
+ ok = ok && builder.WriteUInt32(goaway.last_good_stream_id()) &&
+ // GOAWAY frames also specify the error status code.
+ builder.WriteUInt32(goaway.error_code());
+
+ // GOAWAY frames may also specify opaque data.
+ if (!goaway.description().empty()) {
+ ok = ok && builder.WriteBytes(goaway.description().data(),
+ goaway.description().size());
+ }
+
+ DCHECK_EQ(expected_length, builder.length());
+ return ok;
+}
+
+bool SpdyFramer::SerializeHeaders(const SpdyHeadersIR& headers,
+ ZeroCopyOutputBuffer* output) {
+ uint8_t flags = 0;
+ // The size of this frame, including padding (if there is any) and
+ // variable-length header block.
+ size_t size = 0;
+ SpdyString hpack_encoding;
+ int weight = 0;
+ size_t length_field = 0;
+ SerializeHeadersBuilderHelper(headers, &flags, &size, &hpack_encoding,
+ &weight, &length_field);
+
+ bool ok = true;
+ SpdyFrameBuilder builder(size, output);
+ ok = ok && builder.BeginNewFrame(SpdyFrameType::HEADERS, flags,
+ headers.stream_id(), length_field);
+ DCHECK_EQ(kHeadersFrameMinimumSize, builder.length());
+
+ int padding_payload_len = 0;
+ if (headers.padded()) {
+ ok = ok && builder.WriteUInt8(headers.padding_payload_len());
+ padding_payload_len = headers.padding_payload_len();
+ }
+ if (headers.has_priority()) {
+ ok = ok &&
+ builder.WriteUInt32(PackStreamDependencyValues(
+ headers.exclusive(), headers.parent_stream_id())) &&
+ // Per RFC 7540 section 6.3, serialized weight value is weight - 1.
+ builder.WriteUInt8(weight - 1);
+ }
+ ok = ok && WritePayloadWithContinuation(
+ &builder, hpack_encoding, headers.stream_id(),
+ SpdyFrameType::HEADERS, padding_payload_len);
+
+ if (debug_visitor_) {
+ const size_t header_list_size =
+ GetUncompressedSerializedLength(headers.header_block());
+ debug_visitor_->OnSendCompressedFrame(headers.stream_id(),
+ SpdyFrameType::HEADERS,
+ header_list_size, builder.length());
+ }
+
+ return ok;
+}
+
+bool SpdyFramer::SerializeWindowUpdate(const SpdyWindowUpdateIR& window_update,
+ ZeroCopyOutputBuffer* output) const {
+ SpdyFrameBuilder builder(kWindowUpdateFrameSize, output);
+ bool ok = builder.BeginNewFrame(SpdyFrameType::WINDOW_UPDATE, kNoFlags,
+ window_update.stream_id());
+ ok = ok && builder.WriteUInt32(window_update.delta());
+ DCHECK_EQ(kWindowUpdateFrameSize, builder.length());
+ return ok;
+}
+
+bool SpdyFramer::SerializePushPromise(const SpdyPushPromiseIR& push_promise,
+ ZeroCopyOutputBuffer* output) {
+ uint8_t flags = 0;
+ size_t size = 0;
+ SpdyString hpack_encoding;
+ SerializePushPromiseBuilderHelper(push_promise, &flags, &hpack_encoding,
+ &size);
+
+ bool ok = true;
+ SpdyFrameBuilder builder(size, output);
+ size_t length =
+ std::min(size, kHttp2MaxControlFrameSendSize) - kFrameHeaderSize;
+ ok = builder.BeginNewFrame(SpdyFrameType::PUSH_PROMISE, flags,
+ push_promise.stream_id(), length);
+
+ int padding_payload_len = 0;
+ if (push_promise.padded()) {
+ ok = ok && builder.WriteUInt8(push_promise.padding_payload_len()) &&
+ builder.WriteUInt32(push_promise.promised_stream_id());
+ DCHECK_EQ(kPushPromiseFrameMinimumSize + kPadLengthFieldSize,
+ builder.length());
+
+ padding_payload_len = push_promise.padding_payload_len();
+ } else {
+ ok = ok && builder.WriteUInt32(push_promise.promised_stream_id());
+ DCHECK_EQ(kPushPromiseFrameMinimumSize, builder.length());
+ }
+
+ ok = ok && WritePayloadWithContinuation(
+ &builder, hpack_encoding, push_promise.stream_id(),
+ SpdyFrameType::PUSH_PROMISE, padding_payload_len);
+
+ if (debug_visitor_) {
+ const size_t header_list_size =
+ GetUncompressedSerializedLength(push_promise.header_block());
+ debug_visitor_->OnSendCompressedFrame(push_promise.stream_id(),
+ SpdyFrameType::PUSH_PROMISE,
+ header_list_size, builder.length());
+ }
+
+ return ok;
+}
+
+bool SpdyFramer::SerializeContinuation(const SpdyContinuationIR& continuation,
+ ZeroCopyOutputBuffer* output) const {
+ const SpdyString& encoding = continuation.encoding();
+ size_t frame_size = kContinuationFrameMinimumSize + encoding.size();
+ SpdyFrameBuilder builder(frame_size, output);
+ uint8_t flags = continuation.end_headers() ? HEADERS_FLAG_END_HEADERS : 0;
+ bool ok = builder.BeginNewFrame(SpdyFrameType::CONTINUATION, flags,
+ continuation.stream_id(),
+ frame_size - kFrameHeaderSize);
+ DCHECK_EQ(kFrameHeaderSize, builder.length());
+
+ ok = ok && builder.WriteBytes(encoding.data(), encoding.size());
+ return ok;
+}
+
+bool SpdyFramer::SerializeAltSvc(const SpdyAltSvcIR& altsvc_ir,
+ ZeroCopyOutputBuffer* output) {
+ SpdyString value;
+ size_t size = 0;
+ SerializeAltSvcBuilderHelper(altsvc_ir, &value, &size);
+ SpdyFrameBuilder builder(size, output);
+ bool ok = builder.BeginNewFrame(SpdyFrameType::ALTSVC, kNoFlags,
+ altsvc_ir.stream_id()) &&
+ builder.WriteUInt16(altsvc_ir.origin().length()) &&
+ builder.WriteBytes(altsvc_ir.origin().data(),
+ altsvc_ir.origin().length()) &&
+ builder.WriteBytes(value.data(), value.length());
+ DCHECK_LT(kGetAltSvcFrameMinimumSize, builder.length());
+ return ok;
+}
+
+bool SpdyFramer::SerializePriority(const SpdyPriorityIR& priority,
+ ZeroCopyOutputBuffer* output) const {
+ SpdyFrameBuilder builder(kPriorityFrameSize, output);
+ bool ok = builder.BeginNewFrame(SpdyFrameType::PRIORITY, kNoFlags,
+ priority.stream_id());
+ ok = ok &&
+ builder.WriteUInt32(PackStreamDependencyValues(
+ priority.exclusive(), priority.parent_stream_id())) &&
+ // Per RFC 7540 section 6.3, serialized weight value is actual value - 1.
+ builder.WriteUInt8(priority.weight() - 1);
+ DCHECK_EQ(kPriorityFrameSize, builder.length());
+ return ok;
+}
+
+bool SpdyFramer::SerializeUnknown(const SpdyUnknownIR& unknown,
+ ZeroCopyOutputBuffer* output) const {
+ const size_t total_size = kFrameHeaderSize + unknown.payload().size();
+ SpdyFrameBuilder builder(total_size, output);
+ bool ok = builder.BeginNewUncheckedFrame(
+ unknown.type(), unknown.flags(), unknown.stream_id(), unknown.length());
+ ok = ok &&
+ builder.WriteBytes(unknown.payload().data(), unknown.payload().size());
+ return ok;
+}
+
+namespace {
+
+class FrameSerializationVisitorWithOutput : public SpdyFrameVisitor {
+ public:
+ explicit FrameSerializationVisitorWithOutput(SpdyFramer* framer,
+ ZeroCopyOutputBuffer* output)
+ : framer_(framer), output_(output), result_(false) {}
+ ~FrameSerializationVisitorWithOutput() override = default;
+
+ size_t Result() { return result_; }
+
+ void VisitData(const SpdyDataIR& data) override {
+ result_ = framer_->SerializeData(data, output_);
+ }
+ void VisitRstStream(const SpdyRstStreamIR& rst_stream) override {
+ result_ = framer_->SerializeRstStream(rst_stream, output_);
+ }
+ void VisitSettings(const SpdySettingsIR& settings) override {
+ result_ = framer_->SerializeSettings(settings, output_);
+ }
+ void VisitPing(const SpdyPingIR& ping) override {
+ result_ = framer_->SerializePing(ping, output_);
+ }
+ void VisitGoAway(const SpdyGoAwayIR& goaway) override {
+ result_ = framer_->SerializeGoAway(goaway, output_);
+ }
+ void VisitHeaders(const SpdyHeadersIR& headers) override {
+ result_ = framer_->SerializeHeaders(headers, output_);
+ }
+ void VisitWindowUpdate(const SpdyWindowUpdateIR& window_update) override {
+ result_ = framer_->SerializeWindowUpdate(window_update, output_);
+ }
+ void VisitPushPromise(const SpdyPushPromiseIR& push_promise) override {
+ result_ = framer_->SerializePushPromise(push_promise, output_);
+ }
+ void VisitContinuation(const SpdyContinuationIR& continuation) override {
+ result_ = framer_->SerializeContinuation(continuation, output_);
+ }
+ void VisitAltSvc(const SpdyAltSvcIR& altsvc) override {
+ result_ = framer_->SerializeAltSvc(altsvc, output_);
+ }
+ void VisitPriority(const SpdyPriorityIR& priority) override {
+ result_ = framer_->SerializePriority(priority, output_);
+ }
+ void VisitUnknown(const SpdyUnknownIR& unknown) override {
+ result_ = framer_->SerializeUnknown(unknown, output_);
+ }
+
+ private:
+ SpdyFramer* framer_;
+ ZeroCopyOutputBuffer* output_;
+ bool result_;
+};
+
+} // namespace
+
+size_t SpdyFramer::SerializeFrame(const SpdyFrameIR& frame,
+ ZeroCopyOutputBuffer* output) {
+ FrameSerializationVisitorWithOutput visitor(this, output);
+ size_t free_bytes_before = output->BytesFree();
+ frame.Visit(&visitor);
+ return visitor.Result() ? free_bytes_before - output->BytesFree() : 0;
+}
+
+HpackEncoder* SpdyFramer::GetHpackEncoder() {
+ if (hpack_encoder_ == nullptr) {
+ hpack_encoder_ = SpdyMakeUnique<HpackEncoder>(ObtainHpackHuffmanTable());
+ if (!compression_enabled()) {
+ hpack_encoder_->DisableCompression();
+ }
+ }
+ return hpack_encoder_.get();
+}
+
+void SpdyFramer::UpdateHeaderEncoderTableSize(uint32_t value) {
+ GetHpackEncoder()->ApplyHeaderTableSizeSetting(value);
+}
+
+size_t SpdyFramer::header_encoder_table_size() const {
+ if (hpack_encoder_ == nullptr) {
+ return kDefaultHeaderTableSizeSetting;
+ } else {
+ return hpack_encoder_->CurrentHeaderTableSizeSetting();
+ }
+}
+
+void SpdyFramer::SetEncoderHeaderTableDebugVisitor(
+ std::unique_ptr<HpackHeaderTable::DebugVisitorInterface> visitor) {
+ GetHpackEncoder()->SetHeaderTableDebugVisitor(std::move(visitor));
+}
+
+size_t SpdyFramer::EstimateMemoryUsage() const {
+ return SpdyEstimateMemoryUsage(hpack_encoder_);
+}
+
+} // namespace spdy
diff --git a/chromium/net/third_party/quiche/src/spdy/core/spdy_framer.h b/chromium/net/third_party/quiche/src/spdy/core/spdy_framer.h
new file mode 100644
index 00000000000..e268994e89b
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/spdy/core/spdy_framer.h
@@ -0,0 +1,365 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_SPDY_CORE_SPDY_FRAMER_H_
+#define QUICHE_SPDY_CORE_SPDY_FRAMER_H_
+
+#include <stddef.h>
+
+#include <cstdint>
+#include <map>
+#include <memory>
+#include <utility>
+
+#include "net/third_party/quiche/src/spdy/core/hpack/hpack_encoder.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_alt_svc_wire_format.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_header_block.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_headers_handler_interface.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_protocol.h"
+#include "net/third_party/quiche/src/spdy/core/zero_copy_output_buffer.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_export.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_string.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_string_piece.h"
+
+namespace spdy {
+
+namespace test {
+
+class SpdyFramerPeer;
+class SpdyFramerTest_MultipleContinuationFramesWithIterator_Test;
+class SpdyFramerTest_PushPromiseFramesWithIterator_Test;
+
+} // namespace test
+
+class SPDY_EXPORT_PRIVATE SpdyFrameSequence {
+ public:
+ virtual ~SpdyFrameSequence() {}
+
+ // Serializes the next frame in the sequence to |output|. Returns the number
+ // of bytes written to |output|.
+ virtual size_t NextFrame(ZeroCopyOutputBuffer* output) = 0;
+
+ // Returns true iff there is at least one more frame in the sequence.
+ virtual bool HasNextFrame() const = 0;
+
+ // Get SpdyFrameIR of the frame to be serialized.
+ virtual const SpdyFrameIR& GetIR() const = 0;
+};
+
+class SPDY_EXPORT_PRIVATE SpdyFramer {
+ public:
+ enum CompressionOption {
+ ENABLE_COMPRESSION,
+ DISABLE_COMPRESSION,
+ };
+
+ // Create a SpdyFrameSequence to serialize |frame_ir|.
+ static std::unique_ptr<SpdyFrameSequence> CreateIterator(
+ SpdyFramer* framer,
+ std::unique_ptr<const SpdyFrameIR> frame_ir);
+
+ // Gets the serialized flags for the given |frame|.
+ static uint8_t GetSerializedFlags(const SpdyFrameIR& frame);
+
+ // Serialize a data frame.
+ static SpdySerializedFrame SerializeData(const SpdyDataIR& data_ir);
+ // Serializes the data frame header and optionally padding length fields,
+ // excluding actual data payload and padding.
+ static SpdySerializedFrame SerializeDataFrameHeaderWithPaddingLengthField(
+ const SpdyDataIR& data_ir);
+
+ // Serializes a WINDOW_UPDATE frame. The WINDOW_UPDATE
+ // frame is used to implement per stream flow control.
+ static SpdySerializedFrame SerializeWindowUpdate(
+ const SpdyWindowUpdateIR& window_update);
+
+ explicit SpdyFramer(CompressionOption option);
+
+ virtual ~SpdyFramer();
+
+ // Set debug callbacks to be called from the framer. The debug visitor is
+ // completely optional and need not be set in order for normal operation.
+ // If this is called multiple times, only the last visitor will be used.
+ void set_debug_visitor(SpdyFramerDebugVisitorInterface* debug_visitor);
+
+ SpdySerializedFrame SerializeRstStream(
+ const SpdyRstStreamIR& rst_stream) const;
+
+ // Serializes a SETTINGS frame. The SETTINGS frame is
+ // used to communicate name/value pairs relevant to the communication channel.
+ SpdySerializedFrame SerializeSettings(const SpdySettingsIR& settings) const;
+
+ // Serializes a PING frame. The unique_id is used to
+ // identify the ping request/response.
+ SpdySerializedFrame SerializePing(const SpdyPingIR& ping) const;
+
+ // Serializes a GOAWAY frame. The GOAWAY frame is used
+ // prior to the shutting down of the TCP connection, and includes the
+ // stream_id of the last stream the sender of the frame is willing to process
+ // to completion.
+ SpdySerializedFrame SerializeGoAway(const SpdyGoAwayIR& goaway) const;
+
+ // Serializes a HEADERS frame. The HEADERS frame is used
+ // for sending headers.
+ SpdySerializedFrame SerializeHeaders(const SpdyHeadersIR& headers);
+
+ // Serializes a PUSH_PROMISE frame. The PUSH_PROMISE frame is used
+ // to inform the client that it will be receiving an additional stream
+ // in response to the original request. The frame includes synthesized
+ // headers to explain the upcoming data.
+ SpdySerializedFrame SerializePushPromise(
+ const SpdyPushPromiseIR& push_promise);
+
+ // Serializes a CONTINUATION frame. The CONTINUATION frame is used
+ // to continue a sequence of header block fragments.
+ SpdySerializedFrame SerializeContinuation(
+ const SpdyContinuationIR& continuation) const;
+
+ // Serializes an ALTSVC frame. The ALTSVC frame advertises the
+ // availability of an alternative service to the client.
+ SpdySerializedFrame SerializeAltSvc(const SpdyAltSvcIR& altsvc);
+
+ // Serializes a PRIORITY frame. The PRIORITY frame advises a change in
+ // the relative priority of the given stream.
+ SpdySerializedFrame SerializePriority(const SpdyPriorityIR& priority) const;
+
+ // Serializes an unknown frame given a frame header and payload.
+ SpdySerializedFrame SerializeUnknown(const SpdyUnknownIR& unknown) const;
+
+ // Serialize a frame of unknown type.
+ SpdySerializedFrame SerializeFrame(const SpdyFrameIR& frame);
+
+ // Serialize a data frame.
+ bool SerializeData(const SpdyDataIR& data,
+ ZeroCopyOutputBuffer* output) const;
+
+ // Serializes the data frame header and optionally padding length fields,
+ // excluding actual data payload and padding.
+ bool SerializeDataFrameHeaderWithPaddingLengthField(
+ const SpdyDataIR& data,
+ ZeroCopyOutputBuffer* output) const;
+
+ bool SerializeRstStream(const SpdyRstStreamIR& rst_stream,
+ ZeroCopyOutputBuffer* output) const;
+
+ // Serializes a SETTINGS frame. The SETTINGS frame is
+ // used to communicate name/value pairs relevant to the communication channel.
+ bool SerializeSettings(const SpdySettingsIR& settings,
+ ZeroCopyOutputBuffer* output) const;
+
+ // Serializes a PING frame. The unique_id is used to
+ // identify the ping request/response.
+ bool SerializePing(const SpdyPingIR& ping,
+ ZeroCopyOutputBuffer* output) const;
+
+ // Serializes a GOAWAY frame. The GOAWAY frame is used
+ // prior to the shutting down of the TCP connection, and includes the
+ // stream_id of the last stream the sender of the frame is willing to process
+ // to completion.
+ bool SerializeGoAway(const SpdyGoAwayIR& goaway,
+ ZeroCopyOutputBuffer* output) const;
+
+ // Serializes a HEADERS frame. The HEADERS frame is used
+ // for sending headers.
+ bool SerializeHeaders(const SpdyHeadersIR& headers,
+ ZeroCopyOutputBuffer* output);
+
+ // Serializes a WINDOW_UPDATE frame. The WINDOW_UPDATE
+ // frame is used to implement per stream flow control.
+ bool SerializeWindowUpdate(const SpdyWindowUpdateIR& window_update,
+ ZeroCopyOutputBuffer* output) const;
+
+ // Serializes a PUSH_PROMISE frame. The PUSH_PROMISE frame is used
+ // to inform the client that it will be receiving an additional stream
+ // in response to the original request. The frame includes synthesized
+ // headers to explain the upcoming data.
+ bool SerializePushPromise(const SpdyPushPromiseIR& push_promise,
+ ZeroCopyOutputBuffer* output);
+
+ // Serializes a CONTINUATION frame. The CONTINUATION frame is used
+ // to continue a sequence of header block fragments.
+ bool SerializeContinuation(const SpdyContinuationIR& continuation,
+ ZeroCopyOutputBuffer* output) const;
+
+ // Serializes an ALTSVC frame. The ALTSVC frame advertises the
+ // availability of an alternative service to the client.
+ bool SerializeAltSvc(const SpdyAltSvcIR& altsvc,
+ ZeroCopyOutputBuffer* output);
+
+ // Serializes a PRIORITY frame. The PRIORITY frame advises a change in
+ // the relative priority of the given stream.
+ bool SerializePriority(const SpdyPriorityIR& priority,
+ ZeroCopyOutputBuffer* output) const;
+
+ // Serializes an unknown frame given a frame header and payload.
+ bool SerializeUnknown(const SpdyUnknownIR& unknown,
+ ZeroCopyOutputBuffer* output) const;
+
+ // Serialize a frame of unknown type.
+ size_t SerializeFrame(const SpdyFrameIR& frame, ZeroCopyOutputBuffer* output);
+
+ // Returns whether this SpdyFramer will compress header blocks using HPACK.
+ bool compression_enabled() const {
+ return compression_option_ == ENABLE_COMPRESSION;
+ }
+
+ void SetHpackIndexingPolicy(HpackEncoder::IndexingPolicy policy) {
+ GetHpackEncoder()->SetIndexingPolicy(std::move(policy));
+ }
+
+ // Updates the maximum size of the header encoder compression table.
+ void UpdateHeaderEncoderTableSize(uint32_t value);
+
+ // Returns the maximum size of the header encoder compression table.
+ size_t header_encoder_table_size() const;
+
+ void SetEncoderHeaderTableDebugVisitor(
+ std::unique_ptr<HpackHeaderTable::DebugVisitorInterface> visitor);
+
+ // Get (and lazily initialize) the HPACK encoder state.
+ HpackEncoder* GetHpackEncoder();
+
+ // Returns the estimate of dynamically allocated memory in bytes.
+ size_t EstimateMemoryUsage() const;
+
+ protected:
+ friend class test::SpdyFramerPeer;
+ friend class test::SpdyFramerTest_MultipleContinuationFramesWithIterator_Test;
+ friend class test::SpdyFramerTest_PushPromiseFramesWithIterator_Test;
+
+ // Iteratively converts a SpdyFrameIR into an appropriate sequence of Spdy
+ // frames.
+ // Example usage:
+ // std::unique_ptr<SpdyFrameSequence> it = CreateIterator(framer, frame_ir);
+ // while (it->HasNextFrame()) {
+ // if(it->NextFrame(output) == 0) {
+ // // Write failed;
+ // }
+ // }
+ class SPDY_EXPORT_PRIVATE SpdyFrameIterator : public SpdyFrameSequence {
+ public:
+ // Creates an iterator with the provided framer.
+ // Does not take ownership of |framer|.
+ // |framer| must outlive this instance.
+ explicit SpdyFrameIterator(SpdyFramer* framer);
+ ~SpdyFrameIterator() override;
+
+ // Serializes the next frame in the sequence to |output|. Returns the number
+ // of bytes written to |output|.
+ size_t NextFrame(ZeroCopyOutputBuffer* output) override;
+
+ // Returns true iff there is at least one more frame in the sequence.
+ bool HasNextFrame() const override;
+
+ // SpdyFrameIterator is neither copyable nor movable.
+ SpdyFrameIterator(const SpdyFrameIterator&) = delete;
+ SpdyFrameIterator& operator=(const SpdyFrameIterator&) = delete;
+
+ protected:
+ virtual size_t GetFrameSizeSansBlock() const = 0;
+ virtual bool SerializeGivenEncoding(const SpdyString& encoding,
+ ZeroCopyOutputBuffer* output) const = 0;
+
+ SpdyFramer* GetFramer() const { return framer_; }
+
+ void SetEncoder(const SpdyFrameWithHeaderBlockIR* ir) {
+ encoder_ =
+ framer_->GetHpackEncoder()->EncodeHeaderSet(ir->header_block());
+ }
+
+ bool has_next_frame() const { return has_next_frame_; }
+
+ private:
+ SpdyFramer* const framer_;
+ std::unique_ptr<HpackEncoder::ProgressiveEncoder> encoder_;
+ bool is_first_frame_;
+ bool has_next_frame_;
+ };
+
+ // Iteratively converts a SpdyHeadersIR (with a possibly huge
+ // SpdyHeaderBlock) into an appropriate sequence of SpdySerializedFrames, and
+ // write to the output.
+ class SPDY_EXPORT_PRIVATE SpdyHeaderFrameIterator : public SpdyFrameIterator {
+ public:
+ // Does not take ownership of |framer|. Take ownership of |headers_ir|.
+ SpdyHeaderFrameIterator(SpdyFramer* framer,
+ std::unique_ptr<const SpdyHeadersIR> headers_ir);
+
+ ~SpdyHeaderFrameIterator() override;
+
+ private:
+ const SpdyFrameIR& GetIR() const override;
+ size_t GetFrameSizeSansBlock() const override;
+ bool SerializeGivenEncoding(const SpdyString& encoding,
+ ZeroCopyOutputBuffer* output) const override;
+
+ const std::unique_ptr<const SpdyHeadersIR> headers_ir_;
+ };
+
+ // Iteratively converts a SpdyPushPromiseIR (with a possibly huge
+ // SpdyHeaderBlock) into an appropriate sequence of SpdySerializedFrames, and
+ // write to the output.
+ class SPDY_EXPORT_PRIVATE SpdyPushPromiseFrameIterator
+ : public SpdyFrameIterator {
+ public:
+ // Does not take ownership of |framer|. Take ownership of |push_promise_ir|.
+ SpdyPushPromiseFrameIterator(
+ SpdyFramer* framer,
+ std::unique_ptr<const SpdyPushPromiseIR> push_promise_ir);
+
+ ~SpdyPushPromiseFrameIterator() override;
+
+ private:
+ const SpdyFrameIR& GetIR() const override;
+ size_t GetFrameSizeSansBlock() const override;
+ bool SerializeGivenEncoding(const SpdyString& encoding,
+ ZeroCopyOutputBuffer* output) const override;
+
+ const std::unique_ptr<const SpdyPushPromiseIR> push_promise_ir_;
+ };
+
+ // Converts a SpdyFrameIR into one Spdy frame (a sequence of length 1), and
+ // write it to the output.
+ class SPDY_EXPORT_PRIVATE SpdyControlFrameIterator
+ : public SpdyFrameSequence {
+ public:
+ SpdyControlFrameIterator(SpdyFramer* framer,
+ std::unique_ptr<const SpdyFrameIR> frame_ir);
+ ~SpdyControlFrameIterator() override;
+
+ size_t NextFrame(ZeroCopyOutputBuffer* output) override;
+
+ bool HasNextFrame() const override;
+
+ const SpdyFrameIR& GetIR() const override;
+
+ private:
+ SpdyFramer* const framer_;
+ std::unique_ptr<const SpdyFrameIR> frame_ir_;
+ bool has_next_frame_ = true;
+ };
+
+ private:
+ void SerializeHeadersBuilderHelper(const SpdyHeadersIR& headers,
+ uint8_t* flags,
+ size_t* size,
+ SpdyString* hpack_encoding,
+ int* weight,
+ size_t* length_field);
+ void SerializePushPromiseBuilderHelper(const SpdyPushPromiseIR& push_promise,
+ uint8_t* flags,
+ SpdyString* hpack_encoding,
+ size_t* size);
+
+ std::unique_ptr<HpackEncoder> hpack_encoder_;
+
+ SpdyFramerDebugVisitorInterface* debug_visitor_;
+
+ // Determines whether HPACK compression is used.
+ const CompressionOption compression_option_;
+};
+
+} // namespace spdy
+
+#endif // QUICHE_SPDY_CORE_SPDY_FRAMER_H_
diff --git a/chromium/net/third_party/quiche/src/spdy/core/spdy_framer_test.cc b/chromium/net/third_party/quiche/src/spdy/core/spdy_framer_test.cc
new file mode 100644
index 00000000000..a08d7d78100
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/spdy/core/spdy_framer_test.cc
@@ -0,0 +1,4833 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/spdy/core/spdy_framer.h"
+
+#include <stdlib.h>
+
+#include <algorithm>
+#include <cstdint>
+#include <limits>
+#include <tuple>
+#include <vector>
+
+#include "base/logging.h"
+#include "base/macros.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/spdy/core/array_output_buffer.h"
+#include "net/third_party/quiche/src/spdy/core/hpack/hpack_constants.h"
+#include "net/third_party/quiche/src/spdy/core/mock_spdy_framer_visitor.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_bitmasks.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_frame_builder.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_frame_reader.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_protocol.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_test_utils.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_arraysize.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_flags.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_ptr_util.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_string.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_string_utils.h"
+
+using ::http2::Http2DecoderAdapter;
+using ::testing::_;
+
+namespace spdy {
+
+namespace test {
+
+namespace {
+
+const int64_t kSize = 1024 * 1024;
+char output_buffer[kSize] = "";
+
+// frame_list_char is used to hold frames to be compared with output_buffer.
+const int64_t buffer_size = 64 * 1024;
+char frame_list_char[buffer_size] = "";
+} // namespace
+
+class MockDebugVisitor : public SpdyFramerDebugVisitorInterface {
+ public:
+ MOCK_METHOD4(OnSendCompressedFrame,
+ void(SpdyStreamId stream_id,
+ SpdyFrameType type,
+ size_t payload_len,
+ size_t frame_len));
+
+ MOCK_METHOD3(OnReceiveCompressedFrame,
+ void(SpdyStreamId stream_id,
+ SpdyFrameType type,
+ size_t frame_len));
+};
+
+MATCHER_P(IsFrameUnionOf, frame_list, "") {
+ size_t size_verified = 0;
+ for (const auto& frame : *frame_list) {
+ if (arg.size() < size_verified + frame.size()) {
+ LOG(FATAL) << "Incremental header serialization should not lead to a "
+ << "higher total frame length than non-incremental method.";
+ return false;
+ }
+ if (memcmp(arg.data() + size_verified, frame.data(), frame.size())) {
+ CompareCharArraysWithHexError(
+ "Header serialization methods should be equivalent: ",
+ reinterpret_cast<unsigned char*>(arg.data() + size_verified),
+ frame.size(), reinterpret_cast<unsigned char*>(frame.data()),
+ frame.size());
+ return false;
+ }
+ size_verified += frame.size();
+ }
+ return size_verified == arg.size();
+}
+
+class SpdyFramerPeer {
+ public:
+ // TODO(dahollings): Remove these methods when deprecating non-incremental
+ // header serialization path.
+ static std::unique_ptr<SpdyHeadersIR> CloneSpdyHeadersIR(
+ const SpdyHeadersIR& headers) {
+ auto new_headers = SpdyMakeUnique<SpdyHeadersIR>(
+ headers.stream_id(), headers.header_block().Clone());
+ new_headers->set_fin(headers.fin());
+ new_headers->set_has_priority(headers.has_priority());
+ new_headers->set_weight(headers.weight());
+ new_headers->set_parent_stream_id(headers.parent_stream_id());
+ new_headers->set_exclusive(headers.exclusive());
+ if (headers.padded()) {
+ new_headers->set_padding_len(headers.padding_payload_len() + 1);
+ }
+ return new_headers;
+ }
+
+ static SpdySerializedFrame SerializeHeaders(SpdyFramer* framer,
+ const SpdyHeadersIR& headers) {
+ SpdySerializedFrame serialized_headers_old_version(
+ framer->SerializeHeaders(headers));
+ framer->hpack_encoder_.reset(nullptr);
+ auto* saved_debug_visitor = framer->debug_visitor_;
+ framer->debug_visitor_ = nullptr;
+
+ std::vector<SpdySerializedFrame> frame_list;
+ ArrayOutputBuffer frame_list_buffer(frame_list_char, buffer_size);
+ SpdyFramer::SpdyHeaderFrameIterator it(framer, CloneSpdyHeadersIR(headers));
+ while (it.HasNextFrame()) {
+ size_t size_before = frame_list_buffer.Size();
+ EXPECT_GT(it.NextFrame(&frame_list_buffer), 0u);
+ frame_list.emplace_back(
+ SpdySerializedFrame(frame_list_buffer.Begin() + size_before,
+ frame_list_buffer.Size() - size_before, false));
+ }
+ framer->debug_visitor_ = saved_debug_visitor;
+
+ EXPECT_THAT(serialized_headers_old_version, IsFrameUnionOf(&frame_list));
+ return serialized_headers_old_version;
+ }
+
+ static SpdySerializedFrame SerializeHeaders(SpdyFramer* framer,
+ const SpdyHeadersIR& headers,
+ ArrayOutputBuffer* output) {
+ if (output == nullptr) {
+ return SerializeHeaders(framer, headers);
+ }
+ output->Reset();
+ EXPECT_TRUE(framer->SerializeHeaders(headers, output));
+ SpdySerializedFrame serialized_headers_old_version(output->Begin(),
+ output->Size(), false);
+ framer->hpack_encoder_.reset(nullptr);
+ auto* saved_debug_visitor = framer->debug_visitor_;
+ framer->debug_visitor_ = nullptr;
+
+ std::vector<SpdySerializedFrame> frame_list;
+ ArrayOutputBuffer frame_list_buffer(frame_list_char, buffer_size);
+ SpdyFramer::SpdyHeaderFrameIterator it(framer, CloneSpdyHeadersIR(headers));
+ while (it.HasNextFrame()) {
+ size_t size_before = frame_list_buffer.Size();
+ EXPECT_GT(it.NextFrame(&frame_list_buffer), 0u);
+ frame_list.emplace_back(
+ SpdySerializedFrame(frame_list_buffer.Begin() + size_before,
+ frame_list_buffer.Size() - size_before, false));
+ }
+ framer->debug_visitor_ = saved_debug_visitor;
+
+ EXPECT_THAT(serialized_headers_old_version, IsFrameUnionOf(&frame_list));
+ return serialized_headers_old_version;
+ }
+
+ static std::unique_ptr<SpdyPushPromiseIR> CloneSpdyPushPromiseIR(
+ const SpdyPushPromiseIR& push_promise) {
+ auto new_push_promise = SpdyMakeUnique<SpdyPushPromiseIR>(
+ push_promise.stream_id(), push_promise.promised_stream_id(),
+ push_promise.header_block().Clone());
+ new_push_promise->set_fin(push_promise.fin());
+ if (push_promise.padded()) {
+ new_push_promise->set_padding_len(push_promise.padding_payload_len() + 1);
+ }
+ return new_push_promise;
+ }
+
+ static SpdySerializedFrame SerializePushPromise(
+ SpdyFramer* framer,
+ const SpdyPushPromiseIR& push_promise) {
+ SpdySerializedFrame serialized_headers_old_version =
+ framer->SerializePushPromise(push_promise);
+ framer->hpack_encoder_.reset(nullptr);
+ auto* saved_debug_visitor = framer->debug_visitor_;
+ framer->debug_visitor_ = nullptr;
+
+ std::vector<SpdySerializedFrame> frame_list;
+ ArrayOutputBuffer frame_list_buffer(frame_list_char, buffer_size);
+ frame_list_buffer.Reset();
+ SpdyFramer::SpdyPushPromiseFrameIterator it(
+ framer, CloneSpdyPushPromiseIR(push_promise));
+ while (it.HasNextFrame()) {
+ size_t size_before = frame_list_buffer.Size();
+ EXPECT_GT(it.NextFrame(&frame_list_buffer), 0u);
+ frame_list.emplace_back(
+ SpdySerializedFrame(frame_list_buffer.Begin() + size_before,
+ frame_list_buffer.Size() - size_before, false));
+ }
+ framer->debug_visitor_ = saved_debug_visitor;
+
+ EXPECT_THAT(serialized_headers_old_version, IsFrameUnionOf(&frame_list));
+ return serialized_headers_old_version;
+ }
+
+ static SpdySerializedFrame SerializePushPromise(
+ SpdyFramer* framer,
+ const SpdyPushPromiseIR& push_promise,
+ ArrayOutputBuffer* output) {
+ if (output == nullptr) {
+ return SerializePushPromise(framer, push_promise);
+ }
+ output->Reset();
+ EXPECT_TRUE(framer->SerializePushPromise(push_promise, output));
+ SpdySerializedFrame serialized_headers_old_version(output->Begin(),
+ output->Size(), false);
+ framer->hpack_encoder_.reset(nullptr);
+ auto* saved_debug_visitor = framer->debug_visitor_;
+ framer->debug_visitor_ = nullptr;
+
+ std::vector<SpdySerializedFrame> frame_list;
+ ArrayOutputBuffer frame_list_buffer(frame_list_char, buffer_size);
+ frame_list_buffer.Reset();
+ SpdyFramer::SpdyPushPromiseFrameIterator it(
+ framer, CloneSpdyPushPromiseIR(push_promise));
+ while (it.HasNextFrame()) {
+ size_t size_before = frame_list_buffer.Size();
+ EXPECT_GT(it.NextFrame(&frame_list_buffer), 0u);
+ frame_list.emplace_back(
+ SpdySerializedFrame(frame_list_buffer.Begin() + size_before,
+ frame_list_buffer.Size() - size_before, false));
+ }
+ framer->debug_visitor_ = saved_debug_visitor;
+
+ EXPECT_THAT(serialized_headers_old_version, IsFrameUnionOf(&frame_list));
+ return serialized_headers_old_version;
+ }
+};
+
+class TestSpdyVisitor : public SpdyFramerVisitorInterface,
+ public SpdyFramerDebugVisitorInterface {
+ public:
+ // This is larger than our max frame size because header blocks that
+ // are too long can spill over into CONTINUATION frames.
+ static const size_t kDefaultHeaderBufferSize = 16 * 1024 * 1024;
+
+ explicit TestSpdyVisitor(SpdyFramer::CompressionOption option)
+ : framer_(option),
+ error_count_(0),
+ headers_frame_count_(0),
+ push_promise_frame_count_(0),
+ goaway_count_(0),
+ setting_count_(0),
+ settings_ack_sent_(0),
+ settings_ack_received_(0),
+ continuation_count_(0),
+ altsvc_count_(0),
+ priority_count_(0),
+ on_unknown_frame_result_(false),
+ last_window_update_stream_(0),
+ last_window_update_delta_(0),
+ last_push_promise_stream_(0),
+ last_push_promise_promised_stream_(0),
+ data_bytes_(0),
+ fin_frame_count_(0),
+ fin_flag_count_(0),
+ end_of_stream_count_(0),
+ control_frame_header_data_count_(0),
+ zero_length_control_frame_header_data_count_(0),
+ data_frame_count_(0),
+ last_payload_len_(0),
+ last_frame_len_(0),
+ header_buffer_(new char[kDefaultHeaderBufferSize]),
+ header_buffer_length_(0),
+ header_buffer_size_(kDefaultHeaderBufferSize),
+ header_stream_id_(static_cast<SpdyStreamId>(-1)),
+ header_control_type_(SpdyFrameType::DATA),
+ header_buffer_valid_(false) {}
+
+ void OnError(Http2DecoderAdapter::SpdyFramerError error) override {
+ VLOG(1) << "SpdyFramer Error: "
+ << Http2DecoderAdapter::SpdyFramerErrorToString(error);
+ ++error_count_;
+ }
+
+ void OnDataFrameHeader(SpdyStreamId stream_id,
+ size_t length,
+ bool fin) override {
+ VLOG(1) << "OnDataFrameHeader(" << stream_id << ", " << length << ", "
+ << fin << ")";
+ ++data_frame_count_;
+ header_stream_id_ = stream_id;
+ }
+
+ void OnStreamFrameData(SpdyStreamId stream_id,
+ const char* data,
+ size_t len) override {
+ VLOG(1) << "OnStreamFrameData(" << stream_id << ", data, " << len << ", "
+ << ") data:\n"
+ << SpdyHexDump(SpdyStringPiece(data, len));
+ EXPECT_EQ(header_stream_id_, stream_id);
+
+ data_bytes_ += len;
+ }
+
+ void OnStreamEnd(SpdyStreamId stream_id) override {
+ VLOG(1) << "OnStreamEnd(" << stream_id << ")";
+ EXPECT_EQ(header_stream_id_, stream_id);
+ ++end_of_stream_count_;
+ }
+
+ void OnStreamPadLength(SpdyStreamId stream_id, size_t value) override {
+ VLOG(1) << "OnStreamPadding(" << stream_id << ", " << value << ")\n";
+ EXPECT_EQ(header_stream_id_, stream_id);
+ // Count the padding length field byte against total data bytes.
+ data_bytes_ += 1;
+ }
+
+ void OnStreamPadding(SpdyStreamId stream_id, size_t len) override {
+ VLOG(1) << "OnStreamPadding(" << stream_id << ", " << len << ")\n";
+ EXPECT_EQ(header_stream_id_, stream_id);
+ data_bytes_ += len;
+ }
+
+ SpdyHeadersHandlerInterface* OnHeaderFrameStart(
+ SpdyStreamId stream_id) override {
+ if (headers_handler_ == nullptr) {
+ headers_handler_ = SpdyMakeUnique<TestHeadersHandler>();
+ }
+ return headers_handler_.get();
+ }
+
+ void OnHeaderFrameEnd(SpdyStreamId stream_id) override {
+ CHECK(headers_handler_ != nullptr);
+ headers_ = headers_handler_->decoded_block().Clone();
+ header_bytes_received_ = headers_handler_->header_bytes_parsed();
+ headers_handler_.reset();
+ }
+
+ void OnRstStream(SpdyStreamId stream_id, SpdyErrorCode error_code) override {
+ VLOG(1) << "OnRstStream(" << stream_id << ", " << error_code << ")";
+ ++fin_frame_count_;
+ }
+
+ void OnSetting(SpdySettingsId id, uint32_t value) override {
+ VLOG(1) << "OnSetting(" << id << ", " << std::hex << value << ")";
+ ++setting_count_;
+ }
+
+ void OnSettingsAck() override {
+ VLOG(1) << "OnSettingsAck";
+ ++settings_ack_received_;
+ }
+
+ void OnSettingsEnd() override {
+ VLOG(1) << "OnSettingsEnd";
+ ++settings_ack_sent_;
+ }
+
+ void OnPing(SpdyPingId unique_id, bool is_ack) override {
+ LOG(DFATAL) << "OnPing(" << unique_id << ", " << (is_ack ? 1 : 0) << ")";
+ }
+
+ void OnGoAway(SpdyStreamId last_accepted_stream_id,
+ SpdyErrorCode error_code) override {
+ VLOG(1) << "OnGoAway(" << last_accepted_stream_id << ", " << error_code
+ << ")";
+ ++goaway_count_;
+ }
+
+ void OnHeaders(SpdyStreamId stream_id,
+ bool has_priority,
+ int weight,
+ SpdyStreamId parent_stream_id,
+ bool exclusive,
+ bool fin,
+ bool end) override {
+ VLOG(1) << "OnHeaders(" << stream_id << ", " << has_priority << ", "
+ << weight << ", " << parent_stream_id << ", " << exclusive << ", "
+ << fin << ", " << end << ")";
+ ++headers_frame_count_;
+ InitHeaderStreaming(SpdyFrameType::HEADERS, stream_id);
+ if (fin) {
+ ++fin_flag_count_;
+ }
+ header_has_priority_ = has_priority;
+ header_parent_stream_id_ = parent_stream_id;
+ header_exclusive_ = exclusive;
+ }
+
+ void OnWindowUpdate(SpdyStreamId stream_id, int delta_window_size) override {
+ VLOG(1) << "OnWindowUpdate(" << stream_id << ", " << delta_window_size
+ << ")";
+ last_window_update_stream_ = stream_id;
+ last_window_update_delta_ = delta_window_size;
+ }
+
+ void OnPushPromise(SpdyStreamId stream_id,
+ SpdyStreamId promised_stream_id,
+ bool end) override {
+ VLOG(1) << "OnPushPromise(" << stream_id << ", " << promised_stream_id
+ << ", " << end << ")";
+ ++push_promise_frame_count_;
+ InitHeaderStreaming(SpdyFrameType::PUSH_PROMISE, stream_id);
+ last_push_promise_stream_ = stream_id;
+ last_push_promise_promised_stream_ = promised_stream_id;
+ }
+
+ void OnContinuation(SpdyStreamId stream_id, bool end) override {
+ VLOG(1) << "OnContinuation(" << stream_id << ", " << end << ")";
+ ++continuation_count_;
+ }
+
+ void OnAltSvc(SpdyStreamId stream_id,
+ SpdyStringPiece origin,
+ const SpdyAltSvcWireFormat::AlternativeServiceVector&
+ altsvc_vector) override {
+ VLOG(1) << "OnAltSvc(" << stream_id << ", \"" << origin
+ << "\", altsvc_vector)";
+ test_altsvc_ir_ = SpdyMakeUnique<SpdyAltSvcIR>(stream_id);
+ if (origin.length() > 0) {
+ test_altsvc_ir_->set_origin(SpdyString(origin));
+ }
+ for (const auto& altsvc : altsvc_vector) {
+ test_altsvc_ir_->add_altsvc(altsvc);
+ }
+ ++altsvc_count_;
+ }
+
+ void OnPriority(SpdyStreamId stream_id,
+ SpdyStreamId parent_stream_id,
+ int weight,
+ bool exclusive) override {
+ VLOG(1) << "OnPriority(" << stream_id << ", " << parent_stream_id << ", "
+ << weight << ", " << (exclusive ? 1 : 0) << ")";
+ ++priority_count_;
+ }
+
+ bool OnUnknownFrame(SpdyStreamId stream_id, uint8_t frame_type) override {
+ VLOG(1) << "OnUnknownFrame(" << stream_id << ", " << frame_type << ")";
+ return on_unknown_frame_result_;
+ }
+
+ void OnSendCompressedFrame(SpdyStreamId stream_id,
+ SpdyFrameType type,
+ size_t payload_len,
+ size_t frame_len) override {
+ VLOG(1) << "OnSendCompressedFrame(" << stream_id << ", " << type << ", "
+ << payload_len << ", " << frame_len << ")";
+ last_payload_len_ = payload_len;
+ last_frame_len_ = frame_len;
+ }
+
+ void OnReceiveCompressedFrame(SpdyStreamId stream_id,
+ SpdyFrameType type,
+ size_t frame_len) override {
+ VLOG(1) << "OnReceiveCompressedFrame(" << stream_id << ", " << type << ", "
+ << frame_len << ")";
+ last_frame_len_ = frame_len;
+ }
+
+ // Convenience function which runs a framer simulation with particular input.
+ void SimulateInFramer(const unsigned char* input, size_t size) {
+ deframer_.set_visitor(this);
+ size_t input_remaining = size;
+ const char* input_ptr = reinterpret_cast<const char*>(input);
+ while (input_remaining > 0 && deframer_.spdy_framer_error() ==
+ Http2DecoderAdapter::SPDY_NO_ERROR) {
+ // To make the tests more interesting, we feed random (and small) chunks
+ // into the framer. This simulates getting strange-sized reads from
+ // the socket.
+ const size_t kMaxReadSize = 32;
+ size_t bytes_read =
+ (rand() % std::min(input_remaining, kMaxReadSize)) + 1;
+ size_t bytes_processed = deframer_.ProcessInput(input_ptr, bytes_read);
+ input_remaining -= bytes_processed;
+ input_ptr += bytes_processed;
+ }
+ }
+
+ void InitHeaderStreaming(SpdyFrameType header_control_type,
+ SpdyStreamId stream_id) {
+ if (!IsDefinedFrameType(SerializeFrameType(header_control_type))) {
+ DLOG(FATAL) << "Attempted to init header streaming with "
+ << "invalid control frame type: " << header_control_type;
+ }
+ memset(header_buffer_.get(), 0, header_buffer_size_);
+ header_buffer_length_ = 0;
+ header_stream_id_ = stream_id;
+ header_control_type_ = header_control_type;
+ header_buffer_valid_ = true;
+ }
+
+ void set_extension_visitor(ExtensionVisitorInterface* extension) {
+ deframer_.set_extension_visitor(extension);
+ }
+
+ // Override the default buffer size (16K). Call before using the framer!
+ void set_header_buffer_size(size_t header_buffer_size) {
+ header_buffer_size_ = header_buffer_size;
+ header_buffer_.reset(new char[header_buffer_size]);
+ }
+
+ SpdyFramer framer_;
+ Http2DecoderAdapter deframer_;
+
+ // Counters from the visitor callbacks.
+ int error_count_;
+ int headers_frame_count_;
+ int push_promise_frame_count_;
+ int goaway_count_;
+ int setting_count_;
+ int settings_ack_sent_;
+ int settings_ack_received_;
+ int continuation_count_;
+ int altsvc_count_;
+ int priority_count_;
+ std::unique_ptr<SpdyAltSvcIR> test_altsvc_ir_;
+ bool on_unknown_frame_result_;
+ SpdyStreamId last_window_update_stream_;
+ int last_window_update_delta_;
+ SpdyStreamId last_push_promise_stream_;
+ SpdyStreamId last_push_promise_promised_stream_;
+ int data_bytes_;
+ int fin_frame_count_; // The count of RST_STREAM type frames received.
+ int fin_flag_count_; // The count of frames with the FIN flag set.
+ int end_of_stream_count_; // The count of zero-length data frames.
+ int control_frame_header_data_count_; // The count of chunks received.
+ // The count of zero-length control frame header data chunks received.
+ int zero_length_control_frame_header_data_count_;
+ int data_frame_count_;
+ size_t last_payload_len_;
+ size_t last_frame_len_;
+
+ // Header block streaming state:
+ std::unique_ptr<char[]> header_buffer_;
+ size_t header_buffer_length_;
+ size_t header_buffer_size_;
+ size_t header_bytes_received_;
+ SpdyStreamId header_stream_id_;
+ SpdyFrameType header_control_type_;
+ bool header_buffer_valid_;
+ std::unique_ptr<TestHeadersHandler> headers_handler_;
+ SpdyHeaderBlock headers_;
+ bool header_has_priority_;
+ SpdyStreamId header_parent_stream_id_;
+ bool header_exclusive_;
+};
+
+class TestExtension : public ExtensionVisitorInterface {
+ public:
+ void OnSetting(SpdySettingsId id, uint32_t value) override {
+ settings_received_.push_back({id, value});
+ }
+
+ // Called when non-standard frames are received.
+ bool OnFrameHeader(SpdyStreamId stream_id,
+ size_t length,
+ uint8_t type,
+ uint8_t flags) override {
+ stream_id_ = stream_id;
+ length_ = length;
+ type_ = type;
+ flags_ = flags;
+ return true;
+ }
+
+ // The payload for a single frame may be delivered as multiple calls to
+ // OnFramePayload.
+ void OnFramePayload(const char* data, size_t len) override {
+ payload_.append(data, len);
+ }
+
+ std::vector<std::pair<SpdySettingsId, uint32_t>> settings_received_;
+ SpdyStreamId stream_id_ = 0;
+ size_t length_ = 0;
+ uint8_t type_ = 0;
+ uint8_t flags_ = 0;
+ SpdyString payload_;
+};
+
+// Exposes SpdyUnknownIR::set_length() for testing purposes.
+class TestSpdyUnknownIR : public SpdyUnknownIR {
+ public:
+ using SpdyUnknownIR::set_length;
+ using SpdyUnknownIR::SpdyUnknownIR;
+};
+
+enum Output { USE, NOT_USE };
+
+class SpdyFramerTest : public ::testing::TestWithParam<Output> {
+ public:
+ SpdyFramerTest()
+ : output_(output_buffer, kSize),
+ framer_(SpdyFramer::ENABLE_COMPRESSION) {}
+
+ protected:
+ void SetUp() override {
+ switch (GetParam()) {
+ case USE:
+ use_output_ = true;
+ break;
+ case NOT_USE:
+ // TODO(yasong): remove this case after
+ // gfe2_reloadable_flag_write_queue_zero_copy_buffer deprecates.
+ use_output_ = false;
+ break;
+ }
+ }
+
+ void CompareFrame(const SpdyString& description,
+ const SpdySerializedFrame& actual_frame,
+ const unsigned char* expected,
+ const int expected_len) {
+ const unsigned char* actual =
+ reinterpret_cast<const unsigned char*>(actual_frame.data());
+ CompareCharArraysWithHexError(description, actual, actual_frame.size(),
+ expected, expected_len);
+ }
+
+ bool use_output_ = false;
+ ArrayOutputBuffer output_;
+ SpdyFramer framer_;
+ Http2DecoderAdapter deframer_;
+};
+
+INSTANTIATE_TEST_CASE_P(SpdyFramerTests,
+ SpdyFramerTest,
+ ::testing::Values(USE, NOT_USE));
+
+// Test that we can encode and decode a SpdyHeaderBlock in serialized form.
+TEST_P(SpdyFramerTest, HeaderBlockInBuffer) {
+ SpdyFramer framer(SpdyFramer::DISABLE_COMPRESSION);
+
+ // Encode the header block into a Headers frame.
+ SpdyHeadersIR headers(/* stream_id = */ 1);
+ headers.SetHeader("alpha", "beta");
+ headers.SetHeader("gamma", "charlie");
+ headers.SetHeader("cookie", "key1=value1; key2=value2");
+ SpdySerializedFrame frame(
+ SpdyFramerPeer::SerializeHeaders(&framer, headers, &output_));
+
+ TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION);
+ visitor.SimulateInFramer(reinterpret_cast<unsigned char*>(frame.data()),
+ frame.size());
+
+ EXPECT_EQ(0, visitor.zero_length_control_frame_header_data_count_);
+ EXPECT_EQ(headers.header_block(), visitor.headers_);
+}
+
+// Test that if there's not a full frame, we fail to parse it.
+TEST_P(SpdyFramerTest, UndersizedHeaderBlockInBuffer) {
+ SpdyFramer framer(SpdyFramer::DISABLE_COMPRESSION);
+
+ // Encode the header block into a Headers frame.
+ SpdyHeadersIR headers(/* stream_id = */ 1);
+ headers.SetHeader("alpha", "beta");
+ headers.SetHeader("gamma", "charlie");
+ SpdySerializedFrame frame(
+ SpdyFramerPeer::SerializeHeaders(&framer, headers, &output_));
+
+ TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION);
+ visitor.SimulateInFramer(reinterpret_cast<unsigned char*>(frame.data()),
+ frame.size() - 2);
+
+ EXPECT_EQ(0, visitor.zero_length_control_frame_header_data_count_);
+ EXPECT_THAT(visitor.headers_, testing::IsEmpty());
+}
+
+// Test that we can encode and decode stream dependency values in a header
+// frame.
+TEST_P(SpdyFramerTest, HeaderStreamDependencyValues) {
+ SpdyFramer framer(SpdyFramer::DISABLE_COMPRESSION);
+
+ const SpdyStreamId parent_stream_id_test_array[] = {0, 3};
+ for (SpdyStreamId parent_stream_id : parent_stream_id_test_array) {
+ const bool exclusive_test_array[] = {true, false};
+ for (bool exclusive : exclusive_test_array) {
+ SpdyHeadersIR headers(1);
+ headers.set_has_priority(true);
+ headers.set_parent_stream_id(parent_stream_id);
+ headers.set_exclusive(exclusive);
+ SpdySerializedFrame frame(
+ SpdyFramerPeer::SerializeHeaders(&framer, headers, &output_));
+
+ TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION);
+ visitor.SimulateInFramer(reinterpret_cast<unsigned char*>(frame.data()),
+ frame.size());
+
+ EXPECT_TRUE(visitor.header_has_priority_);
+ EXPECT_EQ(parent_stream_id, visitor.header_parent_stream_id_);
+ EXPECT_EQ(exclusive, visitor.header_exclusive_);
+ }
+ }
+}
+
+// Test that if we receive a frame with payload length field at the
+// advertised max size, we do not set an error in ProcessInput.
+TEST_P(SpdyFramerTest, AcceptMaxFrameSizeSetting) {
+ testing::StrictMock<test::MockSpdyFramerVisitor> visitor;
+ deframer_.set_visitor(&visitor);
+
+ // DATA frame with maximum allowed payload length.
+ unsigned char kH2FrameData[] = {
+ 0x00, 0x40, 0x00, // Length: 2^14
+ 0x00, // Type: HEADERS
+ 0x00, // Flags: None
+ 0x00, 0x00, 0x00, 0x01, // Stream: 1
+ 0x00, 0x00, 0x00, 0x00, // Junk payload
+ };
+
+ SpdySerializedFrame frame(reinterpret_cast<char*>(kH2FrameData),
+ sizeof(kH2FrameData), false);
+
+ EXPECT_CALL(visitor, OnDataFrameHeader(1, 1 << 14, false));
+ EXPECT_CALL(visitor, OnStreamFrameData(1, _, 4));
+ deframer_.ProcessInput(frame.data(), frame.size());
+ EXPECT_FALSE(deframer_.HasError());
+}
+
+// Test that if we receive a frame with payload length larger than the
+// advertised max size, we set an error of SPDY_INVALID_CONTROL_FRAME_SIZE.
+TEST_P(SpdyFramerTest, ExceedMaxFrameSizeSetting) {
+ testing::StrictMock<test::MockSpdyFramerVisitor> visitor;
+ deframer_.set_visitor(&visitor);
+
+ // DATA frame with too large payload length.
+ unsigned char kH2FrameData[] = {
+ 0x00, 0x40, 0x01, // Length: 2^14 + 1
+ 0x00, // Type: HEADERS
+ 0x00, // Flags: None
+ 0x00, 0x00, 0x00, 0x01, // Stream: 1
+ 0x00, 0x00, 0x00, 0x00, // Junk payload
+ };
+
+ SpdySerializedFrame frame(reinterpret_cast<char*>(kH2FrameData),
+ sizeof(kH2FrameData), false);
+
+ EXPECT_CALL(visitor, OnError(Http2DecoderAdapter::SPDY_OVERSIZED_PAYLOAD));
+ deframer_.ProcessInput(frame.data(), frame.size());
+ EXPECT_TRUE(deframer_.HasError());
+ EXPECT_EQ(Http2DecoderAdapter::SPDY_OVERSIZED_PAYLOAD,
+ deframer_.spdy_framer_error())
+ << Http2DecoderAdapter::SpdyFramerErrorToString(
+ deframer_.spdy_framer_error());
+}
+
+// Test that if we receive a DATA frame with padding length larger than the
+// payload length, we set an error of SPDY_INVALID_PADDING
+TEST_P(SpdyFramerTest, OversizedDataPaddingError) {
+ testing::StrictMock<test::MockSpdyFramerVisitor> visitor;
+ deframer_.set_visitor(&visitor);
+
+ // DATA frame with invalid padding length.
+ // |kH2FrameData| has to be |unsigned char|, because Chromium on Windows uses
+ // MSVC, where |char| is signed by default, which would not compile because of
+ // the element exceeding 127.
+ unsigned char kH2FrameData[] = {
+ 0x00, 0x00, 0x05, // Length: 5
+ 0x00, // Type: DATA
+ 0x09, // Flags: END_STREAM|PADDED
+ 0x00, 0x00, 0x00, 0x01, // Stream: 1
+ 0xff, // PadLen: 255 trailing bytes (Too Long)
+ 0x00, 0x00, 0x00, 0x00, // Padding
+ };
+
+ SpdySerializedFrame frame(reinterpret_cast<char*>(kH2FrameData),
+ sizeof(kH2FrameData), false);
+
+ {
+ testing::InSequence seq;
+ EXPECT_CALL(visitor, OnDataFrameHeader(1, 5, 1));
+ EXPECT_CALL(visitor, OnStreamPadding(1, 1));
+ EXPECT_CALL(visitor, OnError(Http2DecoderAdapter::SPDY_INVALID_PADDING));
+ }
+ EXPECT_GT(frame.size(), deframer_.ProcessInput(frame.data(), frame.size()));
+ EXPECT_TRUE(deframer_.HasError());
+ EXPECT_EQ(Http2DecoderAdapter::SPDY_INVALID_PADDING,
+ deframer_.spdy_framer_error())
+ << Http2DecoderAdapter::SpdyFramerErrorToString(
+ deframer_.spdy_framer_error());
+}
+
+// Test that if we receive a DATA frame with padding length not larger than the
+// payload length, we do not set an error of SPDY_INVALID_PADDING
+TEST_P(SpdyFramerTest, CorrectlySizedDataPaddingNoError) {
+ testing::StrictMock<test::MockSpdyFramerVisitor> visitor;
+
+ deframer_.set_visitor(&visitor);
+
+ // DATA frame with valid Padding length
+ char kH2FrameData[] = {
+ 0x00, 0x00, 0x05, // Length: 5
+ 0x00, // Type: DATA
+ 0x08, // Flags: PADDED
+ 0x00, 0x00, 0x00, 0x01, // Stream: 1
+ 0x04, // PadLen: 4 trailing bytes
+ 0x00, 0x00, 0x00, 0x00, // Padding
+ };
+
+ SpdySerializedFrame frame(kH2FrameData, sizeof(kH2FrameData), false);
+
+ {
+ testing::InSequence seq;
+ EXPECT_CALL(visitor, OnDataFrameHeader(1, 5, false));
+ EXPECT_CALL(visitor, OnStreamPadLength(1, 4));
+ EXPECT_CALL(visitor, OnError(_)).Times(0);
+ // Note that OnStreamFrameData(1, _, 1)) is never called
+ // since there is no data, only padding
+ EXPECT_CALL(visitor, OnStreamPadding(1, 4));
+ }
+
+ EXPECT_EQ(frame.size(), deframer_.ProcessInput(frame.data(), frame.size()));
+ EXPECT_FALSE(deframer_.HasError());
+ EXPECT_EQ(Http2DecoderAdapter::SPDY_NO_ERROR, deframer_.spdy_framer_error())
+ << Http2DecoderAdapter::SpdyFramerErrorToString(
+ deframer_.spdy_framer_error());
+}
+
+// Test that if we receive a HEADERS frame with padding length larger than the
+// payload length, we set an error of SPDY_INVALID_PADDING
+TEST_P(SpdyFramerTest, OversizedHeadersPaddingError) {
+ testing::StrictMock<test::MockSpdyFramerVisitor> visitor;
+
+ deframer_.set_visitor(&visitor);
+
+ // HEADERS frame with invalid padding length.
+ // |kH2FrameData| has to be |unsigned char|, because Chromium on Windows uses
+ // MSVC, where |char| is signed by default, which would not compile because of
+ // the element exceeding 127.
+ unsigned char kH2FrameData[] = {
+ 0x00, 0x00, 0x05, // Length: 5
+ 0x01, // Type: HEADERS
+ 0x08, // Flags: PADDED
+ 0x00, 0x00, 0x00, 0x01, // Stream: 1
+ 0xff, // PadLen: 255 trailing bytes (Too Long)
+ 0x00, 0x00, 0x00, 0x00, // Padding
+ };
+
+ SpdySerializedFrame frame(reinterpret_cast<char*>(kH2FrameData),
+ sizeof(kH2FrameData), false);
+
+ EXPECT_CALL(visitor, OnHeaders(1, false, 0, 0, false, false, false));
+ EXPECT_CALL(visitor, OnHeaderFrameStart(1)).Times(1);
+ EXPECT_CALL(visitor, OnError(Http2DecoderAdapter::SPDY_INVALID_PADDING));
+ EXPECT_EQ(frame.size(), deframer_.ProcessInput(frame.data(), frame.size()));
+ EXPECT_TRUE(deframer_.HasError());
+ EXPECT_EQ(Http2DecoderAdapter::SPDY_INVALID_PADDING,
+ deframer_.spdy_framer_error())
+ << Http2DecoderAdapter::SpdyFramerErrorToString(
+ deframer_.spdy_framer_error());
+}
+
+// Test that if we receive a HEADERS frame with padding length not larger
+// than the payload length, we do not set an error of SPDY_INVALID_PADDING
+TEST_P(SpdyFramerTest, CorrectlySizedHeadersPaddingNoError) {
+ testing::StrictMock<test::MockSpdyFramerVisitor> visitor;
+
+ deframer_.set_visitor(&visitor);
+
+ // HEADERS frame with invalid Padding length
+ char kH2FrameData[] = {
+ 0x00, 0x00, 0x05, // Length: 5
+ 0x01, // Type: HEADERS
+ 0x08, // Flags: PADDED
+ 0x00, 0x00, 0x00, 0x01, // Stream: 1
+ 0x04, // PadLen: 4 trailing bytes
+ 0x00, 0x00, 0x00, 0x00, // Padding
+ };
+
+ SpdySerializedFrame frame(kH2FrameData, sizeof(kH2FrameData), false);
+
+ EXPECT_CALL(visitor, OnHeaders(1, false, 0, 0, false, false, false));
+ EXPECT_CALL(visitor, OnHeaderFrameStart(1)).Times(1);
+
+ EXPECT_EQ(frame.size(), deframer_.ProcessInput(frame.data(), frame.size()));
+ EXPECT_FALSE(deframer_.HasError());
+ EXPECT_EQ(Http2DecoderAdapter::SPDY_NO_ERROR, deframer_.spdy_framer_error())
+ << Http2DecoderAdapter::SpdyFramerErrorToString(
+ deframer_.spdy_framer_error());
+}
+
+// Test that if we receive a DATA with stream ID zero, we signal an error
+// (but don't crash).
+TEST_P(SpdyFramerTest, DataWithStreamIdZero) {
+ testing::StrictMock<test::MockSpdyFramerVisitor> visitor;
+
+ deframer_.set_visitor(&visitor);
+
+ const char bytes[] = "hello";
+ SpdyDataIR data_ir(/* stream_id = */ 0, bytes);
+ SpdySerializedFrame frame(framer_.SerializeData(data_ir));
+
+ // We shouldn't have to read the whole frame before we signal an error.
+ EXPECT_CALL(visitor, OnError(Http2DecoderAdapter::SPDY_INVALID_STREAM_ID));
+ EXPECT_GT(frame.size(), deframer_.ProcessInput(frame.data(), frame.size()));
+ EXPECT_TRUE(deframer_.HasError());
+ EXPECT_EQ(Http2DecoderAdapter::SPDY_INVALID_STREAM_ID,
+ deframer_.spdy_framer_error())
+ << Http2DecoderAdapter::SpdyFramerErrorToString(
+ deframer_.spdy_framer_error());
+}
+
+// Test that if we receive a HEADERS with stream ID zero, we signal an error
+// (but don't crash).
+TEST_P(SpdyFramerTest, HeadersWithStreamIdZero) {
+ testing::StrictMock<test::MockSpdyFramerVisitor> visitor;
+
+ deframer_.set_visitor(&visitor);
+
+ SpdyHeadersIR headers(/* stream_id = */ 0);
+ headers.SetHeader("alpha", "beta");
+ SpdySerializedFrame frame(
+ SpdyFramerPeer::SerializeHeaders(&framer_, headers, &output_));
+
+ // We shouldn't have to read the whole frame before we signal an error.
+ EXPECT_CALL(visitor, OnError(Http2DecoderAdapter::SPDY_INVALID_STREAM_ID));
+ EXPECT_GT(frame.size(), deframer_.ProcessInput(frame.data(), frame.size()));
+ EXPECT_TRUE(deframer_.HasError());
+ EXPECT_EQ(Http2DecoderAdapter::SPDY_INVALID_STREAM_ID,
+ deframer_.spdy_framer_error())
+ << Http2DecoderAdapter::SpdyFramerErrorToString(
+ deframer_.spdy_framer_error());
+}
+
+// Test that if we receive a PRIORITY with stream ID zero, we signal an error
+// (but don't crash).
+TEST_P(SpdyFramerTest, PriorityWithStreamIdZero) {
+ testing::StrictMock<test::MockSpdyFramerVisitor> visitor;
+
+ deframer_.set_visitor(&visitor);
+
+ SpdyPriorityIR priority_ir(/* stream_id = */ 0,
+ /* parent_stream_id = */ 1,
+ /* weight = */ 16,
+ /* exclusive = */ true);
+ SpdySerializedFrame frame(framer_.SerializeFrame(priority_ir));
+ if (use_output_) {
+ EXPECT_EQ(framer_.SerializeFrame(priority_ir, &output_), frame.size());
+ frame = SpdySerializedFrame(output_.Begin(), output_.Size(), false);
+ }
+
+ // We shouldn't have to read the whole frame before we signal an error.
+ EXPECT_CALL(visitor, OnError(Http2DecoderAdapter::SPDY_INVALID_STREAM_ID));
+ EXPECT_GT(frame.size(), deframer_.ProcessInput(frame.data(), frame.size()));
+ EXPECT_TRUE(deframer_.HasError());
+ EXPECT_EQ(Http2DecoderAdapter::SPDY_INVALID_STREAM_ID,
+ deframer_.spdy_framer_error())
+ << Http2DecoderAdapter::SpdyFramerErrorToString(
+ deframer_.spdy_framer_error());
+}
+
+// Test that if we receive a RST_STREAM with stream ID zero, we signal an error
+// (but don't crash).
+TEST_P(SpdyFramerTest, RstStreamWithStreamIdZero) {
+ testing::StrictMock<test::MockSpdyFramerVisitor> visitor;
+
+ deframer_.set_visitor(&visitor);
+
+ SpdyRstStreamIR rst_stream_ir(/* stream_id = */ 0, ERROR_CODE_PROTOCOL_ERROR);
+ SpdySerializedFrame frame(framer_.SerializeRstStream(rst_stream_ir));
+ if (use_output_) {
+ EXPECT_TRUE(framer_.SerializeRstStream(rst_stream_ir, &output_));
+ frame = SpdySerializedFrame(output_.Begin(), output_.Size(), false);
+ }
+
+ // We shouldn't have to read the whole frame before we signal an error.
+ EXPECT_CALL(visitor, OnError(Http2DecoderAdapter::SPDY_INVALID_STREAM_ID));
+ EXPECT_GT(frame.size(), deframer_.ProcessInput(frame.data(), frame.size()));
+ EXPECT_TRUE(deframer_.HasError());
+ EXPECT_EQ(Http2DecoderAdapter::SPDY_INVALID_STREAM_ID,
+ deframer_.spdy_framer_error())
+ << Http2DecoderAdapter::SpdyFramerErrorToString(
+ deframer_.spdy_framer_error());
+}
+
+// Test that if we receive a SETTINGS with stream ID other than zero,
+// we signal an error (but don't crash).
+TEST_P(SpdyFramerTest, SettingsWithStreamIdNotZero) {
+ testing::StrictMock<test::MockSpdyFramerVisitor> visitor;
+
+ deframer_.set_visitor(&visitor);
+
+ // Settings frame with invalid StreamID of 0x01
+ char kH2FrameData[] = {
+ 0x00, 0x00, 0x06, // Length: 6
+ 0x04, // Type: SETTINGS
+ 0x00, // Flags: none
+ 0x00, 0x00, 0x00, 0x01, // Stream: 1
+ 0x00, 0x04, // Param: INITIAL_WINDOW_SIZE
+ 0x0a, 0x0b, 0x0c, 0x0d, // Value: 168496141
+ };
+
+ SpdySerializedFrame frame(kH2FrameData, sizeof(kH2FrameData), false);
+
+ // We shouldn't have to read the whole frame before we signal an error.
+ EXPECT_CALL(visitor, OnError(Http2DecoderAdapter::SPDY_INVALID_STREAM_ID));
+ EXPECT_GT(frame.size(), deframer_.ProcessInput(frame.data(), frame.size()));
+ EXPECT_TRUE(deframer_.HasError());
+ EXPECT_EQ(Http2DecoderAdapter::SPDY_INVALID_STREAM_ID,
+ deframer_.spdy_framer_error())
+ << Http2DecoderAdapter::SpdyFramerErrorToString(
+ deframer_.spdy_framer_error());
+}
+
+// Test that if we receive a GOAWAY with stream ID other than zero,
+// we signal an error (but don't crash).
+TEST_P(SpdyFramerTest, GoawayWithStreamIdNotZero) {
+ testing::StrictMock<test::MockSpdyFramerVisitor> visitor;
+
+ deframer_.set_visitor(&visitor);
+
+ // GOAWAY frame with invalid StreamID of 0x01
+ char kH2FrameData[] = {
+ 0x00, 0x00, 0x0a, // Length: 10
+ 0x07, // Type: GOAWAY
+ 0x00, // Flags: none
+ 0x00, 0x00, 0x00, 0x01, // Stream: 1
+ 0x00, 0x00, 0x00, 0x00, // Last: 0
+ 0x00, 0x00, 0x00, 0x00, // Error: NO_ERROR
+ 0x47, 0x41, // Description
+ };
+
+ SpdySerializedFrame frame(kH2FrameData, sizeof(kH2FrameData), false);
+
+ // We shouldn't have to read the whole frame before we signal an error.
+ EXPECT_CALL(visitor, OnError(Http2DecoderAdapter::SPDY_INVALID_STREAM_ID));
+ EXPECT_GT(frame.size(), deframer_.ProcessInput(frame.data(), frame.size()));
+ EXPECT_TRUE(deframer_.HasError());
+ EXPECT_EQ(Http2DecoderAdapter::SPDY_INVALID_STREAM_ID,
+ deframer_.spdy_framer_error())
+ << Http2DecoderAdapter::SpdyFramerErrorToString(
+ deframer_.spdy_framer_error());
+}
+
+// Test that if we receive a CONTINUATION with stream ID zero, we signal
+// SPDY_INVALID_STREAM_ID.
+TEST_P(SpdyFramerTest, ContinuationWithStreamIdZero) {
+ testing::StrictMock<test::MockSpdyFramerVisitor> visitor;
+
+ deframer_.set_visitor(&visitor);
+
+ SpdyContinuationIR continuation(/* stream_id = */ 0);
+ auto some_nonsense_encoding =
+ SpdyMakeUnique<SpdyString>("some nonsense encoding");
+ continuation.take_encoding(std::move(some_nonsense_encoding));
+ continuation.set_end_headers(true);
+ SpdySerializedFrame frame(framer_.SerializeContinuation(continuation));
+ if (use_output_) {
+ ASSERT_TRUE(framer_.SerializeContinuation(continuation, &output_));
+ frame = SpdySerializedFrame(output_.Begin(), output_.Size(), false);
+ }
+
+ // We shouldn't have to read the whole frame before we signal an error.
+ EXPECT_CALL(visitor, OnError(Http2DecoderAdapter::SPDY_INVALID_STREAM_ID));
+ EXPECT_GT(frame.size(), deframer_.ProcessInput(frame.data(), frame.size()));
+ EXPECT_TRUE(deframer_.HasError());
+ EXPECT_EQ(Http2DecoderAdapter::SPDY_INVALID_STREAM_ID,
+ deframer_.spdy_framer_error())
+ << Http2DecoderAdapter::SpdyFramerErrorToString(
+ deframer_.spdy_framer_error());
+}
+
+// Test that if we receive a PUSH_PROMISE with stream ID zero, we signal
+// SPDY_INVALID_STREAM_ID.
+TEST_P(SpdyFramerTest, PushPromiseWithStreamIdZero) {
+ testing::StrictMock<test::MockSpdyFramerVisitor> visitor;
+
+ deframer_.set_visitor(&visitor);
+
+ SpdyPushPromiseIR push_promise(/* stream_id = */ 0,
+ /* promised_stream_id = */ 4);
+ push_promise.SetHeader("alpha", "beta");
+ SpdySerializedFrame frame(SpdyFramerPeer::SerializePushPromise(
+ &framer_, push_promise, use_output_ ? &output_ : nullptr));
+
+ // We shouldn't have to read the whole frame before we signal an error.
+ EXPECT_CALL(visitor, OnError(Http2DecoderAdapter::SPDY_INVALID_STREAM_ID));
+ EXPECT_GT(frame.size(), deframer_.ProcessInput(frame.data(), frame.size()));
+ EXPECT_TRUE(deframer_.HasError());
+ EXPECT_EQ(Http2DecoderAdapter::SPDY_INVALID_STREAM_ID,
+ deframer_.spdy_framer_error())
+ << Http2DecoderAdapter::SpdyFramerErrorToString(
+ deframer_.spdy_framer_error());
+}
+
+// Test that if we receive a PUSH_PROMISE with promised stream ID zero, we
+// signal SPDY_INVALID_CONTROL_FRAME.
+TEST_P(SpdyFramerTest, PushPromiseWithPromisedStreamIdZero) {
+ testing::StrictMock<test::MockSpdyFramerVisitor> visitor;
+
+ deframer_.set_visitor(&visitor);
+
+ SpdyPushPromiseIR push_promise(/* stream_id = */ 3,
+ /* promised_stream_id = */ 0);
+ push_promise.SetHeader("alpha", "beta");
+ SpdySerializedFrame frame(SpdyFramerPeer::SerializePushPromise(
+ &framer_, push_promise, use_output_ ? &output_ : nullptr));
+
+ EXPECT_CALL(visitor,
+ OnError(Http2DecoderAdapter::SPDY_INVALID_CONTROL_FRAME));
+ deframer_.ProcessInput(frame.data(), frame.size());
+ EXPECT_TRUE(deframer_.HasError());
+ EXPECT_EQ(Http2DecoderAdapter::SPDY_INVALID_CONTROL_FRAME,
+ deframer_.spdy_framer_error())
+ << Http2DecoderAdapter::SpdyFramerErrorToString(
+ deframer_.spdy_framer_error());
+}
+
+TEST_P(SpdyFramerTest, MultiValueHeader) {
+ SpdyFramer framer(SpdyFramer::DISABLE_COMPRESSION);
+ SpdyString value("value1\0value2", 13);
+ // TODO(jgraettinger): If this pattern appears again, move to test class.
+ SpdyHeaderBlock header_set;
+ header_set["name"] = value;
+ SpdyString buffer;
+ HpackEncoder encoder(ObtainHpackHuffmanTable());
+ encoder.DisableCompression();
+ encoder.EncodeHeaderSet(header_set, &buffer);
+ // Frame builder with plentiful buffer size.
+ SpdyFrameBuilder frame(1024);
+ frame.BeginNewFrame(SpdyFrameType::HEADERS,
+ HEADERS_FLAG_PRIORITY | HEADERS_FLAG_END_HEADERS, 3,
+ buffer.size() + 5 /* priority */);
+ frame.WriteUInt32(0); // Priority exclusivity and dependent stream.
+ frame.WriteUInt8(255); // Priority weight.
+ frame.WriteBytes(&buffer[0], buffer.size());
+
+ SpdySerializedFrame control_frame(frame.take());
+
+ TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION);
+ visitor.SimulateInFramer(
+ reinterpret_cast<unsigned char*>(control_frame.data()),
+ control_frame.size());
+
+ EXPECT_THAT(visitor.headers_, testing::ElementsAre(testing::Pair(
+ "name", SpdyStringPiece(value))));
+}
+
+TEST_P(SpdyFramerTest, CompressEmptyHeaders) {
+ // See https://crbug.com/172383/
+ SpdyHeadersIR headers(1);
+ headers.SetHeader("server", "SpdyServer 1.0");
+ headers.SetHeader("date", "Mon 12 Jan 2009 12:12:12 PST");
+ headers.SetHeader("status", "200");
+ headers.SetHeader("version", "HTTP/1.1");
+ headers.SetHeader("content-type", "text/html");
+ headers.SetHeader("content-length", "12");
+ headers.SetHeader("x-empty-header", "");
+
+ SpdyFramer framer(SpdyFramer::ENABLE_COMPRESSION);
+ SpdySerializedFrame frame1(
+ SpdyFramerPeer::SerializeHeaders(&framer, headers, &output_));
+}
+
+TEST_P(SpdyFramerTest, Basic) {
+ // Send HEADERS frames with PRIORITY and END_HEADERS set.
+ // frame-format off
+ const unsigned char kH2Input[] = {
+ 0x00, 0x00, 0x05, // Length: 5
+ 0x01, // Type: HEADERS
+ 0x24, // Flags: END_HEADERS|PRIORITY
+ 0x00, 0x00, 0x00, 0x01, // Stream: 1
+ 0x00, 0x00, 0x00, 0x00, // Parent: 0
+ 0x82, // Weight: 131
+
+ 0x00, 0x00, 0x01, // Length: 1
+ 0x01, // Type: HEADERS
+ 0x04, // Flags: END_HEADERS
+ 0x00, 0x00, 0x00, 0x01, // Stream: 1
+ 0x8c, // :status: 200
+
+ 0x00, 0x00, 0x0c, // Length: 12
+ 0x00, // Type: DATA
+ 0x00, // Flags: none
+ 0x00, 0x00, 0x00, 0x01, // Stream: 1
+ 0xde, 0xad, 0xbe, 0xef, // Payload
+ 0xde, 0xad, 0xbe, 0xef, //
+ 0xde, 0xad, 0xbe, 0xef, //
+
+ 0x00, 0x00, 0x05, // Length: 5
+ 0x01, // Type: HEADERS
+ 0x24, // Flags: END_HEADERS|PRIORITY
+ 0x00, 0x00, 0x00, 0x03, // Stream: 3
+ 0x00, 0x00, 0x00, 0x00, // Parent: 0
+ 0x82, // Weight: 131
+
+ 0x00, 0x00, 0x08, // Length: 8
+ 0x00, // Type: DATA
+ 0x00, // Flags: none
+ 0x00, 0x00, 0x00, 0x03, // Stream: 3
+ 0xde, 0xad, 0xbe, 0xef, // Payload
+ 0xde, 0xad, 0xbe, 0xef, //
+
+ 0x00, 0x00, 0x04, // Length: 4
+ 0x00, // Type: DATA
+ 0x00, // Flags: none
+ 0x00, 0x00, 0x00, 0x01, // Stream: 1
+ 0xde, 0xad, 0xbe, 0xef, // Payload
+
+ 0x00, 0x00, 0x04, // Length: 4
+ 0x03, // Type: RST_STREAM
+ 0x00, // Flags: none
+ 0x00, 0x00, 0x00, 0x01, // Stream: 1
+ 0x00, 0x00, 0x00, 0x08, // Error: CANCEL
+
+ 0x00, 0x00, 0x00, // Length: 0
+ 0x00, // Type: DATA
+ 0x00, // Flags: none
+ 0x00, 0x00, 0x00, 0x03, // Stream: 3
+
+ 0x00, 0x00, 0x04, // Length: 4
+ 0x03, // Type: RST_STREAM
+ 0x00, // Flags: none
+ 0x00, 0x00, 0x00, 0x03, // Stream: 3
+ 0x00, 0x00, 0x00, 0x08, // Error: CANCEL
+ };
+ // frame-format on
+
+ TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION);
+ visitor.SimulateInFramer(kH2Input, sizeof(kH2Input));
+
+ EXPECT_EQ(24, visitor.data_bytes_);
+ EXPECT_EQ(0, visitor.error_count_);
+ EXPECT_EQ(2, visitor.fin_frame_count_);
+
+ EXPECT_EQ(3, visitor.headers_frame_count_);
+
+ EXPECT_EQ(0, visitor.fin_flag_count_);
+ EXPECT_EQ(0, visitor.end_of_stream_count_);
+ EXPECT_EQ(4, visitor.data_frame_count_);
+}
+
+// Test that the FIN flag on a data frame signifies EOF.
+TEST_P(SpdyFramerTest, FinOnDataFrame) {
+ // Send HEADERS frames with END_HEADERS set.
+ // frame-format off
+ const unsigned char kH2Input[] = {
+ 0x00, 0x00, 0x05, // Length: 5
+ 0x01, // Type: HEADERS
+ 0x24, // Flags: END_HEADERS|PRIORITY
+ 0x00, 0x00, 0x00, 0x01, // Stream: 1
+ 0x00, 0x00, 0x00, 0x00, // Parent: 0
+ 0x82, // Weight: 131
+
+ 0x00, 0x00, 0x01, // Length: 1
+ 0x01, // Type: HEADERS
+ 0x04, // Flags: END_HEADERS
+ 0x00, 0x00, 0x00, 0x01, // Stream: 1
+ 0x8c, // :status: 200
+
+ 0x00, 0x00, 0x0c, // Length: 12
+ 0x00, // Type: DATA
+ 0x00, // Flags: none
+ 0x00, 0x00, 0x00, 0x01, // Stream: 1
+ 0xde, 0xad, 0xbe, 0xef, // Payload
+ 0xde, 0xad, 0xbe, 0xef, //
+ 0xde, 0xad, 0xbe, 0xef, //
+
+ 0x00, 0x00, 0x04, // Length: 4
+ 0x00, // Type: DATA
+ 0x01, // Flags: END_STREAM
+ 0x00, 0x00, 0x00, 0x01, // Stream: 1
+ 0xde, 0xad, 0xbe, 0xef, // Payload
+ };
+ // frame-format on
+
+ TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION);
+ visitor.SimulateInFramer(kH2Input, sizeof(kH2Input));
+
+ EXPECT_EQ(0, visitor.error_count_);
+ EXPECT_EQ(2, visitor.headers_frame_count_);
+ EXPECT_EQ(16, visitor.data_bytes_);
+ EXPECT_EQ(0, visitor.fin_frame_count_);
+ EXPECT_EQ(0, visitor.fin_flag_count_);
+ EXPECT_EQ(1, visitor.end_of_stream_count_);
+ EXPECT_EQ(2, visitor.data_frame_count_);
+}
+
+TEST_P(SpdyFramerTest, FinOnHeadersFrame) {
+ // Send HEADERS frames with END_HEADERS set.
+ // frame-format off
+ const unsigned char kH2Input[] = {
+ 0x00, 0x00, 0x05, // Length: 5
+ 0x01, // Type: HEADERS
+ 0x24, // Flags: END_HEADERS|PRIORITY
+ 0x00, 0x00, 0x00, 0x01, // Stream: 1
+ 0x00, 0x00, 0x00, 0x00, // Parent: 0
+ 0x82, // Weight: 131
+
+ 0x00, 0x00, 0x01, // Length: 1
+ 0x01, // Type: HEADERS
+ 0x05, // Flags: END_STREAM|END_HEADERS
+ 0x00, 0x00, 0x00, 0x01, // Stream: 1
+ 0x8c, // :status: 200
+ };
+ // frame-format on
+
+ TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION);
+ visitor.SimulateInFramer(kH2Input, sizeof(kH2Input));
+
+ EXPECT_EQ(0, visitor.error_count_);
+ EXPECT_EQ(2, visitor.headers_frame_count_);
+ EXPECT_EQ(0, visitor.data_bytes_);
+ EXPECT_EQ(0, visitor.fin_frame_count_);
+ EXPECT_EQ(1, visitor.fin_flag_count_);
+ EXPECT_EQ(1, visitor.end_of_stream_count_);
+ EXPECT_EQ(0, visitor.data_frame_count_);
+}
+
+// Verify we can decompress the stream even if handed over to the
+// framer 1 byte at a time.
+TEST_P(SpdyFramerTest, UnclosedStreamDataCompressorsOneByteAtATime) {
+ const char kHeader1[] = "header1";
+ const char kHeader2[] = "header2";
+ const char kValue1[] = "value1";
+ const char kValue2[] = "value2";
+
+ SpdyHeadersIR headers(/* stream_id = */ 1);
+ headers.SetHeader(kHeader1, kValue1);
+ headers.SetHeader(kHeader2, kValue2);
+ SpdySerializedFrame headers_frame(SpdyFramerPeer::SerializeHeaders(
+ &framer_, headers, use_output_ ? &output_ : nullptr));
+
+ const char bytes[] = "this is a test test test test test!";
+ SpdyDataIR data_ir(/* stream_id = */ 1,
+ SpdyStringPiece(bytes, SPDY_ARRAYSIZE(bytes)));
+ data_ir.set_fin(true);
+ SpdySerializedFrame send_frame(framer_.SerializeData(data_ir));
+
+ // Run the inputs through the framer.
+ TestSpdyVisitor visitor(SpdyFramer::ENABLE_COMPRESSION);
+ const unsigned char* data;
+ data = reinterpret_cast<const unsigned char*>(headers_frame.data());
+ for (size_t idx = 0; idx < headers_frame.size(); ++idx) {
+ visitor.SimulateInFramer(data + idx, 1);
+ ASSERT_EQ(0, visitor.error_count_);
+ }
+ data = reinterpret_cast<const unsigned char*>(send_frame.data());
+ for (size_t idx = 0; idx < send_frame.size(); ++idx) {
+ visitor.SimulateInFramer(data + idx, 1);
+ ASSERT_EQ(0, visitor.error_count_);
+ }
+
+ EXPECT_EQ(0, visitor.error_count_);
+ EXPECT_EQ(1, visitor.headers_frame_count_);
+ EXPECT_EQ(SPDY_ARRAYSIZE(bytes), static_cast<unsigned>(visitor.data_bytes_));
+ EXPECT_EQ(0, visitor.fin_frame_count_);
+ EXPECT_EQ(0, visitor.fin_flag_count_);
+ EXPECT_EQ(1, visitor.end_of_stream_count_);
+ EXPECT_EQ(1, visitor.data_frame_count_);
+}
+
+TEST_P(SpdyFramerTest, WindowUpdateFrame) {
+ SpdyWindowUpdateIR window_update(/* stream_id = */ 1,
+ /* delta = */ 0x12345678);
+ SpdySerializedFrame frame(framer_.SerializeWindowUpdate(window_update));
+ if (use_output_) {
+ ASSERT_TRUE(framer_.SerializeWindowUpdate(window_update, &output_));
+ frame = SpdySerializedFrame(output_.Begin(), output_.Size(), false);
+ }
+
+ const char kDescription[] = "WINDOW_UPDATE frame, stream 1, delta 0x12345678";
+ const unsigned char kH2FrameData[] = {
+ 0x00, 0x00, 0x04, // Length: 4
+ 0x08, // Type: WINDOW_UPDATE
+ 0x00, // Flags: none
+ 0x00, 0x00, 0x00, 0x01, // Stream: 1
+ 0x12, 0x34, 0x56, 0x78, // Increment: 305419896
+ };
+
+ CompareFrame(kDescription, frame, kH2FrameData, SPDY_ARRAYSIZE(kH2FrameData));
+}
+
+TEST_P(SpdyFramerTest, CreateDataFrame) {
+ {
+ const char kDescription[] = "'hello' data frame, no FIN";
+ // frame-format off
+ const unsigned char kH2FrameData[] = {
+ 0x00, 0x00, 0x05, // Length: 5
+ 0x00, // Type: DATA
+ 0x00, // Flags: none
+ 0x00, 0x00, 0x00, 0x01, // Stream: 1
+ 'h', 'e', 'l', 'l', // Payload
+ 'o', //
+ };
+ // frame-format on
+ const char bytes[] = "hello";
+
+ SpdyDataIR data_ir(/* stream_id = */ 1, bytes);
+ SpdySerializedFrame frame(framer_.SerializeData(data_ir));
+ CompareFrame(kDescription, frame, kH2FrameData,
+ SPDY_ARRAYSIZE(kH2FrameData));
+
+ SpdyDataIR data_header_ir(/* stream_id = */ 1);
+ data_header_ir.SetDataShallow(bytes);
+ frame =
+ framer_.SerializeDataFrameHeaderWithPaddingLengthField(data_header_ir);
+ CompareCharArraysWithHexError(
+ kDescription, reinterpret_cast<const unsigned char*>(frame.data()),
+ kDataFrameMinimumSize, kH2FrameData, kDataFrameMinimumSize);
+ }
+
+ {
+ const char kDescription[] = "'hello' data frame with more padding, no FIN";
+ // clang-format off
+ // frame-format off
+ const unsigned char kH2FrameData[] = {
+ 0x00, 0x00, 0xfd, // Length: 253
+ 0x00, // Type: DATA
+ 0x08, // Flags: PADDED
+ 0x00, 0x00, 0x00, 0x01, // Stream: 1
+ 0xf7, // PadLen: 247 trailing bytes
+ 'h', 'e', 'l', 'l', // Payload
+ 'o', //
+ // Padding of 247 0x00(s).
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ };
+ // frame-format on
+ // clang-format on
+ const char bytes[] = "hello";
+
+ SpdyDataIR data_ir(/* stream_id = */ 1, bytes);
+ // 247 zeros and the pad length field make the overall padding to be 248
+ // bytes.
+ data_ir.set_padding_len(248);
+ SpdySerializedFrame frame(framer_.SerializeData(data_ir));
+ CompareFrame(kDescription, frame, kH2FrameData,
+ SPDY_ARRAYSIZE(kH2FrameData));
+
+ frame = framer_.SerializeDataFrameHeaderWithPaddingLengthField(data_ir);
+ CompareCharArraysWithHexError(
+ kDescription, reinterpret_cast<const unsigned char*>(frame.data()),
+ kDataFrameMinimumSize, kH2FrameData, kDataFrameMinimumSize);
+ }
+
+ {
+ const char kDescription[] = "'hello' data frame with few padding, no FIN";
+ // frame-format off
+ const unsigned char kH2FrameData[] = {
+ 0x00, 0x00, 0x0d, // Length: 13
+ 0x00, // Type: DATA
+ 0x08, // Flags: PADDED
+ 0x00, 0x00, 0x00, 0x01, // Stream: 1
+ 0x07, // PadLen: 7 trailing bytes
+ 'h', 'e', 'l', 'l', // Payload
+ 'o', //
+ 0x00, 0x00, 0x00, 0x00, // Padding
+ 0x00, 0x00, 0x00, // Padding
+ };
+ // frame-format on
+ const char bytes[] = "hello";
+
+ SpdyDataIR data_ir(/* stream_id = */ 1, bytes);
+ // 7 zeros and the pad length field make the overall padding to be 8 bytes.
+ data_ir.set_padding_len(8);
+ SpdySerializedFrame frame(framer_.SerializeData(data_ir));
+ CompareFrame(kDescription, frame, kH2FrameData,
+ SPDY_ARRAYSIZE(kH2FrameData));
+
+ frame = framer_.SerializeDataFrameHeaderWithPaddingLengthField(data_ir);
+ CompareCharArraysWithHexError(
+ kDescription, reinterpret_cast<const unsigned char*>(frame.data()),
+ kDataFrameMinimumSize, kH2FrameData, kDataFrameMinimumSize);
+ }
+
+ {
+ const char kDescription[] =
+ "'hello' data frame with 1 byte padding, no FIN";
+ // frame-format off
+ const unsigned char kH2FrameData[] = {
+ 0x00, 0x00, 0x06, // Length: 6
+ 0x00, // Type: DATA
+ 0x08, // Flags: PADDED
+ 0x00, 0x00, 0x00, 0x01, // Stream: 1
+ 0x00, // PadLen: 0 trailing bytes
+ 'h', 'e', 'l', 'l', // Payload
+ 'o', //
+ };
+ // frame-format on
+ const char bytes[] = "hello";
+
+ SpdyDataIR data_ir(/* stream_id = */ 1, bytes);
+ // The pad length field itself is used for the 1-byte padding and no padding
+ // payload is needed.
+ data_ir.set_padding_len(1);
+ SpdySerializedFrame frame(framer_.SerializeData(data_ir));
+ CompareFrame(kDescription, frame, kH2FrameData,
+ SPDY_ARRAYSIZE(kH2FrameData));
+
+ frame = framer_.SerializeDataFrameHeaderWithPaddingLengthField(data_ir);
+ CompareCharArraysWithHexError(
+ kDescription, reinterpret_cast<const unsigned char*>(frame.data()),
+ kDataFrameMinimumSize, kH2FrameData, kDataFrameMinimumSize);
+ }
+
+ {
+ const char kDescription[] = "Data frame with negative data byte, no FIN";
+ const unsigned char kH2FrameData[] = {
+ 0x00, 0x00, 0x01, // Length: 1
+ 0x00, // Type: DATA
+ 0x00, // Flags: none
+ 0x00, 0x00, 0x00, 0x01, // Stream: 1
+ 0xff, // Payload
+ };
+ SpdyDataIR data_ir(/* stream_id = */ 1, "\xff");
+ SpdySerializedFrame frame(framer_.SerializeData(data_ir));
+ CompareFrame(kDescription, frame, kH2FrameData,
+ SPDY_ARRAYSIZE(kH2FrameData));
+ }
+
+ {
+ const char kDescription[] = "'hello' data frame, with FIN";
+ const unsigned char kH2FrameData[] = {
+ 0x00, 0x00, 0x05, // Length: 5
+ 0x00, // Type: DATA
+ 0x01, // Flags: END_STREAM
+ 0x00, 0x00, 0x00, 0x01, // Stream: 1
+ 0x68, 0x65, 0x6c, 0x6c, // Payload
+ 0x6f, //
+ };
+ SpdyDataIR data_ir(/* stream_id = */ 1, "hello");
+ data_ir.set_fin(true);
+ SpdySerializedFrame frame(framer_.SerializeData(data_ir));
+ CompareFrame(kDescription, frame, kH2FrameData,
+ SPDY_ARRAYSIZE(kH2FrameData));
+ }
+
+ {
+ const char kDescription[] = "Empty data frame";
+ const unsigned char kH2FrameData[] = {
+ 0x00, 0x00, 0x00, // Length: 0
+ 0x00, // Type: DATA
+ 0x00, // Flags: none
+ 0x00, 0x00, 0x00, 0x01, // Stream: 1
+ };
+ SpdyDataIR data_ir(/* stream_id = */ 1, "");
+ SpdySerializedFrame frame(framer_.SerializeData(data_ir));
+ CompareFrame(kDescription, frame, kH2FrameData,
+ SPDY_ARRAYSIZE(kH2FrameData));
+
+ frame = framer_.SerializeDataFrameHeaderWithPaddingLengthField(data_ir);
+ CompareCharArraysWithHexError(
+ kDescription, reinterpret_cast<const unsigned char*>(frame.data()),
+ kDataFrameMinimumSize, kH2FrameData, kDataFrameMinimumSize);
+ }
+
+ {
+ const char kDescription[] = "Data frame with max stream ID";
+ const unsigned char kH2FrameData[] = {
+ 0x00, 0x00, 0x05, // Length: 5
+ 0x00, // Type: DATA
+ 0x01, // Flags: END_STREAM
+ 0x7f, 0xff, 0xff, 0xff, // Stream: 0x7fffffff
+ 0x68, 0x65, 0x6c, 0x6c, // Payload
+ 0x6f, //
+ };
+ SpdyDataIR data_ir(/* stream_id = */ 0x7fffffff, "hello");
+ data_ir.set_fin(true);
+ SpdySerializedFrame frame(framer_.SerializeData(data_ir));
+ CompareFrame(kDescription, frame, kH2FrameData,
+ SPDY_ARRAYSIZE(kH2FrameData));
+ }
+}
+
+TEST_P(SpdyFramerTest, CreateRstStream) {
+ {
+ const char kDescription[] = "RST_STREAM frame";
+ const unsigned char kH2FrameData[] = {
+ 0x00, 0x00, 0x04, // Length: 4
+ 0x03, // Type: RST_STREAM
+ 0x00, // Flags: none
+ 0x00, 0x00, 0x00, 0x01, // Stream: 1
+ 0x00, 0x00, 0x00, 0x01, // Error: PROTOCOL_ERROR
+ };
+ SpdyRstStreamIR rst_stream(/* stream_id = */ 1, ERROR_CODE_PROTOCOL_ERROR);
+ SpdySerializedFrame frame(framer_.SerializeRstStream(rst_stream));
+ if (use_output_) {
+ ASSERT_TRUE(framer_.SerializeRstStream(rst_stream, &output_));
+ frame = SpdySerializedFrame(output_.Begin(), output_.Size(), false);
+ }
+ CompareFrame(kDescription, frame, kH2FrameData,
+ SPDY_ARRAYSIZE(kH2FrameData));
+ }
+
+ {
+ const char kDescription[] = "RST_STREAM frame with max stream ID";
+ const unsigned char kH2FrameData[] = {
+ 0x00, 0x00, 0x04, // Length: 4
+ 0x03, // Type: RST_STREAM
+ 0x00, // Flags: none
+ 0x7f, 0xff, 0xff, 0xff, // Stream: 0x7fffffff
+ 0x00, 0x00, 0x00, 0x01, // Error: PROTOCOL_ERROR
+ };
+ SpdyRstStreamIR rst_stream(/* stream_id = */ 0x7FFFFFFF,
+ ERROR_CODE_PROTOCOL_ERROR);
+ SpdySerializedFrame frame(framer_.SerializeRstStream(rst_stream));
+ if (use_output_) {
+ output_.Reset();
+ ASSERT_TRUE(framer_.SerializeRstStream(rst_stream, &output_));
+ frame = SpdySerializedFrame(output_.Begin(), output_.Size(), false);
+ }
+ CompareFrame(kDescription, frame, kH2FrameData,
+ SPDY_ARRAYSIZE(kH2FrameData));
+ }
+
+ {
+ const char kDescription[] = "RST_STREAM frame with max status code";
+ const unsigned char kH2FrameData[] = {
+ 0x00, 0x00, 0x04, // Length: 4
+ 0x03, // Type: RST_STREAM
+ 0x00, // Flags: none
+ 0x7f, 0xff, 0xff, 0xff, // Stream: 0x7fffffff
+ 0x00, 0x00, 0x00, 0x02, // Error: INTERNAL_ERROR
+ };
+ SpdyRstStreamIR rst_stream(/* stream_id = */ 0x7FFFFFFF,
+ ERROR_CODE_INTERNAL_ERROR);
+ SpdySerializedFrame frame(framer_.SerializeRstStream(rst_stream));
+ if (use_output_) {
+ output_.Reset();
+ ASSERT_TRUE(framer_.SerializeRstStream(rst_stream, &output_));
+ frame = SpdySerializedFrame(output_.Begin(), output_.Size(), false);
+ }
+ CompareFrame(kDescription, frame, kH2FrameData,
+ SPDY_ARRAYSIZE(kH2FrameData));
+ }
+}
+
+TEST_P(SpdyFramerTest, CreateSettings) {
+ {
+ const char kDescription[] = "Network byte order SETTINGS frame";
+ const unsigned char kH2FrameData[] = {
+ 0x00, 0x00, 0x06, // Length: 6
+ 0x04, // Type: SETTINGS
+ 0x00, // Flags: none
+ 0x00, 0x00, 0x00, 0x00, // Stream: 0
+ 0x00, 0x04, // Param: INITIAL_WINDOW_SIZE
+ 0x0a, 0x0b, 0x0c, 0x0d, // Value: 168496141
+ };
+
+ uint32_t kValue = 0x0a0b0c0d;
+ SpdySettingsIR settings_ir;
+
+ SpdyKnownSettingsId kId = SETTINGS_INITIAL_WINDOW_SIZE;
+ settings_ir.AddSetting(kId, kValue);
+
+ SpdySerializedFrame frame(framer_.SerializeSettings(settings_ir));
+ if (use_output_) {
+ ASSERT_TRUE(framer_.SerializeSettings(settings_ir, &output_));
+ frame = SpdySerializedFrame(output_.Begin(), output_.Size(), false);
+ }
+ CompareFrame(kDescription, frame, kH2FrameData,
+ SPDY_ARRAYSIZE(kH2FrameData));
+ }
+
+ {
+ const char kDescription[] = "Basic SETTINGS frame";
+ // These end up seemingly out of order because of the way that our internal
+ // ordering for settings_ir works. HTTP2 has no requirement on ordering on
+ // the wire.
+ const unsigned char kH2FrameData[] = {
+ 0x00, 0x00, 0x18, // Length: 24
+ 0x04, // Type: SETTINGS
+ 0x00, // Flags: none
+ 0x00, 0x00, 0x00, 0x00, // Stream: 0
+ 0x00, 0x01, // Param: HEADER_TABLE_SIZE
+ 0x00, 0x00, 0x00, 0x05, // Value: 5
+ 0x00, 0x02, // Param: ENABLE_PUSH
+ 0x00, 0x00, 0x00, 0x06, // Value: 6
+ 0x00, 0x03, // Param: MAX_CONCURRENT_STREAMS
+ 0x00, 0x00, 0x00, 0x07, // Value: 7
+ 0x00, 0x04, // Param: INITIAL_WINDOW_SIZE
+ 0x00, 0x00, 0x00, 0x08, // Value: 8
+ };
+
+ SpdySettingsIR settings_ir;
+ settings_ir.AddSetting(SETTINGS_HEADER_TABLE_SIZE, 5);
+ settings_ir.AddSetting(SETTINGS_ENABLE_PUSH, 6);
+ settings_ir.AddSetting(SETTINGS_MAX_CONCURRENT_STREAMS, 7);
+ settings_ir.AddSetting(SETTINGS_INITIAL_WINDOW_SIZE, 8);
+ SpdySerializedFrame frame(framer_.SerializeSettings(settings_ir));
+ if (use_output_) {
+ output_.Reset();
+ ASSERT_TRUE(framer_.SerializeSettings(settings_ir, &output_));
+ frame = SpdySerializedFrame(output_.Begin(), output_.Size(), false);
+ }
+
+ CompareFrame(kDescription, frame, kH2FrameData,
+ SPDY_ARRAYSIZE(kH2FrameData));
+ }
+
+ {
+ const char kDescription[] = "Empty SETTINGS frame";
+ const unsigned char kH2FrameData[] = {
+ 0x00, 0x00, 0x00, // Length: 0
+ 0x04, // Type: SETTINGS
+ 0x00, // Flags: none
+ 0x00, 0x00, 0x00, 0x00, // Stream: 0
+ };
+ SpdySettingsIR settings_ir;
+ SpdySerializedFrame frame(framer_.SerializeSettings(settings_ir));
+ if (use_output_) {
+ output_.Reset();
+ ASSERT_TRUE(framer_.SerializeSettings(settings_ir, &output_));
+ frame = SpdySerializedFrame(output_.Begin(), output_.Size(), false);
+ }
+
+ CompareFrame(kDescription, frame, kH2FrameData,
+ SPDY_ARRAYSIZE(kH2FrameData));
+ }
+}
+
+TEST_P(SpdyFramerTest, CreatePingFrame) {
+ {
+ const char kDescription[] = "PING frame";
+ const unsigned char kH2FrameData[] = {
+ 0x00, 0x00, 0x08, // Length: 8
+ 0x06, // Type: PING
+ 0x00, // Flags: none
+ 0x00, 0x00, 0x00, 0x00, // Stream: 0
+ 0x12, 0x34, 0x56, 0x78, // Opaque
+ 0x9a, 0xbc, 0xde, 0xff, // Data
+ };
+ const unsigned char kH2FrameDataWithAck[] = {
+ 0x00, 0x00, 0x08, // Length: 8
+ 0x06, // Type: PING
+ 0x01, // Flags: ACK
+ 0x00, 0x00, 0x00, 0x00, // Stream: 0
+ 0x12, 0x34, 0x56, 0x78, // Opaque
+ 0x9a, 0xbc, 0xde, 0xff, // Data
+ };
+ const SpdyPingId kPingId = 0x123456789abcdeffULL;
+ SpdyPingIR ping_ir(kPingId);
+ // Tests SpdyPingIR when the ping is not an ack.
+ ASSERT_FALSE(ping_ir.is_ack());
+ SpdySerializedFrame frame(framer_.SerializePing(ping_ir));
+ if (use_output_) {
+ ASSERT_TRUE(framer_.SerializePing(ping_ir, &output_));
+ frame = SpdySerializedFrame(output_.Begin(), output_.Size(), false);
+ }
+ CompareFrame(kDescription, frame, kH2FrameData,
+ SPDY_ARRAYSIZE(kH2FrameData));
+
+ // Tests SpdyPingIR when the ping is an ack.
+ ping_ir.set_is_ack(true);
+ frame = framer_.SerializePing(ping_ir);
+ if (use_output_) {
+ output_.Reset();
+ ASSERT_TRUE(framer_.SerializePing(ping_ir, &output_));
+ frame = SpdySerializedFrame(output_.Begin(), output_.Size(), false);
+ }
+ CompareFrame(kDescription, frame, kH2FrameDataWithAck,
+ SPDY_ARRAYSIZE(kH2FrameDataWithAck));
+ }
+}
+
+TEST_P(SpdyFramerTest, CreateGoAway) {
+ {
+ const char kDescription[] = "GOAWAY frame";
+ const unsigned char kH2FrameData[] = {
+ 0x00, 0x00, 0x0a, // Length: 10
+ 0x07, // Type: GOAWAY
+ 0x00, // Flags: none
+ 0x00, 0x00, 0x00, 0x00, // Stream: 0
+ 0x00, 0x00, 0x00, 0x00, // Last: 0
+ 0x00, 0x00, 0x00, 0x00, // Error: NO_ERROR
+ 0x47, 0x41, // Description
+ };
+ SpdyGoAwayIR goaway_ir(/* last_good_stream_id = */ 0, ERROR_CODE_NO_ERROR,
+ "GA");
+ SpdySerializedFrame frame(framer_.SerializeGoAway(goaway_ir));
+ if (use_output_) {
+ ASSERT_TRUE(framer_.SerializeGoAway(goaway_ir, &output_));
+ frame = SpdySerializedFrame(output_.Begin(), output_.Size(), false);
+ }
+ CompareFrame(kDescription, frame, kH2FrameData,
+ SPDY_ARRAYSIZE(kH2FrameData));
+ }
+
+ {
+ const char kDescription[] = "GOAWAY frame with max stream ID, status";
+ const unsigned char kH2FrameData[] = {
+ 0x00, 0x00, 0x0a, // Length: 10
+ 0x07, // Type: GOAWAY
+ 0x00, // Flags: none
+ 0x00, 0x00, 0x00, 0x00, // Stream: 0
+ 0x7f, 0xff, 0xff, 0xff, // Last: 0x7fffffff
+ 0x00, 0x00, 0x00, 0x02, // Error: INTERNAL_ERROR
+ 0x47, 0x41, // Description
+ };
+ SpdyGoAwayIR goaway_ir(/* last_good_stream_id = */ 0x7FFFFFFF,
+ ERROR_CODE_INTERNAL_ERROR, "GA");
+ SpdySerializedFrame frame(framer_.SerializeGoAway(goaway_ir));
+ if (use_output_) {
+ output_.Reset();
+ ASSERT_TRUE(framer_.SerializeGoAway(goaway_ir, &output_));
+ frame = SpdySerializedFrame(output_.Begin(), output_.Size(), false);
+ }
+ CompareFrame(kDescription, frame, kH2FrameData,
+ SPDY_ARRAYSIZE(kH2FrameData));
+ }
+}
+
+TEST_P(SpdyFramerTest, CreateHeadersUncompressed) {
+ SpdyFramer framer(SpdyFramer::DISABLE_COMPRESSION);
+
+ {
+ const char kDescription[] = "HEADERS frame, no FIN";
+ // frame-format off
+ const unsigned char kH2FrameData[] = {
+ 0x00, 0x00, 0x12, // Length: 18
+ 0x01, // Type: HEADERS
+ 0x04, // Flags: END_HEADERS
+ 0x00, 0x00, 0x00, 0x01, // Stream: 1
+
+ 0x00, // Unindexed Entry
+ 0x03, // Name Len: 3
+ 0x62, 0x61, 0x72, // bar
+ 0x03, // Value Len: 3
+ 0x66, 0x6f, 0x6f, // foo
+
+ 0x00, // Unindexed Entry
+ 0x03, // Name Len: 3
+ 0x66, 0x6f, 0x6f, // foo
+ 0x03, // Value Len: 3
+ 0x62, 0x61, 0x72, // bar
+ };
+ // frame-format on
+ SpdyHeadersIR headers(/* stream_id = */ 1);
+ headers.SetHeader("bar", "foo");
+ headers.SetHeader("foo", "bar");
+ SpdySerializedFrame frame(SpdyFramerPeer::SerializeHeaders(
+ &framer, headers, use_output_ ? &output_ : nullptr));
+ CompareFrame(kDescription, frame, kH2FrameData,
+ SPDY_ARRAYSIZE(kH2FrameData));
+ }
+
+ {
+ const char kDescription[] =
+ "HEADERS frame with a 0-length header name, FIN, max stream ID";
+ // frame-format off
+ const unsigned char kH2FrameData[] = {
+ 0x00, 0x00, 0x0f, // Length: 15
+ 0x01, // Type: HEADERS
+ 0x05, // Flags: END_STREAM|END_HEADERS
+ 0x7f, 0xff, 0xff, 0xff, // Stream: 2147483647
+
+ 0x00, // Unindexed Entry
+ 0x00, // Name Len: 0
+ 0x03, // Value Len: 3
+ 0x66, 0x6f, 0x6f, // foo
+
+ 0x00, // Unindexed Entry
+ 0x03, // Name Len: 3
+ 0x66, 0x6f, 0x6f, // foo
+ 0x03, // Value Len: 3
+ 0x62, 0x61, 0x72, // bar
+ };
+ // frame-format on
+ SpdyHeadersIR headers(/* stream_id = */ 0x7fffffff);
+ headers.set_fin(true);
+ headers.SetHeader("", "foo");
+ headers.SetHeader("foo", "bar");
+ SpdySerializedFrame frame(SpdyFramerPeer::SerializeHeaders(
+ &framer, headers, use_output_ ? &output_ : nullptr));
+ CompareFrame(kDescription, frame, kH2FrameData,
+ SPDY_ARRAYSIZE(kH2FrameData));
+ }
+
+ {
+ const char kDescription[] =
+ "HEADERS frame with a 0-length header val, FIN, max stream ID";
+ // frame-format off
+ const unsigned char kH2FrameData[] = {
+ 0x00, 0x00, 0x0f, // Length: 15
+ 0x01, // Type: HEADERS
+ 0x05, // Flags: END_STREAM|END_HEADERS
+ 0x7f, 0xff, 0xff, 0xff, // Stream: 2147483647
+
+ 0x00, // Unindexed Entry
+ 0x03, // Name Len: 3
+ 0x62, 0x61, 0x72, // bar
+ 0x03, // Value Len: 3
+ 0x66, 0x6f, 0x6f, // foo
+
+ 0x00, // Unindexed Entry
+ 0x03, // Name Len: 3
+ 0x66, 0x6f, 0x6f, // foo
+ 0x00, // Value Len: 0
+ };
+ // frame-format on
+ SpdyHeadersIR headers_ir(/* stream_id = */ 0x7fffffff);
+ headers_ir.set_fin(true);
+ headers_ir.SetHeader("bar", "foo");
+ headers_ir.SetHeader("foo", "");
+ SpdySerializedFrame frame(SpdyFramerPeer::SerializeHeaders(
+ &framer, headers_ir, use_output_ ? &output_ : nullptr));
+ CompareFrame(kDescription, frame, kH2FrameData,
+ SPDY_ARRAYSIZE(kH2FrameData));
+ }
+
+ {
+ const char kDescription[] =
+ "HEADERS frame with a 0-length header val, FIN, max stream ID, pri";
+
+ // frame-format off
+ const unsigned char kH2FrameData[] = {
+ 0x00, 0x00, 0x14, // Length: 20
+ 0x01, // Type: HEADERS
+ 0x25, // Flags: END_STREAM|END_HEADERS|PRIORITY
+ 0x7f, 0xff, 0xff, 0xff, // Stream: 2147483647
+ 0x00, 0x00, 0x00, 0x00, // Parent: 0
+ 0xdb, // Weight: 220
+
+ 0x00, // Unindexed Entry
+ 0x03, // Name Len: 3
+ 0x62, 0x61, 0x72, // bar
+ 0x03, // Value Len: 3
+ 0x66, 0x6f, 0x6f, // foo
+
+ 0x00, // Unindexed Entry
+ 0x03, // Name Len: 3
+ 0x66, 0x6f, 0x6f, // foo
+ 0x00, // Value Len: 0
+ };
+ // frame-format on
+ SpdyHeadersIR headers_ir(/* stream_id = */ 0x7fffffff);
+ headers_ir.set_fin(true);
+ headers_ir.set_has_priority(true);
+ headers_ir.set_weight(220);
+ headers_ir.SetHeader("bar", "foo");
+ headers_ir.SetHeader("foo", "");
+ SpdySerializedFrame frame(SpdyFramerPeer::SerializeHeaders(
+ &framer, headers_ir, use_output_ ? &output_ : nullptr));
+ CompareFrame(kDescription, frame, kH2FrameData,
+ SPDY_ARRAYSIZE(kH2FrameData));
+ }
+
+ {
+ const char kDescription[] =
+ "HEADERS frame with a 0-length header val, FIN, max stream ID, pri, "
+ "exclusive=true, parent_stream=0";
+
+ // frame-format off
+ const unsigned char kV4FrameData[] = {
+ 0x00, 0x00, 0x14, // Length: 20
+ 0x01, // Type: HEADERS
+ 0x25, // Flags: END_STREAM|END_HEADERS|PRIORITY
+ 0x7f, 0xff, 0xff, 0xff, // Stream: 2147483647
+ 0x80, 0x00, 0x00, 0x00, // Parent: 0 (Exclusive)
+ 0xdb, // Weight: 220
+
+ 0x00, // Unindexed Entry
+ 0x03, // Name Len: 3
+ 0x62, 0x61, 0x72, // bar
+ 0x03, // Value Len: 3
+ 0x66, 0x6f, 0x6f, // foo
+
+ 0x00, // Unindexed Entry
+ 0x03, // Name Len: 3
+ 0x66, 0x6f, 0x6f, // foo
+ 0x00, // Value Len: 0
+ };
+ // frame-format on
+ SpdyHeadersIR headers_ir(/* stream_id = */ 0x7fffffff);
+ headers_ir.set_fin(true);
+ headers_ir.set_has_priority(true);
+ headers_ir.set_weight(220);
+ headers_ir.set_exclusive(true);
+ headers_ir.set_parent_stream_id(0);
+ headers_ir.SetHeader("bar", "foo");
+ headers_ir.SetHeader("foo", "");
+ SpdySerializedFrame frame(SpdyFramerPeer::SerializeHeaders(
+ &framer, headers_ir, use_output_ ? &output_ : nullptr));
+ CompareFrame(kDescription, frame, kV4FrameData,
+ SPDY_ARRAYSIZE(kV4FrameData));
+ }
+
+ {
+ const char kDescription[] =
+ "HEADERS frame with a 0-length header val, FIN, max stream ID, pri, "
+ "exclusive=false, parent_stream=max stream ID";
+
+ // frame-format off
+ const unsigned char kV4FrameData[] = {
+ 0x00, 0x00, 0x14, // Length: 20
+ 0x01, // Type: HEADERS
+ 0x25, // Flags: END_STREAM|END_HEADERS|PRIORITY
+ 0x7f, 0xff, 0xff, 0xff, // Stream: 2147483647
+ 0x7f, 0xff, 0xff, 0xff, // Parent: 2147483647
+ 0xdb, // Weight: 220
+
+ 0x00, // Unindexed Entry
+ 0x03, // Name Len: 3
+ 0x62, 0x61, 0x72, // bar
+ 0x03, // Value Len: 3
+ 0x66, 0x6f, 0x6f, // foo
+
+ 0x00, // Unindexed Entry
+ 0x03, // Name Len: 3
+ 0x66, 0x6f, 0x6f, // foo
+ 0x00, // Value Len: 0
+ };
+ // frame-format on
+ SpdyHeadersIR headers_ir(/* stream_id = */ 0x7fffffff);
+ headers_ir.set_fin(true);
+ headers_ir.set_has_priority(true);
+ headers_ir.set_weight(220);
+ headers_ir.set_exclusive(false);
+ headers_ir.set_parent_stream_id(0x7fffffff);
+ headers_ir.SetHeader("bar", "foo");
+ headers_ir.SetHeader("foo", "");
+ SpdySerializedFrame frame(SpdyFramerPeer::SerializeHeaders(
+ &framer, headers_ir, use_output_ ? &output_ : nullptr));
+ CompareFrame(kDescription, frame, kV4FrameData,
+ SPDY_ARRAYSIZE(kV4FrameData));
+ }
+
+ {
+ const char kDescription[] =
+ "HEADERS frame with a 0-length header name, FIN, max stream ID, padded";
+
+ // frame-format off
+ const unsigned char kH2FrameData[] = {
+ 0x00, 0x00, 0x15, // Length: 21
+ 0x01, // Type: HEADERS
+ 0x0d, // Flags: END_STREAM|END_HEADERS|PADDED
+ 0x7f, 0xff, 0xff, 0xff, // Stream: 2147483647
+ 0x05, // PadLen: 5 trailing bytes
+
+ 0x00, // Unindexed Entry
+ 0x00, // Name Len: 0
+ 0x03, // Value Len: 3
+ 0x66, 0x6f, 0x6f, // foo
+
+ 0x00, // Unindexed Entry
+ 0x03, // Name Len: 3
+ 0x66, 0x6f, 0x6f, // foo
+ 0x03, // Value Len: 3
+ 0x62, 0x61, 0x72, // bar
+
+ 0x00, 0x00, 0x00, 0x00, // Padding
+ 0x00, // Padding
+ };
+ // frame-format on
+ SpdyHeadersIR headers_ir(/* stream_id = */ 0x7fffffff);
+ headers_ir.set_fin(true);
+ headers_ir.SetHeader("", "foo");
+ headers_ir.SetHeader("foo", "bar");
+ headers_ir.set_padding_len(6);
+ SpdySerializedFrame frame(SpdyFramerPeer::SerializeHeaders(
+ &framer, headers_ir, use_output_ ? &output_ : nullptr));
+ CompareFrame(kDescription, frame, kH2FrameData,
+ SPDY_ARRAYSIZE(kH2FrameData));
+ }
+}
+
+TEST_P(SpdyFramerTest, CreateWindowUpdate) {
+ {
+ const char kDescription[] = "WINDOW_UPDATE frame";
+ const unsigned char kH2FrameData[] = {
+ 0x00, 0x00, 0x04, // Length: 4
+ 0x08, // Type: WINDOW_UPDATE
+ 0x00, // Flags: none
+ 0x00, 0x00, 0x00, 0x01, // Stream: 1
+ 0x00, 0x00, 0x00, 0x01, // Increment: 1
+ };
+ SpdySerializedFrame frame(framer_.SerializeWindowUpdate(
+ SpdyWindowUpdateIR(/* stream_id = */ 1, /* delta = */ 1)));
+ if (use_output_) {
+ output_.Reset();
+ ASSERT_TRUE(framer_.SerializeWindowUpdate(
+ SpdyWindowUpdateIR(/* stream_id = */ 1, /* delta = */ 1), &output_));
+ frame = SpdySerializedFrame(output_.Begin(), output_.Size(), false);
+ }
+ CompareFrame(kDescription, frame, kH2FrameData,
+ SPDY_ARRAYSIZE(kH2FrameData));
+ }
+
+ {
+ const char kDescription[] = "WINDOW_UPDATE frame with max stream ID";
+ const unsigned char kH2FrameData[] = {
+ 0x00, 0x00, 0x04, // Length: 4
+ 0x08, // Type: WINDOW_UPDATE
+ 0x00, // Flags: none
+ 0x7f, 0xff, 0xff, 0xff, // Stream: 0x7fffffff
+ 0x00, 0x00, 0x00, 0x01, // Increment: 1
+ };
+ SpdySerializedFrame frame(framer_.SerializeWindowUpdate(
+ SpdyWindowUpdateIR(/* stream_id = */ 0x7FFFFFFF, /* delta = */ 1)));
+ if (use_output_) {
+ output_.Reset();
+ ASSERT_TRUE(framer_.SerializeWindowUpdate(
+ SpdyWindowUpdateIR(/* stream_id = */ 0x7FFFFFFF, /* delta = */ 1),
+ &output_));
+ frame = SpdySerializedFrame(output_.Begin(), output_.Size(), false);
+ }
+ CompareFrame(kDescription, frame, kH2FrameData,
+ SPDY_ARRAYSIZE(kH2FrameData));
+ }
+
+ {
+ const char kDescription[] = "WINDOW_UPDATE frame with max window delta";
+ const unsigned char kH2FrameData[] = {
+ 0x00, 0x00, 0x04, // Length: 4
+ 0x08, // Type: WINDOW_UPDATE
+ 0x00, // Flags: none
+ 0x00, 0x00, 0x00, 0x01, // Stream: 1
+ 0x7f, 0xff, 0xff, 0xff, // Increment: 0x7fffffff
+ };
+ SpdySerializedFrame frame(framer_.SerializeWindowUpdate(
+ SpdyWindowUpdateIR(/* stream_id = */ 1, /* delta = */ 0x7FFFFFFF)));
+ if (use_output_) {
+ output_.Reset();
+ ASSERT_TRUE(framer_.SerializeWindowUpdate(
+ SpdyWindowUpdateIR(/* stream_id = */ 1, /* delta = */ 0x7FFFFFFF),
+ &output_));
+ frame = SpdySerializedFrame(output_.Begin(), output_.Size(), false);
+ }
+ CompareFrame(kDescription, frame, kH2FrameData,
+ SPDY_ARRAYSIZE(kH2FrameData));
+ }
+}
+
+TEST_P(SpdyFramerTest, CreatePushPromiseUncompressed) {
+ {
+ // Test framing PUSH_PROMISE without padding.
+ SpdyFramer framer(SpdyFramer::DISABLE_COMPRESSION);
+ const char kDescription[] = "PUSH_PROMISE frame without padding";
+
+ // frame-format off
+ const unsigned char kFrameData[] = {
+ 0x00, 0x00, 0x16, // Length: 22
+ 0x05, // Type: PUSH_PROMISE
+ 0x04, // Flags: END_HEADERS
+ 0x00, 0x00, 0x00, 0x29, // Stream: 41
+ 0x00, 0x00, 0x00, 0x3a, // Promise: 58
+
+ 0x00, // Unindexed Entry
+ 0x03, // Name Len: 3
+ 0x62, 0x61, 0x72, // bar
+ 0x03, // Value Len: 3
+ 0x66, 0x6f, 0x6f, // foo
+
+ 0x00, // Unindexed Entry
+ 0x03, // Name Len: 3
+ 0x66, 0x6f, 0x6f, // foo
+ 0x03, // Value Len: 3
+ 0x62, 0x61, 0x72, // bar
+ };
+ // frame-format on
+
+ SpdyPushPromiseIR push_promise(/* stream_id = */ 41,
+ /* promised_stream_id = */ 58);
+ push_promise.SetHeader("bar", "foo");
+ push_promise.SetHeader("foo", "bar");
+ SpdySerializedFrame frame(SpdyFramerPeer::SerializePushPromise(
+ &framer, push_promise, use_output_ ? &output_ : nullptr));
+ CompareFrame(kDescription, frame, kFrameData, SPDY_ARRAYSIZE(kFrameData));
+ }
+
+ {
+ // Test framing PUSH_PROMISE with one byte of padding.
+ SpdyFramer framer(SpdyFramer::DISABLE_COMPRESSION);
+ const char kDescription[] = "PUSH_PROMISE frame with one byte of padding";
+
+ // frame-format off
+ const unsigned char kFrameData[] = {
+ 0x00, 0x00, 0x17, // Length: 23
+ 0x05, // Type: PUSH_PROMISE
+ 0x0c, // Flags: END_HEADERS|PADDED
+ 0x00, 0x00, 0x00, 0x29, // Stream: 41
+ 0x00, // PadLen: 0 trailing bytes
+ 0x00, 0x00, 0x00, 0x3a, // Promise: 58
+
+ 0x00, // Unindexed Entry
+ 0x03, // Name Len: 3
+ 0x62, 0x61, 0x72, // bar
+ 0x03, // Value Len: 3
+ 0x66, 0x6f, 0x6f, // foo
+
+ 0x00, // Unindexed Entry
+ 0x03, // Name Len: 3
+ 0x66, 0x6f, 0x6f, // foo
+ 0x03, // Value Len: 3
+ 0x62, 0x61, 0x72, // bar
+ };
+ // frame-format on
+
+ SpdyPushPromiseIR push_promise(/* stream_id = */ 41,
+ /* promised_stream_id = */ 58);
+ push_promise.set_padding_len(1);
+ push_promise.SetHeader("bar", "foo");
+ push_promise.SetHeader("foo", "bar");
+ output_.Reset();
+ SpdySerializedFrame frame(SpdyFramerPeer::SerializePushPromise(
+ &framer, push_promise, use_output_ ? &output_ : nullptr));
+
+ CompareFrame(kDescription, frame, kFrameData, SPDY_ARRAYSIZE(kFrameData));
+ }
+
+ {
+ // Test framing PUSH_PROMISE with 177 bytes of padding.
+ SpdyFramer framer(SpdyFramer::DISABLE_COMPRESSION);
+ const char kDescription[] = "PUSH_PROMISE frame with 177 bytes of padding";
+
+ // frame-format off
+ // clang-format off
+ const unsigned char kFrameData[] = {
+ 0x00, 0x00, 0xc7, // Length: 199
+ 0x05, // Type: PUSH_PROMISE
+ 0x0c, // Flags: END_HEADERS|PADDED
+ 0x00, 0x00, 0x00, 0x2a, // Stream: 42
+ 0xb0, // PadLen: 176 trailing bytes
+ 0x00, 0x00, 0x00, 0x39, // Promise: 57
+
+ 0x00, // Unindexed Entry
+ 0x03, // Name Len: 3
+ 0x62, 0x61, 0x72, // bar
+ 0x03, // Value Len: 3
+ 0x66, 0x6f, 0x6f, // foo
+
+ 0x00, // Unindexed Entry
+ 0x03, // Name Len: 3
+ 0x66, 0x6f, 0x6f, // foo
+ 0x03, // Value Len: 3
+ 0x62, 0x61, 0x72, // bar
+
+ // Padding of 176 0x00(s).
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ };
+ // clang-format on
+ // frame-format on
+
+ SpdyPushPromiseIR push_promise(/* stream_id = */ 42,
+ /* promised_stream_id = */ 57);
+ push_promise.set_padding_len(177);
+ push_promise.SetHeader("bar", "foo");
+ push_promise.SetHeader("foo", "bar");
+ output_.Reset();
+ SpdySerializedFrame frame(SpdyFramerPeer::SerializePushPromise(
+ &framer, push_promise, use_output_ ? &output_ : nullptr));
+
+ CompareFrame(kDescription, frame, kFrameData, SPDY_ARRAYSIZE(kFrameData));
+ }
+}
+
+// Regression test for https://crbug.com/464748.
+TEST_P(SpdyFramerTest, GetNumberRequiredContinuationFrames) {
+ EXPECT_EQ(1u, GetNumberRequiredContinuationFrames(16383 + 16374));
+ EXPECT_EQ(2u, GetNumberRequiredContinuationFrames(16383 + 16374 + 1));
+ EXPECT_EQ(2u, GetNumberRequiredContinuationFrames(16383 + 2 * 16374));
+ EXPECT_EQ(3u, GetNumberRequiredContinuationFrames(16383 + 2 * 16374 + 1));
+}
+
+TEST_P(SpdyFramerTest, CreateContinuationUncompressed) {
+ SpdyFramer framer(SpdyFramer::DISABLE_COMPRESSION);
+ const char kDescription[] = "CONTINUATION frame";
+
+ // frame-format off
+ const unsigned char kFrameData[] = {
+ 0x00, 0x00, 0x12, // Length: 18
+ 0x09, // Type: CONTINUATION
+ 0x04, // Flags: END_HEADERS
+ 0x00, 0x00, 0x00, 0x2a, // Stream: 42
+
+ 0x00, // Unindexed Entry
+ 0x03, // Name Len: 3
+ 0x62, 0x61, 0x72, // bar
+ 0x03, // Value Len: 3
+ 0x66, 0x6f, 0x6f, // foo
+
+ 0x00, // Unindexed Entry
+ 0x03, // Name Len: 3
+ 0x66, 0x6f, 0x6f, // foo
+ 0x03, // Value Len: 3
+ 0x62, 0x61, 0x72, // bar
+ };
+ // frame-format on
+
+ SpdyHeaderBlock header_block;
+ header_block["bar"] = "foo";
+ header_block["foo"] = "bar";
+ auto buffer = SpdyMakeUnique<SpdyString>();
+ HpackEncoder encoder(ObtainHpackHuffmanTable());
+ encoder.DisableCompression();
+ encoder.EncodeHeaderSet(header_block, buffer.get());
+
+ SpdyContinuationIR continuation(/* stream_id = */ 42);
+ continuation.take_encoding(std::move(buffer));
+ continuation.set_end_headers(true);
+
+ SpdySerializedFrame frame(framer.SerializeContinuation(continuation));
+ if (use_output_) {
+ ASSERT_TRUE(framer.SerializeContinuation(continuation, &output_));
+ frame = SpdySerializedFrame(output_.Begin(), output_.Size(), false);
+ }
+ CompareFrame(kDescription, frame, kFrameData, SPDY_ARRAYSIZE(kFrameData));
+}
+
+// Test that if we send an unexpected CONTINUATION
+// we signal an error (but don't crash).
+TEST_P(SpdyFramerTest, SendUnexpectedContinuation) {
+ testing::StrictMock<test::MockSpdyFramerVisitor> visitor;
+
+ deframer_.set_visitor(&visitor);
+
+ // frame-format off
+ char kH2FrameData[] = {
+ 0x00, 0x00, 0x12, // Length: 18
+ 0x09, // Type: CONTINUATION
+ 0x04, // Flags: END_HEADERS
+ 0x00, 0x00, 0x00, 0x2a, // Stream: 42
+
+ 0x00, // Unindexed Entry
+ 0x03, // Name Len: 3
+ 0x62, 0x61, 0x72, // bar
+ 0x03, // Value Len: 3
+ 0x66, 0x6f, 0x6f, // foo
+
+ 0x00, // Unindexed Entry
+ 0x03, // Name Len: 3
+ 0x66, 0x6f, 0x6f, // foo
+ 0x03, // Value Len: 3
+ 0x62, 0x61, 0x72, // bar
+ };
+ // frame-format on
+
+ SpdySerializedFrame frame(kH2FrameData, sizeof(kH2FrameData), false);
+
+ // We shouldn't have to read the whole frame before we signal an error.
+ EXPECT_CALL(visitor, OnError(Http2DecoderAdapter::SPDY_UNEXPECTED_FRAME));
+ EXPECT_GT(frame.size(), deframer_.ProcessInput(frame.data(), frame.size()));
+ EXPECT_TRUE(deframer_.HasError());
+ EXPECT_EQ(Http2DecoderAdapter::SPDY_UNEXPECTED_FRAME,
+ deframer_.spdy_framer_error())
+ << Http2DecoderAdapter::SpdyFramerErrorToString(
+ deframer_.spdy_framer_error());
+}
+
+TEST_P(SpdyFramerTest, CreatePushPromiseThenContinuationUncompressed) {
+ {
+ // Test framing in a case such that a PUSH_PROMISE frame, with one byte of
+ // padding, cannot hold all the data payload, which is overflowed to the
+ // consecutive CONTINUATION frame.
+ SpdyFramer framer(SpdyFramer::DISABLE_COMPRESSION);
+ const char kDescription[] =
+ "PUSH_PROMISE and CONTINUATION frames with one byte of padding";
+
+ // frame-format off
+ const unsigned char kPartialPushPromiseFrameData[] = {
+ 0x00, 0x3f, 0xf6, // Length: 16374
+ 0x05, // Type: PUSH_PROMISE
+ 0x08, // Flags: PADDED
+ 0x00, 0x00, 0x00, 0x2a, // Stream: 42
+ 0x00, // PadLen: 0 trailing bytes
+ 0x00, 0x00, 0x00, 0x39, // Promise: 57
+
+ 0x00, // Unindexed Entry
+ 0x03, // Name Len: 3
+ 0x78, 0x78, 0x78, // xxx
+ 0x7f, 0x80, 0x7f, // Value Len: 16361
+ 0x78, 0x78, 0x78, 0x78, // xxxx
+ 0x78, 0x78, 0x78, 0x78, // xxxx
+ 0x78, 0x78, 0x78, 0x78, // xxxx
+ 0x78, 0x78, 0x78, 0x78, // xxxx
+ 0x78, 0x78, 0x78, 0x78, // xxxx
+ 0x78, 0x78, 0x78, 0x78, // xxxx
+ 0x78, 0x78, 0x78, 0x78, // xxxx
+ 0x78, 0x78, 0x78, 0x78, // xxxx
+ 0x78, 0x78, 0x78, 0x78, // xxxx
+ 0x78, 0x78, 0x78, 0x78, // xxxx
+ 0x78, 0x78, 0x78, 0x78, // xxxx
+ 0x78, 0x78, 0x78, 0x78, // xxxx
+ 0x78, 0x78, 0x78, 0x78, // xxxx
+ 0x78, 0x78, 0x78, 0x78, // xxxx
+ 0x78, 0x78, 0x78, 0x78, // xxxx
+ 0x78, 0x78, 0x78, 0x78, // xxxx
+ 0x78, 0x78, 0x78, 0x78, // xxxx
+ 0x78, 0x78, 0x78, 0x78, // xxxx
+ 0x78, 0x78, 0x78, 0x78, // xxxx
+ 0x78, 0x78, 0x78, 0x78, // xxxx
+ 0x78, 0x78, 0x78, 0x78, // xxxx
+ };
+ const unsigned char kContinuationFrameData[] = {
+ 0x00, 0x00, 0x16, // Length: 22
+ 0x09, // Type: CONTINUATION
+ 0x04, // Flags: END_HEADERS
+ 0x00, 0x00, 0x00, 0x2a, // Stream: 42
+ 0x78, 0x78, 0x78, 0x78, // xxxx
+ 0x78, 0x78, 0x78, 0x78, // xxxx
+ 0x78, 0x78, 0x78, 0x78, // xxxx
+ 0x78, 0x78, 0x78, 0x78, // xxxx
+ 0x78, 0x78, 0x78, 0x78, // xxxx
+ 0x78, // x
+ };
+ // frame-format on
+
+ SpdyPushPromiseIR push_promise(/* stream_id = */ 42,
+ /* promised_stream_id = */ 57);
+ push_promise.set_padding_len(1);
+ SpdyString big_value(kHttp2MaxControlFrameSendSize, 'x');
+ push_promise.SetHeader("xxx", big_value);
+ SpdySerializedFrame frame(SpdyFramerPeer::SerializePushPromise(
+ &framer, push_promise, use_output_ ? &output_ : nullptr));
+
+ // The entire frame should look like below:
+ // Name Length in Byte
+ // ------------------------------------------- Begin of PUSH_PROMISE frame
+ // PUSH_PROMISE header 9
+ // Pad length field 1
+ // Promised stream 4
+ // Length field of key 2
+ // Content of key 3
+ // Length field of value 3
+ // Part of big_value 16361
+ // ------------------------------------------- Begin of CONTINUATION frame
+ // CONTINUATION header 9
+ // Remaining of big_value 22
+ // ------------------------------------------- End
+
+ // Length of everything listed above except big_value.
+ int len_non_data_payload = 31;
+ EXPECT_EQ(kHttp2MaxControlFrameSendSize + len_non_data_payload,
+ frame.size());
+
+ // Partially compare the PUSH_PROMISE frame against the template.
+ const unsigned char* frame_data =
+ reinterpret_cast<const unsigned char*>(frame.data());
+ CompareCharArraysWithHexError(kDescription, frame_data,
+ SPDY_ARRAYSIZE(kPartialPushPromiseFrameData),
+ kPartialPushPromiseFrameData,
+ SPDY_ARRAYSIZE(kPartialPushPromiseFrameData));
+
+ // Compare the CONTINUATION frame against the template.
+ frame_data += kHttp2MaxControlFrameSendSize;
+ CompareCharArraysWithHexError(
+ kDescription, frame_data, SPDY_ARRAYSIZE(kContinuationFrameData),
+ kContinuationFrameData, SPDY_ARRAYSIZE(kContinuationFrameData));
+ }
+}
+
+TEST_P(SpdyFramerTest, CreateAltSvc) {
+ const char kDescription[] = "ALTSVC frame";
+ const unsigned char kType = SerializeFrameType(SpdyFrameType::ALTSVC);
+ const unsigned char kFrameData[] = {
+ 0x00, 0x00, 0x49, kType, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x06, 'o',
+ 'r', 'i', 'g', 'i', 'n', 'p', 'i', 'd', '1', '=', '"', 'h',
+ 'o', 's', 't', ':', '4', '4', '3', '"', ';', ' ', 'm', 'a',
+ '=', '5', ',', 'p', '%', '2', '2', '%', '3', 'D', 'i', '%',
+ '3', 'A', 'd', '=', '"', 'h', '_', '\\', '\\', 'o', '\\', '"',
+ 's', 't', ':', '1', '2', '3', '"', ';', ' ', 'm', 'a', '=',
+ '4', '2', ';', ' ', 'v', '=', '"', '2', '4', '"'};
+ SpdyAltSvcIR altsvc_ir(/* stream_id = */ 3);
+ altsvc_ir.set_origin("origin");
+ altsvc_ir.add_altsvc(SpdyAltSvcWireFormat::AlternativeService(
+ "pid1", "host", 443, 5, SpdyAltSvcWireFormat::VersionVector()));
+ altsvc_ir.add_altsvc(SpdyAltSvcWireFormat::AlternativeService(
+ "p\"=i:d", "h_\\o\"st", 123, 42,
+ SpdyAltSvcWireFormat::VersionVector{24}));
+ SpdySerializedFrame frame(framer_.SerializeFrame(altsvc_ir));
+ if (use_output_) {
+ EXPECT_EQ(framer_.SerializeFrame(altsvc_ir, &output_), frame.size());
+ frame = SpdySerializedFrame(output_.Begin(), output_.Size(), false);
+ }
+ CompareFrame(kDescription, frame, kFrameData, SPDY_ARRAYSIZE(kFrameData));
+}
+
+TEST_P(SpdyFramerTest, CreatePriority) {
+ const char kDescription[] = "PRIORITY frame";
+ const unsigned char kFrameData[] = {
+ 0x00, 0x00, 0x05, // Length: 5
+ 0x02, // Type: PRIORITY
+ 0x00, // Flags: none
+ 0x00, 0x00, 0x00, 0x02, // Stream: 2
+ 0x80, 0x00, 0x00, 0x01, // Parent: 1 (Exclusive)
+ 0x10, // Weight: 17
+ };
+ SpdyPriorityIR priority_ir(/* stream_id = */ 2,
+ /* parent_stream_id = */ 1,
+ /* weight = */ 17,
+ /* exclusive = */ true);
+ SpdySerializedFrame frame(framer_.SerializeFrame(priority_ir));
+ if (use_output_) {
+ EXPECT_EQ(framer_.SerializeFrame(priority_ir, &output_), frame.size());
+ frame = SpdySerializedFrame(output_.Begin(), output_.Size(), false);
+ }
+ CompareFrame(kDescription, frame, kFrameData, SPDY_ARRAYSIZE(kFrameData));
+}
+
+TEST_P(SpdyFramerTest, CreateUnknown) {
+ const char kDescription[] = "Unknown frame";
+ const uint8_t kType = 0xaf;
+ const uint8_t kFlags = 0x11;
+ const uint8_t kLength = strlen(kDescription);
+ const unsigned char kFrameData[] = {
+ 0x00, 0x00, kLength, // Length: 13
+ kType, // Type: undefined
+ kFlags, // Flags: arbitrary, undefined
+ 0x00, 0x00, 0x00, 0x02, // Stream: 2
+ 0x55, 0x6e, 0x6b, 0x6e, // "Unkn"
+ 0x6f, 0x77, 0x6e, 0x20, // "own "
+ 0x66, 0x72, 0x61, 0x6d, // "fram"
+ 0x65, // "e"
+ };
+ SpdyUnknownIR unknown_ir(/* stream_id = */ 2,
+ /* type = */ kType,
+ /* flags = */ kFlags,
+ /* payload = */ kDescription);
+ SpdySerializedFrame frame(framer_.SerializeFrame(unknown_ir));
+ if (use_output_) {
+ EXPECT_EQ(framer_.SerializeFrame(unknown_ir, &output_), frame.size());
+ frame = SpdySerializedFrame(output_.Begin(), output_.Size(), false);
+ }
+ CompareFrame(kDescription, frame, kFrameData, SPDY_ARRAYSIZE(kFrameData));
+}
+
+// Test serialization of a SpdyUnknownIR with a defined type, a length field
+// that does not match the payload size and in fact exceeds framer limits, and a
+// stream ID that effectively flips the reserved bit.
+TEST_P(SpdyFramerTest, CreateUnknownUnchecked) {
+ const char kDescription[] = "Unknown frame";
+ const uint8_t kType = 0x00;
+ const uint8_t kFlags = 0x11;
+ const uint8_t kLength = std::numeric_limits<uint8_t>::max();
+ const unsigned int kStreamId = kStreamIdMask + 42;
+ const unsigned char kFrameData[] = {
+ 0x00, 0x00, kLength, // Length: 16426
+ kType, // Type: DATA, defined
+ kFlags, // Flags: arbitrary, undefined
+ 0x80, 0x00, 0x00, 0x29, // Stream: 2147483689
+ 0x55, 0x6e, 0x6b, 0x6e, // "Unkn"
+ 0x6f, 0x77, 0x6e, 0x20, // "own "
+ 0x66, 0x72, 0x61, 0x6d, // "fram"
+ 0x65, // "e"
+ };
+ TestSpdyUnknownIR unknown_ir(/* stream_id = */ kStreamId,
+ /* type = */ kType,
+ /* flags = */ kFlags,
+ /* payload = */ kDescription);
+ unknown_ir.set_length(kLength);
+ SpdySerializedFrame frame(framer_.SerializeFrame(unknown_ir));
+ if (use_output_) {
+ EXPECT_EQ(framer_.SerializeFrame(unknown_ir, &output_), frame.size());
+ frame = SpdySerializedFrame(output_.Begin(), output_.Size(), false);
+ }
+ CompareFrame(kDescription, frame, kFrameData, SPDY_ARRAYSIZE(kFrameData));
+}
+
+TEST_P(SpdyFramerTest, ReadCompressedHeadersHeaderBlock) {
+ SpdyHeadersIR headers_ir(/* stream_id = */ 1);
+ headers_ir.SetHeader("alpha", "beta");
+ headers_ir.SetHeader("gamma", "delta");
+ SpdySerializedFrame control_frame(SpdyFramerPeer::SerializeHeaders(
+ &framer_, headers_ir, use_output_ ? &output_ : nullptr));
+ TestSpdyVisitor visitor(SpdyFramer::ENABLE_COMPRESSION);
+ visitor.SimulateInFramer(
+ reinterpret_cast<unsigned char*>(control_frame.data()),
+ control_frame.size());
+ EXPECT_EQ(1, visitor.headers_frame_count_);
+ EXPECT_EQ(0, visitor.control_frame_header_data_count_);
+ EXPECT_EQ(0, visitor.zero_length_control_frame_header_data_count_);
+ EXPECT_EQ(0, visitor.end_of_stream_count_);
+ EXPECT_EQ(headers_ir.header_block(), visitor.headers_);
+}
+
+TEST_P(SpdyFramerTest, ReadCompressedHeadersHeaderBlockWithHalfClose) {
+ SpdyHeadersIR headers_ir(/* stream_id = */ 1);
+ headers_ir.set_fin(true);
+ headers_ir.SetHeader("alpha", "beta");
+ headers_ir.SetHeader("gamma", "delta");
+ SpdySerializedFrame control_frame(SpdyFramerPeer::SerializeHeaders(
+ &framer_, headers_ir, use_output_ ? &output_ : nullptr));
+ TestSpdyVisitor visitor(SpdyFramer::ENABLE_COMPRESSION);
+ visitor.SimulateInFramer(
+ reinterpret_cast<unsigned char*>(control_frame.data()),
+ control_frame.size());
+ EXPECT_EQ(1, visitor.headers_frame_count_);
+ EXPECT_EQ(0, visitor.control_frame_header_data_count_);
+ EXPECT_EQ(0, visitor.zero_length_control_frame_header_data_count_);
+ EXPECT_EQ(1, visitor.end_of_stream_count_);
+ EXPECT_EQ(headers_ir.header_block(), visitor.headers_);
+}
+
+TEST_P(SpdyFramerTest, TooLargeHeadersFrameUsesContinuation) {
+ SpdyFramer framer(SpdyFramer::DISABLE_COMPRESSION);
+ SpdyHeadersIR headers(/* stream_id = */ 1);
+ headers.set_padding_len(256);
+
+ // Exact payload length will change with HPACK, but this should be long
+ // enough to cause an overflow.
+ const size_t kBigValueSize = kHttp2MaxControlFrameSendSize;
+ SpdyString big_value(kBigValueSize, 'x');
+ headers.SetHeader("aa", big_value);
+ SpdySerializedFrame control_frame(SpdyFramerPeer::SerializeHeaders(
+ &framer, headers, use_output_ ? &output_ : nullptr));
+ EXPECT_GT(control_frame.size(), kHttp2MaxControlFrameSendSize);
+
+ TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION);
+ visitor.SimulateInFramer(
+ reinterpret_cast<unsigned char*>(control_frame.data()),
+ control_frame.size());
+ EXPECT_TRUE(visitor.header_buffer_valid_);
+ EXPECT_EQ(0, visitor.error_count_);
+ EXPECT_EQ(1, visitor.headers_frame_count_);
+ EXPECT_EQ(1, visitor.continuation_count_);
+ EXPECT_EQ(0, visitor.zero_length_control_frame_header_data_count_);
+}
+
+TEST_P(SpdyFramerTest, MultipleContinuationFramesWithIterator) {
+ SpdyFramer framer(SpdyFramer::DISABLE_COMPRESSION);
+ auto headers = SpdyMakeUnique<SpdyHeadersIR>(/* stream_id = */ 1);
+ headers->set_padding_len(256);
+
+ // Exact payload length will change with HPACK, but this should be long
+ // enough to cause an overflow.
+ const size_t kBigValueSize = kHttp2MaxControlFrameSendSize;
+ SpdyString big_valuex(kBigValueSize, 'x');
+ headers->SetHeader("aa", big_valuex);
+ SpdyString big_valuez(kBigValueSize, 'z');
+ headers->SetHeader("bb", big_valuez);
+
+ SpdyFramer::SpdyHeaderFrameIterator frame_it(&framer, std::move(headers));
+
+ EXPECT_TRUE(frame_it.HasNextFrame());
+ EXPECT_GT(frame_it.NextFrame(&output_), 0u);
+ SpdySerializedFrame headers_frame(output_.Begin(), output_.Size(), false);
+ EXPECT_EQ(headers_frame.size(), kHttp2MaxControlFrameSendSize);
+
+ TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION);
+ visitor.SimulateInFramer(
+ reinterpret_cast<unsigned char*>(headers_frame.data()),
+ headers_frame.size());
+ EXPECT_TRUE(visitor.header_buffer_valid_);
+ EXPECT_EQ(0, visitor.error_count_);
+ EXPECT_EQ(1, visitor.headers_frame_count_);
+ EXPECT_EQ(0, visitor.continuation_count_);
+ EXPECT_EQ(0, visitor.zero_length_control_frame_header_data_count_);
+
+ output_.Reset();
+ EXPECT_TRUE(frame_it.HasNextFrame());
+ EXPECT_GT(frame_it.NextFrame(&output_), 0u);
+ SpdySerializedFrame first_cont_frame(output_.Begin(), output_.Size(), false);
+ EXPECT_EQ(first_cont_frame.size(), kHttp2MaxControlFrameSendSize);
+
+ visitor.SimulateInFramer(
+ reinterpret_cast<unsigned char*>(first_cont_frame.data()),
+ first_cont_frame.size());
+ EXPECT_TRUE(visitor.header_buffer_valid_);
+ EXPECT_EQ(0, visitor.error_count_);
+ EXPECT_EQ(1, visitor.headers_frame_count_);
+ EXPECT_EQ(1, visitor.continuation_count_);
+ EXPECT_EQ(0, visitor.zero_length_control_frame_header_data_count_);
+
+ output_.Reset();
+ EXPECT_TRUE(frame_it.HasNextFrame());
+ EXPECT_GT(frame_it.NextFrame(&output_), 0u);
+ SpdySerializedFrame second_cont_frame(output_.Begin(), output_.Size(), false);
+ EXPECT_LT(second_cont_frame.size(), kHttp2MaxControlFrameSendSize);
+
+ visitor.SimulateInFramer(
+ reinterpret_cast<unsigned char*>(second_cont_frame.data()),
+ second_cont_frame.size());
+ EXPECT_TRUE(visitor.header_buffer_valid_);
+ EXPECT_EQ(0, visitor.error_count_);
+ EXPECT_EQ(1, visitor.headers_frame_count_);
+ EXPECT_EQ(2, visitor.continuation_count_);
+ EXPECT_EQ(0, visitor.zero_length_control_frame_header_data_count_);
+
+ EXPECT_FALSE(frame_it.HasNextFrame());
+}
+
+TEST_P(SpdyFramerTest, PushPromiseFramesWithIterator) {
+ SpdyFramer framer(SpdyFramer::DISABLE_COMPRESSION);
+ auto push_promise =
+ SpdyMakeUnique<SpdyPushPromiseIR>(/* stream_id = */ 1,
+ /* promised_stream_id = */ 2);
+ push_promise->set_padding_len(256);
+
+ // Exact payload length will change with HPACK, but this should be long
+ // enough to cause an overflow.
+ const size_t kBigValueSize = kHttp2MaxControlFrameSendSize;
+ SpdyString big_valuex(kBigValueSize, 'x');
+ push_promise->SetHeader("aa", big_valuex);
+ SpdyString big_valuez(kBigValueSize, 'z');
+ push_promise->SetHeader("bb", big_valuez);
+
+ SpdyFramer::SpdyPushPromiseFrameIterator frame_it(&framer,
+ std::move(push_promise));
+
+ EXPECT_TRUE(frame_it.HasNextFrame());
+ EXPECT_GT(frame_it.NextFrame(&output_), 0u);
+ SpdySerializedFrame push_promise_frame(output_.Begin(), output_.Size(),
+ false);
+ EXPECT_EQ(push_promise_frame.size(), kHttp2MaxControlFrameSendSize);
+
+ TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION);
+ visitor.SimulateInFramer(
+ reinterpret_cast<unsigned char*>(push_promise_frame.data()),
+ push_promise_frame.size());
+ EXPECT_TRUE(visitor.header_buffer_valid_);
+ EXPECT_EQ(0, visitor.error_count_);
+ EXPECT_EQ(1, visitor.push_promise_frame_count_);
+ EXPECT_EQ(0, visitor.continuation_count_);
+ EXPECT_EQ(0, visitor.zero_length_control_frame_header_data_count_);
+
+ EXPECT_TRUE(frame_it.HasNextFrame());
+ output_.Reset();
+ EXPECT_GT(frame_it.NextFrame(&output_), 0u);
+ SpdySerializedFrame first_cont_frame(output_.Begin(), output_.Size(), false);
+
+ EXPECT_EQ(first_cont_frame.size(), kHttp2MaxControlFrameSendSize);
+ visitor.SimulateInFramer(
+ reinterpret_cast<unsigned char*>(first_cont_frame.data()),
+ first_cont_frame.size());
+ EXPECT_TRUE(visitor.header_buffer_valid_);
+ EXPECT_EQ(0, visitor.error_count_);
+ EXPECT_EQ(1, visitor.push_promise_frame_count_);
+ EXPECT_EQ(1, visitor.continuation_count_);
+ EXPECT_EQ(0, visitor.zero_length_control_frame_header_data_count_);
+
+ EXPECT_TRUE(frame_it.HasNextFrame());
+ output_.Reset();
+ EXPECT_GT(frame_it.NextFrame(&output_), 0u);
+ SpdySerializedFrame second_cont_frame(output_.Begin(), output_.Size(), false);
+ EXPECT_LT(second_cont_frame.size(), kHttp2MaxControlFrameSendSize);
+
+ visitor.SimulateInFramer(
+ reinterpret_cast<unsigned char*>(second_cont_frame.data()),
+ second_cont_frame.size());
+ EXPECT_TRUE(visitor.header_buffer_valid_);
+ EXPECT_EQ(0, visitor.error_count_);
+ EXPECT_EQ(1, visitor.push_promise_frame_count_);
+ EXPECT_EQ(2, visitor.continuation_count_);
+ EXPECT_EQ(0, visitor.zero_length_control_frame_header_data_count_);
+
+ EXPECT_FALSE(frame_it.HasNextFrame());
+}
+
+class SpdyControlFrameIteratorTest : public ::testing::Test {
+ public:
+ SpdyControlFrameIteratorTest() : output_(output_buffer, kSize) {}
+
+ void RunTest(std::unique_ptr<SpdyFrameIR> ir) {
+ SpdyFramer framer(SpdyFramer::DISABLE_COMPRESSION);
+ SpdySerializedFrame frame(framer.SerializeFrame(*ir));
+ std::unique_ptr<SpdyFrameSequence> it =
+ SpdyFramer::CreateIterator(&framer, std::move(ir));
+ EXPECT_TRUE(it->HasNextFrame());
+ EXPECT_EQ(it->NextFrame(&output_), frame.size());
+ EXPECT_FALSE(it->HasNextFrame());
+ }
+
+ private:
+ ArrayOutputBuffer output_;
+};
+
+TEST_F(SpdyControlFrameIteratorTest, RstStreamFrameWithIterator) {
+ auto ir = SpdyMakeUnique<SpdyRstStreamIR>(0, ERROR_CODE_PROTOCOL_ERROR);
+ RunTest(std::move(ir));
+}
+
+TEST_F(SpdyControlFrameIteratorTest, SettingsFrameWithIterator) {
+ auto ir = SpdyMakeUnique<SpdySettingsIR>();
+ uint32_t kValue = 0x0a0b0c0d;
+ SpdyKnownSettingsId kId = SETTINGS_INITIAL_WINDOW_SIZE;
+ ir->AddSetting(kId, kValue);
+ RunTest(std::move(ir));
+}
+
+TEST_F(SpdyControlFrameIteratorTest, PingFrameWithIterator) {
+ const SpdyPingId kPingId = 0x123456789abcdeffULL;
+ auto ir = SpdyMakeUnique<SpdyPingIR>(kPingId);
+ RunTest(std::move(ir));
+}
+
+TEST_F(SpdyControlFrameIteratorTest, GoAwayFrameWithIterator) {
+ auto ir = SpdyMakeUnique<SpdyGoAwayIR>(0, ERROR_CODE_NO_ERROR, "GA");
+ RunTest(std::move(ir));
+}
+
+TEST_F(SpdyControlFrameIteratorTest, WindowUpdateFrameWithIterator) {
+ auto ir = SpdyMakeUnique<SpdyWindowUpdateIR>(1, 1);
+ RunTest(std::move(ir));
+}
+
+TEST_F(SpdyControlFrameIteratorTest, AtlSvcFrameWithIterator) {
+ auto ir = SpdyMakeUnique<SpdyAltSvcIR>(3);
+ ir->set_origin("origin");
+ ir->add_altsvc(SpdyAltSvcWireFormat::AlternativeService(
+ "pid1", "host", 443, 5, SpdyAltSvcWireFormat::VersionVector()));
+ ir->add_altsvc(SpdyAltSvcWireFormat::AlternativeService(
+ "p\"=i:d", "h_\\o\"st", 123, 42,
+ SpdyAltSvcWireFormat::VersionVector{24}));
+ RunTest(std::move(ir));
+}
+
+TEST_F(SpdyControlFrameIteratorTest, PriorityFrameWithIterator) {
+ auto ir = SpdyMakeUnique<SpdyPriorityIR>(2, 1, 17, true);
+ RunTest(std::move(ir));
+}
+
+TEST_P(SpdyFramerTest, TooLargePushPromiseFrameUsesContinuation) {
+ SpdyFramer framer(SpdyFramer::DISABLE_COMPRESSION);
+ SpdyPushPromiseIR push_promise(/* stream_id = */ 1,
+ /* promised_stream_id = */ 2);
+ push_promise.set_padding_len(256);
+
+ // Exact payload length will change with HPACK, but this should be long
+ // enough to cause an overflow.
+ const size_t kBigValueSize = kHttp2MaxControlFrameSendSize;
+ SpdyString big_value(kBigValueSize, 'x');
+ push_promise.SetHeader("aa", big_value);
+ SpdySerializedFrame control_frame(SpdyFramerPeer::SerializePushPromise(
+ &framer, push_promise, use_output_ ? &output_ : nullptr));
+ EXPECT_GT(control_frame.size(), kHttp2MaxControlFrameSendSize);
+
+ TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION);
+ visitor.SimulateInFramer(
+ reinterpret_cast<unsigned char*>(control_frame.data()),
+ control_frame.size());
+ EXPECT_TRUE(visitor.header_buffer_valid_);
+ EXPECT_EQ(0, visitor.error_count_);
+ EXPECT_EQ(1, visitor.push_promise_frame_count_);
+ EXPECT_EQ(1, visitor.continuation_count_);
+ EXPECT_EQ(0, visitor.zero_length_control_frame_header_data_count_);
+}
+
+// Check that the framer stops delivering header data chunks once the visitor
+// declares it doesn't want any more. This is important to guard against
+// "zip bomb" types of attacks.
+TEST_P(SpdyFramerTest, ControlFrameMuchTooLarge) {
+ const size_t kHeaderBufferChunks = 4;
+ const size_t kHeaderBufferSize =
+ kHttp2DefaultFramePayloadLimit / kHeaderBufferChunks;
+ const size_t kBigValueSize = kHeaderBufferSize * 2;
+ SpdyString big_value(kBigValueSize, 'x');
+ SpdyHeadersIR headers(/* stream_id = */ 1);
+ headers.set_fin(true);
+ headers.SetHeader("aa", big_value);
+ SpdySerializedFrame control_frame(SpdyFramerPeer::SerializeHeaders(
+ &framer_, headers, use_output_ ? &output_ : nullptr));
+ TestSpdyVisitor visitor(SpdyFramer::ENABLE_COMPRESSION);
+ visitor.set_header_buffer_size(kHeaderBufferSize);
+ visitor.SimulateInFramer(
+ reinterpret_cast<unsigned char*>(control_frame.data()),
+ control_frame.size());
+ // It's up to the visitor to ignore extraneous header data; the framer
+ // won't throw an error.
+ EXPECT_GT(visitor.header_bytes_received_, visitor.header_buffer_size_);
+ EXPECT_EQ(1, visitor.end_of_stream_count_);
+}
+
+TEST_P(SpdyFramerTest, ControlFrameSizesAreValidated) {
+ // Create a GoAway frame that has a few extra bytes at the end.
+ const size_t length = 20;
+
+ // HTTP/2 GOAWAY frames are only bound by a minimal length, since they may
+ // carry opaque data. Verify that minimal length is tested.
+ ASSERT_GT(kGoawayFrameMinimumSize, kFrameHeaderSize);
+ const size_t less_than_min_length =
+ kGoawayFrameMinimumSize - kFrameHeaderSize - 1;
+ ASSERT_LE(less_than_min_length, std::numeric_limits<unsigned char>::max());
+ const unsigned char kH2Len = static_cast<unsigned char>(less_than_min_length);
+ const unsigned char kH2FrameData[] = {
+ 0x00, 0x00, kH2Len, // Length: min length - 1
+ 0x07, // Type: GOAWAY
+ 0x00, // Flags: none
+ 0x00, 0x00, 0x00, 0x00, // Stream: 0
+ 0x00, 0x00, 0x00, 0x00, // Last: 0
+ 0x00, 0x00, 0x00, // Truncated Status Field
+ };
+ const size_t pad_length = length + kFrameHeaderSize - sizeof(kH2FrameData);
+ SpdyString pad(pad_length, 'A');
+ TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION);
+
+ visitor.SimulateInFramer(kH2FrameData, sizeof(kH2FrameData));
+ visitor.SimulateInFramer(reinterpret_cast<const unsigned char*>(pad.c_str()),
+ pad.length());
+
+ EXPECT_EQ(1, visitor.error_count_); // This generated an error.
+ EXPECT_EQ(Http2DecoderAdapter::SPDY_INVALID_CONTROL_FRAME,
+ visitor.deframer_.spdy_framer_error())
+ << Http2DecoderAdapter::SpdyFramerErrorToString(
+ visitor.deframer_.spdy_framer_error());
+ EXPECT_EQ(0, visitor.goaway_count_); // Frame not parsed.
+}
+
+TEST_P(SpdyFramerTest, ReadZeroLenSettingsFrame) {
+ SpdySettingsIR settings_ir;
+ SpdySerializedFrame control_frame(framer_.SerializeSettings(settings_ir));
+ if (use_output_) {
+ ASSERT_TRUE(framer_.SerializeSettings(settings_ir, &output_));
+ control_frame = SpdySerializedFrame(output_.Begin(), output_.Size(), false);
+ }
+ SetFrameLength(&control_frame, 0);
+ TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION);
+ visitor.SimulateInFramer(
+ reinterpret_cast<unsigned char*>(control_frame.data()), kFrameHeaderSize);
+ // Zero-len settings frames are permitted as of HTTP/2.
+ EXPECT_EQ(0, visitor.error_count_);
+}
+
+// Tests handling of SETTINGS frames with invalid length.
+TEST_P(SpdyFramerTest, ReadBogusLenSettingsFrame) {
+ SpdySettingsIR settings_ir;
+
+ // Add settings to more than fill the frame so that we don't get a buffer
+ // overflow when calling SimulateInFramer() below. These settings must be
+ // distinct parameters because SpdySettingsIR has a map for settings, and
+ // will collapse multiple copies of the same parameter.
+ settings_ir.AddSetting(SETTINGS_INITIAL_WINDOW_SIZE, 0x00000002);
+ settings_ir.AddSetting(SETTINGS_MAX_CONCURRENT_STREAMS, 0x00000002);
+ SpdySerializedFrame control_frame(framer_.SerializeSettings(settings_ir));
+ if (use_output_) {
+ ASSERT_TRUE(framer_.SerializeSettings(settings_ir, &output_));
+ control_frame = SpdySerializedFrame(output_.Begin(), output_.Size(), false);
+ }
+ const size_t kNewLength = 8;
+ SetFrameLength(&control_frame, kNewLength);
+ TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION);
+ visitor.SimulateInFramer(
+ reinterpret_cast<unsigned char*>(control_frame.data()),
+ kFrameHeaderSize + kNewLength);
+ // Should generate an error, since its not possible to have a
+ // settings frame of length kNewLength.
+ EXPECT_EQ(1, visitor.error_count_);
+ EXPECT_EQ(Http2DecoderAdapter::SPDY_INVALID_CONTROL_FRAME_SIZE,
+ visitor.deframer_.spdy_framer_error())
+ << Http2DecoderAdapter::SpdyFramerErrorToString(
+ visitor.deframer_.spdy_framer_error());
+}
+
+// Tests handling of larger SETTINGS frames.
+TEST_P(SpdyFramerTest, ReadLargeSettingsFrame) {
+ SpdySettingsIR settings_ir;
+ settings_ir.AddSetting(SETTINGS_HEADER_TABLE_SIZE, 5);
+ settings_ir.AddSetting(SETTINGS_ENABLE_PUSH, 6);
+ settings_ir.AddSetting(SETTINGS_MAX_CONCURRENT_STREAMS, 7);
+
+ SpdySerializedFrame control_frame(framer_.SerializeSettings(settings_ir));
+ if (use_output_) {
+ ASSERT_TRUE(framer_.SerializeSettings(settings_ir, &output_));
+ control_frame = SpdySerializedFrame(output_.Begin(), output_.Size(), false);
+ }
+
+ TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION);
+
+ // Read all at once.
+ visitor.SimulateInFramer(
+ reinterpret_cast<unsigned char*>(control_frame.data()),
+ control_frame.size());
+ EXPECT_EQ(0, visitor.error_count_);
+ EXPECT_EQ(3, visitor.setting_count_);
+ EXPECT_EQ(1, visitor.settings_ack_sent_);
+
+ // Read data in small chunks.
+ size_t framed_data = 0;
+ size_t unframed_data = control_frame.size();
+ size_t kReadChunkSize = 5; // Read five bytes at a time.
+ while (unframed_data > 0) {
+ size_t to_read = std::min(kReadChunkSize, unframed_data);
+ visitor.SimulateInFramer(
+ reinterpret_cast<unsigned char*>(control_frame.data() + framed_data),
+ to_read);
+ unframed_data -= to_read;
+ framed_data += to_read;
+ }
+ EXPECT_EQ(0, visitor.error_count_);
+ EXPECT_EQ(3 * 2, visitor.setting_count_);
+ EXPECT_EQ(2, visitor.settings_ack_sent_);
+}
+
+// Tests handling of SETTINGS frame with duplicate entries.
+TEST_P(SpdyFramerTest, ReadDuplicateSettings) {
+ const unsigned char kH2FrameData[] = {
+ 0x00, 0x00, 0x12, // Length: 18
+ 0x04, // Type: SETTINGS
+ 0x00, // Flags: none
+ 0x00, 0x00, 0x00, 0x00, // Stream: 0
+ 0x00, 0x01, // Param: HEADER_TABLE_SIZE
+ 0x00, 0x00, 0x00, 0x02, // Value: 2
+ 0x00, 0x01, // Param: HEADER_TABLE_SIZE
+ 0x00, 0x00, 0x00, 0x03, // Value: 3
+ 0x00, 0x03, // Param: MAX_CONCURRENT_STREAMS
+ 0x00, 0x00, 0x00, 0x03, // Value: 3
+ };
+
+ TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION);
+ visitor.SimulateInFramer(kH2FrameData, sizeof(kH2FrameData));
+
+ // In HTTP/2, duplicate settings are allowed;
+ // each setting replaces the previous value for that setting.
+ EXPECT_EQ(3, visitor.setting_count_);
+ EXPECT_EQ(0, visitor.error_count_);
+ EXPECT_EQ(1, visitor.settings_ack_sent_);
+}
+
+// Tests handling of SETTINGS frame with a setting we don't recognize.
+TEST_P(SpdyFramerTest, ReadUnknownSettingsId) {
+ const unsigned char kH2FrameData[] = {
+ 0x00, 0x00, 0x06, // Length: 6
+ 0x04, // Type: SETTINGS
+ 0x00, // Flags: none
+ 0x00, 0x00, 0x00, 0x00, // Stream: 0
+ 0x00, 0x10, // Param: 16
+ 0x00, 0x00, 0x00, 0x02, // Value: 2
+ };
+
+ TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION);
+ visitor.SimulateInFramer(kH2FrameData, sizeof(kH2FrameData));
+
+ // In HTTP/2, we ignore unknown settings because of extensions. However, we
+ // pass the SETTINGS to the visitor, which can decide how to handle them.
+ EXPECT_EQ(1, visitor.setting_count_);
+ EXPECT_EQ(0, visitor.error_count_);
+}
+
+TEST_P(SpdyFramerTest, ReadKnownAndUnknownSettingsWithExtension) {
+ const unsigned char kH2FrameData[] = {
+ 0x00, 0x00, 0x12, // Length: 18
+ 0x04, // Type: SETTINGS
+ 0x00, // Flags: none
+ 0x00, 0x00, 0x00, 0x00, // Stream: 0
+ 0x00, 0x10, // Param: 16
+ 0x00, 0x00, 0x00, 0x02, // Value: 2
+ 0x00, 0x5f, // Param: 95
+ 0x00, 0x01, 0x00, 0x02, // Value: 65538
+ 0x00, 0x02, // Param: ENABLE_PUSH
+ 0x00, 0x00, 0x00, 0x01, // Value: 1
+ };
+
+ TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION);
+ TestExtension extension;
+ visitor.set_extension_visitor(&extension);
+ visitor.SimulateInFramer(kH2FrameData, sizeof(kH2FrameData));
+
+ // In HTTP/2, we ignore unknown settings because of extensions. However, we
+ // pass the SETTINGS to the visitor, which can decide how to handle them.
+ EXPECT_EQ(3, visitor.setting_count_);
+ EXPECT_EQ(0, visitor.error_count_);
+
+ // The extension receives all SETTINGS, including the non-standard SETTINGS.
+ EXPECT_THAT(
+ extension.settings_received_,
+ testing::ElementsAre(testing::Pair(16, 2), testing::Pair(95, 65538),
+ testing::Pair(2, 1)));
+}
+
+// Tests handling of SETTINGS frame with entries out of order.
+TEST_P(SpdyFramerTest, ReadOutOfOrderSettings) {
+ const unsigned char kH2FrameData[] = {
+ 0x00, 0x00, 0x12, // Length: 18
+ 0x04, // Type: SETTINGS
+ 0x00, // Flags: none
+ 0x00, 0x00, 0x00, 0x00, // Stream: 0
+ 0x00, 0x02, // Param: ENABLE_PUSH
+ 0x00, 0x00, 0x00, 0x02, // Value: 2
+ 0x00, 0x01, // Param: HEADER_TABLE_SIZE
+ 0x00, 0x00, 0x00, 0x03, // Value: 3
+ 0x00, 0x03, // Param: MAX_CONCURRENT_STREAMS
+ 0x00, 0x00, 0x00, 0x03, // Value: 3
+ };
+
+ TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION);
+ visitor.SimulateInFramer(kH2FrameData, sizeof(kH2FrameData));
+
+ // In HTTP/2, settings are allowed in any order.
+ EXPECT_EQ(3, visitor.setting_count_);
+ EXPECT_EQ(0, visitor.error_count_);
+}
+
+TEST_P(SpdyFramerTest, ProcessSettingsAckFrame) {
+ const unsigned char kFrameData[] = {
+ 0x00, 0x00, 0x00, // Length: 0
+ 0x04, // Type: SETTINGS
+ 0x01, // Flags: ACK
+ 0x00, 0x00, 0x00, 0x00, // Stream: 0
+ };
+
+ TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION);
+ visitor.SimulateInFramer(kFrameData, sizeof(kFrameData));
+
+ EXPECT_EQ(0, visitor.error_count_);
+ EXPECT_EQ(0, visitor.setting_count_);
+ EXPECT_EQ(1, visitor.settings_ack_received_);
+}
+
+TEST_P(SpdyFramerTest, ProcessDataFrameWithPadding) {
+ const int kPaddingLen = 119;
+ const char data_payload[] = "hello";
+
+ testing::StrictMock<test::MockSpdyFramerVisitor> visitor;
+ deframer_.set_visitor(&visitor);
+
+ SpdyDataIR data_ir(/* stream_id = */ 1, data_payload);
+ data_ir.set_padding_len(kPaddingLen);
+ SpdySerializedFrame frame(framer_.SerializeData(data_ir));
+
+ int bytes_consumed = 0;
+
+ // Send the frame header.
+ EXPECT_CALL(visitor,
+ OnDataFrameHeader(1, kPaddingLen + strlen(data_payload), false));
+ CHECK_EQ(kDataFrameMinimumSize,
+ deframer_.ProcessInput(frame.data(), kDataFrameMinimumSize));
+ CHECK_EQ(deframer_.state(),
+ Http2DecoderAdapter::SPDY_READ_DATA_FRAME_PADDING_LENGTH);
+ CHECK_EQ(deframer_.spdy_framer_error(), Http2DecoderAdapter::SPDY_NO_ERROR);
+ bytes_consumed += kDataFrameMinimumSize;
+
+ // Send the padding length field.
+ EXPECT_CALL(visitor, OnStreamPadLength(1, kPaddingLen - 1));
+ CHECK_EQ(1u, deframer_.ProcessInput(frame.data() + bytes_consumed, 1));
+ CHECK_EQ(deframer_.state(), Http2DecoderAdapter::SPDY_FORWARD_STREAM_FRAME);
+ CHECK_EQ(deframer_.spdy_framer_error(), Http2DecoderAdapter::SPDY_NO_ERROR);
+ bytes_consumed += 1;
+
+ // Send the first two bytes of the data payload, i.e., "he".
+ EXPECT_CALL(visitor, OnStreamFrameData(1, _, 2));
+ CHECK_EQ(2u, deframer_.ProcessInput(frame.data() + bytes_consumed, 2));
+ CHECK_EQ(deframer_.state(), Http2DecoderAdapter::SPDY_FORWARD_STREAM_FRAME);
+ CHECK_EQ(deframer_.spdy_framer_error(), Http2DecoderAdapter::SPDY_NO_ERROR);
+ bytes_consumed += 2;
+
+ // Send the rest three bytes of the data payload, i.e., "llo".
+ EXPECT_CALL(visitor, OnStreamFrameData(1, _, 3));
+ CHECK_EQ(3u, deframer_.ProcessInput(frame.data() + bytes_consumed, 3));
+ CHECK_EQ(deframer_.state(), Http2DecoderAdapter::SPDY_CONSUME_PADDING);
+ CHECK_EQ(deframer_.spdy_framer_error(), Http2DecoderAdapter::SPDY_NO_ERROR);
+ bytes_consumed += 3;
+
+ // Send the first 100 bytes of the padding payload.
+ EXPECT_CALL(visitor, OnStreamPadding(1, 100));
+ CHECK_EQ(100u, deframer_.ProcessInput(frame.data() + bytes_consumed, 100));
+ CHECK_EQ(deframer_.state(), Http2DecoderAdapter::SPDY_CONSUME_PADDING);
+ CHECK_EQ(deframer_.spdy_framer_error(), Http2DecoderAdapter::SPDY_NO_ERROR);
+ bytes_consumed += 100;
+
+ // Send rest of the padding payload.
+ EXPECT_CALL(visitor, OnStreamPadding(1, 18));
+ CHECK_EQ(18u, deframer_.ProcessInput(frame.data() + bytes_consumed, 18));
+ CHECK_EQ(deframer_.state(), Http2DecoderAdapter::SPDY_READY_FOR_FRAME);
+ CHECK_EQ(deframer_.spdy_framer_error(), Http2DecoderAdapter::SPDY_NO_ERROR);
+}
+
+TEST_P(SpdyFramerTest, ReadWindowUpdate) {
+ SpdySerializedFrame control_frame(framer_.SerializeWindowUpdate(
+ SpdyWindowUpdateIR(/* stream_id = */ 1, /* delta = */ 2)));
+ if (use_output_) {
+ ASSERT_TRUE(framer_.SerializeWindowUpdate(
+ SpdyWindowUpdateIR(/* stream_id = */ 1, /* delta = */ 2), &output_));
+ control_frame = SpdySerializedFrame(output_.Begin(), output_.Size(), false);
+ }
+ TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION);
+ visitor.SimulateInFramer(
+ reinterpret_cast<unsigned char*>(control_frame.data()),
+ control_frame.size());
+ EXPECT_EQ(1u, visitor.last_window_update_stream_);
+ EXPECT_EQ(2, visitor.last_window_update_delta_);
+}
+
+TEST_P(SpdyFramerTest, ReadCompressedPushPromise) {
+ SpdyPushPromiseIR push_promise(/* stream_id = */ 42,
+ /* promised_stream_id = */ 57);
+ push_promise.SetHeader("foo", "bar");
+ push_promise.SetHeader("bar", "foofoo");
+ SpdySerializedFrame frame(SpdyFramerPeer::SerializePushPromise(
+ &framer_, push_promise, use_output_ ? &output_ : nullptr));
+ TestSpdyVisitor visitor(SpdyFramer::ENABLE_COMPRESSION);
+ visitor.SimulateInFramer(reinterpret_cast<unsigned char*>(frame.data()),
+ frame.size());
+ EXPECT_EQ(42u, visitor.last_push_promise_stream_);
+ EXPECT_EQ(57u, visitor.last_push_promise_promised_stream_);
+ EXPECT_EQ(push_promise.header_block(), visitor.headers_);
+}
+
+TEST_P(SpdyFramerTest, ReadHeadersWithContinuation) {
+ // frame-format off
+ const unsigned char kInput[] = {
+ 0x00, 0x00, 0x14, // Length: 20
+ 0x01, // Type: HEADERS
+ 0x08, // Flags: PADDED
+ 0x00, 0x00, 0x00, 0x01, // Stream: 1
+ 0x03, // PadLen: 3 trailing bytes
+ 0x00, // Unindexed Entry
+ 0x06, // Name Len: 6
+ 'c', 'o', 'o', 'k', 'i', 'e', // Name
+ 0x07, // Value Len: 7
+ 'f', 'o', 'o', '=', 'b', 'a', 'r', // Value
+ 0x00, 0x00, 0x00, // Padding
+
+ 0x00, 0x00, 0x14, // Length: 20
+ 0x09, // Type: CONTINUATION
+ 0x00, // Flags: none
+ 0x00, 0x00, 0x00, 0x01, // Stream: 1
+ 0x00, // Unindexed Entry
+ 0x06, // Name Len: 6
+ 'c', 'o', 'o', 'k', 'i', 'e', // Name
+ 0x08, // Value Len: 7
+ 'b', 'a', 'z', '=', 'b', 'i', 'n', 'g', // Value
+ 0x00, // Unindexed Entry
+ 0x06, // Name Len: 6
+ 'c', // Name (split)
+
+ 0x00, 0x00, 0x12, // Length: 18
+ 0x09, // Type: CONTINUATION
+ 0x04, // Flags: END_HEADERS
+ 0x00, 0x00, 0x00, 0x01, // Stream: 1
+ 'o', 'o', 'k', 'i', 'e', // Name (continued)
+ 0x00, // Value Len: 0
+ 0x00, // Unindexed Entry
+ 0x04, // Name Len: 4
+ 'n', 'a', 'm', 'e', // Name
+ 0x05, // Value Len: 5
+ 'v', 'a', 'l', 'u', 'e', // Value
+ };
+ // frame-format on
+
+ TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION);
+ visitor.SimulateInFramer(kInput, sizeof(kInput));
+
+ EXPECT_EQ(0, visitor.error_count_);
+ EXPECT_EQ(1, visitor.headers_frame_count_);
+ EXPECT_EQ(2, visitor.continuation_count_);
+ EXPECT_EQ(0, visitor.zero_length_control_frame_header_data_count_);
+ EXPECT_EQ(0, visitor.end_of_stream_count_);
+
+ EXPECT_THAT(
+ visitor.headers_,
+ testing::ElementsAre(testing::Pair("cookie", "foo=bar; baz=bing; "),
+ testing::Pair("name", "value")));
+}
+
+TEST_P(SpdyFramerTest, ReadHeadersWithContinuationAndFin) {
+ // frame-format off
+ const unsigned char kInput[] = {
+ 0x00, 0x00, 0x10, // Length: 20
+ 0x01, // Type: HEADERS
+ 0x01, // Flags: END_STREAM
+ 0x00, 0x00, 0x00, 0x01, // Stream: 1
+ 0x00, // Unindexed Entry
+ 0x06, // Name Len: 6
+ 'c', 'o', 'o', 'k', 'i', 'e', // Name
+ 0x07, // Value Len: 7
+ 'f', 'o', 'o', '=', 'b', 'a', 'r', // Value
+
+ 0x00, 0x00, 0x14, // Length: 20
+ 0x09, // Type: CONTINUATION
+ 0x00, // Flags: none
+ 0x00, 0x00, 0x00, 0x01, // Stream: 1
+ 0x00, // Unindexed Entry
+ 0x06, // Name Len: 6
+ 'c', 'o', 'o', 'k', 'i', 'e', // Name
+ 0x08, // Value Len: 7
+ 'b', 'a', 'z', '=', 'b', 'i', 'n', 'g', // Value
+ 0x00, // Unindexed Entry
+ 0x06, // Name Len: 6
+ 'c', // Name (split)
+
+ 0x00, 0x00, 0x12, // Length: 18
+ 0x09, // Type: CONTINUATION
+ 0x04, // Flags: END_HEADERS
+ 0x00, 0x00, 0x00, 0x01, // Stream: 1
+ 'o', 'o', 'k', 'i', 'e', // Name (continued)
+ 0x00, // Value Len: 0
+ 0x00, // Unindexed Entry
+ 0x04, // Name Len: 4
+ 'n', 'a', 'm', 'e', // Name
+ 0x05, // Value Len: 5
+ 'v', 'a', 'l', 'u', 'e', // Value
+ };
+ // frame-format on
+
+ TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION);
+ visitor.SimulateInFramer(kInput, sizeof(kInput));
+
+ EXPECT_EQ(0, visitor.error_count_);
+ EXPECT_EQ(1, visitor.headers_frame_count_);
+ EXPECT_EQ(2, visitor.continuation_count_);
+ EXPECT_EQ(1, visitor.fin_flag_count_);
+ EXPECT_EQ(0, visitor.zero_length_control_frame_header_data_count_);
+ EXPECT_EQ(1, visitor.end_of_stream_count_);
+
+ EXPECT_THAT(
+ visitor.headers_,
+ testing::ElementsAre(testing::Pair("cookie", "foo=bar; baz=bing; "),
+ testing::Pair("name", "value")));
+}
+
+TEST_P(SpdyFramerTest, ReadPushPromiseWithContinuation) {
+ // frame-format off
+ const unsigned char kInput[] = {
+ 0x00, 0x00, 0x17, // Length: 23
+ 0x05, // Type: PUSH_PROMISE
+ 0x08, // Flags: PADDED
+ 0x00, 0x00, 0x00, 0x01, // Stream: 1
+ 0x02, // PadLen: 2 trailing bytes
+ 0x00, 0x00, 0x00, 0x2a, // Promise: 42
+ 0x00, // Unindexed Entry
+ 0x06, // Name Len: 6
+ 'c', 'o', 'o', 'k', 'i', 'e', // Name
+ 0x07, // Value Len: 7
+ 'f', 'o', 'o', '=', 'b', 'a', 'r', // Value
+ 0x00, 0x00, // Padding
+
+ 0x00, 0x00, 0x14, // Length: 20
+ 0x09, // Type: CONTINUATION
+ 0x00, // Flags: none
+ 0x00, 0x00, 0x00, 0x01, // Stream: 1
+ 0x00, // Unindexed Entry
+ 0x06, // Name Len: 6
+ 'c', 'o', 'o', 'k', 'i', 'e', // Name
+ 0x08, // Value Len: 7
+ 'b', 'a', 'z', '=', 'b', 'i', 'n', 'g', // Value
+ 0x00, // Unindexed Entry
+ 0x06, // Name Len: 6
+ 'c', // Name (split)
+
+ 0x00, 0x00, 0x12, // Length: 18
+ 0x09, // Type: CONTINUATION
+ 0x04, // Flags: END_HEADERS
+ 0x00, 0x00, 0x00, 0x01, // Stream: 1
+ 'o', 'o', 'k', 'i', 'e', // Name (continued)
+ 0x00, // Value Len: 0
+ 0x00, // Unindexed Entry
+ 0x04, // Name Len: 4
+ 'n', 'a', 'm', 'e', // Name
+ 0x05, // Value Len: 5
+ 'v', 'a', 'l', 'u', 'e', // Value
+ };
+ // frame-format on
+
+ TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION);
+ visitor.SimulateInFramer(kInput, sizeof(kInput));
+
+ EXPECT_EQ(0, visitor.error_count_);
+ EXPECT_EQ(1u, visitor.last_push_promise_stream_);
+ EXPECT_EQ(42u, visitor.last_push_promise_promised_stream_);
+ EXPECT_EQ(2, visitor.continuation_count_);
+ EXPECT_EQ(0, visitor.zero_length_control_frame_header_data_count_);
+ EXPECT_EQ(0, visitor.end_of_stream_count_);
+
+ EXPECT_THAT(
+ visitor.headers_,
+ testing::ElementsAre(testing::Pair("cookie", "foo=bar; baz=bing; "),
+ testing::Pair("name", "value")));
+}
+
+// Receiving an unknown frame when a continuation is expected should
+// result in a SPDY_UNEXPECTED_FRAME error
+TEST_P(SpdyFramerTest, ReceiveUnknownMidContinuation) {
+ const unsigned char kInput[] = {
+ 0x00, 0x00, 0x10, // Length: 16
+ 0x01, // Type: HEADERS
+ 0x00, // Flags: none
+ 0x00, 0x00, 0x00, 0x01, // Stream: 1
+ 0x00, 0x06, 0x63, 0x6f, // HPACK
+ 0x6f, 0x6b, 0x69, 0x65, //
+ 0x07, 0x66, 0x6f, 0x6f, //
+ 0x3d, 0x62, 0x61, 0x72, //
+
+ 0x00, 0x00, 0x14, // Length: 20
+ 0xa9, // Type: UnknownFrameType(169)
+ 0x00, // Flags: none
+ 0x00, 0x00, 0x00, 0x01, // Stream: 1
+ 0x00, 0x06, 0x63, 0x6f, // Payload
+ 0x6f, 0x6b, 0x69, 0x65, //
+ 0x08, 0x62, 0x61, 0x7a, //
+ 0x3d, 0x62, 0x69, 0x6e, //
+ 0x67, 0x00, 0x06, 0x63, //
+ };
+
+ TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION);
+ // Assume the unknown frame is allowed
+ visitor.on_unknown_frame_result_ = true;
+ deframer_.set_visitor(&visitor);
+ visitor.SimulateInFramer(kInput, sizeof(kInput));
+
+ EXPECT_EQ(1, visitor.error_count_);
+ EXPECT_EQ(Http2DecoderAdapter::SPDY_UNEXPECTED_FRAME,
+ visitor.deframer_.spdy_framer_error())
+ << Http2DecoderAdapter::SpdyFramerErrorToString(
+ visitor.deframer_.spdy_framer_error());
+ EXPECT_EQ(1, visitor.headers_frame_count_);
+ EXPECT_EQ(0, visitor.continuation_count_);
+ EXPECT_EQ(0u, visitor.header_buffer_length_);
+}
+
+// Receiving an unknown frame when a continuation is expected should
+// result in a SPDY_UNEXPECTED_FRAME error
+TEST_P(SpdyFramerTest, ReceiveUnknownMidContinuationWithExtension) {
+ const unsigned char kInput[] = {
+ 0x00, 0x00, 0x10, // Length: 16
+ 0x01, // Type: HEADERS
+ 0x00, // Flags: none
+ 0x00, 0x00, 0x00, 0x01, // Stream: 1
+ 0x00, 0x06, 0x63, 0x6f, // HPACK
+ 0x6f, 0x6b, 0x69, 0x65, //
+ 0x07, 0x66, 0x6f, 0x6f, //
+ 0x3d, 0x62, 0x61, 0x72, //
+
+ 0x00, 0x00, 0x14, // Length: 20
+ 0xa9, // Type: UnknownFrameType(169)
+ 0x00, // Flags: none
+ 0x00, 0x00, 0x00, 0x01, // Stream: 1
+ 0x00, 0x06, 0x63, 0x6f, // Payload
+ 0x6f, 0x6b, 0x69, 0x65, //
+ 0x08, 0x62, 0x61, 0x7a, //
+ 0x3d, 0x62, 0x69, 0x6e, //
+ 0x67, 0x00, 0x06, 0x63, //
+ };
+
+ TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION);
+ TestExtension extension;
+ visitor.set_extension_visitor(&extension);
+ deframer_.set_visitor(&visitor);
+ visitor.SimulateInFramer(kInput, sizeof(kInput));
+
+ EXPECT_EQ(1, visitor.error_count_);
+ EXPECT_EQ(Http2DecoderAdapter::SPDY_UNEXPECTED_FRAME,
+ visitor.deframer_.spdy_framer_error())
+ << Http2DecoderAdapter::SpdyFramerErrorToString(
+ visitor.deframer_.spdy_framer_error());
+ EXPECT_EQ(1, visitor.headers_frame_count_);
+ EXPECT_EQ(0, visitor.continuation_count_);
+ EXPECT_EQ(0u, visitor.header_buffer_length_);
+}
+
+TEST_P(SpdyFramerTest, ReceiveContinuationOnWrongStream) {
+ const unsigned char kInput[] = {
+ 0x00, 0x00, 0x10, // Length: 16
+ 0x01, // Type: HEADERS
+ 0x00, // Flags: none
+ 0x00, 0x00, 0x00, 0x01, // Stream: 1
+ 0x00, 0x06, 0x63, 0x6f, // HPACK
+ 0x6f, 0x6b, 0x69, 0x65, //
+ 0x07, 0x66, 0x6f, 0x6f, //
+ 0x3d, 0x62, 0x61, 0x72, //
+
+ 0x00, 0x00, 0x14, // Length: 20
+ 0x09, // Type: CONTINUATION
+ 0x00, // Flags: none
+ 0x00, 0x00, 0x00, 0x02, // Stream: 2
+ 0x00, 0x06, 0x63, 0x6f, // HPACK
+ 0x6f, 0x6b, 0x69, 0x65, //
+ 0x08, 0x62, 0x61, 0x7a, //
+ 0x3d, 0x62, 0x69, 0x6e, //
+ 0x67, 0x00, 0x06, 0x63, //
+ };
+
+ TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION);
+ deframer_.set_visitor(&visitor);
+ visitor.SimulateInFramer(kInput, sizeof(kInput));
+
+ EXPECT_EQ(1, visitor.error_count_);
+ EXPECT_EQ(Http2DecoderAdapter::SPDY_UNEXPECTED_FRAME,
+ visitor.deframer_.spdy_framer_error())
+ << Http2DecoderAdapter::SpdyFramerErrorToString(
+ visitor.deframer_.spdy_framer_error());
+ EXPECT_EQ(1, visitor.headers_frame_count_);
+ EXPECT_EQ(0, visitor.continuation_count_);
+ EXPECT_EQ(0u, visitor.header_buffer_length_);
+}
+
+TEST_P(SpdyFramerTest, ReadContinuationOutOfOrder) {
+ const unsigned char kInput[] = {
+ 0x00, 0x00, 0x18, // Length: 24
+ 0x09, // Type: CONTINUATION
+ 0x00, // Flags: none
+ 0x00, 0x00, 0x00, 0x01, // Stream: 1
+ 0x00, 0x06, 0x63, 0x6f, // HPACK
+ 0x6f, 0x6b, 0x69, 0x65, //
+ 0x07, 0x66, 0x6f, 0x6f, //
+ 0x3d, 0x62, 0x61, 0x72, //
+ };
+
+ TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION);
+ deframer_.set_visitor(&visitor);
+ visitor.SimulateInFramer(kInput, sizeof(kInput));
+
+ EXPECT_EQ(1, visitor.error_count_);
+ EXPECT_EQ(Http2DecoderAdapter::SPDY_UNEXPECTED_FRAME,
+ visitor.deframer_.spdy_framer_error())
+ << Http2DecoderAdapter::SpdyFramerErrorToString(
+ visitor.deframer_.spdy_framer_error());
+ EXPECT_EQ(0, visitor.continuation_count_);
+ EXPECT_EQ(0u, visitor.header_buffer_length_);
+}
+
+TEST_P(SpdyFramerTest, ExpectContinuationReceiveData) {
+ const unsigned char kInput[] = {
+ 0x00, 0x00, 0x10, // Length: 16
+ 0x01, // Type: HEADERS
+ 0x00, // Flags: none
+ 0x00, 0x00, 0x00, 0x01, // Stream: 1
+ 0x00, 0x06, 0x63, 0x6f, // HPACK
+ 0x6f, 0x6b, 0x69, 0x65, //
+ 0x07, 0x66, 0x6f, 0x6f, //
+ 0x3d, 0x62, 0x61, 0x72, //
+
+ 0x00, 0x00, 0x00, // Length: 0
+ 0x00, // Type: DATA
+ 0x01, // Flags: END_STREAM
+ 0x00, 0x00, 0x00, 0x04, // Stream: 4
+
+ 0xde, 0xad, 0xbe, 0xef, // Truncated Frame Header
+ };
+
+ TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION);
+ deframer_.set_visitor(&visitor);
+ visitor.SimulateInFramer(kInput, sizeof(kInput));
+
+ EXPECT_EQ(1, visitor.error_count_);
+ EXPECT_EQ(Http2DecoderAdapter::SPDY_UNEXPECTED_FRAME,
+ visitor.deframer_.spdy_framer_error())
+ << Http2DecoderAdapter::SpdyFramerErrorToString(
+ visitor.deframer_.spdy_framer_error());
+ EXPECT_EQ(1, visitor.headers_frame_count_);
+ EXPECT_EQ(0, visitor.continuation_count_);
+ EXPECT_EQ(0u, visitor.header_buffer_length_);
+ EXPECT_EQ(0, visitor.data_frame_count_);
+}
+
+TEST_P(SpdyFramerTest, ExpectContinuationReceiveControlFrame) {
+ const unsigned char kInput[] = {
+ 0x00, 0x00, 0x10, // Length: 16
+ 0x01, // Type: HEADERS
+ 0x00, // Flags: none
+ 0x00, 0x00, 0x00, 0x01, // Stream: 1
+ 0x00, 0x06, 0x63, 0x6f, // HPACK
+ 0x6f, 0x6b, 0x69, 0x65, //
+ 0x07, 0x66, 0x6f, 0x6f, //
+ 0x3d, 0x62, 0x61, 0x72, //
+
+ 0x00, 0x00, 0x10, // Length: 16
+ 0x01, // Type: HEADERS
+ 0x00, // Flags: none
+ 0x00, 0x00, 0x00, 0x01, // Stream: 1
+ 0x00, 0x06, 0x63, 0x6f, // HPACK
+ 0x6f, 0x6b, 0x69, 0x65, //
+ 0x07, 0x66, 0x6f, 0x6f, //
+ 0x3d, 0x62, 0x61, 0x72, //
+ };
+
+ TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION);
+ deframer_.set_visitor(&visitor);
+ visitor.SimulateInFramer(kInput, sizeof(kInput));
+
+ EXPECT_EQ(1, visitor.error_count_);
+ EXPECT_EQ(Http2DecoderAdapter::SPDY_UNEXPECTED_FRAME,
+ visitor.deframer_.spdy_framer_error())
+ << Http2DecoderAdapter::SpdyFramerErrorToString(
+ visitor.deframer_.spdy_framer_error());
+ EXPECT_EQ(1, visitor.headers_frame_count_);
+ EXPECT_EQ(0, visitor.continuation_count_);
+ EXPECT_EQ(0u, visitor.header_buffer_length_);
+ EXPECT_EQ(0, visitor.data_frame_count_);
+}
+
+TEST_P(SpdyFramerTest, ReadGarbage) {
+ unsigned char garbage_frame[256];
+ memset(garbage_frame, ~0, sizeof(garbage_frame));
+ TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION);
+ visitor.SimulateInFramer(garbage_frame, sizeof(garbage_frame));
+ EXPECT_EQ(1, visitor.error_count_);
+}
+
+TEST_P(SpdyFramerTest, ReadUnknownExtensionFrame) {
+ // The unrecognized frame type should still have a valid length.
+ const unsigned char unknown_frame[] = {
+ 0x00, 0x00, 0x08, // Length: 8
+ 0xff, // Type: UnknownFrameType(255)
+ 0xff, // Flags: 0xff
+ 0xff, 0xff, 0xff, 0xff, // Stream: 0x7fffffff (R-bit set)
+ 0xff, 0xff, 0xff, 0xff, // Payload
+ 0xff, 0xff, 0xff, 0xff, //
+ };
+ TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION);
+
+ // Simulate the case where the stream id validation checks out.
+ visitor.on_unknown_frame_result_ = true;
+ visitor.SimulateInFramer(unknown_frame, SPDY_ARRAYSIZE(unknown_frame));
+ EXPECT_EQ(0, visitor.error_count_);
+
+ // Follow it up with a valid control frame to make sure we handle
+ // subsequent frames correctly.
+ SpdySettingsIR settings_ir;
+ settings_ir.AddSetting(SETTINGS_HEADER_TABLE_SIZE, 10);
+ SpdySerializedFrame control_frame(framer_.SerializeSettings(settings_ir));
+ if (use_output_) {
+ ASSERT_TRUE(framer_.SerializeSettings(settings_ir, &output_));
+ control_frame = SpdySerializedFrame(output_.Begin(), output_.Size(), false);
+ }
+ visitor.SimulateInFramer(
+ reinterpret_cast<unsigned char*>(control_frame.data()),
+ control_frame.size());
+ EXPECT_EQ(0, visitor.error_count_);
+ EXPECT_EQ(1, visitor.setting_count_);
+ EXPECT_EQ(1, visitor.settings_ack_sent_);
+}
+
+TEST_P(SpdyFramerTest, ReadUnknownExtensionFrameWithExtension) {
+ // The unrecognized frame type should still have a valid length.
+ const unsigned char unknown_frame[] = {
+ 0x00, 0x00, 0x14, // Length: 20
+ 0xff, // Type: UnknownFrameType(255)
+ 0xff, // Flags: 0xff
+ 0xff, 0xff, 0xff, 0xff, // Stream: 0x7fffffff (R-bit set)
+ 0xff, 0xff, 0xff, 0xff, // Payload
+ 0xff, 0xff, 0xff, 0xff, //
+ 0xff, 0xff, 0xff, 0xff, //
+ 0xff, 0xff, 0xff, 0xff, //
+ 0xff, 0xff, 0xff, 0xff, //
+ };
+ TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION);
+ TestExtension extension;
+ visitor.set_extension_visitor(&extension);
+ visitor.SimulateInFramer(unknown_frame, SPDY_ARRAYSIZE(unknown_frame));
+ EXPECT_EQ(0, visitor.error_count_);
+ EXPECT_EQ(0x7fffffffu, extension.stream_id_);
+ EXPECT_EQ(20u, extension.length_);
+ EXPECT_EQ(255, extension.type_);
+ EXPECT_EQ(0xff, extension.flags_);
+ EXPECT_EQ(SpdyString(20, '\xff'), extension.payload_);
+
+ // Follow it up with a valid control frame to make sure we handle
+ // subsequent frames correctly.
+ SpdySettingsIR settings_ir;
+ settings_ir.AddSetting(SETTINGS_HEADER_TABLE_SIZE, 10);
+ SpdySerializedFrame control_frame(framer_.SerializeSettings(settings_ir));
+ visitor.SimulateInFramer(
+ reinterpret_cast<unsigned char*>(control_frame.data()),
+ control_frame.size());
+ EXPECT_EQ(0, visitor.error_count_);
+ EXPECT_EQ(1, visitor.setting_count_);
+ EXPECT_EQ(1, visitor.settings_ack_sent_);
+}
+
+TEST_P(SpdyFramerTest, ReadGarbageWithValidLength) {
+ const unsigned char kFrameData[] = {
+ 0x00, 0x00, 0x08, // Length: 8
+ 0xff, // Type: UnknownFrameType(255)
+ 0xff, // Flags: 0xff
+ 0xff, 0xff, 0xff, 0xff, // Stream: 0x7fffffff (R-bit set)
+ 0xff, 0xff, 0xff, 0xff, // Payload
+ 0xff, 0xff, 0xff, 0xff, //
+ };
+ TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION);
+ visitor.SimulateInFramer(kFrameData, SPDY_ARRAYSIZE(kFrameData));
+ EXPECT_EQ(1, visitor.error_count_);
+}
+
+TEST_P(SpdyFramerTest, ReadGarbageHPACKEncoding) {
+ const unsigned char kInput[] = {
+ 0x00, 0x12, 0x01, // Length: 4609
+ 0x04, // Type: SETTINGS
+ 0x00, // Flags: none
+ 0x00, 0x00, 0x01, 0xef, // Stream: 495
+ 0xef, 0xff, // Param: 61439
+ 0xff, 0xff, 0xff, 0xff, // Value: 4294967295
+ 0xff, 0xff, // Param: 0xffff
+ 0xff, 0xff, 0xff, 0xff, // Value: 4294967295
+ 0xff, 0xff, 0xff, 0xff, // Settings (Truncated)
+ 0xff, //
+ };
+
+ TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION);
+ visitor.SimulateInFramer(kInput, SPDY_ARRAYSIZE(kInput));
+ EXPECT_EQ(1, visitor.error_count_);
+}
+
+TEST_P(SpdyFramerTest, SizesTest) {
+ EXPECT_EQ(9u, kFrameHeaderSize);
+ EXPECT_EQ(9u, kDataFrameMinimumSize);
+ EXPECT_EQ(9u, kHeadersFrameMinimumSize);
+ EXPECT_EQ(14u, kPriorityFrameSize);
+ EXPECT_EQ(13u, kRstStreamFrameSize);
+ EXPECT_EQ(9u, kSettingsFrameMinimumSize);
+ EXPECT_EQ(13u, kPushPromiseFrameMinimumSize);
+ EXPECT_EQ(17u, kPingFrameSize);
+ EXPECT_EQ(17u, kGoawayFrameMinimumSize);
+ EXPECT_EQ(13u, kWindowUpdateFrameSize);
+ EXPECT_EQ(9u, kContinuationFrameMinimumSize);
+ EXPECT_EQ(11u, kGetAltSvcFrameMinimumSize);
+ EXPECT_EQ(9u, kFrameMinimumSize);
+
+ EXPECT_EQ(16384u, kHttp2DefaultFramePayloadLimit);
+ EXPECT_EQ(16393u, kHttp2DefaultFrameSizeLimit);
+}
+
+TEST_P(SpdyFramerTest, StateToStringTest) {
+ EXPECT_STREQ("ERROR", Http2DecoderAdapter::StateToString(
+ Http2DecoderAdapter::SPDY_ERROR));
+ EXPECT_STREQ("FRAME_COMPLETE", Http2DecoderAdapter::StateToString(
+ Http2DecoderAdapter::SPDY_FRAME_COMPLETE));
+ EXPECT_STREQ("READY_FOR_FRAME",
+ Http2DecoderAdapter::StateToString(
+ Http2DecoderAdapter::SPDY_READY_FOR_FRAME));
+ EXPECT_STREQ("READING_COMMON_HEADER",
+ Http2DecoderAdapter::StateToString(
+ Http2DecoderAdapter::SPDY_READING_COMMON_HEADER));
+ EXPECT_STREQ("CONTROL_FRAME_PAYLOAD",
+ Http2DecoderAdapter::StateToString(
+ Http2DecoderAdapter::SPDY_CONTROL_FRAME_PAYLOAD));
+ EXPECT_STREQ("IGNORE_REMAINING_PAYLOAD",
+ Http2DecoderAdapter::StateToString(
+ Http2DecoderAdapter::SPDY_IGNORE_REMAINING_PAYLOAD));
+ EXPECT_STREQ("FORWARD_STREAM_FRAME",
+ Http2DecoderAdapter::StateToString(
+ Http2DecoderAdapter::SPDY_FORWARD_STREAM_FRAME));
+ EXPECT_STREQ(
+ "SPDY_CONTROL_FRAME_BEFORE_HEADER_BLOCK",
+ Http2DecoderAdapter::StateToString(
+ Http2DecoderAdapter::SPDY_CONTROL_FRAME_BEFORE_HEADER_BLOCK));
+ EXPECT_STREQ("SPDY_CONTROL_FRAME_HEADER_BLOCK",
+ Http2DecoderAdapter::StateToString(
+ Http2DecoderAdapter::SPDY_CONTROL_FRAME_HEADER_BLOCK));
+ EXPECT_STREQ("SPDY_SETTINGS_FRAME_PAYLOAD",
+ Http2DecoderAdapter::StateToString(
+ Http2DecoderAdapter::SPDY_SETTINGS_FRAME_PAYLOAD));
+ EXPECT_STREQ("SPDY_ALTSVC_FRAME_PAYLOAD",
+ Http2DecoderAdapter::StateToString(
+ Http2DecoderAdapter::SPDY_ALTSVC_FRAME_PAYLOAD));
+ EXPECT_STREQ("UNKNOWN_STATE",
+ Http2DecoderAdapter::StateToString(
+ Http2DecoderAdapter::SPDY_ALTSVC_FRAME_PAYLOAD + 1));
+}
+
+TEST_P(SpdyFramerTest, SpdyFramerErrorToStringTest) {
+ EXPECT_STREQ("NO_ERROR", Http2DecoderAdapter::SpdyFramerErrorToString(
+ Http2DecoderAdapter::SPDY_NO_ERROR));
+ EXPECT_STREQ("INVALID_STREAM_ID",
+ Http2DecoderAdapter::SpdyFramerErrorToString(
+ Http2DecoderAdapter::SPDY_INVALID_STREAM_ID));
+ EXPECT_STREQ("INVALID_CONTROL_FRAME",
+ Http2DecoderAdapter::SpdyFramerErrorToString(
+ Http2DecoderAdapter::SPDY_INVALID_CONTROL_FRAME));
+ EXPECT_STREQ("CONTROL_PAYLOAD_TOO_LARGE",
+ Http2DecoderAdapter::SpdyFramerErrorToString(
+ Http2DecoderAdapter::SPDY_CONTROL_PAYLOAD_TOO_LARGE));
+ EXPECT_STREQ("ZLIB_INIT_FAILURE",
+ Http2DecoderAdapter::SpdyFramerErrorToString(
+ Http2DecoderAdapter::SPDY_ZLIB_INIT_FAILURE));
+ EXPECT_STREQ("UNSUPPORTED_VERSION",
+ Http2DecoderAdapter::SpdyFramerErrorToString(
+ Http2DecoderAdapter::SPDY_UNSUPPORTED_VERSION));
+ EXPECT_STREQ("DECOMPRESS_FAILURE",
+ Http2DecoderAdapter::SpdyFramerErrorToString(
+ Http2DecoderAdapter::SPDY_DECOMPRESS_FAILURE));
+ EXPECT_STREQ("COMPRESS_FAILURE",
+ Http2DecoderAdapter::SpdyFramerErrorToString(
+ Http2DecoderAdapter::SPDY_COMPRESS_FAILURE));
+ EXPECT_STREQ("GOAWAY_FRAME_CORRUPT",
+ Http2DecoderAdapter::SpdyFramerErrorToString(
+ Http2DecoderAdapter::SPDY_GOAWAY_FRAME_CORRUPT));
+ EXPECT_STREQ("RST_STREAM_FRAME_CORRUPT",
+ Http2DecoderAdapter::SpdyFramerErrorToString(
+ Http2DecoderAdapter::SPDY_RST_STREAM_FRAME_CORRUPT));
+ EXPECT_STREQ("INVALID_PADDING",
+ Http2DecoderAdapter::SpdyFramerErrorToString(
+ Http2DecoderAdapter::SPDY_INVALID_PADDING));
+ EXPECT_STREQ("INVALID_DATA_FRAME_FLAGS",
+ Http2DecoderAdapter::SpdyFramerErrorToString(
+ Http2DecoderAdapter::SPDY_INVALID_DATA_FRAME_FLAGS));
+ EXPECT_STREQ("INVALID_CONTROL_FRAME_FLAGS",
+ Http2DecoderAdapter::SpdyFramerErrorToString(
+ Http2DecoderAdapter::SPDY_INVALID_CONTROL_FRAME_FLAGS));
+ EXPECT_STREQ("UNEXPECTED_FRAME",
+ Http2DecoderAdapter::SpdyFramerErrorToString(
+ Http2DecoderAdapter::SPDY_UNEXPECTED_FRAME));
+ EXPECT_STREQ("INTERNAL_FRAMER_ERROR",
+ Http2DecoderAdapter::SpdyFramerErrorToString(
+ Http2DecoderAdapter::SPDY_INTERNAL_FRAMER_ERROR));
+ EXPECT_STREQ("INVALID_CONTROL_FRAME_SIZE",
+ Http2DecoderAdapter::SpdyFramerErrorToString(
+ Http2DecoderAdapter::SPDY_INVALID_CONTROL_FRAME_SIZE));
+ EXPECT_STREQ("OVERSIZED_PAYLOAD",
+ Http2DecoderAdapter::SpdyFramerErrorToString(
+ Http2DecoderAdapter::SPDY_OVERSIZED_PAYLOAD));
+ EXPECT_STREQ("UNKNOWN_ERROR", Http2DecoderAdapter::SpdyFramerErrorToString(
+ Http2DecoderAdapter::LAST_ERROR));
+ EXPECT_STREQ("UNKNOWN_ERROR",
+ Http2DecoderAdapter::SpdyFramerErrorToString(
+ static_cast<Http2DecoderAdapter::SpdyFramerError>(
+ Http2DecoderAdapter::LAST_ERROR + 1)));
+}
+
+TEST_P(SpdyFramerTest, DataFrameFlagsV4) {
+ uint8_t valid_data_flags = DATA_FLAG_FIN | DATA_FLAG_PADDED;
+
+ uint8_t flags = 0;
+ do {
+ SCOPED_TRACE(testing::Message()
+ << "Flags " << std::hex << static_cast<int>(flags));
+
+ testing::StrictMock<test::MockSpdyFramerVisitor> visitor;
+
+ deframer_.set_visitor(&visitor);
+
+ SpdyDataIR data_ir(/* stream_id = */ 1, "hello");
+ SpdySerializedFrame frame(framer_.SerializeData(data_ir));
+ SetFrameFlags(&frame, flags);
+
+ if (flags & ~valid_data_flags) {
+ EXPECT_CALL(visitor, OnError(_));
+ } else {
+ EXPECT_CALL(visitor, OnDataFrameHeader(1, 5, flags & DATA_FLAG_FIN));
+ if (flags & DATA_FLAG_PADDED) {
+ // The first byte of payload is parsed as padding length, but 'h'
+ // (0x68) is too large a padding length for a 5 byte payload.
+ EXPECT_CALL(visitor, OnStreamPadding(_, 1));
+ // Expect Error since the frame ends prematurely.
+ EXPECT_CALL(visitor, OnError(_));
+ } else {
+ EXPECT_CALL(visitor, OnStreamFrameData(_, _, 5));
+ if (flags & DATA_FLAG_FIN) {
+ EXPECT_CALL(visitor, OnStreamEnd(_));
+ }
+ }
+ }
+
+ deframer_.ProcessInput(frame.data(), frame.size());
+ if (flags & ~valid_data_flags) {
+ EXPECT_EQ(Http2DecoderAdapter::SPDY_ERROR, deframer_.state());
+ EXPECT_EQ(Http2DecoderAdapter::SPDY_INVALID_DATA_FRAME_FLAGS,
+ deframer_.spdy_framer_error())
+ << Http2DecoderAdapter::SpdyFramerErrorToString(
+ deframer_.spdy_framer_error());
+ } else if (flags & DATA_FLAG_PADDED) {
+ EXPECT_EQ(Http2DecoderAdapter::SPDY_ERROR, deframer_.state());
+ EXPECT_EQ(Http2DecoderAdapter::SPDY_INVALID_PADDING,
+ deframer_.spdy_framer_error())
+ << Http2DecoderAdapter::SpdyFramerErrorToString(
+ deframer_.spdy_framer_error());
+ } else {
+ EXPECT_EQ(Http2DecoderAdapter::SPDY_READY_FOR_FRAME, deframer_.state());
+ EXPECT_EQ(Http2DecoderAdapter::SPDY_NO_ERROR,
+ deframer_.spdy_framer_error())
+ << Http2DecoderAdapter::SpdyFramerErrorToString(
+ deframer_.spdy_framer_error());
+ }
+ deframer_.Reset();
+ } while (++flags != 0);
+}
+
+TEST_P(SpdyFramerTest, RstStreamFrameFlags) {
+ uint8_t flags = 0;
+ do {
+ SCOPED_TRACE(testing::Message()
+ << "Flags " << std::hex << static_cast<int>(flags));
+
+ testing::StrictMock<test::MockSpdyFramerVisitor> visitor;
+ deframer_.set_visitor(&visitor);
+
+ SpdyRstStreamIR rst_stream(/* stream_id = */ 13, ERROR_CODE_CANCEL);
+ SpdySerializedFrame frame(framer_.SerializeRstStream(rst_stream));
+ if (use_output_) {
+ output_.Reset();
+ ASSERT_TRUE(framer_.SerializeRstStream(rst_stream, &output_));
+ frame = SpdySerializedFrame(output_.Begin(), output_.Size(), false);
+ }
+ SetFrameFlags(&frame, flags);
+
+ EXPECT_CALL(visitor, OnRstStream(13, ERROR_CODE_CANCEL));
+
+ deframer_.ProcessInput(frame.data(), frame.size());
+ EXPECT_EQ(Http2DecoderAdapter::SPDY_READY_FOR_FRAME, deframer_.state());
+ EXPECT_EQ(Http2DecoderAdapter::SPDY_NO_ERROR, deframer_.spdy_framer_error())
+ << Http2DecoderAdapter::SpdyFramerErrorToString(
+ deframer_.spdy_framer_error());
+ deframer_.Reset();
+ } while (++flags != 0);
+}
+
+TEST_P(SpdyFramerTest, SettingsFrameFlags) {
+ uint8_t flags = 0;
+ do {
+ SCOPED_TRACE(testing::Message()
+ << "Flags " << std::hex << static_cast<int>(flags));
+
+ testing::StrictMock<test::MockSpdyFramerVisitor> visitor;
+ deframer_.set_visitor(&visitor);
+
+ SpdySettingsIR settings_ir;
+ settings_ir.AddSetting(SETTINGS_INITIAL_WINDOW_SIZE, 16);
+ SpdySerializedFrame frame(framer_.SerializeSettings(settings_ir));
+ if (use_output_) {
+ output_.Reset();
+ ASSERT_TRUE(framer_.SerializeSettings(settings_ir, &output_));
+ frame = SpdySerializedFrame(output_.Begin(), output_.Size(), false);
+ }
+ SetFrameFlags(&frame, flags);
+
+ if (flags & SETTINGS_FLAG_ACK) {
+ EXPECT_CALL(visitor, OnError(_));
+ } else {
+ EXPECT_CALL(visitor, OnSettings());
+ EXPECT_CALL(visitor, OnSetting(SETTINGS_INITIAL_WINDOW_SIZE, 16));
+ EXPECT_CALL(visitor, OnSettingsEnd());
+ }
+
+ deframer_.ProcessInput(frame.data(), frame.size());
+ if (flags & SETTINGS_FLAG_ACK) {
+ // The frame is invalid because ACK frames should have no payload.
+ EXPECT_EQ(Http2DecoderAdapter::SPDY_ERROR, deframer_.state());
+ EXPECT_EQ(Http2DecoderAdapter::SPDY_INVALID_CONTROL_FRAME_SIZE,
+ deframer_.spdy_framer_error())
+ << Http2DecoderAdapter::SpdyFramerErrorToString(
+ deframer_.spdy_framer_error());
+ } else {
+ EXPECT_EQ(Http2DecoderAdapter::SPDY_READY_FOR_FRAME, deframer_.state());
+ EXPECT_EQ(Http2DecoderAdapter::SPDY_NO_ERROR,
+ deframer_.spdy_framer_error())
+ << Http2DecoderAdapter::SpdyFramerErrorToString(
+ deframer_.spdy_framer_error());
+ }
+ deframer_.Reset();
+ } while (++flags != 0);
+}
+
+TEST_P(SpdyFramerTest, GoawayFrameFlags) {
+ uint8_t flags = 0;
+ do {
+ SCOPED_TRACE(testing::Message()
+ << "Flags " << std::hex << static_cast<int>(flags));
+
+ testing::StrictMock<test::MockSpdyFramerVisitor> visitor;
+
+ deframer_.set_visitor(&visitor);
+
+ SpdyGoAwayIR goaway_ir(/* last_good_stream_id = */ 97, ERROR_CODE_NO_ERROR,
+ "test");
+ SpdySerializedFrame frame(framer_.SerializeGoAway(goaway_ir));
+ if (use_output_) {
+ output_.Reset();
+ ASSERT_TRUE(framer_.SerializeGoAway(goaway_ir, &output_));
+ frame = SpdySerializedFrame(output_.Begin(), output_.Size(), false);
+ }
+ SetFrameFlags(&frame, flags);
+
+ EXPECT_CALL(visitor, OnGoAway(97, ERROR_CODE_NO_ERROR));
+
+ deframer_.ProcessInput(frame.data(), frame.size());
+ EXPECT_EQ(Http2DecoderAdapter::SPDY_READY_FOR_FRAME, deframer_.state());
+ EXPECT_EQ(Http2DecoderAdapter::SPDY_NO_ERROR, deframer_.spdy_framer_error())
+ << Http2DecoderAdapter::SpdyFramerErrorToString(
+ deframer_.spdy_framer_error());
+ deframer_.Reset();
+ } while (++flags != 0);
+}
+
+TEST_P(SpdyFramerTest, HeadersFrameFlags) {
+ uint8_t flags = 0;
+ do {
+ SCOPED_TRACE(testing::Message()
+ << "Flags " << std::hex << static_cast<int>(flags));
+
+ testing::StrictMock<test::MockSpdyFramerVisitor> visitor;
+ SpdyFramer framer(SpdyFramer::ENABLE_COMPRESSION);
+ Http2DecoderAdapter deframer;
+ deframer.set_visitor(&visitor);
+
+ SpdyHeadersIR headers_ir(/* stream_id = */ 57);
+ if (flags & HEADERS_FLAG_PRIORITY) {
+ headers_ir.set_weight(3);
+ headers_ir.set_has_priority(true);
+ headers_ir.set_parent_stream_id(5);
+ headers_ir.set_exclusive(true);
+ }
+ headers_ir.SetHeader("foo", "bar");
+ SpdySerializedFrame frame(SpdyFramerPeer::SerializeHeaders(
+ &framer, headers_ir, use_output_ ? &output_ : nullptr));
+ uint8_t set_flags = flags & ~HEADERS_FLAG_PADDED;
+ SetFrameFlags(&frame, set_flags);
+
+ // Expected callback values
+ SpdyStreamId stream_id = 57;
+ bool has_priority = false;
+ int weight = 0;
+ SpdyStreamId parent_stream_id = 0;
+ bool exclusive = false;
+ bool fin = flags & CONTROL_FLAG_FIN;
+ bool end = flags & HEADERS_FLAG_END_HEADERS;
+ if (flags & HEADERS_FLAG_PRIORITY) {
+ has_priority = true;
+ weight = 3;
+ parent_stream_id = 5;
+ exclusive = true;
+ }
+ EXPECT_CALL(visitor, OnHeaders(stream_id, has_priority, weight,
+ parent_stream_id, exclusive, fin, end));
+ EXPECT_CALL(visitor, OnHeaderFrameStart(57)).Times(1);
+ if (end) {
+ EXPECT_CALL(visitor, OnHeaderFrameEnd(57)).Times(1);
+ }
+ if (flags & DATA_FLAG_FIN && end) {
+ EXPECT_CALL(visitor, OnStreamEnd(_));
+ } else {
+ // Do not close the stream if we are expecting a CONTINUATION frame.
+ EXPECT_CALL(visitor, OnStreamEnd(_)).Times(0);
+ }
+
+ deframer.ProcessInput(frame.data(), frame.size());
+ EXPECT_EQ(Http2DecoderAdapter::SPDY_READY_FOR_FRAME, deframer.state());
+ EXPECT_EQ(Http2DecoderAdapter::SPDY_NO_ERROR, deframer.spdy_framer_error())
+ << Http2DecoderAdapter::SpdyFramerErrorToString(
+ deframer.spdy_framer_error());
+ deframer.Reset();
+ } while (++flags != 0);
+}
+
+TEST_P(SpdyFramerTest, PingFrameFlags) {
+ uint8_t flags = 0;
+ do {
+ SCOPED_TRACE(testing::Message()
+ << "Flags " << std::hex << static_cast<int>(flags));
+
+ testing::StrictMock<test::MockSpdyFramerVisitor> visitor;
+ deframer_.set_visitor(&visitor);
+
+ SpdySerializedFrame frame(framer_.SerializePing(SpdyPingIR(42)));
+ SetFrameFlags(&frame, flags);
+
+ EXPECT_CALL(visitor, OnPing(42, flags & PING_FLAG_ACK));
+
+ deframer_.ProcessInput(frame.data(), frame.size());
+ EXPECT_EQ(Http2DecoderAdapter::SPDY_READY_FOR_FRAME, deframer_.state());
+ EXPECT_EQ(Http2DecoderAdapter::SPDY_NO_ERROR, deframer_.spdy_framer_error())
+ << Http2DecoderAdapter::SpdyFramerErrorToString(
+ deframer_.spdy_framer_error());
+ deframer_.Reset();
+ } while (++flags != 0);
+}
+
+TEST_P(SpdyFramerTest, WindowUpdateFrameFlags) {
+ uint8_t flags = 0;
+ do {
+ SCOPED_TRACE(testing::Message()
+ << "Flags " << std::hex << static_cast<int>(flags));
+
+ testing::StrictMock<test::MockSpdyFramerVisitor> visitor;
+
+ deframer_.set_visitor(&visitor);
+
+ SpdySerializedFrame frame(framer_.SerializeWindowUpdate(
+ SpdyWindowUpdateIR(/* stream_id = */ 4, /* delta = */ 1024)));
+ SetFrameFlags(&frame, flags);
+
+ EXPECT_CALL(visitor, OnWindowUpdate(4, 1024));
+
+ deframer_.ProcessInput(frame.data(), frame.size());
+ EXPECT_EQ(Http2DecoderAdapter::SPDY_READY_FOR_FRAME, deframer_.state());
+ EXPECT_EQ(Http2DecoderAdapter::SPDY_NO_ERROR, deframer_.spdy_framer_error())
+ << Http2DecoderAdapter::SpdyFramerErrorToString(
+ deframer_.spdy_framer_error());
+ deframer_.Reset();
+ } while (++flags != 0);
+}
+
+TEST_P(SpdyFramerTest, PushPromiseFrameFlags) {
+ const SpdyStreamId client_id = 123; // Must be odd.
+ const SpdyStreamId promised_id = 22; // Must be even.
+ uint8_t flags = 0;
+ do {
+ SCOPED_TRACE(testing::Message()
+ << "Flags " << std::hex << static_cast<int>(flags));
+
+ testing::StrictMock<test::MockSpdyFramerVisitor> visitor;
+ testing::StrictMock<test::MockDebugVisitor> debug_visitor;
+ SpdyFramer framer(SpdyFramer::ENABLE_COMPRESSION);
+ Http2DecoderAdapter deframer;
+ deframer.set_visitor(&visitor);
+ deframer.set_debug_visitor(&debug_visitor);
+ framer.set_debug_visitor(&debug_visitor);
+
+ EXPECT_CALL(
+ debug_visitor,
+ OnSendCompressedFrame(client_id, SpdyFrameType::PUSH_PROMISE, _, _));
+
+ SpdyPushPromiseIR push_promise(client_id, promised_id);
+ push_promise.SetHeader("foo", "bar");
+ SpdySerializedFrame frame(SpdyFramerPeer::SerializePushPromise(
+ &framer, push_promise, use_output_ ? &output_ : nullptr));
+ // TODO(jgraettinger): Add padding to SpdyPushPromiseIR,
+ // and implement framing.
+ SetFrameFlags(&frame, flags & ~HEADERS_FLAG_PADDED);
+
+ bool end = flags & PUSH_PROMISE_FLAG_END_PUSH_PROMISE;
+ EXPECT_CALL(debug_visitor, OnReceiveCompressedFrame(
+ client_id, SpdyFrameType::PUSH_PROMISE, _));
+ EXPECT_CALL(visitor, OnPushPromise(client_id, promised_id, end));
+ EXPECT_CALL(visitor, OnHeaderFrameStart(client_id)).Times(1);
+ if (end) {
+ EXPECT_CALL(visitor, OnHeaderFrameEnd(client_id)).Times(1);
+ }
+
+ deframer.ProcessInput(frame.data(), frame.size());
+ EXPECT_EQ(Http2DecoderAdapter::SPDY_READY_FOR_FRAME, deframer.state());
+ EXPECT_EQ(Http2DecoderAdapter::SPDY_NO_ERROR, deframer.spdy_framer_error())
+ << Http2DecoderAdapter::SpdyFramerErrorToString(
+ deframer.spdy_framer_error());
+ } while (++flags != 0);
+}
+
+TEST_P(SpdyFramerTest, ContinuationFrameFlags) {
+ uint8_t flags = 0;
+ do {
+ if (use_output_) {
+ output_.Reset();
+ }
+ SCOPED_TRACE(testing::Message()
+ << "Flags " << std::hex << static_cast<int>(flags));
+
+ testing::StrictMock<test::MockSpdyFramerVisitor> visitor;
+ testing::StrictMock<test::MockDebugVisitor> debug_visitor;
+ SpdyFramer framer(SpdyFramer::ENABLE_COMPRESSION);
+ Http2DecoderAdapter deframer;
+ deframer.set_visitor(&visitor);
+ deframer.set_debug_visitor(&debug_visitor);
+ framer.set_debug_visitor(&debug_visitor);
+
+ EXPECT_CALL(debug_visitor,
+ OnSendCompressedFrame(42, SpdyFrameType::HEADERS, _, _));
+ EXPECT_CALL(debug_visitor,
+ OnReceiveCompressedFrame(42, SpdyFrameType::HEADERS, _));
+ EXPECT_CALL(visitor, OnHeaders(42, false, 0, 0, false, false, false));
+ EXPECT_CALL(visitor, OnHeaderFrameStart(42)).Times(1);
+
+ SpdyHeadersIR headers_ir(/* stream_id = */ 42);
+ headers_ir.SetHeader("foo", "bar");
+ SpdySerializedFrame frame0;
+ if (use_output_) {
+ EXPECT_TRUE(framer.SerializeHeaders(headers_ir, &output_));
+ frame0 = SpdySerializedFrame(output_.Begin(), output_.Size(), false);
+ } else {
+ frame0 = framer.SerializeHeaders(headers_ir);
+ }
+ SetFrameFlags(&frame0, 0);
+
+ SpdyContinuationIR continuation(/* stream_id = */ 42);
+ SpdySerializedFrame frame1;
+ if (use_output_) {
+ char* begin = output_.Begin() + output_.Size();
+ ASSERT_TRUE(framer.SerializeContinuation(continuation, &output_));
+ frame1 =
+ SpdySerializedFrame(begin, output_.Size() - frame0.size(), false);
+ } else {
+ frame1 = framer.SerializeContinuation(continuation);
+ }
+ SetFrameFlags(&frame1, flags);
+
+ EXPECT_CALL(debug_visitor,
+ OnReceiveCompressedFrame(42, SpdyFrameType::CONTINUATION, _));
+ EXPECT_CALL(visitor, OnContinuation(42, flags & HEADERS_FLAG_END_HEADERS));
+ bool end = flags & HEADERS_FLAG_END_HEADERS;
+ if (end) {
+ EXPECT_CALL(visitor, OnHeaderFrameEnd(42)).Times(1);
+ }
+
+ deframer.ProcessInput(frame0.data(), frame0.size());
+ deframer.ProcessInput(frame1.data(), frame1.size());
+ EXPECT_EQ(Http2DecoderAdapter::SPDY_READY_FOR_FRAME, deframer.state());
+ EXPECT_EQ(Http2DecoderAdapter::SPDY_NO_ERROR, deframer.spdy_framer_error())
+ << Http2DecoderAdapter::SpdyFramerErrorToString(
+ deframer.spdy_framer_error());
+ } while (++flags != 0);
+}
+
+// TODO(mlavan): Add TEST_P(SpdyFramerTest, AltSvcFrameFlags)
+
+// Test handling of a RST_STREAM with out-of-bounds status codes.
+TEST_P(SpdyFramerTest, RstStreamStatusBounds) {
+ const unsigned char kH2RstStreamInvalid[] = {
+ 0x00, 0x00, 0x04, // Length: 4
+ 0x03, // Type: RST_STREAM
+ 0x00, // Flags: none
+ 0x00, 0x00, 0x00, 0x01, // Stream: 1
+ 0x00, 0x00, 0x00, 0x00, // Error: NO_ERROR
+ };
+ const unsigned char kH2RstStreamNumStatusCodes[] = {
+ 0x00, 0x00, 0x04, // Length: 4
+ 0x03, // Type: RST_STREAM
+ 0x00, // Flags: none
+ 0x00, 0x00, 0x00, 0x01, // Stream: 1
+ 0x00, 0x00, 0x00, 0xff, // Error: 255
+ };
+
+ testing::StrictMock<test::MockSpdyFramerVisitor> visitor;
+ deframer_.set_visitor(&visitor);
+
+ EXPECT_CALL(visitor, OnRstStream(1, ERROR_CODE_NO_ERROR));
+ deframer_.ProcessInput(reinterpret_cast<const char*>(kH2RstStreamInvalid),
+ SPDY_ARRAYSIZE(kH2RstStreamInvalid));
+ EXPECT_EQ(Http2DecoderAdapter::SPDY_READY_FOR_FRAME, deframer_.state());
+ EXPECT_EQ(Http2DecoderAdapter::SPDY_NO_ERROR, deframer_.spdy_framer_error())
+ << Http2DecoderAdapter::SpdyFramerErrorToString(
+ deframer_.spdy_framer_error());
+ deframer_.Reset();
+
+ EXPECT_CALL(visitor, OnRstStream(1, ERROR_CODE_INTERNAL_ERROR));
+ deframer_.ProcessInput(
+ reinterpret_cast<const char*>(kH2RstStreamNumStatusCodes),
+ SPDY_ARRAYSIZE(kH2RstStreamNumStatusCodes));
+ EXPECT_EQ(Http2DecoderAdapter::SPDY_READY_FOR_FRAME, deframer_.state());
+ EXPECT_EQ(Http2DecoderAdapter::SPDY_NO_ERROR, deframer_.spdy_framer_error())
+ << Http2DecoderAdapter::SpdyFramerErrorToString(
+ deframer_.spdy_framer_error());
+}
+
+// Test handling of GOAWAY frames with out-of-bounds status code.
+TEST_P(SpdyFramerTest, GoAwayStatusBounds) {
+ const unsigned char kH2FrameData[] = {
+ 0x00, 0x00, 0x0a, // Length: 10
+ 0x07, // Type: GOAWAY
+ 0x00, // Flags: none
+ 0x00, 0x00, 0x00, 0x00, // Stream: 0
+ 0x00, 0x00, 0x00, 0x01, // Last: 1
+ 0xff, 0xff, 0xff, 0xff, // Error: 0xffffffff
+ 0x47, 0x41, // Description
+ };
+ testing::StrictMock<test::MockSpdyFramerVisitor> visitor;
+ deframer_.set_visitor(&visitor);
+
+ EXPECT_CALL(visitor, OnGoAway(1, ERROR_CODE_INTERNAL_ERROR));
+ deframer_.ProcessInput(reinterpret_cast<const char*>(kH2FrameData),
+ SPDY_ARRAYSIZE(kH2FrameData));
+ EXPECT_EQ(Http2DecoderAdapter::SPDY_READY_FOR_FRAME, deframer_.state());
+ EXPECT_EQ(Http2DecoderAdapter::SPDY_NO_ERROR, deframer_.spdy_framer_error())
+ << Http2DecoderAdapter::SpdyFramerErrorToString(
+ deframer_.spdy_framer_error());
+}
+
+// Tests handling of a GOAWAY frame with out-of-bounds stream ID.
+TEST_P(SpdyFramerTest, GoAwayStreamIdBounds) {
+ const unsigned char kH2FrameData[] = {
+ 0x00, 0x00, 0x08, // Length: 8
+ 0x07, // Type: GOAWAY
+ 0x00, // Flags: none
+ 0x00, 0x00, 0x00, 0x00, // Stream: 0
+ 0xff, 0xff, 0xff, 0xff, // Last: 0x7fffffff (R-bit set)
+ 0x00, 0x00, 0x00, 0x00, // Error: NO_ERROR
+ };
+
+ testing::StrictMock<test::MockSpdyFramerVisitor> visitor;
+
+ deframer_.set_visitor(&visitor);
+
+ EXPECT_CALL(visitor, OnGoAway(0x7fffffff, ERROR_CODE_NO_ERROR));
+ deframer_.ProcessInput(reinterpret_cast<const char*>(kH2FrameData),
+ SPDY_ARRAYSIZE(kH2FrameData));
+ EXPECT_EQ(Http2DecoderAdapter::SPDY_READY_FOR_FRAME, deframer_.state());
+ EXPECT_EQ(Http2DecoderAdapter::SPDY_NO_ERROR, deframer_.spdy_framer_error())
+ << Http2DecoderAdapter::SpdyFramerErrorToString(
+ deframer_.spdy_framer_error());
+}
+
+TEST_P(SpdyFramerTest, OnAltSvcWithOrigin) {
+ const SpdyStreamId kStreamId = 0; // Stream id must be zero if origin given.
+
+ testing::StrictMock<test::MockSpdyFramerVisitor> visitor;
+
+ deframer_.set_visitor(&visitor);
+
+ SpdyAltSvcWireFormat::AlternativeService altsvc1(
+ "pid1", "host", 443, 5, SpdyAltSvcWireFormat::VersionVector());
+ SpdyAltSvcWireFormat::AlternativeService altsvc2(
+ "p\"=i:d", "h_\\o\"st", 123, 42, SpdyAltSvcWireFormat::VersionVector{24});
+ SpdyAltSvcWireFormat::AlternativeServiceVector altsvc_vector;
+ altsvc_vector.push_back(altsvc1);
+ altsvc_vector.push_back(altsvc2);
+ EXPECT_CALL(visitor,
+ OnAltSvc(kStreamId, SpdyStringPiece("o_r|g!n"), altsvc_vector));
+
+ SpdyAltSvcIR altsvc_ir(kStreamId);
+ altsvc_ir.set_origin("o_r|g!n");
+ altsvc_ir.add_altsvc(altsvc1);
+ altsvc_ir.add_altsvc(altsvc2);
+ SpdySerializedFrame frame(framer_.SerializeFrame(altsvc_ir));
+ if (use_output_) {
+ output_.Reset();
+ EXPECT_EQ(framer_.SerializeFrame(altsvc_ir, &output_), frame.size());
+ frame = SpdySerializedFrame(output_.Begin(), output_.Size(), false);
+ }
+ deframer_.ProcessInput(frame.data(), frame.size());
+
+ EXPECT_EQ(Http2DecoderAdapter::SPDY_READY_FOR_FRAME, deframer_.state());
+ EXPECT_EQ(Http2DecoderAdapter::SPDY_NO_ERROR, deframer_.spdy_framer_error())
+ << Http2DecoderAdapter::SpdyFramerErrorToString(
+ deframer_.spdy_framer_error());
+}
+
+TEST_P(SpdyFramerTest, OnAltSvcNoOrigin) {
+ const SpdyStreamId kStreamId = 1;
+
+ testing::StrictMock<test::MockSpdyFramerVisitor> visitor;
+
+ deframer_.set_visitor(&visitor);
+
+ SpdyAltSvcWireFormat::AlternativeService altsvc1(
+ "pid1", "host", 443, 5, SpdyAltSvcWireFormat::VersionVector());
+ SpdyAltSvcWireFormat::AlternativeService altsvc2(
+ "p\"=i:d", "h_\\o\"st", 123, 42, SpdyAltSvcWireFormat::VersionVector{24});
+ SpdyAltSvcWireFormat::AlternativeServiceVector altsvc_vector;
+ altsvc_vector.push_back(altsvc1);
+ altsvc_vector.push_back(altsvc2);
+ EXPECT_CALL(visitor, OnAltSvc(kStreamId, SpdyStringPiece(""), altsvc_vector));
+
+ SpdyAltSvcIR altsvc_ir(kStreamId);
+ altsvc_ir.add_altsvc(altsvc1);
+ altsvc_ir.add_altsvc(altsvc2);
+ SpdySerializedFrame frame(framer_.SerializeFrame(altsvc_ir));
+ deframer_.ProcessInput(frame.data(), frame.size());
+
+ EXPECT_EQ(Http2DecoderAdapter::SPDY_READY_FOR_FRAME, deframer_.state());
+ EXPECT_EQ(Http2DecoderAdapter::SPDY_NO_ERROR, deframer_.spdy_framer_error())
+ << Http2DecoderAdapter::SpdyFramerErrorToString(
+ deframer_.spdy_framer_error());
+}
+
+TEST_P(SpdyFramerTest, OnAltSvcEmptyProtocolId) {
+ const SpdyStreamId kStreamId = 0; // Stream id must be zero if origin given.
+
+ testing::StrictMock<test::MockSpdyFramerVisitor> visitor;
+
+ deframer_.set_visitor(&visitor);
+
+ EXPECT_CALL(visitor,
+ OnError(Http2DecoderAdapter::SPDY_INVALID_CONTROL_FRAME));
+
+ SpdyAltSvcIR altsvc_ir(kStreamId);
+ altsvc_ir.set_origin("o1");
+ altsvc_ir.add_altsvc(SpdyAltSvcWireFormat::AlternativeService(
+ "pid1", "host", 443, 5, SpdyAltSvcWireFormat::VersionVector()));
+ altsvc_ir.add_altsvc(SpdyAltSvcWireFormat::AlternativeService(
+ "", "h1", 443, 10, SpdyAltSvcWireFormat::VersionVector()));
+ SpdySerializedFrame frame(framer_.SerializeFrame(altsvc_ir));
+ if (use_output_) {
+ output_.Reset();
+ EXPECT_EQ(framer_.SerializeFrame(altsvc_ir, &output_), frame.size());
+ frame = SpdySerializedFrame(output_.Begin(), output_.Size(), false);
+ }
+ deframer_.ProcessInput(frame.data(), frame.size());
+
+ EXPECT_EQ(Http2DecoderAdapter::SPDY_ERROR, deframer_.state());
+ EXPECT_EQ(Http2DecoderAdapter::SPDY_INVALID_CONTROL_FRAME,
+ deframer_.spdy_framer_error())
+ << Http2DecoderAdapter::SpdyFramerErrorToString(
+ deframer_.spdy_framer_error());
+}
+
+TEST_P(SpdyFramerTest, OnAltSvcBadLengths) {
+ const unsigned char kType = SerializeFrameType(SpdyFrameType::ALTSVC);
+ const unsigned char kFrameDataOriginLenLargerThanFrame[] = {
+ 0x00, 0x00, 0x05, kType, 0x00, 0x00, 0x00,
+ 0x00, 0x03, 0x42, 0x42, 'f', 'o', 'o',
+ };
+
+ TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION);
+
+ deframer_.set_visitor(&visitor);
+ visitor.SimulateInFramer(kFrameDataOriginLenLargerThanFrame,
+ sizeof(kFrameDataOriginLenLargerThanFrame));
+
+ EXPECT_EQ(1, visitor.error_count_);
+ EXPECT_EQ(Http2DecoderAdapter::SPDY_INVALID_CONTROL_FRAME,
+ visitor.deframer_.spdy_framer_error());
+}
+
+// Tests handling of ALTSVC frames delivered in small chunks.
+TEST_P(SpdyFramerTest, ReadChunkedAltSvcFrame) {
+ SpdyAltSvcIR altsvc_ir(/* stream_id = */ 1);
+ SpdyAltSvcWireFormat::AlternativeService altsvc1(
+ "pid1", "host", 443, 5, SpdyAltSvcWireFormat::VersionVector());
+ SpdyAltSvcWireFormat::AlternativeService altsvc2(
+ "p\"=i:d", "h_\\o\"st", 123, 42, SpdyAltSvcWireFormat::VersionVector{24});
+ altsvc_ir.add_altsvc(altsvc1);
+ altsvc_ir.add_altsvc(altsvc2);
+
+ SpdySerializedFrame control_frame(framer_.SerializeAltSvc(altsvc_ir));
+ TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION);
+
+ // Read data in small chunks.
+ size_t framed_data = 0;
+ size_t unframed_data = control_frame.size();
+ size_t kReadChunkSize = 5; // Read five bytes at a time.
+ while (unframed_data > 0) {
+ size_t to_read = std::min(kReadChunkSize, unframed_data);
+ visitor.SimulateInFramer(
+ reinterpret_cast<unsigned char*>(control_frame.data() + framed_data),
+ to_read);
+ unframed_data -= to_read;
+ framed_data += to_read;
+ }
+ EXPECT_EQ(0, visitor.error_count_);
+ EXPECT_EQ(1, visitor.altsvc_count_);
+ ASSERT_NE(nullptr, visitor.test_altsvc_ir_);
+ ASSERT_EQ(2u, visitor.test_altsvc_ir_->altsvc_vector().size());
+ EXPECT_TRUE(visitor.test_altsvc_ir_->altsvc_vector()[0] == altsvc1);
+ EXPECT_TRUE(visitor.test_altsvc_ir_->altsvc_vector()[1] == altsvc2);
+}
+
+// While RFC7838 Section 4 says that an ALTSVC frame on stream 0 with empty
+// origin MUST be ignored, it is not implemented at the framer level: instead,
+// such frames are passed on to the consumer.
+TEST_P(SpdyFramerTest, ReadAltSvcFrame) {
+ constexpr struct {
+ uint32_t stream_id;
+ const char* origin;
+ } test_cases[] = {{0, ""},
+ {1, ""},
+ {0, "https://www.example.com"},
+ {1, "https://www.example.com"}};
+ for (const auto& test_case : test_cases) {
+ SpdyAltSvcIR altsvc_ir(test_case.stream_id);
+ SpdyAltSvcWireFormat::AlternativeService altsvc(
+ "pid1", "host", 443, 5, SpdyAltSvcWireFormat::VersionVector());
+ altsvc_ir.add_altsvc(altsvc);
+ altsvc_ir.set_origin(test_case.origin);
+ SpdySerializedFrame frame(framer_.SerializeAltSvc(altsvc_ir));
+
+ TestSpdyVisitor visitor(SpdyFramer::ENABLE_COMPRESSION);
+ deframer_.set_visitor(&visitor);
+ deframer_.ProcessInput(frame.data(), frame.size());
+
+ EXPECT_EQ(0, visitor.error_count_);
+ EXPECT_EQ(1, visitor.altsvc_count_);
+ EXPECT_EQ(Http2DecoderAdapter::SPDY_READY_FOR_FRAME, deframer_.state());
+ EXPECT_EQ(Http2DecoderAdapter::SPDY_NO_ERROR, deframer_.spdy_framer_error())
+ << Http2DecoderAdapter::SpdyFramerErrorToString(
+ deframer_.spdy_framer_error());
+ }
+}
+
+// An ALTSVC frame with invalid Alt-Svc-Field-Value results in an error.
+TEST_P(SpdyFramerTest, ErrorOnAltSvcFrameWithInvalidValue) {
+ // Alt-Svc-Field-Value must be "clear" or must contain an "=" character
+ // per RFC7838 Section 3.
+ const char kFrameData[] = {
+ 0x00, 0x00, 0x16, // Length: 22
+ 0x0a, // Type: ALTSVC
+ 0x00, // Flags: none
+ 0x00, 0x00, 0x00, 0x01, // Stream: 1
+ 0x00, 0x00, // Origin-Len: 0
+ 0x74, 0x68, 0x69, 0x73, // thisisnotavalidvalue
+ 0x69, 0x73, 0x6e, 0x6f, 0x74, 0x61, 0x76, 0x61,
+ 0x6c, 0x69, 0x64, 0x76, 0x61, 0x6c, 0x75, 0x65,
+ };
+
+ TestSpdyVisitor visitor(SpdyFramer::ENABLE_COMPRESSION);
+ deframer_.set_visitor(&visitor);
+ deframer_.ProcessInput(kFrameData, sizeof(kFrameData));
+
+ EXPECT_EQ(1, visitor.error_count_);
+ EXPECT_EQ(0, visitor.altsvc_count_);
+ EXPECT_EQ(Http2DecoderAdapter::SPDY_ERROR, deframer_.state());
+ EXPECT_EQ(Http2DecoderAdapter::SPDY_INVALID_CONTROL_FRAME,
+ deframer_.spdy_framer_error())
+ << Http2DecoderAdapter::SpdyFramerErrorToString(
+ deframer_.spdy_framer_error());
+}
+
+// Tests handling of PRIORITY frames.
+TEST_P(SpdyFramerTest, ReadPriority) {
+ SpdyPriorityIR priority(/* stream_id = */ 3,
+ /* parent_stream_id = */ 1,
+ /* weight = */ 256,
+ /* exclusive = */ false);
+ SpdySerializedFrame frame(framer_.SerializePriority(priority));
+ if (use_output_) {
+ output_.Reset();
+ ASSERT_TRUE(framer_.SerializePriority(priority, &output_));
+ frame = SpdySerializedFrame(output_.Begin(), output_.Size(), false);
+ }
+ testing::StrictMock<test::MockSpdyFramerVisitor> visitor;
+ deframer_.set_visitor(&visitor);
+ EXPECT_CALL(visitor, OnPriority(3, 1, 256, false));
+ deframer_.ProcessInput(frame.data(), frame.size());
+
+ EXPECT_EQ(Http2DecoderAdapter::SPDY_READY_FOR_FRAME, deframer_.state());
+ EXPECT_EQ(Http2DecoderAdapter::SPDY_NO_ERROR, deframer_.spdy_framer_error())
+ << Http2DecoderAdapter::SpdyFramerErrorToString(
+ deframer_.spdy_framer_error());
+}
+
+// Tests handling of PRIORITY frame with incorrect size.
+TEST_P(SpdyFramerTest, ReadIncorrectlySizedPriority) {
+ // PRIORITY frame of size 4, which isn't correct.
+ const unsigned char kFrameData[] = {
+ 0x00, 0x00, 0x04, // Length: 4
+ 0x02, // Type: PRIORITY
+ 0x00, // Flags: none
+ 0x00, 0x00, 0x00, 0x03, // Stream: 3
+ 0x00, 0x00, 0x00, 0x01, // Priority (Truncated)
+ };
+
+ TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION);
+ visitor.SimulateInFramer(kFrameData, sizeof(kFrameData));
+
+ EXPECT_EQ(Http2DecoderAdapter::SPDY_ERROR, visitor.deframer_.state());
+ EXPECT_EQ(Http2DecoderAdapter::SPDY_INVALID_CONTROL_FRAME_SIZE,
+ visitor.deframer_.spdy_framer_error())
+ << Http2DecoderAdapter::SpdyFramerErrorToString(
+ visitor.deframer_.spdy_framer_error());
+}
+
+// Tests handling of PING frame with incorrect size.
+TEST_P(SpdyFramerTest, ReadIncorrectlySizedPing) {
+ // PING frame of size 4, which isn't correct.
+ const unsigned char kFrameData[] = {
+ 0x00, 0x00, 0x04, // Length: 4
+ 0x06, // Type: PING
+ 0x00, // Flags: none
+ 0x00, 0x00, 0x00, 0x00, // Stream: 0
+ 0x00, 0x00, 0x00, 0x01, // Ping (Truncated)
+ };
+
+ TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION);
+ visitor.SimulateInFramer(kFrameData, sizeof(kFrameData));
+
+ EXPECT_EQ(Http2DecoderAdapter::SPDY_ERROR, visitor.deframer_.state());
+ EXPECT_EQ(Http2DecoderAdapter::SPDY_INVALID_CONTROL_FRAME_SIZE,
+ visitor.deframer_.spdy_framer_error())
+ << Http2DecoderAdapter::SpdyFramerErrorToString(
+ visitor.deframer_.spdy_framer_error());
+}
+
+// Tests handling of WINDOW_UPDATE frame with incorrect size.
+TEST_P(SpdyFramerTest, ReadIncorrectlySizedWindowUpdate) {
+ // WINDOW_UPDATE frame of size 3, which isn't correct.
+ const unsigned char kFrameData[] = {
+ 0x00, 0x00, 0x03, // Length: 3
+ 0x08, // Type: WINDOW_UPDATE
+ 0x00, // Flags: none
+ 0x00, 0x00, 0x00, 0x03, // Stream: 3
+ 0x00, 0x00, 0x01, // WindowUpdate (Truncated)
+ };
+
+ TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION);
+ visitor.SimulateInFramer(kFrameData, sizeof(kFrameData));
+
+ EXPECT_EQ(Http2DecoderAdapter::SPDY_ERROR, visitor.deframer_.state());
+ EXPECT_EQ(Http2DecoderAdapter::SPDY_INVALID_CONTROL_FRAME_SIZE,
+ visitor.deframer_.spdy_framer_error())
+ << Http2DecoderAdapter::SpdyFramerErrorToString(
+ visitor.deframer_.spdy_framer_error());
+}
+
+// Tests handling of RST_STREAM frame with incorrect size.
+TEST_P(SpdyFramerTest, ReadIncorrectlySizedRstStream) {
+ // RST_STREAM frame of size 3, which isn't correct.
+ const unsigned char kFrameData[] = {
+ 0x00, 0x00, 0x03, // Length: 3
+ 0x03, // Type: RST_STREAM
+ 0x00, // Flags: none
+ 0x00, 0x00, 0x00, 0x03, // Stream: 3
+ 0x00, 0x00, 0x01, // RstStream (Truncated)
+ };
+
+ TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION);
+ visitor.SimulateInFramer(kFrameData, sizeof(kFrameData));
+
+ EXPECT_EQ(Http2DecoderAdapter::SPDY_ERROR, visitor.deframer_.state());
+ EXPECT_EQ(Http2DecoderAdapter::SPDY_INVALID_CONTROL_FRAME_SIZE,
+ visitor.deframer_.spdy_framer_error())
+ << Http2DecoderAdapter::SpdyFramerErrorToString(
+ visitor.deframer_.spdy_framer_error());
+}
+
+// Regression test for https://crbug.com/548674:
+// RST_STREAM with payload must not be accepted.
+TEST_P(SpdyFramerTest, ReadInvalidRstStreamWithPayload) {
+ const unsigned char kFrameData[] = {
+ 0x00, 0x00, 0x07, // Length: 7
+ 0x03, // Type: RST_STREAM
+ 0x00, // Flags: none
+ 0x00, 0x00, 0x00, 0x01, // Stream: 1
+ 0x00, 0x00, 0x00, 0x00, // Error: NO_ERROR
+ 'f', 'o', 'o' // Payload: "foo"
+ };
+
+ TestSpdyVisitor visitor(SpdyFramer::DISABLE_COMPRESSION);
+ visitor.SimulateInFramer(kFrameData, sizeof(kFrameData));
+
+ EXPECT_EQ(Http2DecoderAdapter::SPDY_ERROR, visitor.deframer_.state());
+ EXPECT_EQ(Http2DecoderAdapter::SPDY_INVALID_CONTROL_FRAME_SIZE,
+ visitor.deframer_.spdy_framer_error())
+ << Http2DecoderAdapter::SpdyFramerErrorToString(
+ visitor.deframer_.spdy_framer_error());
+}
+
+// Test that SpdyFramer processes, by default, all passed input in one call
+// to ProcessInput (i.e. will not be calling set_process_single_input_frame()).
+TEST_P(SpdyFramerTest, ProcessAllInput) {
+ auto visitor =
+ SpdyMakeUnique<TestSpdyVisitor>(SpdyFramer::DISABLE_COMPRESSION);
+ deframer_.set_visitor(visitor.get());
+
+ // Create two input frames.
+ SpdyHeadersIR headers(/* stream_id = */ 1);
+ headers.SetHeader("alpha", "beta");
+ headers.SetHeader("gamma", "charlie");
+ headers.SetHeader("cookie", "key1=value1; key2=value2");
+ SpdySerializedFrame headers_frame(SpdyFramerPeer::SerializeHeaders(
+ &framer_, headers, use_output_ ? &output_ : nullptr));
+
+ const char four_score[] = "Four score and seven years ago";
+ SpdyDataIR four_score_ir(/* stream_id = */ 1, four_score);
+ SpdySerializedFrame four_score_frame(framer_.SerializeData(four_score_ir));
+
+ // Put them in a single buffer (new variables here to make it easy to
+ // change the order and type of frames).
+ SpdySerializedFrame frame1 = std::move(headers_frame);
+ SpdySerializedFrame frame2 = std::move(four_score_frame);
+
+ const size_t frame1_size = frame1.size();
+ const size_t frame2_size = frame2.size();
+
+ VLOG(1) << "frame1_size = " << frame1_size;
+ VLOG(1) << "frame2_size = " << frame2_size;
+
+ SpdyString input_buffer;
+ input_buffer.append(frame1.data(), frame1_size);
+ input_buffer.append(frame2.data(), frame2_size);
+
+ const char* buf = input_buffer.data();
+ const size_t buf_size = input_buffer.size();
+
+ VLOG(1) << "buf_size = " << buf_size;
+
+ size_t processed = deframer_.ProcessInput(buf, buf_size);
+ EXPECT_EQ(buf_size, processed);
+ EXPECT_EQ(Http2DecoderAdapter::SPDY_READY_FOR_FRAME, deframer_.state());
+ EXPECT_EQ(1, visitor->headers_frame_count_);
+ EXPECT_EQ(1, visitor->data_frame_count_);
+ EXPECT_EQ(strlen(four_score), static_cast<unsigned>(visitor->data_bytes_));
+}
+
+// Test that SpdyFramer stops after processing a full frame if
+// process_single_input_frame is set. Input to ProcessInput has two frames, but
+// only processes the first when we give it the first frame split at any point,
+// or give it more than one frame in the input buffer.
+TEST_P(SpdyFramerTest, ProcessAtMostOneFrame) {
+ deframer_.set_process_single_input_frame(true);
+
+ // Create two input frames.
+ const char four_score[] = "Four score and ...";
+ SpdyDataIR four_score_ir(/* stream_id = */ 1, four_score);
+ SpdySerializedFrame four_score_frame(framer_.SerializeData(four_score_ir));
+
+ SpdyHeadersIR headers(/* stream_id = */ 2);
+ headers.SetHeader("alpha", "beta");
+ headers.SetHeader("gamma", "charlie");
+ headers.SetHeader("cookie", "key1=value1; key2=value2");
+ SpdySerializedFrame headers_frame(SpdyFramerPeer::SerializeHeaders(
+ &framer_, headers, use_output_ ? &output_ : nullptr));
+
+ // Put them in a single buffer (new variables here to make it easy to
+ // change the order and type of frames).
+ SpdySerializedFrame frame1 = std::move(four_score_frame);
+ SpdySerializedFrame frame2 = std::move(headers_frame);
+
+ const size_t frame1_size = frame1.size();
+ const size_t frame2_size = frame2.size();
+
+ VLOG(1) << "frame1_size = " << frame1_size;
+ VLOG(1) << "frame2_size = " << frame2_size;
+
+ SpdyString input_buffer;
+ input_buffer.append(frame1.data(), frame1_size);
+ input_buffer.append(frame2.data(), frame2_size);
+
+ const char* buf = input_buffer.data();
+ const size_t buf_size = input_buffer.size();
+
+ VLOG(1) << "buf_size = " << buf_size;
+
+ for (size_t first_size = 0; first_size <= buf_size; ++first_size) {
+ VLOG(1) << "first_size = " << first_size;
+ auto visitor =
+ SpdyMakeUnique<TestSpdyVisitor>(SpdyFramer::DISABLE_COMPRESSION);
+ deframer_.set_visitor(visitor.get());
+
+ EXPECT_EQ(Http2DecoderAdapter::SPDY_READY_FOR_FRAME, deframer_.state());
+
+ size_t processed_first = deframer_.ProcessInput(buf, first_size);
+ if (first_size < frame1_size) {
+ EXPECT_EQ(first_size, processed_first);
+
+ if (first_size == 0) {
+ EXPECT_EQ(Http2DecoderAdapter::SPDY_READY_FOR_FRAME, deframer_.state());
+ } else {
+ EXPECT_NE(Http2DecoderAdapter::SPDY_READY_FOR_FRAME, deframer_.state());
+ }
+
+ const char* rest = buf + processed_first;
+ const size_t remaining = buf_size - processed_first;
+ VLOG(1) << "remaining = " << remaining;
+
+ size_t processed_second = deframer_.ProcessInput(rest, remaining);
+
+ // Redundant tests just to make it easier to think about.
+ EXPECT_EQ(frame1_size - processed_first, processed_second);
+ size_t processed_total = processed_first + processed_second;
+ EXPECT_EQ(frame1_size, processed_total);
+ } else {
+ EXPECT_EQ(frame1_size, processed_first);
+ }
+
+ EXPECT_EQ(Http2DecoderAdapter::SPDY_READY_FOR_FRAME, deframer_.state());
+
+ // At this point should have processed the entirety of the first frame,
+ // and none of the second frame.
+
+ EXPECT_EQ(1, visitor->data_frame_count_);
+ EXPECT_EQ(strlen(four_score), static_cast<unsigned>(visitor->data_bytes_));
+ EXPECT_EQ(0, visitor->headers_frame_count_);
+ }
+}
+
+namespace {
+void CheckFrameAndIRSize(SpdyFrameIR* ir,
+ SpdyFramer* framer,
+ ArrayOutputBuffer* output_buffer) {
+ output_buffer->Reset();
+ SpdyFrameType type = ir->frame_type();
+ size_t ir_size = ir->size();
+ framer->SerializeFrame(*ir, output_buffer);
+ if (type == SpdyFrameType::HEADERS || type == SpdyFrameType::PUSH_PROMISE) {
+ // For HEADERS and PUSH_PROMISE, the size is an estimate.
+ EXPECT_GE(ir_size, output_buffer->Size() * 9 / 10);
+ EXPECT_LT(ir_size, output_buffer->Size() * 11 / 10);
+ } else {
+ EXPECT_EQ(ir_size, output_buffer->Size());
+ }
+}
+} // namespace
+
+TEST_P(SpdyFramerTest, SpdyFrameIRSize) {
+ SpdyFramer framer(SpdyFramer::DISABLE_COMPRESSION);
+
+ const char bytes[] = "this is a very short data frame";
+ SpdyDataIR data_ir(1, SpdyStringPiece(bytes, SPDY_ARRAYSIZE(bytes)));
+ CheckFrameAndIRSize(&data_ir, &framer, &output_);
+
+ SpdyRstStreamIR rst_ir(/* stream_id = */ 1, ERROR_CODE_PROTOCOL_ERROR);
+ CheckFrameAndIRSize(&rst_ir, &framer, &output_);
+
+ SpdySettingsIR settings_ir;
+ settings_ir.AddSetting(SETTINGS_HEADER_TABLE_SIZE, 5);
+ settings_ir.AddSetting(SETTINGS_ENABLE_PUSH, 6);
+ settings_ir.AddSetting(SETTINGS_MAX_CONCURRENT_STREAMS, 7);
+ CheckFrameAndIRSize(&settings_ir, &framer, &output_);
+
+ SpdyPingIR ping_ir(42);
+ CheckFrameAndIRSize(&ping_ir, &framer, &output_);
+
+ SpdyGoAwayIR goaway_ir(97, ERROR_CODE_NO_ERROR, "Goaway description");
+ CheckFrameAndIRSize(&goaway_ir, &framer, &output_);
+
+ SpdyHeadersIR headers_ir(1);
+ headers_ir.SetHeader("alpha", "beta");
+ headers_ir.SetHeader("gamma", "charlie");
+ headers_ir.SetHeader("cookie", "key1=value1; key2=value2");
+ CheckFrameAndIRSize(&headers_ir, &framer, &output_);
+
+ SpdyHeadersIR headers_ir_with_continuation(1);
+ headers_ir_with_continuation.SetHeader("alpha", SpdyString(100000, 'x'));
+ headers_ir_with_continuation.SetHeader("beta", SpdyString(100000, 'x'));
+ headers_ir_with_continuation.SetHeader("cookie", "key1=value1; key2=value2");
+ CheckFrameAndIRSize(&headers_ir_with_continuation, &framer, &output_);
+
+ SpdyWindowUpdateIR window_update_ir(4, 1024);
+ CheckFrameAndIRSize(&window_update_ir, &framer, &output_);
+
+ SpdyPushPromiseIR push_promise_ir(3, 8);
+ push_promise_ir.SetHeader("alpha", SpdyString(100000, 'x'));
+ push_promise_ir.SetHeader("beta", SpdyString(100000, 'x'));
+ push_promise_ir.SetHeader("cookie", "key1=value1; key2=value2");
+ CheckFrameAndIRSize(&push_promise_ir, &framer, &output_);
+
+ SpdyAltSvcWireFormat::AlternativeService altsvc1(
+ "pid1", "host", 443, 5, SpdyAltSvcWireFormat::VersionVector());
+ SpdyAltSvcWireFormat::AlternativeService altsvc2(
+ "p\"=i:d", "h_\\o\"st", 123, 42, SpdyAltSvcWireFormat::VersionVector{24});
+ SpdyAltSvcWireFormat::AlternativeServiceVector altsvc_vector;
+ altsvc_vector.push_back(altsvc1);
+ altsvc_vector.push_back(altsvc2);
+ SpdyAltSvcIR altsvc_ir(0);
+ altsvc_ir.set_origin("o_r|g!n");
+ altsvc_ir.add_altsvc(altsvc1);
+ altsvc_ir.add_altsvc(altsvc2);
+ CheckFrameAndIRSize(&altsvc_ir, &framer, &output_);
+
+ SpdyPriorityIR priority_ir(3, 1, 256, false);
+ CheckFrameAndIRSize(&priority_ir, &framer, &output_);
+
+ const char kDescription[] = "Unknown frame";
+ const uint8_t kType = 0xaf;
+ const uint8_t kFlags = 0x11;
+ SpdyUnknownIR unknown_ir(2, kType, kFlags, kDescription);
+ CheckFrameAndIRSize(&unknown_ir, &framer, &output_);
+}
+
+} // namespace test
+
+} // namespace spdy
diff --git a/chromium/net/third_party/quiche/src/spdy/core/spdy_header_block.cc b/chromium/net/third_party/quiche/src/spdy/core/spdy_header_block.cc
new file mode 100644
index 00000000000..dcf84b95b7f
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/spdy/core/spdy_header_block.cc
@@ -0,0 +1,401 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/spdy/core/spdy_header_block.h"
+
+#include <string.h>
+
+#include <algorithm>
+#include <utility>
+
+#include "base/logging.h"
+#include "base/macros.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_estimate_memory_usage.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_ptr_util.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_string_utils.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_unsafe_arena.h"
+
+namespace spdy {
+namespace {
+
+// By default, linked_hash_map's internal map allocates space for 100 map
+// buckets on construction, which is larger than necessary. Standard library
+// unordered map implementations use a list of prime numbers to set the bucket
+// count for a particular capacity. |kInitialMapBuckets| is chosen to reduce
+// memory usage for small header blocks, at the cost of having to rehash for
+// large header blocks.
+const size_t kInitialMapBuckets = 11;
+
+// SpdyHeaderBlock::Storage allocates blocks of this size by default.
+const size_t kDefaultStorageBlockSize = 2048;
+
+const char kCookieKey[] = "cookie";
+const char kNullSeparator = 0;
+
+SpdyStringPiece SeparatorForKey(SpdyStringPiece key) {
+ if (key == kCookieKey) {
+ static SpdyStringPiece cookie_separator = "; ";
+ return cookie_separator;
+ } else {
+ return SpdyStringPiece(&kNullSeparator, 1);
+ }
+}
+
+} // namespace
+
+// This class provides a backing store for SpdyStringPieces. It previously used
+// custom allocation logic, but now uses an UnsafeArena instead. It has the
+// property that SpdyStringPieces that refer to data in Storage are never
+// invalidated until the Storage is deleted or Clear() is called.
+//
+// Write operations always append to the last block. If there is not enough
+// space to perform the write, a new block is allocated, and any unused space
+// is wasted.
+class SpdyHeaderBlock::Storage {
+ public:
+ Storage() : arena_(kDefaultStorageBlockSize) {}
+ Storage(const Storage&) = delete;
+ Storage& operator=(const Storage&) = delete;
+
+ SpdyStringPiece Write(const SpdyStringPiece s) {
+ return SpdyStringPiece(arena_.Memdup(s.data(), s.size()), s.size());
+ }
+
+ // If |s| points to the most recent allocation from arena_, the arena will
+ // reclaim the memory. Otherwise, this method is a no-op.
+ void Rewind(const SpdyStringPiece s) {
+ arena_.Free(const_cast<char*>(s.data()), s.size());
+ }
+
+ void Clear() { arena_.Reset(); }
+
+ // Given a list of fragments and a separator, writes the fragments joined by
+ // the separator to a contiguous region of memory. Returns a SpdyStringPiece
+ // pointing to the region of memory.
+ SpdyStringPiece WriteFragments(const std::vector<SpdyStringPiece>& fragments,
+ SpdyStringPiece separator) {
+ if (fragments.empty()) {
+ return SpdyStringPiece();
+ }
+ size_t total_size = separator.size() * (fragments.size() - 1);
+ for (const auto fragment : fragments) {
+ total_size += fragment.size();
+ }
+ char* dst = arena_.Alloc(total_size);
+ size_t written = Join(dst, fragments, separator);
+ DCHECK_EQ(written, total_size);
+ return SpdyStringPiece(dst, total_size);
+ }
+
+ size_t bytes_allocated() const { return arena_.status().bytes_allocated(); }
+
+ // TODO(xunjieli): https://crbug.com/669108. Merge this with bytes_allocated()
+ size_t EstimateMemoryUsage() const {
+ return arena_.status().bytes_allocated();
+ }
+
+ private:
+ SpdyUnsafeArena arena_;
+};
+
+SpdyHeaderBlock::HeaderValue::HeaderValue(Storage* storage,
+ SpdyStringPiece key,
+ SpdyStringPiece initial_value)
+ : storage_(storage),
+ fragments_({initial_value}),
+ pair_({key, {}}),
+ size_(initial_value.size()),
+ separator_size_(SeparatorForKey(key).size()) {}
+
+SpdyHeaderBlock::HeaderValue::HeaderValue(HeaderValue&& other)
+ : storage_(other.storage_),
+ fragments_(std::move(other.fragments_)),
+ pair_(std::move(other.pair_)),
+ size_(other.size_),
+ separator_size_(other.separator_size_) {}
+
+SpdyHeaderBlock::HeaderValue& SpdyHeaderBlock::HeaderValue::operator=(
+ HeaderValue&& other) {
+ storage_ = other.storage_;
+ fragments_ = std::move(other.fragments_);
+ pair_ = std::move(other.pair_);
+ size_ = other.size_;
+ separator_size_ = other.separator_size_;
+ return *this;
+}
+
+SpdyHeaderBlock::HeaderValue::~HeaderValue() = default;
+
+SpdyStringPiece SpdyHeaderBlock::HeaderValue::ConsolidatedValue() const {
+ if (fragments_.empty()) {
+ return SpdyStringPiece();
+ }
+ if (fragments_.size() > 1) {
+ fragments_ = {
+ storage_->WriteFragments(fragments_, SeparatorForKey(pair_.first))};
+ }
+ return fragments_[0];
+}
+
+void SpdyHeaderBlock::HeaderValue::Append(SpdyStringPiece fragment) {
+ size_ += (fragment.size() + separator_size_);
+ fragments_.push_back(fragment);
+}
+
+const std::pair<SpdyStringPiece, SpdyStringPiece>&
+SpdyHeaderBlock::HeaderValue::as_pair() const {
+ pair_.second = ConsolidatedValue();
+ return pair_;
+}
+
+SpdyHeaderBlock::iterator::iterator(MapType::const_iterator it) : it_(it) {}
+
+SpdyHeaderBlock::iterator::iterator(const iterator& other) = default;
+
+SpdyHeaderBlock::iterator::~iterator() = default;
+
+SpdyHeaderBlock::ValueProxy::ValueProxy(
+ SpdyHeaderBlock::MapType* block,
+ SpdyHeaderBlock::Storage* storage,
+ SpdyHeaderBlock::MapType::iterator lookup_result,
+ const SpdyStringPiece key,
+ size_t* spdy_header_block_value_size)
+ : block_(block),
+ storage_(storage),
+ lookup_result_(lookup_result),
+ key_(key),
+ spdy_header_block_value_size_(spdy_header_block_value_size),
+ valid_(true) {}
+
+SpdyHeaderBlock::ValueProxy::ValueProxy(ValueProxy&& other)
+ : block_(other.block_),
+ storage_(other.storage_),
+ lookup_result_(other.lookup_result_),
+ key_(other.key_),
+ spdy_header_block_value_size_(other.spdy_header_block_value_size_),
+ valid_(true) {
+ other.valid_ = false;
+}
+
+SpdyHeaderBlock::ValueProxy& SpdyHeaderBlock::ValueProxy::operator=(
+ SpdyHeaderBlock::ValueProxy&& other) {
+ block_ = other.block_;
+ storage_ = other.storage_;
+ lookup_result_ = other.lookup_result_;
+ key_ = other.key_;
+ valid_ = true;
+ other.valid_ = false;
+ spdy_header_block_value_size_ = other.spdy_header_block_value_size_;
+ return *this;
+}
+
+SpdyHeaderBlock::ValueProxy::~ValueProxy() {
+ // If the ValueProxy is destroyed while lookup_result_ == block_->end(),
+ // the assignment operator was never used, and the block's Storage can
+ // reclaim the memory used by the key. This makes lookup-only access to
+ // SpdyHeaderBlock through operator[] memory-neutral.
+ if (valid_ && lookup_result_ == block_->end()) {
+ storage_->Rewind(key_);
+ }
+}
+
+SpdyHeaderBlock::ValueProxy& SpdyHeaderBlock::ValueProxy::operator=(
+ const SpdyStringPiece value) {
+ *spdy_header_block_value_size_ += value.size();
+ if (lookup_result_ == block_->end()) {
+ DVLOG(1) << "Inserting: (" << key_ << ", " << value << ")";
+ lookup_result_ =
+ block_
+ ->emplace(std::make_pair(
+ key_, HeaderValue(storage_, key_, storage_->Write(value))))
+ .first;
+ } else {
+ DVLOG(1) << "Updating key: " << key_ << " with value: " << value;
+ *spdy_header_block_value_size_ -= lookup_result_->second.SizeEstimate();
+ lookup_result_->second =
+ HeaderValue(storage_, key_, storage_->Write(value));
+ }
+ return *this;
+}
+
+SpdyString SpdyHeaderBlock::ValueProxy::as_string() const {
+ if (lookup_result_ == block_->end()) {
+ return "";
+ } else {
+ return SpdyString(lookup_result_->second.value());
+ }
+}
+
+SpdyHeaderBlock::SpdyHeaderBlock() : block_(kInitialMapBuckets) {}
+
+SpdyHeaderBlock::SpdyHeaderBlock(SpdyHeaderBlock&& other)
+ : block_(kInitialMapBuckets) {
+ block_.swap(other.block_);
+ storage_.swap(other.storage_);
+ key_size_ = other.key_size_;
+ value_size_ = other.value_size_;
+}
+
+SpdyHeaderBlock::~SpdyHeaderBlock() = default;
+
+SpdyHeaderBlock& SpdyHeaderBlock::operator=(SpdyHeaderBlock&& other) {
+ block_.swap(other.block_);
+ storage_.swap(other.storage_);
+ key_size_ = other.key_size_;
+ value_size_ = other.value_size_;
+ return *this;
+}
+
+SpdyHeaderBlock SpdyHeaderBlock::Clone() const {
+ SpdyHeaderBlock copy;
+ for (const auto& p : *this) {
+ copy.AppendHeader(p.first, p.second);
+ }
+ return copy;
+}
+
+bool SpdyHeaderBlock::operator==(const SpdyHeaderBlock& other) const {
+ return size() == other.size() && std::equal(begin(), end(), other.begin());
+}
+
+bool SpdyHeaderBlock::operator!=(const SpdyHeaderBlock& other) const {
+ return !(operator==(other));
+}
+
+SpdyString SpdyHeaderBlock::DebugString() const {
+ if (empty()) {
+ return "{}";
+ }
+
+ SpdyString output = "\n{\n";
+ for (auto it = begin(); it != end(); ++it) {
+ SpdyStrAppend(&output, " ", it->first, " ", it->second, "\n");
+ }
+ SpdyStrAppend(&output, "}\n");
+ return output;
+}
+
+void SpdyHeaderBlock::erase(SpdyStringPiece key) {
+ auto iter = block_.find(key);
+ if (iter != block_.end()) {
+ DVLOG(1) << "Erasing header with name: " << key;
+ key_size_ -= key.size();
+ value_size_ -= iter->second.SizeEstimate();
+ block_.erase(iter);
+ }
+}
+
+void SpdyHeaderBlock::clear() {
+ key_size_ = 0;
+ value_size_ = 0;
+ block_.clear();
+ storage_.reset();
+}
+
+void SpdyHeaderBlock::insert(const SpdyHeaderBlock::value_type& value) {
+ // TODO(birenroy): Write new value in place of old value, if it fits.
+ value_size_ += value.second.size();
+
+ auto iter = block_.find(value.first);
+ if (iter == block_.end()) {
+ DVLOG(1) << "Inserting: (" << value.first << ", " << value.second << ")";
+ AppendHeader(value.first, value.second);
+ } else {
+ DVLOG(1) << "Updating key: " << iter->first
+ << " with value: " << value.second;
+ value_size_ -= iter->second.SizeEstimate();
+ auto* storage = GetStorage();
+ iter->second =
+ HeaderValue(storage, iter->first, storage->Write(value.second));
+ }
+}
+
+SpdyHeaderBlock::ValueProxy SpdyHeaderBlock::operator[](
+ const SpdyStringPiece key) {
+ DVLOG(2) << "Operator[] saw key: " << key;
+ SpdyStringPiece out_key;
+ auto iter = block_.find(key);
+ if (iter == block_.end()) {
+ // We write the key first, to assure that the ValueProxy has a
+ // reference to a valid SpdyStringPiece in its operator=.
+ out_key = WriteKey(key);
+ DVLOG(2) << "Key written as: " << std::hex
+ << static_cast<const void*>(key.data()) << ", " << std::dec
+ << key.size();
+ } else {
+ out_key = iter->first;
+ }
+ return ValueProxy(&block_, GetStorage(), iter, out_key, &value_size_);
+}
+
+void SpdyHeaderBlock::AppendValueOrAddHeader(const SpdyStringPiece key,
+ const SpdyStringPiece value) {
+ value_size_ += value.size();
+
+ auto iter = block_.find(key);
+ if (iter == block_.end()) {
+ DVLOG(1) << "Inserting: (" << key << ", " << value << ")";
+
+ AppendHeader(key, value);
+ return;
+ }
+ DVLOG(1) << "Updating key: " << iter->first << "; appending value: " << value;
+ value_size_ += SeparatorForKey(key).size();
+ iter->second.Append(GetStorage()->Write(value));
+}
+
+size_t SpdyHeaderBlock::EstimateMemoryUsage() const {
+ // TODO(xunjieli): https://crbug.com/669108. Also include |block_| when EMU()
+ // supports linked_hash_map.
+ return SpdyEstimateMemoryUsage(storage_);
+}
+
+void SpdyHeaderBlock::AppendHeader(const SpdyStringPiece key,
+ const SpdyStringPiece value) {
+ auto backed_key = WriteKey(key);
+ auto* storage = GetStorage();
+ block_.emplace(std::make_pair(
+ backed_key, HeaderValue(storage, backed_key, storage->Write(value))));
+}
+
+SpdyHeaderBlock::Storage* SpdyHeaderBlock::GetStorage() {
+ if (storage_ == nullptr) {
+ storage_ = SpdyMakeUnique<Storage>();
+ }
+ return storage_.get();
+}
+
+SpdyStringPiece SpdyHeaderBlock::WriteKey(const SpdyStringPiece key) {
+ key_size_ += key.size();
+ return GetStorage()->Write(key);
+}
+
+size_t SpdyHeaderBlock::bytes_allocated() const {
+ if (storage_ == nullptr) {
+ return 0;
+ } else {
+ return storage_->bytes_allocated();
+ }
+}
+
+size_t Join(char* dst,
+ const std::vector<SpdyStringPiece>& fragments,
+ SpdyStringPiece separator) {
+ if (fragments.empty()) {
+ return 0;
+ }
+ auto* original_dst = dst;
+ auto it = fragments.begin();
+ memcpy(dst, it->data(), it->size());
+ dst += it->size();
+ for (++it; it != fragments.end(); ++it) {
+ memcpy(dst, separator.data(), separator.size());
+ dst += separator.size();
+ memcpy(dst, it->data(), it->size());
+ dst += it->size();
+ }
+ return dst - original_dst;
+}
+
+} // namespace spdy
diff --git a/chromium/net/third_party/quiche/src/spdy/core/spdy_header_block.h b/chromium/net/third_party/quiche/src/spdy/core/spdy_header_block.h
new file mode 100644
index 00000000000..f453246486d
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/spdy/core/spdy_header_block.h
@@ -0,0 +1,254 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_SPDY_CORE_SPDY_HEADER_BLOCK_H_
+#define QUICHE_SPDY_CORE_SPDY_HEADER_BLOCK_H_
+
+#include <stddef.h>
+
+#include <list>
+#include <memory>
+#include <utility>
+#include <vector>
+
+#include "base/macros.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_containers.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_export.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_macros.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_string.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_string_piece.h"
+
+namespace spdy {
+
+namespace test {
+class SpdyHeaderBlockPeer;
+class ValueProxyPeer;
+} // namespace test
+
+// This class provides a key-value map that can be used to store SPDY header
+// names and values. This data structure preserves insertion order.
+//
+// Under the hood, this data structure uses large, contiguous blocks of memory
+// to store names and values. Lookups may be performed with SpdyStringPiece
+// keys, and values are returned as SpdyStringPieces (via ValueProxy, below).
+// Value SpdyStringPieces are valid as long as the SpdyHeaderBlock exists;
+// allocated memory is never freed until SpdyHeaderBlock's destruction.
+//
+// This implementation does not make much of an effort to minimize wasted space.
+// It's expected that keys are rarely deleted from a SpdyHeaderBlock.
+class SPDY_EXPORT_PRIVATE SpdyHeaderBlock {
+ private:
+ class Storage;
+
+ // Stores a list of value fragments that can be joined later with a
+ // key-dependent separator.
+ class SPDY_EXPORT_PRIVATE HeaderValue {
+ public:
+ HeaderValue(Storage* storage,
+ SpdyStringPiece key,
+ SpdyStringPiece initial_value);
+
+ // Moves are allowed.
+ HeaderValue(HeaderValue&& other);
+ HeaderValue& operator=(HeaderValue&& other);
+
+ // Copies are not.
+ HeaderValue(const HeaderValue& other) = delete;
+ HeaderValue& operator=(const HeaderValue& other) = delete;
+
+ ~HeaderValue();
+
+ // Consumes at most |fragment.size()| bytes of memory.
+ void Append(SpdyStringPiece fragment);
+
+ SpdyStringPiece value() const { return as_pair().second; }
+ const std::pair<SpdyStringPiece, SpdyStringPiece>& as_pair() const;
+
+ // Size estimate including separators. Used when keys are erased from
+ // SpdyHeaderBlock.
+ size_t SizeEstimate() const { return size_; }
+
+ private:
+ // May allocate a large contiguous region of memory to hold the concatenated
+ // fragments and separators.
+ SpdyStringPiece ConsolidatedValue() const;
+
+ mutable Storage* storage_;
+ mutable std::vector<SpdyStringPiece> fragments_;
+ // The first element is the key; the second is the consolidated value.
+ mutable std::pair<SpdyStringPiece, SpdyStringPiece> pair_;
+ size_t size_ = 0;
+ size_t separator_size_ = 0;
+ };
+
+ typedef SpdyLinkedHashMap<SpdyStringPiece, HeaderValue, SpdyStringPieceHash>
+ MapType;
+
+ public:
+ typedef std::pair<SpdyStringPiece, SpdyStringPiece> value_type;
+
+ // Provides iteration over a sequence of std::pair<SpdyStringPiece,
+ // SpdyStringPiece>, even though the underlying MapType::value_type is
+ // different. Dereferencing the iterator will result in memory allocation for
+ // multi-value headers.
+ class SPDY_EXPORT_PRIVATE iterator {
+ public:
+ // The following type definitions fulfill the requirements for iterator
+ // implementations.
+ typedef std::pair<SpdyStringPiece, SpdyStringPiece> value_type;
+ typedef value_type& reference;
+ typedef value_type* pointer;
+ typedef std::forward_iterator_tag iterator_category;
+ typedef MapType::iterator::difference_type difference_type;
+
+ // In practice, this iterator only offers access to const value_type.
+ typedef const value_type& const_reference;
+ typedef const value_type* const_pointer;
+
+ explicit iterator(MapType::const_iterator it);
+ iterator(const iterator& other);
+ ~iterator();
+
+ // This will result in memory allocation if the value consists of multiple
+ // fragments.
+ const_reference operator*() const { return it_->second.as_pair(); }
+
+ const_pointer operator->() const { return &(this->operator*()); }
+ bool operator==(const iterator& it) const { return it_ == it.it_; }
+ bool operator!=(const iterator& it) const { return !(*this == it); }
+
+ iterator& operator++() {
+ it_++;
+ return *this;
+ }
+
+ iterator operator++(int) {
+ auto ret = *this;
+ this->operator++();
+ return ret;
+ }
+
+ private:
+ MapType::const_iterator it_;
+ };
+ typedef iterator const_iterator;
+
+ class ValueProxy;
+
+ SpdyHeaderBlock();
+ SpdyHeaderBlock(const SpdyHeaderBlock& other) = delete;
+ SpdyHeaderBlock(SpdyHeaderBlock&& other);
+ ~SpdyHeaderBlock();
+
+ SpdyHeaderBlock& operator=(const SpdyHeaderBlock& other) = delete;
+ SpdyHeaderBlock& operator=(SpdyHeaderBlock&& other);
+ SpdyHeaderBlock Clone() const;
+
+ bool operator==(const SpdyHeaderBlock& other) const;
+ bool operator!=(const SpdyHeaderBlock& other) const;
+
+ // Provides a human readable multi-line representation of the stored header
+ // keys and values.
+ SpdyString DebugString() const;
+
+ iterator begin() { return iterator(block_.begin()); }
+ iterator end() { return iterator(block_.end()); }
+ const_iterator begin() const { return const_iterator(block_.begin()); }
+ const_iterator end() const { return const_iterator(block_.end()); }
+ bool empty() const { return block_.empty(); }
+ size_t size() const { return block_.size(); }
+ iterator find(SpdyStringPiece key) { return iterator(block_.find(key)); }
+ const_iterator find(SpdyStringPiece key) const {
+ return const_iterator(block_.find(key));
+ }
+ void erase(SpdyStringPiece key);
+
+ // Clears both our MapType member and the memory used to hold headers.
+ void clear();
+
+ // The next few methods copy data into our backing storage.
+
+ // If key already exists in the block, replaces the value of that key. Else
+ // adds a new header to the end of the block.
+ void insert(const value_type& value);
+
+ // If a header with the key is already present, then append the value to the
+ // existing header value, NUL ("\0") separated unless the key is cookie, in
+ // which case the separator is "; ".
+ // If there is no such key, a new header with the key and value is added.
+ void AppendValueOrAddHeader(const SpdyStringPiece key,
+ const SpdyStringPiece value);
+
+ // Allows either lookup or mutation of the value associated with a key.
+ ValueProxy operator[](const SpdyStringPiece key) SPDY_MUST_USE_RESULT;
+
+ // This object provides automatic conversions that allow SpdyHeaderBlock to be
+ // nearly a drop-in replacement for SpdyLinkedHashMap<SpdyString, SpdyString>.
+ // It reads data from or writes data to a SpdyHeaderBlock::Storage.
+ class SPDY_EXPORT_PRIVATE ValueProxy {
+ public:
+ ~ValueProxy();
+
+ // Moves are allowed.
+ ValueProxy(ValueProxy&& other);
+ ValueProxy& operator=(ValueProxy&& other);
+
+ // Copies are not.
+ ValueProxy(const ValueProxy& other) = delete;
+ ValueProxy& operator=(const ValueProxy& other) = delete;
+
+ // Assignment modifies the underlying SpdyHeaderBlock.
+ ValueProxy& operator=(const SpdyStringPiece other);
+
+ SpdyString as_string() const;
+
+ private:
+ friend class SpdyHeaderBlock;
+ friend class test::ValueProxyPeer;
+
+ ValueProxy(SpdyHeaderBlock::MapType* block,
+ SpdyHeaderBlock::Storage* storage,
+ SpdyHeaderBlock::MapType::iterator lookup_result,
+ const SpdyStringPiece key,
+ size_t* spdy_header_block_value_size);
+
+ SpdyHeaderBlock::MapType* block_;
+ SpdyHeaderBlock::Storage* storage_;
+ SpdyHeaderBlock::MapType::iterator lookup_result_;
+ SpdyStringPiece key_;
+ size_t* spdy_header_block_value_size_;
+ bool valid_;
+ };
+
+ // Returns the estimate of dynamically allocated memory in bytes.
+ size_t EstimateMemoryUsage() const;
+
+ size_t TotalBytesUsed() const { return key_size_ + value_size_; }
+
+ private:
+ friend class test::SpdyHeaderBlockPeer;
+
+ void AppendHeader(const SpdyStringPiece key, const SpdyStringPiece value);
+ Storage* GetStorage();
+ SpdyStringPiece WriteKey(const SpdyStringPiece key);
+ size_t bytes_allocated() const;
+
+ // SpdyStringPieces held by |block_| point to memory owned by |*storage_|.
+ // |storage_| might be nullptr as long as |block_| is empty.
+ MapType block_;
+ std::unique_ptr<Storage> storage_;
+
+ size_t key_size_ = 0;
+ size_t value_size_ = 0;
+};
+
+// Writes |fragments| to |dst|, joined by |separator|. |dst| must be large
+// enough to hold the result. Returns the number of bytes written.
+SPDY_EXPORT_PRIVATE size_t Join(char* dst,
+ const std::vector<SpdyStringPiece>& fragments,
+ SpdyStringPiece separator);
+
+} // namespace spdy
+
+#endif // QUICHE_SPDY_CORE_SPDY_HEADER_BLOCK_H_
diff --git a/chromium/net/third_party/quiche/src/spdy/core/spdy_header_block_test.cc b/chromium/net/third_party/quiche/src/spdy/core/spdy_header_block_test.cc
new file mode 100644
index 00000000000..3511a213918
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/spdy/core/spdy_header_block_test.cc
@@ -0,0 +1,252 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/spdy/core/spdy_header_block.h"
+
+#include <memory>
+#include <utility>
+
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_test_utils.h"
+
+using ::testing::ElementsAre;
+
+namespace spdy {
+namespace test {
+
+class ValueProxyPeer {
+ public:
+ static SpdyStringPiece key(SpdyHeaderBlock::ValueProxy* p) { return p->key_; }
+};
+
+std::pair<SpdyStringPiece, SpdyStringPiece> Pair(SpdyStringPiece k,
+ SpdyStringPiece v) {
+ return std::make_pair(k, v);
+}
+
+// This test verifies that SpdyHeaderBlock behaves correctly when empty.
+TEST(SpdyHeaderBlockTest, EmptyBlock) {
+ SpdyHeaderBlock block;
+ EXPECT_TRUE(block.empty());
+ EXPECT_EQ(0u, block.size());
+ EXPECT_EQ(block.end(), block.find("foo"));
+ EXPECT_TRUE(block.end() == block.begin());
+
+ // Should have no effect.
+ block.erase("bar");
+}
+
+TEST(SpdyHeaderBlockTest, KeyMemoryReclaimedOnLookup) {
+ SpdyHeaderBlock block;
+ SpdyStringPiece copied_key1;
+ {
+ auto proxy1 = block["some key name"];
+ copied_key1 = ValueProxyPeer::key(&proxy1);
+ }
+ SpdyStringPiece copied_key2;
+ {
+ auto proxy2 = block["some other key name"];
+ copied_key2 = ValueProxyPeer::key(&proxy2);
+ }
+ // Because proxy1 was never used to modify the block, the memory used for the
+ // key could be reclaimed and used for the second call to operator[].
+ // Therefore, we expect the pointers of the two SpdyStringPieces to be equal.
+ EXPECT_EQ(copied_key1.data(), copied_key2.data());
+
+ {
+ auto proxy1 = block["some key name"];
+ block["some other key name"] = "some value";
+ }
+ // Nothing should blow up when proxy1 is destructed, and we should be able to
+ // modify and access the SpdyHeaderBlock.
+ block["key"] = "value";
+ EXPECT_EQ("value", block["key"]);
+ EXPECT_EQ("some value", block["some other key name"]);
+ EXPECT_TRUE(block.find("some key name") == block.end());
+}
+
+// This test verifies that headers can be set in a variety of ways.
+TEST(SpdyHeaderBlockTest, AddHeaders) {
+ SpdyHeaderBlock block;
+ block["foo"] = SpdyString(300, 'x');
+ block["bar"] = "baz";
+ block["qux"] = "qux1";
+ block["qux"] = "qux2";
+ block.insert(std::make_pair("key", "value"));
+
+ EXPECT_EQ(Pair("foo", SpdyString(300, 'x')), *block.find("foo"));
+ EXPECT_EQ("baz", block["bar"]);
+ SpdyString qux("qux");
+ EXPECT_EQ("qux2", block[qux]);
+ ASSERT_NE(block.end(), block.find("key"));
+ EXPECT_EQ(Pair("key", "value"), *block.find("key"));
+
+ block.erase("key");
+ EXPECT_EQ(block.end(), block.find("key"));
+}
+
+// This test verifies that SpdyHeaderBlock can be copied using Clone().
+TEST(SpdyHeaderBlockTest, CopyBlocks) {
+ SpdyHeaderBlock block1;
+ block1["foo"] = SpdyString(300, 'x');
+ block1["bar"] = "baz";
+ block1.insert(std::make_pair("qux", "qux1"));
+
+ SpdyHeaderBlock block2 = block1.Clone();
+ SpdyHeaderBlock block3(block1.Clone());
+
+ EXPECT_EQ(block1, block2);
+ EXPECT_EQ(block1, block3);
+}
+
+TEST(SpdyHeaderBlockTest, Equality) {
+ // Test equality and inequality operators.
+ SpdyHeaderBlock block1;
+ block1["foo"] = "bar";
+
+ SpdyHeaderBlock block2;
+ block2["foo"] = "bar";
+
+ SpdyHeaderBlock block3;
+ block3["baz"] = "qux";
+
+ EXPECT_EQ(block1, block2);
+ EXPECT_NE(block1, block3);
+
+ block2["baz"] = "qux";
+ EXPECT_NE(block1, block2);
+}
+
+// Test that certain methods do not crash on moved-from instances.
+TEST(SpdyHeaderBlockTest, MovedFromIsValid) {
+ SpdyHeaderBlock block1;
+ block1["foo"] = "bar";
+
+ SpdyHeaderBlock block2(std::move(block1));
+ EXPECT_THAT(block2, ElementsAre(Pair("foo", "bar")));
+
+ block1["baz"] = "qux"; // NOLINT testing post-move behavior
+
+ SpdyHeaderBlock block3(std::move(block1));
+
+ block1["foo"] = "bar"; // NOLINT testing post-move behavior
+
+ SpdyHeaderBlock block4(std::move(block1));
+
+ block1.clear(); // NOLINT testing post-move behavior
+ EXPECT_TRUE(block1.empty());
+
+ block1["foo"] = "bar";
+ EXPECT_THAT(block1, ElementsAre(Pair("foo", "bar")));
+}
+
+// This test verifies that headers can be appended to no matter how they were
+// added originally.
+TEST(SpdyHeaderBlockTest, AppendHeaders) {
+ SpdyHeaderBlock block;
+ block["foo"] = "foo";
+ block.AppendValueOrAddHeader("foo", "bar");
+ EXPECT_EQ(Pair("foo", SpdyString("foo\0bar", 7)), *block.find("foo"));
+
+ block.insert(std::make_pair("foo", "baz"));
+ EXPECT_EQ("baz", block["foo"]);
+ EXPECT_EQ(Pair("foo", "baz"), *block.find("foo"));
+
+ // Try all four methods of adding an entry.
+ block["cookie"] = "key1=value1";
+ block.AppendValueOrAddHeader("h1", "h1v1");
+ block.insert(std::make_pair("h2", "h2v1"));
+
+ block.AppendValueOrAddHeader("h3", "h3v2");
+ block.AppendValueOrAddHeader("h2", "h2v2");
+ block.AppendValueOrAddHeader("h1", "h1v2");
+ block.AppendValueOrAddHeader("cookie", "key2=value2");
+
+ block.AppendValueOrAddHeader("cookie", "key3=value3");
+ block.AppendValueOrAddHeader("h1", "h1v3");
+ block.AppendValueOrAddHeader("h2", "h2v3");
+ block.AppendValueOrAddHeader("h3", "h3v3");
+ block.AppendValueOrAddHeader("h4", "singleton");
+
+ EXPECT_EQ("key1=value1; key2=value2; key3=value3", block["cookie"]);
+ EXPECT_EQ("baz", block["foo"]);
+ EXPECT_EQ(SpdyString("h1v1\0h1v2\0h1v3", 14), block["h1"]);
+ EXPECT_EQ(SpdyString("h2v1\0h2v2\0h2v3", 14), block["h2"]);
+ EXPECT_EQ(SpdyString("h3v2\0h3v3", 9), block["h3"]);
+ EXPECT_EQ("singleton", block["h4"]);
+}
+
+TEST(JoinTest, JoinEmpty) {
+ std::vector<SpdyStringPiece> empty;
+ SpdyStringPiece separator = ", ";
+ char buf[10] = "";
+ size_t written = Join(buf, empty, separator);
+ EXPECT_EQ(0u, written);
+}
+
+TEST(JoinTest, JoinOne) {
+ std::vector<SpdyStringPiece> v = {"one"};
+ SpdyStringPiece separator = ", ";
+ char buf[15];
+ size_t written = Join(buf, v, separator);
+ EXPECT_EQ(3u, written);
+ EXPECT_EQ("one", SpdyStringPiece(buf, written));
+}
+
+TEST(JoinTest, JoinMultiple) {
+ std::vector<SpdyStringPiece> v = {"one", "two", "three"};
+ SpdyStringPiece separator = ", ";
+ char buf[15];
+ size_t written = Join(buf, v, separator);
+ EXPECT_EQ(15u, written);
+ EXPECT_EQ("one, two, three", SpdyStringPiece(buf, written));
+}
+
+namespace {
+size_t SpdyHeaderBlockSize(const SpdyHeaderBlock& block) {
+ size_t size = 0;
+ for (const auto& pair : block) {
+ size += pair.first.size() + pair.second.size();
+ }
+ return size;
+}
+} // namespace
+
+// Tests SpdyHeaderBlock SizeEstimate().
+TEST(SpdyHeaderBlockTest, TotalBytesUsed) {
+ SpdyHeaderBlock block;
+ const size_t value_size = 300;
+ block["foo"] = SpdyString(value_size, 'x');
+ EXPECT_EQ(block.TotalBytesUsed(), SpdyHeaderBlockSize(block));
+ block.insert(std::make_pair("key", SpdyString(value_size, 'x')));
+ EXPECT_EQ(block.TotalBytesUsed(), SpdyHeaderBlockSize(block));
+ block.AppendValueOrAddHeader("abc", SpdyString(value_size, 'x'));
+ EXPECT_EQ(block.TotalBytesUsed(), SpdyHeaderBlockSize(block));
+
+ // Replace value for existing key.
+ block["foo"] = SpdyString(value_size, 'x');
+ EXPECT_EQ(block.TotalBytesUsed(), SpdyHeaderBlockSize(block));
+ block.insert(std::make_pair("key", SpdyString(value_size, 'x')));
+ EXPECT_EQ(block.TotalBytesUsed(), SpdyHeaderBlockSize(block));
+ // Add value for existing key.
+ block.AppendValueOrAddHeader("abc", SpdyString(value_size, 'x'));
+ EXPECT_EQ(block.TotalBytesUsed(), SpdyHeaderBlockSize(block));
+
+ // Copies/clones SpdyHeaderBlock.
+ size_t block_size = block.TotalBytesUsed();
+ SpdyHeaderBlock block_copy = std::move(block);
+ EXPECT_EQ(block_size, block_copy.TotalBytesUsed());
+
+ // Erases key.
+ block_copy.erase("foo");
+ EXPECT_EQ(block_copy.TotalBytesUsed(), SpdyHeaderBlockSize(block_copy));
+ block_copy.erase("key");
+ EXPECT_EQ(block_copy.TotalBytesUsed(), SpdyHeaderBlockSize(block_copy));
+ block_copy.erase("abc");
+ EXPECT_EQ(block_copy.TotalBytesUsed(), SpdyHeaderBlockSize(block_copy));
+}
+
+} // namespace test
+} // namespace spdy
diff --git a/chromium/net/third_party/quiche/src/spdy/core/spdy_headers_handler_interface.h b/chromium/net/third_party/quiche/src/spdy/core/spdy_headers_handler_interface.h
new file mode 100644
index 00000000000..ce6c1b64d7d
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/spdy/core/spdy_headers_handler_interface.h
@@ -0,0 +1,39 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_SPDY_CORE_SPDY_HEADERS_HANDLER_INTERFACE_H_
+#define QUICHE_SPDY_CORE_SPDY_HEADERS_HANDLER_INTERFACE_H_
+
+#include <stddef.h>
+
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_export.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_string_piece.h"
+
+namespace spdy {
+
+// This interface defines how an object that accepts header data should behave.
+// It is used by both SpdyHeadersBlockParser and HpackDecoder.
+class SPDY_EXPORT_PRIVATE SpdyHeadersHandlerInterface {
+ public:
+ virtual ~SpdyHeadersHandlerInterface() {}
+
+ // A callback method which notifies when the parser starts handling a new
+ // header block. Will only be called once per block, even if it extends into
+ // CONTINUATION frames.
+ virtual void OnHeaderBlockStart() = 0;
+
+ // A callback method which notifies on a header key value pair. Multiple
+ // values for a given key will be emitted as multiple calls to OnHeader.
+ virtual void OnHeader(SpdyStringPiece key, SpdyStringPiece value) = 0;
+
+ // A callback method which notifies when the parser finishes handling a
+ // header block (i.e. the containing frame has the END_HEADERS flag set).
+ // Also indicates the total number of bytes in this block.
+ virtual void OnHeaderBlockEnd(size_t uncompressed_header_bytes,
+ size_t compressed_header_bytes) = 0;
+};
+
+} // namespace spdy
+
+#endif // QUICHE_SPDY_CORE_SPDY_HEADERS_HANDLER_INTERFACE_H_
diff --git a/chromium/net/third_party/quiche/src/spdy/core/spdy_no_op_visitor.cc b/chromium/net/third_party/quiche/src/spdy/core/spdy_no_op_visitor.cc
new file mode 100644
index 00000000000..5dcc15c70fa
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/spdy/core/spdy_no_op_visitor.cc
@@ -0,0 +1,29 @@
+// Copyright (c) 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/spdy/core/spdy_no_op_visitor.h"
+
+#include <type_traits>
+
+namespace spdy {
+namespace test {
+
+SpdyNoOpVisitor::SpdyNoOpVisitor() {
+ static_assert(std::is_abstract<SpdyNoOpVisitor>::value == false,
+ "Need to update SpdyNoOpVisitor.");
+}
+SpdyNoOpVisitor::~SpdyNoOpVisitor() = default;
+
+SpdyHeadersHandlerInterface* SpdyNoOpVisitor::OnHeaderFrameStart(
+ SpdyStreamId stream_id) {
+ return this;
+}
+
+bool SpdyNoOpVisitor::OnUnknownFrame(SpdyStreamId stream_id,
+ uint8_t frame_type) {
+ return true;
+}
+
+} // namespace test
+} // namespace spdy
diff --git a/chromium/net/third_party/quiche/src/spdy/core/spdy_no_op_visitor.h b/chromium/net/third_party/quiche/src/spdy/core/spdy_no_op_visitor.h
new file mode 100644
index 00000000000..80e1535514c
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/spdy/core/spdy_no_op_visitor.h
@@ -0,0 +1,89 @@
+// Copyright (c) 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// SpdyNoOpVisitor implements several of the visitor and handler interfaces
+// to make it easier to write tests that need to provide instances. Other
+// interfaces can be added as needed.
+
+#ifndef QUICHE_SPDY_CORE_SPDY_NO_OP_VISITOR_H_
+#define QUICHE_SPDY_CORE_SPDY_NO_OP_VISITOR_H_
+
+#include <cstdint>
+
+#include "net/third_party/quiche/src/spdy/core/http2_frame_decoder_adapter.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_protocol.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_string_piece.h"
+
+namespace spdy {
+namespace test {
+
+class SpdyNoOpVisitor : public SpdyFramerVisitorInterface,
+ public SpdyFramerDebugVisitorInterface,
+ public SpdyHeadersHandlerInterface {
+ public:
+ SpdyNoOpVisitor();
+ ~SpdyNoOpVisitor() override;
+
+ // SpdyFramerVisitorInterface methods:
+ void OnError(http2::Http2DecoderAdapter::SpdyFramerError error) override {}
+ SpdyHeadersHandlerInterface* OnHeaderFrameStart(
+ SpdyStreamId stream_id) override;
+ void OnHeaderFrameEnd(SpdyStreamId stream_id) override {}
+ void OnDataFrameHeader(SpdyStreamId stream_id,
+ size_t length,
+ bool fin) override {}
+ void OnStreamFrameData(SpdyStreamId stream_id,
+ const char* data,
+ size_t len) override {}
+ void OnStreamEnd(SpdyStreamId stream_id) override {}
+ void OnStreamPadding(SpdyStreamId stream_id, size_t len) override {}
+ void OnRstStream(SpdyStreamId stream_id, SpdyErrorCode error_code) override {}
+ void OnSetting(SpdySettingsId id, uint32_t value) override {}
+ void OnPing(SpdyPingId unique_id, bool is_ack) override {}
+ void OnSettingsEnd() override {}
+ void OnSettingsAck() override {}
+ void OnGoAway(SpdyStreamId last_accepted_stream_id,
+ SpdyErrorCode error_code) override {}
+ void OnHeaders(SpdyStreamId stream_id,
+ bool has_priority,
+ int weight,
+ SpdyStreamId parent_stream_id,
+ bool exclusive,
+ bool fin,
+ bool end) override {}
+ void OnWindowUpdate(SpdyStreamId stream_id, int delta_window_size) override {}
+ void OnPushPromise(SpdyStreamId stream_id,
+ SpdyStreamId promised_stream_id,
+ bool end) override {}
+ void OnContinuation(SpdyStreamId stream_id, bool end) override {}
+ void OnAltSvc(SpdyStreamId stream_id,
+ SpdyStringPiece origin,
+ const SpdyAltSvcWireFormat::AlternativeServiceVector&
+ altsvc_vector) override {}
+ void OnPriority(SpdyStreamId stream_id,
+ SpdyStreamId parent_stream_id,
+ int weight,
+ bool exclusive) override {}
+ bool OnUnknownFrame(SpdyStreamId stream_id, uint8_t frame_type) override;
+
+ // SpdyFramerDebugVisitorInterface methods:
+ void OnSendCompressedFrame(SpdyStreamId stream_id,
+ SpdyFrameType type,
+ size_t payload_len,
+ size_t frame_len) override {}
+ void OnReceiveCompressedFrame(SpdyStreamId stream_id,
+ SpdyFrameType type,
+ size_t frame_len) override {}
+
+ // SpdyHeadersHandlerInterface methods:
+ void OnHeaderBlockStart() override {}
+ void OnHeader(SpdyStringPiece key, SpdyStringPiece value) override {}
+ void OnHeaderBlockEnd(size_t /* uncompressed_header_bytes */,
+ size_t /* compressed_header_bytes */) override {}
+};
+
+} // namespace test
+} // namespace spdy
+
+#endif // QUICHE_SPDY_CORE_SPDY_NO_OP_VISITOR_H_
diff --git a/chromium/net/third_party/quiche/src/spdy/core/spdy_pinnable_buffer_piece.cc b/chromium/net/third_party/quiche/src/spdy/core/spdy_pinnable_buffer_piece.cc
new file mode 100644
index 00000000000..8670962c56a
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/spdy/core/spdy_pinnable_buffer_piece.cc
@@ -0,0 +1,36 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/spdy/core/spdy_pinnable_buffer_piece.h"
+
+#include <new>
+
+namespace spdy {
+
+SpdyPinnableBufferPiece::SpdyPinnableBufferPiece()
+ : buffer_(nullptr), length_(0) {}
+
+SpdyPinnableBufferPiece::~SpdyPinnableBufferPiece() = default;
+
+void SpdyPinnableBufferPiece::Pin() {
+ if (!storage_ && buffer_ != nullptr && length_ != 0) {
+ storage_.reset(new char[length_]);
+ std::copy(buffer_, buffer_ + length_, storage_.get());
+ buffer_ = storage_.get();
+ }
+}
+
+void SpdyPinnableBufferPiece::Swap(SpdyPinnableBufferPiece* other) {
+ size_t length = length_;
+ length_ = other->length_;
+ other->length_ = length;
+
+ const char* buffer = buffer_;
+ buffer_ = other->buffer_;
+ other->buffer_ = buffer;
+
+ storage_.swap(other->storage_);
+}
+
+} // namespace spdy
diff --git a/chromium/net/third_party/quiche/src/spdy/core/spdy_pinnable_buffer_piece.h b/chromium/net/third_party/quiche/src/spdy/core/spdy_pinnable_buffer_piece.h
new file mode 100644
index 00000000000..3032ad7ec00
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/spdy/core/spdy_pinnable_buffer_piece.h
@@ -0,0 +1,53 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_SPDY_CORE_SPDY_PINNABLE_BUFFER_PIECE_H_
+#define QUICHE_SPDY_CORE_SPDY_PINNABLE_BUFFER_PIECE_H_
+
+#include <stddef.h>
+
+#include <memory>
+
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_export.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_string_piece.h"
+
+namespace spdy {
+
+class SpdyPrefixedBufferReader;
+
+// Helper class of SpdyPrefixedBufferReader.
+// Represents a piece of consumed buffer which may (or may not) own its
+// underlying storage. Users may "pin" the buffer at a later time to ensure
+// a SpdyPinnableBufferPiece owns and retains storage of the buffer.
+struct SPDY_EXPORT_PRIVATE SpdyPinnableBufferPiece {
+ public:
+ SpdyPinnableBufferPiece();
+ ~SpdyPinnableBufferPiece();
+
+ const char* buffer() const { return buffer_; }
+
+ explicit operator SpdyStringPiece() const {
+ return SpdyStringPiece(buffer_, length_);
+ }
+
+ // Allocates and copies the buffer to internal storage.
+ void Pin();
+
+ bool IsPinned() const { return storage_ != nullptr; }
+
+ // Swaps buffers, including internal storage, with |other|.
+ void Swap(SpdyPinnableBufferPiece* other);
+
+ private:
+ friend class SpdyPrefixedBufferReader;
+
+ const char* buffer_;
+ size_t length_;
+ // Null iff |buffer_| isn't pinned.
+ std::unique_ptr<char[]> storage_;
+};
+
+} // namespace spdy
+
+#endif // QUICHE_SPDY_CORE_SPDY_PINNABLE_BUFFER_PIECE_H_
diff --git a/chromium/net/third_party/quiche/src/spdy/core/spdy_pinnable_buffer_piece_test.cc b/chromium/net/third_party/quiche/src/spdy/core/spdy_pinnable_buffer_piece_test.cc
new file mode 100644
index 00000000000..d5a6c33f6f5
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/spdy/core/spdy_pinnable_buffer_piece_test.cc
@@ -0,0 +1,80 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/spdy/core/spdy_pinnable_buffer_piece.h"
+
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_prefixed_buffer_reader.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_string.h"
+
+namespace spdy {
+
+namespace test {
+
+class SpdyPinnableBufferPieceTest : public ::testing::Test {
+ protected:
+ SpdyPrefixedBufferReader Build(const SpdyString& prefix,
+ const SpdyString& suffix) {
+ prefix_ = prefix;
+ suffix_ = suffix;
+ return SpdyPrefixedBufferReader(prefix_.data(), prefix_.length(),
+ suffix_.data(), suffix_.length());
+ }
+ SpdyString prefix_, suffix_;
+};
+
+TEST_F(SpdyPinnableBufferPieceTest, Pin) {
+ SpdyPrefixedBufferReader reader = Build("foobar", "");
+ SpdyPinnableBufferPiece piece;
+ EXPECT_TRUE(reader.ReadN(6, &piece));
+
+ // Piece points to underlying prefix storage.
+ EXPECT_EQ(SpdyStringPiece("foobar"), SpdyStringPiece(piece));
+ EXPECT_FALSE(piece.IsPinned());
+ EXPECT_EQ(prefix_.data(), piece.buffer());
+
+ piece.Pin();
+
+ // Piece now points to allocated storage.
+ EXPECT_EQ(SpdyStringPiece("foobar"), SpdyStringPiece(piece));
+ EXPECT_TRUE(piece.IsPinned());
+ EXPECT_NE(prefix_.data(), piece.buffer());
+
+ // Pinning again has no effect.
+ const char* buffer = piece.buffer();
+ piece.Pin();
+ EXPECT_EQ(buffer, piece.buffer());
+}
+
+TEST_F(SpdyPinnableBufferPieceTest, Swap) {
+ SpdyPrefixedBufferReader reader = Build("foobar", "");
+ SpdyPinnableBufferPiece piece1, piece2;
+ EXPECT_TRUE(reader.ReadN(4, &piece1));
+ EXPECT_TRUE(reader.ReadN(2, &piece2));
+
+ piece1.Pin();
+
+ EXPECT_EQ(SpdyStringPiece("foob"), SpdyStringPiece(piece1));
+ EXPECT_TRUE(piece1.IsPinned());
+ EXPECT_EQ(SpdyStringPiece("ar"), SpdyStringPiece(piece2));
+ EXPECT_FALSE(piece2.IsPinned());
+
+ piece1.Swap(&piece2);
+
+ EXPECT_EQ(SpdyStringPiece("ar"), SpdyStringPiece(piece1));
+ EXPECT_FALSE(piece1.IsPinned());
+ EXPECT_EQ(SpdyStringPiece("foob"), SpdyStringPiece(piece2));
+ EXPECT_TRUE(piece2.IsPinned());
+
+ SpdyPinnableBufferPiece empty;
+ piece2.Swap(&empty);
+
+ EXPECT_EQ(SpdyStringPiece(""), SpdyStringPiece(piece2));
+ EXPECT_FALSE(piece2.IsPinned());
+}
+
+} // namespace test
+
+} // namespace spdy
diff --git a/chromium/net/third_party/quiche/src/spdy/core/spdy_prefixed_buffer_reader.cc b/chromium/net/third_party/quiche/src/spdy/core/spdy_prefixed_buffer_reader.cc
new file mode 100644
index 00000000000..9f1fa7fda7c
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/spdy/core/spdy_prefixed_buffer_reader.cc
@@ -0,0 +1,84 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/spdy/core/spdy_prefixed_buffer_reader.h"
+
+#include <new>
+
+#include "base/logging.h"
+
+namespace spdy {
+
+SpdyPrefixedBufferReader::SpdyPrefixedBufferReader(const char* prefix,
+ size_t prefix_length,
+ const char* suffix,
+ size_t suffix_length)
+ : prefix_(prefix),
+ suffix_(suffix),
+ prefix_length_(prefix_length),
+ suffix_length_(suffix_length) {}
+
+size_t SpdyPrefixedBufferReader::Available() {
+ return prefix_length_ + suffix_length_;
+}
+
+bool SpdyPrefixedBufferReader::ReadN(size_t count, char* out) {
+ if (Available() < count) {
+ return false;
+ }
+
+ if (prefix_length_ >= count) {
+ // Read is fully satisfied by the prefix.
+ std::copy(prefix_, prefix_ + count, out);
+ prefix_ += count;
+ prefix_length_ -= count;
+ return true;
+ } else if (prefix_length_ != 0) {
+ // Read is partially satisfied by the prefix.
+ out = std::copy(prefix_, prefix_ + prefix_length_, out);
+ count -= prefix_length_;
+ prefix_length_ = 0;
+ // Fallthrough to suffix read.
+ }
+ DCHECK(suffix_length_ >= count);
+ // Read is satisfied by the suffix.
+ std::copy(suffix_, suffix_ + count, out);
+ suffix_ += count;
+ suffix_length_ -= count;
+ return true;
+}
+
+bool SpdyPrefixedBufferReader::ReadN(size_t count,
+ SpdyPinnableBufferPiece* out) {
+ if (Available() < count) {
+ return false;
+ }
+
+ out->storage_.reset();
+ out->length_ = count;
+
+ if (prefix_length_ >= count) {
+ // Read is fully satisfied by the prefix.
+ out->buffer_ = prefix_;
+ prefix_ += count;
+ prefix_length_ -= count;
+ return true;
+ } else if (prefix_length_ != 0) {
+ // Read is only partially satisfied by the prefix. We need to allocate
+ // contiguous storage as the read spans the prefix & suffix.
+ out->storage_.reset(new char[count]);
+ out->buffer_ = out->storage_.get();
+ ReadN(count, out->storage_.get());
+ return true;
+ } else {
+ DCHECK(suffix_length_ >= count);
+ // Read is fully satisfied by the suffix.
+ out->buffer_ = suffix_;
+ suffix_ += count;
+ suffix_length_ -= count;
+ return true;
+ }
+}
+
+} // namespace spdy
diff --git a/chromium/net/third_party/quiche/src/spdy/core/spdy_prefixed_buffer_reader.h b/chromium/net/third_party/quiche/src/spdy/core/spdy_prefixed_buffer_reader.h
new file mode 100644
index 00000000000..2905698e648
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/spdy/core/spdy_prefixed_buffer_reader.h
@@ -0,0 +1,45 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_SPDY_CORE_SPDY_PREFIXED_BUFFER_READER_H_
+#define QUICHE_SPDY_CORE_SPDY_PREFIXED_BUFFER_READER_H_
+
+#include <stddef.h>
+
+#include "net/third_party/quiche/src/spdy/core/spdy_pinnable_buffer_piece.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_export.h"
+
+namespace spdy {
+
+// Reader class which simplifies reading contiguously from
+// from a disjoint buffer prefix & suffix.
+class SPDY_EXPORT_PRIVATE SpdyPrefixedBufferReader {
+ public:
+ SpdyPrefixedBufferReader(const char* prefix,
+ size_t prefix_length,
+ const char* suffix,
+ size_t suffix_length);
+
+ // Returns number of bytes available to be read.
+ size_t Available();
+
+ // Reads |count| bytes, copying into |*out|. Returns true on success,
+ // false if not enough bytes were available.
+ bool ReadN(size_t count, char* out);
+
+ // Reads |count| bytes, returned in |*out|. Returns true on success,
+ // false if not enough bytes were available.
+ bool ReadN(size_t count, SpdyPinnableBufferPiece* out);
+
+ private:
+ const char* prefix_;
+ const char* suffix_;
+
+ size_t prefix_length_;
+ size_t suffix_length_;
+};
+
+} // namespace spdy
+
+#endif // QUICHE_SPDY_CORE_SPDY_PREFIXED_BUFFER_READER_H_
diff --git a/chromium/net/third_party/quiche/src/spdy/core/spdy_prefixed_buffer_reader_test.cc b/chromium/net/third_party/quiche/src/spdy/core/spdy_prefixed_buffer_reader_test.cc
new file mode 100644
index 00000000000..1d052c7f21c
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/spdy/core/spdy_prefixed_buffer_reader_test.cc
@@ -0,0 +1,131 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/spdy/core/spdy_prefixed_buffer_reader.h"
+
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_string.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_string_piece.h"
+
+namespace spdy {
+
+namespace test {
+
+using testing::ElementsAreArray;
+
+class SpdyPrefixedBufferReaderTest : public ::testing::Test {
+ protected:
+ SpdyPrefixedBufferReader Build(const SpdyString& prefix,
+ const SpdyString& suffix) {
+ prefix_ = prefix;
+ suffix_ = suffix;
+ return SpdyPrefixedBufferReader(prefix_.data(), prefix_.length(),
+ suffix_.data(), suffix_.length());
+ }
+ SpdyString prefix_, suffix_;
+};
+
+TEST_F(SpdyPrefixedBufferReaderTest, ReadRawFromPrefix) {
+ SpdyPrefixedBufferReader reader = Build("foobar", "");
+ EXPECT_EQ(6u, reader.Available());
+
+ char buffer[] = "123456";
+ EXPECT_FALSE(reader.ReadN(10, buffer)); // Not enough buffer.
+ EXPECT_TRUE(reader.ReadN(6, buffer));
+ EXPECT_THAT(buffer, ElementsAreArray("foobar"));
+ EXPECT_EQ(0u, reader.Available());
+}
+
+TEST_F(SpdyPrefixedBufferReaderTest, ReadPieceFromPrefix) {
+ SpdyPrefixedBufferReader reader = Build("foobar", "");
+ EXPECT_EQ(6u, reader.Available());
+
+ SpdyPinnableBufferPiece piece;
+ EXPECT_FALSE(reader.ReadN(10, &piece)); // Not enough buffer.
+ EXPECT_TRUE(reader.ReadN(6, &piece));
+ EXPECT_FALSE(piece.IsPinned());
+ EXPECT_EQ(SpdyStringPiece("foobar"), SpdyStringPiece(piece));
+ EXPECT_EQ(0u, reader.Available());
+}
+
+TEST_F(SpdyPrefixedBufferReaderTest, ReadRawFromSuffix) {
+ SpdyPrefixedBufferReader reader = Build("", "foobar");
+ EXPECT_EQ(6u, reader.Available());
+
+ char buffer[] = "123456";
+ EXPECT_FALSE(reader.ReadN(10, buffer)); // Not enough buffer.
+ EXPECT_TRUE(reader.ReadN(6, buffer));
+ EXPECT_THAT(buffer, ElementsAreArray("foobar"));
+ EXPECT_EQ(0u, reader.Available());
+}
+
+TEST_F(SpdyPrefixedBufferReaderTest, ReadPieceFromSuffix) {
+ SpdyPrefixedBufferReader reader = Build("", "foobar");
+ EXPECT_EQ(6u, reader.Available());
+
+ SpdyPinnableBufferPiece piece;
+ EXPECT_FALSE(reader.ReadN(10, &piece)); // Not enough buffer.
+ EXPECT_TRUE(reader.ReadN(6, &piece));
+ EXPECT_FALSE(piece.IsPinned());
+ EXPECT_EQ(SpdyStringPiece("foobar"), SpdyStringPiece(piece));
+ EXPECT_EQ(0u, reader.Available());
+}
+
+TEST_F(SpdyPrefixedBufferReaderTest, ReadRawSpanning) {
+ SpdyPrefixedBufferReader reader = Build("foob", "ar");
+ EXPECT_EQ(6u, reader.Available());
+
+ char buffer[] = "123456";
+ EXPECT_FALSE(reader.ReadN(10, buffer)); // Not enough buffer.
+ EXPECT_TRUE(reader.ReadN(6, buffer));
+ EXPECT_THAT(buffer, ElementsAreArray("foobar"));
+ EXPECT_EQ(0u, reader.Available());
+}
+
+TEST_F(SpdyPrefixedBufferReaderTest, ReadPieceSpanning) {
+ SpdyPrefixedBufferReader reader = Build("foob", "ar");
+ EXPECT_EQ(6u, reader.Available());
+
+ SpdyPinnableBufferPiece piece;
+ EXPECT_FALSE(reader.ReadN(10, &piece)); // Not enough buffer.
+ EXPECT_TRUE(reader.ReadN(6, &piece));
+ EXPECT_TRUE(piece.IsPinned());
+ EXPECT_EQ(SpdyStringPiece("foobar"), SpdyStringPiece(piece));
+ EXPECT_EQ(0u, reader.Available());
+}
+
+TEST_F(SpdyPrefixedBufferReaderTest, ReadMixed) {
+ SpdyPrefixedBufferReader reader = Build("abcdef", "hijkl");
+ EXPECT_EQ(11u, reader.Available());
+
+ char buffer[] = "1234";
+ SpdyPinnableBufferPiece piece;
+
+ EXPECT_TRUE(reader.ReadN(3, buffer));
+ EXPECT_THAT(buffer, ElementsAreArray("abc4"));
+ EXPECT_EQ(8u, reader.Available());
+
+ EXPECT_TRUE(reader.ReadN(2, buffer));
+ EXPECT_THAT(buffer, ElementsAreArray("dec4"));
+ EXPECT_EQ(6u, reader.Available());
+
+ EXPECT_TRUE(reader.ReadN(3, &piece));
+ EXPECT_EQ(SpdyStringPiece("fhi"), SpdyStringPiece(piece));
+ EXPECT_TRUE(piece.IsPinned());
+ EXPECT_EQ(3u, reader.Available());
+
+ EXPECT_TRUE(reader.ReadN(2, &piece));
+ EXPECT_EQ(SpdyStringPiece("jk"), SpdyStringPiece(piece));
+ EXPECT_FALSE(piece.IsPinned());
+ EXPECT_EQ(1u, reader.Available());
+
+ EXPECT_TRUE(reader.ReadN(1, buffer));
+ EXPECT_THAT(buffer, ElementsAreArray("lec4"));
+ EXPECT_EQ(0u, reader.Available());
+}
+
+} // namespace test
+
+} // namespace spdy
diff --git a/chromium/net/third_party/quiche/src/spdy/core/spdy_protocol.cc b/chromium/net/third_party/quiche/src/spdy/core/spdy_protocol.cc
new file mode 100644
index 00000000000..f51dc6e6dfc
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/spdy/core/spdy_protocol.cc
@@ -0,0 +1,571 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/spdy/core/spdy_protocol.h"
+
+#include <ostream>
+
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_bug_tracker.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_ptr_util.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_string_utils.h"
+
+namespace spdy {
+
+const char* const kHttp2ConnectionHeaderPrefix =
+ "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n";
+
+std::ostream& operator<<(std::ostream& out, SpdyKnownSettingsId id) {
+ return out << static_cast<SpdySettingsId>(id);
+}
+
+std::ostream& operator<<(std::ostream& out, SpdyFrameType frame_type) {
+ return out << SerializeFrameType(frame_type);
+}
+
+SpdyPriority ClampSpdy3Priority(SpdyPriority priority) {
+ if (priority < kV3HighestPriority) {
+ SPDY_BUG << "Invalid priority: " << static_cast<int>(priority);
+ return kV3HighestPriority;
+ }
+ if (priority > kV3LowestPriority) {
+ SPDY_BUG << "Invalid priority: " << static_cast<int>(priority);
+ return kV3LowestPriority;
+ }
+ return priority;
+}
+
+int ClampHttp2Weight(int weight) {
+ if (weight < kHttp2MinStreamWeight) {
+ SPDY_BUG << "Invalid weight: " << weight;
+ return kHttp2MinStreamWeight;
+ }
+ if (weight > kHttp2MaxStreamWeight) {
+ SPDY_BUG << "Invalid weight: " << weight;
+ return kHttp2MaxStreamWeight;
+ }
+ return weight;
+}
+
+int Spdy3PriorityToHttp2Weight(SpdyPriority priority) {
+ priority = ClampSpdy3Priority(priority);
+ const float kSteps = 255.9f / 7.f;
+ return static_cast<int>(kSteps * (7.f - priority)) + 1;
+}
+
+SpdyPriority Http2WeightToSpdy3Priority(int weight) {
+ weight = ClampHttp2Weight(weight);
+ const float kSteps = 255.9f / 7.f;
+ return static_cast<SpdyPriority>(7.f - (weight - 1) / kSteps);
+}
+
+bool IsDefinedFrameType(uint8_t frame_type_field) {
+ return frame_type_field <= SerializeFrameType(SpdyFrameType::MAX_FRAME_TYPE);
+}
+
+SpdyFrameType ParseFrameType(uint8_t frame_type_field) {
+ SPDY_BUG_IF(!IsDefinedFrameType(frame_type_field))
+ << "Frame type not defined: " << static_cast<int>(frame_type_field);
+ return static_cast<SpdyFrameType>(frame_type_field);
+}
+
+uint8_t SerializeFrameType(SpdyFrameType frame_type) {
+ return static_cast<uint8_t>(frame_type);
+}
+
+bool IsValidHTTP2FrameStreamId(SpdyStreamId current_frame_stream_id,
+ SpdyFrameType frame_type_field) {
+ if (current_frame_stream_id == 0) {
+ switch (frame_type_field) {
+ case SpdyFrameType::DATA:
+ case SpdyFrameType::HEADERS:
+ case SpdyFrameType::PRIORITY:
+ case SpdyFrameType::RST_STREAM:
+ case SpdyFrameType::CONTINUATION:
+ case SpdyFrameType::PUSH_PROMISE:
+ // These frame types must specify a stream
+ return false;
+ default:
+ return true;
+ }
+ } else {
+ switch (frame_type_field) {
+ case SpdyFrameType::GOAWAY:
+ case SpdyFrameType::SETTINGS:
+ case SpdyFrameType::PING:
+ // These frame types must not specify a stream
+ return false;
+ default:
+ return true;
+ }
+ }
+}
+
+const char* FrameTypeToString(SpdyFrameType frame_type) {
+ switch (frame_type) {
+ case SpdyFrameType::DATA:
+ return "DATA";
+ case SpdyFrameType::RST_STREAM:
+ return "RST_STREAM";
+ case SpdyFrameType::SETTINGS:
+ return "SETTINGS";
+ case SpdyFrameType::PING:
+ return "PING";
+ case SpdyFrameType::GOAWAY:
+ return "GOAWAY";
+ case SpdyFrameType::HEADERS:
+ return "HEADERS";
+ case SpdyFrameType::WINDOW_UPDATE:
+ return "WINDOW_UPDATE";
+ case SpdyFrameType::PUSH_PROMISE:
+ return "PUSH_PROMISE";
+ case SpdyFrameType::CONTINUATION:
+ return "CONTINUATION";
+ case SpdyFrameType::PRIORITY:
+ return "PRIORITY";
+ case SpdyFrameType::ALTSVC:
+ return "ALTSVC";
+ case SpdyFrameType::EXTENSION:
+ return "EXTENSION (unspecified)";
+ }
+ return "UNKNOWN_FRAME_TYPE";
+}
+
+bool ParseSettingsId(SpdySettingsId wire_setting_id,
+ SpdyKnownSettingsId* setting_id) {
+ if (wire_setting_id != SETTINGS_EXPERIMENT_SCHEDULER &&
+ (wire_setting_id < SETTINGS_MIN || wire_setting_id > SETTINGS_MAX)) {
+ return false;
+ }
+
+ *setting_id = static_cast<SpdyKnownSettingsId>(wire_setting_id);
+ // This switch ensures that the casted value is valid. The default case is
+ // explicitly omitted to have compile-time guarantees that new additions to
+ // |SpdyKnownSettingsId| must also be handled here.
+ switch (*setting_id) {
+ case SETTINGS_HEADER_TABLE_SIZE:
+ case SETTINGS_ENABLE_PUSH:
+ case SETTINGS_MAX_CONCURRENT_STREAMS:
+ case SETTINGS_INITIAL_WINDOW_SIZE:
+ case SETTINGS_MAX_FRAME_SIZE:
+ case SETTINGS_MAX_HEADER_LIST_SIZE:
+ case SETTINGS_ENABLE_CONNECT_PROTOCOL:
+ case SETTINGS_EXPERIMENT_SCHEDULER:
+ // FALLTHROUGH_INTENDED
+ return true;
+ }
+ return false;
+}
+
+SpdyString SettingsIdToString(SpdySettingsId id) {
+ SpdyKnownSettingsId known_id;
+ if (!ParseSettingsId(id, &known_id)) {
+ return SpdyStrCat("SETTINGS_UNKNOWN_",
+ SpdyHexEncodeUInt32AndTrim(uint32_t{id}));
+ }
+
+ switch (known_id) {
+ case SETTINGS_HEADER_TABLE_SIZE:
+ return "SETTINGS_HEADER_TABLE_SIZE";
+ case SETTINGS_ENABLE_PUSH:
+ return "SETTINGS_ENABLE_PUSH";
+ case SETTINGS_MAX_CONCURRENT_STREAMS:
+ return "SETTINGS_MAX_CONCURRENT_STREAMS";
+ case SETTINGS_INITIAL_WINDOW_SIZE:
+ return "SETTINGS_INITIAL_WINDOW_SIZE";
+ case SETTINGS_MAX_FRAME_SIZE:
+ return "SETTINGS_MAX_FRAME_SIZE";
+ case SETTINGS_MAX_HEADER_LIST_SIZE:
+ return "SETTINGS_MAX_HEADER_LIST_SIZE";
+ case SETTINGS_ENABLE_CONNECT_PROTOCOL:
+ return "SETTINGS_ENABLE_CONNECT_PROTOCOL";
+ case SETTINGS_EXPERIMENT_SCHEDULER:
+ return "SETTINGS_EXPERIMENT_SCHEDULER";
+ }
+
+ return SpdyStrCat("SETTINGS_UNKNOWN_",
+ SpdyHexEncodeUInt32AndTrim(uint32_t{id}));
+}
+
+SpdyErrorCode ParseErrorCode(uint32_t wire_error_code) {
+ if (wire_error_code > ERROR_CODE_MAX) {
+ return ERROR_CODE_INTERNAL_ERROR;
+ }
+
+ return static_cast<SpdyErrorCode>(wire_error_code);
+}
+
+const char* ErrorCodeToString(SpdyErrorCode error_code) {
+ switch (error_code) {
+ case ERROR_CODE_NO_ERROR:
+ return "NO_ERROR";
+ case ERROR_CODE_PROTOCOL_ERROR:
+ return "PROTOCOL_ERROR";
+ case ERROR_CODE_INTERNAL_ERROR:
+ return "INTERNAL_ERROR";
+ case ERROR_CODE_FLOW_CONTROL_ERROR:
+ return "FLOW_CONTROL_ERROR";
+ case ERROR_CODE_SETTINGS_TIMEOUT:
+ return "SETTINGS_TIMEOUT";
+ case ERROR_CODE_STREAM_CLOSED:
+ return "STREAM_CLOSED";
+ case ERROR_CODE_FRAME_SIZE_ERROR:
+ return "FRAME_SIZE_ERROR";
+ case ERROR_CODE_REFUSED_STREAM:
+ return "REFUSED_STREAM";
+ case ERROR_CODE_CANCEL:
+ return "CANCEL";
+ case ERROR_CODE_COMPRESSION_ERROR:
+ return "COMPRESSION_ERROR";
+ case ERROR_CODE_CONNECT_ERROR:
+ return "CONNECT_ERROR";
+ case ERROR_CODE_ENHANCE_YOUR_CALM:
+ return "ENHANCE_YOUR_CALM";
+ case ERROR_CODE_INADEQUATE_SECURITY:
+ return "INADEQUATE_SECURITY";
+ case ERROR_CODE_HTTP_1_1_REQUIRED:
+ return "HTTP_1_1_REQUIRED";
+ }
+ return "UNKNOWN_ERROR_CODE";
+}
+
+size_t GetNumberRequiredContinuationFrames(size_t size) {
+ DCHECK_GT(size, kHttp2MaxControlFrameSendSize);
+ size_t overflow = size - kHttp2MaxControlFrameSendSize;
+ int payload_size =
+ kHttp2MaxControlFrameSendSize - kContinuationFrameMinimumSize;
+ // This is ceiling(overflow/payload_size) using integer arithmetics.
+ return (overflow - 1) / payload_size + 1;
+}
+
+const char* const kHttp2Npn = "h2";
+
+const char* const kHttp2AuthorityHeader = ":authority";
+const char* const kHttp2MethodHeader = ":method";
+const char* const kHttp2PathHeader = ":path";
+const char* const kHttp2SchemeHeader = ":scheme";
+const char* const kHttp2ProtocolHeader = ":protocol";
+
+const char* const kHttp2StatusHeader = ":status";
+
+bool SpdyFrameIR::fin() const {
+ return false;
+}
+
+int SpdyFrameIR::flow_control_window_consumed() const {
+ return 0;
+}
+
+bool SpdyFrameWithFinIR::fin() const {
+ return fin_;
+}
+
+SpdyFrameWithHeaderBlockIR::SpdyFrameWithHeaderBlockIR(
+ SpdyStreamId stream_id,
+ SpdyHeaderBlock header_block)
+ : SpdyFrameWithFinIR(stream_id), header_block_(std::move(header_block)) {}
+
+SpdyFrameWithHeaderBlockIR::~SpdyFrameWithHeaderBlockIR() = default;
+
+SpdyDataIR::SpdyDataIR(SpdyStreamId stream_id, SpdyStringPiece data)
+ : SpdyFrameWithFinIR(stream_id),
+ data_(nullptr),
+ data_len_(0),
+ padded_(false),
+ padding_payload_len_(0) {
+ SetDataDeep(data);
+}
+
+SpdyDataIR::SpdyDataIR(SpdyStreamId stream_id, const char* data)
+ : SpdyDataIR(stream_id, SpdyStringPiece(data)) {}
+
+SpdyDataIR::SpdyDataIR(SpdyStreamId stream_id, SpdyString data)
+ : SpdyFrameWithFinIR(stream_id),
+ data_store_(SpdyMakeUnique<SpdyString>(std::move(data))),
+ data_(data_store_->data()),
+ data_len_(data_store_->size()),
+ padded_(false),
+ padding_payload_len_(0) {}
+
+SpdyDataIR::SpdyDataIR(SpdyStreamId stream_id)
+ : SpdyFrameWithFinIR(stream_id),
+ data_(nullptr),
+ data_len_(0),
+ padded_(false),
+ padding_payload_len_(0) {}
+
+SpdyDataIR::~SpdyDataIR() = default;
+
+void SpdyDataIR::Visit(SpdyFrameVisitor* visitor) const {
+ return visitor->VisitData(*this);
+}
+
+SpdyFrameType SpdyDataIR::frame_type() const {
+ return SpdyFrameType::DATA;
+}
+
+int SpdyDataIR::flow_control_window_consumed() const {
+ return padded_ ? 1 + padding_payload_len_ + data_len_ : data_len_;
+}
+
+size_t SpdyDataIR::size() const {
+ return kFrameHeaderSize +
+ (padded() ? 1 + padding_payload_len() + data_len() : data_len());
+}
+
+SpdyRstStreamIR::SpdyRstStreamIR(SpdyStreamId stream_id,
+ SpdyErrorCode error_code)
+ : SpdyFrameIR(stream_id) {
+ set_error_code(error_code);
+}
+
+SpdyRstStreamIR::~SpdyRstStreamIR() = default;
+
+void SpdyRstStreamIR::Visit(SpdyFrameVisitor* visitor) const {
+ return visitor->VisitRstStream(*this);
+}
+
+SpdyFrameType SpdyRstStreamIR::frame_type() const {
+ return SpdyFrameType::RST_STREAM;
+}
+
+size_t SpdyRstStreamIR::size() const {
+ return kRstStreamFrameSize;
+}
+
+SpdySettingsIR::SpdySettingsIR() : is_ack_(false) {}
+
+SpdySettingsIR::~SpdySettingsIR() = default;
+
+void SpdySettingsIR::Visit(SpdyFrameVisitor* visitor) const {
+ return visitor->VisitSettings(*this);
+}
+
+SpdyFrameType SpdySettingsIR::frame_type() const {
+ return SpdyFrameType::SETTINGS;
+}
+
+size_t SpdySettingsIR::size() const {
+ return kFrameHeaderSize + values_.size() * kSettingsOneSettingSize;
+}
+
+void SpdyPingIR::Visit(SpdyFrameVisitor* visitor) const {
+ return visitor->VisitPing(*this);
+}
+
+SpdyFrameType SpdyPingIR::frame_type() const {
+ return SpdyFrameType::PING;
+}
+
+size_t SpdyPingIR::size() const {
+ return kPingFrameSize;
+}
+
+SpdyGoAwayIR::SpdyGoAwayIR(SpdyStreamId last_good_stream_id,
+ SpdyErrorCode error_code,
+ SpdyStringPiece description)
+ : description_(description) {
+ set_last_good_stream_id(last_good_stream_id);
+ set_error_code(error_code);
+}
+
+SpdyGoAwayIR::SpdyGoAwayIR(SpdyStreamId last_good_stream_id,
+ SpdyErrorCode error_code,
+ const char* description)
+ : SpdyGoAwayIR(last_good_stream_id,
+ error_code,
+ SpdyStringPiece(description)) {}
+
+SpdyGoAwayIR::SpdyGoAwayIR(SpdyStreamId last_good_stream_id,
+ SpdyErrorCode error_code,
+ SpdyString description)
+ : description_store_(std::move(description)),
+ description_(description_store_) {
+ set_last_good_stream_id(last_good_stream_id);
+ set_error_code(error_code);
+}
+
+SpdyGoAwayIR::~SpdyGoAwayIR() = default;
+
+void SpdyGoAwayIR::Visit(SpdyFrameVisitor* visitor) const {
+ return visitor->VisitGoAway(*this);
+}
+
+SpdyFrameType SpdyGoAwayIR::frame_type() const {
+ return SpdyFrameType::GOAWAY;
+}
+
+size_t SpdyGoAwayIR::size() const {
+ return kGoawayFrameMinimumSize + description_.size();
+}
+
+SpdyContinuationIR::SpdyContinuationIR(SpdyStreamId stream_id)
+ : SpdyFrameIR(stream_id), end_headers_(false) {
+ encoding_ = SpdyMakeUnique<SpdyString>();
+}
+
+SpdyContinuationIR::~SpdyContinuationIR() = default;
+
+void SpdyContinuationIR::Visit(SpdyFrameVisitor* visitor) const {
+ return visitor->VisitContinuation(*this);
+}
+
+SpdyFrameType SpdyContinuationIR::frame_type() const {
+ return SpdyFrameType::CONTINUATION;
+}
+
+size_t SpdyContinuationIR::size() const {
+ // We don't need to get the size of CONTINUATION frame directly. It is
+ // calculated in HEADERS or PUSH_PROMISE frame.
+ DLOG(WARNING) << "Shouldn't not call size() for CONTINUATION frame.";
+ return 0;
+}
+
+void SpdyHeadersIR::Visit(SpdyFrameVisitor* visitor) const {
+ return visitor->VisitHeaders(*this);
+}
+
+SpdyFrameType SpdyHeadersIR::frame_type() const {
+ return SpdyFrameType::HEADERS;
+}
+
+size_t SpdyHeadersIR::size() const {
+ size_t size = kHeadersFrameMinimumSize;
+
+ if (padded_) {
+ // Padding field length.
+ size += 1;
+ size += padding_payload_len_;
+ }
+
+ if (has_priority_) {
+ size += 5;
+ }
+
+ // Assume no hpack encoding is applied.
+ size += header_block().TotalBytesUsed() +
+ header_block().size() * kPerHeaderHpackOverhead;
+ if (size > kHttp2MaxControlFrameSendSize) {
+ size += GetNumberRequiredContinuationFrames(size) *
+ kContinuationFrameMinimumSize;
+ }
+ return size;
+}
+
+void SpdyWindowUpdateIR::Visit(SpdyFrameVisitor* visitor) const {
+ return visitor->VisitWindowUpdate(*this);
+}
+
+SpdyFrameType SpdyWindowUpdateIR::frame_type() const {
+ return SpdyFrameType::WINDOW_UPDATE;
+}
+
+size_t SpdyWindowUpdateIR::size() const {
+ return kWindowUpdateFrameSize;
+}
+
+void SpdyPushPromiseIR::Visit(SpdyFrameVisitor* visitor) const {
+ return visitor->VisitPushPromise(*this);
+}
+
+SpdyFrameType SpdyPushPromiseIR::frame_type() const {
+ return SpdyFrameType::PUSH_PROMISE;
+}
+
+size_t SpdyPushPromiseIR::size() const {
+ size_t size = kPushPromiseFrameMinimumSize;
+
+ if (padded_) {
+ // Padding length field.
+ size += 1;
+ size += padding_payload_len_;
+ }
+
+ size += header_block().TotalBytesUsed();
+ if (size > kHttp2MaxControlFrameSendSize) {
+ size += GetNumberRequiredContinuationFrames(size) *
+ kContinuationFrameMinimumSize;
+ }
+ return size;
+}
+
+SpdyAltSvcIR::SpdyAltSvcIR(SpdyStreamId stream_id) : SpdyFrameIR(stream_id) {}
+
+SpdyAltSvcIR::~SpdyAltSvcIR() = default;
+
+void SpdyAltSvcIR::Visit(SpdyFrameVisitor* visitor) const {
+ return visitor->VisitAltSvc(*this);
+}
+
+SpdyFrameType SpdyAltSvcIR::frame_type() const {
+ return SpdyFrameType::ALTSVC;
+}
+
+size_t SpdyAltSvcIR::size() const {
+ size_t size = kGetAltSvcFrameMinimumSize;
+ size += origin_.length();
+ // TODO(yasong): estimates the size without serializing the vector.
+ SpdyString str =
+ SpdyAltSvcWireFormat::SerializeHeaderFieldValue(altsvc_vector_);
+ size += str.size();
+ return size;
+}
+
+void SpdyPriorityIR::Visit(SpdyFrameVisitor* visitor) const {
+ return visitor->VisitPriority(*this);
+}
+
+SpdyFrameType SpdyPriorityIR::frame_type() const {
+ return SpdyFrameType::PRIORITY;
+}
+
+size_t SpdyPriorityIR::size() const {
+ return kPriorityFrameSize;
+}
+
+void SpdyUnknownIR::Visit(SpdyFrameVisitor* visitor) const {
+ return visitor->VisitUnknown(*this);
+}
+
+SpdyFrameType SpdyUnknownIR::frame_type() const {
+ return static_cast<SpdyFrameType>(type());
+}
+
+size_t SpdyUnknownIR::size() const {
+ return kFrameHeaderSize + payload_.size();
+}
+
+int SpdyUnknownIR::flow_control_window_consumed() const {
+ if (frame_type() == SpdyFrameType::DATA) {
+ return payload_.size();
+ } else {
+ return 0;
+ }
+}
+
+// Wire size of pad length field.
+const size_t kPadLengthFieldSize = 1;
+
+size_t GetHeaderFrameSizeSansBlock(const SpdyHeadersIR& header_ir) {
+ size_t min_size = kFrameHeaderSize;
+ if (header_ir.padded()) {
+ min_size += kPadLengthFieldSize;
+ min_size += header_ir.padding_payload_len();
+ }
+ if (header_ir.has_priority()) {
+ min_size += 5;
+ }
+ return min_size;
+}
+
+size_t GetPushPromiseFrameSizeSansBlock(
+ const SpdyPushPromiseIR& push_promise_ir) {
+ size_t min_size = kPushPromiseFrameMinimumSize;
+ if (push_promise_ir.padded()) {
+ min_size += kPadLengthFieldSize;
+ min_size += push_promise_ir.padding_payload_len();
+ }
+ return min_size;
+}
+
+} // namespace spdy
diff --git a/chromium/net/third_party/quiche/src/spdy/core/spdy_protocol.h b/chromium/net/third_party/quiche/src/spdy/core/spdy_protocol.h
new file mode 100644
index 00000000000..d300d10bed2
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/spdy/core/spdy_protocol.h
@@ -0,0 +1,1066 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// This file contains some protocol structures for use with SPDY 3 and HTTP 2
+// The SPDY 3 spec can be found at:
+// http://dev.chromium.org/spdy/spdy-protocol/spdy-protocol-draft3
+
+#ifndef QUICHE_SPDY_CORE_SPDY_PROTOCOL_H_
+#define QUICHE_SPDY_CORE_SPDY_PROTOCOL_H_
+
+#include <cstddef>
+#include <cstdint>
+#include <iosfwd>
+#include <limits>
+#include <map>
+#include <memory>
+#include <new>
+#include <utility>
+
+#include "base/logging.h"
+#include "base/macros.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_alt_svc_wire_format.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_bitmasks.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_header_block.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_bug_tracker.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_export.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_ptr_util.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_string.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_string_piece.h"
+
+namespace spdy {
+
+// A stream ID is a 31-bit entity.
+using SpdyStreamId = uint32_t;
+
+// A SETTINGS ID is a 16-bit entity.
+using SpdySettingsId = uint16_t;
+
+// Specifies the stream ID used to denote the current session (for
+// flow control).
+const SpdyStreamId kSessionFlowControlStreamId = 0;
+
+// 0 is not a valid stream ID for any other purpose than flow control.
+const SpdyStreamId kInvalidStreamId = 0;
+
+// Max stream id.
+const SpdyStreamId kMaxStreamId = 0x7fffffff;
+
+// The maximum possible frame payload size allowed by the spec.
+const uint32_t kSpdyMaxFrameSizeLimit = (1 << 24) - 1;
+
+// The initial value for the maximum frame payload size as per the spec. This is
+// the maximum control frame size we accept.
+const uint32_t kHttp2DefaultFramePayloadLimit = 1 << 14;
+
+// The maximum size of the control frames that we send, including the size of
+// the header. This limit is arbitrary. We can enforce it here or at the
+// application layer. We chose the framing layer, but this can be changed (or
+// removed) if necessary later down the line.
+const size_t kHttp2MaxControlFrameSendSize = kHttp2DefaultFramePayloadLimit - 1;
+
+// Number of octets in the frame header.
+const size_t kFrameHeaderSize = 9;
+
+// The initial value for the maximum frame payload size as per the spec. This is
+// the maximum control frame size we accept.
+const uint32_t kHttp2DefaultFrameSizeLimit =
+ kHttp2DefaultFramePayloadLimit + kFrameHeaderSize;
+
+// The initial value for the maximum size of the header list, "unlimited" (max
+// unsigned 32-bit int) as per the spec.
+const uint32_t kSpdyInitialHeaderListSizeLimit = 0xFFFFFFFF;
+
+// Maximum window size for a Spdy stream or session.
+const int32_t kSpdyMaximumWindowSize = 0x7FFFFFFF; // Max signed 32bit int
+
+// Maximum padding size in octets for one DATA or HEADERS or PUSH_PROMISE frame.
+const int32_t kPaddingSizePerFrame = 256;
+
+// The HTTP/2 connection preface, which must be the first bytes sent by the
+// client upon starting an HTTP/2 connection, and which must be followed by a
+// SETTINGS frame. Note that even though |kHttp2ConnectionHeaderPrefix| is
+// defined as a string literal with a null terminator, the actual connection
+// preface is only the first |kHttp2ConnectionHeaderPrefixSize| bytes, which
+// excludes the null terminator.
+SPDY_EXPORT_PRIVATE extern const char* const kHttp2ConnectionHeaderPrefix;
+const int kHttp2ConnectionHeaderPrefixSize = 24;
+
+// Wire values for HTTP2 frame types.
+enum class SpdyFrameType : uint8_t {
+ DATA = 0x00,
+ HEADERS = 0x01,
+ PRIORITY = 0x02,
+ RST_STREAM = 0x03,
+ SETTINGS = 0x04,
+ PUSH_PROMISE = 0x05,
+ PING = 0x06,
+ GOAWAY = 0x07,
+ WINDOW_UPDATE = 0x08,
+ CONTINUATION = 0x09,
+ // ALTSVC is a public extension.
+ ALTSVC = 0x0a,
+ MAX_FRAME_TYPE = ALTSVC,
+ // The specific value of EXTENSION is meaningless; it is a placeholder used
+ // within SpdyFramer's state machine when handling unknown frames via an
+ // extension API.
+ // TODO(birenroy): Remove the fake EXTENSION value from the SpdyFrameType
+ // enum.
+ EXTENSION = 0xff
+};
+
+// Flags on data packets.
+enum SpdyDataFlags {
+ DATA_FLAG_NONE = 0x00,
+ DATA_FLAG_FIN = 0x01,
+ DATA_FLAG_PADDED = 0x08,
+};
+
+// Flags on control packets
+enum SpdyControlFlags {
+ CONTROL_FLAG_NONE = 0x00,
+ CONTROL_FLAG_FIN = 0x01,
+};
+
+enum SpdyPingFlags {
+ PING_FLAG_ACK = 0x01,
+};
+
+// Used by HEADERS, PUSH_PROMISE, and CONTINUATION.
+enum SpdyHeadersFlags {
+ HEADERS_FLAG_END_HEADERS = 0x04,
+ HEADERS_FLAG_PADDED = 0x08,
+ HEADERS_FLAG_PRIORITY = 0x20,
+};
+
+enum SpdyPushPromiseFlags {
+ PUSH_PROMISE_FLAG_END_PUSH_PROMISE = 0x04,
+ PUSH_PROMISE_FLAG_PADDED = 0x08,
+};
+
+enum Http2SettingsControlFlags {
+ SETTINGS_FLAG_ACK = 0x01,
+};
+
+// Wire values of HTTP/2 setting identifiers.
+enum SpdyKnownSettingsId : SpdySettingsId {
+ // HPACK header table maximum size.
+ SETTINGS_HEADER_TABLE_SIZE = 0x1,
+ SETTINGS_MIN = SETTINGS_HEADER_TABLE_SIZE,
+ // Whether or not server push (PUSH_PROMISE) is enabled.
+ SETTINGS_ENABLE_PUSH = 0x2,
+ // The maximum number of simultaneous live streams in each direction.
+ SETTINGS_MAX_CONCURRENT_STREAMS = 0x3,
+ // Initial window size in bytes
+ SETTINGS_INITIAL_WINDOW_SIZE = 0x4,
+ // The size of the largest frame payload that a receiver is willing to accept.
+ SETTINGS_MAX_FRAME_SIZE = 0x5,
+ // The maximum size of header list that the sender is prepared to accept.
+ SETTINGS_MAX_HEADER_LIST_SIZE = 0x6,
+ // Enable Websockets over HTTP/2, see
+ // https://tools.ietf.org/html/draft-ietf-httpbis-h2-websockets-00.
+ SETTINGS_ENABLE_CONNECT_PROTOCOL = 0x8,
+ SETTINGS_MAX = SETTINGS_ENABLE_CONNECT_PROTOCOL,
+ // Experimental setting used to configure an alternative write scheduler.
+ SETTINGS_EXPERIMENT_SCHEDULER = 0xFF45,
+};
+
+// This explicit operator is needed, otherwise compiler finds
+// overloaded operator to be ambiguous.
+SPDY_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& out,
+ SpdyKnownSettingsId id);
+
+// This operator is needed, because SpdyFrameType is an enum class,
+// therefore implicit conversion to underlying integer type is not allowed.
+SPDY_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& out,
+ SpdyFrameType frame_type);
+
+using SettingsMap = std::map<SpdySettingsId, uint32_t>;
+
+// HTTP/2 error codes, RFC 7540 Section 7.
+enum SpdyErrorCode : uint32_t {
+ ERROR_CODE_NO_ERROR = 0x0,
+ ERROR_CODE_PROTOCOL_ERROR = 0x1,
+ ERROR_CODE_INTERNAL_ERROR = 0x2,
+ ERROR_CODE_FLOW_CONTROL_ERROR = 0x3,
+ ERROR_CODE_SETTINGS_TIMEOUT = 0x4,
+ ERROR_CODE_STREAM_CLOSED = 0x5,
+ ERROR_CODE_FRAME_SIZE_ERROR = 0x6,
+ ERROR_CODE_REFUSED_STREAM = 0x7,
+ ERROR_CODE_CANCEL = 0x8,
+ ERROR_CODE_COMPRESSION_ERROR = 0x9,
+ ERROR_CODE_CONNECT_ERROR = 0xa,
+ ERROR_CODE_ENHANCE_YOUR_CALM = 0xb,
+ ERROR_CODE_INADEQUATE_SECURITY = 0xc,
+ ERROR_CODE_HTTP_1_1_REQUIRED = 0xd,
+ ERROR_CODE_MAX = ERROR_CODE_HTTP_1_1_REQUIRED
+};
+
+// A SPDY priority is a number between 0 and 7 (inclusive).
+typedef uint8_t SpdyPriority;
+
+// Lowest and Highest here refer to SPDY priorities as described in
+// https://www.chromium.org/spdy/spdy-protocol/spdy-protocol-draft3-1#TOC-2.3.3-Stream-priority
+const SpdyPriority kV3HighestPriority = 0;
+const SpdyPriority kV3LowestPriority = 7;
+
+// Returns SPDY 3.x priority value clamped to the valid range of [0, 7].
+SPDY_EXPORT_PRIVATE SpdyPriority ClampSpdy3Priority(SpdyPriority priority);
+
+// HTTP/2 stream weights are integers in range [1, 256], as specified in RFC
+// 7540 section 5.3.2. Default stream weight is defined in section 5.3.5.
+const int kHttp2MinStreamWeight = 1;
+const int kHttp2MaxStreamWeight = 256;
+const int kHttp2DefaultStreamWeight = 16;
+
+// Returns HTTP/2 weight clamped to the valid range of [1, 256].
+SPDY_EXPORT_PRIVATE int ClampHttp2Weight(int weight);
+
+// Maps SPDY 3.x priority value in range [0, 7] to HTTP/2 weight value in range
+// [1, 256], where priority 0 (i.e. highest precedence) corresponds to maximum
+// weight 256 and priority 7 (lowest precedence) corresponds to minimum weight
+// 1.
+SPDY_EXPORT_PRIVATE int Spdy3PriorityToHttp2Weight(SpdyPriority priority);
+
+// Maps HTTP/2 weight value in range [1, 256] to SPDY 3.x priority value in
+// range [0, 7], where minimum weight 1 corresponds to priority 7 (lowest
+// precedence) and maximum weight 256 corresponds to priority 0 (highest
+// precedence).
+SPDY_EXPORT_PRIVATE SpdyPriority Http2WeightToSpdy3Priority(int weight);
+
+// Reserved ID for root stream of HTTP/2 stream dependency tree, as specified
+// in RFC 7540 section 5.3.1.
+const unsigned int kHttp2RootStreamId = 0;
+
+typedef uint64_t SpdyPingId;
+
+// Returns true if a given on-the-wire enumeration of a frame type is defined
+// in a standardized HTTP/2 specification, false otherwise.
+SPDY_EXPORT_PRIVATE bool IsDefinedFrameType(uint8_t frame_type_field);
+
+// Parses a frame type from an on-the-wire enumeration.
+// Behavior is undefined for invalid frame type fields; consumers should first
+// use IsValidFrameType() to verify validity of frame type fields.
+SPDY_EXPORT_PRIVATE SpdyFrameType ParseFrameType(uint8_t frame_type_field);
+
+// Serializes a frame type to the on-the-wire value.
+SPDY_EXPORT_PRIVATE uint8_t SerializeFrameType(SpdyFrameType frame_type);
+
+// (HTTP/2) All standard frame types except WINDOW_UPDATE are
+// (stream-specific xor connection-level). Returns false iff we know
+// the given frame type does not align with the given streamID.
+SPDY_EXPORT_PRIVATE bool IsValidHTTP2FrameStreamId(
+ SpdyStreamId current_frame_stream_id,
+ SpdyFrameType frame_type_field);
+
+// Serialize |frame_type| to string for logging/debugging.
+const char* FrameTypeToString(SpdyFrameType frame_type);
+
+// If |wire_setting_id| is the on-the-wire representation of a defined SETTINGS
+// parameter, parse it to |*setting_id| and return true.
+SPDY_EXPORT_PRIVATE bool ParseSettingsId(SpdySettingsId wire_setting_id,
+ SpdyKnownSettingsId* setting_id);
+
+// Returns a string representation of the |id| for logging/debugging. Returns
+// the |id| prefixed with "SETTINGS_UNKNOWN_" for unknown SETTINGS IDs. To parse
+// the |id| into a SpdyKnownSettingsId (if applicable), use ParseSettingsId().
+SPDY_EXPORT_PRIVATE SpdyString SettingsIdToString(SpdySettingsId id);
+
+// Parse |wire_error_code| to a SpdyErrorCode.
+// Treat unrecognized error codes as INTERNAL_ERROR
+// as recommended by the HTTP/2 specification.
+SPDY_EXPORT_PRIVATE SpdyErrorCode ParseErrorCode(uint32_t wire_error_code);
+
+// Serialize RST_STREAM or GOAWAY frame error code to string
+// for logging/debugging.
+const char* ErrorCodeToString(SpdyErrorCode error_code);
+
+// Minimum size of a frame, in octets.
+const size_t kFrameMinimumSize = kFrameHeaderSize;
+
+// Minimum frame size for variable size frame types (includes mandatory fields),
+// frame size for fixed size frames, in octets.
+
+const size_t kDataFrameMinimumSize = kFrameHeaderSize;
+const size_t kHeadersFrameMinimumSize = kFrameHeaderSize;
+// PRIORITY frame has stream_dependency (4 octets) and weight (1 octet) fields.
+const size_t kPriorityFrameSize = kFrameHeaderSize + 5;
+// RST_STREAM frame has error_code (4 octets) field.
+const size_t kRstStreamFrameSize = kFrameHeaderSize + 4;
+const size_t kSettingsFrameMinimumSize = kFrameHeaderSize;
+const size_t kSettingsOneSettingSize =
+ sizeof(uint32_t) + sizeof(SpdySettingsId);
+// PUSH_PROMISE frame has promised_stream_id (4 octet) field.
+const size_t kPushPromiseFrameMinimumSize = kFrameHeaderSize + 4;
+// PING frame has opaque_bytes (8 octet) field.
+const size_t kPingFrameSize = kFrameHeaderSize + 8;
+// GOAWAY frame has last_stream_id (4 octet) and error_code (4 octet) fields.
+const size_t kGoawayFrameMinimumSize = kFrameHeaderSize + 8;
+// WINDOW_UPDATE frame has window_size_increment (4 octet) field.
+const size_t kWindowUpdateFrameSize = kFrameHeaderSize + 4;
+const size_t kContinuationFrameMinimumSize = kFrameHeaderSize;
+// ALTSVC frame has origin_len (2 octets) field.
+const size_t kGetAltSvcFrameMinimumSize = kFrameHeaderSize + 2;
+
+// Maximum possible configurable size of a frame in octets.
+const size_t kMaxFrameSizeLimit = kSpdyMaxFrameSizeLimit + kFrameHeaderSize;
+// Size of a header block size field.
+const size_t kSizeOfSizeField = sizeof(uint32_t);
+// Per-header overhead for block size accounting in bytes.
+const size_t kPerHeaderOverhead = 32;
+// Initial window size for a stream in bytes.
+const int32_t kInitialStreamWindowSize = 64 * 1024 - 1;
+// Initial window size for a session in bytes.
+const int32_t kInitialSessionWindowSize = 64 * 1024 - 1;
+// The NPN string for HTTP2, "h2".
+extern const char* const kHttp2Npn;
+// An estimate size of the HPACK overhead for each header field. 1 bytes for
+// indexed literal, 1 bytes for key literal and length encoding, and 2 bytes for
+// value literal and length encoding.
+const size_t kPerHeaderHpackOverhead = 4;
+
+// Names of pseudo-headers defined for HTTP/2 requests.
+SPDY_EXPORT_PRIVATE extern const char* const kHttp2AuthorityHeader;
+SPDY_EXPORT_PRIVATE extern const char* const kHttp2MethodHeader;
+SPDY_EXPORT_PRIVATE extern const char* const kHttp2PathHeader;
+SPDY_EXPORT_PRIVATE extern const char* const kHttp2SchemeHeader;
+SPDY_EXPORT_PRIVATE extern const char* const kHttp2ProtocolHeader;
+
+// Name of pseudo-header defined for HTTP/2 responses.
+SPDY_EXPORT_PRIVATE extern const char* const kHttp2StatusHeader;
+
+SPDY_EXPORT_PRIVATE size_t GetNumberRequiredContinuationFrames(size_t size);
+
+// Variant type (i.e. tagged union) that is either a SPDY 3.x priority value,
+// or else an HTTP/2 stream dependency tuple {parent stream ID, weight,
+// exclusive bit}. Templated to allow for use by QUIC code; SPDY and HTTP/2
+// code should use the concrete type instantiation SpdyStreamPrecedence.
+template <typename StreamIdType>
+class StreamPrecedence {
+ public:
+ // Constructs instance that is a SPDY 3.x priority. Clamps priority value to
+ // the valid range [0, 7].
+ explicit StreamPrecedence(SpdyPriority priority)
+ : is_spdy3_priority_(true),
+ spdy3_priority_(ClampSpdy3Priority(priority)) {}
+
+ // Constructs instance that is an HTTP/2 stream weight, parent stream ID, and
+ // exclusive bit. Clamps stream weight to the valid range [1, 256].
+ StreamPrecedence(StreamIdType parent_id, int weight, bool is_exclusive)
+ : is_spdy3_priority_(false),
+ http2_stream_dependency_{parent_id, ClampHttp2Weight(weight),
+ is_exclusive} {}
+
+ // Intentionally copyable, to support pass by value.
+ StreamPrecedence(const StreamPrecedence& other) = default;
+ StreamPrecedence& operator=(const StreamPrecedence& other) = default;
+
+ // Returns true if this instance is a SPDY 3.x priority, or false if this
+ // instance is an HTTP/2 stream dependency.
+ bool is_spdy3_priority() const { return is_spdy3_priority_; }
+
+ // Returns SPDY 3.x priority value. If |is_spdy3_priority()| is true, this is
+ // the value provided at construction, clamped to the legal priority
+ // range. Otherwise, it is the HTTP/2 stream weight mapped to a SPDY 3.x
+ // priority value, where minimum weight 1 corresponds to priority 7 (lowest
+ // precedence) and maximum weight 256 corresponds to priority 0 (highest
+ // precedence).
+ SpdyPriority spdy3_priority() const {
+ return is_spdy3_priority_
+ ? spdy3_priority_
+ : Http2WeightToSpdy3Priority(http2_stream_dependency_.weight);
+ }
+
+ // Returns HTTP/2 parent stream ID. If |is_spdy3_priority()| is false, this is
+ // the value provided at construction, otherwise it is |kHttp2RootStreamId|.
+ StreamIdType parent_id() const {
+ return is_spdy3_priority_ ? kHttp2RootStreamId
+ : http2_stream_dependency_.parent_id;
+ }
+
+ // Returns HTTP/2 stream weight. If |is_spdy3_priority()| is false, this is
+ // the value provided at construction, clamped to the legal weight
+ // range. Otherwise, it is the SPDY 3.x priority value mapped to an HTTP/2
+ // stream weight, where priority 0 (i.e. highest precedence) corresponds to
+ // maximum weight 256 and priority 7 (lowest precedence) corresponds to
+ // minimum weight 1.
+ int weight() const {
+ return is_spdy3_priority_ ? Spdy3PriorityToHttp2Weight(spdy3_priority_)
+ : http2_stream_dependency_.weight;
+ }
+
+ // Returns HTTP/2 parent stream exclusivity. If |is_spdy3_priority()| is
+ // false, this is the value provided at construction, otherwise it is false.
+ bool is_exclusive() const {
+ return !is_spdy3_priority_ && http2_stream_dependency_.is_exclusive;
+ }
+
+ // Facilitates test assertions.
+ bool operator==(const StreamPrecedence& other) const {
+ if (is_spdy3_priority()) {
+ return other.is_spdy3_priority() &&
+ (spdy3_priority() == other.spdy3_priority());
+ } else {
+ return !other.is_spdy3_priority() && (parent_id() == other.parent_id()) &&
+ (weight() == other.weight()) &&
+ (is_exclusive() == other.is_exclusive());
+ }
+ }
+
+ bool operator!=(const StreamPrecedence& other) const {
+ return !(*this == other);
+ }
+
+ private:
+ struct Http2StreamDependency {
+ StreamIdType parent_id;
+ int weight;
+ bool is_exclusive;
+ };
+
+ bool is_spdy3_priority_;
+ union {
+ SpdyPriority spdy3_priority_;
+ Http2StreamDependency http2_stream_dependency_;
+ };
+};
+
+typedef StreamPrecedence<SpdyStreamId> SpdyStreamPrecedence;
+
+class SpdyFrameVisitor;
+
+// Intermediate representation for HTTP2 frames.
+class SPDY_EXPORT_PRIVATE SpdyFrameIR {
+ public:
+ virtual ~SpdyFrameIR() {}
+
+ virtual void Visit(SpdyFrameVisitor* visitor) const = 0;
+ virtual SpdyFrameType frame_type() const = 0;
+ SpdyStreamId stream_id() const { return stream_id_; }
+ virtual bool fin() const;
+ // Returns an estimate of the size of the serialized frame, without applying
+ // compression. May not be exact.
+ virtual size_t size() const = 0;
+
+ // Returns the number of bytes of flow control window that would be consumed
+ // by this frame if written to the wire.
+ virtual int flow_control_window_consumed() const;
+
+ protected:
+ SpdyFrameIR() : stream_id_(0) {}
+ explicit SpdyFrameIR(SpdyStreamId stream_id) : stream_id_(stream_id) {}
+ SpdyFrameIR(const SpdyFrameIR&) = delete;
+ SpdyFrameIR& operator=(const SpdyFrameIR&) = delete;
+
+ private:
+ SpdyStreamId stream_id_;
+};
+
+// Abstract class intended to be inherited by IRs that have the option of a FIN
+// flag.
+class SPDY_EXPORT_PRIVATE SpdyFrameWithFinIR : public SpdyFrameIR {
+ public:
+ ~SpdyFrameWithFinIR() override {}
+ bool fin() const override;
+ void set_fin(bool fin) { fin_ = fin; }
+
+ protected:
+ explicit SpdyFrameWithFinIR(SpdyStreamId stream_id)
+ : SpdyFrameIR(stream_id), fin_(false) {}
+ SpdyFrameWithFinIR(const SpdyFrameWithFinIR&) = delete;
+ SpdyFrameWithFinIR& operator=(const SpdyFrameWithFinIR&) = delete;
+
+ private:
+ bool fin_;
+};
+
+// Abstract class intended to be inherited by IRs that contain a header
+// block. Implies SpdyFrameWithFinIR.
+class SPDY_EXPORT_PRIVATE SpdyFrameWithHeaderBlockIR
+ : public SpdyFrameWithFinIR {
+ public:
+ ~SpdyFrameWithHeaderBlockIR() override;
+
+ const SpdyHeaderBlock& header_block() const { return header_block_; }
+ void set_header_block(SpdyHeaderBlock header_block) {
+ // Deep copy.
+ header_block_ = std::move(header_block);
+ }
+ void SetHeader(SpdyStringPiece name, SpdyStringPiece value) {
+ header_block_[name] = value;
+ }
+
+ protected:
+ SpdyFrameWithHeaderBlockIR(SpdyStreamId stream_id,
+ SpdyHeaderBlock header_block);
+ SpdyFrameWithHeaderBlockIR(const SpdyFrameWithHeaderBlockIR&) = delete;
+ SpdyFrameWithHeaderBlockIR& operator=(const SpdyFrameWithHeaderBlockIR&) =
+ delete;
+
+ private:
+ SpdyHeaderBlock header_block_;
+};
+
+class SPDY_EXPORT_PRIVATE SpdyDataIR : public SpdyFrameWithFinIR {
+ public:
+ // Performs a deep copy on data.
+ SpdyDataIR(SpdyStreamId stream_id, SpdyStringPiece data);
+
+ // Performs a deep copy on data.
+ SpdyDataIR(SpdyStreamId stream_id, const char* data);
+
+ // Moves data into data_store_. Makes a copy if passed a non-movable string.
+ SpdyDataIR(SpdyStreamId stream_id, SpdyString data);
+
+ // Use in conjunction with SetDataShallow() for shallow-copy on data.
+ explicit SpdyDataIR(SpdyStreamId stream_id);
+ SpdyDataIR(const SpdyDataIR&) = delete;
+ SpdyDataIR& operator=(const SpdyDataIR&) = delete;
+
+ ~SpdyDataIR() override;
+
+ const char* data() const { return data_; }
+ size_t data_len() const { return data_len_; }
+
+ bool padded() const { return padded_; }
+
+ int padding_payload_len() const { return padding_payload_len_; }
+
+ void set_padding_len(int padding_len) {
+ DCHECK_GT(padding_len, 0);
+ DCHECK_LE(padding_len, kPaddingSizePerFrame);
+ padded_ = true;
+ // The pad field takes one octet on the wire.
+ padding_payload_len_ = padding_len - 1;
+ }
+
+ // Deep-copy of data (keep private copy).
+ void SetDataDeep(SpdyStringPiece data) {
+ data_store_ = SpdyMakeUnique<SpdyString>(data.data(), data.size());
+ data_ = data_store_->data();
+ data_len_ = data.size();
+ }
+
+ // Shallow-copy of data (do not keep private copy).
+ void SetDataShallow(SpdyStringPiece data) {
+ data_store_.reset();
+ data_ = data.data();
+ data_len_ = data.size();
+ }
+
+ // Use this method if we don't have a contiguous buffer and only
+ // need a length.
+ void SetDataShallow(size_t len) {
+ data_store_.reset();
+ data_ = nullptr;
+ data_len_ = len;
+ }
+
+ void Visit(SpdyFrameVisitor* visitor) const override;
+
+ SpdyFrameType frame_type() const override;
+
+ int flow_control_window_consumed() const override;
+
+ size_t size() const override;
+
+ private:
+ // Used to store data that this SpdyDataIR should own.
+ std::unique_ptr<SpdyString> data_store_;
+ const char* data_;
+ size_t data_len_;
+
+ bool padded_;
+ // padding_payload_len_ = desired padding length - len(padding length field).
+ int padding_payload_len_;
+};
+
+class SPDY_EXPORT_PRIVATE SpdyRstStreamIR : public SpdyFrameIR {
+ public:
+ SpdyRstStreamIR(SpdyStreamId stream_id, SpdyErrorCode error_code);
+ SpdyRstStreamIR(const SpdyRstStreamIR&) = delete;
+ SpdyRstStreamIR& operator=(const SpdyRstStreamIR&) = delete;
+
+ ~SpdyRstStreamIR() override;
+
+ SpdyErrorCode error_code() const { return error_code_; }
+ void set_error_code(SpdyErrorCode error_code) { error_code_ = error_code; }
+
+ void Visit(SpdyFrameVisitor* visitor) const override;
+
+ SpdyFrameType frame_type() const override;
+
+ size_t size() const override;
+
+ private:
+ SpdyErrorCode error_code_;
+};
+
+class SPDY_EXPORT_PRIVATE SpdySettingsIR : public SpdyFrameIR {
+ public:
+ SpdySettingsIR();
+ SpdySettingsIR(const SpdySettingsIR&) = delete;
+ SpdySettingsIR& operator=(const SpdySettingsIR&) = delete;
+ ~SpdySettingsIR() override;
+
+ // Overwrites as appropriate.
+ const SettingsMap& values() const { return values_; }
+ void AddSetting(SpdySettingsId id, int32_t value) { values_[id] = value; }
+
+ bool is_ack() const { return is_ack_; }
+ void set_is_ack(bool is_ack) { is_ack_ = is_ack; }
+
+ void Visit(SpdyFrameVisitor* visitor) const override;
+
+ SpdyFrameType frame_type() const override;
+
+ size_t size() const override;
+
+ private:
+ SettingsMap values_;
+ bool is_ack_;
+};
+
+class SPDY_EXPORT_PRIVATE SpdyPingIR : public SpdyFrameIR {
+ public:
+ explicit SpdyPingIR(SpdyPingId id) : id_(id), is_ack_(false) {}
+ SpdyPingIR(const SpdyPingIR&) = delete;
+ SpdyPingIR& operator=(const SpdyPingIR&) = delete;
+ SpdyPingId id() const { return id_; }
+
+ bool is_ack() const { return is_ack_; }
+ void set_is_ack(bool is_ack) { is_ack_ = is_ack; }
+
+ void Visit(SpdyFrameVisitor* visitor) const override;
+
+ SpdyFrameType frame_type() const override;
+
+ size_t size() const override;
+
+ private:
+ SpdyPingId id_;
+ bool is_ack_;
+};
+
+class SPDY_EXPORT_PRIVATE SpdyGoAwayIR : public SpdyFrameIR {
+ public:
+ // References description, doesn't copy it, so description must outlast
+ // this SpdyGoAwayIR.
+ SpdyGoAwayIR(SpdyStreamId last_good_stream_id,
+ SpdyErrorCode error_code,
+ SpdyStringPiece description);
+
+ // References description, doesn't copy it, so description must outlast
+ // this SpdyGoAwayIR.
+ SpdyGoAwayIR(SpdyStreamId last_good_stream_id,
+ SpdyErrorCode error_code,
+ const char* description);
+
+ // Moves description into description_store_, so caller doesn't need to
+ // keep description live after constructing this SpdyGoAwayIR.
+ SpdyGoAwayIR(SpdyStreamId last_good_stream_id,
+ SpdyErrorCode error_code,
+ SpdyString description);
+ SpdyGoAwayIR(const SpdyGoAwayIR&) = delete;
+ SpdyGoAwayIR& operator=(const SpdyGoAwayIR&) = delete;
+
+ ~SpdyGoAwayIR() override;
+
+ SpdyStreamId last_good_stream_id() const { return last_good_stream_id_; }
+ void set_last_good_stream_id(SpdyStreamId last_good_stream_id) {
+ DCHECK_EQ(0u, last_good_stream_id & ~kStreamIdMask);
+ last_good_stream_id_ = last_good_stream_id;
+ }
+ SpdyErrorCode error_code() const { return error_code_; }
+ void set_error_code(SpdyErrorCode error_code) {
+ // TODO(hkhalil): Check valid ranges of error_code?
+ error_code_ = error_code;
+ }
+
+ const SpdyStringPiece& description() const { return description_; }
+
+ void Visit(SpdyFrameVisitor* visitor) const override;
+
+ SpdyFrameType frame_type() const override;
+
+ size_t size() const override;
+
+ private:
+ SpdyStreamId last_good_stream_id_;
+ SpdyErrorCode error_code_;
+ const SpdyString description_store_;
+ const SpdyStringPiece description_;
+};
+
+class SPDY_EXPORT_PRIVATE SpdyHeadersIR : public SpdyFrameWithHeaderBlockIR {
+ public:
+ explicit SpdyHeadersIR(SpdyStreamId stream_id)
+ : SpdyHeadersIR(stream_id, SpdyHeaderBlock()) {}
+ SpdyHeadersIR(SpdyStreamId stream_id, SpdyHeaderBlock header_block)
+ : SpdyFrameWithHeaderBlockIR(stream_id, std::move(header_block)) {}
+ SpdyHeadersIR(const SpdyHeadersIR&) = delete;
+ SpdyHeadersIR& operator=(const SpdyHeadersIR&) = delete;
+
+ void Visit(SpdyFrameVisitor* visitor) const override;
+
+ SpdyFrameType frame_type() const override;
+
+ size_t size() const override;
+
+ bool has_priority() const { return has_priority_; }
+ void set_has_priority(bool has_priority) { has_priority_ = has_priority; }
+ int weight() const { return weight_; }
+ void set_weight(int weight) { weight_ = weight; }
+ SpdyStreamId parent_stream_id() const { return parent_stream_id_; }
+ void set_parent_stream_id(SpdyStreamId id) { parent_stream_id_ = id; }
+ bool exclusive() const { return exclusive_; }
+ void set_exclusive(bool exclusive) { exclusive_ = exclusive; }
+ bool padded() const { return padded_; }
+ int padding_payload_len() const { return padding_payload_len_; }
+ void set_padding_len(int padding_len) {
+ DCHECK_GT(padding_len, 0);
+ DCHECK_LE(padding_len, kPaddingSizePerFrame);
+ padded_ = true;
+ // The pad field takes one octet on the wire.
+ padding_payload_len_ = padding_len - 1;
+ }
+
+ private:
+ bool has_priority_ = false;
+ int weight_ = kHttp2DefaultStreamWeight;
+ SpdyStreamId parent_stream_id_ = 0;
+ bool exclusive_ = false;
+ bool padded_ = false;
+ int padding_payload_len_ = 0;
+};
+
+class SPDY_EXPORT_PRIVATE SpdyWindowUpdateIR : public SpdyFrameIR {
+ public:
+ SpdyWindowUpdateIR(SpdyStreamId stream_id, int32_t delta)
+ : SpdyFrameIR(stream_id) {
+ set_delta(delta);
+ }
+ SpdyWindowUpdateIR(const SpdyWindowUpdateIR&) = delete;
+ SpdyWindowUpdateIR& operator=(const SpdyWindowUpdateIR&) = delete;
+
+ int32_t delta() const { return delta_; }
+ void set_delta(int32_t delta) {
+ DCHECK_LE(0, delta);
+ DCHECK_LE(delta, kSpdyMaximumWindowSize);
+ delta_ = delta;
+ }
+
+ void Visit(SpdyFrameVisitor* visitor) const override;
+
+ SpdyFrameType frame_type() const override;
+
+ size_t size() const override;
+
+ private:
+ int32_t delta_;
+};
+
+class SPDY_EXPORT_PRIVATE SpdyPushPromiseIR
+ : public SpdyFrameWithHeaderBlockIR {
+ public:
+ SpdyPushPromiseIR(SpdyStreamId stream_id, SpdyStreamId promised_stream_id)
+ : SpdyPushPromiseIR(stream_id, promised_stream_id, SpdyHeaderBlock()) {}
+ SpdyPushPromiseIR(SpdyStreamId stream_id,
+ SpdyStreamId promised_stream_id,
+ SpdyHeaderBlock header_block)
+ : SpdyFrameWithHeaderBlockIR(stream_id, std::move(header_block)),
+ promised_stream_id_(promised_stream_id),
+ padded_(false),
+ padding_payload_len_(0) {}
+ SpdyPushPromiseIR(const SpdyPushPromiseIR&) = delete;
+ SpdyPushPromiseIR& operator=(const SpdyPushPromiseIR&) = delete;
+ SpdyStreamId promised_stream_id() const { return promised_stream_id_; }
+
+ void Visit(SpdyFrameVisitor* visitor) const override;
+
+ SpdyFrameType frame_type() const override;
+
+ size_t size() const override;
+
+ bool padded() const { return padded_; }
+ int padding_payload_len() const { return padding_payload_len_; }
+ void set_padding_len(int padding_len) {
+ DCHECK_GT(padding_len, 0);
+ DCHECK_LE(padding_len, kPaddingSizePerFrame);
+ padded_ = true;
+ // The pad field takes one octet on the wire.
+ padding_payload_len_ = padding_len - 1;
+ }
+
+ private:
+ SpdyStreamId promised_stream_id_;
+
+ bool padded_;
+ int padding_payload_len_;
+};
+
+class SPDY_EXPORT_PRIVATE SpdyContinuationIR : public SpdyFrameIR {
+ public:
+ explicit SpdyContinuationIR(SpdyStreamId stream_id);
+ SpdyContinuationIR(const SpdyContinuationIR&) = delete;
+ SpdyContinuationIR& operator=(const SpdyContinuationIR&) = delete;
+ ~SpdyContinuationIR() override;
+
+ void Visit(SpdyFrameVisitor* visitor) const override;
+
+ SpdyFrameType frame_type() const override;
+
+ bool end_headers() const { return end_headers_; }
+ void set_end_headers(bool end_headers) { end_headers_ = end_headers; }
+ const SpdyString& encoding() const { return *encoding_; }
+ void take_encoding(std::unique_ptr<SpdyString> encoding) {
+ encoding_ = std::move(encoding);
+ }
+ size_t size() const override;
+
+ private:
+ std::unique_ptr<SpdyString> encoding_;
+ bool end_headers_;
+};
+
+class SPDY_EXPORT_PRIVATE SpdyAltSvcIR : public SpdyFrameIR {
+ public:
+ explicit SpdyAltSvcIR(SpdyStreamId stream_id);
+ SpdyAltSvcIR(const SpdyAltSvcIR&) = delete;
+ SpdyAltSvcIR& operator=(const SpdyAltSvcIR&) = delete;
+ ~SpdyAltSvcIR() override;
+
+ SpdyString origin() const { return origin_; }
+ const SpdyAltSvcWireFormat::AlternativeServiceVector& altsvc_vector() const {
+ return altsvc_vector_;
+ }
+
+ void set_origin(SpdyString origin) { origin_ = std::move(origin); }
+ void add_altsvc(const SpdyAltSvcWireFormat::AlternativeService& altsvc) {
+ altsvc_vector_.push_back(altsvc);
+ }
+
+ void Visit(SpdyFrameVisitor* visitor) const override;
+
+ SpdyFrameType frame_type() const override;
+
+ size_t size() const override;
+
+ private:
+ SpdyString origin_;
+ SpdyAltSvcWireFormat::AlternativeServiceVector altsvc_vector_;
+};
+
+class SPDY_EXPORT_PRIVATE SpdyPriorityIR : public SpdyFrameIR {
+ public:
+ SpdyPriorityIR(SpdyStreamId stream_id,
+ SpdyStreamId parent_stream_id,
+ int weight,
+ bool exclusive)
+ : SpdyFrameIR(stream_id),
+ parent_stream_id_(parent_stream_id),
+ weight_(weight),
+ exclusive_(exclusive) {}
+ SpdyPriorityIR(const SpdyPriorityIR&) = delete;
+ SpdyPriorityIR& operator=(const SpdyPriorityIR&) = delete;
+ SpdyStreamId parent_stream_id() const { return parent_stream_id_; }
+ int weight() const { return weight_; }
+ bool exclusive() const { return exclusive_; }
+
+ void Visit(SpdyFrameVisitor* visitor) const override;
+
+ SpdyFrameType frame_type() const override;
+
+ size_t size() const override;
+
+ private:
+ SpdyStreamId parent_stream_id_;
+ int weight_;
+ bool exclusive_;
+};
+
+// Represents a frame of unrecognized type.
+class SPDY_EXPORT_PRIVATE SpdyUnknownIR : public SpdyFrameIR {
+ public:
+ SpdyUnknownIR(SpdyStreamId stream_id,
+ uint8_t type,
+ uint8_t flags,
+ SpdyString payload)
+ : SpdyFrameIR(stream_id),
+ type_(type),
+ flags_(flags),
+ length_(payload.size()),
+ payload_(std::move(payload)) {}
+ SpdyUnknownIR(const SpdyUnknownIR&) = delete;
+ SpdyUnknownIR& operator=(const SpdyUnknownIR&) = delete;
+ uint8_t type() const { return type_; }
+ uint8_t flags() const { return flags_; }
+ size_t length() const { return length_; }
+ const SpdyString& payload() const { return payload_; }
+
+ void Visit(SpdyFrameVisitor* visitor) const override;
+
+ SpdyFrameType frame_type() const override;
+
+ int flow_control_window_consumed() const override;
+
+ size_t size() const override;
+
+ protected:
+ // Allows subclasses to overwrite the default payload length.
+ void set_length(size_t length) { length_ = length; }
+
+ private:
+ uint8_t type_;
+ uint8_t flags_;
+ size_t length_;
+ const SpdyString payload_;
+};
+
+class SPDY_EXPORT_PRIVATE SpdySerializedFrame {
+ public:
+ SpdySerializedFrame()
+ : frame_(const_cast<char*>("")), size_(0), owns_buffer_(false) {}
+
+ // Create a valid SpdySerializedFrame using a pre-created buffer.
+ // If |owns_buffer| is true, this class takes ownership of the buffer and will
+ // delete it on cleanup. The buffer must have been created using new char[].
+ // If |owns_buffer| is false, the caller retains ownership of the buffer and
+ // is responsible for making sure the buffer outlives this frame. In other
+ // words, this class does NOT create a copy of the buffer.
+ SpdySerializedFrame(char* data, size_t size, bool owns_buffer)
+ : frame_(data), size_(size), owns_buffer_(owns_buffer) {}
+
+ SpdySerializedFrame(SpdySerializedFrame&& other)
+ : frame_(other.frame_),
+ size_(other.size_),
+ owns_buffer_(other.owns_buffer_) {
+ // |other| is no longer responsible for the buffer.
+ other.owns_buffer_ = false;
+ }
+ SpdySerializedFrame(const SpdySerializedFrame&) = delete;
+ SpdySerializedFrame& operator=(const SpdySerializedFrame&) = delete;
+
+ SpdySerializedFrame& operator=(SpdySerializedFrame&& other) {
+ // Free buffer if necessary.
+ if (owns_buffer_) {
+ delete[] frame_;
+ }
+ // Take over |other|.
+ frame_ = other.frame_;
+ size_ = other.size_;
+ owns_buffer_ = other.owns_buffer_;
+ // |other| is no longer responsible for the buffer.
+ other.owns_buffer_ = false;
+ return *this;
+ }
+
+ ~SpdySerializedFrame() {
+ if (owns_buffer_) {
+ delete[] frame_;
+ }
+ }
+
+ // Provides access to the frame bytes, which is a buffer containing the frame
+ // packed as expected for sending over the wire.
+ char* data() const { return frame_; }
+
+ // Returns the actual size of the underlying buffer.
+ size_t size() const { return size_; }
+
+ // Returns a buffer containing the contents of the frame, of which the caller
+ // takes ownership, and clears this SpdySerializedFrame.
+ char* ReleaseBuffer() {
+ char* buffer;
+ if (owns_buffer_) {
+ // If the buffer is owned, relinquish ownership to the caller.
+ buffer = frame_;
+ owns_buffer_ = false;
+ } else {
+ // Otherwise, we need to make a copy to give to the caller.
+ buffer = new char[size_];
+ memcpy(buffer, frame_, size_);
+ }
+ *this = SpdySerializedFrame();
+ return buffer;
+ }
+
+ // Returns the estimate of dynamically allocated memory in bytes.
+ size_t EstimateMemoryUsage() const { return owns_buffer_ ? size_ : 0; }
+
+ protected:
+ char* frame_;
+
+ private:
+ size_t size_;
+ bool owns_buffer_;
+};
+
+// This interface is for classes that want to process SpdyFrameIRs without
+// having to know what type they are. An instance of this interface can be
+// passed to a SpdyFrameIR's Visit method, and the appropriate type-specific
+// method of this class will be called.
+class SPDY_EXPORT_PRIVATE SpdyFrameVisitor {
+ public:
+ virtual void VisitRstStream(const SpdyRstStreamIR& rst_stream) = 0;
+ virtual void VisitSettings(const SpdySettingsIR& settings) = 0;
+ virtual void VisitPing(const SpdyPingIR& ping) = 0;
+ virtual void VisitGoAway(const SpdyGoAwayIR& goaway) = 0;
+ virtual void VisitHeaders(const SpdyHeadersIR& headers) = 0;
+ virtual void VisitWindowUpdate(const SpdyWindowUpdateIR& window_update) = 0;
+ virtual void VisitPushPromise(const SpdyPushPromiseIR& push_promise) = 0;
+ virtual void VisitContinuation(const SpdyContinuationIR& continuation) = 0;
+ virtual void VisitAltSvc(const SpdyAltSvcIR& altsvc) = 0;
+ virtual void VisitPriority(const SpdyPriorityIR& priority) = 0;
+ virtual void VisitData(const SpdyDataIR& data) = 0;
+ virtual void VisitUnknown(const SpdyUnknownIR& unknown) {
+ // TODO(birenroy): make abstract.
+ }
+
+ protected:
+ SpdyFrameVisitor() {}
+ SpdyFrameVisitor(const SpdyFrameVisitor&) = delete;
+ SpdyFrameVisitor& operator=(const SpdyFrameVisitor&) = delete;
+ virtual ~SpdyFrameVisitor() {}
+};
+
+// Optionally, and in addition to SpdyFramerVisitorInterface, a class supporting
+// SpdyFramerDebugVisitorInterface may be used in conjunction with SpdyFramer in
+// order to extract debug/internal information about the SpdyFramer as it
+// operates.
+//
+// Most HTTP2 implementations need not bother with this interface at all.
+class SPDY_EXPORT_PRIVATE SpdyFramerDebugVisitorInterface {
+ public:
+ virtual ~SpdyFramerDebugVisitorInterface() {}
+
+ // Called after compressing a frame with a payload of
+ // a list of name-value pairs.
+ // |payload_len| is the uncompressed payload size.
+ // |frame_len| is the compressed frame size.
+ virtual void OnSendCompressedFrame(SpdyStreamId stream_id,
+ SpdyFrameType type,
+ size_t payload_len,
+ size_t frame_len) {}
+
+ // Called when a frame containing a compressed payload of
+ // name-value pairs is received.
+ // |frame_len| is the compressed frame size.
+ virtual void OnReceiveCompressedFrame(SpdyStreamId stream_id,
+ SpdyFrameType type,
+ size_t frame_len) {}
+};
+
+// Calculates the number of bytes required to serialize a SpdyHeadersIR, not
+// including the bytes to be used for the encoded header set.
+size_t GetHeaderFrameSizeSansBlock(const SpdyHeadersIR& header_ir);
+
+// Calculates the number of bytes required to serialize a SpdyPushPromiseIR,
+// not including the bytes to be used for the encoded header set.
+size_t GetPushPromiseFrameSizeSansBlock(
+ const SpdyPushPromiseIR& push_promise_ir);
+
+} // namespace spdy
+
+#endif // QUICHE_SPDY_CORE_SPDY_PROTOCOL_H_
diff --git a/chromium/net/third_party/quiche/src/spdy/core/spdy_protocol_test.cc b/chromium/net/third_party/quiche/src/spdy/core/spdy_protocol_test.cc
new file mode 100644
index 00000000000..09a56cbe4f4
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/spdy/core/spdy_protocol_test.cc
@@ -0,0 +1,270 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/spdy/core/spdy_protocol.h"
+
+#include <iostream>
+#include <limits>
+#include <memory>
+
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_bitmasks.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_test_utils.h"
+
+namespace spdy {
+
+std::ostream& operator<<(std::ostream& os,
+ const SpdyStreamPrecedence precedence) {
+ if (precedence.is_spdy3_priority()) {
+ os << "SpdyStreamPrecedence[spdy3_priority=" << precedence.spdy3_priority()
+ << "]";
+ } else {
+ os << "SpdyStreamPrecedence[parent_id=" << precedence.parent_id()
+ << ", weight=" << precedence.weight()
+ << ", is_exclusive=" << precedence.is_exclusive() << "]";
+ }
+ return os;
+}
+
+namespace test {
+
+TEST(SpdyProtocolTest, ClampSpdy3Priority) {
+ EXPECT_SPDY_BUG(EXPECT_EQ(7, ClampSpdy3Priority(8)), "Invalid priority: 8");
+ EXPECT_EQ(kV3LowestPriority, ClampSpdy3Priority(kV3LowestPriority));
+ EXPECT_EQ(kV3HighestPriority, ClampSpdy3Priority(kV3HighestPriority));
+}
+
+TEST(SpdyProtocolTest, ClampHttp2Weight) {
+ EXPECT_SPDY_BUG(EXPECT_EQ(kHttp2MinStreamWeight, ClampHttp2Weight(0)),
+ "Invalid weight: 0");
+ EXPECT_SPDY_BUG(EXPECT_EQ(kHttp2MaxStreamWeight, ClampHttp2Weight(300)),
+ "Invalid weight: 300");
+ EXPECT_EQ(kHttp2MinStreamWeight, ClampHttp2Weight(kHttp2MinStreamWeight));
+ EXPECT_EQ(kHttp2MaxStreamWeight, ClampHttp2Weight(kHttp2MaxStreamWeight));
+}
+
+TEST(SpdyProtocolTest, Spdy3PriorityToHttp2Weight) {
+ EXPECT_EQ(256, Spdy3PriorityToHttp2Weight(0));
+ EXPECT_EQ(220, Spdy3PriorityToHttp2Weight(1));
+ EXPECT_EQ(183, Spdy3PriorityToHttp2Weight(2));
+ EXPECT_EQ(147, Spdy3PriorityToHttp2Weight(3));
+ EXPECT_EQ(110, Spdy3PriorityToHttp2Weight(4));
+ EXPECT_EQ(74, Spdy3PriorityToHttp2Weight(5));
+ EXPECT_EQ(37, Spdy3PriorityToHttp2Weight(6));
+ EXPECT_EQ(1, Spdy3PriorityToHttp2Weight(7));
+}
+
+TEST(SpdyProtocolTest, Http2WeightToSpdy3Priority) {
+ EXPECT_EQ(0u, Http2WeightToSpdy3Priority(256));
+ EXPECT_EQ(0u, Http2WeightToSpdy3Priority(221));
+ EXPECT_EQ(1u, Http2WeightToSpdy3Priority(220));
+ EXPECT_EQ(1u, Http2WeightToSpdy3Priority(184));
+ EXPECT_EQ(2u, Http2WeightToSpdy3Priority(183));
+ EXPECT_EQ(2u, Http2WeightToSpdy3Priority(148));
+ EXPECT_EQ(3u, Http2WeightToSpdy3Priority(147));
+ EXPECT_EQ(3u, Http2WeightToSpdy3Priority(111));
+ EXPECT_EQ(4u, Http2WeightToSpdy3Priority(110));
+ EXPECT_EQ(4u, Http2WeightToSpdy3Priority(75));
+ EXPECT_EQ(5u, Http2WeightToSpdy3Priority(74));
+ EXPECT_EQ(5u, Http2WeightToSpdy3Priority(38));
+ EXPECT_EQ(6u, Http2WeightToSpdy3Priority(37));
+ EXPECT_EQ(6u, Http2WeightToSpdy3Priority(2));
+ EXPECT_EQ(7u, Http2WeightToSpdy3Priority(1));
+}
+
+TEST(SpdyProtocolTest, IsValidHTTP2FrameStreamId) {
+ // Stream-specific frames must have non-zero stream ids
+ EXPECT_TRUE(IsValidHTTP2FrameStreamId(1, SpdyFrameType::DATA));
+ EXPECT_FALSE(IsValidHTTP2FrameStreamId(0, SpdyFrameType::DATA));
+ EXPECT_TRUE(IsValidHTTP2FrameStreamId(1, SpdyFrameType::HEADERS));
+ EXPECT_FALSE(IsValidHTTP2FrameStreamId(0, SpdyFrameType::HEADERS));
+ EXPECT_TRUE(IsValidHTTP2FrameStreamId(1, SpdyFrameType::PRIORITY));
+ EXPECT_FALSE(IsValidHTTP2FrameStreamId(0, SpdyFrameType::PRIORITY));
+ EXPECT_TRUE(IsValidHTTP2FrameStreamId(1, SpdyFrameType::RST_STREAM));
+ EXPECT_FALSE(IsValidHTTP2FrameStreamId(0, SpdyFrameType::RST_STREAM));
+ EXPECT_TRUE(IsValidHTTP2FrameStreamId(1, SpdyFrameType::CONTINUATION));
+ EXPECT_FALSE(IsValidHTTP2FrameStreamId(0, SpdyFrameType::CONTINUATION));
+ EXPECT_TRUE(IsValidHTTP2FrameStreamId(1, SpdyFrameType::PUSH_PROMISE));
+ EXPECT_FALSE(IsValidHTTP2FrameStreamId(0, SpdyFrameType::PUSH_PROMISE));
+
+ // Connection-level frames must have zero stream ids
+ EXPECT_FALSE(IsValidHTTP2FrameStreamId(1, SpdyFrameType::GOAWAY));
+ EXPECT_TRUE(IsValidHTTP2FrameStreamId(0, SpdyFrameType::GOAWAY));
+ EXPECT_FALSE(IsValidHTTP2FrameStreamId(1, SpdyFrameType::SETTINGS));
+ EXPECT_TRUE(IsValidHTTP2FrameStreamId(0, SpdyFrameType::SETTINGS));
+ EXPECT_FALSE(IsValidHTTP2FrameStreamId(1, SpdyFrameType::PING));
+ EXPECT_TRUE(IsValidHTTP2FrameStreamId(0, SpdyFrameType::PING));
+
+ // Frames that are neither stream-specific nor connection-level
+ // should not have their stream id declared invalid
+ EXPECT_TRUE(IsValidHTTP2FrameStreamId(1, SpdyFrameType::WINDOW_UPDATE));
+ EXPECT_TRUE(IsValidHTTP2FrameStreamId(0, SpdyFrameType::WINDOW_UPDATE));
+}
+
+TEST(SpdyProtocolTest, ParseSettingsId) {
+ SpdyKnownSettingsId setting_id;
+ EXPECT_FALSE(ParseSettingsId(0, &setting_id));
+ EXPECT_TRUE(ParseSettingsId(1, &setting_id));
+ EXPECT_EQ(SETTINGS_HEADER_TABLE_SIZE, setting_id);
+ EXPECT_TRUE(ParseSettingsId(2, &setting_id));
+ EXPECT_EQ(SETTINGS_ENABLE_PUSH, setting_id);
+ EXPECT_TRUE(ParseSettingsId(3, &setting_id));
+ EXPECT_EQ(SETTINGS_MAX_CONCURRENT_STREAMS, setting_id);
+ EXPECT_TRUE(ParseSettingsId(4, &setting_id));
+ EXPECT_EQ(SETTINGS_INITIAL_WINDOW_SIZE, setting_id);
+ EXPECT_TRUE(ParseSettingsId(5, &setting_id));
+ EXPECT_EQ(SETTINGS_MAX_FRAME_SIZE, setting_id);
+ EXPECT_TRUE(ParseSettingsId(6, &setting_id));
+ EXPECT_EQ(SETTINGS_MAX_HEADER_LIST_SIZE, setting_id);
+ EXPECT_FALSE(ParseSettingsId(7, &setting_id));
+ EXPECT_TRUE(ParseSettingsId(8, &setting_id));
+ EXPECT_EQ(SETTINGS_ENABLE_CONNECT_PROTOCOL, setting_id);
+ EXPECT_FALSE(ParseSettingsId(9, &setting_id));
+ EXPECT_FALSE(ParseSettingsId(0xFF44, &setting_id));
+ EXPECT_TRUE(ParseSettingsId(0xFF45, &setting_id));
+ EXPECT_EQ(SETTINGS_EXPERIMENT_SCHEDULER, setting_id);
+ EXPECT_FALSE(ParseSettingsId(0xFF46, &setting_id));
+}
+
+TEST(SpdyProtocolTest, SettingsIdToString) {
+ struct {
+ SpdySettingsId setting_id;
+ const SpdyString expected_string;
+ } test_cases[] = {
+ {0, "SETTINGS_UNKNOWN_0"},
+ {SETTINGS_HEADER_TABLE_SIZE, "SETTINGS_HEADER_TABLE_SIZE"},
+ {SETTINGS_ENABLE_PUSH, "SETTINGS_ENABLE_PUSH"},
+ {SETTINGS_MAX_CONCURRENT_STREAMS, "SETTINGS_MAX_CONCURRENT_STREAMS"},
+ {SETTINGS_INITIAL_WINDOW_SIZE, "SETTINGS_INITIAL_WINDOW_SIZE"},
+ {SETTINGS_MAX_FRAME_SIZE, "SETTINGS_MAX_FRAME_SIZE"},
+ {SETTINGS_MAX_HEADER_LIST_SIZE, "SETTINGS_MAX_HEADER_LIST_SIZE"},
+ {7, "SETTINGS_UNKNOWN_7"},
+ {SETTINGS_ENABLE_CONNECT_PROTOCOL, "SETTINGS_ENABLE_CONNECT_PROTOCOL"},
+ {9, "SETTINGS_UNKNOWN_9"},
+ {0xFF44, "SETTINGS_UNKNOWN_ff44"},
+ {0xFF45, "SETTINGS_EXPERIMENT_SCHEDULER"},
+ {0xFF46, "SETTINGS_UNKNOWN_ff46"}};
+ for (auto test_case : test_cases) {
+ EXPECT_EQ(test_case.expected_string,
+ SettingsIdToString(test_case.setting_id));
+ }
+}
+
+TEST(SpdyStreamPrecedenceTest, Basic) {
+ SpdyStreamPrecedence spdy3_prec(2);
+ EXPECT_TRUE(spdy3_prec.is_spdy3_priority());
+ EXPECT_EQ(2, spdy3_prec.spdy3_priority());
+ EXPECT_EQ(kHttp2RootStreamId, spdy3_prec.parent_id());
+ EXPECT_EQ(Spdy3PriorityToHttp2Weight(2), spdy3_prec.weight());
+ EXPECT_FALSE(spdy3_prec.is_exclusive());
+
+ for (bool is_exclusive : {true, false}) {
+ SpdyStreamPrecedence h2_prec(7, 123, is_exclusive);
+ EXPECT_FALSE(h2_prec.is_spdy3_priority());
+ EXPECT_EQ(Http2WeightToSpdy3Priority(123), h2_prec.spdy3_priority());
+ EXPECT_EQ(7u, h2_prec.parent_id());
+ EXPECT_EQ(123, h2_prec.weight());
+ EXPECT_EQ(is_exclusive, h2_prec.is_exclusive());
+ }
+}
+
+TEST(SpdyStreamPrecedenceTest, Clamping) {
+ EXPECT_SPDY_BUG(EXPECT_EQ(7, SpdyStreamPrecedence(8).spdy3_priority()),
+ "Invalid priority: 8");
+ EXPECT_SPDY_BUG(EXPECT_EQ(kHttp2MinStreamWeight,
+ SpdyStreamPrecedence(3, 0, false).weight()),
+ "Invalid weight: 0");
+ EXPECT_SPDY_BUG(EXPECT_EQ(kHttp2MaxStreamWeight,
+ SpdyStreamPrecedence(3, 300, false).weight()),
+ "Invalid weight: 300");
+}
+
+TEST(SpdyStreamPrecedenceTest, Copying) {
+ SpdyStreamPrecedence prec1(3);
+ SpdyStreamPrecedence copy1(prec1);
+ EXPECT_TRUE(copy1.is_spdy3_priority());
+ EXPECT_EQ(3, copy1.spdy3_priority());
+
+ SpdyStreamPrecedence prec2(4, 5, true);
+ SpdyStreamPrecedence copy2(prec2);
+ EXPECT_FALSE(copy2.is_spdy3_priority());
+ EXPECT_EQ(4u, copy2.parent_id());
+ EXPECT_EQ(5, copy2.weight());
+ EXPECT_TRUE(copy2.is_exclusive());
+
+ copy1 = prec2;
+ EXPECT_FALSE(copy1.is_spdy3_priority());
+ EXPECT_EQ(4u, copy1.parent_id());
+ EXPECT_EQ(5, copy1.weight());
+ EXPECT_TRUE(copy1.is_exclusive());
+
+ copy2 = prec1;
+ EXPECT_TRUE(copy2.is_spdy3_priority());
+ EXPECT_EQ(3, copy2.spdy3_priority());
+}
+
+TEST(SpdyStreamPrecedenceTest, Equals) {
+ EXPECT_EQ(SpdyStreamPrecedence(3), SpdyStreamPrecedence(3));
+ EXPECT_NE(SpdyStreamPrecedence(3), SpdyStreamPrecedence(4));
+
+ EXPECT_EQ(SpdyStreamPrecedence(1, 2, false),
+ SpdyStreamPrecedence(1, 2, false));
+ EXPECT_NE(SpdyStreamPrecedence(1, 2, false),
+ SpdyStreamPrecedence(2, 2, false));
+ EXPECT_NE(SpdyStreamPrecedence(1, 2, false),
+ SpdyStreamPrecedence(1, 3, false));
+ EXPECT_NE(SpdyStreamPrecedence(1, 2, false),
+ SpdyStreamPrecedence(1, 2, true));
+
+ SpdyStreamPrecedence spdy3_prec(3);
+ SpdyStreamPrecedence h2_prec(spdy3_prec.parent_id(), spdy3_prec.weight(),
+ spdy3_prec.is_exclusive());
+ EXPECT_NE(spdy3_prec, h2_prec);
+}
+
+TEST(SpdyDataIRTest, Construct) {
+ // Confirm that it makes a string of zero length from a
+ // SpdyStringPiece(nullptr).
+ SpdyStringPiece s1;
+ SpdyDataIR d1(/* stream_id = */ 1, s1);
+ EXPECT_EQ(0u, d1.data_len());
+ EXPECT_NE(nullptr, d1.data());
+
+ // Confirms makes a copy of char array.
+ const char s2[] = "something";
+ SpdyDataIR d2(/* stream_id = */ 2, s2);
+ EXPECT_EQ(SpdyStringPiece(d2.data(), d2.data_len()), s2);
+ EXPECT_NE(SpdyStringPiece(d1.data(), d1.data_len()), s2);
+ EXPECT_EQ((int)d1.data_len(), d1.flow_control_window_consumed());
+
+ // Confirm copies a const string.
+ const SpdyString foo = "foo";
+ SpdyDataIR d3(/* stream_id = */ 3, foo);
+ EXPECT_EQ(foo, d3.data());
+ EXPECT_EQ((int)d3.data_len(), d3.flow_control_window_consumed());
+
+ // Confirm copies a non-const string.
+ SpdyString bar = "bar";
+ SpdyDataIR d4(/* stream_id = */ 4, bar);
+ EXPECT_EQ("bar", bar);
+ EXPECT_EQ("bar", SpdyStringPiece(d4.data(), d4.data_len()));
+
+ // Confirm moves an rvalue reference. Note that the test string "baz" is too
+ // short to trigger the move optimization, and instead a copy occurs.
+ SpdyString baz = "the quick brown fox";
+ SpdyDataIR d5(/* stream_id = */ 5, std::move(baz));
+ EXPECT_EQ("", baz);
+ EXPECT_EQ(SpdyStringPiece(d5.data(), d5.data_len()), "the quick brown fox");
+
+ // Confirms makes a copy of string literal.
+ SpdyDataIR d7(/* stream_id = */ 7, "something else");
+ EXPECT_EQ(SpdyStringPiece(d7.data(), d7.data_len()), "something else");
+
+ SpdyDataIR d8(/* stream_id = */ 8, "shawarma");
+ d8.set_padding_len(20);
+ EXPECT_EQ(28, d8.flow_control_window_consumed());
+}
+
+} // namespace test
+} // namespace spdy
diff --git a/chromium/net/third_party/quiche/src/spdy/core/spdy_protocol_test_utils.cc b/chromium/net/third_party/quiche/src/spdy/core/spdy_protocol_test_utils.cc
new file mode 100644
index 00000000000..14d338502eb
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/spdy/core/spdy_protocol_test_utils.cc
@@ -0,0 +1,155 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/spdy/core/spdy_protocol_test_utils.h"
+
+#include <cstdint>
+
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_string_piece.h"
+
+namespace spdy {
+namespace test {
+
+// TODO(jamessynge): Where it makes sense in these functions, it would be nice
+// to make use of the existing gMock matchers here, instead of rolling our own.
+
+::testing::AssertionResult VerifySpdyFrameWithHeaderBlockIREquals(
+ const SpdyFrameWithHeaderBlockIR& expected,
+ const SpdyFrameWithHeaderBlockIR& actual) {
+ VLOG(1) << "VerifySpdyFrameWithHeaderBlockIREquals";
+ VERIFY_TRUE(actual.header_block() == expected.header_block());
+ return ::testing::AssertionSuccess();
+}
+
+::testing::AssertionResult VerifySpdyFrameIREquals(const SpdyAltSvcIR& expected,
+ const SpdyAltSvcIR& actual) {
+ VERIFY_EQ(expected.stream_id(), actual.stream_id());
+ VERIFY_EQ(expected.origin(), actual.origin());
+ VERIFY_THAT(actual.altsvc_vector(),
+ ::testing::ContainerEq(expected.altsvc_vector()));
+ return ::testing::AssertionSuccess();
+}
+
+::testing::AssertionResult VerifySpdyFrameIREquals(
+ const SpdyContinuationIR& expected,
+ const SpdyContinuationIR& actual) {
+ return ::testing::AssertionFailure()
+ << "VerifySpdyFrameIREquals SpdyContinuationIR not yet implemented";
+}
+
+::testing::AssertionResult VerifySpdyFrameIREquals(const SpdyDataIR& expected,
+ const SpdyDataIR& actual) {
+ VLOG(1) << "VerifySpdyFrameIREquals SpdyDataIR";
+ VERIFY_EQ(expected.stream_id(), actual.stream_id());
+ VERIFY_EQ(expected.fin(), actual.fin());
+ VERIFY_EQ(expected.data_len(), actual.data_len());
+ if (expected.data() == nullptr) {
+ VERIFY_EQ(nullptr, actual.data());
+ } else {
+ VERIFY_EQ(SpdyStringPiece(expected.data(), expected.data_len()),
+ SpdyStringPiece(actual.data(), actual.data_len()));
+ }
+ VERIFY_SUCCESS(VerifySpdyFrameWithPaddingIREquals(expected, actual));
+ return ::testing::AssertionSuccess();
+}
+
+::testing::AssertionResult VerifySpdyFrameIREquals(const SpdyGoAwayIR& expected,
+ const SpdyGoAwayIR& actual) {
+ VLOG(1) << "VerifySpdyFrameIREquals SpdyGoAwayIR";
+ VERIFY_EQ(expected.last_good_stream_id(), actual.last_good_stream_id());
+ VERIFY_EQ(expected.error_code(), actual.error_code());
+ VERIFY_EQ(expected.description(), actual.description());
+ return ::testing::AssertionSuccess();
+}
+
+::testing::AssertionResult VerifySpdyFrameIREquals(
+ const SpdyHeadersIR& expected,
+ const SpdyHeadersIR& actual) {
+ VLOG(1) << "VerifySpdyFrameIREquals SpdyHeadersIR";
+ VERIFY_EQ(expected.stream_id(), actual.stream_id());
+ VERIFY_EQ(expected.fin(), actual.fin());
+ VERIFY_SUCCESS(VerifySpdyFrameWithHeaderBlockIREquals(expected, actual));
+ VERIFY_EQ(expected.has_priority(), actual.has_priority());
+ if (expected.has_priority()) {
+ VERIFY_SUCCESS(VerifySpdyFrameWithPriorityIREquals(expected, actual));
+ }
+ VERIFY_SUCCESS(VerifySpdyFrameWithPaddingIREquals(expected, actual));
+ return ::testing::AssertionSuccess();
+}
+
+::testing::AssertionResult VerifySpdyFrameIREquals(const SpdyPingIR& expected,
+ const SpdyPingIR& actual) {
+ VLOG(1) << "VerifySpdyFrameIREquals SpdyPingIR";
+ VERIFY_EQ(expected.id(), actual.id());
+ VERIFY_EQ(expected.is_ack(), actual.is_ack());
+ return ::testing::AssertionSuccess();
+}
+
+::testing::AssertionResult VerifySpdyFrameIREquals(
+ const SpdyPriorityIR& expected,
+ const SpdyPriorityIR& actual) {
+ VLOG(1) << "VerifySpdyFrameIREquals SpdyPriorityIR";
+ VERIFY_EQ(expected.stream_id(), actual.stream_id());
+ VERIFY_SUCCESS(VerifySpdyFrameWithPriorityIREquals(expected, actual));
+ return ::testing::AssertionSuccess();
+}
+
+::testing::AssertionResult VerifySpdyFrameIREquals(
+ const SpdyPushPromiseIR& expected,
+ const SpdyPushPromiseIR& actual) {
+ VLOG(1) << "VerifySpdyFrameIREquals SpdyPushPromiseIR";
+ VERIFY_EQ(expected.stream_id(), actual.stream_id());
+ VERIFY_SUCCESS(VerifySpdyFrameWithPaddingIREquals(expected, actual));
+ VERIFY_EQ(expected.promised_stream_id(), actual.promised_stream_id());
+ VERIFY_SUCCESS(VerifySpdyFrameWithHeaderBlockIREquals(expected, actual));
+ return ::testing::AssertionSuccess();
+}
+
+::testing::AssertionResult VerifySpdyFrameIREquals(
+ const SpdyRstStreamIR& expected,
+ const SpdyRstStreamIR& actual) {
+ VLOG(1) << "VerifySpdyFrameIREquals SpdyRstStreamIR";
+ VERIFY_EQ(expected.stream_id(), actual.stream_id());
+ VERIFY_EQ(expected.error_code(), actual.error_code());
+ return ::testing::AssertionSuccess();
+}
+
+::testing::AssertionResult VerifySpdyFrameIREquals(
+ const SpdySettingsIR& expected,
+ const SpdySettingsIR& actual) {
+ VLOG(1) << "VerifySpdyFrameIREquals SpdySettingsIR";
+ // Note, ignoring non-HTTP/2 fields such as clear_settings.
+ VERIFY_EQ(expected.is_ack(), actual.is_ack());
+
+ // Note, the following doesn't work because there isn't a comparator and
+ // formatter for SpdySettingsIR::Value. Fixable if we cared.
+ //
+ // VERIFY_THAT(actual.values(), ::testing::ContainerEq(actual.values()));
+
+ VERIFY_EQ(expected.values().size(), actual.values().size());
+ for (const auto& entry : expected.values()) {
+ const auto& param = entry.first;
+ auto actual_itr = actual.values().find(param);
+ VERIFY_TRUE(!(actual_itr == actual.values().end()))
+ << "actual doesn't contain param: " << param;
+ uint32_t expected_value = entry.second;
+ uint32_t actual_value = actual_itr->second;
+ VERIFY_EQ(expected_value, actual_value)
+ << "Values don't match for parameter: " << param;
+ }
+
+ return ::testing::AssertionSuccess();
+}
+
+::testing::AssertionResult VerifySpdyFrameIREquals(
+ const SpdyWindowUpdateIR& expected,
+ const SpdyWindowUpdateIR& actual) {
+ VLOG(1) << "VerifySpdyFrameIREquals SpdyWindowUpdateIR";
+ VERIFY_EQ(expected.stream_id(), actual.stream_id());
+ VERIFY_EQ(expected.delta(), actual.delta());
+ return ::testing::AssertionSuccess();
+}
+
+} // namespace test
+} // namespace spdy
diff --git a/chromium/net/third_party/quiche/src/spdy/core/spdy_protocol_test_utils.h b/chromium/net/third_party/quiche/src/spdy/core/spdy_protocol_test_utils.h
new file mode 100644
index 00000000000..bbec5979337
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/spdy/core/spdy_protocol_test_utils.h
@@ -0,0 +1,149 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_SPDY_CORE_SPDY_PROTOCOL_TEST_UTILS_H_
+#define QUICHE_SPDY_CORE_SPDY_PROTOCOL_TEST_UTILS_H_
+
+// These functions support tests that need to compare two concrete SpdyFrameIR
+// instances for equality. They return AssertionResult, so they may be used as
+// follows:
+//
+// SomeSpdyFrameIRSubClass expected_ir(...);
+// std::unique_ptr<SpdyFrameIR> collected_frame;
+// ... some test code that may fill in collected_frame ...
+// ASSERT_TRUE(VerifySpdyFrameIREquals(expected_ir, collected_frame.get()));
+//
+// TODO(jamessynge): Where it makes sense in these functions, it would be nice
+// to make use of the existing gMock matchers here, instead of rolling our own.
+
+#include <typeinfo>
+
+#include "base/logging.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_protocol.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_test_utils.h"
+
+namespace spdy {
+namespace test {
+
+// Verify the header entries in two SpdyFrameWithHeaderBlockIR instances
+// are the same.
+::testing::AssertionResult VerifySpdyFrameWithHeaderBlockIREquals(
+ const SpdyFrameWithHeaderBlockIR& expected,
+ const SpdyFrameWithHeaderBlockIR& actual);
+
+// Verify that the padding in two frames of type T is the same.
+template <class T>
+::testing::AssertionResult VerifySpdyFrameWithPaddingIREquals(const T& expected,
+ const T& actual) {
+ VLOG(1) << "VerifySpdyFrameWithPaddingIREquals";
+ VERIFY_EQ(expected.padded(), actual.padded());
+ if (expected.padded()) {
+ VERIFY_EQ(expected.padding_payload_len(), actual.padding_payload_len());
+ }
+
+ return ::testing::AssertionSuccess();
+}
+
+// Verify the priority fields in two frames of type T are the same.
+template <class T>
+::testing::AssertionResult VerifySpdyFrameWithPriorityIREquals(
+ const T& expected,
+ const T& actual) {
+ VLOG(1) << "VerifySpdyFrameWithPriorityIREquals";
+ VERIFY_EQ(expected.parent_stream_id(), actual.parent_stream_id());
+ VERIFY_EQ(expected.weight(), actual.weight());
+ VERIFY_EQ(expected.exclusive(), actual.exclusive());
+ return ::testing::AssertionSuccess();
+}
+
+// Verify that two SpdyAltSvcIR frames are the same.
+::testing::AssertionResult VerifySpdyFrameIREquals(const SpdyAltSvcIR& expected,
+ const SpdyAltSvcIR& actual);
+
+// VerifySpdyFrameIREquals for SpdyContinuationIR frames isn't really needed
+// because we don't really make use of SpdyContinuationIR, instead creating
+// SpdyHeadersIR or SpdyPushPromiseIR with the pre-encoding form of the HPACK
+// block (i.e. we don't yet have a CONTINUATION frame).
+//
+// ::testing::AssertionResult VerifySpdyFrameIREquals(
+// const SpdyContinuationIR& expected,
+// const SpdyContinuationIR& actual) {
+// return ::testing::AssertionFailure()
+// << "VerifySpdyFrameIREquals SpdyContinuationIR NYI";
+// }
+
+// Verify that two SpdyDataIR frames are the same.
+::testing::AssertionResult VerifySpdyFrameIREquals(const SpdyDataIR& expected,
+ const SpdyDataIR& actual);
+
+// Verify that two SpdyGoAwayIR frames are the same.
+::testing::AssertionResult VerifySpdyFrameIREquals(const SpdyGoAwayIR& expected,
+ const SpdyGoAwayIR& actual);
+
+// Verify that two SpdyHeadersIR frames are the same.
+::testing::AssertionResult VerifySpdyFrameIREquals(
+ const SpdyHeadersIR& expected,
+ const SpdyHeadersIR& actual);
+
+// Verify that two SpdyPingIR frames are the same.
+::testing::AssertionResult VerifySpdyFrameIREquals(const SpdyPingIR& expected,
+ const SpdyPingIR& actual);
+
+// Verify that two SpdyPriorityIR frames are the same.
+::testing::AssertionResult VerifySpdyFrameIREquals(
+ const SpdyPriorityIR& expected,
+ const SpdyPriorityIR& actual);
+
+// Verify that two SpdyPushPromiseIR frames are the same.
+::testing::AssertionResult VerifySpdyFrameIREquals(
+ const SpdyPushPromiseIR& expected,
+ const SpdyPushPromiseIR& actual);
+
+// Verify that two SpdyRstStreamIR frames are the same.
+::testing::AssertionResult VerifySpdyFrameIREquals(
+ const SpdyRstStreamIR& expected,
+ const SpdyRstStreamIR& actual);
+
+// Verify that two SpdySettingsIR frames are the same.
+::testing::AssertionResult VerifySpdyFrameIREquals(
+ const SpdySettingsIR& expected,
+ const SpdySettingsIR& actual);
+
+// Verify that two SpdyWindowUpdateIR frames are the same.
+::testing::AssertionResult VerifySpdyFrameIREquals(
+ const SpdyWindowUpdateIR& expected,
+ const SpdyWindowUpdateIR& actual);
+
+// Verify that either expected and actual are both nullptr, or that both are not
+// nullptr, and that actual is of type E, and that it matches expected.
+template <class E>
+::testing::AssertionResult VerifySpdyFrameIREquals(const E* expected,
+ const SpdyFrameIR* actual) {
+ if (expected == nullptr || actual == nullptr) {
+ VLOG(1) << "VerifySpdyFrameIREquals one null";
+ VERIFY_EQ(expected, nullptr);
+ VERIFY_EQ(actual, nullptr);
+ return ::testing::AssertionSuccess();
+ }
+ VLOG(1) << "VerifySpdyFrameIREquals not null";
+ VERIFY_EQ(actual->frame_type(), expected->frame_type());
+ const E* actual2 = static_cast<const E*>(actual);
+ return VerifySpdyFrameIREquals(*expected, *actual2);
+}
+
+// Verify that actual is not nullptr, that it is of type E and that it
+// matches expected.
+template <class E>
+::testing::AssertionResult VerifySpdyFrameIREquals(const E& expected,
+ const SpdyFrameIR* actual) {
+ VLOG(1) << "VerifySpdyFrameIREquals";
+ return VerifySpdyFrameIREquals(&expected, actual);
+}
+
+} // namespace test
+} // namespace spdy
+
+#endif // QUICHE_SPDY_CORE_SPDY_PROTOCOL_TEST_UTILS_H_
diff --git a/chromium/net/third_party/quiche/src/spdy/core/spdy_test_utils.cc b/chromium/net/third_party/quiche/src/spdy/core/spdy_test_utils.cc
new file mode 100644
index 00000000000..dd6c417abeb
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/spdy/core/spdy_test_utils.cc
@@ -0,0 +1,119 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/spdy/core/spdy_test_utils.h"
+
+#include <algorithm>
+#include <cstring>
+#include <memory>
+#include <new>
+#include <utility>
+#include <vector>
+
+#include "base/logging.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_endianness_util.h"
+
+namespace spdy {
+namespace test {
+
+SpdyString HexDumpWithMarks(const unsigned char* data,
+ int length,
+ const bool* marks,
+ int mark_length) {
+ static const char kHexChars[] = "0123456789abcdef";
+ static const int kColumns = 4;
+
+ const int kSizeLimit = 1024;
+ if (length > kSizeLimit || mark_length > kSizeLimit) {
+ LOG(ERROR) << "Only dumping first " << kSizeLimit << " bytes.";
+ length = std::min(length, kSizeLimit);
+ mark_length = std::min(mark_length, kSizeLimit);
+ }
+
+ SpdyString hex;
+ for (const unsigned char* row = data; length > 0;
+ row += kColumns, length -= kColumns) {
+ for (const unsigned char* p = row; p < row + 4; ++p) {
+ if (p < row + length) {
+ const bool mark =
+ (marks && (p - data) < mark_length && marks[p - data]);
+ hex += mark ? '*' : ' ';
+ hex += kHexChars[(*p & 0xf0) >> 4];
+ hex += kHexChars[*p & 0x0f];
+ hex += mark ? '*' : ' ';
+ } else {
+ hex += " ";
+ }
+ }
+ hex = hex + " ";
+
+ for (const unsigned char* p = row; p < row + 4 && p < row + length; ++p) {
+ hex += (*p >= 0x20 && *p <= 0x7f) ? (*p) : '.';
+ }
+
+ hex = hex + '\n';
+ }
+ return hex;
+}
+
+void CompareCharArraysWithHexError(const SpdyString& description,
+ const unsigned char* actual,
+ const int actual_len,
+ const unsigned char* expected,
+ const int expected_len) {
+ const int min_len = std::min(actual_len, expected_len);
+ const int max_len = std::max(actual_len, expected_len);
+ std::unique_ptr<bool[]> marks(new bool[max_len]);
+ bool identical = (actual_len == expected_len);
+ for (int i = 0; i < min_len; ++i) {
+ if (actual[i] != expected[i]) {
+ marks[i] = true;
+ identical = false;
+ } else {
+ marks[i] = false;
+ }
+ }
+ for (int i = min_len; i < max_len; ++i) {
+ marks[i] = true;
+ }
+ if (identical)
+ return;
+ ADD_FAILURE() << "Description:\n"
+ << description << "\n\nExpected:\n"
+ << HexDumpWithMarks(expected, expected_len, marks.get(),
+ max_len)
+ << "\nActual:\n"
+ << HexDumpWithMarks(actual, actual_len, marks.get(), max_len);
+}
+
+void SetFrameFlags(SpdySerializedFrame* frame, uint8_t flags) {
+ frame->data()[4] = flags;
+}
+
+void SetFrameLength(SpdySerializedFrame* frame, size_t length) {
+ CHECK_GT(1u << 14, length);
+ {
+ int32_t wire_length = SpdyHostToNet32(length);
+ memcpy(frame->data(), reinterpret_cast<char*>(&wire_length) + 1, 3);
+ }
+}
+
+void TestHeadersHandler::OnHeaderBlockStart() {
+ block_.clear();
+}
+
+void TestHeadersHandler::OnHeader(SpdyStringPiece name, SpdyStringPiece value) {
+ block_.AppendValueOrAddHeader(name, value);
+}
+
+void TestHeadersHandler::OnHeaderBlockEnd(
+ size_t header_bytes_parsed,
+ size_t compressed_header_bytes_parsed) {
+ header_bytes_parsed_ = header_bytes_parsed;
+ compressed_header_bytes_parsed_ = compressed_header_bytes_parsed;
+}
+
+} // namespace test
+} // namespace spdy
diff --git a/chromium/net/third_party/quiche/src/spdy/core/spdy_test_utils.h b/chromium/net/third_party/quiche/src/spdy/core/spdy_test_utils.h
new file mode 100644
index 00000000000..bf62eeba930
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/spdy/core/spdy_test_utils.h
@@ -0,0 +1,74 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_SPDY_CORE_SPDY_TEST_UTILS_H_
+#define QUICHE_SPDY_CORE_SPDY_TEST_UTILS_H_
+
+#include <cstddef>
+#include <cstdint>
+
+#include "net/third_party/quiche/src/http2/platform/api/http2_test_helpers.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_header_block.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_headers_handler_interface.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_protocol.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_bug_tracker.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_string.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_string_piece.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_test_helpers.h"
+
+namespace spdy {
+
+inline bool operator==(SpdyStringPiece x,
+ const SpdyHeaderBlock::ValueProxy& y) {
+ return x == y.as_string();
+}
+
+namespace test {
+
+SpdyString HexDumpWithMarks(const unsigned char* data,
+ int length,
+ const bool* marks,
+ int mark_length);
+
+void CompareCharArraysWithHexError(const SpdyString& description,
+ const unsigned char* actual,
+ const int actual_len,
+ const unsigned char* expected,
+ const int expected_len);
+
+void SetFrameFlags(SpdySerializedFrame* frame, uint8_t flags);
+
+void SetFrameLength(SpdySerializedFrame* frame, size_t length);
+
+// A test implementation of SpdyHeadersHandlerInterface that correctly
+// reconstructs multiple header values for the same name.
+class TestHeadersHandler : public SpdyHeadersHandlerInterface {
+ public:
+ TestHeadersHandler() {}
+ TestHeadersHandler(const TestHeadersHandler&) = delete;
+ TestHeadersHandler& operator=(const TestHeadersHandler&) = delete;
+
+ void OnHeaderBlockStart() override;
+
+ void OnHeader(SpdyStringPiece name, SpdyStringPiece value) override;
+
+ void OnHeaderBlockEnd(size_t header_bytes_parsed,
+ size_t compressed_header_bytes_parsed) override;
+
+ const SpdyHeaderBlock& decoded_block() const { return block_; }
+ size_t header_bytes_parsed() const { return header_bytes_parsed_; }
+ size_t compressed_header_bytes_parsed() const {
+ return compressed_header_bytes_parsed_;
+ }
+
+ private:
+ SpdyHeaderBlock block_;
+ size_t header_bytes_parsed_ = 0;
+ size_t compressed_header_bytes_parsed_ = 0;
+};
+
+} // namespace test
+} // namespace spdy
+
+#endif // QUICHE_SPDY_CORE_SPDY_TEST_UTILS_H_
diff --git a/chromium/net/third_party/quiche/src/spdy/core/write_scheduler.h b/chromium/net/third_party/quiche/src/spdy/core/write_scheduler.h
new file mode 100644
index 00000000000..07e5fb32c60
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/spdy/core/write_scheduler.h
@@ -0,0 +1,156 @@
+// Copyright (c) 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_SPDY_CORE_WRITE_SCHEDULER_H_
+#define QUICHE_SPDY_CORE_WRITE_SCHEDULER_H_
+
+#include <cstdint>
+#include <tuple>
+#include <vector>
+
+#include "net/third_party/quiche/src/spdy/core/spdy_protocol.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_export.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_string.h"
+
+namespace spdy {
+
+// Abstract superclass for classes that decide which SPDY or HTTP/2 stream to
+// write next. Concrete subclasses implement various scheduling policies:
+//
+// PriorityWriteScheduler: implements SPDY priority-based stream scheduling,
+// where (writable) higher-priority streams are always given precedence
+// over lower-priority streams.
+//
+// Http2PriorityWriteScheduler: implements SPDY priority-based stream
+// scheduling coupled with the HTTP/2 stream dependency model. This is only
+// intended as a transitional step towards Http2WeightedWriteScheduler.
+//
+// Http2WeightedWriteScheduler (coming soon): implements the HTTP/2 stream
+// dependency model with weighted stream scheduling, fully conforming to
+// RFC 7540.
+//
+// The type used to represent stream IDs (StreamIdType) is templated in order
+// to allow for use by both SPDY and QUIC codebases. It must be a POD that
+// supports comparison (i.e., a numeric type).
+//
+// Each stream can be in one of two states: ready or not ready (for writing).
+// Ready state is changed by calling the MarkStreamReady() and
+// MarkStreamNotReady() methods. Only streams in the ready state can be
+// returned by PopNextReadyStream(); when returned by that method, the stream's
+// state changes to not ready.
+template <typename StreamIdType>
+class SPDY_EXPORT_PRIVATE WriteScheduler {
+ public:
+ typedef StreamPrecedence<StreamIdType> StreamPrecedenceType;
+
+ virtual ~WriteScheduler() {}
+
+ // Registers new stream |stream_id| with the scheduler, assigning it the
+ // given precedence. If the scheduler supports stream dependencies, the
+ // stream is inserted into the dependency tree under
+ // |precedence.parent_id()|.
+ //
+ // Preconditions: |stream_id| should be unregistered, and
+ // |precedence.parent_id()| should be registered or |kHttp2RootStreamId|.
+ virtual void RegisterStream(StreamIdType stream_id,
+ const StreamPrecedenceType& precedence) = 0;
+
+ // Unregisters the given stream from the scheduler, which will no longer keep
+ // state for it.
+ //
+ // Preconditions: |stream_id| should be registered.
+ virtual void UnregisterStream(StreamIdType stream_id) = 0;
+
+ // Returns true if the given stream is currently registered.
+ virtual bool StreamRegistered(StreamIdType stream_id) const = 0;
+
+ // Returns the precedence of the specified stream. If the scheduler supports
+ // stream dependencies, calling |parent_id()| on the return value returns the
+ // stream's parent, and calling |exclusive()| returns true iff the specified
+ // stream is an only child of the parent stream.
+ //
+ // Preconditions: |stream_id| should be registered.
+ virtual StreamPrecedenceType GetStreamPrecedence(
+ StreamIdType stream_id) const = 0;
+
+ // Updates the precedence of the given stream. If the scheduler supports
+ // stream dependencies, |stream_id|'s parent will be updated to be
+ // |precedence.parent_id()| if it is not already.
+ //
+ // Preconditions: |stream_id| should be unregistered, and
+ // |precedence.parent_id()| should be registered or |kHttp2RootStreamId|.
+ virtual void UpdateStreamPrecedence(
+ StreamIdType stream_id,
+ const StreamPrecedenceType& precedence) = 0;
+
+ // Returns child streams of the given stream, if any. If the scheduler
+ // doesn't support stream dependencies, returns an empty vector.
+ //
+ // Preconditions: |stream_id| should be registered.
+ virtual std::vector<StreamIdType> GetStreamChildren(
+ StreamIdType stream_id) const = 0;
+
+ // Records time (in microseconds) of a read/write event for the given
+ // stream.
+ //
+ // Preconditions: |stream_id| should be registered.
+ virtual void RecordStreamEventTime(StreamIdType stream_id,
+ int64_t now_in_usec) = 0;
+
+ // Returns time (in microseconds) of the last read/write event for a stream
+ // with higher priority than the priority of the given stream, or 0 if there
+ // is no such event.
+ //
+ // Preconditions: |stream_id| should be registered.
+ virtual int64_t GetLatestEventWithPrecedence(
+ StreamIdType stream_id) const = 0;
+
+ // If the scheduler has any ready streams, returns the next scheduled
+ // ready stream, in the process transitioning the stream from ready to not
+ // ready.
+ //
+ // Preconditions: |HasReadyStreams() == true|
+ virtual StreamIdType PopNextReadyStream() = 0;
+
+ // If the scheduler has any ready streams, returns the next scheduled
+ // ready stream and its priority, in the process transitioning the stream from
+ // ready to not ready.
+ //
+ // Preconditions: |HasReadyStreams() == true|
+ virtual std::tuple<StreamIdType, StreamPrecedenceType>
+ PopNextReadyStreamAndPrecedence() = 0;
+
+ // Returns true if there's another stream ahead of the given stream in the
+ // scheduling queue. This function can be called to see if the given stream
+ // should yield work to another stream.
+ //
+ // Preconditions: |stream_id| should be registered.
+ virtual bool ShouldYield(StreamIdType stream_id) const = 0;
+
+ // Marks the stream as ready to write. If the stream was already ready, does
+ // nothing. If add_to_front is true, the stream is scheduled ahead of other
+ // streams of the same priority/weight, otherwise it is scheduled behind them.
+ //
+ // Preconditions: |stream_id| should be registered.
+ virtual void MarkStreamReady(StreamIdType stream_id, bool add_to_front) = 0;
+
+ // Marks the stream as not ready to write. If the stream is not registered or
+ // not ready, does nothing.
+ //
+ // Preconditions: |stream_id| should be registered.
+ virtual void MarkStreamNotReady(StreamIdType stream_id) = 0;
+
+ // Returns true iff the scheduler has any ready streams.
+ virtual bool HasReadyStreams() const = 0;
+
+ // Returns the number of streams currently marked ready.
+ virtual size_t NumReadyStreams() const = 0;
+
+ // Returns summary of internal state, for logging/debugging.
+ virtual SpdyString DebugString() const = 0;
+};
+
+} // namespace spdy
+
+#endif // QUICHE_SPDY_CORE_WRITE_SCHEDULER_H_
diff --git a/chromium/net/third_party/quiche/src/spdy/core/zero_copy_output_buffer.h b/chromium/net/third_party/quiche/src/spdy/core/zero_copy_output_buffer.h
new file mode 100644
index 00000000000..3f35bab714d
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/spdy/core/zero_copy_output_buffer.h
@@ -0,0 +1,30 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_SPDY_CORE_ZERO_COPY_OUTPUT_BUFFER_H_
+#define QUICHE_SPDY_CORE_ZERO_COPY_OUTPUT_BUFFER_H_
+
+#include <cstdint>
+
+namespace spdy {
+
+class ZeroCopyOutputBuffer {
+ public:
+ virtual ~ZeroCopyOutputBuffer() {}
+
+ // Returns the next available segment of memory to write. Will always return
+ // the same segment until AdvanceWritePtr is called.
+ virtual void Next(char** data, int* size) = 0;
+
+ // After writing to a buffer returned from Next(), the caller should call
+ // this method to indicate how many bytes were written.
+ virtual void AdvanceWritePtr(int64_t count) = 0;
+
+ // Returns the available capacity of the buffer.
+ virtual uint64_t BytesFree() const = 0;
+};
+
+} // namespace spdy
+
+#endif // QUICHE_SPDY_CORE_ZERO_COPY_OUTPUT_BUFFER_H_
diff --git a/chromium/net/third_party/quiche/src/spdy/platform/api/spdy_arraysize.h b/chromium/net/third_party/quiche/src/spdy/platform/api/spdy_arraysize.h
new file mode 100644
index 00000000000..356051e4700
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/spdy/platform/api/spdy_arraysize.h
@@ -0,0 +1,12 @@
+// Copyright (c) 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_SPDY_PLATFORM_API_SPDY_ARRAYSIZE_H_
+#define QUICHE_SPDY_PLATFORM_API_SPDY_ARRAYSIZE_H_
+
+#include "net/spdy/platform/impl/spdy_arraysize_impl.h"
+
+#define SPDY_ARRAYSIZE(x) SPDY_ARRAYSIZE_IMPL(x)
+
+#endif // QUICHE_SPDY_PLATFORM_API_SPDY_ARRAYSIZE_H_
diff --git a/chromium/net/third_party/quiche/src/spdy/platform/api/spdy_bug_tracker.h b/chromium/net/third_party/quiche/src/spdy/platform/api/spdy_bug_tracker.h
new file mode 100644
index 00000000000..d750a21f976
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/spdy/platform/api/spdy_bug_tracker.h
@@ -0,0 +1,15 @@
+// Copyright (c) 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_SPDY_PLATFORM_API_SPDY_BUG_TRACKER_H_
+#define QUICHE_SPDY_PLATFORM_API_SPDY_BUG_TRACKER_H_
+
+#include "net/spdy/platform/impl/spdy_bug_tracker_impl.h"
+
+#define SPDY_BUG SPDY_BUG_IMPL
+#define SPDY_BUG_IF(condition) SPDY_BUG_IF_IMPL(condition)
+#define FLAGS_spdy_always_log_bugs_for_tests \
+ FLAGS_spdy_always_log_bugs_for_tests_impl
+
+#endif // QUICHE_SPDY_PLATFORM_API_SPDY_BUG_TRACKER_H_
diff --git a/chromium/net/third_party/quiche/src/spdy/platform/api/spdy_containers.h b/chromium/net/third_party/quiche/src/spdy/platform/api/spdy_containers.h
new file mode 100644
index 00000000000..e4f4c494eb4
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/spdy/platform/api/spdy_containers.h
@@ -0,0 +1,42 @@
+// Copyright (c) 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_SPDY_PLATFORM_API_SPDY_CONTAINERS_H_
+#define QUICHE_SPDY_PLATFORM_API_SPDY_CONTAINERS_H_
+
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_string_piece.h"
+#include "net/spdy/platform/impl/spdy_containers_impl.h"
+
+namespace spdy {
+
+template <typename KeyType>
+using SpdyHash = SpdyHashImpl<KeyType>;
+
+// SpdyHashMap does not guarantee pointer stability.
+template <typename KeyType,
+ typename ValueType,
+ typename Hash = SpdyHash<KeyType>>
+using SpdyHashMap = SpdyHashMapImpl<KeyType, ValueType, Hash>;
+
+// SpdyHashSet does not guarantee pointer stability.
+template <typename ElementType, typename Hasher, typename Eq>
+using SpdyHashSet = SpdyHashSetImpl<ElementType, Hasher, Eq>;
+
+// A map which offers insertion-ordered iteration.
+template <typename Key, typename Value, typename Hash = SpdyHash<Key>>
+using SpdyLinkedHashMap = SpdyLinkedHashMapImpl<Key, Value, Hash>;
+
+// A vector optimized for small sizes. Provides the same APIs as a std::vector.
+template <typename T, size_t N, typename A = std::allocator<T>>
+using SpdyInlinedVector = SpdyInlinedVectorImpl<T, N, A>;
+
+using SpdyStringPieceHash = SpdyStringPieceHashImpl;
+
+inline size_t SpdyHashStringPair(SpdyStringPiece a, SpdyStringPiece b) {
+ return SpdyHashStringPairImpl(a, b);
+}
+
+} // namespace spdy
+
+#endif // QUICHE_SPDY_PLATFORM_API_SPDY_CONTAINERS_H_
diff --git a/chromium/net/third_party/quiche/src/spdy/platform/api/spdy_endianness_util.h b/chromium/net/third_party/quiche/src/spdy/platform/api/spdy_endianness_util.h
new file mode 100644
index 00000000000..e4074d710ce
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/spdy/platform/api/spdy_endianness_util.h
@@ -0,0 +1,44 @@
+// Copyright (c) 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_SPDY_PLATFORM_API_SPDY_ENDIANNESS_UTIL_H_
+#define QUICHE_SPDY_PLATFORM_API_SPDY_ENDIANNESS_UTIL_H_
+
+#include <stdint.h>
+
+#include "net/spdy/platform/impl/spdy_endianness_util_impl.h"
+
+namespace spdy {
+
+// Converts the bytes in |x| from network to host order (endianness), and
+// returns the result.
+inline uint16_t SpdyNetToHost16(uint16_t x) {
+ return SpdyNetToHost16Impl(x);
+}
+
+inline uint32_t SpdyNetToHost32(uint32_t x) {
+ return SpdyNetToHost32Impl(x);
+}
+
+inline uint64_t SpdyNetToHost64(uint64_t x) {
+ return SpdyNetToHost64Impl(x);
+}
+
+// Converts the bytes in |x| from host to network order (endianness), and
+// returns the result.
+inline uint16_t SpdyHostToNet16(uint16_t x) {
+ return SpdyHostToNet16Impl(x);
+}
+
+inline uint32_t SpdyHostToNet32(uint32_t x) {
+ return SpdyHostToNet32Impl(x);
+}
+
+inline uint64_t SpdyHostToNet64(uint64_t x) {
+ return SpdyHostToNet64Impl(x);
+}
+
+} // namespace spdy
+
+#endif // QUICHE_SPDY_PLATFORM_API_SPDY_ENDIANNESS_UTIL_H_
diff --git a/chromium/net/third_party/quiche/src/spdy/platform/api/spdy_estimate_memory_usage.h b/chromium/net/third_party/quiche/src/spdy/platform/api/spdy_estimate_memory_usage.h
new file mode 100644
index 00000000000..6aa53c3e79b
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/spdy/platform/api/spdy_estimate_memory_usage.h
@@ -0,0 +1,21 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_SPDY_PLATFORM_API_SPDY_ESTIMATE_MEMORY_USAGE_H_
+#define QUICHE_SPDY_PLATFORM_API_SPDY_ESTIMATE_MEMORY_USAGE_H_
+
+#include <cstddef>
+
+#include "net/spdy/platform/impl/spdy_estimate_memory_usage_impl.h"
+
+namespace spdy {
+
+template <class T>
+size_t SpdyEstimateMemoryUsage(const T& object) {
+ return SpdyEstimateMemoryUsageImpl(object);
+}
+
+} // namespace spdy
+
+#endif // QUICHE_SPDY_PLATFORM_API_SPDY_ESTIMATE_MEMORY_USAGE_H_
diff --git a/chromium/net/third_party/quiche/src/spdy/platform/api/spdy_export.h b/chromium/net/third_party/quiche/src/spdy/platform/api/spdy_export.h
new file mode 100644
index 00000000000..ba92f791dee
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/spdy/platform/api/spdy_export.h
@@ -0,0 +1,10 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_SPDY_PLATFORM_API_SPDY_EXPORT_H_
+#define QUICHE_SPDY_PLATFORM_API_SPDY_EXPORT_H_
+
+#include "net/spdy/platform/impl/spdy_export_impl.h"
+
+#endif // QUICHE_SPDY_PLATFORM_API_SPDY_EXPORT_H_
diff --git a/chromium/net/third_party/quiche/src/spdy/platform/api/spdy_flags.h b/chromium/net/third_party/quiche/src/spdy/platform/api/spdy_flags.h
new file mode 100644
index 00000000000..f3446d98203
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/spdy/platform/api/spdy_flags.h
@@ -0,0 +1,13 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_SPDY_PLATFORM_API_SPDY_FLAGS_H_
+#define QUICHE_SPDY_PLATFORM_API_SPDY_FLAGS_H_
+
+#include "net/spdy/platform/impl/spdy_flags_impl.h"
+
+#define GetSpdyReloadableFlag(flag) GetSpdyReloadableFlagImpl(flag)
+#define GetSpdyRestartFlag(flag) GetSpdyRestartFlagImpl(flag)
+
+#endif // QUICHE_SPDY_PLATFORM_API_SPDY_FLAGS_H_
diff --git a/chromium/net/third_party/quiche/src/spdy/platform/api/spdy_macros.h b/chromium/net/third_party/quiche/src/spdy/platform/api/spdy_macros.h
new file mode 100644
index 00000000000..a75c9576fc8
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/spdy/platform/api/spdy_macros.h
@@ -0,0 +1,15 @@
+// Copyright (c) 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_SPDY_PLATFORM_API_SPDY_MACROS_H_
+#define QUICHE_SPDY_PLATFORM_API_SPDY_MACROS_H_
+
+#include "net/spdy/platform/impl/spdy_macros_impl.h"
+
+#define SPDY_MUST_USE_RESULT SPDY_MUST_USE_RESULT_IMPL
+#define SPDY_UNUSED SPDY_UNUSED_IMPL
+
+#define SPDY_DVLOG_IF SPDY_DVLOG_IF_IMPL
+
+#endif // QUICHE_SPDY_PLATFORM_API_SPDY_MACROS_H_
diff --git a/chromium/net/third_party/quiche/src/spdy/platform/api/spdy_mem_slice.h b/chromium/net/third_party/quiche/src/spdy/platform/api/spdy_mem_slice.h
new file mode 100644
index 00000000000..0d67c000ee9
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/spdy/platform/api/spdy_mem_slice.h
@@ -0,0 +1,54 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_SPDY_PLATFORM_API_SPDY_MEM_SLICE_H_
+#define QUICHE_SPDY_PLATFORM_API_SPDY_MEM_SLICE_H_
+
+#include <utility>
+
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_export.h"
+#include "net/spdy/platform/impl/spdy_mem_slice_impl.h"
+
+namespace spdy {
+
+// SpdyMemSlice is an internally reference counted data buffer used as the
+// source buffers for write operations. SpdyMemSlice implicitly maintains a
+// reference count and will free the underlying data buffer when the reference
+// count reaches zero.
+class SPDY_EXPORT_PRIVATE SpdyMemSlice {
+ public:
+ // Constructs an empty SpdyMemSlice with no underlying data and 0 reference
+ // count.
+ SpdyMemSlice() = default;
+
+ // Constructs a SpdyMemSlice with reference count 1 to a newly allocated data
+ // buffer of |length| bytes.
+ explicit SpdyMemSlice(size_t length) : impl_(length) {}
+
+ // Constructs a SpdyMemSlice from |impl|. It takes the reference away from
+ // |impl|.
+ explicit SpdyMemSlice(SpdyMemSliceImpl impl) : impl_(std::move(impl)) {}
+
+ SpdyMemSlice(const SpdyMemSlice& other) = delete;
+ SpdyMemSlice& operator=(const SpdyMemSlice& other) = delete;
+
+ // Move constructors. |other| will not hold a reference to the data buffer
+ // after this call completes.
+ SpdyMemSlice(SpdyMemSlice&& other) = default;
+ SpdyMemSlice& operator=(SpdyMemSlice&& other) = default;
+
+ ~SpdyMemSlice() = default;
+
+ // Returns a char pointer to underlying data buffer.
+ const char* data() const { return impl_.data(); }
+ // Returns the length of underlying data buffer.
+ size_t length() const { return impl_.length(); }
+
+ private:
+ SpdyMemSliceImpl impl_;
+};
+
+} // namespace spdy
+
+#endif // QUICHE_SPDY_PLATFORM_API_SPDY_MEM_SLICE_H_
diff --git a/chromium/net/third_party/quiche/src/spdy/platform/api/spdy_mem_slice_test.cc b/chromium/net/third_party/quiche/src/spdy/platform/api/spdy_mem_slice_test.cc
new file mode 100644
index 00000000000..af323f6767d
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/spdy/platform/api/spdy_mem_slice_test.cc
@@ -0,0 +1,47 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_mem_slice.h"
+
+#include <utility>
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace spdy {
+namespace test {
+namespace {
+
+class SpdyMemSliceTest : public ::testing::Test {
+ public:
+ SpdyMemSliceTest() {
+ slice_ = SpdyMemSlice(1024);
+ orig_data_ = slice_.data();
+ orig_length_ = slice_.length();
+ }
+
+ SpdyMemSlice slice_;
+ const char* orig_data_;
+ size_t orig_length_;
+};
+
+TEST_F(SpdyMemSliceTest, MoveConstruct) {
+ SpdyMemSlice moved(std::move(slice_));
+ EXPECT_EQ(moved.data(), orig_data_);
+ EXPECT_EQ(moved.length(), orig_length_);
+ EXPECT_EQ(nullptr, slice_.data());
+ EXPECT_EQ(0u, slice_.length());
+}
+
+TEST_F(SpdyMemSliceTest, MoveAssign) {
+ SpdyMemSlice moved;
+ moved = std::move(slice_);
+ EXPECT_EQ(moved.data(), orig_data_);
+ EXPECT_EQ(moved.length(), orig_length_);
+ EXPECT_EQ(nullptr, slice_.data());
+ EXPECT_EQ(0u, slice_.length());
+}
+
+} // namespace
+} // namespace test
+} // namespace spdy
diff --git a/chromium/net/third_party/quiche/src/spdy/platform/api/spdy_ptr_util.h b/chromium/net/third_party/quiche/src/spdy/platform/api/spdy_ptr_util.h
new file mode 100644
index 00000000000..32b8515f4ca
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/spdy/platform/api/spdy_ptr_util.h
@@ -0,0 +1,27 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_SPDY_PLATFORM_API_SPDY_PTR_UTIL_H_
+#define QUICHE_SPDY_PLATFORM_API_SPDY_PTR_UTIL_H_
+
+#include <memory>
+#include <utility>
+
+#include "net/spdy/platform/impl/spdy_ptr_util_impl.h"
+
+namespace spdy {
+
+template <typename T, typename... Args>
+std::unique_ptr<T> SpdyMakeUnique(Args&&... args) {
+ return SpdyMakeUniqueImpl<T>(std::forward<Args>(args)...);
+}
+
+template <typename T>
+std::unique_ptr<T> SpdyWrapUnique(T* ptr) {
+ return SpdyWrapUniqueImpl<T>(ptr);
+}
+
+} // namespace spdy
+
+#endif // QUICHE_SPDY_PLATFORM_API_SPDY_PTR_UTIL_H_
diff --git a/chromium/net/third_party/quiche/src/spdy/platform/api/spdy_string.h b/chromium/net/third_party/quiche/src/spdy/platform/api/spdy_string.h
new file mode 100644
index 00000000000..16eb9e5569d
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/spdy/platform/api/spdy_string.h
@@ -0,0 +1,16 @@
+// Copyright (c) 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_SPDY_PLATFORM_API_SPDY_STRING_H_
+#define QUICHE_SPDY_PLATFORM_API_SPDY_STRING_H_
+
+#include "net/spdy/platform/impl/spdy_string_impl.h"
+
+namespace spdy {
+
+using SpdyString = SpdyStringImpl;
+
+} // namespace spdy
+
+#endif // QUICHE_SPDY_PLATFORM_API_SPDY_STRING_H_
diff --git a/chromium/net/third_party/quiche/src/spdy/platform/api/spdy_string_piece.h b/chromium/net/third_party/quiche/src/spdy/platform/api/spdy_string_piece.h
new file mode 100644
index 00000000000..37eea70f58a
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/spdy/platform/api/spdy_string_piece.h
@@ -0,0 +1,16 @@
+// Copyright (c) 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_SPDY_PLATFORM_API_SPDY_STRING_PIECE_H_
+#define QUICHE_SPDY_PLATFORM_API_SPDY_STRING_PIECE_H_
+
+#include "net/spdy/platform/impl/spdy_string_piece_impl.h"
+
+namespace spdy {
+
+using SpdyStringPiece = SpdyStringPieceImpl;
+
+} // namespace spdy
+
+#endif // QUICHE_SPDY_PLATFORM_API_SPDY_STRING_PIECE_H_
diff --git a/chromium/net/third_party/quiche/src/spdy/platform/api/spdy_string_utils.h b/chromium/net/third_party/quiche/src/spdy/platform/api/spdy_string_utils.h
new file mode 100644
index 00000000000..7554f796a97
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/spdy/platform/api/spdy_string_utils.h
@@ -0,0 +1,58 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_SPDY_PLATFORM_API_SPDY_STRING_UTILS_H_
+#define QUICHE_SPDY_PLATFORM_API_SPDY_STRING_UTILS_H_
+
+#include <utility>
+
+// The following header file has to be included from at least
+// non-test file in order to avoid strange linking errors.
+// TODO(bnc): Remove this include as soon as it is included elsewhere in
+// non-test code.
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_mem_slice.h"
+
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_string.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_string_piece.h"
+#include "net/spdy/platform/impl/spdy_string_utils_impl.h"
+
+namespace spdy {
+
+template <typename... Args>
+inline SpdyString SpdyStrCat(const Args&... args) {
+ return SpdyStrCatImpl(std::forward<const Args&>(args)...);
+}
+
+template <typename... Args>
+inline void SpdyStrAppend(SpdyString* output, const Args&... args) {
+ SpdyStrAppendImpl(output, std::forward<const Args&>(args)...);
+}
+
+inline char SpdyHexDigitToInt(char c) {
+ return SpdyHexDigitToIntImpl(c);
+}
+
+inline SpdyString SpdyHexDecode(SpdyStringPiece data) {
+ return SpdyHexDecodeImpl(data);
+}
+
+inline bool SpdyHexDecodeToUInt32(SpdyStringPiece data, uint32_t* out) {
+ return SpdyHexDecodeToUInt32Impl(data, out);
+}
+
+inline SpdyString SpdyHexEncode(const char* bytes, size_t size) {
+ return SpdyHexEncodeImpl(bytes, size);
+}
+
+inline SpdyString SpdyHexEncodeUInt32AndTrim(uint32_t data) {
+ return SpdyHexEncodeUInt32AndTrimImpl(data);
+}
+
+inline SpdyString SpdyHexDump(SpdyStringPiece data) {
+ return SpdyHexDumpImpl(data);
+}
+
+} // namespace spdy
+
+#endif // QUICHE_SPDY_PLATFORM_API_SPDY_STRING_UTILS_H_
diff --git a/chromium/net/third_party/quiche/src/spdy/platform/api/spdy_string_utils_test.cc b/chromium/net/third_party/quiche/src/spdy/platform/api/spdy_string_utils_test.cc
new file mode 100644
index 00000000000..3f7e6a1882f
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/spdy/platform/api/spdy_string_utils_test.cc
@@ -0,0 +1,242 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_string_utils.h"
+
+#include <cstdint>
+
+#include "testing/gtest/include/gtest/gtest.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_string_piece.h"
+
+namespace spdy {
+namespace test {
+namespace {
+
+TEST(SpdyStringUtilsTest, SpdyStrCat) {
+ // No arguments.
+ EXPECT_EQ("", SpdyStrCat());
+
+ // Single string-like argument.
+ const char kFoo[] = "foo";
+ const SpdyString string_foo(kFoo);
+ const SpdyStringPiece stringpiece_foo(string_foo);
+ EXPECT_EQ("foo", SpdyStrCat(kFoo));
+ EXPECT_EQ("foo", SpdyStrCat(string_foo));
+ EXPECT_EQ("foo", SpdyStrCat(stringpiece_foo));
+
+ // Two string-like arguments.
+ const char kBar[] = "bar";
+ const SpdyStringPiece stringpiece_bar(kBar);
+ const SpdyString string_bar(kBar);
+ EXPECT_EQ("foobar", SpdyStrCat(kFoo, kBar));
+ EXPECT_EQ("foobar", SpdyStrCat(kFoo, string_bar));
+ EXPECT_EQ("foobar", SpdyStrCat(kFoo, stringpiece_bar));
+ EXPECT_EQ("foobar", SpdyStrCat(string_foo, kBar));
+ EXPECT_EQ("foobar", SpdyStrCat(string_foo, string_bar));
+ EXPECT_EQ("foobar", SpdyStrCat(string_foo, stringpiece_bar));
+ EXPECT_EQ("foobar", SpdyStrCat(stringpiece_foo, kBar));
+ EXPECT_EQ("foobar", SpdyStrCat(stringpiece_foo, string_bar));
+ EXPECT_EQ("foobar", SpdyStrCat(stringpiece_foo, stringpiece_bar));
+
+ // Many-many arguments.
+ EXPECT_EQ(
+ "foobarbazquxquuxquuzcorgegraultgarplywaldofredplughxyzzythud",
+ SpdyStrCat("foo", "bar", "baz", "qux", "quux", "quuz", "corge", "grault",
+ "garply", "waldo", "fred", "plugh", "xyzzy", "thud"));
+
+ // Numerical arguments.
+ const int16_t i = 1;
+ const uint64_t u = 8;
+ const double d = 3.1415;
+
+ EXPECT_EQ("1 8", SpdyStrCat(i, " ", u));
+ EXPECT_EQ("3.14151181", SpdyStrCat(d, i, i, u, i));
+ EXPECT_EQ("i: 1, u: 8, d: 3.1415",
+ SpdyStrCat("i: ", i, ", u: ", u, ", d: ", d));
+
+ // Boolean arguments.
+ const bool t = true;
+ const bool f = false;
+
+ EXPECT_EQ("1", SpdyStrCat(t));
+ EXPECT_EQ("0", SpdyStrCat(f));
+ EXPECT_EQ("0110", SpdyStrCat(f, t, t, f));
+
+ // Mixed string-like, numerical, and Boolean arguments.
+ EXPECT_EQ("foo1foo081bar3.14151",
+ SpdyStrCat(kFoo, i, string_foo, f, u, t, stringpiece_bar, d, t));
+ EXPECT_EQ("3.141511bar18bar13.14150",
+ SpdyStrCat(d, t, t, string_bar, i, u, kBar, t, d, f));
+}
+
+TEST(SpdyStringUtilsTest, SpdyStrAppend) {
+ // No arguments on empty string.
+ SpdyString output;
+ SpdyStrAppend(&output);
+ EXPECT_TRUE(output.empty());
+
+ // Single string-like argument.
+ const char kFoo[] = "foo";
+ const SpdyString string_foo(kFoo);
+ const SpdyStringPiece stringpiece_foo(string_foo);
+ SpdyStrAppend(&output, kFoo);
+ EXPECT_EQ("foo", output);
+ SpdyStrAppend(&output, string_foo);
+ EXPECT_EQ("foofoo", output);
+ SpdyStrAppend(&output, stringpiece_foo);
+ EXPECT_EQ("foofoofoo", output);
+
+ // No arguments on non-empty string.
+ SpdyStrAppend(&output);
+ EXPECT_EQ("foofoofoo", output);
+
+ output.clear();
+
+ // Two string-like arguments.
+ const char kBar[] = "bar";
+ const SpdyStringPiece stringpiece_bar(kBar);
+ const SpdyString string_bar(kBar);
+ SpdyStrAppend(&output, kFoo, kBar);
+ EXPECT_EQ("foobar", output);
+ SpdyStrAppend(&output, kFoo, string_bar);
+ EXPECT_EQ("foobarfoobar", output);
+ SpdyStrAppend(&output, kFoo, stringpiece_bar);
+ EXPECT_EQ("foobarfoobarfoobar", output);
+ SpdyStrAppend(&output, string_foo, kBar);
+ EXPECT_EQ("foobarfoobarfoobarfoobar", output);
+
+ output.clear();
+
+ SpdyStrAppend(&output, string_foo, string_bar);
+ EXPECT_EQ("foobar", output);
+ SpdyStrAppend(&output, string_foo, stringpiece_bar);
+ EXPECT_EQ("foobarfoobar", output);
+ SpdyStrAppend(&output, stringpiece_foo, kBar);
+ EXPECT_EQ("foobarfoobarfoobar", output);
+ SpdyStrAppend(&output, stringpiece_foo, string_bar);
+ EXPECT_EQ("foobarfoobarfoobarfoobar", output);
+
+ output.clear();
+
+ SpdyStrAppend(&output, stringpiece_foo, stringpiece_bar);
+ EXPECT_EQ("foobar", output);
+
+ // Many-many arguments.
+ SpdyStrAppend(&output, "foo", "bar", "baz", "qux", "quux", "quuz", "corge",
+ "grault", "garply", "waldo", "fred", "plugh", "xyzzy", "thud");
+ EXPECT_EQ(
+ "foobarfoobarbazquxquuxquuzcorgegraultgarplywaldofredplughxyzzythud",
+ output);
+
+ output.clear();
+
+ // Numerical arguments.
+ const int16_t i = 1;
+ const uint64_t u = 8;
+ const double d = 3.1415;
+
+ SpdyStrAppend(&output, i, " ", u);
+ EXPECT_EQ("1 8", output);
+ SpdyStrAppend(&output, d, i, i, u, i);
+ EXPECT_EQ("1 83.14151181", output);
+ SpdyStrAppend(&output, "i: ", i, ", u: ", u, ", d: ", d);
+ EXPECT_EQ("1 83.14151181i: 1, u: 8, d: 3.1415", output);
+
+ output.clear();
+
+ // Boolean arguments.
+ const bool t = true;
+ const bool f = false;
+
+ SpdyStrAppend(&output, t);
+ EXPECT_EQ("1", output);
+ SpdyStrAppend(&output, f);
+ EXPECT_EQ("10", output);
+ SpdyStrAppend(&output, f, t, t, f);
+ EXPECT_EQ("100110", output);
+
+ output.clear();
+
+ // Mixed string-like, numerical, and Boolean arguments.
+ SpdyStrAppend(&output, kFoo, i, string_foo, f, u, t, stringpiece_bar, d, t);
+ EXPECT_EQ("foo1foo081bar3.14151", output);
+ SpdyStrAppend(&output, d, t, t, string_bar, i, u, kBar, t, d, f);
+ EXPECT_EQ("foo1foo081bar3.141513.141511bar18bar13.14150", output);
+}
+
+TEST(SpdyStringUtilsTest, SpdyHexDigitToInt) {
+ EXPECT_EQ(0, SpdyHexDigitToInt('0'));
+ EXPECT_EQ(1, SpdyHexDigitToInt('1'));
+ EXPECT_EQ(2, SpdyHexDigitToInt('2'));
+ EXPECT_EQ(3, SpdyHexDigitToInt('3'));
+ EXPECT_EQ(4, SpdyHexDigitToInt('4'));
+ EXPECT_EQ(5, SpdyHexDigitToInt('5'));
+ EXPECT_EQ(6, SpdyHexDigitToInt('6'));
+ EXPECT_EQ(7, SpdyHexDigitToInt('7'));
+ EXPECT_EQ(8, SpdyHexDigitToInt('8'));
+ EXPECT_EQ(9, SpdyHexDigitToInt('9'));
+
+ EXPECT_EQ(10, SpdyHexDigitToInt('a'));
+ EXPECT_EQ(11, SpdyHexDigitToInt('b'));
+ EXPECT_EQ(12, SpdyHexDigitToInt('c'));
+ EXPECT_EQ(13, SpdyHexDigitToInt('d'));
+ EXPECT_EQ(14, SpdyHexDigitToInt('e'));
+ EXPECT_EQ(15, SpdyHexDigitToInt('f'));
+
+ EXPECT_EQ(10, SpdyHexDigitToInt('A'));
+ EXPECT_EQ(11, SpdyHexDigitToInt('B'));
+ EXPECT_EQ(12, SpdyHexDigitToInt('C'));
+ EXPECT_EQ(13, SpdyHexDigitToInt('D'));
+ EXPECT_EQ(14, SpdyHexDigitToInt('E'));
+ EXPECT_EQ(15, SpdyHexDigitToInt('F'));
+}
+
+TEST(SpdyStringUtilsTest, SpdyHexDecodeToUInt32) {
+ uint32_t out;
+ EXPECT_TRUE(SpdyHexDecodeToUInt32("0", &out));
+ EXPECT_EQ(0u, out);
+ EXPECT_TRUE(SpdyHexDecodeToUInt32("00", &out));
+ EXPECT_EQ(0u, out);
+ EXPECT_TRUE(SpdyHexDecodeToUInt32("0000000", &out));
+ EXPECT_EQ(0u, out);
+ EXPECT_TRUE(SpdyHexDecodeToUInt32("00000000", &out));
+ EXPECT_EQ(0u, out);
+ EXPECT_TRUE(SpdyHexDecodeToUInt32("1", &out));
+ EXPECT_EQ(1u, out);
+ EXPECT_TRUE(SpdyHexDecodeToUInt32("ffffFFF", &out));
+ EXPECT_EQ(0xFFFFFFFu, out);
+ EXPECT_TRUE(SpdyHexDecodeToUInt32("fFfFffFf", &out));
+ EXPECT_EQ(0xFFFFFFFFu, out);
+ EXPECT_TRUE(SpdyHexDecodeToUInt32("01AEF", &out));
+ EXPECT_EQ(0x1AEFu, out);
+ EXPECT_TRUE(SpdyHexDecodeToUInt32("abcde", &out));
+ EXPECT_EQ(0xABCDEu, out);
+
+ EXPECT_FALSE(SpdyHexDecodeToUInt32("", &out));
+ EXPECT_FALSE(SpdyHexDecodeToUInt32("111111111", &out));
+ EXPECT_FALSE(SpdyHexDecodeToUInt32("1111111111", &out));
+ EXPECT_FALSE(SpdyHexDecodeToUInt32("0x1111", &out));
+}
+
+TEST(SpdyStringUtilsTest, SpdyHexEncode) {
+ unsigned char bytes[] = {0x01, 0xff, 0x02, 0xfe, 0x03, 0x80, 0x81};
+ EXPECT_EQ("01ff02fe038081",
+ SpdyHexEncode(reinterpret_cast<char*>(bytes), sizeof(bytes)));
+}
+
+TEST(SpdyStringUtilsTest, SpdyHexEncodeUInt32AndTrim) {
+ EXPECT_EQ("0", SpdyHexEncodeUInt32AndTrim(0));
+ EXPECT_EQ("1", SpdyHexEncodeUInt32AndTrim(1));
+ EXPECT_EQ("a", SpdyHexEncodeUInt32AndTrim(0xA));
+ EXPECT_EQ("f", SpdyHexEncodeUInt32AndTrim(0xF));
+ EXPECT_EQ("a9", SpdyHexEncodeUInt32AndTrim(0xA9));
+ EXPECT_EQ("9abcdef", SpdyHexEncodeUInt32AndTrim(0x9ABCDEF));
+ EXPECT_EQ("12345678", SpdyHexEncodeUInt32AndTrim(0x12345678));
+ EXPECT_EQ("ffffffff", SpdyHexEncodeUInt32AndTrim(0xFFFFFFFF));
+ EXPECT_EQ("10000001", SpdyHexEncodeUInt32AndTrim(0x10000001));
+}
+
+} // namespace
+} // namespace test
+} // namespace spdy
diff --git a/chromium/net/third_party/quiche/src/spdy/platform/api/spdy_test_helpers.h b/chromium/net/third_party/quiche/src/spdy/platform/api/spdy_test_helpers.h
new file mode 100644
index 00000000000..367d330b6c9
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/spdy/platform/api/spdy_test_helpers.h
@@ -0,0 +1,12 @@
+// Copyright (c) 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_SPDY_PLATFORM_API_SPDY_TEST_HELPERS_H_
+#define QUICHE_SPDY_PLATFORM_API_SPDY_TEST_HELPERS_H_
+
+#include "net/spdy/platform/impl/spdy_test_helpers_impl.h"
+
+#define EXPECT_SPDY_BUG EXPECT_SPDY_BUG_IMPL
+
+#endif // QUICHE_SPDY_PLATFORM_API_SPDY_TEST_HELPERS_H_
diff --git a/chromium/net/third_party/quiche/src/spdy/platform/api/spdy_test_utils_prod.h b/chromium/net/third_party/quiche/src/spdy/platform/api/spdy_test_utils_prod.h
new file mode 100644
index 00000000000..14667701905
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/spdy/platform/api/spdy_test_utils_prod.h
@@ -0,0 +1,12 @@
+// Copyright (c) 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_SPDY_PLATFORM_API_SPDY_TEST_UTILS_PROD_H_
+#define QUICHE_SPDY_PLATFORM_API_SPDY_TEST_UTILS_PROD_H_
+
+#include "net/spdy/platform/impl/spdy_test_utils_prod_impl.h"
+
+#define SPDY_FRIEND_TEST SPDY_FRIEND_TEST_IMPL
+
+#endif // QUICHE_SPDY_PLATFORM_API_SPDY_TEST_UTILS_PROD_H_
diff --git a/chromium/net/third_party/quiche/src/spdy/platform/api/spdy_unsafe_arena.h b/chromium/net/third_party/quiche/src/spdy/platform/api/spdy_unsafe_arena.h
new file mode 100644
index 00000000000..77185e17598
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/spdy/platform/api/spdy_unsafe_arena.h
@@ -0,0 +1,16 @@
+// Copyright (c) 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_SPDY_PLATFORM_API_SPDY_UNSAFE_ARENA_H_
+#define QUICHE_SPDY_PLATFORM_API_SPDY_UNSAFE_ARENA_H_
+
+#include "net/spdy/platform/impl/spdy_unsafe_arena_impl.h"
+
+namespace spdy {
+
+using SpdyUnsafeArena = SpdyUnsafeArenaImpl;
+
+} // namespace spdy
+
+#endif // QUICHE_SPDY_PLATFORM_API_SPDY_UNSAFE_ARENA_H_