| // 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/spdy/buffered_spdy_framer.h" |
| |
| #include <algorithm> |
| #include <utility> |
| |
| #include "base/logging.h" |
| #include "net/log/net_log_with_source.h" |
| #include "net/spdy/spdy_test_util_common.h" |
| #include "testing/platform_test.h" |
| |
| namespace net { |
| |
| namespace { |
| |
| class TestBufferedSpdyVisitor : public BufferedSpdyFramerVisitorInterface { |
| public: |
| TestBufferedSpdyVisitor() |
| : buffered_spdy_framer_(kMaxHeaderListSizeForTest, NetLogWithSource()), |
| error_count_(0), |
| setting_count_(0), |
| headers_frame_count_(0), |
| push_promise_frame_count_(0), |
| goaway_count_(0), |
| altsvc_count_(0), |
| header_stream_id_(static_cast<spdy::SpdyStreamId>(-1)), |
| promised_stream_id_(static_cast<spdy::SpdyStreamId>(-1)) {} |
| |
| void OnError( |
| http2::Http2DecoderAdapter::SpdyFramerError spdy_framer_error) override { |
| VLOG(1) << "spdy::SpdyFramer Error: " << spdy_framer_error; |
| error_count_++; |
| } |
| |
| void OnStreamError(spdy::SpdyStreamId stream_id, |
| const std::string& description) override { |
| VLOG(1) << "spdy::SpdyFramer Error on stream: " << stream_id << " " |
| << description; |
| error_count_++; |
| } |
| |
| void OnHeaders(spdy::SpdyStreamId stream_id, |
| bool has_priority, |
| int weight, |
| spdy::SpdyStreamId parent_stream_id, |
| bool exclusive, |
| bool fin, |
| spdy::SpdyHeaderBlock headers, |
| base::TimeTicks recv_first_byte_time) override { |
| header_stream_id_ = stream_id; |
| headers_frame_count_++; |
| headers_ = std::move(headers); |
| } |
| |
| void OnDataFrameHeader(spdy::SpdyStreamId stream_id, |
| size_t length, |
| bool fin) override { |
| ADD_FAILURE() << "Unexpected OnDataFrameHeader call."; |
| } |
| |
| void OnStreamFrameData(spdy::SpdyStreamId stream_id, |
| const char* data, |
| size_t len) override { |
| LOG(FATAL) << "Unexpected OnStreamFrameData call."; |
| } |
| |
| void OnStreamEnd(spdy::SpdyStreamId stream_id) override { |
| LOG(FATAL) << "Unexpected OnStreamEnd call."; |
| } |
| |
| void OnStreamPadding(spdy::SpdyStreamId stream_id, size_t len) override { |
| LOG(FATAL) << "Unexpected OnStreamPadding call."; |
| } |
| |
| void OnSettings() override {} |
| |
| void OnSettingsAck() override {} |
| |
| void OnSettingsEnd() override {} |
| |
| void OnSetting(spdy::SpdySettingsId id, uint32_t value) override { |
| setting_count_++; |
| } |
| |
| void OnPing(spdy::SpdyPingId unique_id, bool is_ack) override {} |
| |
| void OnRstStream(spdy::SpdyStreamId stream_id, |
| spdy::SpdyErrorCode error_code) override {} |
| |
| void OnGoAway(spdy::SpdyStreamId last_accepted_stream_id, |
| spdy::SpdyErrorCode error_code, |
| base::StringPiece debug_data) override { |
| goaway_count_++; |
| goaway_last_accepted_stream_id_ = last_accepted_stream_id; |
| goaway_error_code_ = error_code; |
| goaway_debug_data_.assign(debug_data.data(), debug_data.size()); |
| } |
| |
| void OnDataFrameHeader(const spdy::SpdySerializedFrame* frame) { |
| LOG(FATAL) << "Unexpected OnDataFrameHeader call."; |
| } |
| |
| void OnRstStream(const spdy::SpdySerializedFrame& frame) {} |
| void OnGoAway(const spdy::SpdySerializedFrame& frame) {} |
| void OnPing(const spdy::SpdySerializedFrame& frame) {} |
| void OnWindowUpdate(spdy::SpdyStreamId stream_id, |
| int delta_window_size) override {} |
| |
| void OnPushPromise(spdy::SpdyStreamId stream_id, |
| spdy::SpdyStreamId promised_stream_id, |
| spdy::SpdyHeaderBlock headers) override { |
| header_stream_id_ = stream_id; |
| push_promise_frame_count_++; |
| promised_stream_id_ = promised_stream_id; |
| headers_ = std::move(headers); |
| } |
| |
| void OnAltSvc(spdy::SpdyStreamId stream_id, |
| base::StringPiece origin, |
| const spdy::SpdyAltSvcWireFormat::AlternativeServiceVector& |
| altsvc_vector) override { |
| altsvc_count_++; |
| altsvc_stream_id_ = stream_id; |
| altsvc_origin_.assign(origin.data(), origin.size()); |
| altsvc_vector_ = altsvc_vector; |
| } |
| |
| bool OnUnknownFrame(spdy::SpdyStreamId stream_id, |
| uint8_t frame_type) override { |
| return true; |
| } |
| |
| // Convenience function which runs a framer simulation with particular input. |
| void SimulateInFramer(const spdy::SpdySerializedFrame& frame) { |
| const char* input_ptr = frame.data(); |
| size_t input_remaining = frame.size(); |
| buffered_spdy_framer_.set_visitor(this); |
| while (input_remaining > 0 && |
| buffered_spdy_framer_.spdy_framer_error() == |
| http2::Http2DecoderAdapter::SPDY_NO_ERROR) { |
| // To make the tests more interesting, we feed random (amd 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 = |
| buffered_spdy_framer_.ProcessInput(input_ptr, bytes_read); |
| input_remaining -= bytes_processed; |
| input_ptr += bytes_processed; |
| } |
| } |
| |
| BufferedSpdyFramer buffered_spdy_framer_; |
| |
| // Counters from the visitor callbacks. |
| int error_count_; |
| int setting_count_; |
| int headers_frame_count_; |
| int push_promise_frame_count_; |
| int goaway_count_; |
| int altsvc_count_; |
| |
| // Header block streaming state: |
| spdy::SpdyStreamId header_stream_id_; |
| spdy::SpdyStreamId promised_stream_id_; |
| |
| // Headers from OnHeaders and OnPushPromise for verification. |
| spdy::SpdyHeaderBlock headers_; |
| |
| // OnGoAway parameters. |
| spdy::SpdyStreamId goaway_last_accepted_stream_id_; |
| spdy::SpdyErrorCode goaway_error_code_; |
| std::string goaway_debug_data_; |
| |
| // OnAltSvc parameters. |
| spdy::SpdyStreamId altsvc_stream_id_; |
| std::string altsvc_origin_; |
| spdy::SpdyAltSvcWireFormat::AlternativeServiceVector altsvc_vector_; |
| }; |
| |
| } // namespace |
| |
| class BufferedSpdyFramerTest : public PlatformTest {}; |
| |
| TEST_F(BufferedSpdyFramerTest, OnSetting) { |
| spdy::SpdyFramer framer(spdy::SpdyFramer::ENABLE_COMPRESSION); |
| spdy::SpdySettingsIR settings_ir; |
| settings_ir.AddSetting(spdy::SETTINGS_INITIAL_WINDOW_SIZE, 2); |
| settings_ir.AddSetting(spdy::SETTINGS_MAX_CONCURRENT_STREAMS, 3); |
| spdy::SpdySerializedFrame control_frame( |
| framer.SerializeSettings(settings_ir)); |
| TestBufferedSpdyVisitor visitor; |
| |
| visitor.SimulateInFramer(control_frame); |
| EXPECT_EQ(0, visitor.error_count_); |
| EXPECT_EQ(2, visitor.setting_count_); |
| } |
| |
| TEST_F(BufferedSpdyFramerTest, HeaderListTooLarge) { |
| spdy::SpdyHeaderBlock headers; |
| std::string long_header_value(256 * 1024, 'x'); |
| headers["foo"] = long_header_value; |
| spdy::SpdyHeadersIR headers_ir(/*stream_id=*/1, std::move(headers)); |
| |
| NetLogWithSource net_log; |
| BufferedSpdyFramer framer(kMaxHeaderListSizeForTest, net_log); |
| spdy::SpdySerializedFrame control_frame = framer.SerializeFrame(headers_ir); |
| |
| TestBufferedSpdyVisitor visitor; |
| visitor.SimulateInFramer(control_frame); |
| |
| EXPECT_EQ(1, visitor.error_count_); |
| EXPECT_EQ(0, visitor.headers_frame_count_); |
| EXPECT_EQ(0, visitor.push_promise_frame_count_); |
| EXPECT_EQ(spdy::SpdyHeaderBlock(), visitor.headers_); |
| } |
| |
| TEST_F(BufferedSpdyFramerTest, ValidHeadersAfterInvalidHeaders) { |
| spdy::SpdyHeaderBlock headers; |
| headers["invalid"] = "\r\n\r\n"; |
| |
| spdy::SpdyHeaderBlock headers2; |
| headers["alpha"] = "beta"; |
| |
| SpdyTestUtil spdy_test_util; |
| spdy::SpdySerializedFrame headers_frame( |
| spdy_test_util.ConstructSpdyReply(1, std::move(headers))); |
| spdy::SpdySerializedFrame headers_frame2( |
| spdy_test_util.ConstructSpdyReply(2, std::move(headers2))); |
| |
| TestBufferedSpdyVisitor visitor; |
| visitor.SimulateInFramer(headers_frame); |
| EXPECT_EQ(1, visitor.error_count_); |
| EXPECT_EQ(0, visitor.headers_frame_count_); |
| |
| visitor.SimulateInFramer(headers_frame2); |
| EXPECT_EQ(1, visitor.error_count_); |
| EXPECT_EQ(1, visitor.headers_frame_count_); |
| } |
| |
| TEST_F(BufferedSpdyFramerTest, ReadHeadersHeaderBlock) { |
| spdy::SpdyHeaderBlock headers; |
| headers["alpha"] = "beta"; |
| headers["gamma"] = "delta"; |
| spdy::SpdyHeadersIR headers_ir(/*stream_id=*/1, headers.Clone()); |
| |
| NetLogWithSource net_log; |
| BufferedSpdyFramer framer(kMaxHeaderListSizeForTest, net_log); |
| spdy::SpdySerializedFrame control_frame = framer.SerializeFrame(headers_ir); |
| |
| TestBufferedSpdyVisitor visitor; |
| visitor.SimulateInFramer(control_frame); |
| EXPECT_EQ(0, visitor.error_count_); |
| EXPECT_EQ(1, visitor.headers_frame_count_); |
| EXPECT_EQ(0, visitor.push_promise_frame_count_); |
| EXPECT_EQ(headers, visitor.headers_); |
| } |
| |
| TEST_F(BufferedSpdyFramerTest, ReadPushPromiseHeaderBlock) { |
| spdy::SpdyHeaderBlock headers; |
| headers["alpha"] = "beta"; |
| headers["gamma"] = "delta"; |
| NetLogWithSource net_log; |
| BufferedSpdyFramer framer(kMaxHeaderListSizeForTest, net_log); |
| spdy::SpdyPushPromiseIR push_promise_ir( |
| /*stream_id=*/1, /*promised_stream_id=*/2, headers.Clone()); |
| spdy::SpdySerializedFrame control_frame = |
| framer.SerializeFrame(push_promise_ir); |
| |
| TestBufferedSpdyVisitor visitor; |
| visitor.SimulateInFramer(control_frame); |
| EXPECT_EQ(0, visitor.error_count_); |
| EXPECT_EQ(0, visitor.headers_frame_count_); |
| EXPECT_EQ(1, visitor.push_promise_frame_count_); |
| EXPECT_EQ(headers, visitor.headers_); |
| EXPECT_EQ(1u, visitor.header_stream_id_); |
| EXPECT_EQ(2u, visitor.promised_stream_id_); |
| } |
| |
| TEST_F(BufferedSpdyFramerTest, GoAwayDebugData) { |
| spdy::SpdyGoAwayIR go_ir(/*last_accepted_stream_id=*/2, |
| spdy::ERROR_CODE_FRAME_SIZE_ERROR, "foo"); |
| NetLogWithSource net_log; |
| BufferedSpdyFramer framer(kMaxHeaderListSizeForTest, net_log); |
| spdy::SpdySerializedFrame goaway_frame = framer.SerializeFrame(go_ir); |
| |
| TestBufferedSpdyVisitor visitor; |
| visitor.SimulateInFramer(goaway_frame); |
| EXPECT_EQ(0, visitor.error_count_); |
| EXPECT_EQ(1, visitor.goaway_count_); |
| EXPECT_EQ(2u, visitor.goaway_last_accepted_stream_id_); |
| EXPECT_EQ(spdy::ERROR_CODE_FRAME_SIZE_ERROR, visitor.goaway_error_code_); |
| EXPECT_EQ("foo", visitor.goaway_debug_data_); |
| } |
| |
| // ALTSVC frame on stream 0 must have an origin. |
| TEST_F(BufferedSpdyFramerTest, OnAltSvcOnStreamZero) { |
| const spdy::SpdyStreamId altsvc_stream_id(0); |
| spdy::SpdyAltSvcIR altsvc_ir(altsvc_stream_id); |
| spdy::SpdyAltSvcWireFormat::AlternativeService alternative_service( |
| "quic", "alternative.example.org", 443, 86400, |
| spdy::SpdyAltSvcWireFormat::VersionVector()); |
| altsvc_ir.add_altsvc(alternative_service); |
| const char altsvc_origin[] = "https://www.example.org"; |
| altsvc_ir.set_origin(altsvc_origin); |
| NetLogWithSource net_log; |
| BufferedSpdyFramer framer(kMaxHeaderListSizeForTest, net_log); |
| spdy::SpdySerializedFrame altsvc_frame(framer.SerializeFrame(altsvc_ir)); |
| |
| TestBufferedSpdyVisitor visitor; |
| visitor.SimulateInFramer(altsvc_frame); |
| EXPECT_EQ(0, visitor.error_count_); |
| EXPECT_EQ(1, visitor.altsvc_count_); |
| EXPECT_EQ(altsvc_stream_id, visitor.altsvc_stream_id_); |
| EXPECT_EQ(altsvc_origin, visitor.altsvc_origin_); |
| ASSERT_EQ(1u, visitor.altsvc_vector_.size()); |
| EXPECT_EQ(alternative_service, visitor.altsvc_vector_[0]); |
| } |
| |
| // ALTSVC frame on a non-zero stream must not have an origin. |
| TEST_F(BufferedSpdyFramerTest, OnAltSvcOnNonzeroStream) { |
| const spdy::SpdyStreamId altsvc_stream_id(1); |
| spdy::SpdyAltSvcIR altsvc_ir(altsvc_stream_id); |
| spdy::SpdyAltSvcWireFormat::AlternativeService alternative_service( |
| "quic", "alternative.example.org", 443, 86400, |
| spdy::SpdyAltSvcWireFormat::VersionVector()); |
| altsvc_ir.add_altsvc(alternative_service); |
| NetLogWithSource net_log; |
| BufferedSpdyFramer framer(kMaxHeaderListSizeForTest, net_log); |
| spdy::SpdySerializedFrame altsvc_frame(framer.SerializeFrame(altsvc_ir)); |
| |
| TestBufferedSpdyVisitor visitor; |
| visitor.SimulateInFramer(altsvc_frame); |
| EXPECT_EQ(0, visitor.error_count_); |
| EXPECT_EQ(1, visitor.altsvc_count_); |
| EXPECT_EQ(altsvc_stream_id, visitor.altsvc_stream_id_); |
| EXPECT_TRUE(visitor.altsvc_origin_.empty()); |
| ASSERT_EQ(1u, visitor.altsvc_vector_.size()); |
| EXPECT_EQ(alternative_service, visitor.altsvc_vector_[0]); |
| } |
| |
| } // namespace net |