blob: 351b8edb91ee01e0f9c83e48fc580f067a30aecb [file] [log] [blame]
// Copyright (c) 2011 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/spdy/spdy_io_buffer.h"
#include "net/spdy/spdy_session_pool.h"
#include "net/spdy/spdy_stream.h"
#include "net/spdy/spdy_test_util.h"
#include "testing/platform_test.h"
namespace net {
// TODO(cbentzel): Expose compression setter/getter in public SpdySession
// interface rather than going through all these contortions.
class SpdySessionTest : public PlatformTest {
public:
static void TurnOffCompression() {
spdy::SpdyFramer::set_enable_compression_default(false);
}
};
namespace {
// Test the SpdyIOBuffer class.
TEST_F(SpdySessionTest, 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(SpdySessionTest, GoAway) {
SpdySessionDependencies session_deps;
session_deps.host_resolver->set_synchronous_mode(true);
MockConnect connect_data(false, OK);
scoped_ptr<spdy::SpdyFrame> goaway(ConstructSpdyGoAway());
MockRead reads[] = {
CreateMockRead(*goaway),
MockRead(false, 0, 0) // EOF
};
StaticSocketDataProvider data(reads, arraysize(reads), NULL, 0);
data.set_connect_data(connect_data);
session_deps.socket_factory->AddSocketDataProvider(&data);
SSLSocketDataProvider ssl(false, 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,
GURL(),
false,
false));
scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle);
EXPECT_EQ(OK,
connection->Init(test_host_port_pair.ToString(),
transport_params, MEDIUM,
NULL, http_session->transport_socket_pool(),
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;
}
class StreamReleaserCallback : public CallbackRunner<Tuple1<int> > {
public:
StreamReleaserCallback(SpdySession* session,
SpdyStream* first_stream)
: session_(session), first_stream_(first_stream) {}
~StreamReleaserCallback() {}
int WaitForResult() { return callback_.WaitForResult(); }
virtual void RunWithParams(const Tuple1<int>& params) {
session_->CloseSessionOnError(ERR_FAILED, false);
session_ = NULL;
first_stream_->Cancel();
first_stream_ = NULL;
stream_->Cancel();
stream_ = NULL;
callback_.RunWithParams(params);
}
scoped_refptr<SpdyStream>* stream() { return &stream_; }
private:
scoped_refptr<SpdySession> session_;
scoped_refptr<SpdyStream> first_stream_;
scoped_refptr<SpdyStream> stream_;
TestCompletionCallback callback_;
};
// 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(SpdySessionTest, 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(false, OK);
scoped_ptr<spdy::SpdyFrame> settings_frame(
ConstructSpdySettings(new_settings));
MockRead reads[] = {
CreateMockRead(*settings_frame),
MockRead(false, 0, 0) // EOF
};
StaticSocketDataProvider data(reads, arraysize(reads), NULL, 0);
data.set_connect_data(connect_data);
session_deps.socket_factory->AddSocketDataProvider(&data);
SSLSocketDataProvider ssl(false, 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->mutable_spdy_settings()->Set(
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,
GURL(),
false,
false));
scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle);
EXPECT_EQ(OK,
connection->Init(test_host_port_pair.ToString(),
transport_params, MEDIUM,
NULL, http_session->transport_socket_pool(),
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));
StreamReleaserCallback stream_releaser(session, spdy_stream1);
ASSERT_EQ(ERR_IO_PENDING,
session->CreateStream(url,
MEDIUM, /* priority, not important */
stream_releaser.stream(),
BoundNetLog(),
&stream_releaser));
// 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(SpdySessionTest, CancelPendingCreateStream) {
SpdySessionDependencies session_deps;
session_deps.host_resolver->set_synchronous_mode(true);
MockRead reads[] = {
MockRead(false, ERR_IO_PENDING) // Stall forever.
};
StaticSocketDataProvider data(reads, arraysize(reads), NULL, 0);
MockConnect connect_data(false, OK);
data.set_connect_data(connect_data);
session_deps.socket_factory->AddSocketDataProvider(&data);
SSLSocketDataProvider ssl(false, 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->mutable_spdy_settings()->Set(
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,
GURL(),
false,
false));
scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle);
EXPECT_EQ(OK,
connection->Init(test_host_port_pair.ToString(),
transport_params, MEDIUM,
NULL, http_session->transport_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<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.get()));
scoped_refptr<SpdyStream> spdy_stream2;
ASSERT_EQ(ERR_IO_PENDING,
session->CreateStream(url,
MEDIUM, /* priority, not important */
&spdy_stream2,
BoundNetLog(),
callback.get()));
// 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(SpdySessionTest, SendSettingsOnNewSession) {
SpdySessionDependencies session_deps;
session_deps.host_resolver->set_synchronous_mode(true);
MockRead reads[] = {
MockRead(false, 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(false, 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(false, 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->mutable_spdy_settings()->Set(
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,
GURL(),
false,
false));
scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle);
EXPECT_EQ(OK,
connection->Init(test_host_port_pair.ToString(),
transport_params, MEDIUM,
NULL, http_session->transport_socket_pool(),
BoundNetLog()));
EXPECT_EQ(OK, session->InitializeWithSocket(connection.release(), false, OK));
MessageLoop::current()->RunAllPending();
EXPECT_TRUE(data.at_write_eof());
}
// 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, NULL, NULL, BoundNetLog());
// Setup a HostPortProxyPair
test_hosts[i].pair = HostPortProxyPair(
HostPortPair(test_hosts[i].name, kTestPort), ProxyServer::Direct());
}
MockConnect connect_data(false, OK);
MockRead reads[] = {
MockRead(false, 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(false, 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,
GURL(),
false,
false));
scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle);
EXPECT_EQ(OK,
connection->Init(test_host_port_pair.ToString(),
transport_params, MEDIUM,
NULL, http_session->transport_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<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));
}
TEST_F(SpdySessionTest, IPPooling) {
IPPoolingTest(false);
}
TEST_F(SpdySessionTest, IPPoolingCloseCurrentSessions) {
IPPoolingTest(true);
}
TEST_F(SpdySessionTest, 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(SpdySessionTest, 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());
SpdySettingsStorage* test_settings_storage =
spdy_session_pool->mutable_spdy_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;
spdy::SpdySettings test_settings;
test_settings.push_back(spdy::SpdySetting(id, max_concurrent_streams));
test_settings_storage->Set(test_host_port_pair, test_settings);
EXPECT_NE(0u, test_settings_storage->Get(test_host_port_pair).size());
spdy_session_pool->OnIPAddressChanged();
EXPECT_EQ(0u, test_settings_storage->Get(test_host_port_pair).size());
}
} // namespace
} // namespace net