| // Copyright 2016 The Chromium Authors. All rights reserved. | |
| // Use of 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 "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_logging.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 |