// Copyright 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/quic/quic_headers_stream.h" #include "net/quic/quic_utils.h" #include "net/quic/spdy_utils.h" #include "net/quic/test_tools/quic_connection_peer.h" #include "net/quic/test_tools/quic_session_peer.h" #include "net/quic/test_tools/quic_test_utils.h" #include "net/quic/test_tools/reliable_quic_stream_peer.h" #include "net/spdy/spdy_protocol.h" #include "testing/gtest/include/gtest/gtest.h" using base::StringPiece; using std::string; using testing::Invoke; using testing::StrictMock; using testing::WithArgs; using testing::_; namespace net { namespace test { namespace { class MockVisitor : public SpdyFramerVisitorInterface { public: MOCK_METHOD1(OnError, void(SpdyFramer* framer)); MOCK_METHOD3(OnDataFrameHeader, void(SpdyStreamId stream_id, size_t length, bool fin)); MOCK_METHOD4(OnStreamFrameData, void(SpdyStreamId stream_id, const char* data, size_t len, bool fin)); MOCK_METHOD3(OnControlFrameHeaderData, bool(SpdyStreamId stream_id, const char* header_data, size_t len)); MOCK_METHOD5(OnSynStream, void(SpdyStreamId stream_id, SpdyStreamId associated_stream_id, SpdyPriority priority, bool fin, bool unidirectional)); MOCK_METHOD2(OnSynReply, void(SpdyStreamId stream_id, bool fin)); MOCK_METHOD2(OnRstStream, void(SpdyStreamId stream_id, SpdyRstStreamStatus status)); MOCK_METHOD1(OnSettings, void(bool clear_persisted)); MOCK_METHOD3(OnSetting, void(SpdySettingsIds id, uint8 flags, uint32 value)); MOCK_METHOD0(OnSettingsAck, void()); MOCK_METHOD0(OnSettingsEnd, void()); MOCK_METHOD2(OnPing, void(SpdyPingId unique_id, bool is_ack)); MOCK_METHOD2(OnGoAway, void(SpdyStreamId last_accepted_stream_id, SpdyGoAwayStatus status)); MOCK_METHOD3(OnHeaders, void(SpdyStreamId stream_id, bool fin, bool end)); MOCK_METHOD2(OnWindowUpdate, void(SpdyStreamId stream_id, uint32 delta_window_size)); MOCK_METHOD2(OnCredentialFrameData, bool(const char* credential_data, size_t len)); MOCK_METHOD1(OnBlocked, void(SpdyStreamId stream_id)); MOCK_METHOD3(OnPushPromise, void(SpdyStreamId stream_id, SpdyStreamId promised_stream_id, bool end)); MOCK_METHOD2(OnContinuation, void(SpdyStreamId stream_id, bool end)); MOCK_METHOD6(OnAltSvc, void(SpdyStreamId stream_id, uint32 max_age, uint16 port, StringPiece protocol_id, StringPiece host, StringPiece origin)); MOCK_METHOD2(OnUnknownFrame, bool(SpdyStreamId stream_id, int frame_type)); }; class QuicHeadersStreamTest : public ::testing::TestWithParam<bool> { public: static QuicVersionVector GetVersions() { QuicVersionVector versions; versions.push_back(QuicVersionMax()); return versions; } QuicHeadersStreamTest() : connection_(new StrictMock<MockConnection>(is_server(), GetVersions())), session_(connection_), headers_stream_(QuicSessionPeer::GetHeadersStream(&session_)), body_("hello world"), framer_(SPDY3) { headers_[":version"] = "HTTP/1.1"; headers_[":status"] = "200 Ok"; headers_["content-length"] = "11"; framer_.set_visitor(&visitor_); EXPECT_EQ(QuicVersionMax(), session_.connection()->version()); EXPECT_TRUE(headers_stream_ != NULL); } QuicConsumedData SaveIov(const IOVector& data) { const iovec* iov = data.iovec(); int count = data.Capacity(); for (int i = 0 ; i < count; ++i) { saved_data_.append(static_cast<char*>(iov[i].iov_base), iov[i].iov_len); } return QuicConsumedData(saved_data_.length(), false); } bool SaveHeaderData(const char* data, int len) { saved_header_data_.append(data, len); return true; } void SaveHeaderDataStringPiece(StringPiece data) { saved_header_data_.append(data.data(), data.length()); } void WriteHeadersAndExpectSynStream(QuicStreamId stream_id, bool fin, QuicPriority priority) { WriteHeadersAndCheckData(stream_id, fin, priority, SYN_STREAM); } void WriteHeadersAndExpectSynReply(QuicStreamId stream_id, bool fin) { WriteHeadersAndCheckData(stream_id, fin, 0, SYN_REPLY); } void WriteHeadersAndCheckData(QuicStreamId stream_id, bool fin, QuicPriority priority, SpdyFrameType type) { // Write the headers and capture the outgoing data EXPECT_CALL(session_, WritevData(kHeadersStreamId, _, _, false, _, NULL)) .WillOnce(WithArgs<1>(Invoke(this, &QuicHeadersStreamTest::SaveIov))); headers_stream_->WriteHeaders(stream_id, headers_, fin, NULL); // Parse the outgoing data and check that it matches was was written. if (type == SYN_STREAM) { EXPECT_CALL(visitor_, OnSynStream(stream_id, kNoAssociatedStream, 0, // priority, fin, kNotUnidirectional)); } else { EXPECT_CALL(visitor_, OnSynReply(stream_id, fin)); } EXPECT_CALL(visitor_, OnControlFrameHeaderData(stream_id, _, _)) .WillRepeatedly(WithArgs<1, 2>( Invoke(this, &QuicHeadersStreamTest::SaveHeaderData))); if (fin) { EXPECT_CALL(visitor_, OnStreamFrameData(stream_id, NULL, 0, true)); } framer_.ProcessInput(saved_data_.data(), saved_data_.length()); EXPECT_FALSE(framer_.HasError()) << framer_.error_code(); CheckHeaders(); saved_data_.clear(); } void CheckHeaders() { SpdyHeaderBlock headers; EXPECT_EQ(saved_header_data_.length(), framer_.ParseHeaderBlockInBuffer(saved_header_data_.data(), saved_header_data_.length(), &headers)); EXPECT_EQ(headers_, headers); saved_header_data_.clear(); } bool is_server() { return GetParam(); } void CloseConnection() { QuicConnectionPeer::CloseConnection(connection_); } static const bool kNotUnidirectional = false; static const bool kNoAssociatedStream = false; StrictMock<MockConnection>* connection_; StrictMock<MockSession> session_; QuicHeadersStream* headers_stream_; SpdyHeaderBlock headers_; string body_; string saved_data_; string saved_header_data_; SpdyFramer framer_; StrictMock<MockVisitor> visitor_; }; INSTANTIATE_TEST_CASE_P(Tests, QuicHeadersStreamTest, testing::Bool()); TEST_P(QuicHeadersStreamTest, StreamId) { EXPECT_EQ(3u, headers_stream_->id()); } TEST_P(QuicHeadersStreamTest, EffectivePriority) { EXPECT_EQ(0u, headers_stream_->EffectivePriority()); } TEST_P(QuicHeadersStreamTest, WriteHeaders) { for (QuicStreamId stream_id = kClientDataStreamId1; stream_id < kClientDataStreamId3; stream_id += 2) { for (int count = 0; count < 2; ++count) { bool fin = (count == 0); if (is_server()) { WriteHeadersAndExpectSynReply(stream_id, fin); } else { for (QuicPriority priority = 0; priority < 7; ++priority) { WriteHeadersAndExpectSynStream(stream_id, fin, priority); } } } } } TEST_P(QuicHeadersStreamTest, ProcessRawData) { for (QuicStreamId stream_id = kClientDataStreamId1; stream_id < kClientDataStreamId3; stream_id += 2) { for (int count = 0; count < 2; ++count) { bool fin = (count == 0); for (QuicPriority priority = 0; priority < 7; ++priority) { // Replace with "WriteHeadersAndSaveData" scoped_ptr<SpdySerializedFrame> frame; if (is_server()) { SpdySynStreamIR syn_stream(stream_id); syn_stream.set_name_value_block(headers_); syn_stream.set_fin(fin); frame.reset(framer_.SerializeSynStream(syn_stream)); EXPECT_CALL(session_, OnStreamHeadersPriority(stream_id, 0)); } else { SpdySynReplyIR syn_reply(stream_id); syn_reply.set_name_value_block(headers_); syn_reply.set_fin(fin); frame.reset(framer_.SerializeSynReply(syn_reply)); } EXPECT_CALL(session_, OnStreamHeaders(stream_id, _)) .WillRepeatedly(WithArgs<1>( Invoke(this, &QuicHeadersStreamTest::SaveHeaderDataStringPiece))); EXPECT_CALL(session_, OnStreamHeadersComplete(stream_id, fin, frame->size())); headers_stream_->ProcessRawData(frame->data(), frame->size()); CheckHeaders(); } } } } TEST_P(QuicHeadersStreamTest, ProcessSpdyDataFrame) { SpdyDataIR data(2, ""); scoped_ptr<SpdySerializedFrame> frame(framer_.SerializeFrame(data)); EXPECT_CALL(*connection_, SendConnectionCloseWithDetails(QUIC_INVALID_HEADERS_STREAM_DATA, "SPDY DATA frame received.")) .WillOnce(InvokeWithoutArgs(this, &QuicHeadersStreamTest::CloseConnection)); headers_stream_->ProcessRawData(frame->data(), frame->size()); } TEST_P(QuicHeadersStreamTest, ProcessSpdyRstStreamFrame) { SpdyRstStreamIR data(2, RST_STREAM_PROTOCOL_ERROR, ""); scoped_ptr<SpdySerializedFrame> frame(framer_.SerializeFrame(data)); EXPECT_CALL(*connection_, SendConnectionCloseWithDetails( QUIC_INVALID_HEADERS_STREAM_DATA, "SPDY RST_STREAM frame received.")) .WillOnce(InvokeWithoutArgs(this, &QuicHeadersStreamTest::CloseConnection)); headers_stream_->ProcessRawData(frame->data(), frame->size()); } TEST_P(QuicHeadersStreamTest, ProcessSpdySettingsFrame) { SpdySettingsIR data; data.AddSetting(SETTINGS_UPLOAD_BANDWIDTH, true, true, 0); scoped_ptr<SpdySerializedFrame> frame(framer_.SerializeFrame(data)); EXPECT_CALL(*connection_, SendConnectionCloseWithDetails( QUIC_INVALID_HEADERS_STREAM_DATA, "SPDY SETTINGS frame received.")) .WillOnce(InvokeWithoutArgs(this, &QuicHeadersStreamTest::CloseConnection)); headers_stream_->ProcessRawData(frame->data(), frame->size()); } TEST_P(QuicHeadersStreamTest, ProcessSpdyPingFrame) { SpdyPingIR data(1); scoped_ptr<SpdySerializedFrame> frame(framer_.SerializeFrame(data)); EXPECT_CALL(*connection_, SendConnectionCloseWithDetails(QUIC_INVALID_HEADERS_STREAM_DATA, "SPDY PING frame received.")) .WillOnce(InvokeWithoutArgs(this, &QuicHeadersStreamTest::CloseConnection)); headers_stream_->ProcessRawData(frame->data(), frame->size()); } TEST_P(QuicHeadersStreamTest, ProcessSpdyGoAwayFrame) { SpdyGoAwayIR data(1, GOAWAY_PROTOCOL_ERROR, "go away"); scoped_ptr<SpdySerializedFrame> frame(framer_.SerializeFrame(data)); EXPECT_CALL(*connection_, SendConnectionCloseWithDetails(QUIC_INVALID_HEADERS_STREAM_DATA, "SPDY GOAWAY frame received.")) .WillOnce(InvokeWithoutArgs(this, &QuicHeadersStreamTest::CloseConnection)); headers_stream_->ProcessRawData(frame->data(), frame->size()); } TEST_P(QuicHeadersStreamTest, ProcessSpdyHeadersFrame) { SpdyHeadersIR data(1); scoped_ptr<SpdySerializedFrame> frame(framer_.SerializeFrame(data)); EXPECT_CALL(*connection_, SendConnectionCloseWithDetails(QUIC_INVALID_HEADERS_STREAM_DATA, "SPDY HEADERS frame received.")) .WillOnce(InvokeWithoutArgs(this, &QuicHeadersStreamTest::CloseConnection)); headers_stream_->ProcessRawData(frame->data(), frame->size()); } TEST_P(QuicHeadersStreamTest, ProcessSpdyWindowUpdateFrame) { SpdyWindowUpdateIR data(1, 1); scoped_ptr<SpdySerializedFrame> frame(framer_.SerializeFrame(data)); EXPECT_CALL(*connection_, SendConnectionCloseWithDetails( QUIC_INVALID_HEADERS_STREAM_DATA, "SPDY WINDOW_UPDATE frame received.")) .WillOnce(InvokeWithoutArgs(this, &QuicHeadersStreamTest::CloseConnection)); headers_stream_->ProcessRawData(frame->data(), frame->size()); } TEST_P(QuicHeadersStreamTest, NoConnectionLevelFlowControl) { if (connection_->version() < QUIC_VERSION_21) { EXPECT_FALSE(headers_stream_->flow_controller()->IsEnabled()); } else { EXPECT_TRUE(headers_stream_->flow_controller()->IsEnabled()); } EXPECT_FALSE(ReliableQuicStreamPeer::StreamContributesToConnectionFlowControl( headers_stream_)); } } // namespace } // namespace test } // namespace net