// Copyright 2013 The Chromium Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "net/spdy/spdy_stream.h" #include #include #include #include #include #include #include #include #include #include "base/functional/bind.h" #include "base/memory/ref_counted.h" #include "base/run_loop.h" #include "base/time/time.h" #include "net/base/request_priority.h" #include "net/base/session_usage.h" #include "net/dns/public/secure_dns_policy.h" #include "net/http/http_request_info.h" #include "net/log/net_log_event_type.h" #include "net/log/test_net_log.h" #include "net/log/test_net_log_util.h" #include "net/socket/socket_tag.h" #include "net/socket/socket_test_util.h" #include "net/spdy/buffered_spdy_framer.h" #include "net/spdy/spdy_http_utils.h" #include "net/spdy/spdy_session.h" #include "net/spdy/spdy_session_pool.h" #include "net/spdy/spdy_stream_test_util.h" #include "net/spdy/spdy_test_util_common.h" #include "net/test/cert_test_util.h" #include "net/test/gtest_util.h" #include "net/test/test_data_directory.h" #include "net/test/test_with_task_environment.h" #include "net/third_party/quiche/src/quiche/spdy/core/spdy_protocol.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" // TODO(ukai): factor out common part with spdy_http_stream_unittest.cc // namespace net::test { namespace { const char kPostBody[] = "\0hello!\xff"; const size_t kPostBodyLength = std::size(kPostBody); const std::string_view kPostBodyStringPiece(kPostBody, kPostBodyLength); // Creates a MockRead from the given serialized frame except for the last byte. MockRead ReadFrameExceptForLastByte(const spdy::SpdySerializedFrame& frame) { CHECK_GE(frame.size(), 2u); return MockRead(ASYNC, frame.data(), frame.size() - 1); } // Creates a MockRead from the last byte of the given serialized frame. MockRead LastByteOfReadFrame(const spdy::SpdySerializedFrame& frame) { CHECK_GE(frame.size(), 2u); return MockRead(ASYNC, frame.data() + frame.size() - 1, 1); } } // namespace class SpdyStreamTest : public ::testing::Test, public WithTaskEnvironment { protected: // A function that takes a SpdyStream and the number of bytes which // will unstall the next frame completely. typedef base::OnceCallback&, int32_t)> UnstallFunction; explicit SpdyStreamTest(base::test::TaskEnvironment::TimeSource time_source = base::test::TaskEnvironment::TimeSource::DEFAULT) : WithTaskEnvironment(time_source), url_(kDefaultUrl), session_(SpdySessionDependencies::SpdyCreateSession(&session_deps_)), ssl_(SYNCHRONOUS, OK) {} ~SpdyStreamTest() override = default; base::WeakPtr CreateDefaultSpdySession() { SpdySessionKey key(HostPortPair::FromURL(url_), PRIVACY_MODE_DISABLED, ProxyChain::Direct(), SessionUsage::kDestination, SocketTag(), NetworkAnonymizationKey(), SecureDnsPolicy::kAllow, /*disable_cert_verification_network_fetches=*/false); return CreateSpdySession(session_.get(), key, NetLogWithSource()); } void TearDown() override { base::RunLoop().RunUntilIdle(); } void RunResumeAfterUnstallRequestResponseTest( UnstallFunction unstall_function); void RunResumeAfterUnstallBidirectionalTest(UnstallFunction unstall_function); // Add{Read,Write}() populates lists that are eventually passed to a // SocketData class. |frame| must live for the whole test. void AddRead(const spdy::SpdySerializedFrame& frame) { reads_.push_back(CreateMockRead(frame, offset_++)); } void AddWrite(const spdy::SpdySerializedFrame& frame) { writes_.push_back(CreateMockWrite(frame, offset_++)); } void AddMockRead(MockRead read) { read.sequence_number = offset_++; reads_.push_back(std::move(read)); } void AddReadEOF() { reads_.emplace_back(ASYNC, 0, offset_++); } void AddWritePause() { writes_.emplace_back(ASYNC, ERR_IO_PENDING, offset_++); } void AddReadPause() { reads_.emplace_back(ASYNC, ERR_IO_PENDING, offset_++); } base::span GetReads() { return reads_; } base::span GetWrites() { return writes_; } void ActivatePushStream(SpdySession* session, SpdyStream* stream) { std::unique_ptr activated = session->ActivateCreatedStream(stream); activated->set_stream_id(2); session->InsertActivatedStream(std::move(activated)); } void AddSSLSocketData() { // Load a cert that is valid for // www.example.org, mail.example.org, and mail.example.com. ssl_.ssl_info.cert = ImportCertFromFile(GetTestCertsDirectory(), "spdy_pooling.pem"); ASSERT_TRUE(ssl_.ssl_info.cert); session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl_); } int32_t unacked_recv_window_bytes(base::WeakPtr stream) { return stream->unacked_recv_window_bytes_; } static SpdySessionPool* spdy_session_pool( base::WeakPtr session) { return session->pool_; } const GURL url_; SpdyTestUtil spdy_util_; SpdySessionDependencies session_deps_; std::unique_ptr session_; private: // Used by Add{Read,Write}() above. std::vector writes_; std::vector reads_; int offset_ = 0; SSLSocketDataProvider ssl_; }; TEST_F(SpdyStreamTest, SendDataAfterOpen) { spdy::SpdySerializedFrame req(spdy_util_.ConstructSpdyPost( kDefaultUrl, 1, kPostBodyLength, LOWEST, nullptr, 0)); AddWrite(req); spdy::SpdySerializedFrame resp(spdy_util_.ConstructSpdyPostReply(nullptr, 0)); AddRead(resp); spdy::SpdySerializedFrame msg( spdy_util_.ConstructSpdyDataFrame(1, kPostBodyStringPiece, false)); AddWrite(msg); spdy::SpdySerializedFrame echo( spdy_util_.ConstructSpdyDataFrame(1, kPostBodyStringPiece, false)); AddRead(echo); AddReadEOF(); SequencedSocketData data(GetReads(), GetWrites()); MockConnect connect_data(SYNCHRONOUS, OK); data.set_connect_data(connect_data); session_deps_.socket_factory->AddSocketDataProvider(&data); AddSSLSocketData(); base::WeakPtr session(CreateDefaultSpdySession()); base::WeakPtr stream = CreateStreamSynchronously( SPDY_BIDIRECTIONAL_STREAM, session, url_, LOWEST, NetLogWithSource()); ASSERT_TRUE(stream); EXPECT_EQ(kDefaultUrl, stream->url().spec()); StreamDelegateSendImmediate delegate(stream, kPostBodyStringPiece); stream->SetDelegate(&delegate); spdy::Http2HeaderBlock headers( spdy_util_.ConstructPostHeaderBlock(kDefaultUrl, kPostBodyLength)); EXPECT_THAT(stream->SendRequestHeaders(std::move(headers), MORE_DATA_TO_SEND), IsError(ERR_IO_PENDING)); EXPECT_THAT(delegate.WaitForClose(), IsError(ERR_CONNECTION_CLOSED)); EXPECT_TRUE(delegate.send_headers_completed()); EXPECT_EQ("200", delegate.GetResponseHeaderValue(spdy::kHttp2StatusHeader)); EXPECT_EQ(std::string(kPostBody, kPostBodyLength), delegate.TakeReceivedData()); EXPECT_TRUE(data.AllWriteDataConsumed()); } TEST_F(SpdyStreamTest, BrokenConnectionDetectionSuccessfulRequest) { spdy::SpdySerializedFrame req(spdy_util_.ConstructSpdyPost( kDefaultUrl, 1, kPostBodyLength, LOWEST, nullptr, 0)); AddWrite(req); spdy::SpdySerializedFrame resp(spdy_util_.ConstructSpdyPostReply(nullptr, 0)); AddRead(resp); spdy::SpdySerializedFrame msg( spdy_util_.ConstructSpdyDataFrame(1, kPostBodyStringPiece, false)); AddWrite(msg); spdy::SpdySerializedFrame echo( spdy_util_.ConstructSpdyDataFrame(1, kPostBodyStringPiece, false)); AddRead(echo); AddReadPause(); AddReadEOF(); SequencedSocketData data(GetReads(), GetWrites()); MockConnect connect_data(SYNCHRONOUS, OK); data.set_connect_data(connect_data); session_deps_.socket_factory->AddSocketDataProvider(&data); AddSSLSocketData(); base::WeakPtr session(CreateDefaultSpdySession()); ASSERT_FALSE(session->IsBrokenConnectionDetectionEnabled()); base::WeakPtr stream = CreateStreamSynchronously( SPDY_BIDIRECTIONAL_STREAM, session, url_, LOWEST, NetLogWithSource(), true, base::Seconds(10)); ASSERT_TRUE(stream); ASSERT_TRUE(session->IsBrokenConnectionDetectionEnabled()); StreamDelegateSendImmediate delegate(stream, kPostBodyStringPiece); stream->SetDelegate(&delegate); spdy::Http2HeaderBlock headers( spdy_util_.ConstructPostHeaderBlock(kDefaultUrl, kPostBodyLength)); EXPECT_THAT(stream->SendRequestHeaders(std::move(headers), MORE_DATA_TO_SEND), IsError(ERR_IO_PENDING)); base::RunLoop().RunUntilIdle(); ASSERT_TRUE(session->IsBrokenConnectionDetectionEnabled()); data.Resume(); EXPECT_THAT(delegate.WaitForClose(), IsError(ERR_CONNECTION_CLOSED)); ASSERT_FALSE(session->IsBrokenConnectionDetectionEnabled()); } // Delegate that receives trailers. class StreamDelegateWithTrailers : public test::StreamDelegateWithBody { public: StreamDelegateWithTrailers(const base::WeakPtr& stream, std::string_view data) : StreamDelegateWithBody(stream, data) {} ~StreamDelegateWithTrailers() override = default; void OnTrailers(const spdy::Http2HeaderBlock& trailers) override { trailers_ = trailers.Clone(); } const spdy::Http2HeaderBlock& trailers() const { return trailers_; } private: spdy::Http2HeaderBlock trailers_; }; // Regression test for https://crbug.com/481033. TEST_F(SpdyStreamTest, Trailers) { spdy::SpdySerializedFrame req(spdy_util_.ConstructSpdyPost( kDefaultUrl, 1, kPostBodyLength, LOWEST, nullptr, 0)); AddWrite(req); spdy::SpdySerializedFrame msg( spdy_util_.ConstructSpdyDataFrame(1, kPostBodyStringPiece, true)); AddWrite(msg); spdy::SpdySerializedFrame resp(spdy_util_.ConstructSpdyPostReply(nullptr, 0)); AddRead(resp); spdy::SpdySerializedFrame echo( spdy_util_.ConstructSpdyDataFrame(1, kPostBodyStringPiece, false)); AddRead(echo); spdy::Http2HeaderBlock late_headers; late_headers["foo"] = "bar"; spdy::SpdySerializedFrame trailers(spdy_util_.ConstructSpdyResponseHeaders( 1, std::move(late_headers), false)); AddRead(trailers); AddReadEOF(); SequencedSocketData data(GetReads(), GetWrites()); MockConnect connect_data(SYNCHRONOUS, OK); data.set_connect_data(connect_data); session_deps_.socket_factory->AddSocketDataProvider(&data); AddSSLSocketData(); base::WeakPtr session(CreateDefaultSpdySession()); base::WeakPtr stream = CreateStreamSynchronously( SPDY_REQUEST_RESPONSE_STREAM, session, url_, LOWEST, NetLogWithSource()); ASSERT_TRUE(stream); EXPECT_EQ(kDefaultUrl, stream->url().spec()); StreamDelegateWithTrailers delegate(stream, kPostBodyStringPiece); stream->SetDelegate(&delegate); spdy::Http2HeaderBlock headers( spdy_util_.ConstructPostHeaderBlock(kDefaultUrl, kPostBodyLength)); EXPECT_THAT(stream->SendRequestHeaders(std::move(headers), MORE_DATA_TO_SEND), IsError(ERR_IO_PENDING)); EXPECT_THAT(delegate.WaitForClose(), IsError(ERR_CONNECTION_CLOSED)); EXPECT_TRUE(delegate.send_headers_completed()); EXPECT_EQ("200", delegate.GetResponseHeaderValue(spdy::kHttp2StatusHeader)); const spdy::Http2HeaderBlock& received_trailers = delegate.trailers(); spdy::Http2HeaderBlock::const_iterator it = received_trailers.find("foo"); EXPECT_EQ("bar", it->second); EXPECT_EQ(std::string(kPostBody, kPostBodyLength), delegate.TakeReceivedData()); EXPECT_TRUE(data.AllWriteDataConsumed()); } TEST_F(SpdyStreamTest, StreamError) { spdy::SpdySerializedFrame req(spdy_util_.ConstructSpdyPost( kDefaultUrl, 1, kPostBodyLength, LOWEST, nullptr, 0)); AddWrite(req); spdy::SpdySerializedFrame resp( spdy_util_.ConstructSpdyGetReply(nullptr, 0, 1)); AddRead(resp); spdy::SpdySerializedFrame msg( spdy_util_.ConstructSpdyDataFrame(1, kPostBodyStringPiece, false)); AddWrite(msg); spdy::SpdySerializedFrame echo( spdy_util_.ConstructSpdyDataFrame(1, kPostBodyStringPiece, false)); AddRead(echo); AddReadEOF(); RecordingNetLogObserver net_log_observer; SequencedSocketData data(GetReads(), GetWrites()); MockConnect connect_data(SYNCHRONOUS, OK); data.set_connect_data(connect_data); session_deps_.socket_factory->AddSocketDataProvider(&data); AddSSLSocketData(); base::WeakPtr session(CreateDefaultSpdySession()); base::WeakPtr stream = CreateStreamSynchronously( SPDY_BIDIRECTIONAL_STREAM, session, url_, LOWEST, NetLogWithSource::Make(NetLogSourceType::NONE)); ASSERT_TRUE(stream); EXPECT_EQ(kDefaultUrl, stream->url().spec()); StreamDelegateSendImmediate delegate(stream, kPostBodyStringPiece); stream->SetDelegate(&delegate); spdy::Http2HeaderBlock headers( spdy_util_.ConstructPostHeaderBlock(kDefaultUrl, kPostBodyLength)); EXPECT_THAT(stream->SendRequestHeaders(std::move(headers), MORE_DATA_TO_SEND), IsError(ERR_IO_PENDING)); EXPECT_THAT(delegate.WaitForClose(), IsError(ERR_CONNECTION_CLOSED)); const spdy::SpdyStreamId stream_id = delegate.stream_id(); EXPECT_TRUE(delegate.send_headers_completed()); EXPECT_EQ("200", delegate.GetResponseHeaderValue(spdy::kHttp2StatusHeader)); EXPECT_EQ(std::string(kPostBody, kPostBodyLength), delegate.TakeReceivedData()); EXPECT_TRUE(data.AllWriteDataConsumed()); // Check that the NetLog was filled reasonably. auto entries = net_log_observer.GetEntries(); EXPECT_LT(0u, entries.size()); // Check that we logged SPDY_STREAM_ERROR correctly. int pos = ExpectLogContainsSomewhere( entries, 0, NetLogEventType::HTTP2_STREAM_ERROR, NetLogEventPhase::NONE); EXPECT_EQ(static_cast(stream_id), GetIntegerValueFromParams(entries[pos], "stream_id")); } // Make sure that large blocks of data are properly split up into frame-sized // chunks for a request/response (i.e., an HTTP-like) stream. TEST_F(SpdyStreamTest, SendLargeDataAfterOpenRequestResponse) { spdy::SpdySerializedFrame req(spdy_util_.ConstructSpdyPost( kDefaultUrl, 1, kPostBodyLength, LOWEST, nullptr, 0)); AddWrite(req); std::string chunk_data(kMaxSpdyFrameChunkSize, 'x'); spdy::SpdySerializedFrame chunk( spdy_util_.ConstructSpdyDataFrame(1, chunk_data, false)); AddWrite(chunk); AddWrite(chunk); spdy::SpdySerializedFrame last_chunk( spdy_util_.ConstructSpdyDataFrame(1, chunk_data, true)); AddWrite(last_chunk); spdy::SpdySerializedFrame resp(spdy_util_.ConstructSpdyPostReply(nullptr, 0)); AddRead(resp); AddReadEOF(); SequencedSocketData data(GetReads(), GetWrites()); MockConnect connect_data(SYNCHRONOUS, OK); data.set_connect_data(connect_data); session_deps_.socket_factory->AddSocketDataProvider(&data); AddSSLSocketData(); base::WeakPtr session(CreateDefaultSpdySession()); base::WeakPtr stream = CreateStreamSynchronously( SPDY_REQUEST_RESPONSE_STREAM, session, url_, LOWEST, NetLogWithSource()); ASSERT_TRUE(stream); EXPECT_EQ(kDefaultUrl, stream->url().spec()); std::string body_data(3 * kMaxSpdyFrameChunkSize, 'x'); StreamDelegateWithBody delegate(stream, body_data); stream->SetDelegate(&delegate); spdy::Http2HeaderBlock headers( spdy_util_.ConstructPostHeaderBlock(kDefaultUrl, kPostBodyLength)); EXPECT_THAT(stream->SendRequestHeaders(std::move(headers), MORE_DATA_TO_SEND), IsError(ERR_IO_PENDING)); EXPECT_THAT(delegate.WaitForClose(), IsError(ERR_CONNECTION_CLOSED)); EXPECT_TRUE(delegate.send_headers_completed()); EXPECT_EQ("200", delegate.GetResponseHeaderValue(spdy::kHttp2StatusHeader)); EXPECT_EQ(std::string(), delegate.TakeReceivedData()); EXPECT_TRUE(data.AllWriteDataConsumed()); } // Make sure that large blocks of data are properly split up into frame-sized // chunks for a bidirectional (i.e., non-HTTP-like) stream. TEST_F(SpdyStreamTest, SendLargeDataAfterOpenBidirectional) { spdy::SpdySerializedFrame req(spdy_util_.ConstructSpdyPost( kDefaultUrl, 1, kPostBodyLength, LOWEST, nullptr, 0)); AddWrite(req); spdy::SpdySerializedFrame resp(spdy_util_.ConstructSpdyPostReply(nullptr, 0)); AddRead(resp); std::string chunk_data(kMaxSpdyFrameChunkSize, 'x'); spdy::SpdySerializedFrame chunk( spdy_util_.ConstructSpdyDataFrame(1, chunk_data, false)); AddWrite(chunk); AddWrite(chunk); AddWrite(chunk); AddReadEOF(); SequencedSocketData data(GetReads(), GetWrites()); MockConnect connect_data(SYNCHRONOUS, OK); data.set_connect_data(connect_data); session_deps_.socket_factory->AddSocketDataProvider(&data); AddSSLSocketData(); base::WeakPtr session(CreateDefaultSpdySession()); base::WeakPtr stream = CreateStreamSynchronously( SPDY_BIDIRECTIONAL_STREAM, session, url_, LOWEST, NetLogWithSource()); ASSERT_TRUE(stream); EXPECT_EQ(kDefaultUrl, stream->url().spec()); std::string body_data(3 * kMaxSpdyFrameChunkSize, 'x'); StreamDelegateSendImmediate delegate(stream, body_data); stream->SetDelegate(&delegate); spdy::Http2HeaderBlock headers( spdy_util_.ConstructPostHeaderBlock(kDefaultUrl, kPostBodyLength)); EXPECT_THAT(stream->SendRequestHeaders(std::move(headers), MORE_DATA_TO_SEND), IsError(ERR_IO_PENDING)); EXPECT_THAT(delegate.WaitForClose(), IsError(ERR_CONNECTION_CLOSED)); EXPECT_TRUE(delegate.send_headers_completed()); EXPECT_EQ("200", delegate.GetResponseHeaderValue(spdy::kHttp2StatusHeader)); EXPECT_EQ(std::string(), delegate.TakeReceivedData()); EXPECT_TRUE(data.AllWriteDataConsumed()); } // Receiving a header with uppercase ASCII should result in a protocol error. TEST_F(SpdyStreamTest, UpperCaseHeaders) { spdy::SpdySerializedFrame req( spdy_util_.ConstructSpdyGet(nullptr, 0, 1, LOWEST)); AddWrite(req); const char* const kExtraHeaders[] = {"X-UpperCase", "yes"}; spdy::SpdySerializedFrame reply(spdy_util_.ConstructSpdyGetReply( kExtraHeaders, std::size(kExtraHeaders) / 2, 1)); AddRead(reply); spdy::SpdySerializedFrame rst( spdy_util_.ConstructSpdyRstStream(1, spdy::ERROR_CODE_PROTOCOL_ERROR)); AddWrite(rst); AddReadEOF(); SequencedSocketData data(GetReads(), GetWrites()); MockConnect connect_data(SYNCHRONOUS, OK); data.set_connect_data(connect_data); session_deps_.socket_factory->AddSocketDataProvider(&data); AddSSLSocketData(); base::WeakPtr session(CreateDefaultSpdySession()); base::WeakPtr stream = CreateStreamSynchronously( SPDY_REQUEST_RESPONSE_STREAM, session, url_, LOWEST, NetLogWithSource()); ASSERT_TRUE(stream); EXPECT_EQ(kDefaultUrl, stream->url().spec()); StreamDelegateDoNothing delegate(stream); stream->SetDelegate(&delegate); spdy::Http2HeaderBlock headers( spdy_util_.ConstructGetHeaderBlock(kDefaultUrl)); EXPECT_THAT( stream->SendRequestHeaders(std::move(headers), NO_MORE_DATA_TO_SEND), IsError(ERR_IO_PENDING)); EXPECT_THAT(delegate.WaitForClose(), IsError(ERR_HTTP2_PROTOCOL_ERROR)); // Finish async network reads and writes. base::RunLoop().RunUntilIdle(); EXPECT_TRUE(data.AllWriteDataConsumed()); EXPECT_TRUE(data.AllReadDataConsumed()); } TEST_F(SpdyStreamTest, HeadersMustHaveStatus) { spdy::SpdySerializedFrame req( spdy_util_.ConstructSpdyGet(nullptr, 0, 1, LOWEST)); AddWrite(req); // Response headers without ":status" header field: protocol error. spdy::Http2HeaderBlock header_block_without_status; header_block_without_status[spdy::kHttp2MethodHeader] = "GET"; header_block_without_status[spdy::kHttp2AuthorityHeader] = "www.example.org"; header_block_without_status[spdy::kHttp2SchemeHeader] = "https"; header_block_without_status[spdy::kHttp2PathHeader] = "/"; spdy::SpdySerializedFrame reply( spdy_util_.ConstructSpdyReply(1, std::move(header_block_without_status))); AddRead(reply); spdy::SpdySerializedFrame rst( spdy_util_.ConstructSpdyRstStream(1, spdy::ERROR_CODE_PROTOCOL_ERROR)); AddWrite(rst); AddReadEOF(); SequencedSocketData data(GetReads(), GetWrites()); MockConnect connect_data(SYNCHRONOUS, OK); data.set_connect_data(connect_data); session_deps_.socket_factory->AddSocketDataProvider(&data); AddSSLSocketData(); base::WeakPtr session(CreateDefaultSpdySession()); base::WeakPtr stream = CreateStreamSynchronously( SPDY_REQUEST_RESPONSE_STREAM, session, url_, LOWEST, NetLogWithSource()); ASSERT_TRUE(stream); EXPECT_EQ(kDefaultUrl, stream->url().spec()); StreamDelegateDoNothing delegate(stream); stream->SetDelegate(&delegate); spdy::Http2HeaderBlock headers( spdy_util_.ConstructGetHeaderBlock(kDefaultUrl)); EXPECT_EQ(ERR_IO_PENDING, stream->SendRequestHeaders(std::move(headers), NO_MORE_DATA_TO_SEND)); EXPECT_THAT(delegate.WaitForClose(), IsError(ERR_HTTP2_PROTOCOL_ERROR)); // Finish async network reads and writes. base::RunLoop().RunUntilIdle(); EXPECT_TRUE(data.AllWriteDataConsumed()); EXPECT_TRUE(data.AllReadDataConsumed()); } TEST_F(SpdyStreamTest, TrailersMustNotFollowTrailers) { spdy::SpdySerializedFrame req( spdy_util_.ConstructSpdyGet(nullptr, 0, 1, LOWEST)); AddWrite(req); spdy::SpdySerializedFrame reply( spdy_util_.ConstructSpdyGetReply(nullptr, 0, 1)); AddRead(reply); spdy::SpdySerializedFrame body( spdy_util_.ConstructSpdyDataFrame(1, kPostBodyStringPiece, false)); AddRead(body); spdy::Http2HeaderBlock trailers_block; trailers_block["foo"] = "bar"; spdy::SpdySerializedFrame first_trailers( spdy_util_.ConstructSpdyResponseHeaders(1, std::move(trailers_block), false)); AddRead(first_trailers); // Trailers following trailers: procotol error. spdy::SpdySerializedFrame second_trailers( spdy_util_.ConstructSpdyResponseHeaders(1, std::move(trailers_block), true)); AddRead(second_trailers); spdy::SpdySerializedFrame rst( spdy_util_.ConstructSpdyRstStream(1, spdy::ERROR_CODE_PROTOCOL_ERROR)); AddWrite(rst); AddReadEOF(); SequencedSocketData data(GetReads(), GetWrites()); MockConnect connect_data(SYNCHRONOUS, OK); data.set_connect_data(connect_data); session_deps_.socket_factory->AddSocketDataProvider(&data); AddSSLSocketData(); base::WeakPtr session(CreateDefaultSpdySession()); base::WeakPtr stream = CreateStreamSynchronously( SPDY_REQUEST_RESPONSE_STREAM, session, url_, LOWEST, NetLogWithSource()); ASSERT_TRUE(stream); EXPECT_EQ(kDefaultUrl, stream->url().spec()); StreamDelegateDoNothing delegate(stream); stream->SetDelegate(&delegate); spdy::Http2HeaderBlock headers( spdy_util_.ConstructGetHeaderBlock(kDefaultUrl)); EXPECT_EQ(ERR_IO_PENDING, stream->SendRequestHeaders(std::move(headers), NO_MORE_DATA_TO_SEND)); EXPECT_THAT(delegate.WaitForClose(), IsError(ERR_HTTP2_PROTOCOL_ERROR)); // Finish async network reads and writes. base::RunLoop().RunUntilIdle(); EXPECT_TRUE(data.AllWriteDataConsumed()); EXPECT_TRUE(data.AllReadDataConsumed()); } TEST_F(SpdyStreamTest, DataMustNotFollowTrailers) { spdy::SpdySerializedFrame req( spdy_util_.ConstructSpdyGet(nullptr, 0, 1, LOWEST)); AddWrite(req); spdy::SpdySerializedFrame reply( spdy_util_.ConstructSpdyGetReply(nullptr, 0, 1)); AddRead(reply); spdy::SpdySerializedFrame body( spdy_util_.ConstructSpdyDataFrame(1, kPostBodyStringPiece, false)); AddRead(body); spdy::Http2HeaderBlock trailers_block; trailers_block["foo"] = "bar"; spdy::SpdySerializedFrame trailers(spdy_util_.ConstructSpdyResponseHeaders( 1, std::move(trailers_block), false)); AddRead(trailers); // DATA frame following trailers: protocol error. AddRead(body); spdy::SpdySerializedFrame rst( spdy_util_.ConstructSpdyRstStream(1, spdy::ERROR_CODE_PROTOCOL_ERROR)); AddWrite(rst); AddReadEOF(); SequencedSocketData data(GetReads(), GetWrites()); MockConnect connect_data(SYNCHRONOUS, OK); data.set_connect_data(connect_data); session_deps_.socket_factory->AddSocketDataProvider(&data); AddSSLSocketData(); base::WeakPtr session(CreateDefaultSpdySession()); base::WeakPtr stream = CreateStreamSynchronously( SPDY_REQUEST_RESPONSE_STREAM, session, url_, LOWEST, NetLogWithSource()); ASSERT_TRUE(stream); EXPECT_EQ(kDefaultUrl, stream->url().spec()); StreamDelegateDoNothing delegate(stream); stream->SetDelegate(&delegate); spdy::Http2HeaderBlock headers( spdy_util_.ConstructGetHeaderBlock(kDefaultUrl)); EXPECT_EQ(ERR_IO_PENDING, stream->SendRequestHeaders(std::move(headers), NO_MORE_DATA_TO_SEND)); EXPECT_THAT(delegate.WaitForClose(), IsError(ERR_HTTP2_PROTOCOL_ERROR)); // Finish async network reads and writes. base::RunLoop().RunUntilIdle(); EXPECT_TRUE(data.AllWriteDataConsumed()); EXPECT_TRUE(data.AllReadDataConsumed()); } class SpdyStreamTestWithMockClock : public SpdyStreamTest { public: SpdyStreamTestWithMockClock() : SpdyStreamTest(base::test::TaskEnvironment::TimeSource::MOCK_TIME) {} void Initialize() { // Set up the sequenced socket data. data_ = std::make_unique(GetReads(), GetWrites()); MockConnect connect_data(SYNCHRONOUS, OK); data_->set_connect_data(connect_data); session_deps_.socket_factory->AddSocketDataProvider(data_.get()); AddSSLSocketData(); // Set up the SPDY stream. base::WeakPtr session(CreateDefaultSpdySession()); stream_ = CreateStreamSynchronously(SPDY_REQUEST_RESPONSE_STREAM, session, url_, LOWEST, NetLogWithSource()); ASSERT_TRUE(stream_); EXPECT_EQ(kDefaultUrl, stream_->url().spec()); DCHECK(!delegate_); delegate_ = std::make_unique(stream_); stream_->SetDelegate(delegate_.get()); } void RunUntilNextPause() { if (data_->IsPaused()) data_->Resume(); data_->RunUntilPaused(); } int RunUntilClose() { if (data_->IsPaused()) data_->Resume(); return delegate_->WaitForClose(); } SequencedSocketData& data() { return *data_; } base::WeakPtr stream() { return stream_; } StreamDelegateDoNothing& delegate() { return *delegate_; } private: std::unique_ptr data_; base::WeakPtr stream_; std::unique_ptr delegate_; }; // Test that the response start time is recorded for non-informational response. TEST_F(SpdyStreamTestWithMockClock, NonInformationalResponseStart) { // Set up the request. spdy::SpdySerializedFrame req( spdy_util_.ConstructSpdyGet(nullptr, 0, 1, LOWEST)); AddWrite(req); // Set up the response headers. spdy::SpdySerializedFrame reply( spdy_util_.ConstructSpdyGetReply(nullptr, 0, 1)); // Separate the headers into 2 fragments and add pauses between the fragments // so that the test runner can advance the mock clock to test timing // information. AddMockRead(ReadFrameExceptForLastByte(reply)); AddReadPause(); AddMockRead(LastByteOfReadFrame(reply)); AddReadPause(); // Set up the response body. spdy::SpdySerializedFrame body( spdy_util_.ConstructSpdyDataFrame(1, kPostBodyStringPiece, true)); AddRead(body); AddReadEOF(); // Set up the sequenced socket data and the spdy stream. Initialize(); // Send a request. spdy::Http2HeaderBlock headers( spdy_util_.ConstructGetHeaderBlock(kDefaultUrl)); EXPECT_EQ(ERR_IO_PENDING, stream()->SendRequestHeaders(std::move(headers), NO_MORE_DATA_TO_SEND)); AdvanceClock(base::Seconds(1)); // The receive headers start time should be captured at this time. base::TimeTicks expected_receive_headers_start_time = base::TimeTicks::Now(); // Read the first header fragment. RunUntilNextPause(); AdvanceClock(base::Seconds(1)); // Read the second header fragment. RunUntilNextPause(); AdvanceClock(base::Seconds(1)); EXPECT_EQ("200", delegate().GetResponseHeaderValue(spdy::kHttp2StatusHeader)); // Read the response body. EXPECT_THAT(RunUntilClose(), IsOk()); EXPECT_EQ(std::string(kPostBody, kPostBodyLength), delegate().TakeReceivedData()); // Finish async network reads and writes. base::RunLoop().RunUntilIdle(); EXPECT_TRUE(data().AllWriteDataConsumed()); EXPECT_TRUE(data().AllReadDataConsumed()); // No informational responses were served. The response start time should be // equal to the non-informational response start time. const LoadTimingInfo& load_timing_info = delegate().GetLoadTimingInfo(); EXPECT_EQ(load_timing_info.receive_headers_start, expected_receive_headers_start_time); EXPECT_EQ(load_timing_info.receive_non_informational_headers_start, expected_receive_headers_start_time); } TEST_F(SpdyStreamTestWithMockClock, InformationalHeaders) { // Set up the request. spdy::SpdySerializedFrame req( spdy_util_.ConstructSpdyGet(nullptr, 0, 1, LOWEST)); AddWrite(req); // Set up the informational response headers. spdy::Http2HeaderBlock informational_headers; informational_headers[":status"] = "100"; spdy::SpdySerializedFrame informational_response( spdy_util_.ConstructSpdyResponseHeaders( 1, std::move(informational_headers), false)); // Separate the headers into 2 fragments and add pauses between the fragments // so that the test runner can advance the mock clock to test timing // information. AddMockRead(ReadFrameExceptForLastByte(informational_response)); AddReadPause(); AddMockRead(LastByteOfReadFrame(informational_response)); AddReadPause(); // Set up the non-informational response headers and body. spdy::SpdySerializedFrame reply( spdy_util_.ConstructSpdyGetReply(nullptr, 0, 1)); AddRead(reply); AddReadPause(); spdy::SpdySerializedFrame body( spdy_util_.ConstructSpdyDataFrame(1, kPostBodyStringPiece, true)); AddRead(body); AddReadEOF(); // Set up the sequenced socket data and the spdy stream. Initialize(); // Send a request. spdy::Http2HeaderBlock headers( spdy_util_.ConstructGetHeaderBlock(kDefaultUrl)); EXPECT_EQ(ERR_IO_PENDING, stream()->SendRequestHeaders(std::move(headers), NO_MORE_DATA_TO_SEND)); AdvanceClock(base::Seconds(1)); // The receive headers start time should be captured at this time. base::TimeTicks expected_receive_headers_start_time = base::TimeTicks::Now(); // Read the first header fragment of the informational response. RunUntilNextPause(); AdvanceClock(base::Seconds(1)); // Read the second header fragment of the informational response. RunUntilNextPause(); AdvanceClock(base::Seconds(1)); // We don't check the status code of the informational headers here because // SpdyStream doesn't propagate it to the delegate. // The receive non-informational headers start time should be captured at this // time. base::TimeTicks expected_receive_non_informational_headers_start_time = base::TimeTicks::Now(); // Read the non-informational response headers. RunUntilNextPause(); AdvanceClock(base::Seconds(1)); EXPECT_EQ("200", delegate().GetResponseHeaderValue(spdy::kHttp2StatusHeader)); // Read the response body. EXPECT_THAT(RunUntilClose(), IsOk()); EXPECT_EQ(std::string(kPostBody, kPostBodyLength), delegate().TakeReceivedData()); // Finish async network reads and writes. base::RunLoop().RunUntilIdle(); EXPECT_TRUE(data().AllWriteDataConsumed()); EXPECT_TRUE(data().AllReadDataConsumed()); const LoadTimingInfo& load_timing_info = delegate().GetLoadTimingInfo(); // The response start time should be captured at the time the first header // fragment of the informational response is received. EXPECT_EQ(load_timing_info.receive_headers_start, expected_receive_headers_start_time); // The non-informational response start time should be captured at the time // the first header fragment of the non-informational response is received. EXPECT_EQ(load_timing_info.receive_non_informational_headers_start, expected_receive_non_informational_headers_start_time); // The first response start time should be earlier than the non-informational // response start time. EXPECT_LT(load_timing_info.receive_headers_start, load_timing_info.receive_non_informational_headers_start); } // Tests that timing information of 103 Eary Hints responses are collected and // callbacks are called as expected. TEST_F(SpdyStreamTestWithMockClock, EarlyHints) { // Set up the request. spdy::SpdySerializedFrame req( spdy_util_.ConstructSpdyGet(nullptr, 0, 1, LOWEST)); AddWrite(req); // Set up two early hints response headers. const char kLinkHeaderValue1[] = "; rel=preload; as=image"; spdy::Http2HeaderBlock informational_headers1; informational_headers1[":status"] = "103"; informational_headers1["link"] = kLinkHeaderValue1; spdy::SpdySerializedFrame informational_response1( spdy_util_.ConstructSpdyResponseHeaders( 1, std::move(informational_headers1), false)); const char kLinkHeaderValue2[] = "; rel=preload; as=stylesheet"; spdy::Http2HeaderBlock informational_headers2; informational_headers2[":status"] = "103"; informational_headers2["link"] = kLinkHeaderValue2; spdy::SpdySerializedFrame informational_response2( spdy_util_.ConstructSpdyResponseHeaders( 1, std::move(informational_headers2), false)); // Add the headers to make sure that multiple informational responses don't // confuse the timing information. const int kNumberOfInformationalResponses = 2; // Separate the headers into 2 fragments and add pauses between the // fragments so that the test runner can advance the mock clock to test // timing information. AddMockRead(ReadFrameExceptForLastByte(informational_response1)); AddReadPause(); AddMockRead(LastByteOfReadFrame(informational_response1)); AddReadPause(); AddMockRead(ReadFrameExceptForLastByte(informational_response2)); AddReadPause(); AddMockRead(LastByteOfReadFrame(informational_response2)); AddReadPause(); // Set up the non-informational response headers and body. spdy::SpdySerializedFrame reply( spdy_util_.ConstructSpdyGetReply(nullptr, 0, 1)); AddRead(reply); AddReadPause(); spdy::SpdySerializedFrame body( spdy_util_.ConstructSpdyDataFrame(1, kPostBodyStringPiece, true)); AddRead(body); AddReadEOF(); // Set up the sequenced socket data and the spdy stream. Initialize(); // Send a request. spdy::Http2HeaderBlock headers( spdy_util_.ConstructGetHeaderBlock(kDefaultUrl)); EXPECT_EQ(ERR_IO_PENDING, stream()->SendRequestHeaders(std::move(headers), NO_MORE_DATA_TO_SEND)); AdvanceClock(base::Seconds(1)); // The receive headers start time should be captured at this time. base::TimeTicks expected_receive_headers_start_time = base::TimeTicks::Now(); // Read the header fragments of the informational responses. for (int i = 0; i < kNumberOfInformationalResponses; ++i) { RunUntilNextPause(); AdvanceClock(base::Seconds(1)); RunUntilNextPause(); AdvanceClock(base::Seconds(1)); } // Check the callback was called twice with 103 status code. const std::vector& early_hints = delegate().early_hints(); EXPECT_EQ(early_hints.size(), static_cast(kNumberOfInformationalResponses)); { const spdy::Http2HeaderBlock& hint = delegate().early_hints()[0]; spdy::Http2HeaderBlock::const_iterator status_iterator = hint.find(spdy::kHttp2StatusHeader); ASSERT_TRUE(status_iterator != hint.end()); EXPECT_EQ(status_iterator->second, "103"); spdy::Http2HeaderBlock::const_iterator link_header_iterator = hint.find("link"); ASSERT_TRUE(link_header_iterator != hint.end()); EXPECT_EQ(link_header_iterator->second, kLinkHeaderValue1); } { const spdy::Http2HeaderBlock& hint = delegate().early_hints()[1]; spdy::Http2HeaderBlock::const_iterator status_iterator = hint.find(spdy::kHttp2StatusHeader); ASSERT_TRUE(status_iterator != hint.end()); EXPECT_EQ(status_iterator->second, "103"); spdy::Http2HeaderBlock::const_iterator link_header_iterator = hint.find("link"); ASSERT_TRUE(link_header_iterator != hint.end()); EXPECT_EQ(link_header_iterator->second, kLinkHeaderValue2); } // The receive non-informational headers start time should be captured at this // time. base::TimeTicks expected_receive_non_informational_headers_start_time = base::TimeTicks::Now(); // Read the non-informational response headers. RunUntilNextPause(); AdvanceClock(base::Seconds(1)); EXPECT_EQ("200", delegate().GetResponseHeaderValue(spdy::kHttp2StatusHeader)); // Read the response body. EXPECT_THAT(RunUntilClose(), IsOk()); EXPECT_EQ(std::string(kPostBody, kPostBodyLength), delegate().TakeReceivedData()); // Finish async network reads and writes. base::RunLoop().RunUntilIdle(); EXPECT_TRUE(data().AllWriteDataConsumed()); EXPECT_TRUE(data().AllReadDataConsumed()); const LoadTimingInfo& load_timing_info = delegate().GetLoadTimingInfo(); // The response start time should be captured at the time the first header // fragment of the first informational response is received. EXPECT_EQ(load_timing_info.receive_headers_start, expected_receive_headers_start_time); // The first early hints time should be recorded as well. EXPECT_EQ(load_timing_info.first_early_hints_time, expected_receive_headers_start_time); // The non-informational response start time should be captured at the time // the first header fragment of the non-informational response is received. EXPECT_EQ(load_timing_info.receive_non_informational_headers_start, expected_receive_non_informational_headers_start_time); // The response start time should be earlier than the non-informational // response start time. EXPECT_LT(load_timing_info.receive_headers_start, load_timing_info.receive_non_informational_headers_start); } TEST_F(SpdyStreamTest, StatusMustBeNumber) { spdy::SpdySerializedFrame req( spdy_util_.ConstructSpdyGet(nullptr, 0, 1, LOWEST)); AddWrite(req); spdy::Http2HeaderBlock incorrect_headers; incorrect_headers[":status"] = "nan"; spdy::SpdySerializedFrame reply(spdy_util_.ConstructSpdyResponseHeaders( 1, std::move(incorrect_headers), false)); AddRead(reply); spdy::SpdySerializedFrame rst( spdy_util_.ConstructSpdyRstStream(1, spdy::ERROR_CODE_PROTOCOL_ERROR)); AddWrite(rst); AddReadEOF(); SequencedSocketData data(GetReads(), GetWrites()); MockConnect connect_data(SYNCHRONOUS, OK); data.set_connect_data(connect_data); session_deps_.socket_factory->AddSocketDataProvider(&data); AddSSLSocketData(); base::WeakPtr session(CreateDefaultSpdySession()); base::WeakPtr stream = CreateStreamSynchronously( SPDY_REQUEST_RESPONSE_STREAM, session, url_, LOWEST, NetLogWithSource()); ASSERT_TRUE(stream); EXPECT_EQ(kDefaultUrl, stream->url().spec()); StreamDelegateDoNothing delegate(stream); stream->SetDelegate(&delegate); spdy::Http2HeaderBlock headers( spdy_util_.ConstructGetHeaderBlock(kDefaultUrl)); EXPECT_EQ(ERR_IO_PENDING, stream->SendRequestHeaders(std::move(headers), NO_MORE_DATA_TO_SEND)); EXPECT_THAT(delegate.WaitForClose(), IsError(ERR_HTTP2_PROTOCOL_ERROR)); // Finish async network reads and writes. base::RunLoop().RunUntilIdle(); EXPECT_TRUE(data.AllWriteDataConsumed()); EXPECT_TRUE(data.AllReadDataConsumed()); } TEST_F(SpdyStreamTest, StatusCannotHaveExtraText) { spdy::SpdySerializedFrame req( spdy_util_.ConstructSpdyGet(nullptr, 0, 1, LOWEST)); AddWrite(req); spdy::Http2HeaderBlock headers_with_status_text; headers_with_status_text[":status"] = "200 Some random extra text describing status"; spdy::SpdySerializedFrame reply(spdy_util_.ConstructSpdyResponseHeaders( 1, std::move(headers_with_status_text), false)); AddRead(reply); spdy::SpdySerializedFrame body( spdy_util_.ConstructSpdyDataFrame(1, kPostBodyStringPiece, true)); AddRead(body); spdy::SpdySerializedFrame rst( spdy_util_.ConstructSpdyRstStream(1, spdy::ERROR_CODE_PROTOCOL_ERROR)); AddWrite(rst); AddReadEOF(); SequencedSocketData data(GetReads(), GetWrites()); MockConnect connect_data(SYNCHRONOUS, OK); data.set_connect_data(connect_data); session_deps_.socket_factory->AddSocketDataProvider(&data); AddSSLSocketData(); base::WeakPtr session(CreateDefaultSpdySession()); base::WeakPtr stream = CreateStreamSynchronously( SPDY_REQUEST_RESPONSE_STREAM, session, url_, LOWEST, NetLogWithSource()); ASSERT_TRUE(stream); EXPECT_EQ(kDefaultUrl, stream->url().spec()); StreamDelegateDoNothing delegate(stream); stream->SetDelegate(&delegate); spdy::Http2HeaderBlock headers( spdy_util_.ConstructGetHeaderBlock(kDefaultUrl)); EXPECT_EQ(ERR_IO_PENDING, stream->SendRequestHeaders(std::move(headers), NO_MORE_DATA_TO_SEND)); EXPECT_THAT(delegate.WaitForClose(), IsError(ERR_HTTP2_PROTOCOL_ERROR)); // Finish async network reads and writes. base::RunLoop().RunUntilIdle(); EXPECT_TRUE(data.AllWriteDataConsumed()); EXPECT_TRUE(data.AllReadDataConsumed()); } TEST_F(SpdyStreamTest, StatusMustBePresent) { spdy::SpdySerializedFrame req( spdy_util_.ConstructSpdyGet(nullptr, 0, 1, LOWEST)); AddWrite(req); spdy::Http2HeaderBlock headers_without_status; spdy::SpdySerializedFrame reply(spdy_util_.ConstructSpdyResponseHeaders( 1, std::move(headers_without_status), false)); AddRead(reply); spdy::SpdySerializedFrame body( spdy_util_.ConstructSpdyDataFrame(1, kPostBodyStringPiece, true)); AddRead(body); spdy::SpdySerializedFrame rst( spdy_util_.ConstructSpdyRstStream(1, spdy::ERROR_CODE_PROTOCOL_ERROR)); AddWrite(rst); AddReadEOF(); SequencedSocketData data(GetReads(), GetWrites()); MockConnect connect_data(SYNCHRONOUS, OK); data.set_connect_data(connect_data); session_deps_.socket_factory->AddSocketDataProvider(&data); AddSSLSocketData(); base::WeakPtr session(CreateDefaultSpdySession()); base::WeakPtr stream = CreateStreamSynchronously( SPDY_REQUEST_RESPONSE_STREAM, session, url_, LOWEST, NetLogWithSource()); ASSERT_TRUE(stream); EXPECT_EQ(kDefaultUrl, stream->url().spec()); StreamDelegateDoNothing delegate(stream); stream->SetDelegate(&delegate); spdy::Http2HeaderBlock headers( spdy_util_.ConstructGetHeaderBlock(kDefaultUrl)); EXPECT_EQ(ERR_IO_PENDING, stream->SendRequestHeaders(std::move(headers), NO_MORE_DATA_TO_SEND)); EXPECT_THAT(delegate.WaitForClose(), IsError(ERR_HTTP2_PROTOCOL_ERROR)); // Finish async network reads and writes. base::RunLoop().RunUntilIdle(); EXPECT_TRUE(data.AllWriteDataConsumed()); EXPECT_TRUE(data.AllReadDataConsumed()); } // Call IncreaseSendWindowSize on a stream with a large enough delta to overflow // an int32_t. The SpdyStream should handle that case gracefully. TEST_F(SpdyStreamTest, IncreaseSendWindowSizeOverflow) { spdy::SpdySerializedFrame req(spdy_util_.ConstructSpdyPost( kDefaultUrl, 1, kPostBodyLength, LOWEST, nullptr, 0)); AddWrite(req); AddReadPause(); // Triggered by the overflowing call to IncreaseSendWindowSize // below. spdy::SpdySerializedFrame rst(spdy_util_.ConstructSpdyRstStream( 1, spdy::ERROR_CODE_FLOW_CONTROL_ERROR)); AddWrite(rst); AddReadEOF(); SequencedSocketData data(GetReads(), GetWrites()); MockConnect connect_data(SYNCHRONOUS, OK); data.set_connect_data(connect_data); session_deps_.socket_factory->AddSocketDataProvider(&data); AddSSLSocketData(); base::WeakPtr session(CreateDefaultSpdySession()); base::WeakPtr stream = CreateStreamSynchronously( SPDY_BIDIRECTIONAL_STREAM, session, url_, LOWEST, NetLogWithSource::Make(NetLogSourceType::NONE)); ASSERT_TRUE(stream); EXPECT_EQ(kDefaultUrl, stream->url().spec()); StreamDelegateSendImmediate delegate(stream, kPostBodyStringPiece); stream->SetDelegate(&delegate); spdy::Http2HeaderBlock headers( spdy_util_.ConstructPostHeaderBlock(kDefaultUrl, kPostBodyLength)); EXPECT_THAT(stream->SendRequestHeaders(std::move(headers), MORE_DATA_TO_SEND), IsError(ERR_IO_PENDING)); data.RunUntilPaused(); int32_t old_send_window_size = stream->send_window_size(); ASSERT_GT(old_send_window_size, 0); int32_t delta_window_size = std::numeric_limits::max() - old_send_window_size + 1; stream->IncreaseSendWindowSize(delta_window_size); EXPECT_FALSE(stream); data.Resume(); base::RunLoop().RunUntilIdle(); EXPECT_THAT(delegate.WaitForClose(), IsError(ERR_HTTP2_FLOW_CONTROL_ERROR)); } // Functions used with // RunResumeAfterUnstall{RequestResponse,Bidirectional}Test(). void StallStream(const base::WeakPtr& stream) { // Reduce the send window size to 0 to stall. while (stream->send_window_size() > 0) { stream->DecreaseSendWindowSize( std::min(kMaxSpdyFrameChunkSize, stream->send_window_size())); } } void IncreaseStreamSendWindowSize(const base::WeakPtr& stream, int32_t delta_window_size) { EXPECT_TRUE(stream->send_stalled_by_flow_control()); stream->IncreaseSendWindowSize(delta_window_size); EXPECT_FALSE(stream->send_stalled_by_flow_control()); } void AdjustStreamSendWindowSize(const base::WeakPtr& stream, int32_t delta_window_size) { // Make sure that negative adjustments are handled properly. EXPECT_TRUE(stream->send_stalled_by_flow_control()); EXPECT_TRUE(stream->AdjustSendWindowSize(-delta_window_size)); EXPECT_TRUE(stream->send_stalled_by_flow_control()); EXPECT_TRUE(stream->AdjustSendWindowSize(+delta_window_size)); EXPECT_TRUE(stream->send_stalled_by_flow_control()); EXPECT_TRUE(stream->AdjustSendWindowSize(+delta_window_size)); EXPECT_FALSE(stream->send_stalled_by_flow_control()); } // Given an unstall function, runs a test to make sure that a // request/response (i.e., an HTTP-like) stream resumes after a stall // and unstall. void SpdyStreamTest::RunResumeAfterUnstallRequestResponseTest( UnstallFunction unstall_function) { spdy::SpdySerializedFrame req(spdy_util_.ConstructSpdyPost( kDefaultUrl, 1, kPostBodyLength, LOWEST, nullptr, 0)); AddWrite(req); spdy::SpdySerializedFrame body( spdy_util_.ConstructSpdyDataFrame(1, kPostBodyStringPiece, true)); AddWrite(body); spdy::SpdySerializedFrame resp( spdy_util_.ConstructSpdyGetReply(nullptr, 0, 1)); AddRead(resp); AddReadEOF(); SequencedSocketData data(GetReads(), GetWrites()); MockConnect connect_data(SYNCHRONOUS, OK); data.set_connect_data(connect_data); session_deps_.socket_factory->AddSocketDataProvider(&data); AddSSLSocketData(); base::WeakPtr session(CreateDefaultSpdySession()); base::WeakPtr stream = CreateStreamSynchronously( SPDY_REQUEST_RESPONSE_STREAM, session, url_, LOWEST, NetLogWithSource()); ASSERT_TRUE(stream); EXPECT_EQ(kDefaultUrl, stream->url().spec()); StreamDelegateWithBody delegate(stream, kPostBodyStringPiece); stream->SetDelegate(&delegate); EXPECT_FALSE(stream->send_stalled_by_flow_control()); spdy::Http2HeaderBlock headers( spdy_util_.ConstructPostHeaderBlock(kDefaultUrl, kPostBodyLength)); EXPECT_THAT(stream->SendRequestHeaders(std::move(headers), MORE_DATA_TO_SEND), IsError(ERR_IO_PENDING)); StallStream(stream); base::RunLoop().RunUntilIdle(); EXPECT_TRUE(stream->send_stalled_by_flow_control()); std::move(unstall_function).Run(stream, kPostBodyLength); EXPECT_FALSE(stream->send_stalled_by_flow_control()); EXPECT_THAT(delegate.WaitForClose(), IsError(ERR_CONNECTION_CLOSED)); EXPECT_TRUE(delegate.send_headers_completed()); EXPECT_EQ("200", delegate.GetResponseHeaderValue(":status")); EXPECT_EQ(std::string(), delegate.TakeReceivedData()); EXPECT_TRUE(data.AllWriteDataConsumed()); } TEST_F(SpdyStreamTest, ResumeAfterSendWindowSizeIncreaseRequestResponse) { RunResumeAfterUnstallRequestResponseTest( base::BindOnce(&IncreaseStreamSendWindowSize)); } TEST_F(SpdyStreamTest, ResumeAfterSendWindowSizeAdjustRequestResponse) { RunResumeAfterUnstallRequestResponseTest( base::BindOnce(&AdjustStreamSendWindowSize)); } // Given an unstall function, runs a test to make sure that a bidirectional // (i.e., non-HTTP-like) stream resumes after a stall and unstall. void SpdyStreamTest::RunResumeAfterUnstallBidirectionalTest( UnstallFunction unstall_function) { spdy::SpdySerializedFrame req(spdy_util_.ConstructSpdyPost( kDefaultUrl, 1, kPostBodyLength, LOWEST, nullptr, 0)); AddWrite(req); AddReadPause(); spdy::SpdySerializedFrame resp( spdy_util_.ConstructSpdyGetReply(nullptr, 0, 1)); AddRead(resp); spdy::SpdySerializedFrame msg( spdy_util_.ConstructSpdyDataFrame(1, kPostBodyStringPiece, false)); AddWrite(msg); spdy::SpdySerializedFrame echo( spdy_util_.ConstructSpdyDataFrame(1, kPostBodyStringPiece, false)); AddRead(echo); AddReadEOF(); SequencedSocketData data(GetReads(), GetWrites()); MockConnect connect_data(SYNCHRONOUS, OK); data.set_connect_data(connect_data); session_deps_.socket_factory->AddSocketDataProvider(&data); AddSSLSocketData(); base::WeakPtr session(CreateDefaultSpdySession()); base::WeakPtr stream = CreateStreamSynchronously( SPDY_BIDIRECTIONAL_STREAM, session, url_, LOWEST, NetLogWithSource()); ASSERT_TRUE(stream); EXPECT_EQ(kDefaultUrl, stream->url().spec()); StreamDelegateSendImmediate delegate(stream, kPostBodyStringPiece); stream->SetDelegate(&delegate); spdy::Http2HeaderBlock headers( spdy_util_.ConstructPostHeaderBlock(kDefaultUrl, kPostBodyLength)); EXPECT_THAT(stream->SendRequestHeaders(std::move(headers), MORE_DATA_TO_SEND), IsError(ERR_IO_PENDING)); data.RunUntilPaused(); EXPECT_FALSE(stream->send_stalled_by_flow_control()); StallStream(stream); data.Resume(); base::RunLoop().RunUntilIdle(); EXPECT_TRUE(stream->send_stalled_by_flow_control()); std::move(unstall_function).Run(stream, kPostBodyLength); EXPECT_FALSE(stream->send_stalled_by_flow_control()); EXPECT_THAT(delegate.WaitForClose(), IsError(ERR_CONNECTION_CLOSED)); EXPECT_TRUE(delegate.send_headers_completed()); EXPECT_EQ("200", delegate.GetResponseHeaderValue(":status")); EXPECT_EQ(std::string(kPostBody, kPostBodyLength), delegate.TakeReceivedData()); EXPECT_TRUE(data.AllWriteDataConsumed()); } TEST_F(SpdyStreamTest, ResumeAfterSendWindowSizeIncreaseBidirectional) { RunResumeAfterUnstallBidirectionalTest( base::BindOnce(&IncreaseStreamSendWindowSize)); } TEST_F(SpdyStreamTest, ResumeAfterSendWindowSizeAdjustBidirectional) { RunResumeAfterUnstallBidirectionalTest( base::BindOnce(&AdjustStreamSendWindowSize)); } // Test calculation of amount of bytes received from network. TEST_F(SpdyStreamTest, ReceivedBytes) { spdy::SpdySerializedFrame req( spdy_util_.ConstructSpdyGet(nullptr, 0, 1, LOWEST)); AddWrite(req); AddReadPause(); spdy::SpdySerializedFrame reply( spdy_util_.ConstructSpdyGetReply(nullptr, 0, 1)); AddRead(reply); AddReadPause(); spdy::SpdySerializedFrame msg( spdy_util_.ConstructSpdyDataFrame(1, kPostBodyStringPiece, false)); AddRead(msg); AddReadPause(); AddReadEOF(); SequencedSocketData data(GetReads(), GetWrites()); MockConnect connect_data(SYNCHRONOUS, OK); data.set_connect_data(connect_data); session_deps_.socket_factory->AddSocketDataProvider(&data); AddSSLSocketData(); base::WeakPtr session(CreateDefaultSpdySession()); base::WeakPtr stream = CreateStreamSynchronously( SPDY_REQUEST_RESPONSE_STREAM, session, url_, LOWEST, NetLogWithSource()); ASSERT_TRUE(stream); EXPECT_EQ(kDefaultUrl, stream->url().spec()); StreamDelegateDoNothing delegate(stream); stream->SetDelegate(&delegate); spdy::Http2HeaderBlock headers( spdy_util_.ConstructGetHeaderBlock(kDefaultUrl)); EXPECT_THAT( stream->SendRequestHeaders(std::move(headers), NO_MORE_DATA_TO_SEND), IsError(ERR_IO_PENDING)); int64_t reply_frame_len = reply.size(); int64_t data_header_len = spdy::kDataFrameMinimumSize; int64_t data_frame_len = data_header_len + kPostBodyLength; int64_t response_len = reply_frame_len + data_frame_len; EXPECT_EQ(0, stream->raw_received_bytes()); // REQUEST data.RunUntilPaused(); EXPECT_EQ(0, stream->raw_received_bytes()); // REPLY data.Resume(); data.RunUntilPaused(); EXPECT_EQ(reply_frame_len, stream->raw_received_bytes()); // DATA data.Resume(); data.RunUntilPaused(); EXPECT_EQ(response_len, stream->raw_received_bytes()); // FIN data.Resume(); EXPECT_THAT(delegate.WaitForClose(), IsError(ERR_CONNECTION_CLOSED)); } // Regression test for https://crbug.com/810763. TEST_F(SpdyStreamTest, DataOnHalfClosedRemoveStream) { spdy::SpdySerializedFrame req(spdy_util_.ConstructSpdyPost( kDefaultUrl, 1, kPostBodyLength, LOWEST, nullptr, 0)); AddWrite(req); spdy::Http2HeaderBlock response_headers; response_headers[spdy::kHttp2StatusHeader] = "200"; spdy::SpdySerializedFrame resp(spdy_util_.ConstructSpdyResponseHeaders( 1, std::move(response_headers), /* fin = */ true)); AddRead(resp); spdy::SpdySerializedFrame data_frame( spdy_util_.ConstructSpdyDataFrame(1, kPostBodyStringPiece, true)); AddRead(data_frame); spdy::SpdySerializedFrame rst( spdy_util_.ConstructSpdyRstStream(1, spdy::ERROR_CODE_STREAM_CLOSED)); AddWrite(rst); AddReadEOF(); SequencedSocketData data(GetReads(), GetWrites()); MockConnect connect_data(SYNCHRONOUS, OK); data.set_connect_data(connect_data); session_deps_.socket_factory->AddSocketDataProvider(&data); AddSSLSocketData(); base::WeakPtr session(CreateDefaultSpdySession()); base::WeakPtr stream = CreateStreamSynchronously( SPDY_BIDIRECTIONAL_STREAM, session, url_, LOWEST, NetLogWithSource()); ASSERT_TRUE(stream); EXPECT_EQ(kDefaultUrl, stream->url().spec()); StreamDelegateDoNothing delegate(stream); stream->SetDelegate(&delegate); spdy::Http2HeaderBlock headers( spdy_util_.ConstructPostHeaderBlock(kDefaultUrl, kPostBodyLength)); EXPECT_THAT(stream->SendRequestHeaders(std::move(headers), MORE_DATA_TO_SEND), IsError(ERR_IO_PENDING)); EXPECT_THAT(delegate.WaitForClose(), IsError(ERR_HTTP2_STREAM_CLOSED)); base::RunLoop().RunUntilIdle(); EXPECT_TRUE(data.AllReadDataConsumed()); EXPECT_TRUE(data.AllWriteDataConsumed()); } TEST_F(SpdyStreamTest, DelegateIsInformedOfEOF) { spdy::SpdySerializedFrame req(spdy_util_.ConstructSpdyPost( kDefaultUrl, 1, kPostBodyLength, LOWEST, nullptr, 0)); AddWrite(req); spdy::Http2HeaderBlock response_headers; response_headers[spdy::kHttp2StatusHeader] = "200"; spdy::SpdySerializedFrame resp(spdy_util_.ConstructSpdyResponseHeaders( 1, std::move(response_headers), /* fin = */ true)); AddRead(resp); spdy::SpdySerializedFrame data_frame( spdy_util_.ConstructSpdyDataFrame(1, kPostBodyStringPiece, true)); AddRead(data_frame); spdy::SpdySerializedFrame rst( spdy_util_.ConstructSpdyRstStream(1, spdy::ERROR_CODE_STREAM_CLOSED)); AddWrite(rst); AddReadEOF(); SequencedSocketData data(GetReads(), GetWrites()); MockConnect connect_data(SYNCHRONOUS, OK); data.set_connect_data(connect_data); session_deps_.socket_factory->AddSocketDataProvider(&data); AddSSLSocketData(); base::WeakPtr session(CreateDefaultSpdySession()); base::WeakPtr stream = CreateStreamSynchronously( SPDY_BIDIRECTIONAL_STREAM, session, url_, LOWEST, NetLogWithSource()); ASSERT_TRUE(stream); EXPECT_EQ(kDefaultUrl, stream->url().spec()); StreamDelegateDetectEOF delegate(stream); stream->SetDelegate(&delegate); spdy::Http2HeaderBlock headers( spdy_util_.ConstructPostHeaderBlock(kDefaultUrl, kPostBodyLength)); EXPECT_THAT(stream->SendRequestHeaders(std::move(headers), MORE_DATA_TO_SEND), IsError(ERR_IO_PENDING)); base::RunLoop().RunUntilIdle(); EXPECT_TRUE(delegate.eof_detected()); EXPECT_TRUE(data.AllReadDataConsumed()); EXPECT_TRUE(data.AllWriteDataConsumed()); } // A small read should trigger sending a receive window update and dropping the // count of unacknowledged bytes to zero only after // kDefaultTimeToBufferSmallWindowUpdates time has passed. TEST_F(SpdyStreamTestWithMockClock, FlowControlSlowReads) { spdy::SpdySerializedFrame req( spdy_util_.ConstructSpdyGet(nullptr, 0, 1, LOWEST)); AddWrite(req); AddReadPause(); spdy::SpdySerializedFrame reply( spdy_util_.ConstructSpdyGetReply(nullptr, 0, 1)); AddRead(reply); AddReadPause(); spdy::SpdySerializedFrame msg( spdy_util_.ConstructSpdyDataFrame(1, kPostBodyStringPiece, false)); AddRead(msg); AddReadPause(); AddReadEOF(); SequencedSocketData data(GetReads(), GetWrites()); MockConnect connect_data(SYNCHRONOUS, OK); data.set_connect_data(connect_data); session_deps_.socket_factory->AddSocketDataProvider(&data); AddSSLSocketData(); base::WeakPtr session(CreateDefaultSpdySession()); session->SetTimeToBufferSmallWindowUpdates( kDefaultTimeToBufferSmallWindowUpdates); base::WeakPtr stream = CreateStreamSynchronously( SPDY_REQUEST_RESPONSE_STREAM, session, url_, LOWEST, NetLogWithSource()); ASSERT_TRUE(stream); EXPECT_EQ(kDefaultUrl, stream->url().spec()); StreamDelegateConsumeData delegate(stream); stream->SetDelegate(&delegate); EXPECT_EQ(0, unacked_recv_window_bytes(stream)); spdy::Http2HeaderBlock headers( spdy_util_.ConstructGetHeaderBlock(kDefaultUrl)); EXPECT_THAT( stream->SendRequestHeaders(std::move(headers), NO_MORE_DATA_TO_SEND), IsError(ERR_IO_PENDING)); // REQUEST data.RunUntilPaused(); // REPLY data.Resume(); data.RunUntilPaused(); // Delay long enough for the receive window to send an update on read, // draining the unacked_recv_window_bytes back to zero. AdvanceClock(kDefaultTimeToBufferSmallWindowUpdates); // DATA data.Resume(); data.RunUntilPaused(); EXPECT_EQ(0, unacked_recv_window_bytes(stream)); // FIN data.Resume(); EXPECT_THAT(delegate.WaitForClose(), IsError(ERR_CONNECTION_CLOSED)); } } // namespace net::test