QUICHE team | 82dee2f | 2019-01-18 12:35:12 -0500 | [diff] [blame] | 1 | // Copyright 2016 The Chromium Authors. All rights reserved. |
| 2 | // Use of this source code is governed by a BSD-style license that can be |
| 3 | // found in the LICENSE file. |
| 4 | |
| 5 | #include "net/third_party/quiche/src/spdy/core/spdy_deframer_visitor.h" |
| 6 | |
| 7 | #include <stdlib.h> |
| 8 | |
| 9 | #include <algorithm> |
| 10 | #include <limits> |
| 11 | |
QUICHE team | 82dee2f | 2019-01-18 12:35:12 -0500 | [diff] [blame] | 12 | #include "net/third_party/quiche/src/http2/test_tools/http2_random.h" |
QUICHE team | f3c80c9 | 2020-02-12 09:47:55 -0800 | [diff] [blame] | 13 | #include "net/third_party/quiche/src/common/platform/api/quiche_test.h" |
QUICHE team | 82dee2f | 2019-01-18 12:35:12 -0500 | [diff] [blame] | 14 | #include "net/third_party/quiche/src/spdy/core/hpack/hpack_constants.h" |
| 15 | #include "net/third_party/quiche/src/spdy/core/mock_spdy_framer_visitor.h" |
| 16 | #include "net/third_party/quiche/src/spdy/core/spdy_frame_builder.h" |
| 17 | #include "net/third_party/quiche/src/spdy/core/spdy_frame_reader.h" |
| 18 | #include "net/third_party/quiche/src/spdy/core/spdy_framer.h" |
| 19 | #include "net/third_party/quiche/src/spdy/core/spdy_protocol.h" |
| 20 | #include "net/third_party/quiche/src/spdy/core/spdy_protocol_test_utils.h" |
| 21 | #include "net/third_party/quiche/src/spdy/core/spdy_test_utils.h" |
QUICHE team | ded0351 | 2019-03-07 14:45:11 -0800 | [diff] [blame] | 22 | #include "net/third_party/quiche/src/spdy/platform/api/spdy_logging.h" |
QUICHE team | 82dee2f | 2019-01-18 12:35:12 -0500 | [diff] [blame] | 23 | |
| 24 | namespace spdy { |
| 25 | namespace test { |
| 26 | namespace { |
| 27 | |
QUICHE team | f3c80c9 | 2020-02-12 09:47:55 -0800 | [diff] [blame] | 28 | class SpdyDeframerVisitorTest : public QuicheTest { |
QUICHE team | 82dee2f | 2019-01-18 12:35:12 -0500 | [diff] [blame] | 29 | protected: |
| 30 | SpdyDeframerVisitorTest() : encoder_(SpdyFramer::ENABLE_COMPRESSION) { |
| 31 | decoder_.set_process_single_input_frame(true); |
| 32 | auto collector = |
bnc | 463f235 | 2019-10-10 04:49:34 -0700 | [diff] [blame] | 33 | std::make_unique<DeframerCallbackCollector>(&collected_frames_); |
QUICHE team | 82dee2f | 2019-01-18 12:35:12 -0500 | [diff] [blame] | 34 | auto log_and_collect = |
| 35 | SpdyDeframerVisitorInterface::LogBeforeVisiting(std::move(collector)); |
| 36 | deframer_ = SpdyTestDeframer::CreateConverter(std::move(log_and_collect)); |
| 37 | decoder_.set_visitor(deframer_.get()); |
| 38 | } |
| 39 | |
| 40 | bool DeframeInput(const char* input, size_t size) { |
| 41 | size_t input_remaining = size; |
| 42 | while (input_remaining > 0 && |
| 43 | decoder_.spdy_framer_error() == |
| 44 | http2::Http2DecoderAdapter::SPDY_NO_ERROR) { |
| 45 | // To make the tests more interesting, we feed random (and small) chunks |
| 46 | // into the framer. This simulates getting strange-sized reads from |
| 47 | // the socket. |
| 48 | const size_t kMaxReadSize = 32; |
| 49 | size_t bytes_read = |
| 50 | (random_.Uniform(std::min(input_remaining, kMaxReadSize))) + 1; |
| 51 | size_t bytes_processed = decoder_.ProcessInput(input, bytes_read); |
| 52 | input_remaining -= bytes_processed; |
| 53 | input += bytes_processed; |
| 54 | if (decoder_.state() == |
| 55 | http2::Http2DecoderAdapter::SPDY_READY_FOR_FRAME) { |
| 56 | deframer_->AtFrameEnd(); |
| 57 | } |
| 58 | } |
| 59 | return (input_remaining == 0 && |
| 60 | decoder_.spdy_framer_error() == |
| 61 | http2::Http2DecoderAdapter::SPDY_NO_ERROR); |
| 62 | } |
| 63 | |
| 64 | SpdyFramer encoder_; |
| 65 | http2::Http2DecoderAdapter decoder_; |
| 66 | std::vector<CollectedFrame> collected_frames_; |
| 67 | std::unique_ptr<SpdyTestDeframer> deframer_; |
| 68 | |
| 69 | private: |
| 70 | http2::test::Http2Random random_; |
| 71 | }; |
| 72 | |
| 73 | TEST_F(SpdyDeframerVisitorTest, DataFrame) { |
| 74 | const char kFrameData[] = { |
| 75 | 0x00, 0x00, 0x0d, // Length = 13. |
| 76 | 0x00, // DATA |
| 77 | 0x08, // PADDED |
| 78 | 0x00, 0x00, 0x00, 0x01, // Stream 1 |
| 79 | 0x07, // Pad length field. |
| 80 | 'h', 'e', 'l', 'l', // Data |
| 81 | 'o', // More Data |
| 82 | 0x00, 0x00, 0x00, 0x00, // Padding |
| 83 | 0x00, 0x00, 0x00 // More Padding |
| 84 | }; |
| 85 | |
| 86 | EXPECT_TRUE(DeframeInput(kFrameData, sizeof kFrameData)); |
| 87 | ASSERT_EQ(1u, collected_frames_.size()); |
| 88 | const CollectedFrame& cf0 = collected_frames_[0]; |
| 89 | ASSERT_NE(cf0.frame_ir, nullptr); |
| 90 | |
| 91 | SpdyDataIR expected_ir(/* stream_id = */ 1, "hello"); |
| 92 | expected_ir.set_padding_len(8); |
| 93 | EXPECT_TRUE(cf0.VerifyHasFrame(expected_ir)); |
| 94 | } |
| 95 | |
| 96 | TEST_F(SpdyDeframerVisitorTest, HeaderFrameWithContinuation) { |
| 97 | const char kFrameData[] = { |
| 98 | 0x00, 0x00, 0x05, // Payload Length: 5 |
| 99 | 0x01, // Type: HEADERS |
| 100 | 0x09, // Flags: PADDED | END_STREAM |
| 101 | 0x00, 0x00, 0x00, 0x01, // Stream: 1 |
| 102 | 0x04, // Padding Length: 4 |
| 103 | 0x00, 0x00, 0x00, 0x00, // Padding |
| 104 | /* Second Frame */ |
| 105 | 0x00, 0x00, 0x12, // Payload Length: 18 |
| 106 | 0x09, // Type: CONTINUATION |
| 107 | 0x04, // Flags: END_HEADERS |
| 108 | 0x00, 0x00, 0x00, 0x01, // Stream: 1 |
| 109 | 0x00, // Unindexed, literal name & value |
| 110 | 0x03, 0x62, 0x61, 0x72, // Name len and name (3, "bar") |
| 111 | 0x03, 0x66, 0x6f, 0x6f, // Value len and value (3, "foo") |
| 112 | 0x00, // Unindexed, literal name & value |
| 113 | 0x03, 0x66, 0x6f, 0x6f, // Name len and name (3, "foo") |
| 114 | 0x03, 0x62, 0x61, 0x72, // Value len and value (3, "bar") |
| 115 | }; |
| 116 | |
| 117 | EXPECT_TRUE(DeframeInput(kFrameData, sizeof kFrameData)); |
| 118 | ASSERT_EQ(1u, collected_frames_.size()); |
| 119 | const CollectedFrame& cf0 = collected_frames_[0]; |
| 120 | |
| 121 | StringPairVector headers; |
| 122 | headers.push_back({"bar", "foo"}); |
| 123 | headers.push_back({"foo", "bar"}); |
| 124 | |
| 125 | EXPECT_TRUE(cf0.VerifyHasHeaders(headers)); |
| 126 | |
| 127 | SpdyHeadersIR expected_ir(/* stream_id = */ 1); |
| 128 | // Yet again SpdyFramerVisitorInterface is lossy: it doesn't call OnPadding |
| 129 | // for HEADERS, just for DATA. Sigh. |
| 130 | // expected_ir.set_padding_len(5); |
| 131 | expected_ir.set_fin(true); |
| 132 | for (const auto& nv : headers) { |
| 133 | expected_ir.SetHeader(nv.first, nv.second); |
| 134 | } |
| 135 | |
| 136 | EXPECT_TRUE(cf0.VerifyHasFrame(expected_ir)); |
| 137 | |
| 138 | // Confirm that mismatches are also detected. |
| 139 | headers.push_back({"baz", "bing"}); |
| 140 | EXPECT_FALSE(cf0.VerifyHasHeaders(headers)); |
| 141 | EXPECT_TRUE(cf0.VerifyHasFrame(expected_ir)); |
| 142 | |
| 143 | headers.pop_back(); |
| 144 | EXPECT_TRUE(cf0.VerifyHasHeaders(headers)); |
| 145 | EXPECT_TRUE(cf0.VerifyHasFrame(expected_ir)); |
| 146 | |
| 147 | expected_ir.SetHeader("baz", "bing"); |
| 148 | EXPECT_FALSE(cf0.VerifyHasFrame(expected_ir)); |
| 149 | EXPECT_TRUE(cf0.VerifyHasHeaders(headers)); |
| 150 | } |
| 151 | |
| 152 | TEST_F(SpdyDeframerVisitorTest, PriorityFrame) { |
| 153 | const char kFrameData[] = { |
| 154 | 0x00, 0x00, 0x05, // Length: 5 |
| 155 | 0x02, // Type: PRIORITY |
| 156 | 0x00, // Flags: none |
| 157 | 0x00, 0x00, 0x00, 0x65, // Stream: 101 |
| 158 | '\x80', 0x00, 0x00, 0x01, // Parent: 1 (Exclusive) |
| 159 | 0x10, // Weight: 17 |
| 160 | }; |
| 161 | |
| 162 | EXPECT_TRUE(DeframeInput(kFrameData, sizeof kFrameData)); |
| 163 | ASSERT_EQ(1u, collected_frames_.size()); |
| 164 | const CollectedFrame& cf0 = collected_frames_[0]; |
| 165 | |
| 166 | SpdyPriorityIR expected_ir(/* stream_id = */ 101, |
| 167 | /* parent_stream_id = */ 1, /* weight = */ 17, |
| 168 | /* exclusive = */ true); |
| 169 | EXPECT_TRUE(cf0.VerifyHasFrame(expected_ir)); |
| 170 | |
| 171 | // Confirm that mismatches are also detected. |
| 172 | EXPECT_FALSE(cf0.VerifyHasFrame(SpdyPriorityIR(101, 1, 16, true))); |
| 173 | EXPECT_FALSE(cf0.VerifyHasFrame(SpdyPriorityIR(101, 50, 17, true))); |
| 174 | EXPECT_FALSE(cf0.VerifyHasFrame(SpdyPriorityIR(201, 1, 17, true))); |
| 175 | EXPECT_FALSE(cf0.VerifyHasFrame(SpdyPriorityIR(101, 1, 17, false))); |
| 176 | } |
| 177 | |
| 178 | TEST_F(SpdyDeframerVisitorTest, DISABLED_RstStreamFrame) { |
| 179 | // TODO(jamessynge): Please implement. |
| 180 | } |
| 181 | |
| 182 | TEST_F(SpdyDeframerVisitorTest, SettingsFrame) { |
| 183 | // Settings frame with two entries for the same parameter but with different |
| 184 | // values. The last one will be in the decoded SpdySettingsIR, but the vector |
| 185 | // of settings will have both, in the same order. |
| 186 | const char kFrameData[] = { |
| 187 | 0x00, 0x00, 0x0c, // Length |
| 188 | 0x04, // Type (SETTINGS) |
| 189 | 0x00, // Flags |
| 190 | 0x00, 0x00, 0x00, 0x00, // Stream id (must be zero) |
| 191 | 0x00, 0x04, // Setting id (SETTINGS_INITIAL_WINDOW_SIZE) |
| 192 | 0x0a, 0x0b, 0x0c, 0x0d, // Setting value |
| 193 | 0x00, 0x04, // Setting id (SETTINGS_INITIAL_WINDOW_SIZE) |
| 194 | 0x00, 0x00, 0x00, '\xff', // Setting value |
| 195 | }; |
| 196 | |
| 197 | EXPECT_TRUE(DeframeInput(kFrameData, sizeof kFrameData)); |
| 198 | ASSERT_EQ(1u, collected_frames_.size()); |
| 199 | const CollectedFrame& cf0 = collected_frames_[0]; |
| 200 | ASSERT_NE(cf0.frame_ir, nullptr); |
| 201 | |
| 202 | SpdySettingsIR expected_ir; |
| 203 | expected_ir.AddSetting(SETTINGS_INITIAL_WINDOW_SIZE, 255); |
| 204 | EXPECT_TRUE(cf0.VerifyHasFrame(expected_ir)); |
| 205 | |
| 206 | SettingVector expected_settings; |
| 207 | expected_settings.push_back({SETTINGS_INITIAL_WINDOW_SIZE, 0x0a0b0c0d}); |
| 208 | expected_settings.push_back({SETTINGS_INITIAL_WINDOW_SIZE, 255}); |
| 209 | |
| 210 | EXPECT_TRUE(cf0.VerifyHasSettings(expected_settings)); |
| 211 | |
| 212 | // Confirm that mismatches are also detected. |
| 213 | expected_settings.push_back({SETTINGS_INITIAL_WINDOW_SIZE, 65536}); |
| 214 | EXPECT_FALSE(cf0.VerifyHasSettings(expected_settings)); |
| 215 | |
| 216 | expected_ir.AddSetting(SETTINGS_INITIAL_WINDOW_SIZE, 65536); |
| 217 | EXPECT_FALSE(cf0.VerifyHasFrame(expected_ir)); |
| 218 | |
| 219 | SpdySettingsIR unexpected_ir; |
| 220 | unexpected_ir.set_is_ack(true); |
| 221 | EXPECT_FALSE(cf0.VerifyHasFrame(unexpected_ir)); |
| 222 | } |
| 223 | |
| 224 | TEST_F(SpdyDeframerVisitorTest, DISABLED_PushPromiseFrame) { |
| 225 | // TODO(jamessynge): Please implement. |
| 226 | } |
| 227 | |
| 228 | TEST_F(SpdyDeframerVisitorTest, DISABLED_PingFrame) { |
| 229 | // TODO(jamessynge): Please implement. |
| 230 | } |
| 231 | |
| 232 | TEST_F(SpdyDeframerVisitorTest, DISABLED_GoAwayFrame) { |
| 233 | // TODO(jamessynge): Please implement. |
| 234 | } |
| 235 | |
| 236 | TEST_F(SpdyDeframerVisitorTest, DISABLED_WindowUpdateFrame) { |
| 237 | // TODO(jamessynge): Please implement. |
| 238 | } |
| 239 | |
| 240 | TEST_F(SpdyDeframerVisitorTest, DISABLED_AltSvcFrame) { |
| 241 | // TODO(jamessynge): Please implement. |
| 242 | } |
| 243 | |
| 244 | } // namespace |
| 245 | } // namespace test |
| 246 | } // namespace spdy |