| // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "net/spdy/spdy_session.h" |
| |
| #include <algorithm> |
| #include <string> |
| #include <utility> |
| |
| #include "base/base64.h" |
| #include "base/bind.h" |
| #include "base/callback.h" |
| #include "base/run_loop.h" |
| #include "base/stl_util.h" |
| #include "base/test/metrics/histogram_tester.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "base/test/scoped_task_environment.h" |
| #include "net/base/hex_utils.h" |
| #include "net/base/host_port_pair.h" |
| #include "net/base/io_buffer.h" |
| #include "net/base/ip_endpoint.h" |
| #include "net/base/proxy_delegate.h" |
| #include "net/base/proxy_server.h" |
| #include "net/base/request_priority.h" |
| #include "net/base/test_data_stream.h" |
| #include "net/cert/ct_policy_status.h" |
| #include "net/http/http_request_info.h" |
| #include "net/log/net_log_event_type.h" |
| #include "net/log/net_log_source.h" |
| #include "net/log/test_net_log.h" |
| #include "net/log/test_net_log_entry.h" |
| #include "net/log/test_net_log_util.h" |
| #include "net/socket/client_socket_pool_manager.h" |
| #include "net/socket/socket_tag.h" |
| #include "net/socket/socket_test_util.h" |
| #include "net/spdy/spdy_http_utils.h" |
| #include "net/spdy/spdy_session_pool.h" |
| #include "net/spdy/spdy_session_test_util.h" |
| #include "net/spdy/spdy_stream.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_scoped_task_environment.h" |
| #include "net/third_party/quiche/src/spdy/core/spdy_test_utils.h" |
| #include "net/traffic_annotation/network_traffic_annotation_test_helper.h" |
| #include "starboard/memory.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/platform_test.h" |
| |
| using net::test::IsError; |
| using net::test::IsOk; |
| using net::test::TestServerPushDelegate; |
| using testing::_; |
| |
| #if SB_IS(COMPILER_MSVC) |
| #pragma warning(disable : 4309) |
| #pragma warning(disable : 4838) |
| #endif |
| |
| namespace net { |
| |
| namespace { |
| |
| const char kHttpURLFromAnotherOrigin[] = "http://www.example2.org/a.dat"; |
| const char kHttpsURLFromAnotherOrigin[] = "https://www.example2.org/b.dat"; |
| const char kPushedUrl[] = "https://www.example.org/a.dat"; |
| |
| const char kBodyData[] = "Body data"; |
| const size_t kBodyDataSize = arraysize(kBodyData); |
| const base::StringPiece kBodyDataStringPiece(kBodyData, kBodyDataSize); |
| |
| static base::TimeDelta g_time_delta; |
| static base::TimeTicks g_time_now; |
| |
| base::TimeTicks TheNearFuture() { |
| return base::TimeTicks::Now() + g_time_delta; |
| } |
| |
| base::TimeTicks SlowReads() { |
| g_time_delta += |
| base::TimeDelta::FromMilliseconds(2 * kYieldAfterDurationMilliseconds); |
| return base::TimeTicks::Now() + g_time_delta; |
| } |
| |
| base::TimeTicks InstantaneousReads() { |
| return g_time_now; |
| } |
| |
| class MockRequireCTDelegate : public TransportSecurityState::RequireCTDelegate { |
| public: |
| MOCK_METHOD3(IsCTRequiredForHost, |
| CTRequirementLevel(const std::string& host, |
| const X509Certificate* chain, |
| const HashValueVector& hashes)); |
| }; |
| |
| } // namespace |
| |
| class SpdySessionTest : public PlatformTest, public WithScopedTaskEnvironment { |
| public: |
| // Functions used with RunResumeAfterUnstallTest(). |
| |
| void StallSessionOnly(SpdyStream* stream) { StallSessionSend(); } |
| |
| void StallStreamOnly(SpdyStream* stream) { StallStreamSend(stream); } |
| |
| void StallSessionStream(SpdyStream* stream) { |
| StallSessionSend(); |
| StallStreamSend(stream); |
| } |
| |
| void StallStreamSession(SpdyStream* stream) { |
| StallStreamSend(stream); |
| StallSessionSend(); |
| } |
| |
| void UnstallSessionOnly(SpdyStream* stream, int32_t delta_window_size) { |
| UnstallSessionSend(delta_window_size); |
| } |
| |
| void UnstallStreamOnly(SpdyStream* stream, int32_t delta_window_size) { |
| UnstallStreamSend(stream, delta_window_size); |
| } |
| |
| void UnstallSessionStream(SpdyStream* stream, int32_t delta_window_size) { |
| UnstallSessionSend(delta_window_size); |
| UnstallStreamSend(stream, delta_window_size); |
| } |
| |
| void UnstallStreamSession(SpdyStream* stream, int32_t delta_window_size) { |
| UnstallStreamSend(stream, delta_window_size); |
| UnstallSessionSend(delta_window_size); |
| } |
| |
| protected: |
| SpdySessionTest() |
| : SpdySessionTest( |
| base::test::ScopedTaskEnvironment::MainThreadType::IO){}; |
| |
| explicit SpdySessionTest( |
| base::test::ScopedTaskEnvironment::MainThreadType type) |
| : WithScopedTaskEnvironment(type), |
| old_max_group_sockets_(ClientSocketPoolManager::max_sockets_per_group( |
| HttpNetworkSession::NORMAL_SOCKET_POOL)), |
| old_max_pool_sockets_(ClientSocketPoolManager::max_sockets_per_pool( |
| HttpNetworkSession::NORMAL_SOCKET_POOL)), |
| test_push_delegate_(nullptr), |
| spdy_session_pool_(nullptr), |
| test_url_(kDefaultUrl), |
| test_server_(test_url_), |
| key_(HostPortPair::FromURL(test_url_), |
| ProxyServer::Direct(), |
| PRIVACY_MODE_DISABLED, |
| SocketTag()), |
| ssl_(SYNCHRONOUS, OK) {} |
| |
| ~SpdySessionTest() override { |
| // Important to restore the per-pool limit first, since the pool limit must |
| // always be greater than group limit, and the tests reduce both limits. |
| ClientSocketPoolManager::set_max_sockets_per_pool( |
| HttpNetworkSession::NORMAL_SOCKET_POOL, old_max_pool_sockets_); |
| ClientSocketPoolManager::set_max_sockets_per_group( |
| HttpNetworkSession::NORMAL_SOCKET_POOL, old_max_group_sockets_); |
| } |
| |
| void SetUp() override { |
| g_time_delta = base::TimeDelta(); |
| g_time_now = base::TimeTicks::Now(); |
| session_deps_.net_log = log_.bound().net_log(); |
| session_deps_.enable_server_push_cancellation = true; |
| } |
| |
| void CreateNetworkSession() { |
| DCHECK(!http_session_); |
| DCHECK(!spdy_session_pool_); |
| http_session_ = |
| SpdySessionDependencies::SpdyCreateSession(&session_deps_); |
| auto test_push_delegate = std::make_unique<TestServerPushDelegate>(); |
| test_push_delegate_ = test_push_delegate.get(); |
| http_session_->SetServerPushDelegate(std::move(test_push_delegate)); |
| spdy_session_pool_ = http_session_->spdy_session_pool(); |
| } |
| |
| void AddSSLSocketData() { |
| ssl_.ssl_info.cert = |
| ImportCertFromFile(GetTestCertsDirectory(), "spdy_pooling.pem"); |
| ASSERT_TRUE(ssl_.ssl_info.cert); |
| session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl_); |
| } |
| |
| void CreateSpdySession() { |
| DCHECK(!session_); |
| session_ = |
| ::net::CreateSpdySession(http_session_.get(), key_, log_.bound()); |
| } |
| |
| void CreateTrustedSpdySession() { |
| DCHECK(!session_); |
| session_ = ::net::CreateTrustedSpdySession(http_session_.get(), key_, |
| log_.bound()); |
| } |
| |
| void StallSessionSend() { |
| // Reduce the send window size to 0 to stall. |
| while (session_send_window_size() > 0) { |
| DecreaseSendWindowSize( |
| std::min(kMaxSpdyFrameChunkSize, session_send_window_size())); |
| } |
| } |
| |
| void UnstallSessionSend(int32_t delta_window_size) { |
| IncreaseSendWindowSize(delta_window_size); |
| } |
| |
| void StallStreamSend(SpdyStream* 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 UnstallStreamSend(SpdyStream* stream, int32_t delta_window_size) { |
| stream->IncreaseSendWindowSize(delta_window_size); |
| } |
| |
| void RunResumeAfterUnstallTest( |
| const base::Callback<void(SpdyStream*)>& stall_function, |
| const base::Callback<void(SpdyStream*, int32_t)>& unstall_function); |
| |
| // SpdySession private methods. |
| |
| void MaybeSendPrefacePing() { session_->MaybeSendPrefacePing(); } |
| |
| void WritePingFrame(spdy::SpdyPingId unique_id, bool is_ack) { |
| session_->WritePingFrame(unique_id, is_ack); |
| } |
| |
| void CheckPingStatus(base::TimeTicks last_check_time) { |
| session_->CheckPingStatus(last_check_time); |
| } |
| |
| bool OnUnknownFrame(spdy::SpdyStreamId stream_id, uint8_t frame_type) { |
| return session_->OnUnknownFrame(stream_id, frame_type); |
| } |
| |
| void IncreaseSendWindowSize(int delta_window_size) { |
| session_->IncreaseSendWindowSize(delta_window_size); |
| } |
| |
| void DecreaseSendWindowSize(int32_t delta_window_size) { |
| session_->DecreaseSendWindowSize(delta_window_size); |
| } |
| |
| void IncreaseRecvWindowSize(int delta_window_size) { |
| session_->IncreaseRecvWindowSize(delta_window_size); |
| } |
| |
| void DecreaseRecvWindowSize(int32_t delta_window_size) { |
| session_->DecreaseRecvWindowSize(delta_window_size); |
| } |
| |
| // Accessors for SpdySession private members. |
| |
| void set_in_io_loop(bool in_io_loop) { session_->in_io_loop_ = in_io_loop; } |
| |
| void set_stream_hi_water_mark(spdy::SpdyStreamId stream_hi_water_mark) { |
| session_->stream_hi_water_mark_ = stream_hi_water_mark; |
| } |
| |
| void set_last_accepted_push_stream_id( |
| spdy::SpdyStreamId last_accepted_push_stream_id) { |
| session_->last_accepted_push_stream_id_ = last_accepted_push_stream_id; |
| } |
| |
| size_t num_pushed_streams() { return session_->num_pushed_streams_; } |
| |
| size_t num_active_pushed_streams() { |
| return session_->num_active_pushed_streams_; |
| } |
| |
| size_t max_concurrent_streams() { return session_->max_concurrent_streams_; } |
| |
| void set_max_concurrent_streams(size_t max_concurrent_streams) { |
| session_->max_concurrent_streams_ = max_concurrent_streams; |
| } |
| |
| void set_max_concurrent_pushed_streams(size_t max_concurrent_pushed_streams) { |
| session_->max_concurrent_pushed_streams_ = max_concurrent_pushed_streams; |
| } |
| |
| bool ping_in_flight() { return session_->ping_in_flight_; } |
| |
| spdy::SpdyPingId next_ping_id() { return session_->next_ping_id_; } |
| |
| base::TimeTicks last_read_time() { return session_->last_read_time_; } |
| |
| bool check_ping_status_pending() { |
| return session_->check_ping_status_pending_; |
| } |
| |
| int32_t session_send_window_size() { |
| return session_->session_send_window_size_; |
| } |
| |
| int32_t session_recv_window_size() { |
| return session_->session_recv_window_size_; |
| } |
| |
| void set_session_recv_window_size(int32_t session_recv_window_size) { |
| session_->session_recv_window_size_ = session_recv_window_size; |
| } |
| |
| int32_t session_unacked_recv_window_bytes() { |
| return session_->session_unacked_recv_window_bytes_; |
| } |
| |
| int32_t stream_initial_send_window_size() { |
| return session_->stream_initial_send_window_size_; |
| } |
| |
| void set_connection_at_risk_of_loss_time(base::TimeDelta duration) { |
| session_->connection_at_risk_of_loss_time_ = duration; |
| } |
| |
| // Quantities derived from SpdySession private members. |
| |
| size_t pending_create_stream_queue_size(RequestPriority priority) { |
| DCHECK_GE(priority, MINIMUM_PRIORITY); |
| DCHECK_LE(priority, MAXIMUM_PRIORITY); |
| return session_->pending_create_stream_queues_[priority].size(); |
| } |
| |
| size_t num_active_streams() { return session_->active_streams_.size(); } |
| |
| size_t num_created_streams() { return session_->created_streams_.size(); } |
| |
| size_t num_unclaimed_pushed_streams() { |
| return spdy_session_pool_->push_promise_index()->CountStreamsForSession( |
| session_.get()); |
| } |
| |
| bool has_unclaimed_pushed_stream_for_url(const GURL& url) { |
| return spdy_session_pool_->push_promise_index()->FindStream( |
| url, session_.get()) != kNoPushedStreamFound; |
| } |
| |
| // Original socket limits. Some tests set these. Safest to always restore |
| // them once each test has been run. |
| int old_max_group_sockets_; |
| int old_max_pool_sockets_; |
| |
| SpdyTestUtil spdy_util_; |
| SpdySessionDependencies session_deps_; |
| std::unique_ptr<HttpNetworkSession> http_session_; |
| base::WeakPtr<SpdySession> session_; |
| TestServerPushDelegate* test_push_delegate_; |
| SpdySessionPool* spdy_session_pool_; |
| const GURL test_url_; |
| const url::SchemeHostPort test_server_; |
| SpdySessionKey key_; |
| SSLSocketDataProvider ssl_; |
| BoundTestNetLog log_; |
| }; |
| |
| class SpdySessionTestWithMockTime : public SpdySessionTest { |
| protected: |
| SpdySessionTestWithMockTime() |
| : SpdySessionTest( |
| base::test::ScopedTaskEnvironment::MainThreadType::MOCK_TIME){}; |
| }; |
| |
| // Try to create a SPDY session that will fail during |
| // initialization. Nothing should blow up. |
| TEST_F(SpdySessionTest, InitialReadError) { |
| CreateNetworkSession(); |
| |
| session_ = TryCreateFakeSpdySessionExpectingFailure(spdy_session_pool_, key_, |
| ERR_CONNECTION_CLOSED); |
| EXPECT_TRUE(session_); |
| // Flush the read. |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_FALSE(session_); |
| } |
| |
| namespace { |
| |
| // A helper class that vends a callback that, when fired, destroys a |
| // given SpdyStreamRequest. |
| class StreamRequestDestroyingCallback : public TestCompletionCallbackBase { |
| public: |
| StreamRequestDestroyingCallback() = default; |
| |
| ~StreamRequestDestroyingCallback() override = default; |
| |
| void SetRequestToDestroy(std::unique_ptr<SpdyStreamRequest> request) { |
| request_ = std::move(request); |
| } |
| |
| CompletionOnceCallback MakeCallback() { |
| return base::BindOnce(&StreamRequestDestroyingCallback::OnComplete, |
| base::Unretained(this)); |
| } |
| |
| private: |
| void OnComplete(int result) { |
| request_.reset(); |
| SetResult(result); |
| } |
| |
| std::unique_ptr<SpdyStreamRequest> request_; |
| }; |
| |
| } // namespace |
| |
| // Request kInitialMaxConcurrentStreams streams. Request two more |
| // streams, but have the callback for one destroy the second stream |
| // request. Close the session. Nothing should blow up. This is a |
| // regression test for http://crbug.com/250841 . |
| TEST_F(SpdySessionTest, PendingStreamCancellingAnother) { |
| MockRead reads[] = {MockRead(ASYNC, 0, 0), }; |
| |
| SequencedSocketData data(reads, base::span<MockWrite>()); |
| session_deps_.socket_factory->AddSocketDataProvider(&data); |
| |
| AddSSLSocketData(); |
| |
| CreateNetworkSession(); |
| CreateSpdySession(); |
| |
| // Create the maximum number of concurrent streams. |
| for (size_t i = 0; i < kInitialMaxConcurrentStreams; ++i) { |
| base::WeakPtr<SpdyStream> spdy_stream = |
| CreateStreamSynchronously(SPDY_BIDIRECTIONAL_STREAM, session_, |
| test_url_, MEDIUM, NetLogWithSource()); |
| ASSERT_TRUE(spdy_stream); |
| } |
| |
| SpdyStreamRequest request1; |
| auto request2 = std::make_unique<SpdyStreamRequest>(); |
| |
| StreamRequestDestroyingCallback callback1; |
| ASSERT_EQ(ERR_IO_PENDING, |
| request1.StartRequest(SPDY_BIDIRECTIONAL_STREAM, session_, |
| test_url_, MEDIUM, SocketTag(), |
| NetLogWithSource(), callback1.MakeCallback(), |
| TRAFFIC_ANNOTATION_FOR_TESTS)); |
| |
| // |callback2| is never called. |
| TestCompletionCallback callback2; |
| ASSERT_EQ(ERR_IO_PENDING, |
| request2->StartRequest(SPDY_BIDIRECTIONAL_STREAM, session_, |
| test_url_, MEDIUM, SocketTag(), |
| NetLogWithSource(), callback2.callback(), |
| TRAFFIC_ANNOTATION_FOR_TESTS)); |
| |
| callback1.SetRequestToDestroy(std::move(request2)); |
| |
| session_->CloseSessionOnError(ERR_ABORTED, "Aborting session"); |
| |
| EXPECT_THAT(callback1.WaitForResult(), IsError(ERR_ABORTED)); |
| } |
| |
| // A session receiving a GOAWAY frame with no active streams should close. |
| TEST_F(SpdySessionTest, GoAwayWithNoActiveStreams) { |
| spdy::SpdySerializedFrame goaway(spdy_util_.ConstructSpdyGoAway(1)); |
| MockRead reads[] = { |
| CreateMockRead(goaway, 0), |
| }; |
| SequencedSocketData data(reads, base::span<MockWrite>()); |
| session_deps_.socket_factory->AddSocketDataProvider(&data); |
| |
| AddSSLSocketData(); |
| |
| CreateNetworkSession(); |
| CreateSpdySession(); |
| |
| EXPECT_TRUE(HasSpdySession(spdy_session_pool_, key_)); |
| |
| // Read and process the GOAWAY frame. |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_FALSE(HasSpdySession(spdy_session_pool_, key_)); |
| EXPECT_FALSE(session_); |
| } |
| |
| // A session receiving a GOAWAY frame immediately with no active |
| // streams should then close. |
| TEST_F(SpdySessionTest, GoAwayImmediatelyWithNoActiveStreams) { |
| spdy::SpdySerializedFrame goaway(spdy_util_.ConstructSpdyGoAway(1)); |
| MockRead reads[] = { |
| CreateMockRead(goaway, 0, SYNCHRONOUS), MockRead(ASYNC, 0, 1) // EOF |
| }; |
| SequencedSocketData data(reads, base::span<MockWrite>()); |
| session_deps_.socket_factory->AddSocketDataProvider(&data); |
| |
| AddSSLSocketData(); |
| |
| CreateNetworkSession(); |
| CreateSpdySession(); |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_FALSE(session_); |
| EXPECT_FALSE(HasSpdySession(spdy_session_pool_, key_)); |
| EXPECT_FALSE(data.AllReadDataConsumed()); |
| } |
| |
| // A session receiving a GOAWAY frame with active streams should close |
| // when the last active stream is closed. |
| TEST_F(SpdySessionTest, GoAwayWithActiveStreams) { |
| spdy::SpdySerializedFrame goaway(spdy_util_.ConstructSpdyGoAway(1)); |
| MockRead reads[] = { |
| MockRead(ASYNC, ERR_IO_PENDING, 2), CreateMockRead(goaway, 3), |
| MockRead(ASYNC, ERR_IO_PENDING, 4), MockRead(ASYNC, 0, 5) // EOF |
| }; |
| spdy::SpdySerializedFrame req1( |
| spdy_util_.ConstructSpdyGet(nullptr, 0, 1, MEDIUM)); |
| spdy::SpdySerializedFrame req2( |
| spdy_util_.ConstructSpdyGet(nullptr, 0, 3, MEDIUM)); |
| MockWrite writes[] = { |
| CreateMockWrite(req1, 0), CreateMockWrite(req2, 1), |
| }; |
| SequencedSocketData data(reads, writes); |
| session_deps_.socket_factory->AddSocketDataProvider(&data); |
| |
| AddSSLSocketData(); |
| |
| CreateNetworkSession(); |
| CreateSpdySession(); |
| |
| base::WeakPtr<SpdyStream> spdy_stream1 = |
| CreateStreamSynchronously(SPDY_REQUEST_RESPONSE_STREAM, session_, |
| test_url_, MEDIUM, NetLogWithSource()); |
| test::StreamDelegateDoNothing delegate1(spdy_stream1); |
| spdy_stream1->SetDelegate(&delegate1); |
| |
| base::WeakPtr<SpdyStream> spdy_stream2 = |
| CreateStreamSynchronously(SPDY_REQUEST_RESPONSE_STREAM, session_, |
| test_url_, MEDIUM, NetLogWithSource()); |
| test::StreamDelegateDoNothing delegate2(spdy_stream2); |
| spdy_stream2->SetDelegate(&delegate2); |
| |
| spdy::SpdyHeaderBlock headers( |
| spdy_util_.ConstructGetHeaderBlock(kDefaultUrl)); |
| spdy::SpdyHeaderBlock headers2(headers.Clone()); |
| |
| spdy_stream1->SendRequestHeaders(std::move(headers), NO_MORE_DATA_TO_SEND); |
| spdy_stream2->SendRequestHeaders(std::move(headers2), NO_MORE_DATA_TO_SEND); |
| |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_EQ(1u, spdy_stream1->stream_id()); |
| EXPECT_EQ(3u, spdy_stream2->stream_id()); |
| |
| EXPECT_TRUE(HasSpdySession(spdy_session_pool_, key_)); |
| |
| // Read and process the GOAWAY frame. |
| data.Resume(); |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_FALSE(HasSpdySession(spdy_session_pool_, key_)); |
| |
| EXPECT_FALSE(session_->IsStreamActive(3)); |
| EXPECT_FALSE(spdy_stream2); |
| EXPECT_TRUE(session_->IsStreamActive(1)); |
| |
| EXPECT_TRUE(session_->IsGoingAway()); |
| |
| // Should close the session. |
| spdy_stream1->Close(); |
| EXPECT_FALSE(spdy_stream1); |
| |
| EXPECT_TRUE(session_); |
| data.Resume(); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_FALSE(session_); |
| } |
| |
| // Regression test for https://crbug.com/547130. |
| TEST_F(SpdySessionTest, GoAwayWithActiveAndCreatedStream) { |
| spdy::SpdySerializedFrame goaway(spdy_util_.ConstructSpdyGoAway(0)); |
| MockRead reads[] = { |
| MockRead(ASYNC, ERR_IO_PENDING, 1), CreateMockRead(goaway, 2), |
| }; |
| |
| // No |req2|, because the second stream will never get activated. |
| spdy::SpdySerializedFrame req1( |
| spdy_util_.ConstructSpdyGet(nullptr, 0, 1, MEDIUM)); |
| MockWrite writes[] = { |
| CreateMockWrite(req1, 0), |
| }; |
| SequencedSocketData data(reads, writes); |
| session_deps_.socket_factory->AddSocketDataProvider(&data); |
| |
| AddSSLSocketData(); |
| |
| CreateNetworkSession(); |
| CreateSpdySession(); |
| |
| base::WeakPtr<SpdyStream> spdy_stream1 = |
| CreateStreamSynchronously(SPDY_REQUEST_RESPONSE_STREAM, session_, |
| test_url_, MEDIUM, NetLogWithSource()); |
| test::StreamDelegateDoNothing delegate1(spdy_stream1); |
| spdy_stream1->SetDelegate(&delegate1); |
| spdy::SpdyHeaderBlock headers1( |
| spdy_util_.ConstructGetHeaderBlock(kDefaultUrl)); |
| spdy_stream1->SendRequestHeaders(std::move(headers1), NO_MORE_DATA_TO_SEND); |
| |
| EXPECT_EQ(0u, spdy_stream1->stream_id()); |
| |
| // Active stream 1. |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_EQ(1u, spdy_stream1->stream_id()); |
| EXPECT_TRUE(session_->IsStreamActive(1)); |
| |
| // Create stream corresponding to the next request. |
| base::WeakPtr<SpdyStream> spdy_stream2 = |
| CreateStreamSynchronously(SPDY_REQUEST_RESPONSE_STREAM, session_, |
| test_url_, MEDIUM, NetLogWithSource()); |
| |
| EXPECT_EQ(0u, spdy_stream2->stream_id()); |
| |
| // Read and process the GOAWAY frame before the second stream could be |
| // activated. |
| data.Resume(); |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_FALSE(session_); |
| |
| EXPECT_TRUE(data.AllWriteDataConsumed()); |
| EXPECT_TRUE(data.AllReadDataConsumed()); |
| } |
| |
| // Have a session receive two GOAWAY frames, with the last one causing |
| // the last active stream to be closed. The session should then be |
| // closed after the second GOAWAY frame. |
| TEST_F(SpdySessionTest, GoAwayTwice) { |
| spdy::SpdySerializedFrame goaway1(spdy_util_.ConstructSpdyGoAway(1)); |
| spdy::SpdySerializedFrame goaway2(spdy_util_.ConstructSpdyGoAway(0)); |
| MockRead reads[] = { |
| MockRead(ASYNC, ERR_IO_PENDING, 2), CreateMockRead(goaway1, 3), |
| MockRead(ASYNC, ERR_IO_PENDING, 4), CreateMockRead(goaway2, 5), |
| MockRead(ASYNC, ERR_IO_PENDING, 6), MockRead(ASYNC, 0, 7) // EOF |
| }; |
| spdy::SpdySerializedFrame req1( |
| spdy_util_.ConstructSpdyGet(nullptr, 0, 1, MEDIUM)); |
| spdy::SpdySerializedFrame req2( |
| spdy_util_.ConstructSpdyGet(nullptr, 0, 3, MEDIUM)); |
| MockWrite writes[] = { |
| CreateMockWrite(req1, 0), CreateMockWrite(req2, 1), |
| }; |
| SequencedSocketData data(reads, writes); |
| session_deps_.socket_factory->AddSocketDataProvider(&data); |
| |
| AddSSLSocketData(); |
| |
| CreateNetworkSession(); |
| CreateSpdySession(); |
| |
| base::WeakPtr<SpdyStream> spdy_stream1 = |
| CreateStreamSynchronously(SPDY_REQUEST_RESPONSE_STREAM, session_, |
| test_url_, MEDIUM, NetLogWithSource()); |
| test::StreamDelegateDoNothing delegate1(spdy_stream1); |
| spdy_stream1->SetDelegate(&delegate1); |
| |
| base::WeakPtr<SpdyStream> spdy_stream2 = |
| CreateStreamSynchronously(SPDY_REQUEST_RESPONSE_STREAM, session_, |
| test_url_, MEDIUM, NetLogWithSource()); |
| test::StreamDelegateDoNothing delegate2(spdy_stream2); |
| spdy_stream2->SetDelegate(&delegate2); |
| |
| spdy::SpdyHeaderBlock headers( |
| spdy_util_.ConstructGetHeaderBlock(kDefaultUrl)); |
| spdy::SpdyHeaderBlock headers2(headers.Clone()); |
| |
| spdy_stream1->SendRequestHeaders(std::move(headers), NO_MORE_DATA_TO_SEND); |
| spdy_stream2->SendRequestHeaders(std::move(headers2), NO_MORE_DATA_TO_SEND); |
| |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_EQ(1u, spdy_stream1->stream_id()); |
| EXPECT_EQ(3u, spdy_stream2->stream_id()); |
| |
| EXPECT_TRUE(HasSpdySession(spdy_session_pool_, key_)); |
| |
| // Read and process the first GOAWAY frame. |
| data.Resume(); |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_FALSE(HasSpdySession(spdy_session_pool_, key_)); |
| |
| EXPECT_FALSE(session_->IsStreamActive(3)); |
| EXPECT_FALSE(spdy_stream2); |
| EXPECT_TRUE(session_->IsStreamActive(1)); |
| EXPECT_TRUE(session_->IsGoingAway()); |
| |
| // Read and process the second GOAWAY frame, which should close the |
| // session. |
| data.Resume(); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_FALSE(session_); |
| } |
| |
| // Have a session with active streams receive a GOAWAY frame and then |
| // close it. It should handle the close properly (i.e., not try to |
| // make itself unavailable in its pool twice). |
| TEST_F(SpdySessionTest, GoAwayWithActiveStreamsThenClose) { |
| spdy::SpdySerializedFrame goaway(spdy_util_.ConstructSpdyGoAway(1)); |
| MockRead reads[] = { |
| MockRead(ASYNC, ERR_IO_PENDING, 2), CreateMockRead(goaway, 3), |
| MockRead(ASYNC, ERR_IO_PENDING, 4), MockRead(ASYNC, 0, 5) // EOF |
| }; |
| spdy::SpdySerializedFrame req1( |
| spdy_util_.ConstructSpdyGet(nullptr, 0, 1, MEDIUM)); |
| spdy::SpdySerializedFrame req2( |
| spdy_util_.ConstructSpdyGet(nullptr, 0, 3, MEDIUM)); |
| MockWrite writes[] = { |
| CreateMockWrite(req1, 0), CreateMockWrite(req2, 1), |
| }; |
| SequencedSocketData data(reads, writes); |
| session_deps_.socket_factory->AddSocketDataProvider(&data); |
| |
| AddSSLSocketData(); |
| |
| CreateNetworkSession(); |
| CreateSpdySession(); |
| |
| base::WeakPtr<SpdyStream> spdy_stream1 = |
| CreateStreamSynchronously(SPDY_REQUEST_RESPONSE_STREAM, session_, |
| test_url_, MEDIUM, NetLogWithSource()); |
| test::StreamDelegateDoNothing delegate1(spdy_stream1); |
| spdy_stream1->SetDelegate(&delegate1); |
| |
| base::WeakPtr<SpdyStream> spdy_stream2 = |
| CreateStreamSynchronously(SPDY_REQUEST_RESPONSE_STREAM, session_, |
| test_url_, MEDIUM, NetLogWithSource()); |
| test::StreamDelegateDoNothing delegate2(spdy_stream2); |
| spdy_stream2->SetDelegate(&delegate2); |
| |
| spdy::SpdyHeaderBlock headers( |
| spdy_util_.ConstructGetHeaderBlock(kDefaultUrl)); |
| spdy::SpdyHeaderBlock headers2(headers.Clone()); |
| |
| spdy_stream1->SendRequestHeaders(std::move(headers), NO_MORE_DATA_TO_SEND); |
| spdy_stream2->SendRequestHeaders(std::move(headers2), NO_MORE_DATA_TO_SEND); |
| |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_EQ(1u, spdy_stream1->stream_id()); |
| EXPECT_EQ(3u, spdy_stream2->stream_id()); |
| |
| EXPECT_TRUE(HasSpdySession(spdy_session_pool_, key_)); |
| |
| // Read and process the GOAWAY frame. |
| data.Resume(); |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_FALSE(HasSpdySession(spdy_session_pool_, key_)); |
| |
| EXPECT_FALSE(session_->IsStreamActive(3)); |
| EXPECT_FALSE(spdy_stream2); |
| EXPECT_TRUE(session_->IsStreamActive(1)); |
| EXPECT_TRUE(session_->IsGoingAway()); |
| |
| session_->CloseSessionOnError(ERR_ABORTED, "Aborting session"); |
| EXPECT_FALSE(spdy_stream1); |
| |
| data.Resume(); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_FALSE(session_); |
| } |
| |
| // Process a joint read buffer which causes the session to begin draining, and |
| // then processes a GOAWAY. The session should gracefully drain. Regression test |
| // for crbug.com/379469 |
| TEST_F(SpdySessionTest, GoAwayWhileDraining) { |
| spdy::SpdySerializedFrame req( |
| spdy_util_.ConstructSpdyGet(nullptr, 0, 1, MEDIUM)); |
| MockWrite writes[] = { |
| CreateMockWrite(req, 0), |
| }; |
| |
| spdy::SpdySerializedFrame resp( |
| spdy_util_.ConstructSpdyGetReply(nullptr, 0, 1)); |
| spdy::SpdySerializedFrame goaway(spdy_util_.ConstructSpdyGoAway(1)); |
| spdy::SpdySerializedFrame body(spdy_util_.ConstructSpdyDataFrame(1, true)); |
| size_t joint_size = goaway.size() * 2 + body.size(); |
| |
| // Compose interleaved |goaway| and |body| frames into a single read. |
| auto buffer = std::make_unique<char[]>(joint_size); |
| { |
| size_t out = 0; |
| memcpy(&buffer[out], goaway.data(), goaway.size()); |
| out += goaway.size(); |
| memcpy(&buffer[out], body.data(), body.size()); |
| out += body.size(); |
| memcpy(&buffer[out], goaway.data(), goaway.size()); |
| out += goaway.size(); |
| ASSERT_EQ(out, joint_size); |
| } |
| spdy::SpdySerializedFrame joint_frames(buffer.get(), joint_size, false); |
| |
| MockRead reads[] = { |
| CreateMockRead(resp, 1), CreateMockRead(joint_frames, 2), |
| MockRead(ASYNC, 0, 3) // EOF |
| }; |
| |
| SequencedSocketData data(reads, writes); |
| session_deps_.socket_factory->AddSocketDataProvider(&data); |
| |
| AddSSLSocketData(); |
| |
| CreateNetworkSession(); |
| CreateSpdySession(); |
| |
| base::WeakPtr<SpdyStream> spdy_stream = |
| CreateStreamSynchronously(SPDY_REQUEST_RESPONSE_STREAM, session_, |
| test_url_, MEDIUM, NetLogWithSource()); |
| test::StreamDelegateDoNothing delegate(spdy_stream); |
| spdy_stream->SetDelegate(&delegate); |
| |
| spdy::SpdyHeaderBlock headers( |
| spdy_util_.ConstructGetHeaderBlock(kDefaultUrl)); |
| spdy_stream->SendRequestHeaders(std::move(headers), NO_MORE_DATA_TO_SEND); |
| |
| base::RunLoop().RunUntilIdle(); |
| |
| // Stream and session closed gracefully. |
| EXPECT_TRUE(delegate.StreamIsClosed()); |
| EXPECT_THAT(delegate.WaitForClose(), IsOk()); |
| EXPECT_EQ(kUploadData, delegate.TakeReceivedData()); |
| EXPECT_FALSE(session_); |
| } |
| |
| // Try to create a stream after receiving a GOAWAY frame. It should |
| // fail. |
| TEST_F(SpdySessionTest, CreateStreamAfterGoAway) { |
| spdy::SpdySerializedFrame goaway(spdy_util_.ConstructSpdyGoAway(1)); |
| MockRead reads[] = { |
| MockRead(ASYNC, ERR_IO_PENDING, 1), CreateMockRead(goaway, 2), |
| MockRead(ASYNC, ERR_IO_PENDING, 3), MockRead(ASYNC, 0, 4) // EOF |
| }; |
| spdy::SpdySerializedFrame req( |
| spdy_util_.ConstructSpdyGet(nullptr, 0, 1, MEDIUM)); |
| MockWrite writes[] = { |
| CreateMockWrite(req, 0), |
| }; |
| SequencedSocketData data(reads, writes); |
| session_deps_.socket_factory->AddSocketDataProvider(&data); |
| |
| AddSSLSocketData(); |
| |
| CreateNetworkSession(); |
| CreateSpdySession(); |
| |
| base::WeakPtr<SpdyStream> spdy_stream = |
| CreateStreamSynchronously(SPDY_REQUEST_RESPONSE_STREAM, session_, |
| test_url_, MEDIUM, NetLogWithSource()); |
| test::StreamDelegateDoNothing delegate(spdy_stream); |
| spdy_stream->SetDelegate(&delegate); |
| |
| spdy::SpdyHeaderBlock headers( |
| spdy_util_.ConstructGetHeaderBlock(kDefaultUrl)); |
| spdy_stream->SendRequestHeaders(std::move(headers), NO_MORE_DATA_TO_SEND); |
| |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_EQ(1u, spdy_stream->stream_id()); |
| |
| EXPECT_TRUE(HasSpdySession(spdy_session_pool_, key_)); |
| |
| // Read and process the GOAWAY frame. |
| data.Resume(); |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_FALSE(HasSpdySession(spdy_session_pool_, key_)); |
| EXPECT_TRUE(session_->IsStreamActive(1)); |
| |
| SpdyStreamRequest stream_request; |
| int rv = stream_request.StartRequest( |
| SPDY_REQUEST_RESPONSE_STREAM, session_, test_url_, MEDIUM, SocketTag(), |
| NetLogWithSource(), CompletionOnceCallback(), |
| TRAFFIC_ANNOTATION_FOR_TESTS); |
| EXPECT_THAT(rv, IsError(ERR_FAILED)); |
| |
| EXPECT_TRUE(session_); |
| data.Resume(); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_FALSE(session_); |
| } |
| |
| // Receiving a HEADERS frame after a GOAWAY frame should result in |
| // the stream being refused. |
| TEST_F(SpdySessionTest, HeadersAfterGoAway) { |
| base::HistogramTester histogram_tester; |
| |
| spdy::SpdySerializedFrame goaway(spdy_util_.ConstructSpdyGoAway(1)); |
| spdy::SpdySerializedFrame push( |
| spdy_util_.ConstructSpdyPush(nullptr, 0, 2, 1, kDefaultUrl)); |
| MockRead reads[] = { |
| MockRead(ASYNC, ERR_IO_PENDING, 1), CreateMockRead(goaway, 2), |
| MockRead(ASYNC, ERR_IO_PENDING, 3), CreateMockRead(push, 4), |
| MockRead(ASYNC, 0, 6) // EOF |
| }; |
| spdy::SpdySerializedFrame req( |
| spdy_util_.ConstructSpdyGet(nullptr, 0, 1, MEDIUM)); |
| spdy::SpdySerializedFrame rst( |
| spdy_util_.ConstructSpdyRstStream(2, spdy::ERROR_CODE_REFUSED_STREAM)); |
| MockWrite writes[] = {CreateMockWrite(req, 0), CreateMockWrite(rst, 5)}; |
| SequencedSocketData data(reads, writes); |
| session_deps_.socket_factory->AddSocketDataProvider(&data); |
| |
| AddSSLSocketData(); |
| |
| CreateNetworkSession(); |
| CreateSpdySession(); |
| |
| base::WeakPtr<SpdyStream> spdy_stream = |
| CreateStreamSynchronously(SPDY_REQUEST_RESPONSE_STREAM, session_, |
| test_url_, MEDIUM, NetLogWithSource()); |
| test::StreamDelegateDoNothing delegate(spdy_stream); |
| spdy_stream->SetDelegate(&delegate); |
| |
| spdy::SpdyHeaderBlock headers( |
| spdy_util_.ConstructGetHeaderBlock(kDefaultUrl)); |
| spdy_stream->SendRequestHeaders(std::move(headers), NO_MORE_DATA_TO_SEND); |
| |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_EQ(1u, spdy_stream->stream_id()); |
| |
| EXPECT_TRUE(HasSpdySession(spdy_session_pool_, key_)); |
| |
| // Read and process the GOAWAY frame. |
| data.Resume(); |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_FALSE(HasSpdySession(spdy_session_pool_, key_)); |
| EXPECT_TRUE(session_->IsStreamActive(1)); |
| |
| // Read and process the HEADERS frame, the subsequent RST_STREAM, |
| // and EOF. |
| data.Resume(); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_FALSE(session_); |
| |
| histogram_tester.ExpectBucketCount( |
| "Net.SpdyPushedStreamFate", |
| static_cast<int>(SpdyPushedStreamFate::kGoingAway), 1); |
| histogram_tester.ExpectTotalCount("Net.SpdyPushedStreamFate", 1); |
| } |
| |
| // A session observing a network change with active streams should close |
| // when the last active stream is closed. |
| TEST_F(SpdySessionTest, NetworkChangeWithActiveStreams) { |
| MockRead reads[] = { |
| MockRead(ASYNC, ERR_IO_PENDING, 1), MockRead(ASYNC, 0, 2) // EOF |
| }; |
| spdy::SpdySerializedFrame req1( |
| spdy_util_.ConstructSpdyGet(nullptr, 0, 1, MEDIUM)); |
| MockWrite writes[] = { |
| CreateMockWrite(req1, 0), |
| }; |
| SequencedSocketData data(reads, writes); |
| session_deps_.socket_factory->AddSocketDataProvider(&data); |
| |
| AddSSLSocketData(); |
| |
| CreateNetworkSession(); |
| CreateSpdySession(); |
| |
| base::WeakPtr<SpdyStream> spdy_stream = |
| CreateStreamSynchronously(SPDY_REQUEST_RESPONSE_STREAM, session_, |
| test_url_, MEDIUM, NetLogWithSource()); |
| test::StreamDelegateDoNothing delegate(spdy_stream); |
| spdy_stream->SetDelegate(&delegate); |
| |
| spdy::SpdyHeaderBlock headers( |
| spdy_util_.ConstructGetHeaderBlock(kDefaultUrl)); |
| |
| spdy_stream->SendRequestHeaders(std::move(headers), NO_MORE_DATA_TO_SEND); |
| |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_EQ(1u, spdy_stream->stream_id()); |
| |
| EXPECT_TRUE(HasSpdySession(spdy_session_pool_, key_)); |
| |
| spdy_session_pool_->OnIPAddressChanged(); |
| |
| // The SpdySessionPool behavior differs based on how the OSs reacts to |
| // network changes; see comment in SpdySessionPool::OnIPAddressChanged(). |
| #if defined(OS_ANDROID) || defined(OS_WIN) || defined(OS_IOS) |
| // For OSs where the TCP connections will close upon relevant network |
| // changes, SpdySessionPool doesn't need to force them to close, so in these |
| // cases verify the session has become unavailable but remains open and the |
| // pre-existing stream is still active. |
| EXPECT_FALSE(HasSpdySession(spdy_session_pool_, key_)); |
| |
| EXPECT_TRUE(session_->IsGoingAway()); |
| |
| EXPECT_TRUE(session_->IsStreamActive(1)); |
| |
| // Should close the session. |
| spdy_stream->Close(); |
| #endif |
| EXPECT_FALSE(spdy_stream); |
| |
| data.Resume(); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_FALSE(session_); |
| } |
| |
| TEST_F(SpdySessionTestWithMockTime, ClientPing) { |
| session_deps_.enable_ping = true; |
| |
| spdy::SpdySerializedFrame read_ping(spdy_util_.ConstructSpdyPing(1, true)); |
| MockRead reads[] = { |
| CreateMockRead(read_ping, 1), MockRead(ASYNC, ERR_IO_PENDING, 2), |
| MockRead(ASYNC, 0, 3) // EOF |
| }; |
| spdy::SpdySerializedFrame write_ping(spdy_util_.ConstructSpdyPing(1, false)); |
| MockWrite writes[] = { |
| CreateMockWrite(write_ping, 0), |
| }; |
| SequencedSocketData data(reads, writes); |
| session_deps_.socket_factory->AddSocketDataProvider(&data); |
| |
| AddSSLSocketData(); |
| |
| CreateNetworkSession(); |
| CreateSpdySession(); |
| |
| base::WeakPtr<SpdyStream> spdy_stream1 = |
| CreateStreamSynchronously(SPDY_BIDIRECTIONAL_STREAM, session_, test_url_, |
| MEDIUM, NetLogWithSource()); |
| ASSERT_TRUE(spdy_stream1); |
| test::StreamDelegateSendImmediate delegate(spdy_stream1, nullptr); |
| spdy_stream1->SetDelegate(&delegate); |
| |
| base::TimeTicks before_ping_time = base::TimeTicks::Now(); |
| |
| // Negative value means a preface ping will always be sent. |
| set_connection_at_risk_of_loss_time(base::TimeDelta::FromSeconds(-1)); |
| |
| // Send a PING frame. This posts CheckPingStatus() with delay. |
| MaybeSendPrefacePing(); |
| |
| EXPECT_TRUE(ping_in_flight()); |
| EXPECT_EQ(2u, next_ping_id()); |
| EXPECT_TRUE(check_ping_status_pending()); |
| |
| // MaybeSendPrefacePing() should not send another PING frame if there is |
| // already one in flight. |
| MaybeSendPrefacePing(); |
| |
| EXPECT_TRUE(ping_in_flight()); |
| EXPECT_EQ(2u, next_ping_id()); |
| EXPECT_TRUE(check_ping_status_pending()); |
| |
| // Run posted CheckPingStatus() task. |
| FastForwardUntilNoTasksRemain(); |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_FALSE(ping_in_flight()); |
| EXPECT_EQ(2u, next_ping_id()); |
| EXPECT_FALSE(check_ping_status_pending()); |
| EXPECT_GE(last_read_time(), before_ping_time); |
| |
| data.Resume(); |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_THAT(delegate.WaitForClose(), IsError(ERR_CONNECTION_CLOSED)); |
| |
| EXPECT_FALSE(MainThreadHasPendingTask()); |
| EXPECT_FALSE(HasSpdySession(spdy_session_pool_, key_)); |
| EXPECT_FALSE(session_); |
| EXPECT_FALSE(spdy_stream1); |
| |
| EXPECT_TRUE(data.AllWriteDataConsumed()); |
| EXPECT_TRUE(data.AllReadDataConsumed()); |
| } |
| |
| TEST_F(SpdySessionTest, ServerPing) { |
| spdy::SpdySerializedFrame read_ping(spdy_util_.ConstructSpdyPing(2, false)); |
| MockRead reads[] = { |
| CreateMockRead(read_ping), MockRead(SYNCHRONOUS, 0, 0) // EOF |
| }; |
| spdy::SpdySerializedFrame write_ping(spdy_util_.ConstructSpdyPing(2, true)); |
| MockWrite writes[] = { |
| CreateMockWrite(write_ping), |
| }; |
| StaticSocketDataProvider data(reads, writes); |
| session_deps_.socket_factory->AddSocketDataProvider(&data); |
| |
| AddSSLSocketData(); |
| |
| CreateNetworkSession(); |
| CreateSpdySession(); |
| |
| base::WeakPtr<SpdyStream> spdy_stream1 = |
| CreateStreamSynchronously(SPDY_BIDIRECTIONAL_STREAM, session_, test_url_, |
| MEDIUM, NetLogWithSource()); |
| ASSERT_TRUE(spdy_stream1); |
| test::StreamDelegateSendImmediate delegate(spdy_stream1, nullptr); |
| spdy_stream1->SetDelegate(&delegate); |
| |
| // Flush the read completion task. |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_FALSE(HasSpdySession(spdy_session_pool_, key_)); |
| |
| EXPECT_FALSE(session_); |
| EXPECT_FALSE(spdy_stream1); |
| } |
| |
| // Cause a ping to be sent out while producing a write. The write loop |
| // should handle this properly, i.e. another DoWriteLoop task should |
| // not be posted. This is a regression test for |
| // http://crbug.com/261043 . |
| TEST_F(SpdySessionTest, PingAndWriteLoop) { |
| session_deps_.enable_ping = true; |
| session_deps_.time_func = TheNearFuture; |
| |
| spdy::SpdySerializedFrame write_ping(spdy_util_.ConstructSpdyPing(1, false)); |
| spdy::SpdySerializedFrame req( |
| spdy_util_.ConstructSpdyGet(nullptr, 0, 1, LOWEST)); |
| MockWrite writes[] = { |
| CreateMockWrite(req, 0), CreateMockWrite(write_ping, 1), |
| }; |
| |
| MockRead reads[] = { |
| MockRead(ASYNC, ERR_IO_PENDING, 2), MockRead(ASYNC, 0, 3) // EOF |
| }; |
| |
| SequencedSocketData data(reads, writes); |
| session_deps_.socket_factory->AddSocketDataProvider(&data); |
| |
| AddSSLSocketData(); |
| |
| CreateNetworkSession(); |
| CreateSpdySession(); |
| |
| base::WeakPtr<SpdyStream> spdy_stream = |
| CreateStreamSynchronously(SPDY_REQUEST_RESPONSE_STREAM, session_, |
| test_url_, LOWEST, NetLogWithSource()); |
| test::StreamDelegateDoNothing delegate(spdy_stream); |
| spdy_stream->SetDelegate(&delegate); |
| |
| spdy::SpdyHeaderBlock headers( |
| spdy_util_.ConstructGetHeaderBlock(kDefaultUrl)); |
| spdy_stream->SendRequestHeaders(std::move(headers), NO_MORE_DATA_TO_SEND); |
| |
| // Shift time so that a ping will be sent out. |
| g_time_delta = base::TimeDelta::FromSeconds(11); |
| |
| base::RunLoop().RunUntilIdle(); |
| session_->CloseSessionOnError(ERR_ABORTED, "Aborting"); |
| |
| data.Resume(); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_FALSE(session_); |
| } |
| |
| TEST_F(SpdySessionTest, StreamIdSpaceExhausted) { |
| const spdy::SpdyStreamId kLastStreamId = 0x7fffffff; |
| |
| // Test setup: |stream_hi_water_mark_| and |max_concurrent_streams_| are |
| // fixed to allow for two stream ID assignments, and three concurrent |
| // streams. Four streams are started, and two are activated. Verify the |
| // session goes away, and that the created (but not activated) and |
| // stalled streams are aborted. Also verify the activated streams complete, |
| // at which point the session closes. |
| |
| spdy::SpdySerializedFrame req1( |
| spdy_util_.ConstructSpdyGet(nullptr, 0, kLastStreamId - 2, MEDIUM)); |
| spdy::SpdySerializedFrame req2( |
| spdy_util_.ConstructSpdyGet(nullptr, 0, kLastStreamId, MEDIUM)); |
| |
| MockWrite writes[] = { |
| CreateMockWrite(req1, 0), CreateMockWrite(req2, 1), |
| }; |
| |
| spdy::SpdySerializedFrame resp1( |
| spdy_util_.ConstructSpdyGetReply(nullptr, 0, kLastStreamId - 2)); |
| spdy::SpdySerializedFrame resp2( |
| spdy_util_.ConstructSpdyGetReply(nullptr, 0, kLastStreamId)); |
| |
| spdy::SpdySerializedFrame body1( |
| spdy_util_.ConstructSpdyDataFrame(kLastStreamId - 2, true)); |
| spdy::SpdySerializedFrame body2( |
| spdy_util_.ConstructSpdyDataFrame(kLastStreamId, true)); |
| |
| MockRead reads[] = { |
| CreateMockRead(resp1, 2), CreateMockRead(resp2, 3), |
| MockRead(ASYNC, ERR_IO_PENDING, 4), CreateMockRead(body1, 5), |
| CreateMockRead(body2, 6), MockRead(ASYNC, 0, 7) // EOF |
| }; |
| |
| SequencedSocketData data(reads, writes); |
| session_deps_.socket_factory->AddSocketDataProvider(&data); |
| |
| AddSSLSocketData(); |
| |
| CreateNetworkSession(); |
| CreateSpdySession(); |
| |
| // Fix stream_hi_water_mark_ to allow for two stream activations. |
| set_stream_hi_water_mark(kLastStreamId - 2); |
| // Fix max_concurrent_streams to allow for three stream creations. |
| set_max_concurrent_streams(3); |
| |
| // Create three streams synchronously, and begin a fourth (which is stalled). |
| base::WeakPtr<SpdyStream> stream1 = |
| CreateStreamSynchronously(SPDY_REQUEST_RESPONSE_STREAM, session_, |
| test_url_, MEDIUM, NetLogWithSource()); |
| test::StreamDelegateDoNothing delegate1(stream1); |
| stream1->SetDelegate(&delegate1); |
| |
| base::WeakPtr<SpdyStream> stream2 = |
| CreateStreamSynchronously(SPDY_REQUEST_RESPONSE_STREAM, session_, |
| test_url_, MEDIUM, NetLogWithSource()); |
| test::StreamDelegateDoNothing delegate2(stream2); |
| stream2->SetDelegate(&delegate2); |
| |
| base::WeakPtr<SpdyStream> stream3 = |
| CreateStreamSynchronously(SPDY_REQUEST_RESPONSE_STREAM, session_, |
| test_url_, MEDIUM, NetLogWithSource()); |
| test::StreamDelegateDoNothing delegate3(stream3); |
| stream3->SetDelegate(&delegate3); |
| |
| SpdyStreamRequest request4; |
| TestCompletionCallback callback4; |
| EXPECT_EQ(ERR_IO_PENDING, |
| request4.StartRequest(SPDY_REQUEST_RESPONSE_STREAM, session_, |
| test_url_, MEDIUM, SocketTag(), |
| NetLogWithSource(), callback4.callback(), |
| TRAFFIC_ANNOTATION_FOR_TESTS)); |
| |
| // Streams 1-3 were created. 4th is stalled. No streams are active yet. |
| EXPECT_EQ(0u, num_active_streams()); |
| EXPECT_EQ(3u, num_created_streams()); |
| EXPECT_EQ(1u, pending_create_stream_queue_size(MEDIUM)); |
| |
| // Activate stream 1. One ID remains available. |
| stream1->SendRequestHeaders(spdy_util_.ConstructGetHeaderBlock(kDefaultUrl), |
| NO_MORE_DATA_TO_SEND); |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_EQ(kLastStreamId - 2u, stream1->stream_id()); |
| EXPECT_EQ(1u, num_active_streams()); |
| EXPECT_EQ(2u, num_created_streams()); |
| EXPECT_EQ(1u, pending_create_stream_queue_size(MEDIUM)); |
| |
| // Activate stream 2. ID space is exhausted. |
| stream2->SendRequestHeaders(spdy_util_.ConstructGetHeaderBlock(kDefaultUrl), |
| NO_MORE_DATA_TO_SEND); |
| base::RunLoop().RunUntilIdle(); |
| |
| // Active streams remain active. |
| EXPECT_EQ(kLastStreamId, stream2->stream_id()); |
| EXPECT_EQ(2u, num_active_streams()); |
| |
| // Session is going away. Created and stalled streams were aborted. |
| EXPECT_TRUE(session_->IsGoingAway()); |
| EXPECT_THAT(delegate3.WaitForClose(), IsError(ERR_ABORTED)); |
| EXPECT_THAT(callback4.WaitForResult(), IsError(ERR_ABORTED)); |
| EXPECT_EQ(0u, num_created_streams()); |
| EXPECT_EQ(0u, pending_create_stream_queue_size(MEDIUM)); |
| |
| // Read responses on remaining active streams. |
| data.Resume(); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_THAT(delegate1.WaitForClose(), IsOk()); |
| EXPECT_EQ(kUploadData, delegate1.TakeReceivedData()); |
| EXPECT_THAT(delegate2.WaitForClose(), IsOk()); |
| EXPECT_EQ(kUploadData, delegate2.TakeReceivedData()); |
| |
| // Session was destroyed. |
| EXPECT_FALSE(session_); |
| } |
| |
| // Regression test for https://crbug.com/481009. |
| TEST_F(SpdySessionTest, MaxConcurrentStreamsZero) { |
| |
| // Receive SETTINGS frame that sets max_concurrent_streams to zero. |
| spdy::SettingsMap settings_zero; |
| settings_zero[spdy::SETTINGS_MAX_CONCURRENT_STREAMS] = 0; |
| spdy::SpdySerializedFrame settings_frame_zero( |
| spdy_util_.ConstructSpdySettings(settings_zero)); |
| |
| // Acknowledge it. |
| spdy::SpdySerializedFrame settings_ack0( |
| spdy_util_.ConstructSpdySettingsAck()); |
| |
| // Receive SETTINGS frame that sets max_concurrent_streams to one. |
| spdy::SettingsMap settings_one; |
| settings_one[spdy::SETTINGS_MAX_CONCURRENT_STREAMS] = 1; |
| spdy::SpdySerializedFrame settings_frame_one( |
| spdy_util_.ConstructSpdySettings(settings_one)); |
| |
| // Acknowledge it. |
| spdy::SpdySerializedFrame settings_ack1( |
| spdy_util_.ConstructSpdySettingsAck()); |
| |
| // Request and response. |
| spdy::SpdySerializedFrame req( |
| spdy_util_.ConstructSpdyGet(nullptr, 0, 1, MEDIUM)); |
| |
| spdy::SpdySerializedFrame resp( |
| spdy_util_.ConstructSpdyGetReply(nullptr, 0, 1)); |
| |
| spdy::SpdySerializedFrame body(spdy_util_.ConstructSpdyDataFrame(1, true)); |
| |
| MockRead reads[] = {CreateMockRead(settings_frame_zero, 0), |
| MockRead(ASYNC, ERR_IO_PENDING, 2), |
| CreateMockRead(settings_frame_one, 3), |
| CreateMockRead(resp, 6), |
| CreateMockRead(body, 7), |
| MockRead(ASYNC, 0, 8)}; |
| |
| MockWrite writes[] = {CreateMockWrite(settings_ack0, 1), |
| CreateMockWrite(settings_ack1, 4), |
| CreateMockWrite(req, 5)}; |
| |
| SequencedSocketData data(reads, writes); |
| session_deps_.socket_factory->AddSocketDataProvider(&data); |
| |
| AddSSLSocketData(); |
| |
| // Create session. |
| CreateNetworkSession(); |
| CreateSpdySession(); |
| |
| // Receive SETTINGS frame that sets max_concurrent_streams to zero. |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_EQ(0u, max_concurrent_streams()); |
| |
| // Start request. |
| SpdyStreamRequest request; |
| TestCompletionCallback callback; |
| int rv = request.StartRequest( |
| SPDY_REQUEST_RESPONSE_STREAM, session_, test_url_, MEDIUM, SocketTag(), |
| NetLogWithSource(), callback.callback(), TRAFFIC_ANNOTATION_FOR_TESTS); |
| EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); |
| |
| // Stream is stalled. |
| EXPECT_EQ(1u, pending_create_stream_queue_size(MEDIUM)); |
| EXPECT_EQ(0u, num_created_streams()); |
| |
| // Receive SETTINGS frame that sets max_concurrent_streams to one. |
| data.Resume(); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_EQ(1u, max_concurrent_streams()); |
| |
| // Stream is created. |
| EXPECT_EQ(0u, pending_create_stream_queue_size(MEDIUM)); |
| EXPECT_EQ(1u, num_created_streams()); |
| |
| EXPECT_THAT(callback.WaitForResult(), IsOk()); |
| |
| // Send request. |
| base::WeakPtr<SpdyStream> stream = request.ReleaseStream(); |
| test::StreamDelegateDoNothing delegate(stream); |
| stream->SetDelegate(&delegate); |
| spdy::SpdyHeaderBlock headers( |
| spdy_util_.ConstructGetHeaderBlock(kDefaultUrl)); |
| stream->SendRequestHeaders(std::move(headers), NO_MORE_DATA_TO_SEND); |
| |
| EXPECT_THAT(delegate.WaitForClose(), IsOk()); |
| EXPECT_EQ("hello!", delegate.TakeReceivedData()); |
| |
| // Finish async network reads/writes. |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_TRUE(data.AllWriteDataConsumed()); |
| EXPECT_TRUE(data.AllReadDataConsumed()); |
| |
| // Session is destroyed. |
| EXPECT_FALSE(session_); |
| } |
| |
| // Verifies that an unstalled pending stream creation racing with a new stream |
| // creation doesn't violate the maximum stream concurrency. Regression test for |
| // crbug.com/373858. |
| TEST_F(SpdySessionTest, UnstallRacesWithStreamCreation) { |
| MockRead reads[] = { |
| MockRead(SYNCHRONOUS, ERR_IO_PENDING) // Stall forever. |
| }; |
| |
| StaticSocketDataProvider data(reads, base::span<MockWrite>()); |
| session_deps_.socket_factory->AddSocketDataProvider(&data); |
| |
| AddSSLSocketData(); |
| |
| CreateNetworkSession(); |
| CreateSpdySession(); |
| |
| // Fix max_concurrent_streams to allow for one open stream. |
| set_max_concurrent_streams(1); |
| |
| // Create two streams: one synchronously, and one which stalls. |
| base::WeakPtr<SpdyStream> stream1 = |
| CreateStreamSynchronously(SPDY_REQUEST_RESPONSE_STREAM, session_, |
| test_url_, MEDIUM, NetLogWithSource()); |
| |
| SpdyStreamRequest request2; |
| TestCompletionCallback callback2; |
| EXPECT_EQ(ERR_IO_PENDING, |
| request2.StartRequest(SPDY_REQUEST_RESPONSE_STREAM, session_, |
| test_url_, MEDIUM, SocketTag(), |
| NetLogWithSource(), callback2.callback(), |
| TRAFFIC_ANNOTATION_FOR_TESTS)); |
| |
| EXPECT_EQ(1u, num_created_streams()); |
| EXPECT_EQ(1u, pending_create_stream_queue_size(MEDIUM)); |
| |
| // Cancel the first stream. A callback to unstall the second stream was |
| // posted. Don't run it yet. |
| stream1->Cancel(ERR_ABORTED); |
| |
| EXPECT_EQ(0u, num_created_streams()); |
| EXPECT_EQ(0u, pending_create_stream_queue_size(MEDIUM)); |
| |
| // Create a third stream prior to the second stream's callback. |
| base::WeakPtr<SpdyStream> stream3 = |
| CreateStreamSynchronously(SPDY_REQUEST_RESPONSE_STREAM, session_, |
| test_url_, MEDIUM, NetLogWithSource()); |
| |
| EXPECT_EQ(1u, num_created_streams()); |
| EXPECT_EQ(0u, pending_create_stream_queue_size(MEDIUM)); |
| |
| // Now run the message loop. The unstalled stream will re-stall itself. |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_EQ(1u, num_created_streams()); |
| EXPECT_EQ(1u, pending_create_stream_queue_size(MEDIUM)); |
| |
| // Cancel the third stream and run the message loop. Verify that the second |
| // stream creation now completes. |
| stream3->Cancel(ERR_ABORTED); |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_EQ(1u, num_created_streams()); |
| EXPECT_EQ(0u, pending_create_stream_queue_size(MEDIUM)); |
| EXPECT_THAT(callback2.WaitForResult(), IsOk()); |
| } |
| |
| TEST_F(SpdySessionTest, CancelPushAfterSessionGoesAway) { |
| base::HistogramTester histogram_tester; |
| |
| spdy::SpdySerializedFrame req( |
| spdy_util_.ConstructSpdyGet(nullptr, 0, 1, MEDIUM)); |
| spdy::SpdySerializedFrame priority( |
| spdy_util_.ConstructSpdyPriority(2, 1, IDLE, true)); |
| MockWrite writes[] = {CreateMockWrite(req, 0), CreateMockWrite(priority, 2)}; |
| |
| spdy::SpdySerializedFrame push( |
| spdy_util_.ConstructSpdyPush(nullptr, 0, 2, 1, kPushedUrl)); |
| spdy::SpdySerializedFrame push_body( |
| spdy_util_.ConstructSpdyDataFrame(2, false)); |
| MockRead reads[] = {CreateMockRead(push, 1), CreateMockRead(push_body, 3), |
| MockRead(ASYNC, ERR_IO_PENDING, 4), |
| MockRead(ASYNC, 0, 5)}; |
| |
| SequencedSocketData data(reads, writes); |
| session_deps_.socket_factory->AddSocketDataProvider(&data); |
| |
| AddSSLSocketData(); |
| |
| CreateNetworkSession(); |
| CreateSpdySession(); |
| |
| base::WeakPtr<SpdyStream> spdy_stream = |
| CreateStreamSynchronously(SPDY_REQUEST_RESPONSE_STREAM, session_, |
| test_url_, MEDIUM, NetLogWithSource()); |
| test::StreamDelegateDoNothing delegate(spdy_stream); |
| spdy_stream->SetDelegate(&delegate); |
| |
| spdy::SpdyHeaderBlock headers( |
| spdy_util_.ConstructGetHeaderBlock(kDefaultUrl)); |
| spdy_stream->SendRequestHeaders(std::move(headers), NO_MORE_DATA_TO_SEND); |
| |
| base::RunLoop().RunUntilIdle(); |
| |
| // Verify that there is one unclaimed push stream. |
| const GURL pushed_url(kPushedUrl); |
| EXPECT_EQ(1u, num_unclaimed_pushed_streams()); |
| EXPECT_TRUE(has_unclaimed_pushed_stream_for_url(pushed_url)); |
| |
| // Unclaimed push body consumes bytes from the session window. |
| EXPECT_EQ(kDefaultInitialWindowSize - kUploadDataSize, |
| session_recv_window_size()); |
| EXPECT_EQ(0, session_unacked_recv_window_bytes()); |
| |
| // Read and process EOF. |
| data.Resume(); |
| base::RunLoop().RunUntilIdle(); |
| |
| // Cancel the push after session goes away. The test must not crash. |
| EXPECT_FALSE(session_); |
| EXPECT_TRUE(test_push_delegate_->CancelPush(pushed_url)); |
| |
| histogram_tester.ExpectBucketCount("Net.SpdyStreamsPushedPerSession", 1, 1); |
| histogram_tester.ExpectBucketCount("Net.SpdySession.PushedBytes", 6, 1); |
| histogram_tester.ExpectBucketCount("Net.SpdySession.PushedAndUnclaimedBytes", |
| 6, 1); |
| } |
| |
| TEST_F(SpdySessionTestWithMockTime, CancelPushAfterExpired) { |
| base::HistogramTester histogram_tester; |
| |
| spdy::SpdySerializedFrame req( |
| spdy_util_.ConstructSpdyGet(nullptr, 0, 1, MEDIUM)); |
| spdy::SpdySerializedFrame priority( |
| spdy_util_.ConstructSpdyPriority(2, 1, IDLE, true)); |
| spdy::SpdySerializedFrame rst( |
| spdy_util_.ConstructSpdyRstStream(2, spdy::ERROR_CODE_REFUSED_STREAM)); |
| MockWrite writes[] = { |
| CreateMockWrite(req, 0), CreateMockWrite(priority, 3), |
| CreateMockWrite(rst, 5), |
| }; |
| |
| spdy::SpdySerializedFrame push( |
| spdy_util_.ConstructSpdyPush(nullptr, 0, 2, 1, kPushedUrl)); |
| spdy::SpdySerializedFrame push_body( |
| spdy_util_.ConstructSpdyDataFrame(2, false)); |
| MockRead reads[] = {CreateMockRead(push, 1), CreateMockRead(push_body, 2), |
| MockRead(ASYNC, ERR_IO_PENDING, 4), |
| MockRead(ASYNC, 0, 6)}; |
| |
| SequencedSocketData data(reads, writes); |
| session_deps_.socket_factory->AddSocketDataProvider(&data); |
| |
| AddSSLSocketData(); |
| |
| CreateNetworkSession(); |
| CreateSpdySession(); |
| |
| base::WeakPtr<SpdyStream> spdy_stream = |
| CreateStreamSynchronously(SPDY_REQUEST_RESPONSE_STREAM, session_, |
| test_url_, MEDIUM, NetLogWithSource()); |
| test::StreamDelegateDoNothing delegate(spdy_stream); |
| spdy_stream->SetDelegate(&delegate); |
| |
| spdy::SpdyHeaderBlock headers( |
| spdy_util_.ConstructGetHeaderBlock(kDefaultUrl)); |
| spdy_stream->SendRequestHeaders(std::move(headers), NO_MORE_DATA_TO_SEND); |
| |
| RunUntilIdle(); |
| |
| // Verify that there is one unclaimed push stream. |
| const GURL pushed_url(kPushedUrl); |
| EXPECT_EQ(1u, num_unclaimed_pushed_streams()); |
| EXPECT_TRUE(has_unclaimed_pushed_stream_for_url(pushed_url)); |
| |
| // Unclaimed push body consumes bytes from the session window. |
| EXPECT_EQ(kDefaultInitialWindowSize - kUploadDataSize, |
| session_recv_window_size()); |
| EXPECT_EQ(0, session_unacked_recv_window_bytes()); |
| |
| // Fast forward to CancelPushedStreamIfUnclaimed() that was posted with a |
| // delay. |
| FastForwardUntilNoTasksRemain(); |
| RunUntilIdle(); |
| |
| // Verify that pushed stream is cancelled. |
| EXPECT_EQ(0u, num_unclaimed_pushed_streams()); |
| |
| // Verify that the session window reclaimed the evicted stream body. |
| EXPECT_EQ(kDefaultInitialWindowSize, session_recv_window_size()); |
| EXPECT_EQ(kUploadDataSize, session_unacked_recv_window_bytes()); |
| |
| // Try to cancel the expired push after its expiration: must not crash. |
| EXPECT_TRUE(session_); |
| EXPECT_TRUE(test_push_delegate_->CancelPush(pushed_url)); |
| EXPECT_EQ(0u, num_unclaimed_pushed_streams()); |
| |
| // Read and process EOF. |
| data.Resume(); |
| RunUntilIdle(); |
| EXPECT_FALSE(session_); |
| |
| histogram_tester.ExpectBucketCount("Net.SpdyStreamsPushedPerSession", 1, 1); |
| histogram_tester.ExpectBucketCount("Net.SpdySession.PushedBytes", 6, 1); |
| histogram_tester.ExpectBucketCount("Net.SpdySession.PushedAndUnclaimedBytes", |
| 6, 1); |
| |
| histogram_tester.ExpectBucketCount( |
| "Net.SpdyPushedStreamFate", |
| static_cast<int>(SpdyPushedStreamFate::kTimeout), 1); |
| histogram_tester.ExpectTotalCount("Net.SpdyPushedStreamFate", 1); |
| } |
| |
| TEST_F(SpdySessionTestWithMockTime, ClaimPushedStreamBeforeExpires) { |
| base::HistogramTester histogram_tester; |
| |
| spdy::SpdySerializedFrame req( |
| spdy_util_.ConstructSpdyGet(nullptr, 0, 1, MEDIUM)); |
| spdy::SpdySerializedFrame priority( |
| spdy_util_.ConstructSpdyPriority(2, 1, IDLE, true)); |
| MockWrite writes[] = {CreateMockWrite(req, 0), CreateMockWrite(priority, 3)}; |
| |
| spdy::SpdySerializedFrame push( |
| spdy_util_.ConstructSpdyPush(nullptr, 0, 2, 1, kPushedUrl)); |
| spdy::SpdySerializedFrame push_body( |
| spdy_util_.ConstructSpdyDataFrame(2, false)); |
| MockRead reads[] = {CreateMockRead(push, 1), CreateMockRead(push_body, 2), |
| MockRead(ASYNC, ERR_IO_PENDING, 4), |
| MockRead(ASYNC, 0, 5)}; |
| |
| SequencedSocketData data(reads, writes); |
| session_deps_.socket_factory->AddSocketDataProvider(&data); |
| |
| AddSSLSocketData(); |
| |
| CreateNetworkSession(); |
| CreateSpdySession(); |
| |
| base::WeakPtr<SpdyStream> spdy_stream1 = |
| CreateStreamSynchronously(SPDY_REQUEST_RESPONSE_STREAM, session_, |
| test_url_, MEDIUM, NetLogWithSource()); |
| test::StreamDelegateDoNothing delegate1(spdy_stream1); |
| spdy_stream1->SetDelegate(&delegate1); |
| |
| spdy::SpdyHeaderBlock headers1( |
| spdy_util_.ConstructGetHeaderBlock(kDefaultUrl)); |
| spdy_stream1->SendRequestHeaders(std::move(headers1), NO_MORE_DATA_TO_SEND); |
| |
| RunUntilIdle(); |
| |
| // Verify that there is one unclaimed push stream. |
| const GURL pushed_url(kPushedUrl); |
| EXPECT_EQ(1u, num_unclaimed_pushed_streams()); |
| EXPECT_TRUE(has_unclaimed_pushed_stream_for_url(pushed_url)); |
| |
| // Unclaimed push body consumes bytes from the session window. |
| EXPECT_EQ(kDefaultInitialWindowSize - kUploadDataSize, |
| session_recv_window_size()); |
| EXPECT_EQ(0, session_unacked_recv_window_bytes()); |
| |
| // Claim pushed stream from Http2PushPromiseIndex. |
| HttpRequestInfo push_request; |
| push_request.url = pushed_url; |
| push_request.method = "GET"; |
| base::WeakPtr<SpdySession> session_with_pushed_stream; |
| spdy::SpdyStreamId pushed_stream_id; |
| spdy_session_pool_->push_promise_index()->ClaimPushedStream( |
| key_, pushed_url, push_request, &session_with_pushed_stream, |
| &pushed_stream_id); |
| EXPECT_EQ(session_.get(), session_with_pushed_stream.get()); |
| EXPECT_EQ(2u, pushed_stream_id); |
| |
| // Verify that pushed stream is claimed. |
| EXPECT_EQ(0u, num_unclaimed_pushed_streams()); |
| |
| SpdyStream* spdy_stream2; |
| int rv = session_->GetPushedStream(pushed_url, pushed_stream_id, MEDIUM, |
| &spdy_stream2); |
| ASSERT_THAT(rv, IsOk()); |
| ASSERT_TRUE(spdy_stream2); |
| |
| test::StreamDelegateDoNothing delegate2(spdy_stream2->GetWeakPtr()); |
| spdy_stream2->SetDelegate(&delegate2); |
| |
| // Fast forward to CancelPushedStreamIfUnclaimed() that was posted with a |
| // delay. CancelPushedStreamIfUnclaimed() must be a no-op. |
| FastForwardUntilNoTasksRemain(); |
| RunUntilIdle(); |
| EXPECT_TRUE(session_); |
| |
| // Read and process EOF. |
| data.Resume(); |
| RunUntilIdle(); |
| EXPECT_FALSE(session_); |
| |
| histogram_tester.ExpectBucketCount("Net.SpdyStreamsPushedPerSession", 1, 1); |
| histogram_tester.ExpectBucketCount("Net.SpdySession.PushedBytes", 6, 1); |
| histogram_tester.ExpectBucketCount("Net.SpdySession.PushedAndUnclaimedBytes", |
| 0, 1); |
| } |
| |
| TEST_F(SpdySessionTest, CancelPushBeforeClaimed) { |
| base::HistogramTester histogram_tester; |
| |
| spdy::SpdySerializedFrame req( |
| spdy_util_.ConstructSpdyGet(nullptr, 0, 1, MEDIUM)); |
| spdy::SpdySerializedFrame priority( |
| spdy_util_.ConstructSpdyPriority(2, 1, IDLE, true)); |
| spdy::SpdySerializedFrame rst( |
| spdy_util_.ConstructSpdyRstStream(2, spdy::ERROR_CODE_CANCEL)); |
| MockWrite writes[] = {CreateMockWrite(req, 0), CreateMockWrite(priority, 3), |
| CreateMockWrite(rst, 5)}; |
| |
| spdy::SpdySerializedFrame push( |
| spdy_util_.ConstructSpdyPush(nullptr, 0, 2, 1, kPushedUrl)); |
| spdy::SpdySerializedFrame push_body( |
| spdy_util_.ConstructSpdyDataFrame(2, false)); |
| MockRead reads[] = {CreateMockRead(push, 1), CreateMockRead(push_body, 2), |
| MockRead(ASYNC, ERR_IO_PENDING, 4), |
| MockRead(ASYNC, 0, 6)}; |
| |
| SequencedSocketData data(reads, writes); |
| session_deps_.socket_factory->AddSocketDataProvider(&data); |
| |
| AddSSLSocketData(); |
| |
| CreateNetworkSession(); |
| CreateSpdySession(); |
| |
| base::WeakPtr<SpdyStream> spdy_stream = |
| CreateStreamSynchronously(SPDY_REQUEST_RESPONSE_STREAM, session_, |
| test_url_, MEDIUM, NetLogWithSource()); |
| test::StreamDelegateDoNothing delegate(spdy_stream); |
| spdy_stream->SetDelegate(&delegate); |
| |
| spdy::SpdyHeaderBlock headers( |
| spdy_util_.ConstructGetHeaderBlock(kDefaultUrl)); |
| spdy_stream->SendRequestHeaders(std::move(headers), NO_MORE_DATA_TO_SEND); |
| |
| base::RunLoop().RunUntilIdle(); |
| |
| // Verify that there is one unclaimed push stream. |
| const GURL pushed_url(kPushedUrl); |
| EXPECT_EQ(1u, num_unclaimed_pushed_streams()); |
| EXPECT_TRUE(has_unclaimed_pushed_stream_for_url(pushed_url)); |
| |
| // Unclaimed push body consumes bytes from the session window. |
| EXPECT_EQ(kDefaultInitialWindowSize - kUploadDataSize, |
| session_recv_window_size()); |
| EXPECT_EQ(0, session_unacked_recv_window_bytes()); |
| |
| // Cancel the push before it is claimed. This normally happens because |
| // resource is found in cache. |
| EXPECT_TRUE(test_push_delegate_->CancelPush(pushed_url)); |
| EXPECT_EQ(0u, num_unclaimed_pushed_streams()); |
| EXPECT_FALSE(has_unclaimed_pushed_stream_for_url(pushed_url)); |
| |
| // Verify that the session window reclaimed the evicted stream body. |
| EXPECT_EQ(kDefaultInitialWindowSize, session_recv_window_size()); |
| EXPECT_EQ(kUploadDataSize, session_unacked_recv_window_bytes()); |
| |
| EXPECT_TRUE(session_); |
| |
| // Read and process EOF. |
| data.Resume(); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_FALSE(session_); |
| |
| histogram_tester.ExpectBucketCount("Net.SpdyStreamsPushedPerSession", 1, 1); |
| histogram_tester.ExpectBucketCount("Net.SpdySession.PushedBytes", 6, 1); |
| histogram_tester.ExpectBucketCount("Net.SpdySession.PushedAndUnclaimedBytes", |
| 6, 1); |
| |
| histogram_tester.ExpectBucketCount( |
| "Net.SpdyPushedStreamFate", |
| static_cast<int>(SpdyPushedStreamFate::kAlreadyInCache), 1); |
| histogram_tester.ExpectTotalCount("Net.SpdyPushedStreamFate", 1); |
| } |
| |
| TEST_F(SpdySessionTestWithMockTime, FailedPing) { |
| session_deps_.enable_ping = true; |
| session_deps_.time_func = TheNearFuture; |
| |
| MockRead reads[] = {MockRead(SYNCHRONOUS, ERR_IO_PENDING)}; // Stall forever. |
| spdy::SpdySerializedFrame write_ping(spdy_util_.ConstructSpdyPing(1, false)); |
| spdy::SpdySerializedFrame goaway(spdy_util_.ConstructSpdyGoAway( |
| 0, spdy::ERROR_CODE_PROTOCOL_ERROR, "Failed ping.")); |
| MockWrite writes[] = {CreateMockWrite(write_ping), CreateMockWrite(goaway)}; |
| |
| StaticSocketDataProvider data(reads, writes); |
| session_deps_.socket_factory->AddSocketDataProvider(&data); |
| |
| AddSSLSocketData(); |
| |
| CreateNetworkSession(); |
| CreateSpdySession(); |
| |
| base::WeakPtr<SpdyStream> spdy_stream1 = |
| CreateStreamSynchronously(SPDY_BIDIRECTIONAL_STREAM, session_, test_url_, |
| MEDIUM, NetLogWithSource()); |
| ASSERT_TRUE(spdy_stream1); |
| test::StreamDelegateSendImmediate delegate(spdy_stream1, nullptr); |
| spdy_stream1->SetDelegate(&delegate); |
| |
| // Negative value means a preface ping will always be sent. |
| set_connection_at_risk_of_loss_time(base::TimeDelta::FromSeconds(-1)); |
| |
| // Send a PING frame. This posts CheckPingStatus() with delay. |
| MaybeSendPrefacePing(); |
| EXPECT_TRUE(ping_in_flight()); |
| EXPECT_EQ(2u, next_ping_id()); |
| EXPECT_TRUE(check_ping_status_pending()); |
| |
| // Assert session is not closed. |
| EXPECT_TRUE(session_->IsAvailable()); |
| EXPECT_LT(0u, num_active_streams() + num_created_streams()); |
| EXPECT_TRUE(HasSpdySession(spdy_session_pool_, key_)); |
| |
| // Run CheckPingStatus() and make it believe hung_interval has passed. |
| g_time_delta = base::TimeDelta::FromSeconds(15); |
| FastForwardUntilNoTasksRemain(); |
| base::RunLoop().RunUntilIdle(); |
| |
| // Since no response to PING has been received, |
| // CheckPingStatus() closes the connection. |
| EXPECT_FALSE(MainThreadHasPendingTask()); |
| EXPECT_FALSE(HasSpdySession(spdy_session_pool_, key_)); |
| EXPECT_FALSE(session_); |
| EXPECT_FALSE(spdy_stream1); |
| |
| EXPECT_TRUE(data.AllWriteDataConsumed()); |
| EXPECT_TRUE(data.AllReadDataConsumed()); |
| } |
| |
| // Regression test for https://crbug.com/784975. |
| TEST_F(SpdySessionTestWithMockTime, NoPingSentWhenCheckPingPending) { |
| session_deps_.enable_ping = true; |
| session_deps_.time_func = TheNearFuture; |
| |
| spdy::SpdySerializedFrame read_ping(spdy_util_.ConstructSpdyPing(1, true)); |
| MockRead reads[] = {CreateMockRead(read_ping, 1), |
| MockRead(ASYNC, ERR_IO_PENDING, 2), |
| MockRead(ASYNC, 0, 3)}; |
| |
| spdy::SpdySerializedFrame write_ping0(spdy_util_.ConstructSpdyPing(1, false)); |
| MockWrite writes[] = {CreateMockWrite(write_ping0, 0)}; |
| |
| SequencedSocketData data(reads, writes); |
| session_deps_.socket_factory->AddSocketDataProvider(&data); |
| AddSSLSocketData(); |
| |
| CreateNetworkSession(); |
| CreateSpdySession(); |
| |
| // Negative value means a preface ping will always be sent. |
| set_connection_at_risk_of_loss_time(base::TimeDelta::FromSeconds(-1)); |
| |
| base::WeakPtr<SpdyStream> spdy_stream1 = |
| CreateStreamSynchronously(SPDY_BIDIRECTIONAL_STREAM, session_, test_url_, |
| MEDIUM, NetLogWithSource()); |
| ASSERT_TRUE(spdy_stream1); |
| test::StreamDelegateSendImmediate delegate(spdy_stream1, nullptr); |
| spdy_stream1->SetDelegate(&delegate); |
| |
| EXPECT_FALSE(ping_in_flight()); |
| EXPECT_EQ(1u, next_ping_id()); |
| EXPECT_FALSE(check_ping_status_pending()); |
| |
| // Send preface ping and post CheckPingStatus() task with delay. |
| MaybeSendPrefacePing(); |
| |
| EXPECT_TRUE(ping_in_flight()); |
| EXPECT_EQ(2u, next_ping_id()); |
| EXPECT_TRUE(check_ping_status_pending()); |
| |
| // Read PING ACK. |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_FALSE(ping_in_flight()); |
| EXPECT_TRUE(check_ping_status_pending()); |
| |
| // Fast forward mock time so that normally another ping would be sent out. |
| // However, since CheckPingStatus() is still pending, no new ping is sent. |
| g_time_delta = base::TimeDelta::FromSeconds(15); |
| MaybeSendPrefacePing(); |
| |
| EXPECT_FALSE(ping_in_flight()); |
| EXPECT_EQ(2u, next_ping_id()); |
| EXPECT_TRUE(check_ping_status_pending()); |
| |
| // Run CheckPingStatus(). |
| FastForwardUntilNoTasksRemain(); |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_FALSE(check_ping_status_pending()); |
| |
| // Read EOF. |
| data.Resume(); |
| EXPECT_THAT(delegate.WaitForClose(), IsError(ERR_CONNECTION_CLOSED)); |
| |
| // Finish going away. |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_FALSE(HasSpdySession(spdy_session_pool_, key_)); |
| EXPECT_FALSE(session_); |
| |
| EXPECT_TRUE(data.AllWriteDataConsumed()); |
| EXPECT_TRUE(data.AllReadDataConsumed()); |
| } |
| |
| // Request kInitialMaxConcurrentStreams + 1 streams. Receive a |
| // settings frame increasing the max concurrent streams by 1. Make |
| // sure nothing blows up. This is a regression test for |
| // http://crbug.com/57331 . |
| TEST_F(SpdySessionTest, OnSettings) { |
| const spdy::SpdySettingsId kSpdySettingsId = |
| spdy::SETTINGS_MAX_CONCURRENT_STREAMS; |
| |
| spdy::SettingsMap new_settings; |
| const uint32_t max_concurrent_streams = kInitialMaxConcurrentStreams + 1; |
| new_settings[kSpdySettingsId] = max_concurrent_streams; |
| spdy::SpdySerializedFrame settings_frame( |
| spdy_util_.ConstructSpdySettings(new_settings)); |
| MockRead reads[] = { |
| CreateMockRead(settings_frame, 0), MockRead(ASYNC, ERR_IO_PENDING, 2), |
| MockRead(ASYNC, 0, 3), |
| }; |
| |
| spdy::SpdySerializedFrame settings_ack(spdy_util_.ConstructSpdySettingsAck()); |
| MockWrite writes[] = {CreateMockWrite(settings_ack, 1)}; |
| |
| SequencedSocketData data(reads, writes); |
| session_deps_.socket_factory->AddSocketDataProvider(&data); |
| |
| AddSSLSocketData(); |
| |
| CreateNetworkSession(); |
| CreateSpdySession(); |
| |
| // Create the maximum number of concurrent streams. |
| for (size_t i = 0; i < kInitialMaxConcurrentStreams; ++i) { |
| base::WeakPtr<SpdyStream> spdy_stream = |
| CreateStreamSynchronously(SPDY_BIDIRECTIONAL_STREAM, session_, |
| test_url_, MEDIUM, NetLogWithSource()); |
| ASSERT_TRUE(spdy_stream); |
| } |
| |
| StreamReleaserCallback stream_releaser; |
| SpdyStreamRequest request; |
| ASSERT_EQ(ERR_IO_PENDING, |
| request.StartRequest(SPDY_BIDIRECTIONAL_STREAM, session_, test_url_, |
| MEDIUM, SocketTag(), NetLogWithSource(), |
| stream_releaser.MakeCallback(&request), |
| TRAFFIC_ANNOTATION_FOR_TESTS)); |
| |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_THAT(stream_releaser.WaitForResult(), IsOk()); |
| |
| data.Resume(); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_FALSE(session_); |
| |
| EXPECT_TRUE(data.AllWriteDataConsumed()); |
| EXPECT_TRUE(data.AllReadDataConsumed()); |
| } |
| |
| // Create one more stream than maximum number of concurrent streams, |
| // so that one of them is pending. Cancel one stream, which should trigger the |
| // creation of the pending stream. Then cancel that one immediately as well, |
| // and make sure this does not lead to a crash. |
| // This is a regression test for https://crbug.com/63532. |
| TEST_F(SpdySessionTest, CancelPendingCreateStream) { |
| MockRead reads[] = { |
| MockRead(SYNCHRONOUS, ERR_IO_PENDING) // Stall forever. |
| }; |
| |
| StaticSocketDataProvider data(reads, base::span<MockWrite>()); |
| session_deps_.socket_factory->AddSocketDataProvider(&data); |
| |
| AddSSLSocketData(); |
| |
| CreateNetworkSession(); |
| CreateSpdySession(); |
| |
| // Leave room for only one more stream to be created. |
| for (size_t i = 0; i < kInitialMaxConcurrentStreams - 1; ++i) { |
| base::WeakPtr<SpdyStream> spdy_stream = |
| CreateStreamSynchronously(SPDY_BIDIRECTIONAL_STREAM, session_, |
| test_url_, MEDIUM, NetLogWithSource()); |
| ASSERT_TRUE(spdy_stream); |
| } |
| |
| // Create 2 more streams. First will succeed. Second will be pending. |
| base::WeakPtr<SpdyStream> spdy_stream1 = |
| CreateStreamSynchronously(SPDY_BIDIRECTIONAL_STREAM, session_, test_url_, |
| MEDIUM, NetLogWithSource()); |
| ASSERT_TRUE(spdy_stream1); |
| |
| // Use unique_ptr to let us invalidate the memory when we want to, to trigger |
| // an error in memory corruption detectors if the callback is invoked when |
| // it's not supposed to be. |
| auto callback = std::make_unique<TestCompletionCallback>(); |
| |
| SpdyStreamRequest request; |
| ASSERT_THAT( |
| request.StartRequest(SPDY_BIDIRECTIONAL_STREAM, session_, test_url_, |
| MEDIUM, SocketTag(), NetLogWithSource(), |
| callback->callback(), TRAFFIC_ANNOTATION_FOR_TESTS), |
| IsError(ERR_IO_PENDING)); |
| |
| // Release the first one, this will allow the second to be created. |
| spdy_stream1->Cancel(ERR_ABORTED); |
| EXPECT_FALSE(spdy_stream1); |
| |
| request.CancelRequest(); |
| callback.reset(); |
| |
| // Should not crash when running the pending callback. |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| TEST_F(SpdySessionTest, Initialize) { |
| MockRead reads[] = { |
| MockRead(ASYNC, 0, 0) // EOF |
| }; |
| |
| StaticSocketDataProvider data(reads, base::span<MockWrite>()); |
| session_deps_.socket_factory->AddSocketDataProvider(&data); |
| |
| AddSSLSocketData(); |
| |
| CreateNetworkSession(); |
| CreateSpdySession(); |
| EXPECT_TRUE(HasSpdySession(spdy_session_pool_, key_)); |
| |
| // Flush the read completion task. |
| base::RunLoop().RunUntilIdle(); |
| |
| TestNetLogEntry::List entries; |
| log_.GetEntries(&entries); |
| EXPECT_LT(0u, entries.size()); |
| |
| // Check that we logged HTTP2_SESSION_INITIALIZED correctly. |
| int pos = ExpectLogContainsSomewhere( |
| entries, 0, NetLogEventType::HTTP2_SESSION_INITIALIZED, |
| NetLogEventPhase::NONE); |
| EXPECT_LT(0, pos); |
| |
| TestNetLogEntry entry = entries[pos]; |
| NetLogSource socket_source; |
| EXPECT_TRUE( |
| NetLogSource::FromEventParameters(entry.params.get(), &socket_source)); |
| EXPECT_TRUE(socket_source.IsValid()); |
| EXPECT_NE(log_.bound().source().id, socket_source.id); |
| } |
| |
| TEST_F(SpdySessionTest, NetLogOnSessionGoaway) { |
| spdy::SpdySerializedFrame goaway(spdy_util_.ConstructSpdyGoAway( |
| 42, spdy::ERROR_CODE_ENHANCE_YOUR_CALM, "foo")); |
| MockRead reads[] = { |
| CreateMockRead(goaway), MockRead(SYNCHRONOUS, 0, 0) // EOF |
| }; |
| |
| StaticSocketDataProvider data(reads, base::span<MockWrite>()); |
| session_deps_.socket_factory->AddSocketDataProvider(&data); |
| |
| AddSSLSocketData(); |
| |
| CreateNetworkSession(); |
| CreateSpdySession(); |
| EXPECT_TRUE(HasSpdySession(spdy_session_pool_, key_)); |
| |
| // Flush the read completion task. |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_FALSE(HasSpdySession(spdy_session_pool_, key_)); |
| EXPECT_FALSE(session_); |
| |
| // Check that the NetLog was filled reasonably. |
| TestNetLogEntry::List entries; |
| log_.GetEntries(&entries); |
| EXPECT_LT(0u, entries.size()); |
| |
| int pos = ExpectLogContainsSomewhere( |
| entries, 0, NetLogEventType::HTTP2_SESSION_RECV_GOAWAY, |
| NetLogEventPhase::NONE); |
| TestNetLogEntry entry = entries[pos]; |
| int last_accepted_stream_id; |
| ASSERT_TRUE(entry.GetIntegerValue("last_accepted_stream_id", |
| &last_accepted_stream_id)); |
| EXPECT_EQ(42, last_accepted_stream_id); |
| int active_streams; |
| ASSERT_TRUE(entry.GetIntegerValue("active_streams", &active_streams)); |
| EXPECT_EQ(0, active_streams); |
| int unclaimed_streams; |
| ASSERT_TRUE(entry.GetIntegerValue("unclaimed_streams", &unclaimed_streams)); |
| EXPECT_EQ(0, unclaimed_streams); |
| std::string error_code; |
| ASSERT_TRUE(entry.GetStringValue("error_code", &error_code)); |
| EXPECT_EQ("11 (ENHANCE_YOUR_CALM)", error_code); |
| std::string debug_data; |
| ASSERT_TRUE(entry.GetStringValue("debug_data", &debug_data)); |
| EXPECT_EQ("foo", debug_data); |
| |
| // Check that we logged SPDY_SESSION_CLOSE correctly. |
| pos = ExpectLogContainsSomewhere( |
| entries, 0, NetLogEventType::HTTP2_SESSION_CLOSE, NetLogEventPhase::NONE); |
| entry = entries[pos]; |
| int net_error_code = 0; |
| ASSERT_TRUE(entry.GetNetErrorCode(&net_error_code)); |
| EXPECT_THAT(net_error_code, IsOk()); |
| } |
| |
| TEST_F(SpdySessionTest, NetLogOnSessionEOF) { |
| MockRead reads[] = { |
| MockRead(SYNCHRONOUS, 0, 0) // EOF |
| }; |
| |
| StaticSocketDataProvider data(reads, base::span<MockWrite>()); |
| session_deps_.socket_factory->AddSocketDataProvider(&data); |
| |
| AddSSLSocketData(); |
| |
| CreateNetworkSession(); |
| CreateSpdySession(); |
| EXPECT_TRUE(HasSpdySession(spdy_session_pool_, key_)); |
| |
| // Flush the read completion task. |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_FALSE(HasSpdySession(spdy_session_pool_, key_)); |
| EXPECT_FALSE(session_); |
| |
| // Check that the NetLog was filled reasonably. |
| TestNetLogEntry::List entries; |
| log_.GetEntries(&entries); |
| EXPECT_LT(0u, entries.size()); |
| |
| // Check that we logged SPDY_SESSION_CLOSE correctly. |
| int pos = ExpectLogContainsSomewhere( |
| entries, 0, NetLogEventType::HTTP2_SESSION_CLOSE, NetLogEventPhase::NONE); |
| |
| if (pos < static_cast<int>(entries.size())) { |
| TestNetLogEntry entry = entries[pos]; |
| int error_code = 0; |
| ASSERT_TRUE(entry.GetNetErrorCode(&error_code)); |
| EXPECT_THAT(error_code, IsError(ERR_CONNECTION_CLOSED)); |
| } else { |
| ADD_FAILURE(); |
| } |
| } |
| |
| TEST_F(SpdySessionTest, HeadersCompressionHistograms) { |
| spdy::SpdySerializedFrame req( |
| spdy_util_.ConstructSpdyGet(nullptr, 0, 1, MEDIUM)); |
| MockWrite writes[] = { |
| CreateMockWrite(req, 0), |
| }; |
| MockRead reads[] = { |
| MockRead(ASYNC, ERR_IO_PENDING, 1), MockRead(ASYNC, 0, 2) // EOF |
| }; |
| SequencedSocketData data(reads, writes); |
| session_deps_.socket_factory->AddSocketDataProvider(&data); |
| |
| AddSSLSocketData(); |
| |
| CreateNetworkSession(); |
| CreateSpdySession(); |
| |
| base::WeakPtr<SpdyStream> spdy_stream = |
| CreateStreamSynchronously(SPDY_REQUEST_RESPONSE_STREAM, session_, |
| test_url_, MEDIUM, NetLogWithSource()); |
| test::StreamDelegateDoNothing delegate(spdy_stream); |
| spdy_stream->SetDelegate(&delegate); |
| |
| spdy::SpdyHeaderBlock headers( |
| spdy_util_.ConstructGetHeaderBlock(kDefaultUrl)); |
| spdy_stream->SendRequestHeaders(std::move(headers), NO_MORE_DATA_TO_SEND); |
| |
| // Write request headers & capture resulting histogram update. |
| base::HistogramTester histogram_tester; |
| |
| base::RunLoop().RunUntilIdle(); |
| // Regression test of compression performance under the request fixture. |
| histogram_tester.ExpectBucketCount("Net.SpdyHeadersCompressionPercentage", 76, |
| 1); |
| |
| // Read and process EOF. |
| EXPECT_TRUE(session_); |
| data.Resume(); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_FALSE(session_); |
| } |
| |
| // Queue up a low-priority HEADERS followed by a high-priority |
| // one. The high priority one should still send first and receive |
| // first. |
| TEST_F(SpdySessionTest, OutOfOrderHeaders) { |
| // Construct the request. |
| spdy::SpdySerializedFrame req_highest( |
| spdy_util_.ConstructSpdyGet(nullptr, 0, 1, HIGHEST)); |
| spdy::SpdySerializedFrame req_lowest( |
| spdy_util_.ConstructSpdyGet(nullptr, 0, 3, LOWEST)); |
| MockWrite writes[] = { |
| CreateMockWrite(req_highest, 0), CreateMockWrite(req_lowest, 1), |
| }; |
| |
| spdy::SpdySerializedFrame resp_highest( |
| spdy_util_.ConstructSpdyGetReply(nullptr, 0, 1)); |
| spdy::SpdySerializedFrame body_highest( |
| spdy_util_.ConstructSpdyDataFrame(1, true)); |
| spdy::SpdySerializedFrame resp_lowest( |
| spdy_util_.ConstructSpdyGetReply(nullptr, 0, 3)); |
| spdy::SpdySerializedFrame body_lowest( |
| spdy_util_.ConstructSpdyDataFrame(3, true)); |
| MockRead reads[] = { |
| CreateMockRead(resp_highest, 2), CreateMockRead(body_highest, 3), |
| CreateMockRead(resp_lowest, 4), CreateMockRead(body_lowest, 5), |
| MockRead(ASYNC, 0, 6) // EOF |
| }; |
| |
| SequencedSocketData data(reads, writes); |
| session_deps_.socket_factory->AddSocketDataProvider(&data); |
| |
| AddSSLSocketData(); |
| |
| CreateNetworkSession(); |
| CreateSpdySession(); |
| |
| base::WeakPtr<SpdyStream> spdy_stream_lowest = |
| CreateStreamSynchronously(SPDY_REQUEST_RESPONSE_STREAM, session_, |
| test_url_, LOWEST, NetLogWithSource()); |
| ASSERT_TRUE(spdy_stream_lowest); |
| EXPECT_EQ(0u, spdy_stream_lowest->stream_id()); |
| test::StreamDelegateDoNothing delegate_lowest(spdy_stream_lowest); |
| spdy_stream_lowest->SetDelegate(&delegate_lowest); |
| |
| base::WeakPtr<SpdyStream> spdy_stream_highest = |
| CreateStreamSynchronously(SPDY_REQUEST_RESPONSE_STREAM, session_, |
| test_url_, HIGHEST, NetLogWithSource()); |
| ASSERT_TRUE(spdy_stream_highest); |
| EXPECT_EQ(0u, spdy_stream_highest->stream_id()); |
| test::StreamDelegateDoNothing delegate_highest(spdy_stream_highest); |
| spdy_stream_highest->SetDelegate(&delegate_highest); |
| |
| // Queue the lower priority one first. |
| |
| spdy::SpdyHeaderBlock headers_lowest( |
| spdy_util_.ConstructGetHeaderBlock(kDefaultUrl)); |
| spdy_stream_lowest->SendRequestHeaders(std::move(headers_lowest), |
| NO_MORE_DATA_TO_SEND); |
| |
| spdy::SpdyHeaderBlock headers_highest( |
| spdy_util_.ConstructGetHeaderBlock(kDefaultUrl)); |
| spdy_stream_highest->SendRequestHeaders(std::move(headers_highest), |
| NO_MORE_DATA_TO_SEND); |
| |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_FALSE(spdy_stream_lowest); |
| EXPECT_FALSE(spdy_stream_highest); |
| EXPECT_EQ(3u, delegate_lowest.stream_id()); |
| EXPECT_EQ(1u, delegate_highest.stream_id()); |
| } |
| |
| TEST_F(SpdySessionTest, CancelStream) { |
| // Request 1, at HIGHEST priority, will be cancelled before it writes data. |
| // Request 2, at LOWEST priority, will be a full request and will be id 1. |
| spdy::SpdySerializedFrame req2( |
| spdy_util_.ConstructSpdyGet(nullptr, 0, 1, LOWEST)); |
| MockWrite writes[] = { |
| CreateMockWrite(req2, 0), |
| }; |
| |
| spdy::SpdySerializedFrame resp2( |
| spdy_util_.ConstructSpdyGetReply(nullptr, 0, 1)); |
| spdy::SpdySerializedFrame body2(spdy_util_.ConstructSpdyDataFrame(1, true)); |
| MockRead reads[] = { |
| CreateMockRead(resp2, 1), MockRead(ASYNC, ERR_IO_PENDING, 2), |
| CreateMockRead(body2, 3), MockRead(ASYNC, 0, 4) // EOF |
| }; |
| |
| SequencedSocketData data(reads, writes); |
| session_deps_.socket_factory->AddSocketDataProvider(&data); |
| |
| AddSSLSocketData(); |
| |
| CreateNetworkSession(); |
| CreateSpdySession(); |
| |
| base::WeakPtr<SpdyStream> spdy_stream1 = |
| CreateStreamSynchronously(SPDY_REQUEST_RESPONSE_STREAM, session_, |
| test_url_, HIGHEST, NetLogWithSource()); |
| ASSERT_TRUE(spdy_stream1); |
| EXPECT_EQ(0u, spdy_stream1->stream_id()); |
| test::StreamDelegateDoNothing delegate1(spdy_stream1); |
| spdy_stream1->SetDelegate(&delegate1); |
| |
| base::WeakPtr<SpdyStream> spdy_stream2 = |
| CreateStreamSynchronously(SPDY_REQUEST_RESPONSE_STREAM, session_, |
| test_url_, LOWEST, NetLogWithSource()); |
| ASSERT_TRUE(spdy_stream2); |
| EXPECT_EQ(0u, spdy_stream2->stream_id()); |
| test::StreamDelegateDoNothing delegate2(spdy_stream2); |
| spdy_stream2->SetDelegate(&delegate2); |
| |
| spdy::SpdyHeaderBlock headers( |
| spdy_util_.ConstructGetHeaderBlock(kDefaultUrl)); |
| spdy_stream1->SendRequestHeaders(std::move(headers), NO_MORE_DATA_TO_SEND); |
| |
| spdy::SpdyHeaderBlock headers2( |
| spdy_util_.ConstructGetHeaderBlock(kDefaultUrl)); |
| spdy_stream2->SendRequestHeaders(std::move(headers2), NO_MORE_DATA_TO_SEND); |
| |
| EXPECT_EQ(0u, spdy_stream1->stream_id()); |
| |
| spdy_stream1->Cancel(ERR_ABORTED); |
| EXPECT_FALSE(spdy_stream1); |
| |
| EXPECT_EQ(0u, delegate1.stream_id()); |
| |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_EQ(0u, delegate1.stream_id()); |
| EXPECT_EQ(1u, delegate2.stream_id()); |
| |
| spdy_stream2->Cancel(ERR_ABORTED); |
| EXPECT_FALSE(spdy_stream2); |
| } |
| |
| // Create two streams that are set to re-close themselves on close, |
| // and then close the session. Nothing should blow up. Also a |
| // regression test for http://crbug.com/139518 . |
| TEST_F(SpdySessionTest, CloseSessionWithTwoCreatedSelfClosingStreams) { |
| // No actual data will be sent. |
| MockWrite writes[] = { |
| MockWrite(ASYNC, 0, 1) // EOF |
| }; |
| |
| MockRead reads[] = { |
| MockRead(ASYNC, 0, 0) // EOF |
| }; |
| SequencedSocketData data(reads, writes); |
| session_deps_.socket_factory->AddSocketDataProvider(&data); |
| |
| AddSSLSocketData(); |
| |
| CreateNetworkSession(); |
| CreateSpdySession(); |
| |
| base::WeakPtr<SpdyStream> spdy_stream1 = |
| CreateStreamSynchronously(SPDY_BIDIRECTIONAL_STREAM, session_, test_url_, |
| HIGHEST, NetLogWithSource()); |
| ASSERT_TRUE(spdy_stream1); |
| EXPECT_EQ(0u, spdy_stream1->stream_id()); |
| |
| base::WeakPtr<SpdyStream> spdy_stream2 = |
| CreateStreamSynchronously(SPDY_BIDIRECTIONAL_STREAM, session_, test_url_, |
| LOWEST, NetLogWithSource()); |
| ASSERT_TRUE(spdy_stream2); |
| EXPECT_EQ(0u, spdy_stream2->stream_id()); |
| |
| test::ClosingDelegate delegate1(spdy_stream1); |
| spdy_stream1->SetDelegate(&delegate1); |
| |
| test::ClosingDelegate delegate2(spdy_stream2); |
| spdy_stream2->SetDelegate(&delegate2); |
| |
| spdy::SpdyHeaderBlock headers( |
| spdy_util_.ConstructGetHeaderBlock(kDefaultUrl)); |
| spdy_stream1->SendRequestHeaders(std::move(headers), NO_MORE_DATA_TO_SEND); |
| |
| spdy::SpdyHeaderBlock headers2( |
| spdy_util_.ConstructGetHeaderBlock(kDefaultUrl)); |
| spdy_stream2->SendRequestHeaders(std::move(headers2), NO_MORE_DATA_TO_SEND); |
| |
| // Ensure that the streams have not yet been activated and assigned an id. |
| EXPECT_EQ(0u, spdy_stream1->stream_id()); |
| EXPECT_EQ(0u, spdy_stream2->stream_id()); |
| |
| // Ensure we don't crash while closing the session. |
| session_->CloseSessionOnError(ERR_ABORTED, std::string()); |
| |
| EXPECT_FALSE(spdy_stream1); |
| EXPECT_FALSE(spdy_stream2); |
| |
| EXPECT_TRUE(delegate1.StreamIsClosed()); |
| EXPECT_TRUE(delegate2.StreamIsClosed()); |
| |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_FALSE(session_); |
| } |
| |
| // Create two streams that are set to close each other on close, and |
| // then close the session. Nothing should blow up. |
| TEST_F(SpdySessionTest, CloseSessionWithTwoCreatedMutuallyClosingStreams) { |
| SequencedSocketData data; |
| session_deps_.socket_factory->AddSocketDataProvider(&data); |
| |
| AddSSLSocketData(); |
| |
| CreateNetworkSession(); |
| CreateSpdySession(); |
| |
| base::WeakPtr<SpdyStream> spdy_stream1 = |
| CreateStreamSynchronously(SPDY_BIDIRECTIONAL_STREAM, session_, test_url_, |
| HIGHEST, NetLogWithSource()); |
| ASSERT_TRUE(spdy_stream1); |
| EXPECT_EQ(0u, spdy_stream1->stream_id()); |
| |
| base::WeakPtr<SpdyStream> spdy_stream2 = |
| CreateStreamSynchronously(SPDY_BIDIRECTIONAL_STREAM, session_, test_url_, |
| LOWEST, NetLogWithSource()); |
| ASSERT_TRUE(spdy_stream2); |
| EXPECT_EQ(0u, spdy_stream2->stream_id()); |
| |
| // Make |spdy_stream1| close |spdy_stream2|. |
| test::ClosingDelegate delegate1(spdy_stream2); |
| spdy_stream1->SetDelegate(&delegate1); |
| |
| // Make |spdy_stream2| close |spdy_stream1|. |
| test::ClosingDelegate delegate2(spdy_stream1); |
| spdy_stream2->SetDelegate(&delegate2); |
| |
| spdy::SpdyHeaderBlock headers( |
| spdy_util_.ConstructGetHeaderBlock(kDefaultUrl)); |
| spdy_stream1->SendRequestHeaders(std::move(headers), NO_MORE_DATA_TO_SEND); |
| |
| spdy::SpdyHeaderBlock headers2( |
| spdy_util_.ConstructGetHeaderBlock(kDefaultUrl)); |
| spdy_stream2->SendRequestHeaders(std::move(headers2), NO_MORE_DATA_TO_SEND); |
| |
| // Ensure that the streams have not yet been activated and assigned an id. |
| EXPECT_EQ(0u, spdy_stream1->stream_id()); |
| EXPECT_EQ(0u, spdy_stream2->stream_id()); |
| |
| // Ensure we don't crash while closing the session. |
| session_->CloseSessionOnError(ERR_ABORTED, std::string()); |
| |
| EXPECT_FALSE(spdy_stream1); |
| EXPECT_FALSE(spdy_stream2); |
| |
| EXPECT_TRUE(delegate1.StreamIsClosed()); |
| EXPECT_TRUE(delegate2.StreamIsClosed()); |
| |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_FALSE(session_); |
| } |
| |
| // Create two streams that are set to re-close themselves on close, |
| // activate them, and then close the session. Nothing should blow up. |
| TEST_F(SpdySessionTest, CloseSessionWithTwoActivatedSelfClosingStreams) { |
| spdy::SpdySerializedFrame req1( |
| spdy_util_.ConstructSpdyGet(nullptr, 0, 1, MEDIUM)); |
| spdy::SpdySerializedFrame req2( |
| spdy_util_.ConstructSpdyGet(nullptr, 0, 3, MEDIUM)); |
| MockWrite writes[] = { |
| CreateMockWrite(req1, 0), CreateMockWrite(req2, 1), |
| }; |
| |
| MockRead reads[] = { |
| MockRead(ASYNC, ERR_IO_PENDING, 2), MockRead(ASYNC, 0, 3) // EOF |
| }; |
| |
| SequencedSocketData data(reads, writes); |
| session_deps_.socket_factory->AddSocketDataProvider(&data); |
| |
| AddSSLSocketData(); |
| |
| CreateNetworkSession(); |
| CreateSpdySession(); |
| |
| base::WeakPtr<SpdyStream> spdy_stream1 = |
| CreateStreamSynchronously(SPDY_REQUEST_RESPONSE_STREAM, session_, |
| test_url_, MEDIUM, NetLogWithSource()); |
| ASSERT_TRUE(spdy_stream1); |
| EXPECT_EQ(0u, spdy_stream1->stream_id()); |
| |
| base::WeakPtr<SpdyStream> spdy_stream2 = |
| CreateStreamSynchronously(SPDY_REQUEST_RESPONSE_STREAM, session_, |
| test_url_, MEDIUM, NetLogWithSource()); |
| ASSERT_TRUE(spdy_stream2); |
| EXPECT_EQ(0u, spdy_stream2->stream_id()); |
| |
| test::ClosingDelegate delegate1(spdy_stream1); |
| spdy_stream1->SetDelegate(&delegate1); |
| |
| test::ClosingDelegate delegate2(spdy_stream2); |
| spdy_stream2->SetDelegate(&delegate2); |
| |
| spdy::SpdyHeaderBlock headers( |
| spdy_util_.ConstructGetHeaderBlock(kDefaultUrl)); |
| spdy_stream1->SendRequestHeaders(std::move(headers), NO_MORE_DATA_TO_SEND); |
| |
| spdy::SpdyHeaderBlock headers2( |
| spdy_util_.ConstructGetHeaderBlock(kDefaultUrl)); |
| spdy_stream2->SendRequestHeaders(std::move(headers2), NO_MORE_DATA_TO_SEND); |
| |
| // Ensure that the streams have not yet been activated and assigned an id. |
| EXPECT_EQ(0u, spdy_stream1->stream_id()); |
| EXPECT_EQ(0u, spdy_stream2->stream_id()); |
| |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_EQ(1u, spdy_stream1->stream_id()); |
| EXPECT_EQ(3u, spdy_stream2->stream_id()); |
| |
| // Ensure we don't crash while closing the session. |
| session_->CloseSessionOnError(ERR_ABORTED, std::string()); |
| |
| EXPECT_FALSE(spdy_stream1); |
| EXPECT_FALSE(spdy_stream2); |
| |
| EXPECT_TRUE(delegate1.StreamIsClosed()); |
| EXPECT_TRUE(delegate2.StreamIsClosed()); |
| |
| EXPECT_TRUE(session_); |
| data.Resume(); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_FALSE(session_); |
| } |
| |
| // Create two streams that are set to close each other on close, |
| // activate them, and then close the session. Nothing should blow up. |
| TEST_F(SpdySessionTest, CloseSessionWithTwoActivatedMutuallyClosingStreams) { |
| spdy::SpdySerializedFrame req1( |
| spdy_util_.ConstructSpdyGet(nullptr, 0, 1, MEDIUM)); |
| spdy::SpdySerializedFrame req2( |
| spdy_util_.ConstructSpdyGet(nullptr, 0, 3, MEDIUM)); |
| MockWrite writes[] = { |
| CreateMockWrite(req1, 0), CreateMockWrite(req2, 1), |
| }; |
| |
| MockRead reads[] = { |
| MockRead(ASYNC, ERR_IO_PENDING, 2), MockRead(ASYNC, 0, 3) // EOF |
| }; |
| |
| SequencedSocketData data(reads, writes); |
| session_deps_.socket_factory->AddSocketDataProvider(&data); |
| |
| AddSSLSocketData(); |
| |
| CreateNetworkSession(); |
| CreateSpdySession(); |
| |
| base::WeakPtr<SpdyStream> spdy_stream1 = |
| CreateStreamSynchronously(SPDY_REQUEST_RESPONSE_STREAM, session_, |
| test_url_, MEDIUM, NetLogWithSource()); |
| ASSERT_TRUE(spdy_stream1); |
| EXPECT_EQ(0u, spdy_stream1->stream_id()); |
| |
| base::WeakPtr<SpdyStream> spdy_stream2 = |
| CreateStreamSynchronously(SPDY_REQUEST_RESPONSE_STREAM, session_, |
| test_url_, MEDIUM, NetLogWithSource()); |
| ASSERT_TRUE(spdy_stream2); |
| EXPECT_EQ(0u, spdy_stream2->stream_id()); |
| |
| // Make |spdy_stream1| close |spdy_stream2|. |
| test::ClosingDelegate delegate1(spdy_stream2); |
| spdy_stream1->SetDelegate(&delegate1); |
| |
| // Make |spdy_stream2| close |spdy_stream1|. |
| test::ClosingDelegate delegate2(spdy_stream1); |
| spdy_stream2->SetDelegate(&delegate2); |
| |
| spdy::SpdyHeaderBlock headers( |
| spdy_util_.ConstructGetHeaderBlock(kDefaultUrl)); |
| spdy_stream1->SendRequestHeaders(std::move(headers), NO_MORE_DATA_TO_SEND); |
| |
| spdy::SpdyHeaderBlock headers2( |
| spdy_util_.ConstructGetHeaderBlock(kDefaultUrl)); |
| spdy_stream2->SendRequestHeaders(std::move(headers2), NO_MORE_DATA_TO_SEND); |
| |
| // Ensure that the streams have not yet been activated and assigned an id. |
| EXPECT_EQ(0u, spdy_stream1->stream_id()); |
| EXPECT_EQ(0u, spdy_stream2->stream_id()); |
| |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_EQ(1u, spdy_stream1->stream_id()); |
| EXPECT_EQ(3u, spdy_stream2->stream_id()); |
| |
| // Ensure we don't crash while closing the session. |
| session_->CloseSessionOnError(ERR_ABORTED, std::string()); |
| |
| EXPECT_FALSE(spdy_stream1); |
| EXPECT_FALSE(spdy_stream2); |
| |
| EXPECT_TRUE(delegate1.StreamIsClosed()); |
| EXPECT_TRUE(delegate2.StreamIsClosed()); |
| |
| EXPECT_TRUE(session_); |
| data.Resume(); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_FALSE(session_); |
| } |
| |
| // Delegate that closes a given session when the stream is closed. |
| class SessionClosingDelegate : public test::StreamDelegateDoNothing { |
| public: |
| SessionClosingDelegate(const base::WeakPtr<SpdyStream>& stream, |
| const base::WeakPtr<SpdySession>& session_to_close) |
| : StreamDelegateDoNothing(stream), |
| session_to_close_(session_to_close) {} |
| |
| ~SessionClosingDelegate() override = default; |
| |
| void OnClose(int status) override { |
| session_to_close_->CloseSessionOnError(ERR_SPDY_PROTOCOL_ERROR, "Error"); |
| } |
| |
| private: |
| base::WeakPtr<SpdySession> session_to_close_; |
| }; |
| |
| // Close an activated stream that closes its session. Nothing should |
| // blow up. This is a regression test for https://crbug.com/263691. |
| TEST_F(SpdySessionTest, CloseActivatedStreamThatClosesSession) { |
| spdy::SpdySerializedFrame req( |
| spdy_util_.ConstructSpdyGet(nullptr, 0, 1, MEDIUM)); |
| spdy::SpdySerializedFrame rst( |
| spdy_util_.ConstructSpdyRstStream(1, spdy::ERROR_CODE_CANCEL)); |
| spdy::SpdySerializedFrame goaway(spdy_util_.ConstructSpdyGoAway( |
| 0, spdy::ERROR_CODE_PROTOCOL_ERROR, "Error")); |
| // The GOAWAY has higher-priority than the RST_STREAM, and is written first |
| // despite being queued second. |
| MockWrite writes[] = { |
| CreateMockWrite(req, 0), CreateMockWrite(goaway, 1), |
| CreateMockWrite(rst, 3), |
| }; |
| |
| MockRead reads[] = { |
| MockRead(ASYNC, 0, 2) // EOF |
| }; |
| SequencedSocketData data(reads, writes); |
| session_deps_.socket_factory->AddSocketDataProvider(&data); |
| |
| AddSSLSocketData(); |
| |
| CreateNetworkSession(); |
| CreateSpdySession(); |
| |
| base::WeakPtr<SpdyStream> spdy_stream = |
| CreateStreamSynchronously(SPDY_REQUEST_RESPONSE_STREAM, session_, |
| test_url_, MEDIUM, NetLogWithSource()); |
| ASSERT_TRUE(spdy_stream); |
| EXPECT_EQ(0u, spdy_stream->stream_id()); |
| |
| SessionClosingDelegate delegate(spdy_stream, session_); |
| spdy_stream->SetDelegate(&delegate); |
| |
| spdy::SpdyHeaderBlock headers( |
| spdy_util_.ConstructGetHeaderBlock(kDefaultUrl)); |
| spdy_stream->SendRequestHeaders(std::move(headers), NO_MORE_DATA_TO_SEND); |
| |
| EXPECT_EQ(0u, spdy_stream->stream_id()); |
| |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_EQ(1u, spdy_stream->stream_id()); |
| |
| // Ensure we don't crash while closing the stream (which closes the |
| // session). |
| spdy_stream->Cancel(ERR_ABORTED); |
| |
| EXPECT_FALSE(spdy_stream); |
| EXPECT_TRUE(delegate.StreamIsClosed()); |
| |
| // Write the RST_STREAM & GOAWAY. |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_TRUE(data.AllWriteDataConsumed()); |
| EXPECT_TRUE(data.AllReadDataConsumed()); |
| } |
| |
| TEST_F(SpdySessionTest, VerifyDomainAuthentication) { |
| SequencedSocketData data; |
| session_deps_.socket_factory->AddSocketDataProvider(&data); |
| |
| AddSSLSocketData(); |
| |
| CreateNetworkSession(); |
| CreateSpdySession(); |
| |
| EXPECT_TRUE(session_->VerifyDomainAuthentication("www.example.org")); |
| EXPECT_TRUE(session_->VerifyDomainAuthentication("mail.example.org")); |
| EXPECT_TRUE(session_->VerifyDomainAuthentication("mail.example.com")); |
| EXPECT_FALSE(session_->VerifyDomainAuthentication("mail.google.com")); |
| } |
| |
| TEST_F(SpdySessionTest, ConnectionPooledWithTlsChannelId) { |
| SequencedSocketData data; |
| session_deps_.socket_factory->AddSocketDataProvider(&data); |
| |
| ssl_.ssl_info.channel_id_sent = true; |
| AddSSLSocketData(); |
| |
| CreateNetworkSession(); |
| CreateSpdySession(); |
| |
| EXPECT_TRUE(session_->VerifyDomainAuthentication("www.example.org")); |
| EXPECT_TRUE(session_->VerifyDomainAuthentication("mail.example.org")); |
| EXPECT_FALSE(session_->VerifyDomainAuthentication("mail.example.com")); |
| EXPECT_FALSE(session_->VerifyDomainAuthentication("mail.google.com")); |
| } |
| |
| TEST_F(SpdySessionTest, CloseTwoStalledCreateStream) { |
| // TODO(rtenneti): Define a helper class/methods and move the common code in |
| // this file. |
| spdy::SettingsMap new_settings; |
| const spdy::SpdySettingsId kSpdySettingsId1 = |
| spdy::SETTINGS_MAX_CONCURRENT_STREAMS; |
| const uint32_t max_concurrent_streams = 1; |
| new_settings[kSpdySettingsId1] = max_concurrent_streams; |
| |
| spdy::SpdySerializedFrame settings_ack(spdy_util_.ConstructSpdySettingsAck()); |
| spdy::SpdySerializedFrame req1( |
| spdy_util_.ConstructSpdyGet(nullptr, 0, 1, LOWEST)); |
| spdy_util_.UpdateWithStreamDestruction(1); |
| spdy::SpdySerializedFrame req2( |
| spdy_util_.ConstructSpdyGet(nullptr, 0, 3, LOWEST)); |
| spdy_util_.UpdateWithStreamDestruction(3); |
| spdy::SpdySerializedFrame req3( |
| spdy_util_.ConstructSpdyGet(nullptr, 0, 5, LOWEST)); |
| MockWrite writes[] = { |
| CreateMockWrite(settings_ack, 1), CreateMockWrite(req1, 2), |
| CreateMockWrite(req2, 5), CreateMockWrite(req3, 8), |
| }; |
| |
| // Set up the socket so we read a SETTINGS frame that sets max concurrent |
| // streams to 1. |
| spdy::SpdySerializedFrame settings_frame( |
| spdy_util_.ConstructSpdySettings(new_settings)); |
| |
| spdy::SpdySerializedFrame resp1( |
| spdy_util_.ConstructSpdyGetReply(nullptr, 0, 1)); |
| spdy::SpdySerializedFrame body1(spdy_util_.ConstructSpdyDataFrame(1, true)); |
| |
| spdy::SpdySerializedFrame resp2( |
| spdy_util_.ConstructSpdyGetReply(nullptr, 0, 3)); |
| spdy::SpdySerializedFrame body2(spdy_util_.ConstructSpdyDataFrame(3, true)); |
| |
| spdy::SpdySerializedFrame resp3( |
| spdy_util_.ConstructSpdyGetReply(nullptr, 0, 5)); |
| spdy::SpdySerializedFrame body3(spdy_util_.ConstructSpdyDataFrame(5, true)); |
| |
| MockRead reads[] = { |
| CreateMockRead(settings_frame, 0), |
| CreateMockRead(resp1, 3), |
| CreateMockRead(body1, 4), |
| CreateMockRead(resp2, 6), |
| CreateMockRead(body2, 7), |
| CreateMockRead(resp3, 9), |
| CreateMockRead(body3, 10), |
| MockRead(ASYNC, ERR_IO_PENDING, 11), |
| MockRead(ASYNC, 0, 12) // EOF |
| }; |
| |
| SequencedSocketData data(reads, writes); |
| session_deps_.socket_factory->AddSocketDataProvider(&data); |
| |
| AddSSLSocketData(); |
| |
| CreateNetworkSession(); |
| CreateSpdySession(); |
| |
| // Read the settings frame. |
| base::RunLoop().RunUntilIdle(); |
| |
| base::WeakPtr<SpdyStream> spdy_stream1 = |
| CreateStreamSynchronously(SPDY_REQUEST_RESPONSE_STREAM, session_, |
| test_url_, LOWEST, NetLogWithSource()); |
| ASSERT_TRUE(spdy_stream1); |
| EXPECT_EQ(0u, spdy_stream1->stream_id()); |
| test::StreamDelegateDoNothing delegate1(spdy_stream1); |
| spdy_stream1->SetDelegate(&delegate1); |
| |
| TestCompletionCallback callback2; |
| SpdyStreamRequest request2; |
| ASSERT_EQ(ERR_IO_PENDING, |
| request2.StartRequest(SPDY_REQUEST_RESPONSE_STREAM, session_, |
| test_url_, LOWEST, SocketTag(), |
| NetLogWithSource(), callback2.callback(), |
| TRAFFIC_ANNOTATION_FOR_TESTS)); |
| |
| TestCompletionCallback callback3; |
| SpdyStreamRequest request3; |
| ASSERT_EQ(ERR_IO_PENDING, |
| request3.StartRequest(SPDY_REQUEST_RESPONSE_STREAM, session_, |
| test_url_, LOWEST, SocketTag(), |
| NetLogWithSource(), callback3.callback(), |
| TRAFFIC_ANNOTATION_FOR_TESTS)); |
| |
| EXPECT_EQ(0u, num_active_streams()); |
| EXPECT_EQ(1u, num_created_streams()); |
| EXPECT_EQ(2u, pending_create_stream_queue_size(LOWEST)); |
| |
| spdy::SpdyHeaderBlock headers( |
| spdy_util_.ConstructGetHeaderBlock(kDefaultUrl)); |
| spdy_stream1->SendRequestHeaders(std::move(headers), NO_MORE_DATA_TO_SEND); |
| |
| // Run until 1st stream is activated and then closed. |
| EXPECT_EQ(0u, delegate1.stream_id()); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_FALSE(spdy_stream1); |
| EXPECT_EQ(1u, delegate1.stream_id()); |
| |
| EXPECT_EQ(0u, num_active_streams()); |
| EXPECT_EQ(1u, pending_create_stream_queue_size(LOWEST)); |
| |
| // Pump loop for SpdySession::ProcessPendingStreamRequests() to |
| // create the 2nd stream. |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_EQ(0u, num_active_streams()); |
| EXPECT_EQ(1u, num_created_streams()); |
| EXPECT_EQ(1u, pending_create_stream_queue_size(LOWEST)); |
| |
| base::WeakPtr<SpdyStream> stream2 = request2.ReleaseStream(); |
| test::StreamDelegateDoNothing delegate2(stream2); |
| stream2->SetDelegate(&delegate2); |
| spdy::SpdyHeaderBlock headers2( |
| spdy_util_.ConstructGetHeaderBlock(kDefaultUrl)); |
| stream2->SendRequestHeaders(std::move(headers2), NO_MORE_DATA_TO_SEND); |
| |
| // Run until 2nd stream is activated and then closed. |
| EXPECT_EQ(0u, delegate2.stream_id()); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_FALSE(stream2); |
| EXPECT_EQ(3u, delegate2.stream_id()); |
| |
| EXPECT_EQ(0u, num_active_streams()); |
| EXPECT_EQ(0u, pending_create_stream_queue_size(LOWEST)); |
| |
| // Pump loop for SpdySession::ProcessPendingStreamRequests() to |
| // create the 3rd stream. |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_EQ(0u, num_active_streams()); |
| EXPECT_EQ(1u, num_created_streams()); |
| EXPECT_EQ(0u, pending_create_stream_queue_size(LOWEST)); |
| |
| base::WeakPtr<SpdyStream> stream3 = request3.ReleaseStream(); |
| test::StreamDelegateDoNothing delegate3(stream3); |
| stream3->SetDelegate(&delegate3); |
| spdy::SpdyHeaderBlock headers3( |
| spdy_util_.ConstructGetHeaderBlock(kDefaultUrl)); |
| stream3->SendRequestHeaders(std::move(headers3), NO_MORE_DATA_TO_SEND); |
| |
| // Run until 2nd stream is activated and then closed. |
| EXPECT_EQ(0u, delegate3.stream_id()); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_FALSE(stream3); |
| EXPECT_EQ(5u, delegate3.stream_id()); |
| |
| EXPECT_EQ(0u, num_active_streams()); |
| EXPECT_EQ(0u, num_created_streams()); |
| EXPECT_EQ(0u, pending_create_stream_queue_size(LOWEST)); |
| |
| data.Resume(); |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| TEST_F(SpdySessionTest, CancelTwoStalledCreateStream) { |
| MockRead reads[] = { |
| MockRead(SYNCHRONOUS, ERR_IO_PENDING) // Stall forever. |
| }; |
| |
| StaticSocketDataProvider data(reads, base::span<MockWrite>()); |
| session_deps_.socket_factory->AddSocketDataProvider(&data); |
| |
| AddSSLSocketData(); |
| |
| CreateNetworkSession(); |
| CreateSpdySession(); |
| |
| // Leave room for only one more stream to be created. |
| for (size_t i = 0; i < kInitialMaxConcurrentStreams - 1; ++i) { |
| base::WeakPtr<SpdyStream> spdy_stream = |
| CreateStreamSynchronously(SPDY_BIDIRECTIONAL_STREAM, session_, |
| test_url_, MEDIUM, NetLogWithSource()); |
| ASSERT_TRUE(spdy_stream); |
| } |
| |
| base::WeakPtr<SpdyStream> spdy_stream1 = |
| CreateStreamSynchronously(SPDY_BIDIRECTIONAL_STREAM, session_, test_url_, |
| LOWEST, NetLogWithSource()); |
| ASSERT_TRUE(spdy_stream1); |
| EXPECT_EQ(0u, spdy_stream1->stream_id()); |
| |
| TestCompletionCallback callback2; |
| SpdyStreamRequest request2; |
| ASSERT_EQ(ERR_IO_PENDING, |
| request2.StartRequest(SPDY_BIDIRECTIONAL_STREAM, session_, |
| test_url_, LOWEST, SocketTag(), |
| NetLogWithSource(), callback2.callback(), |
| TRAFFIC_ANNOTATION_FOR_TESTS)); |
| |
| TestCompletionCallback callback3; |
| SpdyStreamRequest request3; |
| ASSERT_EQ(ERR_IO_PENDING, |
| request3.StartRequest(SPDY_BIDIRECTIONAL_STREAM, session_, |
| test_url_, LOWEST, SocketTag(), |
| NetLogWithSource(), callback3.callback(), |
| TRAFFIC_ANNOTATION_FOR_TESTS)); |
| |
| EXPECT_EQ(0u, num_active_streams()); |
| EXPECT_EQ(kInitialMaxConcurrentStreams, num_created_streams()); |
| EXPECT_EQ(2u, pending_create_stream_queue_size(LOWEST)); |
| |
| // Cancel the first stream; this will allow the second stream to be created. |
| EXPECT_TRUE(spdy_stream1); |
| spdy_stream1->Cancel(ERR_ABORTED); |
| EXPECT_FALSE(spdy_stream1); |
| |
| EXPECT_THAT(callback2.WaitForResult(), IsOk()); |
| EXPECT_EQ(0u, num_active_streams()); |
| EXPECT_EQ(kInitialMaxConcurrentStreams, num_created_streams()); |
| EXPECT_EQ(1u, pending_create_stream_queue_size(LOWEST)); |
| |
| // Cancel the second stream; this will allow the third stream to be created. |
| base::WeakPtr<SpdyStream> spdy_stream2 = request2.ReleaseStream(); |
| spdy_stream2->Cancel(ERR_ABORTED); |
| EXPECT_FALSE(spdy_stream2); |
| |
| EXPECT_THAT(callback3.WaitForResult(), IsOk()); |
| EXPECT_EQ(0u, num_active_streams()); |
| EXPECT_EQ(kInitialMaxConcurrentStreams, num_created_streams()); |
| EXPECT_EQ(0u, pending_create_stream_queue_size(LOWEST)); |
| |
| // Cancel the third stream. |
| base::WeakPtr<SpdyStream> spdy_stream3 = request3.ReleaseStream(); |
| spdy_stream3->Cancel(ERR_ABORTED); |
| EXPECT_FALSE(spdy_stream3); |
| EXPECT_EQ(0u, num_active_streams()); |
| EXPECT_EQ(kInitialMaxConcurrentStreams - 1, num_created_streams()); |
| EXPECT_EQ(0u, pending_create_stream_queue_size(LOWEST)); |
| } |
| |
| // Test that SpdySession::DoReadLoop reads data from the socket |
| // without yielding. This test makes 32k - 1 bytes of data available |
| // on the socket for reading. It then verifies that it has read all |
| // the available data without yielding. |
| TEST_F(SpdySessionTest, ReadDataWithoutYielding) { |
| session_deps_.time_func = InstantaneousReads; |
| |
| spdy::SpdySerializedFrame req1( |
| spdy_util_.ConstructSpdyGet(nullptr, 0, 1, MEDIUM)); |
| MockWrite writes[] = { |
| CreateMockWrite(req1, 0), |
| }; |
| |
| // Build buffer of size kYieldAfterBytesRead / 4 |
| // (-spdy_data_frame_size). |
| ASSERT_EQ(32 * 1024, kYieldAfterBytesRead); |
| const int kPayloadSize = kYieldAfterBytesRead / 4 - spdy::kFrameHeaderSize; |
| TestDataStream test_stream; |
| scoped_refptr<IOBuffer> payload = |
| base::MakeRefCounted<IOBuffer>(kPayloadSize); |
| char* payload_data = payload->data(); |
| test_stream.GetBytes(payload_data, kPayloadSize); |
| |
| spdy::SpdySerializedFrame partial_data_frame( |
| spdy_util_.ConstructSpdyDataFrame( |
| 1, base::StringPiece(payload_data, kPayloadSize), /*fin=*/false)); |
| spdy::SpdySerializedFrame finish_data_frame(spdy_util_.ConstructSpdyDataFrame( |
| 1, base::StringPiece(payload_data, kPayloadSize - 1), /*fin=*/true)); |
| |
| spdy::SpdySerializedFrame resp1( |
| spdy_util_.ConstructSpdyGetReply(nullptr, 0, 1)); |
| |
| // Write 1 byte less than kMaxReadBytes to check that DoRead reads up to 32k |
| // bytes. |
| MockRead reads[] = { |
| CreateMockRead(resp1, 1), |
| MockRead(ASYNC, ERR_IO_PENDING, 2), |
| CreateMockRead(partial_data_frame, 3), |
| CreateMockRead(partial_data_frame, 4, SYNCHRONOUS), |
| CreateMockRead(partial_data_frame, 5, SYNCHRONOUS), |
| CreateMockRead(finish_data_frame, 6, SYNCHRONOUS), |
| MockRead(ASYNC, 0, 7) // EOF |
| }; |
| |
| // Create SpdySession and SpdyStream and send the request. |
| SequencedSocketData data(reads, writes); |
| session_deps_.socket_factory->AddSocketDataProvider(&data); |
| |
| AddSSLSocketData(); |
| |
| CreateNetworkSession(); |
| CreateSpdySession(); |
| |
| base::WeakPtr<SpdyStream> spdy_stream1 = |
| CreateStreamSynchronously(SPDY_REQUEST_RESPONSE_STREAM, session_, |
| test_url_, MEDIUM, NetLogWithSource()); |
| ASSERT_TRUE(spdy_stream1); |
| EXPECT_EQ(0u, spdy_stream1->stream_id()); |
| test::StreamDelegateDoNothing delegate1(spdy_stream1); |
| spdy_stream1->SetDelegate(&delegate1); |
| |
| spdy::SpdyHeaderBlock headers1( |
| spdy_util_.ConstructGetHeaderBlock(kDefaultUrl)); |
| spdy_stream1->SendRequestHeaders(std::move(headers1), NO_MORE_DATA_TO_SEND); |
| |
| // Set up the TaskObserver to verify SpdySession::DoReadLoop doesn't |
| // post a task. |
| SpdySessionTestTaskObserver observer("spdy_session.cc", "DoReadLoop"); |
| |
| // Run until 1st read. |
| EXPECT_EQ(0u, delegate1.stream_id()); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_EQ(1u, delegate1.stream_id()); |
| EXPECT_EQ(0u, observer.executed_count()); |
| |
| // Read all the data and verify SpdySession::DoReadLoop has not |
| // posted a task. |
| data.Resume(); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_FALSE(spdy_stream1); |
| |
| // Verify task observer's executed_count is zero, which indicates DoRead read |
| // all the available data. |
| EXPECT_EQ(0u, observer.executed_count()); |
| EXPECT_TRUE(data.AllWriteDataConsumed()); |
| EXPECT_TRUE(data.AllReadDataConsumed()); |
| } |
| |
| // Test that SpdySession::DoReadLoop yields if more than |
| // |kYieldAfterDurationMilliseconds| has passed. This test uses a mock time |
| // function that makes the response frame look very slow to read. |
| TEST_F(SpdySessionTest, TestYieldingSlowReads) { |
| session_deps_.time_func = SlowReads; |
| |
| spdy::SpdySerializedFrame req1( |
| spdy_util_.ConstructSpdyGet(nullptr, 0, 1, MEDIUM)); |
| MockWrite writes[] = { |
| CreateMockWrite(req1, 0), |
| }; |
| |
| spdy::SpdySerializedFrame resp1( |
| spdy_util_.ConstructSpdyGetReply(nullptr, 0, 1)); |
| |
| MockRead reads[] = { |
| CreateMockRead(resp1, 1), MockRead(ASYNC, 0, 2) // EOF |
| }; |
| |
| // Create SpdySession and SpdyStream and send the request. |
| SequencedSocketData data(reads, writes); |
| session_deps_.socket_factory->AddSocketDataProvider(&data); |
| |
| AddSSLSocketData(); |
| |
| CreateNetworkSession(); |
| CreateSpdySession(); |
| |
| base::WeakPtr<SpdyStream> spdy_stream1 = |
| CreateStreamSynchronously(SPDY_REQUEST_RESPONSE_STREAM, session_, |
| test_url_, MEDIUM, NetLogWithSource()); |
| ASSERT_TRUE(spdy_stream1); |
| EXPECT_EQ(0u, spdy_stream1->stream_id()); |
| test::StreamDelegateDoNothing delegate1(spdy_stream1); |
| spdy_stream1->SetDelegate(&delegate1); |
| |
| spdy::SpdyHeaderBlock headers1( |
| spdy_util_.ConstructGetHeaderBlock(kDefaultUrl)); |
| spdy_stream1->SendRequestHeaders(std::move(headers1), NO_MORE_DATA_TO_SEND); |
| |
| // Set up the TaskObserver to verify that SpdySession::DoReadLoop posts a |
| // task. |
| SpdySessionTestTaskObserver observer("spdy_session.cc", "DoReadLoop"); |
| |
| EXPECT_EQ(0u, delegate1.stream_id()); |
| EXPECT_EQ(0u, observer.executed_count()); |
| |
| // Read all the data and verify that SpdySession::DoReadLoop has posted a |
| // task. |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_EQ(1u, delegate1.stream_id()); |
| EXPECT_FALSE(spdy_stream1); |
| |
| // Verify task that the observer's executed_count is 1, which indicates DoRead |
| // has posted only one task and thus yielded though there is data available |
| // for it to read. |
| EXPECT_EQ(1u, observer.executed_count()); |
| EXPECT_TRUE(data.AllWriteDataConsumed()); |
| EXPECT_TRUE(data.AllReadDataConsumed()); |
| } |
| |
| // Regression test for https://crbug.com/531570. |
| // Test the case where DoRead() takes long but returns synchronously. |
| TEST_F(SpdySessionTest, TestYieldingSlowSynchronousReads) { |
| session_deps_.time_func = SlowReads; |
| |
| spdy::SpdySerializedFrame req1( |
| spdy_util_.ConstructSpdyGet(nullptr, 0, 1, MEDIUM)); |
| MockWrite writes[] = { |
| CreateMockWrite(req1, 0), |
| }; |
| |
| spdy::SpdySerializedFrame partial_data_frame( |
| spdy_util_.ConstructSpdyDataFrame(1, "foo ", /*fin=*/false)); |
| spdy::SpdySerializedFrame finish_data_frame( |
| spdy_util_.ConstructSpdyDataFrame(1, "bar", /*fin=*/true)); |
| |
| spdy::SpdySerializedFrame resp1( |
| spdy_util_.ConstructSpdyGetReply(nullptr, 0, 1)); |
| |
| MockRead reads[] = { |
| CreateMockRead(resp1, 1), |
| MockRead(ASYNC, ERR_IO_PENDING, 2), |
| CreateMockRead(partial_data_frame, 3, ASYNC), |
| CreateMockRead(partial_data_frame, 4, SYNCHRONOUS), |
| CreateMockRead(partial_data_frame, 5, SYNCHRONOUS), |
| CreateMockRead(finish_data_frame, 6, SYNCHRONOUS), |
| MockRead(ASYNC, 0, 7) // EOF |
| }; |
| |
| // Create SpdySession and SpdyStream and send the request. |
| SequencedSocketData data(reads, writes); |
| session_deps_.socket_factory->AddSocketDataProvider(&data); |
| |
| AddSSLSocketData(); |
| |
| CreateNetworkSession(); |
| CreateSpdySession(); |
| |
| base::WeakPtr<SpdyStream> spdy_stream1 = |
| CreateStreamSynchronously(SPDY_REQUEST_RESPONSE_STREAM, session_, |
| test_url_, MEDIUM, NetLogWithSource()); |
| ASSERT_TRUE(spdy_stream1); |
| EXPECT_EQ(0u, spdy_stream1->stream_id()); |
| test::StreamDelegateDoNothing delegate1(spdy_stream1); |
| spdy_stream1->SetDelegate(&delegate1); |
| |
| spdy::SpdyHeaderBlock headers1( |
| spdy_util_.ConstructGetHeaderBlock(kDefaultUrl)); |
| spdy_stream1->SendRequestHeaders(std::move(headers1), NO_MORE_DATA_TO_SEND); |
| |
| // Run until 1st read. |
| EXPECT_EQ(0u, delegate1.stream_id()); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_EQ(1u, delegate1.stream_id()); |
| |
| // Read all the data and verify SpdySession::DoReadLoop has posted a task. |
| data.Resume(); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_EQ("foo foo foo bar", delegate1.TakeReceivedData()); |
| EXPECT_FALSE(spdy_stream1); |
| |
| EXPECT_TRUE(data.AllWriteDataConsumed()); |
| EXPECT_TRUE(data.AllReadDataConsumed()); |
| } |
| |
| // Test that SpdySession::DoReadLoop yields while reading the |
| // data. This test makes 32k + 1 bytes of data available on the socket |
| // for reading. It then verifies that DoRead has yielded even though |
| // there is data available for it to read (i.e, socket()->Read didn't |
| // return ERR_IO_PENDING during socket reads). |
| TEST_F(SpdySessionTest, TestYieldingDuringReadData) { |
| session_deps_.time_func = InstantaneousReads; |
| |
| spdy::SpdySerializedFrame req1( |
| spdy_util_.ConstructSpdyGet(nullptr, 0, 1, MEDIUM)); |
| MockWrite writes[] = { |
| CreateMockWrite(req1, 0), |
| }; |
| |
| // Build buffer of size kYieldAfterBytesRead / 4 |
| // (-spdy_data_frame_size). |
| ASSERT_EQ(32 * 1024, kYieldAfterBytesRead); |
| const int kPayloadSize = kYieldAfterBytesRead / 4 - spdy::kFrameHeaderSize; |
| TestDataStream test_stream; |
| scoped_refptr<IOBuffer> payload = |
| base::MakeRefCounted<IOBuffer>(kPayloadSize); |
| char* payload_data = payload->data(); |
| test_stream.GetBytes(payload_data, kPayloadSize); |
| |
| spdy::SpdySerializedFrame partial_data_frame( |
| spdy_util_.ConstructSpdyDataFrame( |
| 1, base::StringPiece(payload_data, kPayloadSize), /*fin=*/false)); |
| spdy::SpdySerializedFrame finish_data_frame( |
| spdy_util_.ConstructSpdyDataFrame(1, "h", /*fin=*/true)); |
| |
| spdy::SpdySerializedFrame resp1( |
| spdy_util_.ConstructSpdyGetReply(nullptr, 0, 1)); |
| |
| // Write 1 byte more than kMaxReadBytes to check that DoRead yields. |
| MockRead reads[] = { |
| CreateMockRead(resp1, 1), |
| MockRead(ASYNC, ERR_IO_PENDING, 2), |
| CreateMockRead(partial_data_frame, 3), |
| CreateMockRead(partial_data_frame, 4, SYNCHRONOUS), |
| CreateMockRead(partial_data_frame, 5, SYNCHRONOUS), |
| CreateMockRead(partial_data_frame, 6, SYNCHRONOUS), |
| CreateMockRead(finish_data_frame, 7, SYNCHRONOUS), |
| MockRead(ASYNC, 0, 8) // EOF |
| }; |
| |
| // Create SpdySession and SpdyStream and send the request. |
| SequencedSocketData data(reads, writes); |
| session_deps_.socket_factory->AddSocketDataProvider(&data); |
| |
| AddSSLSocketData(); |
| |
| CreateNetworkSession(); |
| CreateSpdySession(); |
| |
| base::WeakPtr<SpdyStream> spdy_stream1 = |
| CreateStreamSynchronously(SPDY_REQUEST_RESPONSE_STREAM, session_, |
| test_url_, MEDIUM, NetLogWithSource()); |
| ASSERT_TRUE(spdy_stream1); |
| EXPECT_EQ(0u, spdy_stream1->stream_id()); |
| test::StreamDelegateDoNothing delegate1(spdy_stream1); |
| spdy_stream1->SetDelegate(&delegate1); |
| |
| spdy::SpdyHeaderBlock headers1( |
| spdy_util_.ConstructGetHeaderBlock(kDefaultUrl)); |
| spdy_stream1->SendRequestHeaders(std::move(headers1), NO_MORE_DATA_TO_SEND); |
| |
| // Set up the TaskObserver to verify SpdySession::DoReadLoop posts a task. |
| SpdySessionTestTaskObserver observer("spdy_session.cc", "DoReadLoop"); |
| |
| // Run until 1st read. |
| EXPECT_EQ(0u, delegate1.stream_id()); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_EQ(1u, delegate1.stream_id()); |
| EXPECT_EQ(0u, observer.executed_count()); |
| |
| // Read all the data and verify SpdySession::DoReadLoop has posted a task. |
| data.Resume(); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_FALSE(spdy_stream1); |
| |
| // Verify task observer's executed_count is 1, which indicates DoRead has |
| // posted only one task and thus yielded though there is data available for it |
| // to read. |
| EXPECT_EQ(1u, observer.executed_count()); |
| EXPECT_TRUE(data.AllWriteDataConsumed()); |
| EXPECT_TRUE(data.AllReadDataConsumed()); |
| } |
| |
| // Test that SpdySession::DoReadLoop() tests interactions of yielding |
| // + async, by doing the following MockReads. |
| // |
| // MockRead of SYNCHRONOUS 8K, SYNCHRONOUS 8K, SYNCHRONOUS 8K, SYNCHRONOUS 2K |
| // ASYNC 8K, SYNCHRONOUS 8K, SYNCHRONOUS 8K, SYNCHRONOUS 8K, SYNCHRONOUS 2K. |
| // |
| // The above reads 26K synchronously. Since that is less that 32K, we |
| // will attempt to read again. However, that DoRead() will return |
| // ERR_IO_PENDING (because of async read), so DoReadLoop() will |
| // yield. When we come back, DoRead() will read the results from the |
| // async read, and rest of the data synchronously. |
| TEST_F(SpdySessionTest, TestYieldingDuringAsyncReadData) { |
| session_deps_.time_func = InstantaneousReads; |
| |
| spdy::SpdySerializedFrame req1( |
| spdy_util_.ConstructSpdyGet(nullptr, 0, 1, MEDIUM)); |
| MockWrite writes[] = { |
| CreateMockWrite(req1, 0), |
| }; |
| |
| // Build buffer of size kYieldAfterBytesRead / 4 |
| // (-spdy_data_frame_size). |
| ASSERT_EQ(32 * 1024, kYieldAfterBytesRead); |
| TestDataStream test_stream; |
| const int kEightKPayloadSize = |
| kYieldAfterBytesRead / 4 - spdy::kFrameHeaderSize; |
| scoped_refptr<IOBuffer> eightk_payload = |
| base::MakeRefCounted<IOBuffer>(kEightKPayloadSize); |
| char* eightk_payload_data = eightk_payload->data(); |
| test_stream.GetBytes(eightk_payload_data, kEightKPayloadSize); |
| |
| // Build buffer of 2k size. |
| TestDataStream test_stream2; |
| const int kTwoKPayloadSize = kEightKPayloadSize - 6 * 1024; |
| scoped_refptr<IOBuffer> twok_payload = |
| base::MakeRefCounted<IOBuffer>(kTwoKPayloadSize); |
| char* twok_payload_data = twok_payload->data(); |
| test_stream2.GetBytes(twok_payload_data, kTwoKPayloadSize); |
| |
| spdy::SpdySerializedFrame eightk_data_frame(spdy_util_.ConstructSpdyDataFrame( |
| 1, base::StringPiece(eightk_payload_data, kEightKPayloadSize), |
| /*fin=*/false)); |
| spdy::SpdySerializedFrame twok_data_frame(spdy_util_.ConstructSpdyDataFrame( |
| 1, base::StringPiece(twok_payload_data, kTwoKPayloadSize), |
| /*fin=*/false)); |
| spdy::SpdySerializedFrame finish_data_frame( |
| spdy_util_.ConstructSpdyDataFrame(1, "h", /*fin=*/true)); |
| |
| spdy::SpdySerializedFrame resp1( |
| spdy_util_.ConstructSpdyGetReply(nullptr, 0, 1)); |
| |
| MockRead reads[] = { |
| CreateMockRead(resp1, 1), |
| MockRead(ASYNC, ERR_IO_PENDING, 2), |
| CreateMockRead(eightk_data_frame, 3), |
| CreateMockRead(eightk_data_frame, 4, SYNCHRONOUS), |
| CreateMockRead(eightk_data_frame, 5, SYNCHRONOUS), |
| CreateMockRead(twok_data_frame, 6, SYNCHRONOUS), |
| CreateMockRead(eightk_data_frame, 7, ASYNC), |
| CreateMockRead(eightk_data_frame, 8, SYNCHRONOUS), |
| CreateMockRead(eightk_data_frame, 9, SYNCHRONOUS), |
| CreateMockRead(eightk_data_frame, 10, SYNCHRONOUS), |
| CreateMockRead(twok_data_frame, 11, SYNCHRONOUS), |
| CreateMockRead(finish_data_frame, 12, SYNCHRONOUS), |
| MockRead(ASYNC, 0, 13) // EOF |
| }; |
| |
| // Create SpdySession and SpdyStream and send the request. |
| SequencedSocketData data(reads, writes); |
| session_deps_.socket_factory->AddSocketDataProvider(&data); |
| |
| AddSSLSocketData(); |
| |
| CreateNetworkSession(); |
| CreateSpdySession(); |
| |
| base::WeakPtr<SpdyStream> spdy_stream1 = |
| CreateStreamSynchronously(SPDY_REQUEST_RESPONSE_STREAM, session_, |
| test_url_, MEDIUM, NetLogWithSource()); |
| ASSERT_TRUE(spdy_stream1); |
| EXPECT_EQ(0u, spdy_stream1->stream_id()); |
| test::StreamDelegateDoNothing delegate1(spdy_stream1); |
| spdy_stream1->SetDelegate(&delegate1); |
| |
| spdy::SpdyHeaderBlock headers1( |
| spdy_util_.ConstructGetHeaderBlock(kDefaultUrl)); |
| spdy_stream1->SendRequestHeaders(std::move(headers1), NO_MORE_DATA_TO_SEND); |
| |
| // Set up the TaskObserver to monitor SpdySession::DoReadLoop |
| // posting of tasks. |
| SpdySessionTestTaskObserver observer("spdy_session.cc", "DoReadLoop"); |
| |
| // Run until 1st read. |
| EXPECT_EQ(0u, delegate1.stream_id()); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_EQ(1u, delegate1.stream_id()); |
| EXPECT_EQ(0u, observer.executed_count()); |
| |
| // Read all the data and verify SpdySession::DoReadLoop has posted a |
| // task. |
| data.Resume(); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_FALSE(spdy_stream1); |
| |
| // Verify task observer's executed_count is 1, which indicates DoRead has |
| // posted only one task and thus yielded though there is data available for |
| // it to read. |
| EXPECT_EQ(1u, observer.executed_count()); |
| EXPECT_TRUE(data.AllWriteDataConsumed()); |
| EXPECT_TRUE(data.AllReadDataConsumed()); |
| } |
| |
| // Send a GoAway frame when SpdySession is in DoReadLoop. Make sure |
| // nothing blows up. |
| TEST_F(SpdySessionTest, GoAwayWhileInDoReadLoop) { |
| spdy::SpdySerializedFrame req1( |
| spdy_util_.ConstructSpdyGet(nullptr, 0, 1, MEDIUM)); |
| MockWrite writes[] = { |
| CreateMockWrite(req1, 0), |
| }; |
| |
| spdy::SpdySerializedFrame resp1( |
| spdy_util_.ConstructSpdyGetReply(nullptr, 0, 1)); |
| spdy::SpdySerializedFrame body1(spdy_util_.ConstructSpdyDataFrame(1, true)); |
| spdy::SpdySerializedFrame goaway(spdy_util_.ConstructSpdyGoAway(0)); |
| |
| MockRead reads[] = { |
| CreateMockRead(resp1, 1), MockRead(ASYNC, ERR_IO_PENDING, 2), |
| CreateMockRead(body1, 3), CreateMockRead(goaway, 4), |
| }; |
| |
| // Create SpdySession and SpdyStream and send the request. |
| SequencedSocketData data(reads, writes); |
| session_deps_.socket_factory->AddSocketDataProvider(&data); |
| |
| AddSSLSocketData(); |
| |
| CreateNetworkSession(); |
| CreateSpdySession(); |
| |
| base::WeakPtr<SpdyStream> spdy_stream1 = |
| CreateStreamSynchronously(SPDY_REQUEST_RESPONSE_STREAM, session_, |
| test_url_, MEDIUM, NetLogWithSource()); |
| test::StreamDelegateDoNothing delegate1(spdy_stream1); |
| spdy_stream1->SetDelegate(&delegate1); |
| ASSERT_TRUE(spdy_stream1); |
| EXPECT_EQ(0u, spdy_stream1->stream_id()); |
| |
| spdy::SpdyHeaderBlock headers1( |
| spdy_util_.ConstructGetHeaderBlock(kDefaultUrl)); |
| spdy_stream1->SendRequestHeaders(std::move(headers1), NO_MORE_DATA_TO_SEND); |
| |
| // Run until 1st read. |
| EXPECT_EQ(0u, spdy_stream1->stream_id()); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_EQ(1u, spdy_stream1->stream_id()); |
| |
| // Run until GoAway. |
| data.Resume(); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_FALSE(spdy_stream1); |
| EXPECT_TRUE(data.AllWriteDataConsumed()); |
| EXPECT_TRUE(data.AllReadDataConsumed()); |
| EXPECT_FALSE(session_); |
| } |
| |
| // Within this framework, a SpdySession should be initialized with |
| // flow control disabled for protocol version 2, with flow control |
| // enabled only for streams for protocol version 3, and with flow |
| // control enabled for streams and sessions for higher versions. |
| TEST_F(SpdySessionTest, ProtocolNegotiation) { |
| MockRead reads[] = { |
| MockRead(SYNCHRONOUS, 0, 0) // EOF |
| }; |
| StaticSocketDataProvider data(reads, base::span<MockWrite>()); |
| session_deps_.socket_factory->AddSocketDataProvider(&data); |
| |
| CreateNetworkSession(); |
| session_ = CreateFakeSpdySession(spdy_session_pool_, key_); |
| |
| EXPECT_EQ(kDefaultInitialWindowSize, session_send_window_size()); |
| EXPECT_EQ(kDefaultInitialWindowSize, session_recv_window_size()); |
| EXPECT_EQ(0, session_unacked_recv_window_bytes()); |
| } |
| |
| // Tests the case of a non-SPDY request closing an idle SPDY session when no |
| // pointers to the idle session are currently held. |
| TEST_F(SpdySessionTest, CloseOneIdleConnection) { |
| ClientSocketPoolManager::set_max_sockets_per_group( |
| HttpNetworkSession::NORMAL_SOCKET_POOL, 1); |
| ClientSocketPoolManager::set_max_sockets_per_pool( |
| HttpNetworkSession::NORMAL_SOCKET_POOL, 1); |
| |
| MockRead reads[] = { |
| MockRead(SYNCHRONOUS, ERR_IO_PENDING) // Stall forever. |
| }; |
| StaticSocketDataProvider data(reads, base::span<MockWrite>()); |
| session_deps_.socket_factory->AddSocketDataProvider(&data); |
| session_deps_.socket_factory->AddSocketDataProvider(&data); |
| |
| AddSSLSocketData(); |
| |
| CreateNetworkSession(); |
| |
| TransportClientSocketPool* pool = |
| http_session_->GetTransportSocketPool( |
| HttpNetworkSession::NORMAL_SOCKET_POOL); |
| |
| // Create an idle SPDY session. |
| CreateSpdySession(); |
| EXPECT_FALSE(pool->IsStalled()); |
| |
| // Trying to create a new connection should cause the pool to be stalled, and |
| // post a task asynchronously to try and close the session. |
| TestCompletionCallback callback2; |
| HostPortPair host_port2("2.com", 80); |
| scoped_refptr<TransportSocketParams> params2(new TransportSocketParams( |
| host_port2, false, OnHostResolutionCallback(), |
| TransportSocketParams::COMBINE_CONNECT_AND_WRITE_DEFAULT)); |
| auto connection2 = std::make_unique<ClientSocketHandle>(); |
| EXPECT_EQ( |
| ERR_IO_PENDING, |
| connection2->Init(host_port2.ToString(), params2, DEFAULT_PRIORITY, |
| SocketTag(), ClientSocketPool::RespectLimits::ENABLED, |
| callback2.callback(), pool, NetLogWithSource())); |
| EXPECT_TRUE(pool->IsStalled()); |
| |
| // The socket pool should close the connection asynchronously and establish a |
| // new connection. |
| EXPECT_THAT(callback2.WaitForResult(), IsOk()); |
| EXPECT_FALSE(pool->IsStalled()); |
| EXPECT_FALSE(session_); |
| } |
| |
| // Tests the case of a non-SPDY request closing an idle SPDY session when no |
| // pointers to the idle session are currently held, in the case the SPDY session |
| // has an alias. |
| TEST_F(SpdySessionTest, CloseOneIdleConnectionWithAlias) { |
| ClientSocketPoolManager::set_max_sockets_per_group( |
| HttpNetworkSession::NORMAL_SOCKET_POOL, 1); |
| ClientSocketPoolManager::set_max_sockets_per_pool( |
| HttpNetworkSession::NORMAL_SOCKET_POOL, 1); |
| |
| MockRead reads[] = { |
| MockRead(SYNCHRONOUS, ERR_IO_PENDING) // Stall forever. |
| }; |
| StaticSocketDataProvider data(reads, base::span<MockWrite>()); |
| session_deps_.socket_factory->AddSocketDataProvider(&data); |
| session_deps_.socket_factory->AddSocketDataProvider(&data); |
| |
| AddSSLSocketData(); |
| |
| session_deps_.host_resolver->set_synchronous_mode(true); |
| session_deps_.host_resolver->rules()->AddIPLiteralRule( |
| "www.example.org", "192.168.0.2", std::string()); |
| session_deps_.host_resolver->rules()->AddIPLiteralRule( |
| "mail.example.org", "192.168.0.2", std::string()); |
| // Not strictly needed. |
| session_deps_.host_resolver->rules()->AddIPLiteralRule("3.com", "192.168.0.3", |
| std::string()); |
| |
| CreateNetworkSession(); |
| |
| TransportClientSocketPool* pool = |
| http_session_->GetTransportSocketPool( |
| HttpNetworkSession::NORMAL_SOCKET_POOL); |
| |
| // Create an idle SPDY session. |
| SpdySessionKey key1(HostPortPair("www.example.org", 80), |
| ProxyServer::Direct(), PRIVACY_MODE_DISABLED, |
| SocketTag()); |
| base::WeakPtr<SpdySession> session1 = |
| ::net::CreateSpdySession(http_session_.get(), key1, NetLogWithSource()); |
| EXPECT_FALSE(pool->IsStalled()); |
| |
| // Set up an alias for the idle SPDY session, increasing its ref count to 2. |
| SpdySessionKey key2(HostPortPair("mail.example.org", 80), |
| ProxyServer::Direct(), PRIVACY_MODE_DISABLED, |
| SocketTag()); |
| HostResolver::RequestInfo info(key2.host_port_pair()); |
| AddressList addresses; |
| std::unique_ptr<HostResolver::Request> request; |
| // Pre-populate the DNS cache, since a cached entry is required in order to |
| // create the alias. |
| int rv = session_deps_.host_resolver->Resolve( |
| info, DEFAULT_PRIORITY, &addresses, CompletionOnceCallback(), &request, |
| NetLogWithSource()); |
| EXPECT_THAT(rv, IsOk()); |
| |
| // Get a session for |key2|, which should return the session created earlier. |
| base::WeakPtr<SpdySession> session2 = |
| spdy_session_pool_->FindAvailableSession( |
| key2, /* enable_ip_based_pooling = */ true, |
| /* is_websocket = */ false, NetLogWithSource()); |
| ASSERT_EQ(session1.get(), session2.get()); |
| EXPECT_FALSE(pool->IsStalled()); |
| |
| // Trying to create a new connection should cause the pool to be stalled, and |
| // post a task asynchronously to try and close the session. |
| TestCompletionCallback callback3; |
| HostPortPair host_port3("3.com", 80); |
| scoped_refptr<TransportSocketParams> params3(new TransportSocketParams( |
| host_port3, false, OnHostResolutionCallback(), |
| TransportSocketParams::COMBINE_CONNECT_AND_WRITE_DEFAULT)); |
| auto connection3 = std::make_unique<ClientSocketHandle>(); |
| EXPECT_EQ( |
| ERR_IO_PENDING, |
| connection3->Init(host_port3.ToString(), params3, DEFAULT_PRIORITY, |
| SocketTag(), ClientSocketPool::RespectLimits::ENABLED, |
| callback3.callback(), pool, NetLogWithSource())); |
| EXPECT_TRUE(pool->IsStalled()); |
| |
| // The socket pool should close the connection asynchronously and establish a |
| // new connection. |
| EXPECT_THAT(callback3.WaitForResult(), IsOk()); |
| EXPECT_FALSE(pool->IsStalled()); |
| EXPECT_FALSE(session1); |
| EXPECT_FALSE(session2); |
| } |
| |
| // Tests that when a SPDY session becomes idle, it closes itself if there is |
| // a lower layer pool stalled on the per-pool socket limit. |
| TEST_F(SpdySessionTest, CloseSessionOnIdleWhenPoolStalled) { |
| ClientSocketPoolManager::set_max_sockets_per_group( |
| HttpNetworkSession::NORMAL_SOCKET_POOL, 1); |
| ClientSocketPoolManager::set_max_sockets_per_pool( |
| HttpNetworkSession::NORMAL_SOCKET_POOL, 1); |
| |
| MockRead reads[] = { |
| MockRead(SYNCHRONOUS, ERR_IO_PENDING) // Stall forever. |
| }; |
| spdy::SpdySerializedFrame req1( |
| spdy_util_.ConstructSpdyGet(nullptr, 0, 1, LOWEST)); |
| spdy::SpdySerializedFrame cancel1( |
| spdy_util_.ConstructSpdyRstStream(1, spdy::ERROR_CODE_CANCEL)); |
| MockWrite writes[] = { |
| CreateMockWrite(req1, 1), CreateMockWrite(cancel1, 1), |
| }; |
| StaticSocketDataProvider data(reads, writes); |
| session_deps_.socket_factory->AddSocketDataProvider(&data); |
| |
| MockRead http_reads[] = { |
| MockRead(SYNCHRONOUS, ERR_IO_PENDING) // Stall forever. |
| }; |
| StaticSocketDataProvider http_data(http_reads, base::span<MockWrite>()); |
| session_deps_.socket_factory->AddSocketDataProvider(&http_data); |
| |
| AddSSLSocketData(); |
| |
| CreateNetworkSession(); |
| |
| TransportClientSocketPool* pool = |
| http_session_->GetTransportSocketPool( |
| HttpNetworkSession::NORMAL_SOCKET_POOL); |
| |
| // Create a SPDY session. |
| CreateSpdySession(); |
| EXPECT_FALSE(pool->IsStalled()); |
| |
| // Create a stream using the session, and send a request. |
| |
| TestCompletionCallback callback1; |
| base::WeakPtr<SpdyStream> spdy_stream1 = CreateStreamSynchronously( |
| SPDY_REQUEST_RESPONSE_STREAM, session_, test_url_, DEFAULT_PRIORITY, |
| NetLogWithSource()); |
| ASSERT_TRUE(spdy_stream1.get()); |
| test::StreamDelegateDoNothing delegate1(spdy_stream1); |
| spdy_stream1->SetDelegate(&delegate1); |
| |
| spdy::SpdyHeaderBlock headers1( |
| spdy_util_.ConstructGetHeaderBlock(kDefaultUrl)); |
| EXPECT_EQ(ERR_IO_PENDING, spdy_stream1->SendRequestHeaders( |
| std::move(headers1), NO_MORE_DATA_TO_SEND)); |
| |
| base::RunLoop().RunUntilIdle(); |
| |
| // Trying to create a new connection should cause the pool to be stalled, and |
| // post a task asynchronously to try and close the session. |
| TestCompletionCallback callback2; |
| HostPortPair host_port2("2.com", 80); |
| scoped_refptr<TransportSocketParams> params2(new TransportSocketParams( |
| host_port2, false, OnHostResolutionCallback(), |
| TransportSocketParams::COMBINE_CONNECT_AND_WRITE_DEFAULT)); |
| auto connection2 = std::make_unique<ClientSocketHandle>(); |
| EXPECT_EQ( |
| ERR_IO_PENDING, |
| connection2->Init(host_port2.ToString(), params2, DEFAULT_PRIORITY, |
| SocketTag(), ClientSocketPool::RespectLimits::ENABLED, |
| callback2.callback(), pool, NetLogWithSource())); |
| EXPECT_TRUE(pool->IsStalled()); |
| |
| // Running the message loop should cause the socket pool to ask the SPDY |
| // session to close an idle socket, but since the socket is in use, nothing |
| // happens. |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_TRUE(pool->IsStalled()); |
| EXPECT_FALSE(callback2.have_result()); |
| |
| // Cancelling the request should result in the session's socket being |
| // closed, since the pool is stalled. |
| ASSERT_TRUE(spdy_stream1.get()); |
| spdy_stream1->Cancel(ERR_ABORTED); |
| base::RunLoop().RunUntilIdle(); |
| ASSERT_FALSE(pool->IsStalled()); |
| EXPECT_THAT(callback2.WaitForResult(), IsOk()); |
| } |
| |
| // Verify that SpdySessionKey and therefore SpdySession is different when |
| // privacy mode is enabled or disabled. |
| TEST_F(SpdySessionTest, SpdySessionKeyPrivacyMode) { |
| CreateNetworkSession(); |
| |
| HostPortPair host_port_pair("www.example.org", 443); |
| SpdySessionKey key_privacy_enabled(host_port_pair, ProxyServer::Direct(), |
| PRIVACY_MODE_ENABLED, SocketTag()); |
| SpdySessionKey key_privacy_disabled(host_port_pair, ProxyServer::Direct(), |
| PRIVACY_MODE_DISABLED, SocketTag()); |
| |
| EXPECT_FALSE(HasSpdySession(spdy_session_pool_, key_privacy_enabled)); |
| EXPECT_FALSE(HasSpdySession(spdy_session_pool_, key_privacy_disabled)); |
| |
| // Add SpdySession with PrivacyMode Enabled to the pool. |
| base::WeakPtr<SpdySession> session_privacy_enabled = |
| CreateFakeSpdySession(spdy_session_pool_, key_privacy_enabled); |
| |
| EXPECT_TRUE(HasSpdySession(spdy_session_pool_, key_privacy_enabled)); |
| EXPECT_FALSE(HasSpdySession(spdy_session_pool_, key_privacy_disabled)); |
| |
| // Add SpdySession with PrivacyMode Disabled to the pool. |
| base::WeakPtr<SpdySession> session_privacy_disabled = |
| CreateFakeSpdySession(spdy_session_pool_, key_privacy_disabled); |
| |
| EXPECT_TRUE(HasSpdySession(spdy_session_pool_, key_privacy_enabled)); |
| EXPECT_TRUE(HasSpdySession(spdy_session_pool_, key_privacy_disabled)); |
| |
| session_privacy_enabled->CloseSessionOnError(ERR_ABORTED, std::string()); |
| EXPECT_FALSE(HasSpdySession(spdy_session_pool_, key_privacy_enabled)); |
| EXPECT_TRUE(HasSpdySession(spdy_session_pool_, key_privacy_disabled)); |
| |
| session_privacy_disabled->CloseSessionOnError(ERR_ABORTED, std::string()); |
| EXPECT_FALSE(HasSpdySession(spdy_session_pool_, key_privacy_enabled)); |
| EXPECT_FALSE(HasSpdySession(spdy_session_pool_, key_privacy_disabled)); |
| } |
| |
| // Delegate that creates another stream when its stream is closed. |
| class StreamCreatingDelegate : public test::StreamDelegateDoNothing { |
| public: |
| StreamCreatingDelegate(const base::WeakPtr<SpdyStream>& stream, |
| const base::WeakPtr<SpdySession>& session) |
| : StreamDelegateDoNothing(stream), |
| session_(session) {} |
| |
| ~StreamCreatingDelegate() override = default; |
| |
| void OnClose(int status) override { |
| GURL url(kDefaultUrl); |
| ignore_result(CreateStreamSynchronously(SPDY_REQUEST_RESPONSE_STREAM, |
| session_, url, MEDIUM, |
| NetLogWithSource())); |
| } |
| |
| private: |
| const base::WeakPtr<SpdySession> session_; |
| }; |
| |
| // Create another stream in response to a stream being reset. Nothing |
| // should blow up. This is a regression test for |
| // http://crbug.com/263690 . |
| TEST_F(SpdySessionTest, CreateStreamOnStreamReset) { |
| spdy::SpdySerializedFrame req( |
| spdy_util_.ConstructSpdyGet(nullptr, 0, 1, MEDIUM)); |
| MockWrite writes[] = { |
| CreateMockWrite(req, 0), |
| }; |
| |
| spdy::SpdySerializedFrame rst( |
| spdy_util_.ConstructSpdyRstStream(1, spdy::ERROR_CODE_REFUSED_STREAM)); |
| MockRead reads[] = { |
| MockRead(ASYNC, ERR_IO_PENDING, 1), CreateMockRead(rst, 2), |
| MockRead(ASYNC, ERR_IO_PENDING, 3), MockRead(ASYNC, 0, 4) // EOF |
| }; |
| SequencedSocketData data(reads, writes); |
| session_deps_.socket_factory->AddSocketDataProvider(&data); |
| |
| AddSSLSocketData(); |
| |
| CreateNetworkSession(); |
| CreateSpdySession(); |
| |
| base::WeakPtr<SpdyStream> spdy_stream = |
| CreateStreamSynchronously(SPDY_REQUEST_RESPONSE_STREAM, session_, |
| test_url_, MEDIUM, NetLogWithSource()); |
| ASSERT_TRUE(spdy_stream); |
| EXPECT_EQ(0u, spdy_stream->stream_id()); |
| |
| StreamCreatingDelegate delegate(spdy_stream, session_); |
| spdy_stream->SetDelegate(&delegate); |
| |
| spdy::SpdyHeaderBlock headers( |
| spdy_util_.ConstructGetHeaderBlock(kDefaultUrl)); |
| spdy_stream->SendRequestHeaders(std::move(headers), NO_MORE_DATA_TO_SEND); |
| |
| EXPECT_EQ(0u, spdy_stream->stream_id()); |
| |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_EQ(1u, spdy_stream->stream_id()); |
| |
| // Cause the stream to be reset, which should cause another stream |
| // to be created. |
| data.Resume(); |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_FALSE(spdy_stream); |
| EXPECT_TRUE(delegate.StreamIsClosed()); |
| EXPECT_EQ(0u, num_active_streams()); |
| EXPECT_EQ(1u, num_created_streams()); |
| |
| data.Resume(); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_FALSE(session_); |
| } |
| |
| TEST_F(SpdySessionTest, UpdateStreamsSendWindowSize) { |
| // Set spdy::SETTINGS_INITIAL_WINDOW_SIZE to a small number so that |
| // WINDOW_UPDATE gets sent. |
| spdy::SettingsMap new_settings; |
| int32_t window_size = 1; |
| new_settings[spdy::SETTINGS_INITIAL_WINDOW_SIZE] = window_size; |
| |
| // Set up the socket so we read a SETTINGS frame that sets |
| // INITIAL_WINDOW_SIZE. |
| spdy::SpdySerializedFrame settings_frame( |
| spdy_util_.ConstructSpdySettings(new_settings)); |
| MockRead reads[] = { |
| CreateMockRead(settings_frame, 0), MockRead(ASYNC, ERR_IO_PENDING, 1), |
| MockRead(ASYNC, 0, 2) // EOF |
| }; |
| |
| spdy::SpdySerializedFrame settings_ack(spdy_util_.ConstructSpdySettingsAck()); |
| MockWrite writes[] = { |
| CreateMockWrite(settings_ack, 3), |
| }; |
| |
| SequencedSocketData data(reads, writes); |
| session_deps_.socket_factory->AddSocketDataProvider(&data); |
| |
| AddSSLSocketData(); |
| |
| CreateNetworkSession(); |
| CreateSpdySession(); |
| base::WeakPtr<SpdyStream> spdy_stream1 = |
| CreateStreamSynchronously(SPDY_BIDIRECTIONAL_STREAM, session_, test_url_, |
| MEDIUM, NetLogWithSource()); |
| ASSERT_TRUE(spdy_stream1); |
| TestCompletionCallback callback1; |
| EXPECT_NE(spdy_stream1->send_window_size(), window_size); |
| |
| // Process the SETTINGS frame. |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_EQ(stream_initial_send_window_size(), window_size); |
| EXPECT_EQ(spdy_stream1->send_window_size(), window_size); |
| |
| // Release the first one, this will allow the second to be created. |
| spdy_stream1->Cancel(ERR_ABORTED); |
| EXPECT_FALSE(spdy_stream1); |
| |
| base::WeakPtr<SpdyStream> spdy_stream2 = |
| CreateStreamSynchronously(SPDY_BIDIRECTIONAL_STREAM, session_, test_url_, |
| MEDIUM, NetLogWithSource()); |
| ASSERT_TRUE(spdy_stream2); |
| EXPECT_EQ(spdy_stream2->send_window_size(), window_size); |
| spdy_stream2->Cancel(ERR_ABORTED); |
| EXPECT_FALSE(spdy_stream2); |
| |
| EXPECT_TRUE(session_); |
| data.Resume(); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_FALSE(session_); |
| } |
| |
| // SpdySession::{Increase,Decrease}RecvWindowSize should properly |
| // adjust the session receive window size. In addition, |
| // SpdySession::IncreaseRecvWindowSize should trigger |
| // sending a WINDOW_UPDATE frame for a large enough delta. |
| TEST_F(SpdySessionTest, AdjustRecvWindowSize) { |
| const int32_t initial_window_size = kDefaultInitialWindowSize; |
| const int32_t delta_window_size = 100; |
| |
| MockRead reads[] = { |
| MockRead(ASYNC, ERR_IO_PENDING, 1), MockRead(ASYNC, 0, 2) // EOF |
| }; |
| spdy::SpdySerializedFrame window_update(spdy_util_.ConstructSpdyWindowUpdate( |
| spdy::kSessionFlowControlStreamId, |
| initial_window_size + delta_window_size)); |
| MockWrite writes[] = { |
| CreateMockWrite(window_update, 0), |
| }; |
| SequencedSocketData data(reads, writes); |
| session_deps_.socket_factory->AddSocketDataProvider(&data); |
| |
| AddSSLSocketData(); |
| |
| CreateNetworkSession(); |
| CreateSpdySession(); |
| |
| EXPECT_EQ(initial_window_size, session_recv_window_size()); |
| EXPECT_EQ(0, session_unacked_recv_window_bytes()); |
| |
| IncreaseRecvWindowSize(delta_window_size); |
| EXPECT_EQ(initial_window_size + delta_window_size, |
| session_recv_window_size()); |
| EXPECT_EQ(delta_window_size, session_unacked_recv_window_bytes()); |
| |
| // Should trigger sending a WINDOW_UPDATE frame. |
| IncreaseRecvWindowSize(initial_window_size); |
| EXPECT_EQ(initial_window_size + delta_window_size + initial_window_size, |
| session_recv_window_size()); |
| EXPECT_EQ(0, session_unacked_recv_window_bytes()); |
| |
| base::RunLoop().RunUntilIdle(); |
| |
| // DecreaseRecvWindowSize() expects |in_io_loop_| to be true. |
| set_in_io_loop(true); |
| DecreaseRecvWindowSize(initial_window_size + delta_window_size + |
| initial_window_size); |
| set_in_io_loop(false); |
| EXPECT_EQ(0, session_recv_window_size()); |
| EXPECT_EQ(0, session_unacked_recv_window_bytes()); |
| |
| EXPECT_TRUE(session_); |
| data.Resume(); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_FALSE(session_); |
| } |
| |
| // SpdySession::{Increase,Decrease}SendWindowSize should properly |
| // adjust the session send window size when the "enable_spdy_31" flag |
| // is set. |
| TEST_F(SpdySessionTest, AdjustSendWindowSize) { |
| MockRead reads[] = { |
| MockRead(SYNCHRONOUS, 0, 0) // EOF |
| }; |
| StaticSocketDataProvider data(reads, base::span<MockWrite>()); |
| session_deps_.socket_factory->AddSocketDataProvider(&data); |
| |
| CreateNetworkSession(); |
| session_ = CreateFakeSpdySession(spdy_session_pool_, key_); |
| |
| const int32_t initial_window_size = kDefaultInitialWindowSize; |
| const int32_t delta_window_size = 100; |
| |
| EXPECT_EQ(initial_window_size, session_send_window_size()); |
| |
| IncreaseSendWindowSize(delta_window_size); |
| EXPECT_EQ(initial_window_size + delta_window_size, |
| session_send_window_size()); |
| |
| DecreaseSendWindowSize(delta_window_size); |
| EXPECT_EQ(initial_window_size, session_send_window_size()); |
| } |
| |
| // Incoming data for an inactive stream should not cause the session |
| // receive window size to decrease, but it should cause the unacked |
| // bytes to increase. |
| TEST_F(SpdySessionTest, SessionFlowControlInactiveStream) { |
| spdy::SpdySerializedFrame resp(spdy_util_.ConstructSpdyDataFrame(1, false)); |
| MockRead reads[] = { |
| CreateMockRead(resp, 0), MockRead(ASYNC, ERR_IO_PENDING, 1), |
| MockRead(ASYNC, 0, 2) // EOF |
| }; |
| SequencedSocketData data(reads, base::span<MockWrite>()); |
| session_deps_.socket_factory->AddSocketDataProvider(&data); |
| |
| AddSSLSocketData(); |
| |
| CreateNetworkSession(); |
| CreateSpdySession(); |
| |
| EXPECT_EQ(kDefaultInitialWindowSize, session_recv_window_size()); |
| EXPECT_EQ(0, session_unacked_recv_window_bytes()); |
| |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_EQ(kDefaultInitialWindowSize, session_recv_window_size()); |
| EXPECT_EQ(kUploadDataSize, session_unacked_recv_window_bytes()); |
| |
| EXPECT_TRUE(session_); |
| data.Resume(); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_FALSE(session_); |
| } |
| |
| // The frame header is not included in flow control, but frame payload |
| // (including optional pad length and padding) is. |
| TEST_F(SpdySessionTest, SessionFlowControlPadding) { |
| const int padding_length = 42; |
| spdy::SpdySerializedFrame resp( |
| spdy_util_.ConstructSpdyDataFrame(1, kUploadData, false, padding_length)); |
| MockRead reads[] = { |
| CreateMockRead(resp, 0), MockRead(ASYNC, ERR_IO_PENDING, 1), |
| MockRead(ASYNC, 0, 2) // EOF |
| }; |
| SequencedSocketData data(reads, base::span<MockWrite>()); |
| session_deps_.socket_factory->AddSocketDataProvider(&data); |
| |
| AddSSLSocketData(); |
| |
| CreateNetworkSession(); |
| CreateSpdySession(); |
| |
| EXPECT_EQ(kDefaultInitialWindowSize, session_recv_window_size()); |
| EXPECT_EQ(0, session_unacked_recv_window_bytes()); |
| |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_EQ(kDefaultInitialWindowSize, session_recv_window_size()); |
| EXPECT_EQ(kUploadDataSize + padding_length, |
| session_unacked_recv_window_bytes()); |
| |
| data.Resume(); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_FALSE(session_); |
| } |
| |
| // Peer sends more data than stream level receiving flow control window. |
| TEST_F(SpdySessionTest, StreamFlowControlTooMuchData) { |
| const int32_t stream_max_recv_window_size = 1024; |
| const int32_t data_frame_size = 2 * stream_max_recv_window_size; |
| |
| spdy::SpdySerializedFrame req( |
| spdy_util_.ConstructSpdyGet(nullptr, 0, 1, LOWEST)); |
| spdy::SpdySerializedFrame rst(spdy_util_.ConstructSpdyRstStream( |
| 1, spdy::ERROR_CODE_FLOW_CONTROL_ERROR)); |
| MockWrite writes[] = { |
| CreateMockWrite(req, 0), CreateMockWrite(rst, 4), |
| }; |
| |
| spdy::SpdySerializedFrame resp( |
| spdy_util_.ConstructSpdyGetReply(nullptr, 0, 1)); |
| const std::string payload(data_frame_size, 'a'); |
| spdy::SpdySerializedFrame data_frame( |
| spdy_util_.ConstructSpdyDataFrame(1, payload, false)); |
| MockRead reads[] = { |
| CreateMockRead(resp, 1), MockRead(ASYNC, ERR_IO_PENDING, 2), |
| CreateMockRead(data_frame, 3), MockRead(ASYNC, ERR_IO_PENDING, 5), |
| MockRead(ASYNC, 0, 6), |
| }; |
| |
| SequencedSocketData data(reads, writes); |
| session_deps_.socket_factory->AddSocketDataProvider(&data); |
| |
| AddSSLSocketData(); |
| |
| session_deps_.http2_settings[spdy::SETTINGS_INITIAL_WINDOW_SIZE] = |
| stream_max_recv_window_size; |
| CreateNetworkSession(); |
| |
| CreateSpdySession(); |
| |
| base::WeakPtr<SpdyStream> spdy_stream = |
| CreateStreamSynchronously(SPDY_REQUEST_RESPONSE_STREAM, session_, |
| test_url_, LOWEST, NetLogWithSource()); |
| EXPECT_EQ(stream_max_recv_window_size, spdy_stream->recv_window_size()); |
| |
| test::StreamDelegateDoNothing delegate(spdy_stream); |
| spdy_stream->SetDelegate(&delegate); |
| |
| spdy::SpdyHeaderBlock headers( |
| spdy_util_.ConstructGetHeaderBlock(kDefaultUrl)); |
| EXPECT_EQ(ERR_IO_PENDING, spdy_stream->SendRequestHeaders( |
| std::move(headers), NO_MORE_DATA_TO_SEND)); |
| |
| // Request and response. |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_EQ(1u, spdy_stream->stream_id()); |
| |
| // Too large data frame causes flow control error, should close stream. |
| data.Resume(); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_FALSE(spdy_stream); |
| |
| EXPECT_TRUE(session_); |
| data.Resume(); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_FALSE(session_); |
| } |
| |
| // Regression test for a bug that was caused by including unsent WINDOW_UPDATE |
| // deltas in the receiving window size when checking incoming frames for flow |
| // control errors at session level. |
| TEST_F(SpdySessionTest, SessionFlowControlTooMuchDataTwoDataFrames) { |
| const int32_t session_max_recv_window_size = 500; |
| const int32_t first_data_frame_size = 200; |
| const int32_t second_data_frame_size = 400; |
| |
| // First data frame should not trigger a WINDOW_UPDATE. |
| ASSERT_GT(session_max_recv_window_size / 2, first_data_frame_size); |
| // Second data frame would be fine had there been a WINDOW_UPDATE. |
| ASSERT_GT(session_max_recv_window_size, second_data_frame_size); |
| // But in fact, the two data frames together overflow the receiving window at |
| // session level. |
| ASSERT_LT(session_max_recv_window_size, |
| first_data_frame_size + second_data_frame_size); |
| |
| spdy::SpdySerializedFrame goaway(spdy_util_.ConstructSpdyGoAway( |
| 0, spdy::ERROR_CODE_FLOW_CONTROL_ERROR, |
| "delta_window_size is 400 in DecreaseRecvWindowSize, which is larger " |
| "than the receive window size of 500")); |
| MockWrite writes[] = { |
| CreateMockWrite(goaway, 4), |
| }; |
| |
| const std::string first_data_frame(first_data_frame_size, 'a'); |
| spdy::SpdySerializedFrame first( |
| spdy_util_.ConstructSpdyDataFrame(1, first_data_frame, false)); |
| const std::string second_data_frame(second_data_frame_size, 'b'); |
| spdy::SpdySerializedFrame second( |
| spdy_util_.ConstructSpdyDataFrame(1, second_data_frame, false)); |
| MockRead reads[] = { |
| CreateMockRead(first, 0), MockRead(ASYNC, ERR_IO_PENDING, 1), |
| CreateMockRead(second, 2), MockRead(ASYNC, 0, 3), |
| }; |
| SequencedSocketData data(reads, writes); |
| session_deps_.socket_factory->AddSocketDataProvider(&data); |
| |
| AddSSLSocketData(); |
| |
| CreateNetworkSession(); |
| CreateSpdySession(); |
| // Setting session level receiving window size to smaller than initial is not |
| // possible via SpdySessionPoolPeer. |
| set_session_recv_window_size(session_max_recv_window_size); |
| |
| // First data frame is immediately consumed and does not trigger |
| // WINDOW_UPDATE. |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_EQ(first_data_frame_size, session_unacked_recv_window_bytes()); |
| EXPECT_EQ(session_max_recv_window_size, session_recv_window_size()); |
| EXPECT_TRUE(session_->IsAvailable()); |
| |
| // Second data frame overflows receiving window, causes session to close. |
| data.Resume(); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_TRUE(session_->IsDraining()); |
| } |
| |
| // Regression test for a bug that was caused by including unsent WINDOW_UPDATE |
| // deltas in the receiving window size when checking incoming data frames for |
| // flow control errors at stream level. |
| TEST_F(SpdySessionTest, StreamFlowControlTooMuchDataTwoDataFrames) { |
| const int32_t stream_max_recv_window_size = 500; |
| const int32_t first_data_frame_size = 200; |
| const int32_t second_data_frame_size = 400; |
| |
| // First data frame should not trigger a WINDOW_UPDATE. |
| ASSERT_GT(stream_max_recv_window_size / 2, first_data_frame_size); |
| // Second data frame would be fine had there been a WINDOW_UPDATE. |
| ASSERT_GT(stream_max_recv_window_size, second_data_frame_size); |
| // But in fact, they should overflow the receiving window at stream level. |
| ASSERT_LT(stream_max_recv_window_size, |
| first_data_frame_size + second_data_frame_size); |
| |
| spdy::SpdySerializedFrame req( |
| spdy_util_.ConstructSpdyGet(nullptr, 0, 1, LOWEST)); |
| spdy::SpdySerializedFrame rst(spdy_util_.ConstructSpdyRstStream( |
| 1, spdy::ERROR_CODE_FLOW_CONTROL_ERROR)); |
| MockWrite writes[] = { |
| CreateMockWrite(req, 0), CreateMockWrite(rst, 6), |
| }; |
| |
| spdy::SpdySerializedFrame resp( |
| spdy_util_.ConstructSpdyGetReply(nullptr, 0, 1)); |
| const std::string first_data_frame(first_data_frame_size, 'a'); |
| spdy::SpdySerializedFrame first( |
| spdy_util_.ConstructSpdyDataFrame(1, first_data_frame, false)); |
| const std::string second_data_frame(second_data_frame_size, 'b'); |
| spdy::SpdySerializedFrame second( |
| spdy_util_.ConstructSpdyDataFrame(1, second_data_frame, false)); |
| MockRead reads[] = { |
| CreateMockRead(resp, 1), MockRead(ASYNC, ERR_IO_PENDING, 2), |
| CreateMockRead(first, 3), MockRead(ASYNC, ERR_IO_PENDING, 4), |
| CreateMockRead(second, 5), MockRead(ASYNC, ERR_IO_PENDING, 7), |
| MockRead(ASYNC, 0, 8), |
| }; |
| |
| SequencedSocketData data(reads, writes); |
| session_deps_.socket_factory->AddSocketDataProvider(&data); |
| |
| AddSSLSocketData(); |
| |
| session_deps_.http2_settings[spdy::SETTINGS_INITIAL_WINDOW_SIZE] = |
| stream_max_recv_window_size; |
| CreateNetworkSession(); |
| |
| CreateSpdySession(); |
| |
| base::WeakPtr<SpdyStream> spdy_stream = |
| CreateStreamSynchronously(SPDY_REQUEST_RESPONSE_STREAM, session_, |
| test_url_, LOWEST, NetLogWithSource()); |
| test::StreamDelegateDoNothing delegate(spdy_stream); |
| spdy_stream->SetDelegate(&delegate); |
| |
| spdy::SpdyHeaderBlock headers( |
| spdy_util_.ConstructGetHeaderBlock(kDefaultUrl)); |
| EXPECT_EQ(ERR_IO_PENDING, spdy_stream->SendRequestHeaders( |
| std::move(headers), NO_MORE_DATA_TO_SEND)); |
| |
| // Request and response. |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_TRUE(spdy_stream->IsLocallyClosed()); |
| EXPECT_EQ(stream_max_recv_window_size, spdy_stream->recv_window_size()); |
| |
| // First data frame. |
| data.Resume(); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_TRUE(spdy_stream->IsLocallyClosed()); |
| EXPECT_EQ(stream_max_recv_window_size - first_data_frame_size, |
| spdy_stream->recv_window_size()); |
| |
| // Consume first data frame. This does not trigger a WINDOW_UPDATE. |
| std::string received_data = delegate.TakeReceivedData(); |
| EXPECT_EQ(static_cast<size_t>(first_data_frame_size), received_data.size()); |
| EXPECT_EQ(stream_max_recv_window_size, spdy_stream->recv_window_size()); |
| |
| // Second data frame overflows receiving window, causes the stream to close. |
| data.Resume(); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_FALSE(spdy_stream.get()); |
| |
| // RST_STREAM |
| EXPECT_TRUE(session_); |
| data.Resume(); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_FALSE(session_); |
| } |
| |
| // A delegate that drops any received data. |
| class DropReceivedDataDelegate : public test::StreamDelegateSendImmediate { |
| public: |
| DropReceivedDataDelegate(const base::WeakPtr<SpdyStream>& stream, |
| base::StringPiece data) |
| : StreamDelegateSendImmediate(stream, data) {} |
| |
| ~DropReceivedDataDelegate() override = default; |
| |
| // Drop any received data. |
| void OnDataReceived(std::unique_ptr<SpdyBuffer> buffer) override {} |
| }; |
| |
| // Send data back and forth but use a delegate that drops its received |
| // data. The receive window should still increase to its original |
| // value, i.e. we shouldn't "leak" receive window bytes. |
| TEST_F(SpdySessionTest, SessionFlowControlNoReceiveLeaks) { |
| const int32_t kMsgDataSize = 100; |
| const std::string msg_data(kMsgDataSize, 'a'); |
| |
| spdy::SpdySerializedFrame req(spdy_util_.ConstructSpdyPost( |
| kDefaultUrl, 1, kMsgDataSize, MEDIUM, nullptr, 0)); |
| spdy::SpdySerializedFrame msg( |
| spdy_util_.ConstructSpdyDataFrame(1, msg_data, false)); |
| MockWrite writes[] = { |
| CreateMockWrite(req, 0), CreateMockWrite(msg, 2), |
| }; |
| |
| spdy::SpdySerializedFrame resp( |
| spdy_util_.ConstructSpdyGetReply(nullptr, 0, 1)); |
| spdy::SpdySerializedFrame echo( |
| spdy_util_.ConstructSpdyDataFrame(1, msg_data, false)); |
| spdy::SpdySerializedFrame window_update(spdy_util_.ConstructSpdyWindowUpdate( |
| spdy::kSessionFlowControlStreamId, kMsgDataSize)); |
| MockRead reads[] = { |
| CreateMockRead(resp, 1), CreateMockRead(echo, 3), |
| MockRead(ASYNC, ERR_IO_PENDING, 4), MockRead(ASYNC, 0, 5) // EOF |
| }; |
| |
| // Create SpdySession and SpdyStream and send the request. |
| SequencedSocketData data(reads, writes); |
| session_deps_.socket_factory->AddSocketDataProvider(&data); |
| |
| AddSSLSocketData(); |
| |
| CreateNetworkSession(); |
| CreateSpdySession(); |
| |
| base::WeakPtr<SpdyStream> stream = |
| CreateStreamSynchronously(SPDY_BIDIRECTIONAL_STREAM, session_, test_url_, |
| MEDIUM, NetLogWithSource()); |
| ASSERT_TRUE(stream); |
| EXPECT_EQ(0u, stream->stream_id()); |
| |
| DropReceivedDataDelegate delegate(stream, msg_data); |
| stream->SetDelegate(&delegate); |
| |
| spdy::SpdyHeaderBlock headers( |
| spdy_util_.ConstructPostHeaderBlock(kDefaultUrl, kMsgDataSize)); |
| EXPECT_EQ(ERR_IO_PENDING, |
| stream->SendRequestHeaders(std::move(headers), MORE_DATA_TO_SEND)); |
| |
| const int32_t initial_window_size = kDefaultInitialWindowSize; |
| EXPECT_EQ(initial_window_size, session_recv_window_size()); |
| EXPECT_EQ(0, session_unacked_recv_window_bytes()); |
| |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_EQ(initial_window_size, session_recv_window_size()); |
| EXPECT_EQ(kMsgDataSize, session_unacked_recv_window_bytes()); |
| |
| stream->Close(); |
| EXPECT_FALSE(stream); |
| |
| EXPECT_THAT(delegate.WaitForClose(), IsOk()); |
| |
| EXPECT_EQ(initial_window_size, session_recv_window_size()); |
| EXPECT_EQ(kMsgDataSize, session_unacked_recv_window_bytes()); |
| |
| data.Resume(); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_FALSE(session_); |
| } |
| |
| // Send data back and forth but close the stream before its data frame |
| // can be written to the socket. The send window should then increase |
| // to its original value, i.e. we shouldn't "leak" send window bytes. |
| TEST_F(SpdySessionTest, SessionFlowControlNoSendLeaks) { |
| const int32_t kMsgDataSize = 100; |
| const std::string msg_data(kMsgDataSize, 'a'); |
| |
| spdy::SpdySerializedFrame req(spdy_util_.ConstructSpdyPost( |
| kDefaultUrl, 1, kMsgDataSize, MEDIUM, nullptr, 0)); |
| MockWrite writes[] = { |
| CreateMockWrite(req, 0), |
| }; |
| |
| spdy::SpdySerializedFrame resp( |
| spdy_util_.ConstructSpdyGetReply(nullptr, 0, 1)); |
| MockRead reads[] = { |
| MockRead(ASYNC, ERR_IO_PENDING, 1), CreateMockRead(resp, 2), |
| MockRead(ASYNC, 0, 3) // EOF |
| }; |
| |
| // Create SpdySession and SpdyStream and send the request. |
| SequencedSocketData data(reads, writes); |
| session_deps_.socket_factory->AddSocketDataProvider(&data); |
| |
| AddSSLSocketData(); |
| |
| CreateNetworkSession(); |
| CreateSpdySession(); |
| |
| base::WeakPtr<SpdyStream> stream = |
| CreateStreamSynchronously(SPDY_BIDIRECTIONAL_STREAM, session_, test_url_, |
| MEDIUM, NetLogWithSource()); |
| ASSERT_TRUE(stream); |
| EXPECT_EQ(0u, stream->stream_id()); |
| |
| test::StreamDelegateSendImmediate delegate(stream, msg_data); |
| stream->SetDelegate(&delegate); |
| |
| spdy::SpdyHeaderBlock headers( |
| spdy_util_.ConstructPostHeaderBlock(kDefaultUrl, kMsgDataSize)); |
| EXPECT_EQ(ERR_IO_PENDING, |
| stream->SendRequestHeaders(std::move(headers), MORE_DATA_TO_SEND)); |
| |
| const int32_t initial_window_size = kDefaultInitialWindowSize; |
| EXPECT_EQ(initial_window_size, session_send_window_size()); |
| |
| // Write request. |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_EQ(initial_window_size, session_send_window_size()); |
| |
| // Read response, but do not run the message loop, so that the body is not |
| // written to the socket. |
| data.Resume(); |
| |
| EXPECT_EQ(initial_window_size - kMsgDataSize, session_send_window_size()); |
| |
| // Closing the stream should increase the session's send window. |
| stream->Close(); |
| EXPECT_FALSE(stream); |
| |
| EXPECT_EQ(initial_window_size, session_send_window_size()); |
| |
| EXPECT_THAT(delegate.WaitForClose(), IsOk()); |
| |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_FALSE(session_); |
| |
| EXPECT_TRUE(data.AllWriteDataConsumed()); |
| EXPECT_TRUE(data.AllReadDataConsumed()); |
| } |
| |
| // Send data back and forth; the send and receive windows should |
| // change appropriately. |
| TEST_F(SpdySessionTest, SessionFlowControlEndToEnd) { |
| const int32_t kMsgDataSize = 100; |
| const std::string msg_data(kMsgDataSize, 'a'); |
| |
| spdy::SpdySerializedFrame req(spdy_util_.ConstructSpdyPost( |
| kDefaultUrl, 1, kMsgDataSize, MEDIUM, nullptr, 0)); |
| spdy::SpdySerializedFrame msg( |
| spdy_util_.ConstructSpdyDataFrame(1, msg_data, false)); |
| MockWrite writes[] = { |
| CreateMockWrite(req, 0), CreateMockWrite(msg, 2), |
| }; |
| |
| spdy::SpdySerializedFrame resp( |
| spdy_util_.ConstructSpdyGetReply(nullptr, 0, 1)); |
| spdy::SpdySerializedFrame echo( |
| spdy_util_.ConstructSpdyDataFrame(1, msg_data, false)); |
| spdy::SpdySerializedFrame window_update(spdy_util_.ConstructSpdyWindowUpdate( |
| spdy::kSessionFlowControlStreamId, kMsgDataSize)); |
| MockRead reads[] = { |
| CreateMockRead(resp, 1), |
| MockRead(ASYNC, ERR_IO_PENDING, 3), |
| CreateMockRead(echo, 4), |
| MockRead(ASYNC, ERR_IO_PENDING, 5), |
| CreateMockRead(window_update, 6), |
| MockRead(ASYNC, ERR_IO_PENDING, 7), |
| MockRead(ASYNC, 0, 8) // EOF |
| }; |
| |
| // Create SpdySession and SpdyStream and send the request. |
| SequencedSocketData data(reads, writes); |
| session_deps_.socket_factory->AddSocketDataProvider(&data); |
| |
| AddSSLSocketData(); |
| |
| CreateNetworkSession(); |
| CreateSpdySession(); |
| |
| base::WeakPtr<SpdyStream> stream = |
| CreateStreamSynchronously(SPDY_BIDIRECTIONAL_STREAM, session_, test_url_, |
| MEDIUM, NetLogWithSource()); |
| ASSERT_TRUE(stream); |
| EXPECT_EQ(0u, stream->stream_id()); |
| |
| test::StreamDelegateSendImmediate delegate(stream, msg_data); |
| stream->SetDelegate(&delegate); |
| |
| spdy::SpdyHeaderBlock headers( |
| spdy_util_.ConstructPostHeaderBlock(kDefaultUrl, kMsgDataSize)); |
| EXPECT_EQ(ERR_IO_PENDING, |
| stream->SendRequestHeaders(std::move(headers), MORE_DATA_TO_SEND)); |
| |
| const int32_t initial_window_size = kDefaultInitialWindowSize; |
| EXPECT_EQ(initial_window_size, session_send_window_size()); |
| EXPECT_EQ(initial_window_size, session_recv_window_size()); |
| EXPECT_EQ(0, session_unacked_recv_window_bytes()); |
| |
| // Send request and message. |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_EQ(initial_window_size - kMsgDataSize, session_send_window_size()); |
| EXPECT_EQ(initial_window_size, session_recv_window_size()); |
| EXPECT_EQ(0, session_unacked_recv_window_bytes()); |
| |
| // Read echo. |
| data.Resume(); |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_EQ(initial_window_size - kMsgDataSize, session_send_window_size()); |
| EXPECT_EQ(initial_window_size - kMsgDataSize, session_recv_window_size()); |
| EXPECT_EQ(0, session_unacked_recv_window_bytes()); |
| |
| // Read window update. |
| data.Resume(); |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_EQ(initial_window_size, session_send_window_size()); |
| EXPECT_EQ(initial_window_size - kMsgDataSize, session_recv_window_size()); |
| EXPECT_EQ(0, session_unacked_recv_window_bytes()); |
| |
| EXPECT_EQ(msg_data, delegate.TakeReceivedData()); |
| |
| // Draining the delegate's read queue should increase the session's |
| // receive window. |
| EXPECT_EQ(initial_window_size, session_send_window_size()); |
| EXPECT_EQ(initial_window_size, session_recv_window_size()); |
| EXPECT_EQ(kMsgDataSize, session_unacked_recv_window_bytes()); |
| |
| stream->Close(); |
| EXPECT_FALSE(stream); |
| |
| EXPECT_THAT(delegate.WaitForClose(), IsOk()); |
| |
| EXPECT_EQ(initial_window_size, session_send_window_size()); |
| EXPECT_EQ(initial_window_size, session_recv_window_size()); |
| EXPECT_EQ(kMsgDataSize, session_unacked_recv_window_bytes()); |
| |
| data.Resume(); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_FALSE(session_); |
| } |
| |
| // Given a stall function and an unstall function, runs a test to make |
| // sure that a stream resumes after unstall. |
| void SpdySessionTest::RunResumeAfterUnstallTest( |
| const base::Callback<void(SpdyStream*)>& stall_function, |
| const base::Callback<void(SpdyStream*, int32_t)>& unstall_function) { |
| spdy::SpdySerializedFrame req(spdy_util_.ConstructSpdyPost( |
| kDefaultUrl, 1, kBodyDataSize, LOWEST, nullptr, 0)); |
| spdy::SpdySerializedFrame body( |
| spdy_util_.ConstructSpdyDataFrame(1, kBodyDataStringPiece, true)); |
| MockWrite writes[] = { |
| CreateMockWrite(req, 0), CreateMockWrite(body, 1), |
| }; |
| |
| spdy::SpdySerializedFrame resp( |
| spdy_util_.ConstructSpdyGetReply(nullptr, 0, 1)); |
| spdy::SpdySerializedFrame echo( |
| spdy_util_.ConstructSpdyDataFrame(1, kBodyDataStringPiece, false)); |
| MockRead reads[] = { |
| CreateMockRead(resp, 2), MockRead(ASYNC, 0, 3) // EOF |
| }; |
| |
| SequencedSocketData data(reads, writes); |
| session_deps_.socket_factory->AddSocketDataProvider(&data); |
| |
| AddSSLSocketData(); |
| |
| CreateNetworkSession(); |
| CreateSpdySession(); |
| |
| base::WeakPtr<SpdyStream> stream = |
| CreateStreamSynchronously(SPDY_REQUEST_RESPONSE_STREAM, session_, |
| test_url_, LOWEST, NetLogWithSource()); |
| ASSERT_TRUE(stream); |
| |
| test::StreamDelegateWithBody delegate(stream, kBodyDataStringPiece); |
| stream->SetDelegate(&delegate); |
| |
| EXPECT_FALSE(stream->send_stalled_by_flow_control()); |
| |
| spdy::SpdyHeaderBlock headers( |
| spdy_util_.ConstructPostHeaderBlock(kDefaultUrl, kBodyDataSize)); |
| EXPECT_EQ(ERR_IO_PENDING, |
| stream->SendRequestHeaders(std::move(headers), MORE_DATA_TO_SEND)); |
| EXPECT_EQ(kDefaultUrl, stream->url().spec()); |
| |
| stall_function.Run(stream.get()); |
| |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_TRUE(stream->send_stalled_by_flow_control()); |
| |
| unstall_function.Run(stream.get(), kBodyDataSize); |
| |
| 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()); |
| |
| // Run SpdySession::PumpWriteLoop which destroys |session_|. |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_FALSE(session_); |
| EXPECT_TRUE(data.AllWriteDataConsumed()); |
| } |
| |
| // Run the resume-after-unstall test with all possible stall and |
| // unstall sequences. |
| |
| TEST_F(SpdySessionTest, ResumeAfterUnstallSession) { |
| RunResumeAfterUnstallTest( |
| base::Bind(&SpdySessionTest::StallSessionOnly, |
| base::Unretained(this)), |
| base::Bind(&SpdySessionTest::UnstallSessionOnly, |
| base::Unretained(this))); |
| } |
| |
| // Equivalent to |
| // SpdyStreamTest.ResumeAfterSendWindowSizeIncrease. |
| TEST_F(SpdySessionTest, ResumeAfterUnstallStream) { |
| RunResumeAfterUnstallTest( |
| base::Bind(&SpdySessionTest::StallStreamOnly, |
| base::Unretained(this)), |
| base::Bind(&SpdySessionTest::UnstallStreamOnly, |
| base::Unretained(this))); |
| } |
| |
| TEST_F(SpdySessionTest, StallSessionStreamResumeAfterUnstallSessionStream) { |
| RunResumeAfterUnstallTest( |
| base::Bind(&SpdySessionTest::StallSessionStream, |
| base::Unretained(this)), |
| base::Bind(&SpdySessionTest::UnstallSessionStream, |
| base::Unretained(this))); |
| } |
| |
| TEST_F(SpdySessionTest, StallStreamSessionResumeAfterUnstallSessionStream) { |
| RunResumeAfterUnstallTest( |
| base::Bind(&SpdySessionTest::StallStreamSession, |
| base::Unretained(this)), |
| base::Bind(&SpdySessionTest::UnstallSessionStream, |
| base::Unretained(this))); |
| } |
| |
| TEST_F(SpdySessionTest, StallStreamSessionResumeAfterUnstallStreamSession) { |
| RunResumeAfterUnstallTest( |
| base::Bind(&SpdySessionTest::StallStreamSession, |
| base::Unretained(this)), |
| base::Bind(&SpdySessionTest::UnstallStreamSession, |
| base::Unretained(this))); |
| } |
| |
| TEST_F(SpdySessionTest, StallSessionStreamResumeAfterUnstallStreamSession) { |
| RunResumeAfterUnstallTest( |
| base::Bind(&SpdySessionTest::StallSessionStream, |
| base::Unretained(this)), |
| base::Bind(&SpdySessionTest::UnstallStreamSession, |
| base::Unretained(this))); |
| } |
| |
| // Cause a stall by reducing the flow control send window to 0. The |
| // streams should resume in priority order when that window is then |
| // increased. |
| TEST_F(SpdySessionTest, ResumeByPriorityAfterSendWindowSizeIncrease) { |
| spdy::SpdySerializedFrame req1(spdy_util_.ConstructSpdyPost( |
| kDefaultUrl, 1, kBodyDataSize, LOWEST, nullptr, 0)); |
| spdy::SpdySerializedFrame req2(spdy_util_.ConstructSpdyPost( |
| kDefaultUrl, 3, kBodyDataSize, MEDIUM, nullptr, 0)); |
| spdy::SpdySerializedFrame body1( |
| spdy_util_.ConstructSpdyDataFrame(1, kBodyDataStringPiece, true)); |
| spdy::SpdySerializedFrame body2( |
| spdy_util_.ConstructSpdyDataFrame(3, kBodyDataStringPiece, true)); |
| MockWrite writes[] = { |
| CreateMockWrite(req1, 0), CreateMockWrite(req2, 1), |
| CreateMockWrite(body2, 2), CreateMockWrite(body1, 3), |
| }; |
| |
| spdy::SpdySerializedFrame resp1( |
| spdy_util_.ConstructSpdyGetReply(nullptr, 0, 1)); |
| spdy::SpdySerializedFrame resp2( |
| spdy_util_.ConstructSpdyGetReply(nullptr, 0, 3)); |
| MockRead reads[] = { |
| CreateMockRead(resp1, 4), CreateMockRead(resp2, 5), |
| MockRead(ASYNC, 0, 6) // EOF |
| }; |
| |
| SequencedSocketData data(reads, writes); |
| session_deps_.socket_factory->AddSocketDataProvider(&data); |
| |
| AddSSLSocketData(); |
| |
| CreateNetworkSession(); |
| CreateSpdySession(); |
| |
| base::WeakPtr<SpdyStream> stream1 = |
| CreateStreamSynchronously(SPDY_REQUEST_RESPONSE_STREAM, session_, |
| test_url_, LOWEST, NetLogWithSource()); |
| ASSERT_TRUE(stream1); |
| |
| test::StreamDelegateWithBody delegate1(stream1, kBodyDataStringPiece); |
| stream1->SetDelegate(&delegate1); |
| |
| base::WeakPtr<SpdyStream> stream2 = |
| CreateStreamSynchronously(SPDY_REQUEST_RESPONSE_STREAM, session_, |
| test_url_, MEDIUM, NetLogWithSource()); |
| ASSERT_TRUE(stream2); |
| |
| test::StreamDelegateWithBody delegate2(stream2, kBodyDataStringPiece); |
| stream2->SetDelegate(&delegate2); |
| |
| EXPECT_FALSE(stream1->send_stalled_by_flow_control()); |
| EXPECT_FALSE(stream2->send_stalled_by_flow_control()); |
| |
| StallSessionSend(); |
| |
| spdy::SpdyHeaderBlock headers1( |
| spdy_util_.ConstructPostHeaderBlock(kDefaultUrl, kBodyDataSize)); |
| EXPECT_EQ(ERR_IO_PENDING, stream1->SendRequestHeaders(std::move(headers1), |
| MORE_DATA_TO_SEND)); |
| EXPECT_EQ(kDefaultUrl, stream1->url().spec()); |
| |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_EQ(1u, stream1->stream_id()); |
| EXPECT_TRUE(stream1->send_stalled_by_flow_control()); |
| |
| spdy::SpdyHeaderBlock headers2( |
| spdy_util_.ConstructPostHeaderBlock(kDefaultUrl, kBodyDataSize)); |
| EXPECT_EQ(ERR_IO_PENDING, stream2->SendRequestHeaders(std::move(headers2), |
| MORE_DATA_TO_SEND)); |
| EXPECT_EQ(kDefaultUrl, stream2->url().spec()); |
| |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_EQ(3u, stream2->stream_id()); |
| EXPECT_TRUE(stream2->send_stalled_by_flow_control()); |
| |
| // This should unstall only stream2. |
| UnstallSessionSend(kBodyDataSize); |
| |
| EXPECT_TRUE(stream1->send_stalled_by_flow_control()); |
| EXPECT_FALSE(stream2->send_stalled_by_flow_control()); |
| |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_TRUE(stream1->send_stalled_by_flow_control()); |
| EXPECT_FALSE(stream2->send_stalled_by_flow_control()); |
| |
| // This should then unstall stream1. |
| UnstallSessionSend(kBodyDataSize); |
| |
| EXPECT_FALSE(stream1->send_stalled_by_flow_control()); |
| EXPECT_FALSE(stream2->send_stalled_by_flow_control()); |
| |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_THAT(delegate1.WaitForClose(), IsError(ERR_CONNECTION_CLOSED)); |
| EXPECT_THAT(delegate2.WaitForClose(), IsError(ERR_CONNECTION_CLOSED)); |
| |
| EXPECT_TRUE(delegate1.send_headers_completed()); |
| EXPECT_EQ("200", delegate1.GetResponseHeaderValue(":status")); |
| EXPECT_EQ(std::string(), delegate1.TakeReceivedData()); |
| |
| EXPECT_TRUE(delegate2.send_headers_completed()); |
| EXPECT_EQ("200", delegate2.GetResponseHeaderValue(":status")); |
| EXPECT_EQ(std::string(), delegate2.TakeReceivedData()); |
| |
| EXPECT_FALSE(session_); |
| EXPECT_TRUE(data.AllWriteDataConsumed()); |
| EXPECT_TRUE(data.AllReadDataConsumed()); |
| } |
| |
| // An upload stream is stalled when the session gets unstalled, then the session |
| // is stalled again when the stream gets unstalled. The stream should not fail. |
| // Regression test for https://crbug.com/761919. |
| TEST_F(SpdySessionTest, ResumeSessionWithStalledStream) { |
| spdy::SpdySerializedFrame req1(spdy_util_.ConstructSpdyPost( |
| kDefaultUrl, 1, kBodyDataSize, LOWEST, nullptr, 0)); |
| spdy::SpdySerializedFrame req2(spdy_util_.ConstructSpdyPost( |
| kDefaultUrl, 3, kBodyDataSize, LOWEST, nullptr, 0)); |
| spdy::SpdySerializedFrame body1( |
| spdy_util_.ConstructSpdyDataFrame(3, kBodyDataStringPiece, true)); |
| spdy::SpdySerializedFrame body2( |
| spdy_util_.ConstructSpdyDataFrame(1, kBodyDataStringPiece, true)); |
| MockWrite writes[] = {CreateMockWrite(req1, 0), CreateMockWrite(req2, 1), |
| CreateMockWrite(body1, 2), CreateMockWrite(body2, 3)}; |
| |
| spdy::SpdySerializedFrame resp1( |
| spdy_util_.ConstructSpdyGetReply(nullptr, 0, 1)); |
| spdy::SpdySerializedFrame resp2( |
| spdy_util_.ConstructSpdyGetReply(nullptr, 0, 3)); |
| MockRead reads[] = {CreateMockRead(resp1, 4), CreateMockRead(resp2, 5), |
| MockRead(ASYNC, 0, 6)}; |
| |
| SequencedSocketData data(reads, writes); |
| session_deps_.socket_factory->AddSocketDataProvider(&data); |
| |
| AddSSLSocketData(); |
| |
| CreateNetworkSession(); |
| CreateSpdySession(); |
| |
| base::WeakPtr<SpdyStream> stream1 = |
| CreateStreamSynchronously(SPDY_REQUEST_RESPONSE_STREAM, session_, |
| test_url_, LOWEST, NetLogWithSource()); |
| ASSERT_TRUE(stream1); |
| |
| test::StreamDelegateWithBody delegate1(stream1, kBodyDataStringPiece); |
| stream1->SetDelegate(&delegate1); |
| |
| base::WeakPtr<SpdyStream> stream2 = |
| CreateStreamSynchronously(SPDY_REQUEST_RESPONSE_STREAM, session_, |
| test_url_, LOWEST, NetLogWithSource()); |
| ASSERT_TRUE(stream2); |
| |
| test::StreamDelegateWithBody delegate2(stream2, kBodyDataStringPiece); |
| stream2->SetDelegate(&delegate2); |
| |
| EXPECT_FALSE(stream1->send_stalled_by_flow_control()); |
| EXPECT_FALSE(stream2->send_stalled_by_flow_control()); |
| |
| StallSessionSend(); |
| |
| spdy::SpdyHeaderBlock headers1( |
| spdy_util_.ConstructPostHeaderBlock(kDefaultUrl, kBodyDataSize)); |
| EXPECT_EQ(ERR_IO_PENDING, stream1->SendRequestHeaders(std::move(headers1), |
| MORE_DATA_TO_SEND)); |
| EXPECT_EQ(kDefaultUrl, stream1->url().spec()); |
| |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_EQ(1u, stream1->stream_id()); |
| EXPECT_TRUE(stream1->send_stalled_by_flow_control()); |
| |
| spdy::SpdyHeaderBlock headers2( |
| spdy_util_.ConstructPostHeaderBlock(kDefaultUrl, kBodyDataSize)); |
| EXPECT_EQ(ERR_IO_PENDING, stream2->SendRequestHeaders(std::move(headers2), |
| MORE_DATA_TO_SEND)); |
| EXPECT_EQ(kDefaultUrl, stream2->url().spec()); |
| |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_EQ(3u, stream2->stream_id()); |
| EXPECT_TRUE(stream2->send_stalled_by_flow_control()); |
| |
| StallStreamSend(stream1.get()); |
| |
| // At this point, both |session| and |stream1| are stalled |
| // by their respective flow control mechanisms. Now unstall the session. |
| // This calls session->ResumeSendStalledStreams(), which calls |
| // stream1->PossiblyResumeIfSendStalled(). However, |stream1| is stalled, so |
| // no data are sent on that stream. At this point, |stream1| should not be |
| // removed from session_->stream_send_unstall_queue_. |
| // Then stream2->PossiblyResumeIfSendStalled() is called, |
| // data are sent on |stream2|, and |session_| stalls again. |
| UnstallSessionSend(kBodyDataSize); |
| |
| EXPECT_TRUE(stream1->send_stalled_by_flow_control()); |
| EXPECT_FALSE(stream2->send_stalled_by_flow_control()); |
| |
| // Make sure that the session is stalled. Otherwise |
| // stream1->PossiblyResumeIfSendStalled() would resume the stream as soon as |
| // the stream is unstalled, hiding the bug. |
| EXPECT_TRUE(session_->IsSendStalled()); |
| UnstallStreamSend(stream1.get(), kBodyDataSize); |
| |
| // Finally, unstall session. |
| UnstallSessionSend(kBodyDataSize); |
| |
| EXPECT_FALSE(stream1->send_stalled_by_flow_control()); |
| EXPECT_FALSE(stream2->send_stalled_by_flow_control()); |
| |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_THAT(delegate1.WaitForClose(), IsError(ERR_CONNECTION_CLOSED)); |
| EXPECT_THAT(delegate2.WaitForClose(), IsError(ERR_CONNECTION_CLOSED)); |
| |
| EXPECT_TRUE(delegate1.send_headers_completed()); |
| EXPECT_EQ("200", delegate1.GetResponseHeaderValue(":status")); |
| EXPECT_EQ(std::string(), delegate1.TakeReceivedData()); |
| |
| EXPECT_TRUE(delegate2.send_headers_completed()); |
| EXPECT_EQ("200", delegate2.GetResponseHeaderValue(":status")); |
| EXPECT_EQ(std::string(), delegate2.TakeReceivedData()); |
| |
| EXPECT_FALSE(session_); |
| EXPECT_TRUE(data.AllWriteDataConsumed()); |
| EXPECT_TRUE(data.AllReadDataConsumed()); |
| } |
| |
| // Delegate that closes a given stream after sending its body. |
| class StreamClosingDelegate : public test::StreamDelegateWithBody { |
| public: |
| StreamClosingDelegate(const base::WeakPtr<SpdyStream>& stream, |
| base::StringPiece data) |
| : StreamDelegateWithBody(stream, data) {} |
| |
| ~StreamClosingDelegate() override = default; |
| |
| void set_stream_to_close(const base::WeakPtr<SpdyStream>& stream_to_close) { |
| stream_to_close_ = stream_to_close; |
| } |
| |
| void OnDataSent() override { |
| test::StreamDelegateWithBody::OnDataSent(); |
| if (stream_to_close_.get()) { |
| stream_to_close_->Close(); |
| EXPECT_FALSE(stream_to_close_); |
| } |
| } |
| |
| private: |
| base::WeakPtr<SpdyStream> stream_to_close_; |
| }; |
| |
| // Cause a stall by reducing the flow control send window to |
| // 0. Unstalling the session should properly handle deleted streams. |
| TEST_F(SpdySessionTest, SendWindowSizeIncreaseWithDeletedStreams) { |
| spdy::SpdySerializedFrame req1(spdy_util_.ConstructSpdyPost( |
| kDefaultUrl, 1, kBodyDataSize, LOWEST, nullptr, 0)); |
| spdy::SpdySerializedFrame req2(spdy_util_.ConstructSpdyPost( |
| kDefaultUrl, 3, kBodyDataSize, LOWEST, nullptr, 0)); |
| spdy::SpdySerializedFrame req3(spdy_util_.ConstructSpdyPost( |
| kDefaultUrl, 5, kBodyDataSize, LOWEST, nullptr, 0)); |
| spdy::SpdySerializedFrame body2( |
| spdy_util_.ConstructSpdyDataFrame(3, kBodyDataStringPiece, true)); |
| MockWrite writes[] = { |
| CreateMockWrite(req1, 0), CreateMockWrite(req2, 1), |
| CreateMockWrite(req3, 2), CreateMockWrite(body2, 3), |
| }; |
| |
| spdy::SpdySerializedFrame resp2( |
| spdy_util_.ConstructSpdyGetReply(nullptr, 0, 3)); |
| MockRead reads[] = { |
| CreateMockRead(resp2, 4), MockRead(ASYNC, ERR_IO_PENDING, 5), |
| MockRead(ASYNC, 0, 6) // EOF |
| }; |
| |
| SequencedSocketData data(reads, writes); |
| session_deps_.socket_factory->AddSocketDataProvider(&data); |
| |
| AddSSLSocketData(); |
| |
| CreateNetworkSession(); |
| CreateSpdySession(); |
| |
| base::WeakPtr<SpdyStream> stream1 = |
| CreateStreamSynchronously(SPDY_REQUEST_RESPONSE_STREAM, session_, |
| test_url_, LOWEST, NetLogWithSource()); |
| ASSERT_TRUE(stream1); |
| |
| test::StreamDelegateWithBody delegate1(stream1, kBodyDataStringPiece); |
| stream1->SetDelegate(&delegate1); |
| |
| base::WeakPtr<SpdyStream> stream2 = |
| CreateStreamSynchronously(SPDY_REQUEST_RESPONSE_STREAM, session_, |
| test_url_, LOWEST, NetLogWithSource()); |
| ASSERT_TRUE(stream2); |
| |
| StreamClosingDelegate delegate2(stream2, kBodyDataStringPiece); |
| stream2->SetDelegate(&delegate2); |
| |
| base::WeakPtr<SpdyStream> stream3 = |
| CreateStreamSynchronously(SPDY_REQUEST_RESPONSE_STREAM, session_, |
| test_url_, LOWEST, NetLogWithSource()); |
| ASSERT_TRUE(stream3); |
| |
| test::StreamDelegateWithBody delegate3(stream3, kBodyDataStringPiece); |
| stream3->SetDelegate(&delegate3); |
| |
| EXPECT_FALSE(stream1->send_stalled_by_flow_control()); |
| EXPECT_FALSE(stream2->send_stalled_by_flow_control()); |
| EXPECT_FALSE(stream3->send_stalled_by_flow_control()); |
| |
| StallSessionSend(); |
| |
| spdy::SpdyHeaderBlock headers1( |
| spdy_util_.ConstructPostHeaderBlock(kDefaultUrl, kBodyDataSize)); |
| EXPECT_EQ(ERR_IO_PENDING, stream1->SendRequestHeaders(std::move(headers1), |
| MORE_DATA_TO_SEND)); |
| EXPECT_EQ(kDefaultUrl, stream1->url().spec()); |
| |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_EQ(1u, stream1->stream_id()); |
| EXPECT_TRUE(stream1->send_stalled_by_flow_control()); |
| |
| spdy::SpdyHeaderBlock headers2( |
| spdy_util_.ConstructPostHeaderBlock(kDefaultUrl, kBodyDataSize)); |
| EXPECT_EQ(ERR_IO_PENDING, stream2->SendRequestHeaders(std::move(headers2), |
| MORE_DATA_TO_SEND)); |
| EXPECT_EQ(kDefaultUrl, stream2->url().spec()); |
| |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_EQ(3u, stream2->stream_id()); |
| EXPECT_TRUE(stream2->send_stalled_by_flow_control()); |
| |
| spdy::SpdyHeaderBlock headers3( |
| spdy_util_.ConstructPostHeaderBlock(kDefaultUrl, kBodyDataSize)); |
| EXPECT_EQ(ERR_IO_PENDING, stream3->SendRequestHeaders(std::move(headers3), |
| MORE_DATA_TO_SEND)); |
| EXPECT_EQ(kDefaultUrl, stream3->url().spec()); |
| |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_EQ(5u, stream3->stream_id()); |
| EXPECT_TRUE(stream3->send_stalled_by_flow_control()); |
| |
| spdy::SpdyStreamId stream_id1 = stream1->stream_id(); |
| spdy::SpdyStreamId stream_id2 = stream2->stream_id(); |
| spdy::SpdyStreamId stream_id3 = stream3->stream_id(); |
| |
| // Close stream1 preemptively. |
| session_->CloseActiveStream(stream_id1, ERR_CONNECTION_CLOSED); |
| EXPECT_FALSE(stream1); |
| |
| EXPECT_FALSE(session_->IsStreamActive(stream_id1)); |
| EXPECT_TRUE(session_->IsStreamActive(stream_id2)); |
| EXPECT_TRUE(session_->IsStreamActive(stream_id3)); |
| |
| // Unstall stream2, which should then close stream3. |
| delegate2.set_stream_to_close(stream3); |
| UnstallSessionSend(kBodyDataSize); |
| |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_FALSE(stream3); |
| |
| EXPECT_FALSE(stream2->send_stalled_by_flow_control()); |
| EXPECT_FALSE(session_->IsStreamActive(stream_id1)); |
| EXPECT_TRUE(session_->IsStreamActive(stream_id2)); |
| EXPECT_FALSE(session_->IsStreamActive(stream_id3)); |
| |
| data.Resume(); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_FALSE(stream2); |
| EXPECT_FALSE(session_); |
| |
| EXPECT_THAT(delegate1.WaitForClose(), IsError(ERR_CONNECTION_CLOSED)); |
| EXPECT_THAT(delegate2.WaitForClose(), IsError(ERR_CONNECTION_CLOSED)); |
| EXPECT_THAT(delegate3.WaitForClose(), IsOk()); |
| |
| EXPECT_TRUE(delegate1.send_headers_completed()); |
| EXPECT_EQ(std::string(), delegate1.TakeReceivedData()); |
| |
| EXPECT_TRUE(delegate2.send_headers_completed()); |
| EXPECT_EQ("200", delegate2.GetResponseHeaderValue(":status")); |
| EXPECT_EQ(std::string(), delegate2.TakeReceivedData()); |
| |
| EXPECT_TRUE(delegate3.send_headers_completed()); |
| EXPECT_EQ(std::string(), delegate3.TakeReceivedData()); |
| |
| EXPECT_TRUE(data.AllWriteDataConsumed()); |
| } |
| |
| // Cause a stall by reducing the flow control send window to |
| // 0. Unstalling the session should properly handle the session itself |
| // being closed. |
| TEST_F(SpdySessionTest, SendWindowSizeIncreaseWithDeletedSession) { |
| spdy::SpdySerializedFrame req1(spdy_util_.ConstructSpdyPost( |
| kDefaultUrl, 1, kBodyDataSize, LOWEST, nullptr, 0)); |
| spdy::SpdySerializedFrame req2(spdy_util_.ConstructSpdyPost( |
| kDefaultUrl, 3, kBodyDataSize, LOWEST, nullptr, 0)); |
| spdy::SpdySerializedFrame body1( |
| spdy_util_.ConstructSpdyDataFrame(1, kBodyDataStringPiece, false)); |
| MockWrite writes[] = { |
| CreateMockWrite(req1, 0), CreateMockWrite(req2, 1), |
| }; |
| |
| MockRead reads[] = { |
| MockRead(ASYNC, ERR_IO_PENDING, 2), MockRead(ASYNC, 0, 3) // EOF |
| }; |
| |
| SequencedSocketData data(reads, writes); |
| session_deps_.socket_factory->AddSocketDataProvider(&data); |
| |
| AddSSLSocketData(); |
| |
| CreateNetworkSession(); |
| CreateSpdySession(); |
| |
| base::WeakPtr<SpdyStream> stream1 = |
| CreateStreamSynchronously(SPDY_REQUEST_RESPONSE_STREAM, session_, |
| test_url_, LOWEST, NetLogWithSource()); |
| ASSERT_TRUE(stream1); |
| |
| test::StreamDelegateWithBody delegate1(stream1, kBodyDataStringPiece); |
| stream1->SetDelegate(&delegate1); |
| |
| base::WeakPtr<SpdyStream> stream2 = |
| CreateStreamSynchronously(SPDY_REQUEST_RESPONSE_STREAM, session_, |
| test_url_, LOWEST, NetLogWithSource()); |
| ASSERT_TRUE(stream2); |
| |
| test::StreamDelegateWithBody delegate2(stream2, kBodyDataStringPiece); |
| stream2->SetDelegate(&delegate2); |
| |
| EXPECT_FALSE(stream1->send_stalled_by_flow_control()); |
| EXPECT_FALSE(stream2->send_stalled_by_flow_control()); |
| |
| StallSessionSend(); |
| |
| spdy::SpdyHeaderBlock headers1( |
| spdy_util_.ConstructPostHeaderBlock(kDefaultUrl, kBodyDataSize)); |
| EXPECT_EQ(ERR_IO_PENDING, stream1->SendRequestHeaders(std::move(headers1), |
| MORE_DATA_TO_SEND)); |
| EXPECT_EQ(kDefaultUrl, stream1->url().spec()); |
| |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_EQ(1u, stream1->stream_id()); |
| EXPECT_TRUE(stream1->send_stalled_by_flow_control()); |
| |
| spdy::SpdyHeaderBlock headers2( |
| spdy_util_.ConstructPostHeaderBlock(kDefaultUrl, kBodyDataSize)); |
| EXPECT_EQ(ERR_IO_PENDING, stream2->SendRequestHeaders(std::move(headers2), |
| MORE_DATA_TO_SEND)); |
| EXPECT_EQ(kDefaultUrl, stream2->url().spec()); |
| |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_EQ(3u, stream2->stream_id()); |
| EXPECT_TRUE(stream2->send_stalled_by_flow_control()); |
| |
| EXPECT_TRUE(HasSpdySession(spdy_session_pool_, key_)); |
| |
| // Unstall stream1. |
| UnstallSessionSend(kBodyDataSize); |
| |
| // Close the session (since we can't do it from within the delegate |
| // method, since it's in the stream's loop). |
| session_->CloseSessionOnError(ERR_CONNECTION_CLOSED, "Closing session"); |
| data.Resume(); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_FALSE(session_); |
| |
| EXPECT_FALSE(HasSpdySession(spdy_session_pool_, key_)); |
| |
| EXPECT_THAT(delegate1.WaitForClose(), IsError(ERR_CONNECTION_CLOSED)); |
| EXPECT_THAT(delegate2.WaitForClose(), IsError(ERR_CONNECTION_CLOSED)); |
| |
| EXPECT_TRUE(delegate1.send_headers_completed()); |
| EXPECT_EQ(std::string(), delegate1.TakeReceivedData()); |
| |
| EXPECT_TRUE(delegate2.send_headers_completed()); |
| EXPECT_EQ(std::string(), delegate2.TakeReceivedData()); |
| |
| EXPECT_TRUE(data.AllWriteDataConsumed()); |
| } |
| |
| TEST_F(SpdySessionTest, GoAwayOnSessionFlowControlError) { |
| spdy::SpdySerializedFrame req( |
| spdy_util_.ConstructSpdyGet(nullptr, 0, 1, LOWEST)); |
| spdy::SpdySerializedFrame goaway(spdy_util_.ConstructSpdyGoAway( |
| 0, spdy::ERROR_CODE_FLOW_CONTROL_ERROR, |
| "delta_window_size is 6 in DecreaseRecvWindowSize, which is larger than " |
| "the receive window size of 1")); |
| MockWrite writes[] = { |
| CreateMockWrite(req, 0), CreateMockWrite(goaway, 4), |
| }; |
| |
| spdy::SpdySerializedFrame resp( |
| spdy_util_.ConstructSpdyGetReply(nullptr, 0, 1)); |
| spdy::SpdySerializedFrame body(spdy_util_.ConstructSpdyDataFrame(1, true)); |
| MockRead reads[] = { |
| MockRead(ASYNC, ERR_IO_PENDING, 1), CreateMockRead(resp, 2), |
| CreateMockRead(body, 3), |
| }; |
| |
| SequencedSocketData data(reads, writes); |
| session_deps_.socket_factory->AddSocketDataProvider(&data); |
| |
| AddSSLSocketData(); |
| |
| CreateNetworkSession(); |
| CreateSpdySession(); |
| |
| base::WeakPtr<SpdyStream> spdy_stream = |
| CreateStreamSynchronously(SPDY_REQUEST_RESPONSE_STREAM, session_, |
| test_url_, LOWEST, NetLogWithSource()); |
| ASSERT_TRUE(spdy_stream); |
| test::StreamDelegateDoNothing delegate(spdy_stream); |
| spdy_stream->SetDelegate(&delegate); |
| |
| spdy::SpdyHeaderBlock headers( |
| spdy_util_.ConstructGetHeaderBlock(kDefaultUrl)); |
| spdy_stream->SendRequestHeaders(std::move(headers), NO_MORE_DATA_TO_SEND); |
| |
| // Write request. |
| base::RunLoop().RunUntilIdle(); |
| |
| // Put session on the edge of overflowing it's recv window. |
| set_session_recv_window_size(1); |
| |
| // Read response headers & body. Body overflows the session window, and a |
| // goaway is written. |
| data.Resume(); |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_THAT(delegate.WaitForClose(), IsError(ERR_SPDY_FLOW_CONTROL_ERROR)); |
| EXPECT_FALSE(session_); |
| } |
| |
| // Regression. Sorta. Push streams and client streams were sharing a single |
| // limit for a long time. |
| TEST_F(SpdySessionTest, PushedStreamShouldNotCountToClientConcurrencyLimit) { |
| spdy::SettingsMap new_settings; |
| new_settings[spdy::SETTINGS_MAX_CONCURRENT_STREAMS] = 2; |
| spdy::SpdySerializedFrame settings_frame( |
| spdy_util_.ConstructSpdySettings(new_settings)); |
| spdy::SpdySerializedFrame pushed( |
| spdy_util_.ConstructSpdyPush(nullptr, 0, 2, 1, kPushedUrl)); |
| MockRead reads[] = { |
| CreateMockRead(settings_frame, 0), |
| MockRead(ASYNC, ERR_IO_PENDING, 3), |
| CreateMockRead(pushed, 4), |
| MockRead(ASYNC, ERR_IO_PENDING, 6), |
| MockRead(ASYNC, 0, 7), |
| }; |
| |
| spdy::SpdySerializedFrame settings_ack(spdy_util_.ConstructSpdySettingsAck()); |
| spdy::SpdySerializedFrame req( |
| spdy_util_.ConstructSpdyGet(nullptr, 0, 1, LOWEST)); |
| spdy::SpdySerializedFrame priority( |
| spdy_util_.ConstructSpdyPriority(2, 1, IDLE, true)); |
| MockWrite writes[] = { |
| CreateMockWrite(settings_ack, 1), CreateMockWrite(req, 2), |
| CreateMockWrite(priority, 5), |
| }; |
| |
| SequencedSocketData data(reads, writes); |
| session_deps_.socket_factory->AddSocketDataProvider(&data); |
| |
| AddSSLSocketData(); |
| |
| CreateNetworkSession(); |
| CreateSpdySession(); |
| |
| // Read the settings frame. |
| base::RunLoop().RunUntilIdle(); |
| |
| base::WeakPtr<SpdyStream> spdy_stream1 = |
| CreateStreamSynchronously(SPDY_REQUEST_RESPONSE_STREAM, session_, |
| test_url_, LOWEST, NetLogWithSource()); |
| ASSERT_TRUE(spdy_stream1); |
| EXPECT_EQ(0u, spdy_stream1->stream_id()); |
| test::StreamDelegateDoNothing delegate1(spdy_stream1); |
| spdy_stream1->SetDelegate(&delegate1); |
| |
| EXPECT_EQ(0u, num_active_streams()); |
| EXPECT_EQ(1u, num_created_streams()); |
| EXPECT_EQ(0u, num_pushed_streams()); |
| EXPECT_EQ(0u, num_active_pushed_streams()); |
| |
| spdy::SpdyHeaderBlock headers( |
| spdy_util_.ConstructGetHeaderBlock(kDefaultUrl)); |
| spdy_stream1->SendRequestHeaders(std::move(headers), NO_MORE_DATA_TO_SEND); |
| |
| // Run until 1st stream is activated. |
| EXPECT_EQ(0u, delegate1.stream_id()); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_EQ(1u, delegate1.stream_id()); |
| EXPECT_EQ(1u, num_active_streams()); |
| EXPECT_EQ(0u, num_created_streams()); |
| EXPECT_EQ(0u, num_pushed_streams()); |
| EXPECT_EQ(0u, num_active_pushed_streams()); |
| |
| // Run until pushed stream is created. |
| data.Resume(); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_EQ(2u, num_active_streams()); |
| EXPECT_EQ(0u, num_created_streams()); |
| EXPECT_EQ(1u, num_pushed_streams()); |
| EXPECT_EQ(1u, num_active_pushed_streams()); |
| |
| // Second stream should not be stalled, although we have 2 active streams, but |
| // one of them is push stream and should not be taken into account when we |
| // create streams on the client. |
| base::WeakPtr<SpdyStream> spdy_stream2 = |
| CreateStreamSynchronously(SPDY_REQUEST_RESPONSE_STREAM, session_, |
| test_url_, LOWEST, NetLogWithSource()); |
| EXPECT_TRUE(spdy_stream2); |
| EXPECT_EQ(2u, num_active_streams()); |
| EXPECT_EQ(1u, num_created_streams()); |
| EXPECT_EQ(1u, num_pushed_streams()); |
| EXPECT_EQ(1u, num_active_pushed_streams()); |
| |
| // Read EOF. |
| data.Resume(); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_FALSE(session_); |
| } |
| |
| TEST_F(SpdySessionTest, RejectPushedStreamExceedingConcurrencyLimit) { |
| base::HistogramTester histogram_tester; |
| |
| spdy::SpdySerializedFrame push_a( |
| spdy_util_.ConstructSpdyPush(nullptr, 0, 2, 1, kPushedUrl)); |
| spdy::SpdySerializedFrame push_b(spdy_util_.ConstructSpdyPush( |
| nullptr, 0, 4, 1, "https://www.example.org/b.dat")); |
| MockRead reads[] = { |
| MockRead(ASYNC, ERR_IO_PENDING, 1), CreateMockRead(push_a, 2), |
| MockRead(ASYNC, ERR_IO_PENDING, 4), CreateMockRead(push_b, 5), |
| MockRead(ASYNC, ERR_IO_PENDING, 8), MockRead(ASYNC, 0, 9), |
| }; |
| |
| spdy::SpdySerializedFrame req( |
| spdy_util_.ConstructSpdyGet(nullptr, 0, 1, LOWEST)); |
| spdy::SpdySerializedFrame priority_a( |
| spdy_util_.ConstructSpdyPriority(2, 1, IDLE, true)); |
| spdy::SpdySerializedFrame priority_b( |
| spdy_util_.ConstructSpdyPriority(4, 2, IDLE, true)); |
| spdy::SpdySerializedFrame rst_b( |
| spdy_util_.ConstructSpdyRstStream(4, spdy::ERROR_CODE_REFUSED_STREAM)); |
| MockWrite writes[] = { |
| CreateMockWrite(req, 0), CreateMockWrite(priority_a, 3), |
| CreateMockWrite(priority_b, 6), CreateMockWrite(rst_b, 7), |
| }; |
| |
| SequencedSocketData data(reads, writes); |
| session_deps_.socket_factory->AddSocketDataProvider(&data); |
| |
| AddSSLSocketData(); |
| |
| CreateNetworkSession(); |
| CreateSpdySession(); |
| set_max_concurrent_pushed_streams(1); |
| |
| base::WeakPtr<SpdyStream> spdy_stream1 = |
| CreateStreamSynchronously(SPDY_REQUEST_RESPONSE_STREAM, session_, |
| test_url_, LOWEST, NetLogWithSource()); |
| ASSERT_TRUE(spdy_stream1); |
| EXPECT_EQ(0u, spdy_stream1->stream_id()); |
| test::StreamDelegateDoNothing delegate1(spdy_stream1); |
| spdy_stream1->SetDelegate(&delegate1); |
| |
| EXPECT_EQ(0u, num_active_streams()); |
| EXPECT_EQ(1u, num_created_streams()); |
| EXPECT_EQ(0u, num_pushed_streams()); |
| EXPECT_EQ(0u, num_active_pushed_streams()); |
| |
| spdy::SpdyHeaderBlock headers( |
| spdy_util_.ConstructGetHeaderBlock(kDefaultUrl)); |
| spdy_stream1->SendRequestHeaders(std::move(headers), NO_MORE_DATA_TO_SEND); |
| |
| // Run until 1st stream is activated. |
| EXPECT_EQ(0u, delegate1.stream_id()); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_EQ(1u, delegate1.stream_id()); |
| EXPECT_EQ(1u, num_active_streams()); |
| EXPECT_EQ(0u, num_created_streams()); |
| EXPECT_EQ(0u, num_pushed_streams()); |
| EXPECT_EQ(0u, num_active_pushed_streams()); |
| |
| // Run until pushed stream is created. |
| data.Resume(); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_EQ(2u, num_active_streams()); |
| EXPECT_EQ(0u, num_created_streams()); |
| EXPECT_EQ(1u, num_pushed_streams()); |
| EXPECT_EQ(1u, num_active_pushed_streams()); |
| |
| // Reset incoming pushed stream. |
| data.Resume(); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_EQ(2u, num_active_streams()); |
| EXPECT_EQ(0u, num_created_streams()); |
| EXPECT_EQ(1u, num_pushed_streams()); |
| EXPECT_EQ(1u, num_active_pushed_streams()); |
| |
| // Read EOF. |
| data.Resume(); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_FALSE(session_); |
| |
| histogram_tester.ExpectBucketCount( |
| "Net.SpdyPushedStreamFate", |
| static_cast<int>(SpdyPushedStreamFate::kTooManyPushedStreams), 1); |
| histogram_tester.ExpectTotalCount("Net.SpdyPushedStreamFate", 1); |
| } |
| |
| // Tests that HTTP SPDY push streams that advertise an origin different from the |
| // associated stream are accepted from a trusted SPDY proxy. |
| TEST_F(SpdySessionTest, TrustedSpdyProxy) { |
| base::HistogramTester histogram_tester; |
| |
| // Origin of kDefaultUrl should be different from the origin of |
| // kHttpURLFromAnotherOrigin and kHttpsURLFromAnotherOrigin. |
| ASSERT_NE(GURL(kDefaultUrl).host(), GURL(kHttpURLFromAnotherOrigin).host()); |
| ASSERT_NE(GURL(kDefaultUrl).host(), GURL(kHttpsURLFromAnotherOrigin).host()); |
| |
| // cross_origin_push contains HTTP resource for an origin different from the |
| // origin of kDefaultUrl, and should be accepted. |
| spdy::SpdySerializedFrame cross_origin_push(spdy_util_.ConstructSpdyPush( |
| nullptr, 0, 2, 1, kHttpURLFromAnotherOrigin)); |
| // cross_origin_https_push contains HTTPS resource, and should be refused. |
| spdy::SpdySerializedFrame cross_origin_https_push( |
| spdy_util_.ConstructSpdyPush(nullptr, 0, 4, 1, |
| kHttpsURLFromAnotherOrigin)); |
| MockRead reads[] = { |
| MockRead(ASYNC, ERR_IO_PENDING, 1), |
| CreateMockRead(cross_origin_push, 2), |
| MockRead(ASYNC, ERR_IO_PENDING, 4), |
| CreateMockRead(cross_origin_https_push, 5), |
| MockRead(ASYNC, ERR_IO_PENDING, 7), |
| MockRead(ASYNC, 0, 8), |
| }; |
| |
| spdy::SpdySerializedFrame req( |
| spdy_util_.ConstructSpdyGet(nullptr, 0, 1, LOWEST)); |
| spdy::SpdySerializedFrame priority_http( |
| spdy_util_.ConstructSpdyPriority(2, 1, IDLE, true)); |
| spdy::SpdySerializedFrame rst_https( |
| spdy_util_.ConstructSpdyRstStream(4, spdy::ERROR_CODE_REFUSED_STREAM)); |
| MockWrite writes[] = { |
| CreateMockWrite(req, 0), CreateMockWrite(priority_http, 3), |
| CreateMockWrite(rst_https, 6), |
| }; |
| |
| SequencedSocketData data(reads, writes); |
| session_deps_.socket_factory->AddSocketDataProvider(&data); |
| |
| AddSSLSocketData(); |
| |
| CreateNetworkSession(); |
| CreateTrustedSpdySession(); |
| |
| base::WeakPtr<SpdyStream> spdy_stream = |
| CreateStreamSynchronously(SPDY_REQUEST_RESPONSE_STREAM, session_, |
| test_url_, LOWEST, NetLogWithSource()); |
| ASSERT_TRUE(spdy_stream); |
| EXPECT_EQ(0u, spdy_stream->stream_id()); |
| test::StreamDelegateDoNothing delegate(spdy_stream); |
| spdy_stream->SetDelegate(&delegate); |
| |
| EXPECT_EQ(0u, num_active_streams()); |
| EXPECT_EQ(1u, num_created_streams()); |
| EXPECT_EQ(0u, num_pushed_streams()); |
| EXPECT_EQ(0u, num_active_pushed_streams()); |
| |
| spdy::SpdyHeaderBlock headers( |
| spdy_util_.ConstructGetHeaderBlock(kDefaultUrl)); |
| spdy_stream->SendRequestHeaders(std::move(headers), NO_MORE_DATA_TO_SEND); |
| |
| // Run until 1st stream is activated. |
| EXPECT_EQ(0u, delegate.stream_id()); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_EQ(1u, delegate.stream_id()); |
| EXPECT_EQ(1u, num_active_streams()); |
| EXPECT_EQ(0u, num_created_streams()); |
| EXPECT_EQ(0u, num_pushed_streams()); |
| EXPECT_EQ(0u, num_active_pushed_streams()); |
| |
| // Run until pushed stream is created. |
| data.Resume(); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_EQ(2u, num_active_streams()); |
| EXPECT_EQ(0u, num_created_streams()); |
| EXPECT_EQ(1u, num_pushed_streams()); |
| EXPECT_EQ(1u, num_active_pushed_streams()); |
| |
| // Reset incoming pushed stream. |
| data.Resume(); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_EQ(2u, num_active_streams()); |
| EXPECT_EQ(0u, num_created_streams()); |
| EXPECT_EQ(1u, num_pushed_streams()); |
| EXPECT_EQ(1u, num_active_pushed_streams()); |
| |
| // Read EOF. |
| data.Resume(); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_FALSE(session_); |
| |
| histogram_tester.ExpectBucketCount( |
| "Net.SpdyPushedStreamFate", |
| static_cast<int>(SpdyPushedStreamFate::kNonHttpSchemeFromTrustedProxy), |
| 1); |
| histogram_tester.ExpectTotalCount("Net.SpdyPushedStreamFate", 1); |
| } |
| |
| // Tests that if the SPDY trusted proxy is not set, then push streams that |
| // advertise an origin different from the associated stream are refused. |
| TEST_F(SpdySessionTest, TrustedSpdyProxyNotSet) { |
| base::HistogramTester histogram_tester; |
| |
| // Origin of kDefaultUrl should be different from the origin of |
| // kHttpURLFromAnotherOrigin. |
| ASSERT_NE(GURL(kDefaultUrl).host(), GURL(kHttpURLFromAnotherOrigin).host()); |
| |
| // cross_origin_push contains resource for an origin different from the |
| // origin of kDefaultUrl, and should be refused. |
| spdy::SpdySerializedFrame cross_origin_push(spdy_util_.ConstructSpdyPush( |
| nullptr, 0, 2, 1, kHttpURLFromAnotherOrigin)); |
| MockRead reads[] = { |
| MockRead(ASYNC, ERR_IO_PENDING, 1), CreateMockRead(cross_origin_push, 2), |
| MockRead(ASYNC, 0, 4), |
| }; |
| |
| spdy::SpdySerializedFrame req( |
| spdy_util_.ConstructSpdyGet(nullptr, 0, 1, LOWEST)); |
| spdy::SpdySerializedFrame rst( |
| spdy_util_.ConstructSpdyRstStream(2, spdy::ERROR_CODE_REFUSED_STREAM)); |
| MockWrite writes[] = { |
| CreateMockWrite(req, 0), CreateMockWrite(rst, 3), |
| }; |
| |
| SequencedSocketData data(reads, writes); |
| session_deps_.socket_factory->AddSocketDataProvider(&data); |
| |
| AddSSLSocketData(); |
| |
| CreateNetworkSession(); |
| CreateSpdySession(); |
| |
| base::WeakPtr<SpdyStream> spdy_stream = |
| CreateStreamSynchronously(SPDY_REQUEST_RESPONSE_STREAM, session_, |
| test_url_, LOWEST, NetLogWithSource()); |
| ASSERT_TRUE(spdy_stream); |
| EXPECT_EQ(0u, spdy_stream->stream_id()); |
| test::StreamDelegateDoNothing delegate(spdy_stream); |
| spdy_stream->SetDelegate(&delegate); |
| |
| EXPECT_EQ(0u, num_active_streams()); |
| EXPECT_EQ(1u, num_created_streams()); |
| EXPECT_EQ(0u, num_pushed_streams()); |
| EXPECT_EQ(0u, num_active_pushed_streams()); |
| |
| spdy::SpdyHeaderBlock headers( |
| spdy_util_.ConstructGetHeaderBlock(kDefaultUrl)); |
| spdy_stream->SendRequestHeaders(std::move(headers), NO_MORE_DATA_TO_SEND); |
| |
| // Run until 1st stream is activated. |
| EXPECT_EQ(0u, delegate.stream_id()); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_EQ(1u, delegate.stream_id()); |
| EXPECT_EQ(1u, num_active_streams()); |
| EXPECT_EQ(0u, num_created_streams()); |
| EXPECT_EQ(0u, num_pushed_streams()); |
| EXPECT_EQ(0u, num_active_pushed_streams()); |
| |
| // Read EOF. |
| data.Resume(); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_FALSE(session_); |
| |
| histogram_tester.ExpectBucketCount( |
| "Net.SpdyPushedStreamFate", |
| static_cast<int>(SpdyPushedStreamFate::kNonHttpsPushedScheme), 1); |
| histogram_tester.ExpectTotalCount("Net.SpdyPushedStreamFate", 1); |
| } |
| |
| TEST_F(SpdySessionTest, IgnoreReservedRemoteStreamsCount) { |
| base::HistogramTester histogram_tester; |
| |
| spdy::SpdySerializedFrame push_a( |
| spdy_util_.ConstructSpdyPush(nullptr, 0, 2, 1, kPushedUrl)); |
| spdy::SpdyHeaderBlock push_headers; |
| push_headers[":method"] = "GET"; |
| spdy_util_.AddUrlToHeaderBlock("https://www.example.org/b.dat", |
| &push_headers); |
| spdy::SpdySerializedFrame push_b( |
| spdy_util_.ConstructSpdyPushPromise(1, 4, std::move(push_headers))); |
| spdy::SpdySerializedFrame headers_b( |
| spdy_util_.ConstructSpdyPushHeaders(4, nullptr, 0)); |
| MockRead reads[] = { |
| MockRead(ASYNC, ERR_IO_PENDING, 1), CreateMockRead(push_a, 2), |
| MockRead(ASYNC, ERR_IO_PENDING, 4), CreateMockRead(push_b, 5), |
| MockRead(ASYNC, ERR_IO_PENDING, 7), CreateMockRead(headers_b, 8), |
| MockRead(ASYNC, ERR_IO_PENDING, 10), MockRead(ASYNC, 0, 11), |
| }; |
| |
| spdy::SpdySerializedFrame req( |
| spdy_util_.ConstructSpdyGet(nullptr, 0, 1, LOWEST)); |
| spdy::SpdySerializedFrame priority_a( |
| spdy_util_.ConstructSpdyPriority(2, 1, IDLE, true)); |
| spdy::SpdySerializedFrame priority_b( |
| spdy_util_.ConstructSpdyPriority(4, 2, IDLE, true)); |
| spdy::SpdySerializedFrame rst_b( |
| spdy_util_.ConstructSpdyRstStream(4, spdy::ERROR_CODE_REFUSED_STREAM)); |
| MockWrite writes[] = { |
| CreateMockWrite(req, 0), CreateMockWrite(priority_a, 3), |
| CreateMockWrite(priority_b, 6), CreateMockWrite(rst_b, 9), |
| }; |
| |
| SequencedSocketData data(reads, writes); |
| session_deps_.socket_factory->AddSocketDataProvider(&data); |
| |
| AddSSLSocketData(); |
| |
| CreateNetworkSession(); |
| CreateSpdySession(); |
| set_max_concurrent_pushed_streams(1); |
| |
| base::WeakPtr<SpdyStream> spdy_stream1 = |
| CreateStreamSynchronously(SPDY_REQUEST_RESPONSE_STREAM, session_, |
| test_url_, LOWEST, NetLogWithSource()); |
| ASSERT_TRUE(spdy_stream1); |
| EXPECT_EQ(0u, spdy_stream1->stream_id()); |
| test::StreamDelegateDoNothing delegate1(spdy_stream1); |
| spdy_stream1->SetDelegate(&delegate1); |
| |
| EXPECT_EQ(0u, num_active_streams()); |
| EXPECT_EQ(1u, num_created_streams()); |
| EXPECT_EQ(0u, num_pushed_streams()); |
| EXPECT_EQ(0u, num_active_pushed_streams()); |
| |
| spdy::SpdyHeaderBlock headers( |
| spdy_util_.ConstructGetHeaderBlock(kDefaultUrl)); |
| spdy_stream1->SendRequestHeaders(std::move(headers), NO_MORE_DATA_TO_SEND); |
| |
| // Run until 1st stream is activated. |
| EXPECT_EQ(0u, delegate1.stream_id()); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_EQ(1u, delegate1.stream_id()); |
| EXPECT_EQ(1u, num_active_streams()); |
| EXPECT_EQ(0u, num_created_streams()); |
| EXPECT_EQ(0u, num_pushed_streams()); |
| EXPECT_EQ(0u, num_active_pushed_streams()); |
| |
| // Run until pushed stream is created. |
| data.Resume(); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_EQ(2u, num_active_streams()); |
| EXPECT_EQ(0u, num_created_streams()); |
| EXPECT_EQ(1u, num_pushed_streams()); |
| EXPECT_EQ(1u, num_active_pushed_streams()); |
| |
| // Accept promised stream. It should not count towards pushed stream limit. |
| data.Resume(); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_EQ(3u, num_active_streams()); |
| EXPECT_EQ(0u, num_created_streams()); |
| EXPECT_EQ(2u, num_pushed_streams()); |
| EXPECT_EQ(1u, num_active_pushed_streams()); |
| |
| // Reset last pushed stream upon headers reception as it is going to be 2nd, |
| // while we accept only one. |
| data.Resume(); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_EQ(2u, num_active_streams()); |
| EXPECT_EQ(0u, num_created_streams()); |
| EXPECT_EQ(1u, num_pushed_streams()); |
| EXPECT_EQ(1u, num_active_pushed_streams()); |
| |
| // Read EOF. |
| data.Resume(); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_FALSE(session_); |
| |
| histogram_tester.ExpectBucketCount( |
| "Net.SpdyPushedStreamFate", |
| static_cast<int>(SpdyPushedStreamFate::kTooManyPushedStreams), 1); |
| histogram_tester.ExpectTotalCount("Net.SpdyPushedStreamFate", 1); |
| } |
| |
| TEST_F(SpdySessionTest, CancelReservedStreamOnHeadersReceived) { |
| spdy::SpdyHeaderBlock push_headers; |
| push_headers[":method"] = "GET"; |
| spdy_util_.AddUrlToHeaderBlock(kPushedUrl, &push_headers); |
| spdy::SpdySerializedFrame push_promise( |
| spdy_util_.ConstructSpdyPushPromise(1, 2, std::move(push_headers))); |
| spdy::SpdySerializedFrame headers_frame( |
| spdy_util_.ConstructSpdyPushHeaders(2, nullptr, 0)); |
| MockRead reads[] = { |
| MockRead(ASYNC, ERR_IO_PENDING, 1), CreateMockRead(push_promise, 2), |
| MockRead(ASYNC, ERR_IO_PENDING, 4), CreateMockRead(headers_frame, 5), |
| MockRead(ASYNC, ERR_IO_PENDING, 7), MockRead(ASYNC, 0, 8), |
| }; |
| |
| spdy::SpdySerializedFrame req( |
| spdy_util_.ConstructSpdyGet(nullptr, 0, 1, LOWEST)); |
| spdy::SpdySerializedFrame priority( |
| spdy_util_.ConstructSpdyPriority(2, 1, IDLE, true)); |
| spdy::SpdySerializedFrame rst( |
| spdy_util_.ConstructSpdyRstStream(2, spdy::ERROR_CODE_CANCEL)); |
| MockWrite writes[] = { |
| CreateMockWrite(req, 0), CreateMockWrite(priority, 3), |
| CreateMockWrite(rst, 6), |
| }; |
| |
| SequencedSocketData data(reads, writes); |
| session_deps_.socket_factory->AddSocketDataProvider(&data); |
| |
| AddSSLSocketData(); |
| |
| CreateNetworkSession(); |
| CreateSpdySession(); |
| |
| base::WeakPtr<SpdyStream> spdy_stream1 = |
| CreateStreamSynchronously(SPDY_REQUEST_RESPONSE_STREAM, session_, |
| test_url_, LOWEST, NetLogWithSource()); |
| ASSERT_TRUE(spdy_stream1); |
| EXPECT_EQ(0u, spdy_stream1->stream_id()); |
| test::StreamDelegateDoNothing delegate1(spdy_stream1); |
| spdy_stream1->SetDelegate(&delegate1); |
| |
| EXPECT_EQ(0u, num_active_streams()); |
| EXPECT_EQ(1u, num_created_streams()); |
| EXPECT_EQ(0u, num_pushed_streams()); |
| EXPECT_EQ(0u, num_active_pushed_streams()); |
| |
| spdy::SpdyHeaderBlock headers( |
| spdy_util_.ConstructGetHeaderBlock(kDefaultUrl)); |
| spdy_stream1->SendRequestHeaders(std::move(headers), NO_MORE_DATA_TO_SEND); |
| |
| // Run until 1st stream is activated. |
| EXPECT_EQ(0u, delegate1.stream_id()); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_EQ(1u, delegate1.stream_id()); |
| EXPECT_EQ(1u, num_active_streams()); |
| EXPECT_EQ(0u, num_created_streams()); |
| EXPECT_EQ(0u, num_pushed_streams()); |
| EXPECT_EQ(0u, num_active_pushed_streams()); |
| EXPECT_EQ(0u, num_unclaimed_pushed_streams()); |
| |
| // Run until pushed stream is created. |
| data.Resume(); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_EQ(2u, num_active_streams()); |
| EXPECT_EQ(0u, num_created_streams()); |
| EXPECT_EQ(1u, num_pushed_streams()); |
| EXPECT_EQ(0u, num_active_pushed_streams()); |
| EXPECT_EQ(1u, num_unclaimed_pushed_streams()); |
| |
| // Claim pushed stream from Http2PushPromiseIndex. |
| const GURL pushed_url(kPushedUrl); |
| HttpRequestInfo push_request; |
| push_request.url = pushed_url; |
| push_request.method = "GET"; |
| base::WeakPtr<SpdySession> session_with_pushed_stream; |
| spdy::SpdyStreamId pushed_stream_id; |
| spdy_session_pool_->push_promise_index()->ClaimPushedStream( |
| key_, pushed_url, push_request, &session_with_pushed_stream, |
| &pushed_stream_id); |
| EXPECT_EQ(session_.get(), session_with_pushed_stream.get()); |
| EXPECT_EQ(2u, pushed_stream_id); |
| EXPECT_EQ(0u, num_unclaimed_pushed_streams()); |
| |
| SpdyStream* pushed_stream; |
| int rv = session_->GetPushedStream(pushed_url, pushed_stream_id, IDLE, |
| &pushed_stream); |
| ASSERT_THAT(rv, IsOk()); |
| ASSERT_TRUE(pushed_stream); |
| test::StreamDelegateCloseOnHeaders delegate2(pushed_stream->GetWeakPtr()); |
| pushed_stream->SetDelegate(&delegate2); |
| |
| // Receive headers for pushed stream. Delegate will cancel the stream, ensure |
| // that all our counters are in consistent state. |
| data.Resume(); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_EQ(1u, num_active_streams()); |
| EXPECT_EQ(0u, num_created_streams()); |
| EXPECT_EQ(0u, num_pushed_streams()); |
| EXPECT_EQ(0u, num_active_pushed_streams()); |
| EXPECT_EQ(0u, num_unclaimed_pushed_streams()); |
| |
| // Read EOF. |
| data.Resume(); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_TRUE(data.AllWriteDataConsumed()); |
| EXPECT_TRUE(data.AllReadDataConsumed()); |
| } |
| |
| TEST_F(SpdySessionTest, GetPushedStream) { |
| spdy::SpdyHeaderBlock push_headers; |
| push_headers[":method"] = "GET"; |
| spdy_util_.AddUrlToHeaderBlock(kPushedUrl, &push_headers); |
| spdy::SpdySerializedFrame push_promise( |
| spdy_util_.ConstructSpdyPushPromise(1, 2, std::move(push_headers))); |
| spdy::SpdySerializedFrame headers_frame( |
| spdy_util_.ConstructSpdyPushHeaders(2, nullptr, 0)); |
| MockRead reads[] = { |
| MockRead(ASYNC, ERR_IO_PENDING, 1), CreateMockRead(push_promise, 2), |
| MockRead(ASYNC, ERR_IO_PENDING, 4), CreateMockRead(headers_frame, 5), |
| MockRead(ASYNC, ERR_IO_PENDING, 7), MockRead(ASYNC, 0, 8)}; |
| |
| spdy::SpdySerializedFrame req( |
| spdy_util_.ConstructSpdyGet(nullptr, 0, 1, LOWEST)); |
| spdy::SpdySerializedFrame priority( |
| spdy_util_.ConstructSpdyPriority(2, 1, IDLE, true)); |
| spdy::SpdySerializedFrame rst( |
| spdy_util_.ConstructSpdyRstStream(2, spdy::ERROR_CODE_CANCEL)); |
| MockWrite writes[] = {CreateMockWrite(req, 0), CreateMockWrite(priority, 3), |
| CreateMockWrite(rst, 6)}; |
| |
| SequencedSocketData data(reads, writes); |
| session_deps_.socket_factory->AddSocketDataProvider(&data); |
| |
| AddSSLSocketData(); |
| |
| CreateNetworkSession(); |
| CreateSpdySession(); |
| |
| base::WeakPtr<SpdyStream> spdy_stream1 = |
| CreateStreamSynchronously(SPDY_REQUEST_RESPONSE_STREAM, session_, |
| test_url_, LOWEST, NetLogWithSource()); |
| ASSERT_TRUE(spdy_stream1); |
| EXPECT_EQ(0u, spdy_stream1->stream_id()); |
| test::StreamDelegateDoNothing delegate1(spdy_stream1); |
| spdy_stream1->SetDelegate(&delegate1); |
| |
| EXPECT_EQ(0u, num_active_streams()); |
| EXPECT_EQ(1u, num_created_streams()); |
| EXPECT_EQ(0u, num_pushed_streams()); |
| EXPECT_EQ(0u, num_active_pushed_streams()); |
| |
| spdy::SpdyHeaderBlock headers( |
| spdy_util_.ConstructGetHeaderBlock(kDefaultUrl)); |
| spdy_stream1->SendRequestHeaders(std::move(headers), NO_MORE_DATA_TO_SEND); |
| |
| // Activate first request. |
| EXPECT_EQ(0u, delegate1.stream_id()); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_EQ(1u, delegate1.stream_id()); |
| EXPECT_EQ(1u, num_active_streams()); |
| EXPECT_EQ(0u, num_created_streams()); |
| EXPECT_EQ(0u, num_pushed_streams()); |
| EXPECT_EQ(0u, num_active_pushed_streams()); |
| |
| // No streams are pushed yet, therefore GetPushedStream() should return an |
| // error. |
| const GURL pushed_url(kPushedUrl); |
| SpdyStream* pushed_stream; |
| int rv = session_->GetPushedStream(pushed_url, 2 /* pushed_stream_id */, IDLE, |
| &pushed_stream); |
| EXPECT_THAT(rv, IsError(ERR_SPDY_PUSHED_STREAM_NOT_AVAILABLE)); |
| |
| // Read PUSH_PROMISE. |
| data.Resume(); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_EQ(2u, num_active_streams()); |
| EXPECT_EQ(0u, num_created_streams()); |
| EXPECT_EQ(1u, num_pushed_streams()); |
| EXPECT_EQ(0u, num_active_pushed_streams()); |
| EXPECT_EQ(1u, num_unclaimed_pushed_streams()); |
| |
| // Claim pushed stream from Http2PushPromiseIndex so that GetPushedStream() |
| // can be called. |
| HttpRequestInfo push_request; |
| push_request.url = pushed_url; |
| push_request.method = "GET"; |
| base::WeakPtr<SpdySession> session_with_pushed_stream; |
| spdy::SpdyStreamId pushed_stream_id; |
| spdy_session_pool_->push_promise_index()->ClaimPushedStream( |
| key_, pushed_url, push_request, &session_with_pushed_stream, |
| &pushed_stream_id); |
| EXPECT_EQ(session_.get(), session_with_pushed_stream.get()); |
| EXPECT_EQ(2u, pushed_stream_id); |
| |
| // Verify that pushed stream is claimed. |
| EXPECT_EQ(0u, num_unclaimed_pushed_streams()); |
| |
| // GetPushedStream() should return an error if there does not exist a pushed |
| // stream with ID |pushed_stream_id|. |
| rv = session_->GetPushedStream(pushed_url, 4 /* pushed_stream_id */, IDLE, |
| &pushed_stream); |
| EXPECT_THAT(rv, IsError(ERR_SPDY_PUSHED_STREAM_NOT_AVAILABLE)); |
| |
| // GetPushedStream() should return OK and return the pushed stream in |
| // |pushed_stream| outparam if |pushed_stream_id| matches. |
| rv = session_->GetPushedStream(pushed_url, 2 /* pushed_stream_id */, IDLE, |
| &pushed_stream); |
| EXPECT_THAT(rv, IsOk()); |
| ASSERT_TRUE(pushed_stream); |
| test::StreamDelegateCloseOnHeaders delegate2(pushed_stream->GetWeakPtr()); |
| pushed_stream->SetDelegate(&delegate2); |
| |
| // Upon reading pushed headers, delegate closes the stream. |
| data.Resume(); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_EQ(1u, num_active_streams()); |
| EXPECT_EQ(0u, num_created_streams()); |
| EXPECT_EQ(0u, num_pushed_streams()); |
| EXPECT_EQ(0u, num_active_pushed_streams()); |
| |
| // Read EOF. |
| data.Resume(); |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_TRUE(delegate1.StreamIsClosed()); |
| EXPECT_TRUE(delegate2.StreamIsClosed()); |
| |
| EXPECT_TRUE(data.AllWriteDataConsumed()); |
| EXPECT_TRUE(data.AllReadDataConsumed()); |
| } |
| |
| TEST_F(SpdySessionTest, RejectInvalidUnknownFrames) { |
| MockRead reads[] = { |
| MockRead(SYNCHRONOUS, ERR_IO_PENDING) // Stall forever. |
| }; |
| |
| StaticSocketDataProvider data(reads, base::span<MockWrite>()); |
| session_deps_.socket_factory->AddSocketDataProvider(&data); |
| |
| AddSSLSocketData(); |
| |
| CreateNetworkSession(); |
| CreateSpdySession(); |
| |
| set_stream_hi_water_mark(5); |
| // Low client (odd) ids are fine. |
| EXPECT_TRUE(OnUnknownFrame(3, 0)); |
| // Client id exceeding watermark. |
| EXPECT_FALSE(OnUnknownFrame(9, 0)); |
| |
| set_last_accepted_push_stream_id(6); |
| // Low server (even) ids are fine. |
| EXPECT_TRUE(OnUnknownFrame(2, 0)); |
| // Server id exceeding last accepted id. |
| EXPECT_FALSE(OnUnknownFrame(8, 0)); |
| } |
| |
| TEST_F(SpdySessionTest, EnableWebSocket) { |
| spdy::SettingsMap settings_map; |
| settings_map[spdy::SETTINGS_ENABLE_CONNECT_PROTOCOL] = 1; |
| spdy::SpdySerializedFrame settings( |
| spdy_util_.ConstructSpdySettings(settings_map)); |
| MockRead reads[] = {CreateMockRead(settings, 0), |
| MockRead(ASYNC, ERR_IO_PENDING, 2), |
| MockRead(ASYNC, 0, 3)}; |
| |
| spdy::SpdySerializedFrame ack(spdy_util_.ConstructSpdySettingsAck()); |
| MockWrite writes[] = {CreateMockWrite(ack, 1)}; |
| |
| SequencedSocketData data(reads, writes); |
| session_deps_.socket_factory->AddSocketDataProvider(&data); |
| |
| AddSSLSocketData(); |
| |
| CreateNetworkSession(); |
| CreateSpdySession(); |
| |
| EXPECT_FALSE(session_->support_websocket()); |
| |
| // Read SETTINGS frame. |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_TRUE(session_->support_websocket()); |
| |
| // Read EOF. |
| data.Resume(); |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_TRUE(data.AllWriteDataConsumed()); |
| EXPECT_TRUE(data.AllReadDataConsumed()); |
| EXPECT_FALSE(session_); |
| } |
| |
| TEST_F(SpdySessionTest, DisableWebSocketDoesNothing) { |
| spdy::SettingsMap settings_map; |
| settings_map[spdy::SETTINGS_ENABLE_CONNECT_PROTOCOL] = 0; |
| spdy::SpdySerializedFrame settings( |
| spdy_util_.ConstructSpdySettings(settings_map)); |
| MockRead reads[] = {CreateMockRead(settings, 0), |
| MockRead(ASYNC, ERR_IO_PENDING, 2), |
| MockRead(ASYNC, 0, 3)}; |
| |
| spdy::SpdySerializedFrame ack(spdy_util_.ConstructSpdySettingsAck()); |
| MockWrite writes[] = {CreateMockWrite(ack, 1)}; |
| |
| SequencedSocketData data(reads, writes); |
| session_deps_.socket_factory->AddSocketDataProvider(&data); |
| |
| AddSSLSocketData(); |
| |
| CreateNetworkSession(); |
| CreateSpdySession(); |
| |
| EXPECT_FALSE(session_->support_websocket()); |
| |
| // Read SETTINGS frame. |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_FALSE(session_->support_websocket()); |
| |
| // Read EOF. |
| data.Resume(); |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_TRUE(data.AllWriteDataConsumed()); |
| EXPECT_TRUE(data.AllReadDataConsumed()); |
| EXPECT_FALSE(session_); |
| } |
| |
| TEST_F(SpdySessionTest, EnableWebSocketThenDisableIsProtocolError) { |
| spdy::SettingsMap settings_map1; |
| settings_map1[spdy::SETTINGS_ENABLE_CONNECT_PROTOCOL] = 1; |
| spdy::SpdySerializedFrame settings1( |
| spdy_util_.ConstructSpdySettings(settings_map1)); |
| spdy::SettingsMap settings_map2; |
| settings_map2[spdy::SETTINGS_ENABLE_CONNECT_PROTOCOL] = 0; |
| spdy::SpdySerializedFrame settings2( |
| spdy_util_.ConstructSpdySettings(settings_map2)); |
| MockRead reads[] = {CreateMockRead(settings1, 0), |
| MockRead(ASYNC, ERR_IO_PENDING, 2), |
| CreateMockRead(settings2, 3)}; |
| |
| spdy::SpdySerializedFrame ack1(spdy_util_.ConstructSpdySettingsAck()); |
| spdy::SpdySerializedFrame ack2(spdy_util_.ConstructSpdySettingsAck()); |
| spdy::SpdySerializedFrame goaway(spdy_util_.ConstructSpdyGoAway( |
| 0, spdy::ERROR_CODE_PROTOCOL_ERROR, |
| "Invalid value for spdy::SETTINGS_ENABLE_CONNECT_PROTOCOL.")); |
| MockWrite writes[] = {CreateMockWrite(ack1, 1), CreateMockWrite(ack2, 4), |
| CreateMockWrite(goaway, 5)}; |
| |
| SequencedSocketData data(reads, writes); |
| session_deps_.socket_factory->AddSocketDataProvider(&data); |
| |
| AddSSLSocketData(); |
| |
| CreateNetworkSession(); |
| CreateSpdySession(); |
| |
| EXPECT_FALSE(session_->support_websocket()); |
| |
| // Read first SETTINGS frame. |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_TRUE(session_->support_websocket()); |
| |
| // Read second SETTINGS frame. |
| data.Resume(); |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_TRUE(data.AllWriteDataConsumed()); |
| EXPECT_TRUE(data.AllReadDataConsumed()); |
| EXPECT_FALSE(session_); |
| } |
| |
| TEST_F(SpdySessionTest, GreaseFrameType) { |
| const uint8_t type = 0x0b; |
| const uint8_t flags = 0xcc; |
| const std::string payload("foo"); |
| session_deps_.greased_http2_frame = |
| base::Optional<net::SpdySessionPool::GreasedHttp2Frame>( |
| {type, flags, payload}); |
| |
| // Connection preface. |
| spdy::SpdySerializedFrame preface( |
| const_cast<char*>(spdy::kHttp2ConnectionHeaderPrefix), |
| spdy::kHttp2ConnectionHeaderPrefixSize, |
| /* owns_buffer = */ false); |
| |
| // Initial SETTINGS frame. |
| spdy::SettingsMap expected_settings; |
| expected_settings[spdy::SETTINGS_HEADER_TABLE_SIZE] = kSpdyMaxHeaderTableSize; |
| expected_settings[spdy::SETTINGS_MAX_CONCURRENT_STREAMS] = |
| kSpdyMaxConcurrentPushedStreams; |
| spdy::SpdySerializedFrame settings_frame( |
| spdy_util_.ConstructSpdySettings(expected_settings)); |
| |
| spdy::SpdySerializedFrame combined_frame = |
| CombineFrames({&preface, &settings_frame}); |
| |
| // Greased frame sent on stream 0 after initial SETTINGS frame. |
| const char kRawFrameData0[] = { |
| 0x00, 0x00, 0x03, // length |
| 0x0b, // type |
| 0xcc, // flags |
| 0x00, 0x00, 0x00, 0x00, // stream ID |
| 'f', 'o', 'o' // payload |
| }; |
| spdy::SpdySerializedFrame grease0(const_cast<char*>(kRawFrameData0), |
| base::size(kRawFrameData0), |
| /* owns_buffer = */ false); |
| spdy::SpdySerializedFrame req( |
| spdy_util_.ConstructSpdyGet(nullptr, 0, 1, DEFAULT_PRIORITY)); |
| |
| // Greased frame sent on stream 1 after request. |
| const char kRawFrameData1[] = { |
| 0x00, 0x00, 0x03, // length |
| 0x0b, // type |
| 0xcc, // flags |
| 0x00, 0x00, 0x00, 0x01, // stream ID |
| 'f', 'o', 'o' // payload |
| }; |
| spdy::SpdySerializedFrame grease1(const_cast<char*>(kRawFrameData1), |
| base::size(kRawFrameData1), |
| /* owns_buffer = */ false); |
| |
| MockWrite writes[] = {CreateMockWrite(combined_frame, 0), |
| CreateMockWrite(grease0, 1), CreateMockWrite(req, 2), |
| CreateMockWrite(grease1, 3)}; |
| |
| spdy::SpdySerializedFrame resp( |
| spdy_util_.ConstructSpdyGetReply(nullptr, 0, 1)); |
| spdy::SpdySerializedFrame body(spdy_util_.ConstructSpdyDataFrame(1, true)); |
| |
| MockRead reads[] = {CreateMockRead(resp, 4), CreateMockRead(body, 5), |
| MockRead(ASYNC, 0, 6)}; |
| |
| SequencedSocketData data(reads, writes); |
| session_deps_.socket_factory->AddSocketDataProvider(&data); |
| AddSSLSocketData(); |
| |
| CreateNetworkSession(); |
| |
| SpdySessionPoolPeer pool_peer(spdy_session_pool_); |
| pool_peer.SetEnableSendingInitialData(true); |
| |
| CreateSpdySession(); |
| |
| base::WeakPtr<SpdyStream> stream = CreateStreamSynchronously( |
| SPDY_REQUEST_RESPONSE_STREAM, session_, test_url_, DEFAULT_PRIORITY, |
| NetLogWithSource()); |
| test::StreamDelegateDoNothing delegate(stream); |
| stream->SetDelegate(&delegate); |
| |
| stream->SendRequestHeaders(spdy_util_.ConstructGetHeaderBlock(kDefaultUrl), |
| NO_MORE_DATA_TO_SEND); |
| |
| EXPECT_THAT(delegate.WaitForClose(), IsOk()); |
| |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_TRUE(data.AllWriteDataConsumed()); |
| EXPECT_TRUE(data.AllReadDataConsumed()); |
| } |
| |
| enum ReadIfReadySupport { |
| // ReadIfReady() field trial is enabled, and ReadIfReady() is implemented. |
| READ_IF_READY_ENABLED_SUPPORTED, |
| // ReadIfReady() field trial is enabled, but ReadIfReady() is unimplemented. |
| READ_IF_READY_ENABLED_NOT_SUPPORTED, |
| // ReadIfReady() field trial is disabled. |
| READ_IF_READY_DISABLED, |
| }; |
| |
| class SpdySessionReadIfReadyTest |
| : public SpdySessionTest, |
| public testing::WithParamInterface<ReadIfReadySupport> { |
| public: |
| void SetUp() override { |
| if (GetParam() == READ_IF_READY_DISABLED) { |
| scoped_feature_list_.InitAndDisableFeature( |
| Socket::kReadIfReadyExperiment); |
| } else if (GetParam() == READ_IF_READY_ENABLED_SUPPORTED) { |
| session_deps_.socket_factory->set_enable_read_if_ready(true); |
| } |
| SpdySessionTest::SetUp(); |
| } |
| |
| private: |
| base::test::ScopedFeatureList scoped_feature_list_; |
| }; |
| |
| INSTANTIATE_TEST_CASE_P(/* no prefix */, |
| SpdySessionReadIfReadyTest, |
| testing::Values(READ_IF_READY_ENABLED_SUPPORTED, |
| READ_IF_READY_ENABLED_NOT_SUPPORTED, |
| READ_IF_READY_DISABLED)); |
| |
| // Tests basic functionality of ReadIfReady() when it is enabled or disabled. |
| TEST_P(SpdySessionReadIfReadyTest, ReadIfReady) { |
| spdy::SpdySerializedFrame req( |
| spdy_util_.ConstructSpdyGet(nullptr, 0, 1, HIGHEST)); |
| MockWrite writes[] = { |
| CreateMockWrite(req, 0), |
| }; |
| |
| spdy::SpdySerializedFrame resp( |
| spdy_util_.ConstructSpdyGetReply(nullptr, 0, 1)); |
| spdy::SpdySerializedFrame body(spdy_util_.ConstructSpdyDataFrame(1, true)); |
| MockRead reads[] = { |
| CreateMockRead(resp, 1), CreateMockRead(body, 2), |
| MockRead(ASYNC, 0, 3) // EOF |
| }; |
| |
| SequencedSocketData data(reads, writes); |
| session_deps_.socket_factory->AddSocketDataProvider(&data); |
| |
| AddSSLSocketData(); |
| |
| CreateNetworkSession(); |
| CreateSpdySession(); |
| |
| base::WeakPtr<SpdyStream> spdy_stream = |
| CreateStreamSynchronously(SPDY_REQUEST_RESPONSE_STREAM, session_, |
| test_url_, HIGHEST, NetLogWithSource()); |
| ASSERT_TRUE(spdy_stream); |
| EXPECT_EQ(0u, spdy_stream->stream_id()); |
| test::StreamDelegateDoNothing delegate(spdy_stream); |
| spdy_stream->SetDelegate(&delegate); |
| |
| spdy::SpdyHeaderBlock headers( |
| spdy_util_.ConstructGetHeaderBlock(kDefaultUrl)); |
| spdy_stream->SendRequestHeaders(std::move(headers), NO_MORE_DATA_TO_SEND); |
| |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_FALSE(spdy_stream); |
| EXPECT_EQ(1u, delegate.stream_id()); |
| } |
| |
| class SendInitialSettingsOnNewSpdySessionTest : public SpdySessionTest { |
| protected: |
| void RunInitialSettingsTest(const spdy::SettingsMap expected_settings) { |
| MockRead reads[] = {MockRead(SYNCHRONOUS, ERR_IO_PENDING)}; |
| |
| spdy::SpdySerializedFrame preface( |
| const_cast<char*>(spdy::kHttp2ConnectionHeaderPrefix), |
| spdy::kHttp2ConnectionHeaderPrefixSize, |
| /* owns_buffer = */ false); |
| spdy::SpdySerializedFrame settings_frame( |
| spdy_util_.ConstructSpdySettings(expected_settings)); |
| |
| spdy::SpdySerializedFrame combined_frame = |
| CombineFrames({&preface, &settings_frame}); |
| MockWrite writes[] = {CreateMockWrite(combined_frame, 0)}; |
| |
| StaticSocketDataProvider data(reads, writes); |
| session_deps_.socket_factory->AddSocketDataProvider(&data); |
| AddSSLSocketData(); |
| |
| CreateNetworkSession(); |
| |
| SpdySessionPoolPeer pool_peer(spdy_session_pool_); |
| pool_peer.SetEnableSendingInitialData(true); |
| |
| CreateSpdySession(); |
| |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_TRUE(data.AllWriteDataConsumed()); |
| } |
| }; |
| |
| // Setting values when Params::http2_settings is empty. Note that |
| // spdy::SETTINGS_INITIAL_WINDOW_SIZE is sent in production, because it is set |
| // to a non-default value, but it is not sent in tests, because the protocol |
| // default value is used in tests. |
| TEST_F(SendInitialSettingsOnNewSpdySessionTest, Empty) { |
| spdy::SettingsMap expected_settings; |
| expected_settings[spdy::SETTINGS_HEADER_TABLE_SIZE] = kSpdyMaxHeaderTableSize; |
| expected_settings[spdy::SETTINGS_MAX_CONCURRENT_STREAMS] = |
| kSpdyMaxConcurrentPushedStreams; |
| RunInitialSettingsTest(expected_settings); |
| } |
| |
| // When a setting is set to the protocol default value, |
| // no corresponding value is sent on the wire. |
| TEST_F(SendInitialSettingsOnNewSpdySessionTest, ProtocolDefault) { |
| // Explicitly set protocol default values for the following settings. |
| session_deps_.http2_settings[spdy::SETTINGS_HEADER_TABLE_SIZE] = 4096; |
| session_deps_.http2_settings[spdy::SETTINGS_ENABLE_PUSH] = 1; |
| session_deps_.http2_settings[spdy::SETTINGS_INITIAL_WINDOW_SIZE] = |
| 64 * 1024 - 1; |
| |
| spdy::SettingsMap expected_settings; |
| expected_settings[spdy::SETTINGS_MAX_CONCURRENT_STREAMS] = |
| kSpdyMaxConcurrentPushedStreams; |
| RunInitialSettingsTest(expected_settings); |
| } |
| |
| // Values set in Params::http2_settings overwrite Chromium's default values. |
| TEST_F(SendInitialSettingsOnNewSpdySessionTest, OverwriteValues) { |
| session_deps_.http2_settings[spdy::SETTINGS_HEADER_TABLE_SIZE] = 16 * 1024; |
| session_deps_.http2_settings[spdy::SETTINGS_ENABLE_PUSH] = 0; |
| session_deps_.http2_settings[spdy::SETTINGS_MAX_CONCURRENT_STREAMS] = 42; |
| session_deps_.http2_settings[spdy::SETTINGS_INITIAL_WINDOW_SIZE] = 32 * 1024; |
| session_deps_.http2_settings[spdy::SETTINGS_MAX_HEADER_LIST_SIZE] = |
| 101 * 1024; |
| |
| spdy::SettingsMap expected_settings; |
| expected_settings[spdy::SETTINGS_HEADER_TABLE_SIZE] = 16 * 1024; |
| expected_settings[spdy::SETTINGS_ENABLE_PUSH] = 0; |
| expected_settings[spdy::SETTINGS_MAX_CONCURRENT_STREAMS] = 42; |
| expected_settings[spdy::SETTINGS_INITIAL_WINDOW_SIZE] = 32 * 1024; |
| expected_settings[spdy::SETTINGS_MAX_HEADER_LIST_SIZE] = 101 * 1024; |
| RunInitialSettingsTest(expected_settings); |
| } |
| |
| // Unknown parameters should still be sent to the server. |
| TEST_F(SendInitialSettingsOnNewSpdySessionTest, UnknownSettings) { |
| // The following parameters are not defined in the HTTP/2 specification. |
| session_deps_.http2_settings[7] = 1234; |
| session_deps_.http2_settings[25] = 5678; |
| |
| spdy::SettingsMap expected_settings; |
| expected_settings[spdy::SETTINGS_HEADER_TABLE_SIZE] = kSpdyMaxHeaderTableSize; |
| expected_settings[spdy::SETTINGS_MAX_CONCURRENT_STREAMS] = |
| kSpdyMaxConcurrentPushedStreams; |
| expected_settings[7] = 1234; |
| expected_settings[25] = 5678; |
| RunInitialSettingsTest(expected_settings); |
| } |
| |
| class AltSvcFrameTest : public SpdySessionTest { |
| public: |
| AltSvcFrameTest() |
| : alternative_service_("quic", |
| "alternative.example.org", |
| 443, |
| 86400, |
| spdy::SpdyAltSvcWireFormat::VersionVector()) {} |
| |
| void AddSocketData(const spdy::SpdyAltSvcIR& altsvc_ir) { |
| altsvc_frame_ = spdy_util_.SerializeFrame(altsvc_ir); |
| reads_.push_back(CreateMockRead(altsvc_frame_, 0)); |
| reads_.push_back(MockRead(ASYNC, 0, 1)); |
| |
| data_ = |
| std::make_unique<SequencedSocketData>(reads_, base::span<MockWrite>()); |
| session_deps_.socket_factory->AddSocketDataProvider(data_.get()); |
| } |
| |
| void CreateSpdySession() { |
| session_ = |
| ::net::CreateSpdySession(http_session_.get(), key_, NetLogWithSource()); |
| } |
| |
| spdy::SpdyAltSvcWireFormat::AlternativeService alternative_service_; |
| |
| private: |
| spdy::SpdySerializedFrame altsvc_frame_; |
| std::vector<MockRead> reads_; |
| std::unique_ptr<SequencedSocketData> data_; |
| }; |
| |
| TEST_F(AltSvcFrameTest, ProcessAltSvcFrame) { |
| const char origin[] = "https://mail.example.org"; |
| spdy::SpdyAltSvcIR altsvc_ir(/* stream_id = */ 0); |
| altsvc_ir.add_altsvc(alternative_service_); |
| altsvc_ir.set_origin(origin); |
| AddSocketData(altsvc_ir); |
| AddSSLSocketData(); |
| |
| CreateNetworkSession(); |
| CreateSpdySession(); |
| |
| base::RunLoop().RunUntilIdle(); |
| |
| const url::SchemeHostPort session_origin("https", test_url_.host(), |
| test_url_.EffectiveIntPort()); |
| AlternativeServiceInfoVector altsvc_info_vector = |
| spdy_session_pool_->http_server_properties()->GetAlternativeServiceInfos( |
| session_origin); |
| ASSERT_TRUE(altsvc_info_vector.empty()); |
| |
| altsvc_info_vector = |
| spdy_session_pool_->http_server_properties()->GetAlternativeServiceInfos( |
| url::SchemeHostPort(GURL(origin))); |
| ASSERT_EQ(1u, altsvc_info_vector.size()); |
| AlternativeService alternative_service(kProtoQUIC, "alternative.example.org", |
| 443u); |
| EXPECT_EQ(alternative_service, altsvc_info_vector[0].alternative_service()); |
| } |
| |
| // Regression test for https://crbug.com/736063. |
| TEST_F(AltSvcFrameTest, IgnoreQuicAltSvcWithUnsupportedVersion) { |
| const char origin[] = "https://mail.example.org"; |
| spdy::SpdyAltSvcIR altsvc_ir(/* stream_id = */ 0); |
| spdy::SpdyAltSvcWireFormat::AlternativeService quic_alternative_service( |
| "quic", "alternative.example.org", 443, 86400, |
| spdy::SpdyAltSvcWireFormat::VersionVector()); |
| // TODO(zhongyi): spdy::SpdyAltSvcWireFormat::ParseHeaderFieldValue expects |
| // positve versions while VersionVector allows nonnegative verisons. Fix the |
| // parse function and change the hardcoded invalid version to |
| // quic::QUIC_VERSION_UNSUPPORTED. |
| quic_alternative_service.version.push_back(/* invalid QUIC version */ 1); |
| altsvc_ir.add_altsvc(quic_alternative_service); |
| altsvc_ir.set_origin(origin); |
| AddSocketData(altsvc_ir); |
| AddSSLSocketData(); |
| |
| CreateNetworkSession(); |
| CreateSpdySession(); |
| |
| base::RunLoop().RunUntilIdle(); |
| |
| const url::SchemeHostPort session_origin("https", test_url_.host(), |
| test_url_.EffectiveIntPort()); |
| AlternativeServiceInfoVector altsvc_info_vector = |
| spdy_session_pool_->http_server_properties()->GetAlternativeServiceInfos( |
| session_origin); |
| ASSERT_TRUE(altsvc_info_vector.empty()); |
| |
| altsvc_info_vector = |
| spdy_session_pool_->http_server_properties()->GetAlternativeServiceInfos( |
| url::SchemeHostPort(GURL(origin))); |
| ASSERT_EQ(0u, altsvc_info_vector.size()); |
| } |
| |
| TEST_F(AltSvcFrameTest, DoNotProcessAltSvcFrameForOriginNotCoveredByCert) { |
| const char origin[] = "https://invalid.example.org"; |
| spdy::SpdyAltSvcIR altsvc_ir(/* stream_id = */ 0); |
| altsvc_ir.add_altsvc(alternative_service_); |
| altsvc_ir.set_origin(origin); |
| AddSocketData(altsvc_ir); |
| AddSSLSocketData(); |
| |
| CreateNetworkSession(); |
| CreateSpdySession(); |
| |
| base::RunLoop().RunUntilIdle(); |
| |
| const url::SchemeHostPort session_origin("https", test_url_.host(), |
| test_url_.EffectiveIntPort()); |
| ASSERT_TRUE(spdy_session_pool_->http_server_properties() |
| ->GetAlternativeServiceInfos(session_origin) |
| .empty()); |
| |
| ASSERT_TRUE( |
| spdy_session_pool_->http_server_properties() |
| ->GetAlternativeServiceInfos(url::SchemeHostPort(GURL(origin))) |
| .empty()); |
| } |
| |
| // An ALTSVC frame on stream 0 with empty origin MUST be ignored. |
| // (RFC 7838 Section 4) |
| TEST_F(AltSvcFrameTest, DoNotProcessAltSvcFrameWithEmptyOriginOnStreamZero) { |
| spdy::SpdyAltSvcIR altsvc_ir(/* stream_id = */ 0); |
| altsvc_ir.add_altsvc(alternative_service_); |
| AddSocketData(altsvc_ir); |
| AddSSLSocketData(); |
| |
| CreateNetworkSession(); |
| CreateSpdySession(); |
| |
| base::RunLoop().RunUntilIdle(); |
| |
| const url::SchemeHostPort session_origin("https", test_url_.host(), |
| test_url_.EffectiveIntPort()); |
| ASSERT_TRUE(spdy_session_pool_->http_server_properties() |
| ->GetAlternativeServiceInfos(session_origin) |
| .empty()); |
| } |
| |
| // An ALTSVC frame on a stream other than stream 0 with non-empty origin MUST be |
| // ignored. (RFC 7838 Section 4) |
| TEST_F(AltSvcFrameTest, |
| DoNotProcessAltSvcFrameWithNonEmptyOriginOnNonZeroStream) { |
| spdy::SpdyAltSvcIR altsvc_ir(/* stream_id = */ 1); |
| altsvc_ir.add_altsvc(alternative_service_); |
| altsvc_ir.set_origin("https://mail.example.org"); |
| AddSocketData(altsvc_ir); |
| AddSSLSocketData(); |
| |
| CreateNetworkSession(); |
| CreateSpdySession(); |
| |
| base::RunLoop().RunUntilIdle(); |
| |
| const url::SchemeHostPort session_origin("https", test_url_.host(), |
| test_url_.EffectiveIntPort()); |
| ASSERT_TRUE(spdy_session_pool_->http_server_properties() |
| ->GetAlternativeServiceInfos(session_origin) |
| .empty()); |
| } |
| |
| TEST_F(AltSvcFrameTest, ProcessAltSvcFrameOnActiveStream) { |
| spdy::SpdyAltSvcIR altsvc_ir(/* stream_id = */ 1); |
| altsvc_ir.add_altsvc(alternative_service_); |
| |
| spdy::SpdySerializedFrame altsvc_frame(spdy_util_.SerializeFrame(altsvc_ir)); |
| spdy::SpdySerializedFrame rst( |
| spdy_util_.ConstructSpdyRstStream(1, spdy::ERROR_CODE_REFUSED_STREAM)); |
| MockRead reads[] = { |
| CreateMockRead(altsvc_frame, 1), CreateMockRead(rst, 2), |
| MockRead(ASYNC, 0, 3) // EOF |
| }; |
| |
| const char request_origin[] = "https://mail.example.org"; |
| spdy::SpdySerializedFrame req( |
| spdy_util_.ConstructSpdyGet(request_origin, 1, MEDIUM)); |
| MockWrite writes[] = { |
| CreateMockWrite(req, 0), |
| }; |
| SequencedSocketData data(reads, writes); |
| session_deps_.socket_factory->AddSocketDataProvider(&data); |
| |
| AddSSLSocketData(); |
| |
| CreateNetworkSession(); |
| CreateSpdySession(); |
| |
| base::WeakPtr<SpdyStream> spdy_stream1 = CreateStreamSynchronously( |
| SPDY_REQUEST_RESPONSE_STREAM, session_, GURL(request_origin), MEDIUM, |
| NetLogWithSource()); |
| test::StreamDelegateDoNothing delegate1(spdy_stream1); |
| spdy_stream1->SetDelegate(&delegate1); |
| |
| spdy::SpdyHeaderBlock headers( |
| spdy_util_.ConstructGetHeaderBlock(request_origin)); |
| |
| spdy_stream1->SendRequestHeaders(std::move(headers), NO_MORE_DATA_TO_SEND); |
| |
| base::RunLoop().RunUntilIdle(); |
| |
| const url::SchemeHostPort session_origin("https", test_url_.host(), |
| test_url_.EffectiveIntPort()); |
| ASSERT_TRUE(spdy_session_pool_->http_server_properties() |
| ->GetAlternativeServiceInfos(session_origin) |
| .empty()); |
| |
| AlternativeServiceInfoVector altsvc_info_vector = |
| spdy_session_pool_->http_server_properties()->GetAlternativeServiceInfos( |
| url::SchemeHostPort(GURL(request_origin))); |
| ASSERT_EQ(1u, altsvc_info_vector.size()); |
| EXPECT_EQ(kProtoQUIC, altsvc_info_vector[0].alternative_service().protocol); |
| EXPECT_EQ("alternative.example.org", |
| altsvc_info_vector[0].alternative_service().host); |
| EXPECT_EQ(443u, altsvc_info_vector[0].alternative_service().port); |
| } |
| |
| TEST_F(AltSvcFrameTest, DoNotProcessAltSvcFrameOnStreamWithInsecureOrigin) { |
| spdy::SpdyAltSvcIR altsvc_ir(/* stream_id = */ 1); |
| altsvc_ir.add_altsvc(alternative_service_); |
| |
| spdy::SpdySerializedFrame altsvc_frame(spdy_util_.SerializeFrame(altsvc_ir)); |
| spdy::SpdySerializedFrame rst( |
| spdy_util_.ConstructSpdyRstStream(1, spdy::ERROR_CODE_REFUSED_STREAM)); |
| MockRead reads[] = { |
| CreateMockRead(altsvc_frame, 1), CreateMockRead(rst, 2), |
| MockRead(ASYNC, 0, 3) // EOF |
| }; |
| |
| const char request_origin[] = "http://mail.example.org"; |
| spdy::SpdySerializedFrame req( |
| spdy_util_.ConstructSpdyGet(request_origin, 1, MEDIUM)); |
| MockWrite writes[] = { |
| CreateMockWrite(req, 0), |
| }; |
| SequencedSocketData data(reads, writes); |
| session_deps_.socket_factory->AddSocketDataProvider(&data); |
| |
| AddSSLSocketData(); |
| |
| CreateNetworkSession(); |
| CreateSpdySession(); |
| |
| base::WeakPtr<SpdyStream> spdy_stream1 = CreateStreamSynchronously( |
| SPDY_REQUEST_RESPONSE_STREAM, session_, GURL(request_origin), MEDIUM, |
| NetLogWithSource()); |
| test::StreamDelegateDoNothing delegate1(spdy_stream1); |
| spdy_stream1->SetDelegate(&delegate1); |
| |
| spdy::SpdyHeaderBlock headers( |
| spdy_util_.ConstructGetHeaderBlock(request_origin)); |
| |
| spdy_stream1->SendRequestHeaders(std::move(headers), NO_MORE_DATA_TO_SEND); |
| |
| base::RunLoop().RunUntilIdle(); |
| |
| const url::SchemeHostPort session_origin("https", test_url_.host(), |
| test_url_.EffectiveIntPort()); |
| ASSERT_TRUE(spdy_session_pool_->http_server_properties() |
| ->GetAlternativeServiceInfos(session_origin) |
| .empty()); |
| |
| ASSERT_TRUE(spdy_session_pool_->http_server_properties() |
| ->GetAlternativeServiceInfos( |
| url::SchemeHostPort(GURL(request_origin))) |
| .empty()); |
| } |
| |
| TEST_F(AltSvcFrameTest, DoNotProcessAltSvcFrameOnNonExistentStream) { |
| spdy::SpdyAltSvcIR altsvc_ir(/* stream_id = */ 1); |
| altsvc_ir.add_altsvc(alternative_service_); |
| AddSocketData(altsvc_ir); |
| AddSSLSocketData(); |
| |
| CreateNetworkSession(); |
| CreateSpdySession(); |
| |
| base::RunLoop().RunUntilIdle(); |
| |
| const url::SchemeHostPort session_origin("https", test_url_.host(), |
| test_url_.EffectiveIntPort()); |
| ASSERT_TRUE(spdy_session_pool_->http_server_properties() |
| ->GetAlternativeServiceInfos(session_origin) |
| .empty()); |
| } |
| |
| // Regression test for https://crbug.com/810404. |
| TEST_F(AltSvcFrameTest, InvalidOrigin) { |
| // This origin parses to an invalid GURL with https scheme. |
| const std::string origin("https:?"); |
| const GURL origin_gurl(origin); |
| EXPECT_FALSE(origin_gurl.is_valid()); |
| EXPECT_TRUE(origin_gurl.host().empty()); |
| EXPECT_TRUE(origin_gurl.SchemeIs(url::kHttpsScheme)); |
| |
| spdy::SpdyAltSvcIR altsvc_ir(/* stream_id = */ 0); |
| altsvc_ir.add_altsvc(alternative_service_); |
| altsvc_ir.set_origin(origin); |
| AddSocketData(altsvc_ir); |
| AddSSLSocketData(); |
| |
| CreateNetworkSession(); |
| CreateSpdySession(); |
| |
| base::RunLoop().RunUntilIdle(); |
| |
| const url::SchemeHostPort session_origin("https", test_url_.host(), |
| test_url_.EffectiveIntPort()); |
| AlternativeServiceInfoVector altsvc_info_vector = |
| spdy_session_pool_->http_server_properties()->GetAlternativeServiceInfos( |
| session_origin); |
| EXPECT_TRUE(altsvc_info_vector.empty()); |
| } |
| |
| TEST(MapFramerErrorToProtocolError, MapsValues) { |
| CHECK_EQ(SPDY_ERROR_INVALID_CONTROL_FRAME, |
| MapFramerErrorToProtocolError( |
| http2::Http2DecoderAdapter::SPDY_INVALID_CONTROL_FRAME)); |
| CHECK_EQ(SPDY_ERROR_INVALID_DATA_FRAME_FLAGS, |
| MapFramerErrorToProtocolError( |
| http2::Http2DecoderAdapter::SPDY_INVALID_DATA_FRAME_FLAGS)); |
| CHECK_EQ(SPDY_ERROR_GOAWAY_FRAME_CORRUPT, |
| MapFramerErrorToProtocolError( |
| http2::Http2DecoderAdapter::SPDY_GOAWAY_FRAME_CORRUPT)); |
| CHECK_EQ(SPDY_ERROR_UNEXPECTED_FRAME, |
| MapFramerErrorToProtocolError( |
| http2::Http2DecoderAdapter::SPDY_UNEXPECTED_FRAME)); |
| } |
| |
| TEST(MapFramerErrorToNetError, MapsValue) { |
| CHECK_EQ(ERR_SPDY_PROTOCOL_ERROR, |
| MapFramerErrorToNetError( |
| http2::Http2DecoderAdapter::SPDY_INVALID_CONTROL_FRAME)); |
| CHECK_EQ(ERR_SPDY_COMPRESSION_ERROR, |
| MapFramerErrorToNetError( |
| http2::Http2DecoderAdapter::SPDY_COMPRESS_FAILURE)); |
| CHECK_EQ(ERR_SPDY_COMPRESSION_ERROR, |
| MapFramerErrorToNetError( |
| http2::Http2DecoderAdapter::SPDY_DECOMPRESS_FAILURE)); |
| CHECK_EQ(ERR_SPDY_FRAME_SIZE_ERROR, |
| MapFramerErrorToNetError( |
| http2::Http2DecoderAdapter::SPDY_CONTROL_PAYLOAD_TOO_LARGE)); |
| CHECK_EQ(ERR_SPDY_FRAME_SIZE_ERROR, |
| MapFramerErrorToNetError( |
| http2::Http2DecoderAdapter::SPDY_OVERSIZED_PAYLOAD)); |
| } |
| |
| TEST(MapRstStreamStatusToProtocolError, MapsValues) { |
| CHECK_EQ(STATUS_CODE_PROTOCOL_ERROR, |
| MapRstStreamStatusToProtocolError(spdy::ERROR_CODE_PROTOCOL_ERROR)); |
| CHECK_EQ( |
| STATUS_CODE_FRAME_SIZE_ERROR, |
| MapRstStreamStatusToProtocolError(spdy::ERROR_CODE_FRAME_SIZE_ERROR)); |
| CHECK_EQ( |
| STATUS_CODE_ENHANCE_YOUR_CALM, |
| MapRstStreamStatusToProtocolError(spdy::ERROR_CODE_ENHANCE_YOUR_CALM)); |
| CHECK_EQ( |
| STATUS_CODE_INADEQUATE_SECURITY, |
| MapRstStreamStatusToProtocolError(spdy::ERROR_CODE_INADEQUATE_SECURITY)); |
| CHECK_EQ( |
| STATUS_CODE_HTTP_1_1_REQUIRED, |
| MapRstStreamStatusToProtocolError(spdy::ERROR_CODE_HTTP_1_1_REQUIRED)); |
| } |
| |
| TEST(MapNetErrorToGoAwayStatus, MapsValue) { |
| CHECK_EQ(spdy::ERROR_CODE_INADEQUATE_SECURITY, |
| MapNetErrorToGoAwayStatus(ERR_SPDY_INADEQUATE_TRANSPORT_SECURITY)); |
| CHECK_EQ(spdy::ERROR_CODE_FLOW_CONTROL_ERROR, |
| MapNetErrorToGoAwayStatus(ERR_SPDY_FLOW_CONTROL_ERROR)); |
| CHECK_EQ(spdy::ERROR_CODE_PROTOCOL_ERROR, |
| MapNetErrorToGoAwayStatus(ERR_SPDY_PROTOCOL_ERROR)); |
| CHECK_EQ(spdy::ERROR_CODE_COMPRESSION_ERROR, |
| MapNetErrorToGoAwayStatus(ERR_SPDY_COMPRESSION_ERROR)); |
| CHECK_EQ(spdy::ERROR_CODE_FRAME_SIZE_ERROR, |
| MapNetErrorToGoAwayStatus(ERR_SPDY_FRAME_SIZE_ERROR)); |
| CHECK_EQ(spdy::ERROR_CODE_PROTOCOL_ERROR, |
| MapNetErrorToGoAwayStatus(ERR_UNEXPECTED)); |
| } |
| |
| namespace { |
| |
| class TestSSLConfigService : public SSLConfigService { |
| public: |
| TestSSLConfigService() {} |
| ~TestSSLConfigService() override = default; |
| |
| void GetSSLConfig(SSLConfig* config) override { *config = config_; } |
| |
| // Returns true if |hostname| is in domains_for_pooling_. This is a simpler |
| // implementation than the production implementation in SSLConfigServiceMojo. |
| bool CanShareConnectionWithClientCerts( |
| const std::string& hostname) const override { |
| for (const std::string& domain : domains_for_pooling_) { |
| if (domain == hostname) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| void SetDomainsForPooling(const std::vector<std::string>& domains) { |
| domains_for_pooling_ = domains; |
| } |
| |
| private: |
| SSLConfig config_; |
| std::vector<std::string> domains_for_pooling_; |
| }; |
| |
| } // namespace |
| |
| TEST(CanPoolTest, CanPool) { |
| // Load a cert that is valid for: |
| // www.example.org |
| // mail.example.org |
| // mail.example.com |
| |
| TransportSecurityState tss; |
| TestSSLConfigService ssl_config_service; |
| SSLInfo ssl_info; |
| ssl_info.cert = ImportCertFromFile(GetTestCertsDirectory(), |
| "spdy_pooling.pem"); |
| |
| EXPECT_TRUE(SpdySession::CanPool(&tss, ssl_info, ssl_config_service, |
| "www.example.org", "www.example.org")); |
| EXPECT_TRUE(SpdySession::CanPool(&tss, ssl_info, ssl_config_service, |
| "www.example.org", "mail.example.org")); |
| EXPECT_TRUE(SpdySession::CanPool(&tss, ssl_info, ssl_config_service, |
| "www.example.org", "mail.example.com")); |
| EXPECT_FALSE(SpdySession::CanPool(&tss, ssl_info, ssl_config_service, |
| "www.example.org", "mail.google.com")); |
| } |
| |
| TEST(CanPoolTest, CanPoolExpectCT) { |
| base::test::ScopedFeatureList feature_list; |
| feature_list.InitAndEnableFeature( |
| TransportSecurityState::kDynamicExpectCTFeature); |
| // Load a cert that is valid for: |
| // www.example.org |
| // mail.example.org |
| // mail.example.com |
| |
| TransportSecurityState tss; |
| TestSSLConfigService ssl_config_service; |
| SSLInfo ssl_info; |
| ssl_info.cert = |
| ImportCertFromFile(GetTestCertsDirectory(), "spdy_pooling.pem"); |
| ssl_info.unverified_cert = ssl_info.cert; |
| ssl_info.ct_policy_compliance = |
| ct::CTPolicyCompliance::CT_POLICY_NOT_ENOUGH_SCTS; |
| ssl_info.is_issued_by_known_root = true; |
| |
| EXPECT_TRUE(SpdySession::CanPool(&tss, ssl_info, ssl_config_service, |
| "www.example.org", "www.example.org")); |
| |
| const base::Time current_time(base::Time::Now()); |
| const base::Time expiry = current_time + base::TimeDelta::FromSeconds(1000); |
| ssl_info.ct_policy_compliance = |
| ct::CTPolicyCompliance::CT_POLICY_NOT_ENOUGH_SCTS; |
| |
| // A different Expect-CT enabled host should not be allowed to pool. |
| tss.AddExpectCT("mail.example.org", expiry, true, GURL()); |
| EXPECT_FALSE(SpdySession::CanPool(&tss, ssl_info, ssl_config_service, |
| "www.example.org", "mail.example.org")); |
| // A report-only Expect-CT configuration should not prevent pooling. |
| tss.AddExpectCT("mail.example.org", expiry, false, |
| GURL("https://report.test")); |
| EXPECT_TRUE(SpdySession::CanPool(&tss, ssl_info, ssl_config_service, |
| "www.example.org", "mail.example.org")); |
| // If Expect-CT becomes enabled for the same host for which the connection was |
| // already made, subsequent connections to that host should not be allowed to |
| // pool. |
| tss.AddExpectCT("www.example.org", expiry, true, GURL()); |
| EXPECT_FALSE(SpdySession::CanPool(&tss, ssl_info, ssl_config_service, |
| "www.example.org", "www.example.org")); |
| } |
| |
| TEST(CanPoolTest, CanNotPoolWithCertErrors) { |
| // Load a cert that is valid for: |
| // www.example.org |
| // mail.example.org |
| // mail.example.com |
| |
| TransportSecurityState tss; |
| TestSSLConfigService ssl_config_service; |
| SSLInfo ssl_info; |
| ssl_info.cert = ImportCertFromFile(GetTestCertsDirectory(), |
| "spdy_pooling.pem"); |
| ssl_info.cert_status = CERT_STATUS_REVOKED; |
| |
| EXPECT_FALSE(SpdySession::CanPool(&tss, ssl_info, ssl_config_service, |
| "www.example.org", "mail.example.org")); |
| } |
| |
| TEST(CanPoolTest, CanNotPoolWithClientCerts) { |
| // Load a cert that is valid for: |
| // www.example.org |
| // mail.example.org |
| // mail.example.com |
| |
| TransportSecurityState tss; |
| TestSSLConfigService ssl_config_service; |
| SSLInfo ssl_info; |
| ssl_info.cert = ImportCertFromFile(GetTestCertsDirectory(), |
| "spdy_pooling.pem"); |
| ssl_info.client_cert_sent = true; |
| |
| EXPECT_FALSE(SpdySession::CanPool(&tss, ssl_info, ssl_config_service, |
| "www.example.org", "mail.example.org")); |
| } |
| |
| TEST(CanPoolTest, CanNotPoolAcrossETLDsWithChannelID) { |
| // Load a cert that is valid for: |
| // www.example.org |
| // mail.example.org |
| // mail.example.com |
| |
| TransportSecurityState tss; |
| TestSSLConfigService ssl_config_service; |
| SSLInfo ssl_info; |
| ssl_info.cert = ImportCertFromFile(GetTestCertsDirectory(), |
| "spdy_pooling.pem"); |
| ssl_info.channel_id_sent = true; |
| |
| EXPECT_TRUE(SpdySession::CanPool(&tss, ssl_info, ssl_config_service, |
| "www.example.org", "mail.example.org")); |
| EXPECT_FALSE(SpdySession::CanPool(&tss, ssl_info, ssl_config_service, |
| "www.example.org", "www.example.com")); |
| } |
| |
| TEST(CanPoolTest, CanNotPoolWithBadPins) { |
| uint8_t primary_pin = 1; |
| uint8_t backup_pin = 2; |
| uint8_t bad_pin = 3; |
| TransportSecurityState tss; |
| test::AddPin(&tss, "mail.example.org", primary_pin, backup_pin); |
| |
| TestSSLConfigService ssl_config_service; |
| SSLInfo ssl_info; |
| ssl_info.cert = ImportCertFromFile(GetTestCertsDirectory(), |
| "spdy_pooling.pem"); |
| ssl_info.is_issued_by_known_root = true; |
| ssl_info.public_key_hashes.push_back(test::GetTestHashValue(bad_pin)); |
| |
| EXPECT_FALSE(SpdySession::CanPool(&tss, ssl_info, ssl_config_service, |
| "www.example.org", "mail.example.org")); |
| } |
| |
| TEST(CanPoolTest, CanNotPoolWithBadCTWhenCTRequired) { |
| using testing::Return; |
| using CTRequirementLevel = |
| TransportSecurityState::RequireCTDelegate::CTRequirementLevel; |
| |
| TestSSLConfigService ssl_config_service; |
| SSLInfo ssl_info; |
| ssl_info.cert = |
| ImportCertFromFile(GetTestCertsDirectory(), "spdy_pooling.pem"); |
| ssl_info.is_issued_by_known_root = true; |
| ssl_info.public_key_hashes.push_back(test::GetTestHashValue(1)); |
| ssl_info.ct_policy_compliance = |
| ct::CTPolicyCompliance::CT_POLICY_NOT_ENOUGH_SCTS; |
| |
| MockRequireCTDelegate require_ct_delegate; |
| EXPECT_CALL(require_ct_delegate, IsCTRequiredForHost("www.example.org", _, _)) |
| .WillRepeatedly(Return(CTRequirementLevel::NOT_REQUIRED)); |
| EXPECT_CALL(require_ct_delegate, |
| IsCTRequiredForHost("mail.example.org", _, _)) |
| .WillRepeatedly(Return(CTRequirementLevel::REQUIRED)); |
| |
| TransportSecurityState tss; |
| tss.SetRequireCTDelegate(&require_ct_delegate); |
| |
| EXPECT_FALSE(SpdySession::CanPool(&tss, ssl_info, ssl_config_service, |
| "www.example.org", "mail.example.org")); |
| } |
| |
| TEST(CanPoolTest, CanPoolWithBadCTWhenCTNotRequired) { |
| using testing::Return; |
| using CTRequirementLevel = |
| TransportSecurityState::RequireCTDelegate::CTRequirementLevel; |
| |
| TestSSLConfigService ssl_config_service; |
| SSLInfo ssl_info; |
| ssl_info.cert = |
| ImportCertFromFile(GetTestCertsDirectory(), "spdy_pooling.pem"); |
| ssl_info.is_issued_by_known_root = true; |
| ssl_info.public_key_hashes.push_back(test::GetTestHashValue(1)); |
| ssl_info.ct_policy_compliance = |
| ct::CTPolicyCompliance::CT_POLICY_NOT_ENOUGH_SCTS; |
| |
| MockRequireCTDelegate require_ct_delegate; |
| EXPECT_CALL(require_ct_delegate, IsCTRequiredForHost("www.example.org", _, _)) |
| .WillRepeatedly(Return(CTRequirementLevel::NOT_REQUIRED)); |
| EXPECT_CALL(require_ct_delegate, |
| IsCTRequiredForHost("mail.example.org", _, _)) |
| .WillRepeatedly(Return(CTRequirementLevel::NOT_REQUIRED)); |
| |
| TransportSecurityState tss; |
| tss.SetRequireCTDelegate(&require_ct_delegate); |
| |
| EXPECT_TRUE(SpdySession::CanPool(&tss, ssl_info, ssl_config_service, |
| "www.example.org", "mail.example.org")); |
| } |
| |
| TEST(CanPoolTest, CanPoolWithGoodCTWhenCTRequired) { |
| using testing::Return; |
| using CTRequirementLevel = |
| TransportSecurityState::RequireCTDelegate::CTRequirementLevel; |
| |
| TestSSLConfigService ssl_config_service; |
| SSLInfo ssl_info; |
| ssl_info.cert = |
| ImportCertFromFile(GetTestCertsDirectory(), "spdy_pooling.pem"); |
| ssl_info.is_issued_by_known_root = true; |
| ssl_info.public_key_hashes.push_back(test::GetTestHashValue(1)); |
| ssl_info.ct_policy_compliance = |
| ct::CTPolicyCompliance::CT_POLICY_COMPLIES_VIA_SCTS; |
| |
| MockRequireCTDelegate require_ct_delegate; |
| EXPECT_CALL(require_ct_delegate, IsCTRequiredForHost("www.example.org", _, _)) |
| .WillRepeatedly(Return(CTRequirementLevel::NOT_REQUIRED)); |
| EXPECT_CALL(require_ct_delegate, |
| IsCTRequiredForHost("mail.example.org", _, _)) |
| .WillRepeatedly(Return(CTRequirementLevel::REQUIRED)); |
| |
| TransportSecurityState tss; |
| tss.SetRequireCTDelegate(&require_ct_delegate); |
| |
| EXPECT_TRUE(SpdySession::CanPool(&tss, ssl_info, ssl_config_service, |
| "www.example.org", "mail.example.org")); |
| } |
| |
| TEST(CanPoolTest, CanPoolWithAcceptablePins) { |
| uint8_t primary_pin = 1; |
| uint8_t backup_pin = 2; |
| TransportSecurityState tss; |
| test::AddPin(&tss, "mail.example.org", primary_pin, backup_pin); |
| |
| TestSSLConfigService ssl_config_service; |
| SSLInfo ssl_info; |
| ssl_info.cert = ImportCertFromFile(GetTestCertsDirectory(), |
| "spdy_pooling.pem"); |
| ssl_info.is_issued_by_known_root = true; |
| ssl_info.public_key_hashes.push_back(test::GetTestHashValue(primary_pin)); |
| |
| EXPECT_TRUE(SpdySession::CanPool(&tss, ssl_info, ssl_config_service, |
| "www.example.org", "mail.example.org")); |
| } |
| |
| TEST(CanPoolTest, CanPoolWithClientCertsAndPolicy) { |
| TransportSecurityState tss; |
| SSLInfo ssl_info; |
| ssl_info.cert = |
| ImportCertFromFile(GetTestCertsDirectory(), "spdy_pooling.pem"); |
| ssl_info.client_cert_sent = true; |
| |
| // Configure ssl_config_service so that CanShareConnectionWithClientCerts |
| // returns true for www.example.org and mail.example.org. |
| TestSSLConfigService ssl_config_service; |
| ssl_config_service.SetDomainsForPooling( |
| {"www.example.org", "mail.example.org"}); |
| |
| // Test that CanPool returns true when client certs are enabled and |
| // CanShareConnectionWithClientCerts returns true for both hostnames, but not |
| // just one hostname. |
| EXPECT_TRUE(SpdySession::CanPool(&tss, ssl_info, ssl_config_service, |
| "www.example.org", "mail.example.org")); |
| EXPECT_FALSE(SpdySession::CanPool(&tss, ssl_info, ssl_config_service, |
| "www.example.org", "mail.example.com")); |
| EXPECT_FALSE(SpdySession::CanPool(&tss, ssl_info, ssl_config_service, |
| "mail.example.com", "www.example.org")); |
| } |
| |
| TEST(RecordPushedStreamHistogramTest, VaryResponseHeader) { |
| struct { |
| size_t num_headers; |
| const char* headers[2]; |
| int expected_bucket; |
| } test_cases[] = {{0, {}, 0}, |
| {1, {"foo", "bar"}, 0}, |
| {1, {"vary", ""}, 1}, |
| {1, {"vary", "*"}, 2}, |
| {1, {"vary", "accept-encoding"}, 3}, |
| {1, {"vary", "foo , accept-encoding ,bar"}, 4}, |
| {1, {"vary", "\taccept-encoding, foo"}, 4}, |
| {1, {"vary", "foo"}, 5}, |
| {1, {"vary", "fooaccept-encoding"}, 5}, |
| {1, {"vary", "foo, accept-encodingbar"}, 5}}; |
| |
| for (size_t i = 0; i < arraysize(test_cases); ++i) { |
| spdy::SpdyHeaderBlock headers; |
| for (size_t j = 0; j < test_cases[i].num_headers; ++j) { |
| headers[test_cases[i].headers[2 * j]] = test_cases[i].headers[2 * j + 1]; |
| } |
| base::HistogramTester histograms; |
| histograms.ExpectTotalCount("Net.PushedStreamVaryResponseHeader", 0); |
| SpdySession::RecordPushedStreamVaryResponseHeaderHistogram(headers); |
| histograms.ExpectTotalCount("Net.PushedStreamVaryResponseHeader", 1); |
| histograms.ExpectBucketCount("Net.PushedStreamVaryResponseHeader", |
| test_cases[i].expected_bucket, 1); |
| // Adding an unrelated header field should not change how Vary is parsed. |
| headers["foo"] = "bar"; |
| SpdySession::RecordPushedStreamVaryResponseHeaderHistogram(headers); |
| histograms.ExpectTotalCount("Net.PushedStreamVaryResponseHeader", 2); |
| histograms.ExpectBucketCount("Net.PushedStreamVaryResponseHeader", |
| test_cases[i].expected_bucket, 2); |
| } |
| } |
| |
| } // namespace net |