diff options
Diffstat (limited to 'net/spdy/spdy_session_spdy2_unittest.cc')
-rw-r--r-- | net/spdy/spdy_session_spdy2_unittest.cc | 1128 |
1 files changed, 1128 insertions, 0 deletions
diff --git a/net/spdy/spdy_session_spdy2_unittest.cc b/net/spdy/spdy_session_spdy2_unittest.cc new file mode 100644 index 0000000..f5f33fc --- /dev/null +++ b/net/spdy/spdy_session_spdy2_unittest.cc @@ -0,0 +1,1128 @@ +// 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/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 { + public: + static void TurnOffCompression() { + spdy::SpdyFramer::set_enable_compression_default(false); + } + protected: + virtual void TearDown() { + // Wanted to be 100% sure PING is disabled. + SpdySession::set_enable_ping_based_connection_checking(false); + } +}; + +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<SpdyIOBuffer> 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<spdy::SpdyFrame> 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); + session_deps.socket_factory->AddSSLSocketDataProvider(&ssl); + + scoped_refptr<HttpNetworkSession> 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<SpdySession> session = + spdy_session_pool->Get(pair, BoundNetLog()); + EXPECT_TRUE(spdy_session_pool->HasSession(pair)); + + scoped_refptr<TransportSocketParams> transport_params( + new TransportSocketParams(test_host_port_pair, + MEDIUM, + false, + false)); + scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle); + EXPECT_EQ(OK, connection->Init(test_host_port_pair.ToString(), + transport_params, MEDIUM, CompletionCallback(), + http_session->GetTransportSocketPool(), + BoundNetLog())); + EXPECT_EQ(OK, session->InitializeWithSocket(connection.release(), false, OK)); + + // Flush the SpdySession::OnReadComplete() task. + MessageLoop::current()->RunAllPending(); + + EXPECT_FALSE(spdy_session_pool->HasSession(pair)); + + scoped_refptr<SpdySession> 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<spdy::SpdyFrame> read_ping(ConstructSpdyPing()); + MockRead reads[] = { + CreateMockRead(*read_ping), + CreateMockRead(*read_ping), + MockRead(SYNCHRONOUS, 0, 0) // EOF + }; + scoped_ptr<spdy::SpdyFrame> 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<HttpNetworkSession> 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<SpdySession> session = + spdy_session_pool->Get(pair, BoundNetLog()); + EXPECT_TRUE(spdy_session_pool->HasSession(pair)); + + + scoped_refptr<TransportSocketParams> transport_params( + new TransportSocketParams(test_host_port_pair, + MEDIUM, + false, + false)); + scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle); + EXPECT_EQ(OK, connection->Init(test_host_port_pair.ToString(), + transport_params, MEDIUM, CompletionCallback(), + http_session->GetTransportSocketPool(), + BoundNetLog())); + EXPECT_EQ(OK, session->InitializeWithSocket(connection.release(), false, OK)); + + scoped_refptr<SpdyStream> spdy_stream1; + TestCompletionCallback callback1; + EXPECT_EQ(OK, session->CreateStream(url, + MEDIUM, + &spdy_stream1, + BoundNetLog(), + callback1.callback())); + scoped_ptr<TestSpdyStreamDelegate> 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); + SpdySession::set_connection_at_risk_of_loss_seconds(0); + SpdySession::set_trailing_ping_delay_time_ms(0); + SpdySession::set_hung_interval_ms(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<uint32>(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<spdy::SpdyFrame> read_ping(ConstructSpdyPing()); + MockRead reads[] = { + CreateMockRead(*read_ping), + MockRead(SYNCHRONOUS, 0, 0) // EOF + }; + scoped_ptr<spdy::SpdyFrame> 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<HttpNetworkSession> 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<SpdySession> session = + spdy_session_pool->Get(pair, BoundNetLog()); + EXPECT_TRUE(spdy_session_pool->HasSession(pair)); + + scoped_refptr<TransportSocketParams> transport_params( + new TransportSocketParams(test_host_port_pair, + MEDIUM, + false, + false)); + scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle); + EXPECT_EQ(OK, connection->Init(test_host_port_pair.ToString(), + transport_params, MEDIUM, CompletionCallback(), + http_session->GetTransportSocketPool(), + BoundNetLog())); + EXPECT_EQ(OK, session->InitializeWithSocket(connection.release(), false, OK)); + + scoped_refptr<SpdyStream> spdy_stream1; + TestCompletionCallback callback1; + EXPECT_EQ(OK, session->CreateStream(url, + MEDIUM, + &spdy_stream1, + BoundNetLog(), + callback1.callback())); + scoped_ptr<TestSpdyStreamDelegate> delegate( + new TestSpdyStreamDelegate(callback1.callback())); + spdy_stream1->SetDelegate(delegate.get()); + + // Enable sending of PING. + SpdySession::set_enable_ping_based_connection_checking(true); + SpdySession::set_connection_at_risk_of_loss_seconds(0); + SpdySession::set_trailing_ping_delay_time_ms(0); + SpdySession::set_hung_interval_ms(0); + + // Send a PING frame. + session->WritePingFrame(1); + EXPECT_LT(0, session->pings_in_flight()); + EXPECT_GT(session->next_ping_id(), static_cast<uint32>(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<SpdyStream>* 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<SpdySession> session_; + scoped_refptr<SpdyStream> first_stream_; + scoped_refptr<SpdyStream> 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<HttpNetworkSession> 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<SpdySession> session1 = + spdy_session_pool->Get(pair1, BoundNetLog()); + scoped_refptr<SpdyStream> 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<SpdySession> session2 = + spdy_session_pool->Get(pair2, BoundNetLog()); + scoped_refptr<SpdyStream> 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<SpdySession> session3 = + spdy_session_pool->Get(pair3, BoundNetLog()); + scoped_refptr<SpdyStream> 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); + id.set_id(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<spdy::SpdyFrame> 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<HttpNetworkSession> 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; + id.set_flags(spdy::SETTINGS_FLAG_PLEASE_PERSIST); + old_settings.push_back(spdy::SpdySetting(id, 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<SpdySession> session = + spdy_session_pool->Get(pair, BoundNetLog()); + ASSERT_TRUE(spdy_session_pool->HasSession(pair)); + + scoped_refptr<TransportSocketParams> transport_params( + new TransportSocketParams(test_host_port_pair, + MEDIUM, + false, + false)); + scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle); + EXPECT_EQ(OK, connection->Init(test_host_port_pair.ToString(), + transport_params, MEDIUM, CompletionCallback(), + http_session->GetTransportSocketPool(), + BoundNetLog())); + EXPECT_EQ(OK, session->InitializeWithSocket(connection.release(), false, OK)); + + // Create 2 streams. First will succeed. Second will be pending. + scoped_refptr<SpdyStream> 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<HttpNetworkSession> 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(0); + id.set_id(spdy::SETTINGS_MAX_CONCURRENT_STREAMS); + id.set_flags(spdy::SETTINGS_FLAG_PLEASE_PERSIST); + 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<SpdySession> session = + spdy_session_pool->Get(pair, BoundNetLog()); + ASSERT_TRUE(spdy_session_pool->HasSession(pair)); + + scoped_refptr<TransportSocketParams> transport_params( + new TransportSocketParams(test_host_port_pair, + MEDIUM, + false, + false)); + scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle); + EXPECT_EQ(OK, connection->Init(test_host_port_pair.ToString(), + transport_params, MEDIUM, CompletionCallback(), + http_session->GetTransportSocketPool(), + 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<TestCompletionCallback> callback(new TestCompletionCallback); + + // Create 2 streams. First will succeed. Second will be pending. + scoped_refptr<SpdyStream> 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<SpdyStream> 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(0); + id.set_id(kBogusSettingId); + id.set_flags(spdy::SETTINGS_FLAG_PERSISTED); + settings.push_back(spdy::SpdySetting(id, kBogusSettingValue)); + MockConnect connect_data(SYNCHRONOUS, OK); + scoped_ptr<spdy::SpdyFrame> 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<HttpNetworkSession> 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()); + + id.set_flags(spdy::SETTINGS_FLAG_PLEASE_PERSIST); + settings.clear(); + settings.push_back(spdy::SpdySetting(id, 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<SpdySession> session = + spdy_session_pool->Get(pair, BoundNetLog()); + EXPECT_TRUE(spdy_session_pool->HasSession(pair)); + + scoped_refptr<TransportSocketParams> transport_params( + new TransportSocketParams(test_host_port_pair, + MEDIUM, + false, + false)); + scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle); + EXPECT_EQ(OK, connection->Init(test_host_port_pair.ToString(), + transport_params, MEDIUM, CompletionCallback(), + http_session->GetTransportSocketPool(), + 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<HttpNetworkSession> 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<SpdySession> 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<TransportSocketParams> transport_params( + new TransportSocketParams(test_host_port_pair, + MEDIUM, + false, + false)); + scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle); + EXPECT_EQ(OK, connection->Init(test_host_port_pair.ToString(), + transport_params, MEDIUM, CompletionCallback(), + http_session->GetTransportSocketPool(), + 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<SpdySession> 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)); + + // 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(0); + id.set_id(spdy::SETTINGS_MAX_CONCURRENT_STREAMS); + id.set_flags(spdy::SETTINGS_FLAG_PLEASE_PERSIST); + 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<HttpNetworkSession> 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(0); + id.set_id(spdy::SETTINGS_MAX_CONCURRENT_STREAMS); + id.set_flags(spdy::SETTINGS_FLAG_PLEASE_PERSIST); + 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.origin_bound_cert_type = CLIENT_CERT_ECDSA_SIGN; + ssl.protocol_negotiated = SSLClientSocket::kProtoSPDY3; + session_deps.socket_factory->AddSSLSocketDataProvider(&ssl); + + scoped_refptr<HttpNetworkSession> 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<SpdySession> session = + spdy_session_pool->Get(pair, BoundNetLog()); + EXPECT_TRUE(spdy_session_pool->HasSession(pair)); + + SSLConfig ssl_config; + scoped_refptr<TransportSocketParams> transport_params( + new TransportSocketParams(test_host_port_pair, + MEDIUM, + false, + false)); + scoped_refptr<SOCKSSocketParams> socks_params; + scoped_refptr<HttpProxySocketParams> http_proxy_params; + scoped_refptr<SSLSocketParams> 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<ClientSocketHandle> connection(new ClientSocketHandle); + EXPECT_EQ(OK, connection->Init(test_host_port_pair.ToString(), + ssl_params, MEDIUM, CompletionCallback(), + http_session->GetSSLSocketPool(), + 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_TRUE(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, SendCredentials) { + SpdySessionDependencies session_deps; + + MockConnect connect_data(SYNCHRONOUS, OK); + MockRead reads[] = { + MockRead(SYNCHRONOUS, ERR_IO_PENDING) // Stall forever. + }; + spdy::SpdySettings settings; + scoped_ptr<spdy::SpdyFrame> 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); + ssl.origin_bound_cert_type = CLIENT_CERT_ECDSA_SIGN; + ssl.protocol_negotiated = SSLClientSocket::kProtoSPDY3; + session_deps.socket_factory->AddSSLSocketDataProvider(&ssl); + + scoped_refptr<HttpNetworkSession> 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<SpdySession> session = + spdy_session_pool->Get(pair, BoundNetLog()); + EXPECT_TRUE(spdy_session_pool->HasSession(pair)); + + SSLConfig ssl_config; + scoped_refptr<TransportSocketParams> transport_params( + new TransportSocketParams(test_host_port_pair, + MEDIUM, + false, + false)); + scoped_refptr<SOCKSSocketParams> socks_params; + scoped_refptr<HttpProxySocketParams> http_proxy_params; + scoped_refptr<SSLSocketParams> 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<ClientSocketHandle> connection(new ClientSocketHandle); + EXPECT_EQ(OK, connection->Init(test_host_port_pair.ToString(), + ssl_params, MEDIUM, CompletionCallback(), + http_session->GetSSLSocketPool(), + 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_TRUE(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<spdy::SpdyFrame> 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<HttpNetworkSession> 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<SpdySession> session = + spdy_session_pool->Get(pair, log.bound()); + EXPECT_TRUE(spdy_session_pool->HasSession(pair)); + + scoped_refptr<TransportSocketParams> transport_params( + new TransportSocketParams(test_host_port_pair, + MEDIUM, + false, + false)); + scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle); + EXPECT_EQ(OK, connection->Init(test_host_port_pair.ToString(), + transport_params, MEDIUM, CompletionCallback(), + http_session->GetTransportSocketPool(), + 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<NetLogSpdySessionCloseParameter*>( + entry.extra_parameters.get()); + EXPECT_EQ(ERR_CONNECTION_CLOSED, request_params->status()); +} + +} // namespace net |