| // 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/third_party/quiche/src/quic/core/http/quic_headers_stream.h" |
| |
| #include <cstdint> |
| #include <ostream> |
| #include <tuple> |
| #include <utility> |
| #include <vector> |
| |
| #include "net/third_party/quiche/src/quic/core/http/spdy_utils.h" |
| #include "net/third_party/quiche/src/quic/core/quic_data_writer.h" |
| #include "net/third_party/quiche/src/quic/core/quic_utils.h" |
| #include "net/third_party/quiche/src/quic/platform/api/quic_bug_tracker.h" |
| #include "net/third_party/quiche/src/quic/platform/api/quic_expect_bug.h" |
| #include "net/third_party/quiche/src/quic/platform/api/quic_flags.h" |
| #include "net/third_party/quiche/src/quic/platform/api/quic_logging.h" |
| #include "net/third_party/quiche/src/quic/platform/api/quic_ptr_util.h" |
| #include "net/third_party/quiche/src/quic/platform/api/quic_str_cat.h" |
| #include "net/third_party/quiche/src/quic/platform/api/quic_string.h" |
| #include "net/third_party/quiche/src/quic/platform/api/quic_string_piece.h" |
| #include "net/third_party/quiche/src/quic/platform/api/quic_test.h" |
| #include "net/third_party/quiche/src/quic/test_tools/quic_connection_peer.h" |
| #include "net/third_party/quiche/src/quic/test_tools/quic_spdy_session_peer.h" |
| #include "net/third_party/quiche/src/quic/test_tools/quic_stream_peer.h" |
| #include "net/third_party/quiche/src/quic/test_tools/quic_test_utils.h" |
| #include "net/third_party/quiche/src/spdy/core/http2_frame_decoder_adapter.h" |
| #include "net/third_party/quiche/src/spdy/core/spdy_alt_svc_wire_format.h" |
| #include "net/third_party/quiche/src/spdy/core/spdy_protocol.h" |
| #include "net/third_party/quiche/src/spdy/core/spdy_test_utils.h" |
| |
| using spdy::ERROR_CODE_PROTOCOL_ERROR; |
| using spdy::SETTINGS_ENABLE_PUSH; |
| using spdy::SETTINGS_HEADER_TABLE_SIZE; |
| using spdy::SETTINGS_INITIAL_WINDOW_SIZE; |
| using spdy::SETTINGS_MAX_CONCURRENT_STREAMS; |
| using spdy::SETTINGS_MAX_FRAME_SIZE; |
| using spdy::SETTINGS_MAX_HEADER_LIST_SIZE; |
| using spdy::Spdy3PriorityToHttp2Weight; |
| using spdy::SpdyAltSvcWireFormat; |
| using spdy::SpdyDataIR; |
| using spdy::SpdyErrorCode; |
| using spdy::SpdyFramer; |
| using spdy::SpdyFramerVisitorInterface; |
| using spdy::SpdyGoAwayIR; |
| using spdy::SpdyHeaderBlock; |
| using spdy::SpdyHeadersHandlerInterface; |
| using spdy::SpdyHeadersIR; |
| using spdy::SpdyKnownSettingsId; |
| using spdy::SpdyPingId; |
| using spdy::SpdyPingIR; |
| using spdy::SpdyPriority; |
| using spdy::SpdyPriorityIR; |
| using spdy::SpdyPushPromiseIR; |
| using spdy::SpdyRstStreamIR; |
| using spdy::SpdySerializedFrame; |
| using spdy::SpdySettingsId; |
| using spdy::SpdySettingsIR; |
| using spdy::SpdyStreamId; |
| using spdy::SpdyWindowUpdateIR; |
| using spdy::test::TestHeadersHandler; |
| using testing::_; |
| using testing::AtLeast; |
| using testing::InSequence; |
| using testing::Invoke; |
| using testing::Return; |
| using testing::StrictMock; |
| using testing::WithArgs; |
| |
| namespace quic { |
| namespace test { |
| |
| class MockQuicHpackDebugVisitor : public QuicHpackDebugVisitor { |
| public: |
| MockQuicHpackDebugVisitor() : QuicHpackDebugVisitor() {} |
| MockQuicHpackDebugVisitor(const MockQuicHpackDebugVisitor&) = delete; |
| MockQuicHpackDebugVisitor& operator=(const MockQuicHpackDebugVisitor&) = |
| delete; |
| |
| MOCK_METHOD1(OnUseEntry, void(QuicTime::Delta elapsed)); |
| }; |
| |
| namespace { |
| |
| class MockVisitor : public SpdyFramerVisitorInterface { |
| public: |
| 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(OnStreamPadding, void(SpdyStreamId stream_id, size_t len)); |
| MOCK_METHOD1(OnHeaderFrameStart, |
| SpdyHeadersHandlerInterface*(SpdyStreamId stream_id)); |
| MOCK_METHOD1(OnHeaderFrameEnd, void(SpdyStreamId stream_id)); |
| MOCK_METHOD3(OnControlFrameHeaderData, |
| bool(SpdyStreamId stream_id, |
| const char* header_data, |
| size_t len)); |
| MOCK_METHOD2(OnRstStream, |
| void(SpdyStreamId stream_id, SpdyErrorCode error_code)); |
| MOCK_METHOD0(OnSettings, void()); |
| MOCK_METHOD2(OnSetting, void(SpdySettingsId id, uint32_t 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, |
| 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_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_METHOD3(OnAltSvc, |
| void(SpdyStreamId stream_id, |
| QuicStringPiece 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)); |
| }; |
| |
| struct TestParams { |
| TestParams(const ParsedQuicVersion& version, Perspective perspective) |
| : version(version), perspective(perspective) { |
| QUIC_LOG(INFO) << "TestParams: version: " |
| << ParsedQuicVersionToString(version) |
| << ", perspective: " << perspective; |
| } |
| |
| TestParams(const TestParams& other) |
| : version(other.version), perspective(other.perspective) {} |
| |
| ParsedQuicVersion version; |
| Perspective perspective; |
| }; |
| |
| std::vector<TestParams> GetTestParams() { |
| std::vector<TestParams> params; |
| ParsedQuicVersionVector all_supported_versions = AllSupportedVersions(); |
| for (size_t i = 0; i < all_supported_versions.size(); ++i) { |
| for (Perspective p : {Perspective::IS_SERVER, Perspective::IS_CLIENT}) { |
| params.emplace_back(all_supported_versions[i], p); |
| } |
| } |
| return params; |
| } |
| |
| class QuicHeadersStreamTest : public QuicTestWithParam<TestParams> { |
| public: |
| QuicHeadersStreamTest() |
| : connection_(new StrictMock<MockQuicConnection>(&helper_, |
| &alarm_factory_, |
| perspective(), |
| GetVersion())), |
| session_(connection_), |
| body_("hello world"), |
| stream_frame_( |
| QuicUtils::GetHeadersStreamId(connection_->transport_version()), |
| /*fin=*/false, |
| /*offset=*/0, |
| ""), |
| next_promised_stream_id_(2) { |
| session_.Initialize(); |
| headers_stream_ = QuicSpdySessionPeer::GetHeadersStream(&session_); |
| headers_[":version"] = "HTTP/1.1"; |
| headers_[":status"] = "200 Ok"; |
| headers_["content-length"] = "11"; |
| framer_ = std::unique_ptr<SpdyFramer>( |
| new SpdyFramer(SpdyFramer::ENABLE_COMPRESSION)); |
| deframer_ = std::unique_ptr<http2::Http2DecoderAdapter>( |
| new http2::Http2DecoderAdapter()); |
| deframer_->set_visitor(&visitor_); |
| EXPECT_EQ(transport_version(), session_.connection()->transport_version()); |
| EXPECT_TRUE(headers_stream_ != nullptr); |
| connection_->AdvanceTime(QuicTime::Delta::FromMilliseconds(1)); |
| client_id_1_ = |
| QuicSpdySessionPeer::GetNthClientInitiatedBidirectionalStreamId( |
| session_, 0); |
| client_id_2_ = |
| QuicSpdySessionPeer::GetNthClientInitiatedBidirectionalStreamId( |
| session_, 1); |
| client_id_3_ = |
| QuicSpdySessionPeer::GetNthClientInitiatedBidirectionalStreamId( |
| session_, 2); |
| next_stream_id_ = QuicSpdySessionPeer::StreamIdDelta(session_); |
| } |
| |
| QuicStreamId GetNthClientInitiatedId(int n) { |
| return QuicSpdySessionPeer::GetNthClientInitiatedBidirectionalStreamId( |
| session_, n); |
| } |
| |
| QuicConsumedData SaveIov(size_t write_length) { |
| char* buf = new char[write_length]; |
| QuicDataWriter writer(write_length, buf, NETWORK_BYTE_ORDER); |
| headers_stream_->WriteStreamData(headers_stream_->stream_bytes_written(), |
| write_length, &writer); |
| saved_data_.append(buf, write_length); |
| delete[] buf; |
| return QuicConsumedData(write_length, false); |
| } |
| |
| void SavePayload(const char* data, size_t len) { |
| saved_payloads_.append(data, len); |
| } |
| |
| bool SaveHeaderData(const char* data, int len) { |
| saved_header_data_.append(data, len); |
| return true; |
| } |
| |
| void SaveHeaderDataStringPiece(QuicStringPiece data) { |
| saved_header_data_.append(data.data(), data.length()); |
| } |
| |
| void SavePromiseHeaderList(QuicStreamId /* stream_id */, |
| QuicStreamId /* promised_stream_id */, |
| size_t size, |
| const QuicHeaderList& header_list) { |
| SaveToHandler(size, header_list); |
| } |
| |
| void SaveHeaderList(QuicStreamId /* stream_id */, |
| bool /* fin */, |
| size_t size, |
| const QuicHeaderList& header_list) { |
| SaveToHandler(size, header_list); |
| } |
| |
| void SaveToHandler(size_t size, const QuicHeaderList& header_list) { |
| headers_handler_ = QuicMakeUnique<TestHeadersHandler>(); |
| headers_handler_->OnHeaderBlockStart(); |
| for (const auto& p : header_list) { |
| headers_handler_->OnHeader(p.first, p.second); |
| } |
| headers_handler_->OnHeaderBlockEnd(size, size); |
| } |
| |
| void WriteAndExpectRequestHeaders(QuicStreamId stream_id, |
| bool fin, |
| SpdyPriority priority) { |
| WriteHeadersAndCheckData(stream_id, fin, priority, true /*is_request*/); |
| } |
| |
| void WriteAndExpectResponseHeaders(QuicStreamId stream_id, bool fin) { |
| WriteHeadersAndCheckData(stream_id, fin, 0, false /*is_request*/); |
| } |
| |
| void WriteHeadersAndCheckData(QuicStreamId stream_id, |
| bool fin, |
| SpdyPriority priority, |
| bool is_request) { |
| // Write the headers and capture the outgoing data |
| EXPECT_CALL(session_, WritevData(headers_stream_, |
| QuicUtils::GetHeadersStreamId( |
| connection_->transport_version()), |
| _, _, NO_FIN)) |
| .WillOnce(WithArgs<2>(Invoke(this, &QuicHeadersStreamTest::SaveIov))); |
| QuicSpdySessionPeer::WriteHeadersImpl( |
| &session_, stream_id, headers_.Clone(), fin, |
| Spdy3PriorityToHttp2Weight(priority), 0, false, nullptr); |
| |
| // Parse the outgoing data and check that it matches was was written. |
| if (is_request) { |
| EXPECT_CALL(visitor_, |
| OnHeaders(stream_id, kHasPriority, |
| Spdy3PriorityToHttp2Weight(priority), |
| /*parent_stream_id=*/0, |
| /*exclusive=*/false, fin, kFrameComplete)); |
| } else { |
| EXPECT_CALL(visitor_, |
| OnHeaders(stream_id, !kHasPriority, |
| /*priority=*/0, |
| /*parent_stream_id=*/0, |
| /*exclusive=*/false, fin, kFrameComplete)); |
| } |
| headers_handler_ = QuicMakeUnique<TestHeadersHandler>(); |
| EXPECT_CALL(visitor_, OnHeaderFrameStart(stream_id)) |
| .WillOnce(Return(headers_handler_.get())); |
| EXPECT_CALL(visitor_, OnHeaderFrameEnd(stream_id)).Times(1); |
| if (fin) { |
| EXPECT_CALL(visitor_, OnStreamEnd(stream_id)); |
| } |
| deframer_->ProcessInput(saved_data_.data(), saved_data_.length()); |
| EXPECT_FALSE(deframer_->HasError()) |
| << http2::Http2DecoderAdapter::SpdyFramerErrorToString( |
| deframer_->spdy_framer_error()); |
| |
| CheckHeaders(); |
| saved_data_.clear(); |
| } |
| |
| void CheckHeaders() { |
| EXPECT_EQ(headers_, headers_handler_->decoded_block()); |
| headers_handler_.reset(); |
| } |
| |
| Perspective perspective() const { return GetParam().perspective; } |
| |
| QuicTransportVersion transport_version() const { |
| return GetParam().version.transport_version; |
| } |
| |
| ParsedQuicVersionVector GetVersion() { |
| ParsedQuicVersionVector versions; |
| versions.push_back(GetParam().version); |
| return versions; |
| } |
| |
| void TearDownLocalConnectionState() { |
| QuicConnectionPeer::TearDownLocalConnectionState(connection_); |
| } |
| |
| QuicStreamId NextPromisedStreamId() { |
| return next_promised_stream_id_ += next_stream_id_; |
| } |
| |
| static const bool kFrameComplete = true; |
| static const bool kHasPriority = true; |
| |
| MockQuicConnectionHelper helper_; |
| MockAlarmFactory alarm_factory_; |
| StrictMock<MockQuicConnection>* connection_; |
| StrictMock<MockQuicSpdySession> session_; |
| QuicHeadersStream* headers_stream_; |
| SpdyHeaderBlock headers_; |
| std::unique_ptr<TestHeadersHandler> headers_handler_; |
| QuicString body_; |
| QuicString saved_data_; |
| QuicString saved_header_data_; |
| QuicString saved_payloads_; |
| std::unique_ptr<SpdyFramer> framer_; |
| std::unique_ptr<http2::Http2DecoderAdapter> deframer_; |
| StrictMock<MockVisitor> visitor_; |
| QuicStreamFrame stream_frame_; |
| QuicStreamId next_promised_stream_id_; |
| QuicStreamId client_id_1_; |
| QuicStreamId client_id_2_; |
| QuicStreamId client_id_3_; |
| QuicStreamId next_stream_id_; |
| }; |
| |
| // Run all tests with each version and perspective (client or server). |
| INSTANTIATE_TEST_CASE_P(Tests, |
| QuicHeadersStreamTest, |
| ::testing::ValuesIn(GetTestParams())); |
| |
| TEST_P(QuicHeadersStreamTest, StreamId) { |
| EXPECT_EQ(QuicUtils::GetHeadersStreamId(connection_->transport_version()), |
| headers_stream_->id()); |
| } |
| |
| TEST_P(QuicHeadersStreamTest, WriteHeaders) { |
| for (QuicStreamId stream_id = client_id_1_; stream_id < client_id_3_; |
| stream_id += next_stream_id_) { |
| for (bool fin : {false, true}) { |
| if (perspective() == Perspective::IS_SERVER) { |
| WriteAndExpectResponseHeaders(stream_id, fin); |
| } else { |
| for (SpdyPriority priority = 0; priority < 7; ++priority) { |
| // TODO(rch): implement priorities correctly. |
| WriteAndExpectRequestHeaders(stream_id, fin, 0); |
| } |
| } |
| } |
| } |
| } |
| |
| TEST_P(QuicHeadersStreamTest, WritePushPromises) { |
| for (QuicStreamId stream_id = client_id_1_; stream_id < client_id_3_; |
| stream_id += next_stream_id_) { |
| QuicStreamId promised_stream_id = NextPromisedStreamId(); |
| if (perspective() == Perspective::IS_SERVER) { |
| // Write the headers and capture the outgoing data |
| EXPECT_CALL(session_, WritevData(headers_stream_, |
| QuicUtils::GetHeadersStreamId( |
| connection_->transport_version()), |
| _, _, NO_FIN)) |
| .WillOnce(WithArgs<2>(Invoke(this, &QuicHeadersStreamTest::SaveIov))); |
| session_.WritePushPromise(stream_id, promised_stream_id, |
| headers_.Clone()); |
| |
| // Parse the outgoing data and check that it matches was was written. |
| EXPECT_CALL(visitor_, |
| OnPushPromise(stream_id, promised_stream_id, kFrameComplete)); |
| headers_handler_ = QuicMakeUnique<TestHeadersHandler>(); |
| EXPECT_CALL(visitor_, OnHeaderFrameStart(stream_id)) |
| .WillOnce(Return(headers_handler_.get())); |
| EXPECT_CALL(visitor_, OnHeaderFrameEnd(stream_id)).Times(1); |
| deframer_->ProcessInput(saved_data_.data(), saved_data_.length()); |
| EXPECT_FALSE(deframer_->HasError()) |
| << http2::Http2DecoderAdapter::SpdyFramerErrorToString( |
| deframer_->spdy_framer_error()); |
| CheckHeaders(); |
| saved_data_.clear(); |
| } else { |
| EXPECT_QUIC_BUG(session_.WritePushPromise(stream_id, promised_stream_id, |
| headers_.Clone()), |
| "Client shouldn't send PUSH_PROMISE"); |
| } |
| } |
| } |
| |
| TEST_P(QuicHeadersStreamTest, ProcessRawData) { |
| for (QuicStreamId stream_id = client_id_1_; stream_id < client_id_3_; |
| stream_id += next_stream_id_) { |
| for (bool fin : {false, true}) { |
| for (SpdyPriority priority = 0; priority < 7; ++priority) { |
| // Replace with "WriteHeadersAndSaveData" |
| SpdySerializedFrame frame; |
| if (perspective() == Perspective::IS_SERVER) { |
| SpdyHeadersIR headers_frame(stream_id, headers_.Clone()); |
| headers_frame.set_fin(fin); |
| headers_frame.set_has_priority(true); |
| headers_frame.set_weight(Spdy3PriorityToHttp2Weight(0)); |
| frame = framer_->SerializeFrame(headers_frame); |
| EXPECT_CALL(session_, OnStreamHeadersPriority(stream_id, 0)); |
| } else { |
| SpdyHeadersIR headers_frame(stream_id, headers_.Clone()); |
| headers_frame.set_fin(fin); |
| frame = framer_->SerializeFrame(headers_frame); |
| } |
| EXPECT_CALL(session_, |
| OnStreamHeaderList(stream_id, fin, frame.size(), _)) |
| .WillOnce(Invoke(this, &QuicHeadersStreamTest::SaveHeaderList)); |
| stream_frame_.data_buffer = frame.data(); |
| stream_frame_.data_length = frame.size(); |
| headers_stream_->OnStreamFrame(stream_frame_); |
| stream_frame_.offset += frame.size(); |
| CheckHeaders(); |
| } |
| } |
| } |
| } |
| |
| TEST_P(QuicHeadersStreamTest, ProcessPushPromise) { |
| if (perspective() == Perspective::IS_SERVER) { |
| return; |
| } |
| for (QuicStreamId stream_id = client_id_1_; stream_id < client_id_3_; |
| stream_id += next_stream_id_) { |
| QuicStreamId promised_stream_id = NextPromisedStreamId(); |
| SpdyPushPromiseIR push_promise(stream_id, promised_stream_id, |
| headers_.Clone()); |
| SpdySerializedFrame frame(framer_->SerializeFrame(push_promise)); |
| if (perspective() == Perspective::IS_SERVER) { |
| EXPECT_CALL(*connection_, |
| CloseConnection(QUIC_INVALID_HEADERS_STREAM_DATA, |
| "PUSH_PROMISE not supported.", _)) |
| .WillRepeatedly(InvokeWithoutArgs( |
| this, &QuicHeadersStreamTest::TearDownLocalConnectionState)); |
| } else { |
| EXPECT_CALL(session_, OnPromiseHeaderList(stream_id, promised_stream_id, |
| frame.size(), _)) |
| .WillOnce( |
| Invoke(this, &QuicHeadersStreamTest::SavePromiseHeaderList)); |
| } |
| stream_frame_.data_buffer = frame.data(); |
| stream_frame_.data_length = frame.size(); |
| headers_stream_->OnStreamFrame(stream_frame_); |
| if (perspective() == Perspective::IS_CLIENT) { |
| stream_frame_.offset += frame.size(); |
| CheckHeaders(); |
| } |
| } |
| } |
| |
| TEST_P(QuicHeadersStreamTest, ProcessPriorityFrame) { |
| QuicStreamId parent_stream_id = 0; |
| for (SpdyPriority priority = 0; priority < 7; ++priority) { |
| for (QuicStreamId stream_id = client_id_1_; stream_id < client_id_3_; |
| stream_id += next_stream_id_) { |
| int weight = Spdy3PriorityToHttp2Weight(priority); |
| SpdyPriorityIR priority_frame(stream_id, parent_stream_id, weight, true); |
| SpdySerializedFrame frame(framer_->SerializeFrame(priority_frame)); |
| parent_stream_id = stream_id; |
| if (transport_version() <= QUIC_VERSION_39) { |
| EXPECT_CALL(*connection_, |
| CloseConnection(QUIC_INVALID_HEADERS_STREAM_DATA, |
| "SPDY PRIORITY frame received.", _)) |
| .WillRepeatedly(InvokeWithoutArgs( |
| this, &QuicHeadersStreamTest::TearDownLocalConnectionState)); |
| } else if (perspective() == Perspective::IS_CLIENT) { |
| EXPECT_CALL(*connection_, |
| CloseConnection(QUIC_INVALID_HEADERS_STREAM_DATA, |
| "Server must not send PRIORITY frames.", _)) |
| .WillRepeatedly(InvokeWithoutArgs( |
| this, &QuicHeadersStreamTest::TearDownLocalConnectionState)); |
| } else { |
| EXPECT_CALL(session_, OnPriorityFrame(stream_id, priority)).Times(1); |
| } |
| stream_frame_.data_buffer = frame.data(); |
| stream_frame_.data_length = frame.size(); |
| headers_stream_->OnStreamFrame(stream_frame_); |
| stream_frame_.offset += frame.size(); |
| } |
| } |
| } |
| |
| TEST_P(QuicHeadersStreamTest, ProcessPushPromiseDisabledSetting) { |
| session_.OnConfigNegotiated(); |
| SpdySettingsIR data; |
| // Respect supported settings frames SETTINGS_ENABLE_PUSH. |
| data.AddSetting(SETTINGS_ENABLE_PUSH, 0); |
| SpdySerializedFrame frame(framer_->SerializeFrame(data)); |
| stream_frame_.data_buffer = frame.data(); |
| stream_frame_.data_length = frame.size(); |
| if (perspective() == Perspective::IS_CLIENT) { |
| EXPECT_CALL( |
| *connection_, |
| CloseConnection(QUIC_INVALID_HEADERS_STREAM_DATA, |
| "Unsupported field of HTTP/2 SETTINGS frame: 2", _)); |
| } |
| headers_stream_->OnStreamFrame(stream_frame_); |
| EXPECT_EQ(session_.server_push_enabled(), |
| perspective() == Perspective::IS_CLIENT); |
| } |
| |
| TEST_P(QuicHeadersStreamTest, ProcessLargeRawData) { |
| QuicSpdySessionPeer::SetMaxUncompressedHeaderBytes(&session_, 256 * 1024); |
| // We want to create a frame that is more than the SPDY Framer's max control |
| // frame size, which is 16K, but less than the HPACK decoders max decode |
| // buffer size, which is 32K. |
| headers_["key0"] = QuicString(1 << 13, '.'); |
| headers_["key1"] = QuicString(1 << 13, '.'); |
| headers_["key2"] = QuicString(1 << 13, '.'); |
| for (QuicStreamId stream_id = client_id_1_; stream_id < client_id_3_; |
| stream_id += next_stream_id_) { |
| for (bool fin : {false, true}) { |
| for (SpdyPriority priority = 0; priority < 7; ++priority) { |
| // Replace with "WriteHeadersAndSaveData" |
| SpdySerializedFrame frame; |
| if (perspective() == Perspective::IS_SERVER) { |
| SpdyHeadersIR headers_frame(stream_id, headers_.Clone()); |
| headers_frame.set_fin(fin); |
| headers_frame.set_has_priority(true); |
| headers_frame.set_weight(Spdy3PriorityToHttp2Weight(0)); |
| frame = framer_->SerializeFrame(headers_frame); |
| EXPECT_CALL(session_, OnStreamHeadersPriority(stream_id, 0)); |
| } else { |
| SpdyHeadersIR headers_frame(stream_id, headers_.Clone()); |
| headers_frame.set_fin(fin); |
| frame = framer_->SerializeFrame(headers_frame); |
| } |
| EXPECT_CALL(session_, |
| OnStreamHeaderList(stream_id, fin, frame.size(), _)) |
| .WillOnce(Invoke(this, &QuicHeadersStreamTest::SaveHeaderList)); |
| stream_frame_.data_buffer = frame.data(); |
| stream_frame_.data_length = frame.size(); |
| headers_stream_->OnStreamFrame(stream_frame_); |
| stream_frame_.offset += frame.size(); |
| CheckHeaders(); |
| } |
| } |
| } |
| } |
| |
| TEST_P(QuicHeadersStreamTest, ProcessBadData) { |
| const char kBadData[] = "blah blah blah"; |
| EXPECT_CALL(*connection_, |
| CloseConnection(QUIC_INVALID_HEADERS_STREAM_DATA, _, _)) |
| .Times(::testing::AnyNumber()); |
| stream_frame_.data_buffer = kBadData; |
| stream_frame_.data_length = strlen(kBadData); |
| headers_stream_->OnStreamFrame(stream_frame_); |
| } |
| |
| TEST_P(QuicHeadersStreamTest, ProcessSpdyDataFrame) { |
| SpdyDataIR data(/* stream_id = */ 2, "ping"); |
| SpdySerializedFrame frame(framer_->SerializeFrame(data)); |
| |
| EXPECT_CALL(*connection_, CloseConnection(QUIC_INVALID_HEADERS_STREAM_DATA, |
| "SPDY DATA frame received.", _)) |
| .WillOnce(InvokeWithoutArgs( |
| this, &QuicHeadersStreamTest::TearDownLocalConnectionState)); |
| stream_frame_.data_buffer = frame.data(); |
| stream_frame_.data_length = frame.size(); |
| headers_stream_->OnStreamFrame(stream_frame_); |
| } |
| |
| TEST_P(QuicHeadersStreamTest, ProcessSpdyRstStreamFrame) { |
| SpdyRstStreamIR data(/* stream_id = */ 2, ERROR_CODE_PROTOCOL_ERROR); |
| SpdySerializedFrame frame(framer_->SerializeFrame(data)); |
| EXPECT_CALL(*connection_, |
| CloseConnection(QUIC_INVALID_HEADERS_STREAM_DATA, |
| "SPDY RST_STREAM frame received.", _)) |
| .WillOnce(InvokeWithoutArgs( |
| this, &QuicHeadersStreamTest::TearDownLocalConnectionState)); |
| stream_frame_.data_buffer = frame.data(); |
| stream_frame_.data_length = frame.size(); |
| headers_stream_->OnStreamFrame(stream_frame_); |
| } |
| |
| TEST_P(QuicHeadersStreamTest, RespectHttp2SettingsFrameSupportedFields) { |
| const uint32_t kTestHeaderTableSize = 1000; |
| SpdySettingsIR data; |
| // Respect supported settings frames SETTINGS_HEADER_TABLE_SIZE, |
| // SETTINGS_MAX_HEADER_LIST_SIZE. |
| data.AddSetting(SETTINGS_HEADER_TABLE_SIZE, kTestHeaderTableSize); |
| data.AddSetting(SETTINGS_MAX_HEADER_LIST_SIZE, 2000); |
| SpdySerializedFrame frame(framer_->SerializeFrame(data)); |
| stream_frame_.data_buffer = frame.data(); |
| stream_frame_.data_length = frame.size(); |
| headers_stream_->OnStreamFrame(stream_frame_); |
| EXPECT_EQ(kTestHeaderTableSize, QuicSpdySessionPeer::GetSpdyFramer(&session_) |
| .header_encoder_table_size()); |
| } |
| |
| TEST_P(QuicHeadersStreamTest, RespectHttp2SettingsFrameUnsupportedFields) { |
| SpdySettingsIR data; |
| // Does not support SETTINGS_MAX_CONCURRENT_STREAMS, |
| // SETTINGS_INITIAL_WINDOW_SIZE, SETTINGS_ENABLE_PUSH and |
| // SETTINGS_MAX_FRAME_SIZE. |
| data.AddSetting(SETTINGS_MAX_CONCURRENT_STREAMS, 100); |
| data.AddSetting(SETTINGS_INITIAL_WINDOW_SIZE, 100); |
| data.AddSetting(SETTINGS_ENABLE_PUSH, 1); |
| data.AddSetting(SETTINGS_MAX_FRAME_SIZE, 1250); |
| SpdySerializedFrame frame(framer_->SerializeFrame(data)); |
| EXPECT_CALL( |
| *connection_, |
| CloseConnection(QUIC_INVALID_HEADERS_STREAM_DATA, |
| QuicStrCat("Unsupported field of HTTP/2 SETTINGS frame: ", |
| SETTINGS_MAX_CONCURRENT_STREAMS), |
| _)); |
| EXPECT_CALL( |
| *connection_, |
| CloseConnection(QUIC_INVALID_HEADERS_STREAM_DATA, |
| QuicStrCat("Unsupported field of HTTP/2 SETTINGS frame: ", |
| SETTINGS_INITIAL_WINDOW_SIZE), |
| _)); |
| if (session_.perspective() == Perspective::IS_CLIENT) { |
| EXPECT_CALL(*connection_, |
| CloseConnection( |
| QUIC_INVALID_HEADERS_STREAM_DATA, |
| QuicStrCat("Unsupported field of HTTP/2 SETTINGS frame: ", |
| SETTINGS_ENABLE_PUSH), |
| _)); |
| } |
| EXPECT_CALL( |
| *connection_, |
| CloseConnection(QUIC_INVALID_HEADERS_STREAM_DATA, |
| QuicStrCat("Unsupported field of HTTP/2 SETTINGS frame: ", |
| SETTINGS_MAX_FRAME_SIZE), |
| _)); |
| stream_frame_.data_buffer = frame.data(); |
| stream_frame_.data_length = frame.size(); |
| headers_stream_->OnStreamFrame(stream_frame_); |
| } |
| |
| TEST_P(QuicHeadersStreamTest, ProcessSpdyPingFrame) { |
| SpdyPingIR data(1); |
| SpdySerializedFrame frame(framer_->SerializeFrame(data)); |
| EXPECT_CALL(*connection_, CloseConnection(QUIC_INVALID_HEADERS_STREAM_DATA, |
| "SPDY PING frame received.", _)) |
| .WillOnce(InvokeWithoutArgs( |
| this, &QuicHeadersStreamTest::TearDownLocalConnectionState)); |
| stream_frame_.data_buffer = frame.data(); |
| stream_frame_.data_length = frame.size(); |
| headers_stream_->OnStreamFrame(stream_frame_); |
| } |
| |
| TEST_P(QuicHeadersStreamTest, ProcessSpdyGoAwayFrame) { |
| SpdyGoAwayIR data(/* last_good_stream_id = */ 1, ERROR_CODE_PROTOCOL_ERROR, |
| "go away"); |
| SpdySerializedFrame frame(framer_->SerializeFrame(data)); |
| EXPECT_CALL(*connection_, CloseConnection(QUIC_INVALID_HEADERS_STREAM_DATA, |
| "SPDY GOAWAY frame received.", _)) |
| .WillOnce(InvokeWithoutArgs( |
| this, &QuicHeadersStreamTest::TearDownLocalConnectionState)); |
| stream_frame_.data_buffer = frame.data(); |
| stream_frame_.data_length = frame.size(); |
| headers_stream_->OnStreamFrame(stream_frame_); |
| } |
| |
| TEST_P(QuicHeadersStreamTest, ProcessSpdyWindowUpdateFrame) { |
| SpdyWindowUpdateIR data(/* stream_id = */ 1, /* delta = */ 1); |
| SpdySerializedFrame frame(framer_->SerializeFrame(data)); |
| EXPECT_CALL(*connection_, |
| CloseConnection(QUIC_INVALID_HEADERS_STREAM_DATA, |
| "SPDY WINDOW_UPDATE frame received.", _)) |
| .WillOnce(InvokeWithoutArgs( |
| this, &QuicHeadersStreamTest::TearDownLocalConnectionState)); |
| stream_frame_.data_buffer = frame.data(); |
| stream_frame_.data_length = frame.size(); |
| headers_stream_->OnStreamFrame(stream_frame_); |
| } |
| |
| TEST_P(QuicHeadersStreamTest, NoConnectionLevelFlowControl) { |
| EXPECT_FALSE(QuicStreamPeer::StreamContributesToConnectionFlowControl( |
| headers_stream_)); |
| } |
| |
| TEST_P(QuicHeadersStreamTest, HpackDecoderDebugVisitor) { |
| auto hpack_decoder_visitor = |
| QuicMakeUnique<StrictMock<MockQuicHpackDebugVisitor>>(); |
| { |
| InSequence seq; |
| // Number of indexed representations generated in headers below. |
| for (int i = 1; i < 28; i++) { |
| EXPECT_CALL(*hpack_decoder_visitor, |
| OnUseEntry(QuicTime::Delta::FromMilliseconds(i))) |
| .Times(4); |
| } |
| } |
| QuicSpdySessionPeer::SetHpackDecoderDebugVisitor( |
| &session_, std::move(hpack_decoder_visitor)); |
| |
| // Create some headers we expect to generate entries in HPACK's |
| // dynamic table, in addition to content-length. |
| headers_["key0"] = QuicString(1 << 1, '.'); |
| headers_["key1"] = QuicString(1 << 2, '.'); |
| headers_["key2"] = QuicString(1 << 3, '.'); |
| for (QuicStreamId stream_id = client_id_1_; stream_id < client_id_3_; |
| stream_id += next_stream_id_) { |
| for (bool fin : {false, true}) { |
| for (SpdyPriority priority = 0; priority < 7; ++priority) { |
| // Replace with "WriteHeadersAndSaveData" |
| SpdySerializedFrame frame; |
| if (perspective() == Perspective::IS_SERVER) { |
| SpdyHeadersIR headers_frame(stream_id, headers_.Clone()); |
| headers_frame.set_fin(fin); |
| headers_frame.set_has_priority(true); |
| headers_frame.set_weight(Spdy3PriorityToHttp2Weight(0)); |
| frame = framer_->SerializeFrame(headers_frame); |
| EXPECT_CALL(session_, OnStreamHeadersPriority(stream_id, 0)); |
| } else { |
| SpdyHeadersIR headers_frame(stream_id, headers_.Clone()); |
| headers_frame.set_fin(fin); |
| frame = framer_->SerializeFrame(headers_frame); |
| } |
| EXPECT_CALL(session_, |
| OnStreamHeaderList(stream_id, fin, frame.size(), _)) |
| .WillOnce(Invoke(this, &QuicHeadersStreamTest::SaveHeaderList)); |
| stream_frame_.data_buffer = frame.data(); |
| stream_frame_.data_length = frame.size(); |
| connection_->AdvanceTime(QuicTime::Delta::FromMilliseconds(1)); |
| headers_stream_->OnStreamFrame(stream_frame_); |
| stream_frame_.offset += frame.size(); |
| CheckHeaders(); |
| } |
| } |
| } |
| } |
| |
| TEST_P(QuicHeadersStreamTest, HpackEncoderDebugVisitor) { |
| auto hpack_encoder_visitor = |
| QuicMakeUnique<StrictMock<MockQuicHpackDebugVisitor>>(); |
| if (perspective() == Perspective::IS_SERVER) { |
| InSequence seq; |
| for (int i = 1; i < 4; i++) { |
| EXPECT_CALL(*hpack_encoder_visitor, |
| OnUseEntry(QuicTime::Delta::FromMilliseconds(i))); |
| } |
| } else { |
| InSequence seq; |
| for (int i = 1; i < 28; i++) { |
| EXPECT_CALL(*hpack_encoder_visitor, |
| OnUseEntry(QuicTime::Delta::FromMilliseconds(i))); |
| } |
| } |
| QuicSpdySessionPeer::SetHpackEncoderDebugVisitor( |
| &session_, std::move(hpack_encoder_visitor)); |
| |
| for (QuicStreamId stream_id = client_id_1_; stream_id < client_id_3_; |
| stream_id += next_stream_id_) { |
| for (bool fin : {false, true}) { |
| if (perspective() == Perspective::IS_SERVER) { |
| WriteAndExpectResponseHeaders(stream_id, fin); |
| connection_->AdvanceTime(QuicTime::Delta::FromMilliseconds(1)); |
| } else { |
| for (SpdyPriority priority = 0; priority < 7; ++priority) { |
| // TODO(rch): implement priorities correctly. |
| WriteAndExpectRequestHeaders(stream_id, fin, 0); |
| connection_->AdvanceTime(QuicTime::Delta::FromMilliseconds(1)); |
| } |
| } |
| } |
| } |
| } |
| |
| TEST_P(QuicHeadersStreamTest, AckSentData) { |
| EXPECT_CALL(session_, WritevData(headers_stream_, |
| QuicUtils::GetHeadersStreamId( |
| connection_->transport_version()), |
| _, _, NO_FIN)) |
| .WillRepeatedly(Invoke(MockQuicSession::ConsumeData)); |
| InSequence s; |
| QuicReferenceCountedPointer<MockAckListener> ack_listener1( |
| new MockAckListener()); |
| QuicReferenceCountedPointer<MockAckListener> ack_listener2( |
| new MockAckListener()); |
| QuicReferenceCountedPointer<MockAckListener> ack_listener3( |
| new MockAckListener()); |
| |
| // Packet 1. |
| headers_stream_->WriteOrBufferData("Header5", false, ack_listener1); |
| headers_stream_->WriteOrBufferData("Header5", false, ack_listener1); |
| headers_stream_->WriteOrBufferData("Header7", false, ack_listener2); |
| |
| // Packet 2. |
| headers_stream_->WriteOrBufferData("Header9", false, ack_listener3); |
| headers_stream_->WriteOrBufferData("Header7", false, ack_listener2); |
| |
| // Packet 3. |
| headers_stream_->WriteOrBufferData("Header9", false, ack_listener3); |
| |
| // Packet 2 gets retransmitted. |
| EXPECT_CALL(*ack_listener3, OnPacketRetransmitted(7)).Times(1); |
| EXPECT_CALL(*ack_listener2, OnPacketRetransmitted(7)).Times(1); |
| headers_stream_->OnStreamFrameRetransmitted(21, 7, false); |
| headers_stream_->OnStreamFrameRetransmitted(28, 7, false); |
| |
| // Packets are acked in order: 2, 3, 1. |
| EXPECT_CALL(*ack_listener3, OnPacketAcked(7, _)); |
| EXPECT_CALL(*ack_listener2, OnPacketAcked(7, _)); |
| EXPECT_TRUE(headers_stream_->OnStreamFrameAcked(21, 7, false, |
| QuicTime::Delta::Zero())); |
| EXPECT_TRUE(headers_stream_->OnStreamFrameAcked(28, 7, false, |
| QuicTime::Delta::Zero())); |
| |
| EXPECT_CALL(*ack_listener3, OnPacketAcked(7, _)); |
| EXPECT_TRUE(headers_stream_->OnStreamFrameAcked(35, 7, false, |
| QuicTime::Delta::Zero())); |
| |
| EXPECT_CALL(*ack_listener1, OnPacketAcked(7, _)); |
| EXPECT_CALL(*ack_listener1, OnPacketAcked(7, _)); |
| EXPECT_TRUE(headers_stream_->OnStreamFrameAcked(0, 7, false, |
| QuicTime::Delta::Zero())); |
| EXPECT_TRUE(headers_stream_->OnStreamFrameAcked(7, 7, false, |
| QuicTime::Delta::Zero())); |
| // Unsent data is acked. |
| EXPECT_CALL(*ack_listener2, OnPacketAcked(7, _)); |
| EXPECT_TRUE(headers_stream_->OnStreamFrameAcked(14, 10, false, |
| QuicTime::Delta::Zero())); |
| } |
| |
| TEST_P(QuicHeadersStreamTest, FrameContainsMultipleHeaders) { |
| // In this test, a stream frame can contain multiple headers. |
| EXPECT_CALL(session_, WritevData(headers_stream_, |
| QuicUtils::GetHeadersStreamId( |
| connection_->transport_version()), |
| _, _, NO_FIN)) |
| .WillRepeatedly(Invoke(MockQuicSession::ConsumeData)); |
| InSequence s; |
| QuicReferenceCountedPointer<MockAckListener> ack_listener1( |
| new MockAckListener()); |
| QuicReferenceCountedPointer<MockAckListener> ack_listener2( |
| new MockAckListener()); |
| QuicReferenceCountedPointer<MockAckListener> ack_listener3( |
| new MockAckListener()); |
| |
| headers_stream_->WriteOrBufferData("Header5", false, ack_listener1); |
| headers_stream_->WriteOrBufferData("Header5", false, ack_listener1); |
| headers_stream_->WriteOrBufferData("Header7", false, ack_listener2); |
| headers_stream_->WriteOrBufferData("Header9", false, ack_listener3); |
| headers_stream_->WriteOrBufferData("Header7", false, ack_listener2); |
| headers_stream_->WriteOrBufferData("Header9", false, ack_listener3); |
| |
| // Frame 1 is retransmitted. |
| EXPECT_CALL(*ack_listener1, OnPacketRetransmitted(14)); |
| EXPECT_CALL(*ack_listener2, OnPacketRetransmitted(3)); |
| headers_stream_->OnStreamFrameRetransmitted(0, 17, false); |
| |
| // Frames are acked in order: 2, 3, 1. |
| EXPECT_CALL(*ack_listener2, OnPacketAcked(4, _)); |
| EXPECT_CALL(*ack_listener3, OnPacketAcked(7, _)); |
| EXPECT_CALL(*ack_listener2, OnPacketAcked(2, _)); |
| EXPECT_TRUE(headers_stream_->OnStreamFrameAcked(17, 13, false, |
| QuicTime::Delta::Zero())); |
| |
| EXPECT_CALL(*ack_listener2, OnPacketAcked(5, _)); |
| EXPECT_CALL(*ack_listener3, OnPacketAcked(7, _)); |
| EXPECT_TRUE(headers_stream_->OnStreamFrameAcked(30, 12, false, |
| QuicTime::Delta::Zero())); |
| |
| EXPECT_CALL(*ack_listener1, OnPacketAcked(14, _)); |
| EXPECT_CALL(*ack_listener2, OnPacketAcked(3, _)); |
| EXPECT_TRUE(headers_stream_->OnStreamFrameAcked(0, 17, false, |
| QuicTime::Delta::Zero())); |
| } |
| |
| TEST_P(QuicHeadersStreamTest, HeadersGetAckedMultipleTimes) { |
| EXPECT_CALL(session_, WritevData(headers_stream_, |
| QuicUtils::GetHeadersStreamId( |
| connection_->transport_version()), |
| _, _, NO_FIN)) |
| .WillRepeatedly(Invoke(MockQuicSession::ConsumeData)); |
| InSequence s; |
| QuicReferenceCountedPointer<MockAckListener> ack_listener1( |
| new MockAckListener()); |
| QuicReferenceCountedPointer<MockAckListener> ack_listener2( |
| new MockAckListener()); |
| QuicReferenceCountedPointer<MockAckListener> ack_listener3( |
| new MockAckListener()); |
| |
| // Send [0, 42). |
| headers_stream_->WriteOrBufferData("Header5", false, ack_listener1); |
| headers_stream_->WriteOrBufferData("Header5", false, ack_listener1); |
| headers_stream_->WriteOrBufferData("Header7", false, ack_listener2); |
| headers_stream_->WriteOrBufferData("Header9", false, ack_listener3); |
| headers_stream_->WriteOrBufferData("Header7", false, ack_listener2); |
| headers_stream_->WriteOrBufferData("Header9", false, ack_listener3); |
| |
| // Ack [15, 20), [5, 25), [10, 17), [0, 12) and [22, 42). |
| EXPECT_CALL(*ack_listener2, OnPacketAcked(5, _)); |
| EXPECT_TRUE(headers_stream_->OnStreamFrameAcked(15, 5, false, |
| QuicTime::Delta::Zero())); |
| |
| EXPECT_CALL(*ack_listener1, OnPacketAcked(9, _)); |
| EXPECT_CALL(*ack_listener2, OnPacketAcked(1, _)); |
| EXPECT_CALL(*ack_listener2, OnPacketAcked(1, _)); |
| EXPECT_CALL(*ack_listener3, OnPacketAcked(4, _)); |
| EXPECT_TRUE(headers_stream_->OnStreamFrameAcked(5, 20, false, |
| QuicTime::Delta::Zero())); |
| |
| // Duplicate ack. |
| EXPECT_FALSE(headers_stream_->OnStreamFrameAcked(10, 7, false, |
| QuicTime::Delta::Zero())); |
| |
| EXPECT_CALL(*ack_listener1, OnPacketAcked(5, _)); |
| EXPECT_TRUE(headers_stream_->OnStreamFrameAcked(0, 12, false, |
| QuicTime::Delta::Zero())); |
| |
| EXPECT_CALL(*ack_listener3, OnPacketAcked(3, _)); |
| EXPECT_CALL(*ack_listener2, OnPacketAcked(7, _)); |
| EXPECT_CALL(*ack_listener3, OnPacketAcked(7, _)); |
| EXPECT_TRUE(headers_stream_->OnStreamFrameAcked(22, 20, false, |
| QuicTime::Delta::Zero())); |
| } |
| |
| } // namespace |
| } // namespace test |
| } // namespace quic |