// 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 "net/base/host_cache.h" #include "net/base/ip_endpoint.h" #include "net/base/net_log_unittest.h" #include "net/spdy/spdy_io_buffer.h" #include "net/spdy/spdy_session_pool.h" #include "net/spdy/spdy_stream.h" #include "net/spdy/spdy_test_util_spdy2.h" #include "testing/platform_test.h" using namespace net::test_spdy2; namespace net { // TODO(cbentzel): Expose compression setter/getter in public SpdySession // interface rather than going through all these contortions. class SpdySessionSpdy2Test : public PlatformTest { protected: virtual void SetUp() { SpdySession::set_default_protocol(SSLClientSocket::kProtoSPDY2); } private: SpdyTestStateHelper spdy_state_; }; class TestSpdyStreamDelegate : public net::SpdyStream::Delegate { public: explicit TestSpdyStreamDelegate(const CompletionCallback& callback) : callback_(callback) {} virtual ~TestSpdyStreamDelegate() {} virtual bool OnSendHeadersComplete(int status) { return true; } virtual int OnSendBody() { return ERR_UNEXPECTED; } virtual int OnSendBodyComplete(int /*status*/, bool* /*eof*/) { return ERR_UNEXPECTED; } virtual int OnResponseReceived(const spdy::SpdyHeaderBlock& response, base::Time response_time, int status) { return status; } virtual void OnDataReceived(const char* buffer, int bytes) { } virtual void OnDataSent(int length) { } virtual void OnClose(int status) { CompletionCallback callback = callback_; callback_.Reset(); callback.Run(OK); } virtual void set_chunk_callback(net::ChunkCallback *) {} private: CompletionCallback callback_; }; // Test the SpdyIOBuffer class. TEST_F(SpdySessionSpdy2Test, SpdyIOBuffer) { std::priority_queue queue_; const size_t kQueueSize = 100; // Insert 100 items; pri 100 to 1. for (size_t index = 0; index < kQueueSize; ++index) { SpdyIOBuffer buffer(new IOBuffer(), 0, kQueueSize - index, NULL); queue_.push(buffer); } // Insert several priority 0 items last. const size_t kNumDuplicates = 12; IOBufferWithSize* buffers[kNumDuplicates]; for (size_t index = 0; index < kNumDuplicates; ++index) { buffers[index] = new IOBufferWithSize(index+1); queue_.push(SpdyIOBuffer(buffers[index], buffers[index]->size(), 0, NULL)); } EXPECT_EQ(kQueueSize + kNumDuplicates, queue_.size()); // Verify the P0 items come out in FIFO order. for (size_t index = 0; index < kNumDuplicates; ++index) { SpdyIOBuffer buffer = queue_.top(); EXPECT_EQ(0, buffer.priority()); EXPECT_EQ(index + 1, buffer.size()); queue_.pop(); } int priority = 1; while (queue_.size()) { SpdyIOBuffer buffer = queue_.top(); EXPECT_EQ(priority++, buffer.priority()); queue_.pop(); } } TEST_F(SpdySessionSpdy2Test, GoAway) { SpdySessionDependencies session_deps; session_deps.host_resolver->set_synchronous_mode(true); MockConnect connect_data(SYNCHRONOUS, OK); scoped_ptr goaway(ConstructSpdyGoAway()); MockRead reads[] = { CreateMockRead(*goaway), MockRead(SYNCHRONOUS, 0, 0) // EOF }; StaticSocketDataProvider data(reads, arraysize(reads), NULL, 0); data.set_connect_data(connect_data); session_deps.socket_factory->AddSocketDataProvider(&data); SSLSocketDataProvider ssl(SYNCHRONOUS, OK); ssl.SetNextProto(SSLClientSocket::kProtoSPDY2); session_deps.socket_factory->AddSSLSocketDataProvider(&ssl); scoped_refptr http_session( SpdySessionDependencies::SpdyCreateSession(&session_deps)); const std::string kTestHost("www.foo.com"); const int kTestPort = 80; HostPortPair test_host_port_pair(kTestHost, kTestPort); HostPortProxyPair pair(test_host_port_pair, ProxyServer::Direct()); SpdySessionPool* spdy_session_pool(http_session->spdy_session_pool()); EXPECT_FALSE(spdy_session_pool->HasSession(pair)); scoped_refptr session = spdy_session_pool->Get(pair, BoundNetLog()); EXPECT_TRUE(spdy_session_pool->HasSession(pair)); scoped_refptr transport_params( new TransportSocketParams(test_host_port_pair, MEDIUM, false, false)); scoped_ptr connection(new ClientSocketHandle); EXPECT_EQ(OK, connection->Init(test_host_port_pair.ToString(), transport_params, MEDIUM, CompletionCallback(), http_session->GetTransportSocketPool( HttpNetworkSession::NORMAL_SOCKET_POOL), BoundNetLog())); EXPECT_EQ(OK, session->InitializeWithSocket(connection.release(), false, OK)); EXPECT_EQ(2, session->GetProtocolVersion()); // Flush the SpdySession::OnReadComplete() task. MessageLoop::current()->RunAllPending(); EXPECT_FALSE(spdy_session_pool->HasSession(pair)); scoped_refptr session2 = spdy_session_pool->Get(pair, BoundNetLog()); // Delete the first session. session = NULL; // Delete the second session. spdy_session_pool->Remove(session2); session2 = NULL; } TEST_F(SpdySessionSpdy2Test, Ping) { SpdySessionDependencies session_deps; session_deps.host_resolver->set_synchronous_mode(true); MockConnect connect_data(SYNCHRONOUS, OK); scoped_ptr read_ping(ConstructSpdyPing()); MockRead reads[] = { CreateMockRead(*read_ping), CreateMockRead(*read_ping), MockRead(SYNCHRONOUS, 0, 0) // EOF }; scoped_ptr write_ping(ConstructSpdyPing()); MockRead writes[] = { CreateMockRead(*write_ping), CreateMockRead(*write_ping), }; StaticSocketDataProvider data( reads, arraysize(reads), writes, arraysize(writes)); data.set_connect_data(connect_data); session_deps.socket_factory->AddSocketDataProvider(&data); SSLSocketDataProvider ssl(SYNCHRONOUS, OK); session_deps.socket_factory->AddSSLSocketDataProvider(&ssl); scoped_refptr http_session( SpdySessionDependencies::SpdyCreateSession(&session_deps)); static const char kStreamUrl[] = "http://www.google.com/"; GURL url(kStreamUrl); const std::string kTestHost("www.google.com"); const int kTestPort = 80; HostPortPair test_host_port_pair(kTestHost, kTestPort); HostPortProxyPair pair(test_host_port_pair, ProxyServer::Direct()); SpdySessionPool* spdy_session_pool(http_session->spdy_session_pool()); EXPECT_FALSE(spdy_session_pool->HasSession(pair)); scoped_refptr session = spdy_session_pool->Get(pair, BoundNetLog()); EXPECT_TRUE(spdy_session_pool->HasSession(pair)); scoped_refptr transport_params( new TransportSocketParams(test_host_port_pair, MEDIUM, false, false)); scoped_ptr connection(new ClientSocketHandle); EXPECT_EQ(OK, connection->Init(test_host_port_pair.ToString(), transport_params, MEDIUM, CompletionCallback(), http_session->GetTransportSocketPool( HttpNetworkSession::NORMAL_SOCKET_POOL), BoundNetLog())); EXPECT_EQ(OK, session->InitializeWithSocket(connection.release(), false, OK)); scoped_refptr spdy_stream1; TestCompletionCallback callback1; EXPECT_EQ(OK, session->CreateStream(url, MEDIUM, &spdy_stream1, BoundNetLog(), callback1.callback())); scoped_ptr delegate( new TestSpdyStreamDelegate(callback1.callback())); spdy_stream1->SetDelegate(delegate.get()); base::TimeTicks before_ping_time = base::TimeTicks::Now(); // Enable sending of PING. SpdySession::set_enable_ping_based_connection_checking(true); session->set_connection_at_risk_of_loss_time(base::TimeDelta::FromSeconds(0)); session->set_trailing_ping_delay_time(base::TimeDelta::FromSeconds(0)); session->set_hung_interval(base::TimeDelta::FromMilliseconds(50)); session->SendPrefacePingIfNoneInFlight(); EXPECT_EQ(OK, callback1.WaitForResult()); session->CheckPingStatus(before_ping_time); EXPECT_EQ(0, session->pings_in_flight()); EXPECT_GT(session->next_ping_id(), static_cast(1)); EXPECT_FALSE(session->trailing_ping_pending()); EXPECT_FALSE(session->check_ping_status_pending()); EXPECT_GE(session->received_data_time(), before_ping_time); EXPECT_FALSE(spdy_session_pool->HasSession(pair)); // Delete the first session. session = NULL; } TEST_F(SpdySessionSpdy2Test, FailedPing) { SpdySessionDependencies session_deps; session_deps.host_resolver->set_synchronous_mode(true); MockConnect connect_data(SYNCHRONOUS, OK); scoped_ptr read_ping(ConstructSpdyPing()); MockRead reads[] = { CreateMockRead(*read_ping), MockRead(SYNCHRONOUS, 0, 0) // EOF }; scoped_ptr write_ping(ConstructSpdyPing()); MockRead writes[] = { CreateMockRead(*write_ping), }; StaticSocketDataProvider data( reads, arraysize(reads), writes, arraysize(writes)); data.set_connect_data(connect_data); session_deps.socket_factory->AddSocketDataProvider(&data); SSLSocketDataProvider ssl(SYNCHRONOUS, OK); session_deps.socket_factory->AddSSLSocketDataProvider(&ssl); scoped_refptr http_session( SpdySessionDependencies::SpdyCreateSession(&session_deps)); static const char kStreamUrl[] = "http://www.gmail.com/"; GURL url(kStreamUrl); const std::string kTestHost("www.gmail.com"); const int kTestPort = 80; HostPortPair test_host_port_pair(kTestHost, kTestPort); HostPortProxyPair pair(test_host_port_pair, ProxyServer::Direct()); SpdySessionPool* spdy_session_pool(http_session->spdy_session_pool()); EXPECT_FALSE(spdy_session_pool->HasSession(pair)); scoped_refptr session = spdy_session_pool->Get(pair, BoundNetLog()); EXPECT_TRUE(spdy_session_pool->HasSession(pair)); scoped_refptr transport_params( new TransportSocketParams(test_host_port_pair, MEDIUM, false, false)); scoped_ptr connection(new ClientSocketHandle); EXPECT_EQ(OK, connection->Init(test_host_port_pair.ToString(), transport_params, MEDIUM, CompletionCallback(), http_session->GetTransportSocketPool( HttpNetworkSession::NORMAL_SOCKET_POOL), BoundNetLog())); EXPECT_EQ(OK, session->InitializeWithSocket(connection.release(), false, OK)); scoped_refptr spdy_stream1; TestCompletionCallback callback1; EXPECT_EQ(OK, session->CreateStream(url, MEDIUM, &spdy_stream1, BoundNetLog(), callback1.callback())); scoped_ptr delegate( new TestSpdyStreamDelegate(callback1.callback())); spdy_stream1->SetDelegate(delegate.get()); // Enable sending of PING. SpdySession::set_enable_ping_based_connection_checking(true); session->set_connection_at_risk_of_loss_time(base::TimeDelta::FromSeconds(0)); session->set_trailing_ping_delay_time(base::TimeDelta::FromSeconds(0)); session->set_hung_interval(base::TimeDelta::FromSeconds(0)); // Send a PING frame. session->WritePingFrame(1); EXPECT_LT(0, session->pings_in_flight()); EXPECT_GT(session->next_ping_id(), static_cast(1)); EXPECT_TRUE(session->check_ping_status_pending()); // Assert session is not closed. EXPECT_FALSE(session->IsClosed()); EXPECT_LT(0u, session->num_active_streams()); EXPECT_TRUE(spdy_session_pool->HasSession(pair)); // We set last time we have received any data in 1 sec less than now. // CheckPingStatus will trigger timeout because hung interval is zero. base::TimeTicks now = base::TimeTicks::Now(); session->received_data_time_ = now - base::TimeDelta::FromSeconds(1); session->CheckPingStatus(now); EXPECT_TRUE(session->IsClosed()); EXPECT_EQ(0u, session->num_active_streams()); EXPECT_EQ(0u, session->num_unclaimed_pushed_streams()); EXPECT_FALSE(spdy_session_pool->HasSession(pair)); // Delete the first session. session = NULL; } class StreamReleaserCallback : public TestCompletionCallbackBase { public: StreamReleaserCallback(SpdySession* session, SpdyStream* first_stream) : session_(session), first_stream_(first_stream), ALLOW_THIS_IN_INITIALIZER_LIST(callback_( base::Bind(&StreamReleaserCallback::OnComplete, base::Unretained(this)))) { } virtual ~StreamReleaserCallback() {} scoped_refptr* stream() { return &stream_; } const CompletionCallback& callback() const { return callback_; } private: void OnComplete(int result) { session_->CloseSessionOnError(ERR_FAILED, false, "On complete."); session_ = NULL; first_stream_->Cancel(); first_stream_ = NULL; stream_->Cancel(); stream_ = NULL; SetResult(result); } scoped_refptr session_; scoped_refptr first_stream_; scoped_refptr stream_; CompletionCallback callback_; }; // TODO(kristianm): Could also test with more sessions where some are idle, // and more than one session to a HostPortPair. TEST_F(SpdySessionSpdy2Test, CloseIdleSessions) { SpdySessionDependencies session_deps; scoped_refptr http_session( SpdySessionDependencies::SpdyCreateSession(&session_deps)); SpdySessionPool* spdy_session_pool(http_session->spdy_session_pool()); // Set up session 1 const std::string kTestHost1("http://www.a.com"); HostPortPair test_host_port_pair1(kTestHost1, 80); HostPortProxyPair pair1(test_host_port_pair1, ProxyServer::Direct()); scoped_refptr session1 = spdy_session_pool->Get(pair1, BoundNetLog()); scoped_refptr spdy_stream1; TestCompletionCallback callback1; GURL url1(kTestHost1); EXPECT_EQ(OK, session1->CreateStream(url1, MEDIUM, /* priority, not important */ &spdy_stream1, BoundNetLog(), callback1.callback())); // Set up session 2 const std::string kTestHost2("http://www.b.com"); HostPortPair test_host_port_pair2(kTestHost2, 80); HostPortProxyPair pair2(test_host_port_pair2, ProxyServer::Direct()); scoped_refptr session2 = spdy_session_pool->Get(pair2, BoundNetLog()); scoped_refptr spdy_stream2; TestCompletionCallback callback2; GURL url2(kTestHost2); EXPECT_EQ(OK, session2->CreateStream( url2, MEDIUM, /* priority, not important */ &spdy_stream2, BoundNetLog(), callback2.callback())); // Set up session 3 const std::string kTestHost3("http://www.c.com"); HostPortPair test_host_port_pair3(kTestHost3, 80); HostPortProxyPair pair3(test_host_port_pair3, ProxyServer::Direct()); scoped_refptr session3 = spdy_session_pool->Get(pair3, BoundNetLog()); scoped_refptr spdy_stream3; TestCompletionCallback callback3; GURL url3(kTestHost3); EXPECT_EQ(OK, session3->CreateStream( url3, MEDIUM, /* priority, not important */ &spdy_stream3, BoundNetLog(), callback3.callback())); // All sessions are active and not closed EXPECT_TRUE(session1->is_active()); EXPECT_FALSE(session1->IsClosed()); EXPECT_TRUE(session2->is_active()); EXPECT_FALSE(session2->IsClosed()); EXPECT_TRUE(session3->is_active()); EXPECT_FALSE(session3->IsClosed()); // Should not do anything, all are active spdy_session_pool->CloseIdleSessions(); EXPECT_TRUE(session1->is_active()); EXPECT_FALSE(session1->IsClosed()); EXPECT_TRUE(session2->is_active()); EXPECT_FALSE(session2->IsClosed()); EXPECT_TRUE(session3->is_active()); EXPECT_FALSE(session3->IsClosed()); // Make sessions 1 and 3 inactive, but keep them open. // Session 2 still open and active session1->CloseStream(spdy_stream1->stream_id(), OK); session3->CloseStream(spdy_stream3->stream_id(), OK); EXPECT_FALSE(session1->is_active()); EXPECT_FALSE(session1->IsClosed()); EXPECT_TRUE(session2->is_active()); EXPECT_FALSE(session2->IsClosed()); EXPECT_FALSE(session3->is_active()); EXPECT_FALSE(session3->IsClosed()); // Should close session 1 and 3, 2 should be left open spdy_session_pool->CloseIdleSessions(); EXPECT_FALSE(session1->is_active()); EXPECT_TRUE(session1->IsClosed()); EXPECT_TRUE(session2->is_active()); EXPECT_FALSE(session2->IsClosed()); EXPECT_FALSE(session3->is_active()); EXPECT_TRUE(session3->IsClosed()); // Should not do anything spdy_session_pool->CloseIdleSessions(); EXPECT_TRUE(session2->is_active()); EXPECT_FALSE(session2->IsClosed()); // Make 2 not active session2->CloseStream(spdy_stream2->stream_id(), OK); EXPECT_FALSE(session2->is_active()); EXPECT_FALSE(session2->IsClosed()); // This should close session 2 spdy_session_pool->CloseIdleSessions(); EXPECT_FALSE(session2->is_active()); EXPECT_TRUE(session2->IsClosed()); } // Start with max concurrent streams set to 1. Request two streams. Receive a // settings frame setting max concurrent streams to 2. Have the callback // release the stream, which releases its reference (the last) to the session. // Make sure nothing blows up. // http://crbug.com/57331 TEST_F(SpdySessionSpdy2Test, OnSettings) { SpdySessionDependencies session_deps; session_deps.host_resolver->set_synchronous_mode(true); spdy::SpdySettings new_settings; spdy::SettingsFlagsAndId id(0, spdy::SETTINGS_MAX_CONCURRENT_STREAMS); const size_t max_concurrent_streams = 2; new_settings.push_back(spdy::SpdySetting(id, max_concurrent_streams)); // Set up the socket so we read a SETTINGS frame that raises max concurrent // streams to 2. MockConnect connect_data(SYNCHRONOUS, OK); scoped_ptr settings_frame( ConstructSpdySettings(new_settings)); MockRead reads[] = { CreateMockRead(*settings_frame), MockRead(SYNCHRONOUS, 0, 0) // EOF }; StaticSocketDataProvider data(reads, arraysize(reads), NULL, 0); data.set_connect_data(connect_data); session_deps.socket_factory->AddSocketDataProvider(&data); SSLSocketDataProvider ssl(SYNCHRONOUS, OK); session_deps.socket_factory->AddSSLSocketDataProvider(&ssl); scoped_refptr http_session( SpdySessionDependencies::SpdyCreateSession(&session_deps)); const std::string kTestHost("www.foo.com"); const int kTestPort = 80; HostPortPair test_host_port_pair(kTestHost, kTestPort); HostPortProxyPair pair(test_host_port_pair, ProxyServer::Direct()); // Initialize the SpdySettingsStorage with 1 max concurrent streams. SpdySessionPool* spdy_session_pool(http_session->spdy_session_pool()); spdy::SpdySettings old_settings; spdy::SettingsFlagsAndId id1(spdy::SETTINGS_FLAG_PLEASE_PERSIST, id.id()); old_settings.push_back(spdy::SpdySetting(id1, 1)); spdy_session_pool->http_server_properties()->SetSpdySettings( test_host_port_pair, old_settings); // Create a session. EXPECT_FALSE(spdy_session_pool->HasSession(pair)); scoped_refptr session = spdy_session_pool->Get(pair, BoundNetLog()); ASSERT_TRUE(spdy_session_pool->HasSession(pair)); scoped_refptr transport_params( new TransportSocketParams(test_host_port_pair, MEDIUM, false, false)); scoped_ptr connection(new ClientSocketHandle); EXPECT_EQ(OK, connection->Init(test_host_port_pair.ToString(), transport_params, MEDIUM, CompletionCallback(), http_session->GetTransportSocketPool( HttpNetworkSession::NORMAL_SOCKET_POOL), BoundNetLog())); EXPECT_EQ(OK, session->InitializeWithSocket(connection.release(), false, OK)); // Create 2 streams. First will succeed. Second will be pending. scoped_refptr spdy_stream1; TestCompletionCallback callback1; GURL url("http://www.google.com"); EXPECT_EQ(OK, session->CreateStream(url, MEDIUM, /* priority, not important */ &spdy_stream1, BoundNetLog(), callback1.callback())); StreamReleaserCallback stream_releaser(session, spdy_stream1); ASSERT_EQ(ERR_IO_PENDING, session->CreateStream(url, MEDIUM, /* priority, not important */ stream_releaser.stream(), BoundNetLog(), stream_releaser.callback())); // Make sure |stream_releaser| holds the last refs. session = NULL; spdy_stream1 = NULL; EXPECT_EQ(OK, stream_releaser.WaitForResult()); } // Start with max concurrent streams set to 1. Request two streams. When the // first completes, have the callback close itself, which should trigger the // second stream creation. Then cancel that one immediately. Don't crash. // http://crbug.com/63532 TEST_F(SpdySessionSpdy2Test, CancelPendingCreateStream) { SpdySessionDependencies session_deps; session_deps.host_resolver->set_synchronous_mode(true); MockRead reads[] = { MockRead(SYNCHRONOUS, ERR_IO_PENDING) // Stall forever. }; StaticSocketDataProvider data(reads, arraysize(reads), NULL, 0); MockConnect connect_data(SYNCHRONOUS, OK); data.set_connect_data(connect_data); session_deps.socket_factory->AddSocketDataProvider(&data); SSLSocketDataProvider ssl(SYNCHRONOUS, OK); session_deps.socket_factory->AddSSLSocketDataProvider(&ssl); scoped_refptr http_session( SpdySessionDependencies::SpdyCreateSession(&session_deps)); const std::string kTestHost("www.foo.com"); const int kTestPort = 80; HostPortPair test_host_port_pair(kTestHost, kTestPort); HostPortProxyPair pair(test_host_port_pair, ProxyServer::Direct()); // Initialize the SpdySettingsStorage with 1 max concurrent streams. SpdySessionPool* spdy_session_pool(http_session->spdy_session_pool()); spdy::SpdySettings settings; spdy::SettingsFlagsAndId id(spdy::SETTINGS_FLAG_PLEASE_PERSIST, spdy::SETTINGS_MAX_CONCURRENT_STREAMS); settings.push_back(spdy::SpdySetting(id, 1)); spdy_session_pool->http_server_properties()->SetSpdySettings( test_host_port_pair, settings); // Create a session. EXPECT_FALSE(spdy_session_pool->HasSession(pair)); scoped_refptr session = spdy_session_pool->Get(pair, BoundNetLog()); ASSERT_TRUE(spdy_session_pool->HasSession(pair)); scoped_refptr transport_params( new TransportSocketParams(test_host_port_pair, MEDIUM, false, false)); scoped_ptr connection(new ClientSocketHandle); EXPECT_EQ(OK, connection->Init(test_host_port_pair.ToString(), transport_params, MEDIUM, CompletionCallback(), http_session->GetTransportSocketPool( HttpNetworkSession::NORMAL_SOCKET_POOL), BoundNetLog())); EXPECT_EQ(OK, session->InitializeWithSocket(connection.release(), false, OK)); // Use scoped_ptr to let us invalidate the memory when we want to, to trigger // a valgrind error if the callback is invoked when it's not supposed to be. scoped_ptr callback(new TestCompletionCallback); // Create 2 streams. First will succeed. Second will be pending. scoped_refptr spdy_stream1; GURL url("http://www.google.com"); ASSERT_EQ(OK, session->CreateStream(url, MEDIUM, /* priority, not important */ &spdy_stream1, BoundNetLog(), callback->callback())); scoped_refptr spdy_stream2; ASSERT_EQ(ERR_IO_PENDING, session->CreateStream(url, MEDIUM, /* priority, not important */ &spdy_stream2, BoundNetLog(), callback->callback())); // Release the first one, this will allow the second to be created. spdy_stream1->Cancel(); spdy_stream1 = NULL; session->CancelPendingCreateStreams(&spdy_stream2); callback.reset(); // Should not crash when running the pending callback. MessageLoop::current()->RunAllPending(); } TEST_F(SpdySessionSpdy2Test, SendSettingsOnNewSession) { SpdySessionDependencies session_deps; session_deps.host_resolver->set_synchronous_mode(true); MockRead reads[] = { MockRead(SYNCHRONOUS, ERR_IO_PENDING) // Stall forever. }; // Create the bogus setting that we want to verify is sent out. // Note that it will be marked as SETTINGS_FLAG_PERSISTED when sent out. But // to set it into the SpdySettingsStorage, we need to mark as // SETTINGS_FLAG_PLEASE_PERSIST. spdy::SpdySettings settings; const uint32 kBogusSettingId = 0xABAB; const uint32 kBogusSettingValue = 0xCDCD; spdy::SettingsFlagsAndId id(spdy::SETTINGS_FLAG_PERSISTED, kBogusSettingId); settings.push_back(spdy::SpdySetting(id, kBogusSettingValue)); MockConnect connect_data(SYNCHRONOUS, OK); scoped_ptr settings_frame( ConstructSpdySettings(settings)); MockWrite writes[] = { CreateMockWrite(*settings_frame), }; StaticSocketDataProvider data( reads, arraysize(reads), writes, arraysize(writes)); data.set_connect_data(connect_data); session_deps.socket_factory->AddSocketDataProvider(&data); SSLSocketDataProvider ssl(SYNCHRONOUS, OK); session_deps.socket_factory->AddSSLSocketDataProvider(&ssl); scoped_refptr http_session( SpdySessionDependencies::SpdyCreateSession(&session_deps)); const std::string kTestHost("www.foo.com"); const int kTestPort = 80; HostPortPair test_host_port_pair(kTestHost, kTestPort); HostPortProxyPair pair(test_host_port_pair, ProxyServer::Direct()); spdy::SettingsFlagsAndId id1(spdy::SETTINGS_FLAG_PLEASE_PERSIST, id.id()); settings.clear(); settings.push_back(spdy::SpdySetting(id1, kBogusSettingValue)); SpdySessionPool* spdy_session_pool(http_session->spdy_session_pool()); spdy_session_pool->http_server_properties()->SetSpdySettings( test_host_port_pair, settings); EXPECT_FALSE(spdy_session_pool->HasSession(pair)); scoped_refptr session = spdy_session_pool->Get(pair, BoundNetLog()); EXPECT_TRUE(spdy_session_pool->HasSession(pair)); scoped_refptr transport_params( new TransportSocketParams(test_host_port_pair, MEDIUM, false, false)); scoped_ptr connection(new ClientSocketHandle); EXPECT_EQ(OK, connection->Init(test_host_port_pair.ToString(), transport_params, MEDIUM, CompletionCallback(), http_session->GetTransportSocketPool( HttpNetworkSession::NORMAL_SOCKET_POOL), BoundNetLog())); EXPECT_EQ(OK, session->InitializeWithSocket(connection.release(), false, OK)); MessageLoop::current()->RunAllPending(); EXPECT_TRUE(data.at_write_eof()); } namespace { // This test has two variants, one for each style of closing the connection. // If |clean_via_close_current_sessions| is false, the sessions are closed // manually, calling SpdySessionPool::Remove() directly. If it is true, // sessions are closed with SpdySessionPool::CloseCurrentSessions(). void IPPoolingTest(bool clean_via_close_current_sessions) { const int kTestPort = 80; struct TestHosts { std::string name; std::string iplist; HostPortProxyPair pair; AddressList addresses; } test_hosts[] = { { "www.foo.com", "192.0.2.33,192.168.0.1,192.168.0.5" }, { "images.foo.com", "192.168.0.2,192.168.0.3,192.168.0.5,192.0.2.33" }, { "js.foo.com", "192.168.0.4,192.168.0.3" }, }; SpdySessionDependencies session_deps; session_deps.host_resolver->set_synchronous_mode(true); for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_hosts); i++) { session_deps.host_resolver->rules()->AddIPLiteralRule(test_hosts[i].name, test_hosts[i].iplist, ""); // This test requires that the HostResolver cache be populated. Normal // code would have done this already, but we do it manually. HostResolver::RequestInfo info(HostPortPair(test_hosts[i].name, kTestPort)); session_deps.host_resolver->Resolve( info, &test_hosts[i].addresses, CompletionCallback(), NULL, BoundNetLog()); // Setup a HostPortProxyPair test_hosts[i].pair = HostPortProxyPair( HostPortPair(test_hosts[i].name, kTestPort), ProxyServer::Direct()); } MockConnect connect_data(SYNCHRONOUS, OK); MockRead reads[] = { MockRead(SYNCHRONOUS, ERR_IO_PENDING) // Stall forever. }; StaticSocketDataProvider data(reads, arraysize(reads), NULL, 0); data.set_connect_data(connect_data); session_deps.socket_factory->AddSocketDataProvider(&data); SSLSocketDataProvider ssl(SYNCHRONOUS, OK); session_deps.socket_factory->AddSSLSocketDataProvider(&ssl); scoped_refptr http_session( SpdySessionDependencies::SpdyCreateSession(&session_deps)); // Setup the first session to the first host. SpdySessionPool* spdy_session_pool(http_session->spdy_session_pool()); EXPECT_FALSE(spdy_session_pool->HasSession(test_hosts[0].pair)); scoped_refptr session = spdy_session_pool->Get(test_hosts[0].pair, BoundNetLog()); EXPECT_TRUE(spdy_session_pool->HasSession(test_hosts[0].pair)); HostPortPair test_host_port_pair(test_hosts[0].name, kTestPort); scoped_refptr transport_params( new TransportSocketParams(test_host_port_pair, MEDIUM, false, false)); scoped_ptr connection(new ClientSocketHandle); EXPECT_EQ(OK, connection->Init(test_host_port_pair.ToString(), transport_params, MEDIUM, CompletionCallback(), http_session->GetTransportSocketPool( HttpNetworkSession::NORMAL_SOCKET_POOL), BoundNetLog())); EXPECT_EQ(OK, session->InitializeWithSocket(connection.release(), false, OK)); // TODO(rtenneti): MockClientSocket::GetPeerAddress return's 0 as the port // number. Fix it to return port 80 and then use GetPeerAddress to AddAlias. const addrinfo* address = test_hosts[0].addresses.head(); SpdySessionPoolPeer pool_peer(spdy_session_pool); pool_peer.AddAlias(address, test_hosts[0].pair); // Flush the SpdySession::OnReadComplete() task. MessageLoop::current()->RunAllPending(); // The third host has no overlap with the first, so it can't pool IPs. EXPECT_FALSE(spdy_session_pool->HasSession(test_hosts[2].pair)); // The second host overlaps with the first, and should IP pool. EXPECT_TRUE(spdy_session_pool->HasSession(test_hosts[1].pair)); // Verify that the second host, through a proxy, won't share the IP. HostPortProxyPair proxy_pair(test_hosts[1].pair.first, ProxyServer::FromPacString("HTTP http://proxy.foo.com/")); EXPECT_FALSE(spdy_session_pool->HasSession(proxy_pair)); // Overlap between 2 and 3 does is not transitive to 1. EXPECT_FALSE(spdy_session_pool->HasSession(test_hosts[2].pair)); // Create a new session to host 2. scoped_refptr session2 = spdy_session_pool->Get(test_hosts[2].pair, BoundNetLog()); // Verify that we have sessions for everything. EXPECT_TRUE(spdy_session_pool->HasSession(test_hosts[0].pair)); EXPECT_TRUE(spdy_session_pool->HasSession(test_hosts[1].pair)); EXPECT_TRUE(spdy_session_pool->HasSession(test_hosts[2].pair)); // Grab the session to host 1 and verify that it is the same session // we got with host 0, and that is a different than host 2's session. scoped_refptr session1 = spdy_session_pool->Get(test_hosts[1].pair, BoundNetLog()); EXPECT_EQ(session.get(), session1.get()); EXPECT_NE(session2.get(), session1.get()); // Remove the aliases and observe that we still have a session for host1. pool_peer.RemoveAliases(test_hosts[0].pair); pool_peer.RemoveAliases(test_hosts[1].pair); EXPECT_TRUE(spdy_session_pool->HasSession(test_hosts[1].pair)); // Expire the host cache session_deps.host_resolver->GetHostCache()->clear(); EXPECT_TRUE(spdy_session_pool->HasSession(test_hosts[1].pair)); // Cleanup the sessions. if (!clean_via_close_current_sessions) { spdy_session_pool->Remove(session); session = NULL; spdy_session_pool->Remove(session2); session2 = NULL; } else { spdy_session_pool->CloseCurrentSessions(); } // Verify that the map is all cleaned up. EXPECT_FALSE(spdy_session_pool->HasSession(test_hosts[0].pair)); EXPECT_FALSE(spdy_session_pool->HasSession(test_hosts[1].pair)); EXPECT_FALSE(spdy_session_pool->HasSession(test_hosts[2].pair)); } } // namespace TEST_F(SpdySessionSpdy2Test, IPPooling) { IPPoolingTest(false); } TEST_F(SpdySessionSpdy2Test, IPPoolingCloseCurrentSessions) { IPPoolingTest(true); } TEST_F(SpdySessionSpdy2Test, ClearSettingsStorage) { SpdySettingsStorage settings_storage; const std::string kTestHost("www.foo.com"); const int kTestPort = 80; HostPortPair test_host_port_pair(kTestHost, kTestPort); spdy::SpdySettings test_settings; spdy::SettingsFlagsAndId id(spdy::SETTINGS_FLAG_PLEASE_PERSIST, spdy::SETTINGS_MAX_CONCURRENT_STREAMS); const size_t max_concurrent_streams = 2; test_settings.push_back(spdy::SpdySetting(id, max_concurrent_streams)); settings_storage.Set(test_host_port_pair, test_settings); EXPECT_NE(0u, settings_storage.Get(test_host_port_pair).size()); settings_storage.Clear(); EXPECT_EQ(0u, settings_storage.Get(test_host_port_pair).size()); } TEST_F(SpdySessionSpdy2Test, ClearSettingsStorageOnIPAddressChanged) { const std::string kTestHost("www.foo.com"); const int kTestPort = 80; HostPortPair test_host_port_pair(kTestHost, kTestPort); SpdySessionDependencies session_deps; scoped_refptr http_session( SpdySessionDependencies::SpdyCreateSession(&session_deps)); SpdySessionPool* spdy_session_pool(http_session->spdy_session_pool()); HttpServerProperties* test_http_server_properties = spdy_session_pool->http_server_properties(); spdy::SettingsFlagsAndId id(spdy::SETTINGS_FLAG_PLEASE_PERSIST, spdy::SETTINGS_MAX_CONCURRENT_STREAMS); const size_t max_concurrent_streams = 2; spdy::SpdySettings test_settings; test_settings.push_back(spdy::SpdySetting(id, max_concurrent_streams)); test_http_server_properties->SetSpdySettings(test_host_port_pair, test_settings); EXPECT_NE(0u, test_http_server_properties->GetSpdySettings( test_host_port_pair).size()); spdy_session_pool->OnIPAddressChanged(); EXPECT_EQ(0u, test_http_server_properties->GetSpdySettings( test_host_port_pair).size()); } TEST_F(SpdySessionSpdy2Test, NeedsCredentials) { SpdySessionDependencies session_deps; MockConnect connect_data(SYNCHRONOUS, OK); MockRead reads[] = { MockRead(SYNCHRONOUS, ERR_IO_PENDING) // Stall forever. }; StaticSocketDataProvider data(reads, arraysize(reads), NULL, 0); data.set_connect_data(connect_data); session_deps.socket_factory->AddSocketDataProvider(&data); SSLSocketDataProvider ssl(SYNCHRONOUS, OK); ssl.domain_bound_cert_type = CLIENT_CERT_ECDSA_SIGN; ssl.protocol_negotiated = SSLClientSocket::kProtoSPDY2; session_deps.socket_factory->AddSSLSocketDataProvider(&ssl); scoped_refptr http_session( SpdySessionDependencies::SpdyCreateSession(&session_deps)); const std::string kTestHost("www.foo.com"); const int kTestPort = 80; HostPortPair test_host_port_pair(kTestHost, kTestPort); HostPortProxyPair pair(test_host_port_pair, ProxyServer::Direct()); SpdySessionPool* spdy_session_pool(http_session->spdy_session_pool()); EXPECT_FALSE(spdy_session_pool->HasSession(pair)); scoped_refptr session = spdy_session_pool->Get(pair, BoundNetLog()); EXPECT_TRUE(spdy_session_pool->HasSession(pair)); SSLConfig ssl_config; scoped_refptr transport_params( new TransportSocketParams(test_host_port_pair, MEDIUM, false, false)); scoped_refptr socks_params; scoped_refptr http_proxy_params; scoped_refptr ssl_params( new SSLSocketParams(transport_params, socks_params, http_proxy_params, ProxyServer::SCHEME_DIRECT, test_host_port_pair, ssl_config, 0, false, false)); scoped_ptr connection(new ClientSocketHandle); EXPECT_EQ(OK, connection->Init(test_host_port_pair.ToString(), ssl_params, MEDIUM, CompletionCallback(), http_session->GetSSLSocketPool( HttpNetworkSession::NORMAL_SOCKET_POOL), BoundNetLog())); EXPECT_EQ(OK, session->InitializeWithSocket(connection.release(), true, OK)); EXPECT_FALSE(session->NeedsCredentials(test_host_port_pair)); const std::string kTestHost2("www.bar.com"); HostPortPair test_host_port_pair2(kTestHost2, kTestPort); EXPECT_FALSE(session->NeedsCredentials(test_host_port_pair2)); // Flush the SpdySession::OnReadComplete() task. MessageLoop::current()->RunAllPending(); spdy_session_pool->Remove(session); EXPECT_FALSE(spdy_session_pool->HasSession(pair)); } TEST_F(SpdySessionSpdy2Test, CloseSessionOnError) { SpdySessionDependencies session_deps; session_deps.host_resolver->set_synchronous_mode(true); MockConnect connect_data(SYNCHRONOUS, OK); scoped_ptr goaway(ConstructSpdyGoAway()); MockRead reads[] = { CreateMockRead(*goaway), MockRead(SYNCHRONOUS, 0, 0) // EOF }; net::CapturingBoundNetLog log(net::CapturingNetLog::kUnbounded); StaticSocketDataProvider data(reads, arraysize(reads), NULL, 0); data.set_connect_data(connect_data); session_deps.socket_factory->AddSocketDataProvider(&data); SSLSocketDataProvider ssl(SYNCHRONOUS, OK); session_deps.socket_factory->AddSSLSocketDataProvider(&ssl); scoped_refptr http_session( SpdySessionDependencies::SpdyCreateSession(&session_deps)); const std::string kTestHost("www.foo.com"); const int kTestPort = 80; HostPortPair test_host_port_pair(kTestHost, kTestPort); HostPortProxyPair pair(test_host_port_pair, ProxyServer::Direct()); SpdySessionPool* spdy_session_pool(http_session->spdy_session_pool()); EXPECT_FALSE(spdy_session_pool->HasSession(pair)); scoped_refptr session = spdy_session_pool->Get(pair, log.bound()); EXPECT_TRUE(spdy_session_pool->HasSession(pair)); scoped_refptr transport_params( new TransportSocketParams(test_host_port_pair, MEDIUM, false, false)); scoped_ptr connection(new ClientSocketHandle); EXPECT_EQ(OK, connection->Init(test_host_port_pair.ToString(), transport_params, MEDIUM, CompletionCallback(), http_session->GetTransportSocketPool( HttpNetworkSession::NORMAL_SOCKET_POOL), log.bound())); EXPECT_EQ(OK, session->InitializeWithSocket(connection.release(), false, OK)); // Flush the SpdySession::OnReadComplete() task. MessageLoop::current()->RunAllPending(); EXPECT_FALSE(spdy_session_pool->HasSession(pair)); // Check that the NetLog was filled reasonably. net::CapturingNetLog::EntryList entries; log.GetEntries(&entries); EXPECT_LT(0u, entries.size()); // Check that we logged SPDY_SESSION_CLOSE correctly. int pos = net::ExpectLogContainsSomewhere( entries, 0, net::NetLog::TYPE_SPDY_SESSION_CLOSE, net::NetLog::PHASE_NONE); CapturingNetLog::Entry entry = entries[pos]; NetLogSpdySessionCloseParameter* request_params = static_cast( entry.extra_parameters.get()); EXPECT_EQ(ERR_CONNECTION_CLOSED, request_params->status()); } TEST_F(SpdySessionSpdy2Test, CloseOneIdleConnection) { MockHostResolver host_resolver; CapturingBoundNetLog log(CapturingNetLog::kUnbounded); ClientSocketPoolHistograms tcp_histograms(""); MockClientSocketFactory socket_factory; MockConnect connect_data(SYNCHRONOUS, OK); MockRead reads[] = { MockRead(SYNCHRONOUS, ERR_IO_PENDING) // Stall forever. }; StaticSocketDataProvider data(reads, arraysize(reads), NULL, 0); data.set_connect_data(connect_data); socket_factory.AddSocketDataProvider(&data); socket_factory.AddSocketDataProvider(&data); socket_factory.AddSocketDataProvider(&data); socket_factory.AddSocketDataProvider(&data); socket_factory.AddSocketDataProvider(&data); socket_factory.AddSocketDataProvider(&data); TransportClientSocketPool pool( 3, 2, &tcp_histograms, &host_resolver, &socket_factory, NULL); // Now if I check out 1 socket from 3 different groups, the next request // will leave us stalled. TestCompletionCallback callback1; HostPortPair host_port1("1.com", 80); scoped_refptr params1( new TransportSocketParams(host_port1, MEDIUM, false, false)); scoped_ptr connection1(new ClientSocketHandle); EXPECT_EQ(ERR_IO_PENDING, connection1->Init(host_port1.ToString(), params1, MEDIUM, callback1.callback(), &pool, log.bound())); EXPECT_EQ(OK, callback1.WaitForResult()); EXPECT_FALSE(pool.IsStalled()); EXPECT_TRUE(connection1->is_initialized()); EXPECT_TRUE(connection1->socket()); TestCompletionCallback callback2; HostPortPair host_port2("2.com", 80); scoped_refptr params2( new TransportSocketParams(host_port2, MEDIUM, false, false)); scoped_ptr connection2(new ClientSocketHandle); EXPECT_EQ(ERR_IO_PENDING, connection2->Init(host_port2.ToString(), params2, MEDIUM, callback2.callback(), &pool, log.bound())); EXPECT_EQ(OK, callback2.WaitForResult()); EXPECT_FALSE(pool.IsStalled()); TestCompletionCallback callback3; HostPortPair host_port3("3.com", 80); scoped_refptr params3( new TransportSocketParams(host_port3, MEDIUM, false, false)); scoped_ptr connection3(new ClientSocketHandle); EXPECT_EQ(ERR_IO_PENDING, connection3->Init(host_port3.ToString(), params3, MEDIUM, callback3.callback(), &pool, log.bound())); EXPECT_EQ(OK, callback3.WaitForResult()); EXPECT_FALSE(pool.IsStalled()); TestCompletionCallback callback4; HostPortPair host_port4("4.com", 80); scoped_refptr params4( new TransportSocketParams(host_port4, MEDIUM, false, false)); scoped_ptr connection4(new ClientSocketHandle); EXPECT_EQ(ERR_IO_PENDING, connection4->Init(host_port4.ToString(), params4, MEDIUM, callback4.callback(), &pool, log.bound())); EXPECT_TRUE(pool.IsStalled()); // Return 1 socket to the pool so that we are no longer stalled connection3->socket()->Disconnect(); connection3->Reset(); EXPECT_EQ(OK, callback4.WaitForResult()); EXPECT_FALSE(pool.IsStalled()); // Now, wrap one of the sockets in a SpdySession HttpServerPropertiesImpl props; SpdySessionPool spdy_session_pool(&host_resolver, NULL, &props); HostPortProxyPair pair1(host_port1, ProxyServer::Direct()); EXPECT_FALSE(spdy_session_pool.HasSession(pair1)); scoped_refptr session1 = spdy_session_pool.Get(pair1, log.bound()); EXPECT_TRUE(spdy_session_pool.HasSession(pair1)); EXPECT_EQ(OK, session1->InitializeWithSocket(connection1.release(), false, OK)); session1 = NULL; EXPECT_TRUE(spdy_session_pool.HasSession(pair1)); // The SpdySession is now idle. When we request the next socket from the // transport pool, the session will be closed via CloseOneIdleConnection(). TestCompletionCallback callback5; HostPortPair host_port5("5.com", 80); scoped_refptr params5( new TransportSocketParams(host_port5, MEDIUM, false, false)); scoped_ptr connection5(new ClientSocketHandle); EXPECT_EQ(ERR_IO_PENDING, connection5->Init(host_port5.ToString(), params5, MEDIUM, callback5.callback(), &pool, log.bound())); EXPECT_FALSE(pool.IsStalled()); EXPECT_EQ(OK, callback5.WaitForResult()); EXPECT_FALSE(spdy_session_pool.HasSession(pair1)); EXPECT_FALSE(pool.IsStalled()); // Now, wrap one of the sockets in a SpdySession HostPortProxyPair pair2(host_port2, ProxyServer::Direct()); EXPECT_FALSE(spdy_session_pool.HasSession(pair2)); scoped_refptr session2 = spdy_session_pool.Get(pair2, log.bound()); EXPECT_TRUE(spdy_session_pool.HasSession(pair2)); EXPECT_EQ(OK, session2->InitializeWithSocket(connection2.release(), false, OK)); // Manually remove the socket from the pool. This does *not* return the // transport socket. It will be returned only when the SpdySession is // destructed. session2->RemoveFromPool(); EXPECT_FALSE(spdy_session_pool.HasSession(pair2)); // Although there are no active streams on the session, the pool does not // hold a reference. This means that CloseOneIdleConnection should not // return true, and this request should stall. TestCompletionCallback callback6; HostPortPair host_port6("6.com", 80); scoped_refptr params6( new TransportSocketParams(host_port5, MEDIUM, false, false)); scoped_ptr connection6(new ClientSocketHandle); EXPECT_EQ(ERR_IO_PENDING, connection6->Init(host_port6.ToString(), params6, MEDIUM, callback6.callback(), &pool, log.bound())); EXPECT_TRUE(pool.IsStalled()); // But now if we drop our reference to the session, it will be destructed // and the transport socket will return to the pool, unblocking this // request. session2 = NULL; EXPECT_EQ(OK, callback6.WaitForResult()); EXPECT_FALSE(pool.IsStalled()); } } // namespace net