1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #include "net/spdy/spdy_session.h" 6 7 #include "net/spdy/spdy_io_buffer.h" 8 #include "net/spdy/spdy_session_pool.h" 9 #include "net/spdy/spdy_stream.h" 10 #include "net/spdy/spdy_test_util.h" 11 #include "testing/platform_test.h" 12 13 namespace net { 14 15 // TODO(cbentzel): Expose compression setter/getter in public SpdySession 16 // interface rather than going through all these contortions. 17 class SpdySessionTest : public PlatformTest { 18 public: 19 static void TurnOffCompression() { 20 spdy::SpdyFramer::set_enable_compression_default(false); 21 } 22 }; 23 24 namespace { 25 26 // Test the SpdyIOBuffer class. 27 TEST_F(SpdySessionTest, SpdyIOBuffer) { 28 std::priority_queue<SpdyIOBuffer> queue_; 29 const size_t kQueueSize = 100; 30 31 // Insert 100 items; pri 100 to 1. 32 for (size_t index = 0; index < kQueueSize; ++index) { 33 SpdyIOBuffer buffer(new IOBuffer(), 0, kQueueSize - index, NULL); 34 queue_.push(buffer); 35 } 36 37 // Insert several priority 0 items last. 38 const size_t kNumDuplicates = 12; 39 IOBufferWithSize* buffers[kNumDuplicates]; 40 for (size_t index = 0; index < kNumDuplicates; ++index) { 41 buffers[index] = new IOBufferWithSize(index+1); 42 queue_.push(SpdyIOBuffer(buffers[index], buffers[index]->size(), 0, NULL)); 43 } 44 45 EXPECT_EQ(kQueueSize + kNumDuplicates, queue_.size()); 46 47 // Verify the P0 items come out in FIFO order. 48 for (size_t index = 0; index < kNumDuplicates; ++index) { 49 SpdyIOBuffer buffer = queue_.top(); 50 EXPECT_EQ(0, buffer.priority()); 51 EXPECT_EQ(index + 1, buffer.size()); 52 queue_.pop(); 53 } 54 55 int priority = 1; 56 while (queue_.size()) { 57 SpdyIOBuffer buffer = queue_.top(); 58 EXPECT_EQ(priority++, buffer.priority()); 59 queue_.pop(); 60 } 61 } 62 63 TEST_F(SpdySessionTest, GoAway) { 64 SpdySessionDependencies session_deps; 65 session_deps.host_resolver->set_synchronous_mode(true); 66 67 MockConnect connect_data(false, OK); 68 scoped_ptr<spdy::SpdyFrame> goaway(ConstructSpdyGoAway()); 69 MockRead reads[] = { 70 CreateMockRead(*goaway), 71 MockRead(false, 0, 0) // EOF 72 }; 73 StaticSocketDataProvider data(reads, arraysize(reads), NULL, 0); 74 data.set_connect_data(connect_data); 75 session_deps.socket_factory->AddSocketDataProvider(&data); 76 77 SSLSocketDataProvider ssl(false, OK); 78 session_deps.socket_factory->AddSSLSocketDataProvider(&ssl); 79 80 scoped_refptr<HttpNetworkSession> http_session( 81 SpdySessionDependencies::SpdyCreateSession(&session_deps)); 82 83 const std::string kTestHost("www.foo.com"); 84 const int kTestPort = 80; 85 HostPortPair test_host_port_pair(kTestHost, kTestPort); 86 HostPortProxyPair pair(test_host_port_pair, ProxyServer::Direct()); 87 88 SpdySessionPool* spdy_session_pool(http_session->spdy_session_pool()); 89 EXPECT_FALSE(spdy_session_pool->HasSession(pair)); 90 scoped_refptr<SpdySession> session = 91 spdy_session_pool->Get(pair, BoundNetLog()); 92 EXPECT_TRUE(spdy_session_pool->HasSession(pair)); 93 94 scoped_refptr<TransportSocketParams> transport_params( 95 new TransportSocketParams(test_host_port_pair, 96 MEDIUM, 97 GURL(), 98 false, 99 false)); 100 scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle); 101 EXPECT_EQ(OK, 102 connection->Init(test_host_port_pair.ToString(), 103 transport_params, MEDIUM, 104 NULL, http_session->transport_socket_pool(), 105 BoundNetLog())); 106 EXPECT_EQ(OK, session->InitializeWithSocket(connection.release(), false, OK)); 107 108 // Flush the SpdySession::OnReadComplete() task. 109 MessageLoop::current()->RunAllPending(); 110 111 EXPECT_FALSE(spdy_session_pool->HasSession(pair)); 112 113 scoped_refptr<SpdySession> session2 = 114 spdy_session_pool->Get(pair, BoundNetLog()); 115 116 // Delete the first session. 117 session = NULL; 118 119 // Delete the second session. 120 spdy_session_pool->Remove(session2); 121 session2 = NULL; 122 } 123 124 class StreamReleaserCallback : public CallbackRunner<Tuple1<int> > { 125 public: 126 StreamReleaserCallback(SpdySession* session, 127 SpdyStream* first_stream) 128 : session_(session), first_stream_(first_stream) {} 129 ~StreamReleaserCallback() {} 130 131 int WaitForResult() { return callback_.WaitForResult(); } 132 133 virtual void RunWithParams(const Tuple1<int>& params) { 134 session_->CloseSessionOnError(ERR_FAILED, false); 135 session_ = NULL; 136 first_stream_->Cancel(); 137 first_stream_ = NULL; 138 stream_->Cancel(); 139 stream_ = NULL; 140 callback_.RunWithParams(params); 141 } 142 143 scoped_refptr<SpdyStream>* stream() { return &stream_; } 144 145 private: 146 scoped_refptr<SpdySession> session_; 147 scoped_refptr<SpdyStream> first_stream_; 148 scoped_refptr<SpdyStream> stream_; 149 TestCompletionCallback callback_; 150 }; 151 152 // Start with max concurrent streams set to 1. Request two streams. Receive a 153 // settings frame setting max concurrent streams to 2. Have the callback 154 // release the stream, which releases its reference (the last) to the session. 155 // Make sure nothing blows up. 156 // http://crbug.com/57331 157 TEST_F(SpdySessionTest, OnSettings) { 158 SpdySessionDependencies session_deps; 159 session_deps.host_resolver->set_synchronous_mode(true); 160 161 spdy::SpdySettings new_settings; 162 spdy::SettingsFlagsAndId id(spdy::SETTINGS_MAX_CONCURRENT_STREAMS); 163 id.set_id(spdy::SETTINGS_MAX_CONCURRENT_STREAMS); 164 const size_t max_concurrent_streams = 2; 165 new_settings.push_back(spdy::SpdySetting(id, max_concurrent_streams)); 166 167 // Set up the socket so we read a SETTINGS frame that raises max concurrent 168 // streams to 2. 169 MockConnect connect_data(false, OK); 170 scoped_ptr<spdy::SpdyFrame> settings_frame( 171 ConstructSpdySettings(new_settings)); 172 MockRead reads[] = { 173 CreateMockRead(*settings_frame), 174 MockRead(false, 0, 0) // EOF 175 }; 176 177 StaticSocketDataProvider data(reads, arraysize(reads), NULL, 0); 178 data.set_connect_data(connect_data); 179 session_deps.socket_factory->AddSocketDataProvider(&data); 180 181 SSLSocketDataProvider ssl(false, OK); 182 session_deps.socket_factory->AddSSLSocketDataProvider(&ssl); 183 184 scoped_refptr<HttpNetworkSession> http_session( 185 SpdySessionDependencies::SpdyCreateSession(&session_deps)); 186 187 const std::string kTestHost("www.foo.com"); 188 const int kTestPort = 80; 189 HostPortPair test_host_port_pair(kTestHost, kTestPort); 190 HostPortProxyPair pair(test_host_port_pair, ProxyServer::Direct()); 191 192 // Initialize the SpdySettingsStorage with 1 max concurrent streams. 193 SpdySessionPool* spdy_session_pool(http_session->spdy_session_pool()); 194 spdy::SpdySettings old_settings; 195 id.set_flags(spdy::SETTINGS_FLAG_PLEASE_PERSIST); 196 old_settings.push_back(spdy::SpdySetting(id, 1)); 197 spdy_session_pool->mutable_spdy_settings()->Set( 198 test_host_port_pair, old_settings); 199 200 // Create a session. 201 EXPECT_FALSE(spdy_session_pool->HasSession(pair)); 202 scoped_refptr<SpdySession> session = 203 spdy_session_pool->Get(pair, BoundNetLog()); 204 ASSERT_TRUE(spdy_session_pool->HasSession(pair)); 205 206 scoped_refptr<TransportSocketParams> transport_params( 207 new TransportSocketParams(test_host_port_pair, 208 MEDIUM, 209 GURL(), 210 false, 211 false)); 212 scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle); 213 EXPECT_EQ(OK, 214 connection->Init(test_host_port_pair.ToString(), 215 transport_params, MEDIUM, 216 NULL, http_session->transport_socket_pool(), 217 BoundNetLog())); 218 EXPECT_EQ(OK, session->InitializeWithSocket(connection.release(), false, OK)); 219 220 // Create 2 streams. First will succeed. Second will be pending. 221 scoped_refptr<SpdyStream> spdy_stream1; 222 TestCompletionCallback callback1; 223 GURL url("http://www.google.com"); 224 EXPECT_EQ(OK, 225 session->CreateStream(url, 226 MEDIUM, /* priority, not important */ 227 &spdy_stream1, 228 BoundNetLog(), 229 &callback1)); 230 231 StreamReleaserCallback stream_releaser(session, spdy_stream1); 232 233 ASSERT_EQ(ERR_IO_PENDING, 234 session->CreateStream(url, 235 MEDIUM, /* priority, not important */ 236 stream_releaser.stream(), 237 BoundNetLog(), 238 &stream_releaser)); 239 240 // Make sure |stream_releaser| holds the last refs. 241 session = NULL; 242 spdy_stream1 = NULL; 243 244 EXPECT_EQ(OK, stream_releaser.WaitForResult()); 245 } 246 247 // Start with max concurrent streams set to 1. Request two streams. When the 248 // first completes, have the callback close itself, which should trigger the 249 // second stream creation. Then cancel that one immediately. Don't crash. 250 // http://crbug.com/63532 251 TEST_F(SpdySessionTest, CancelPendingCreateStream) { 252 SpdySessionDependencies session_deps; 253 session_deps.host_resolver->set_synchronous_mode(true); 254 255 MockRead reads[] = { 256 MockRead(false, ERR_IO_PENDING) // Stall forever. 257 }; 258 259 StaticSocketDataProvider data(reads, arraysize(reads), NULL, 0); 260 MockConnect connect_data(false, OK); 261 262 data.set_connect_data(connect_data); 263 session_deps.socket_factory->AddSocketDataProvider(&data); 264 265 SSLSocketDataProvider ssl(false, OK); 266 session_deps.socket_factory->AddSSLSocketDataProvider(&ssl); 267 268 scoped_refptr<HttpNetworkSession> http_session( 269 SpdySessionDependencies::SpdyCreateSession(&session_deps)); 270 271 const std::string kTestHost("www.foo.com"); 272 const int kTestPort = 80; 273 HostPortPair test_host_port_pair(kTestHost, kTestPort); 274 HostPortProxyPair pair(test_host_port_pair, ProxyServer::Direct()); 275 276 // Initialize the SpdySettingsStorage with 1 max concurrent streams. 277 SpdySessionPool* spdy_session_pool(http_session->spdy_session_pool()); 278 spdy::SpdySettings settings; 279 spdy::SettingsFlagsAndId id(spdy::SETTINGS_MAX_CONCURRENT_STREAMS); 280 id.set_id(spdy::SETTINGS_MAX_CONCURRENT_STREAMS); 281 id.set_flags(spdy::SETTINGS_FLAG_PLEASE_PERSIST); 282 settings.push_back(spdy::SpdySetting(id, 1)); 283 spdy_session_pool->mutable_spdy_settings()->Set( 284 test_host_port_pair, settings); 285 286 // Create a session. 287 EXPECT_FALSE(spdy_session_pool->HasSession(pair)); 288 scoped_refptr<SpdySession> session = 289 spdy_session_pool->Get(pair, BoundNetLog()); 290 ASSERT_TRUE(spdy_session_pool->HasSession(pair)); 291 292 scoped_refptr<TransportSocketParams> transport_params( 293 new TransportSocketParams(test_host_port_pair, 294 MEDIUM, 295 GURL(), 296 false, 297 false)); 298 scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle); 299 EXPECT_EQ(OK, 300 connection->Init(test_host_port_pair.ToString(), 301 transport_params, MEDIUM, 302 NULL, http_session->transport_socket_pool(), 303 BoundNetLog())); 304 EXPECT_EQ(OK, session->InitializeWithSocket(connection.release(), false, OK)); 305 306 // Use scoped_ptr to let us invalidate the memory when we want to, to trigger 307 // a valgrind error if the callback is invoked when it's not supposed to be. 308 scoped_ptr<TestCompletionCallback> callback(new TestCompletionCallback); 309 310 // Create 2 streams. First will succeed. Second will be pending. 311 scoped_refptr<SpdyStream> spdy_stream1; 312 GURL url("http://www.google.com"); 313 ASSERT_EQ(OK, 314 session->CreateStream(url, 315 MEDIUM, /* priority, not important */ 316 &spdy_stream1, 317 BoundNetLog(), 318 callback.get())); 319 320 scoped_refptr<SpdyStream> spdy_stream2; 321 ASSERT_EQ(ERR_IO_PENDING, 322 session->CreateStream(url, 323 MEDIUM, /* priority, not important */ 324 &spdy_stream2, 325 BoundNetLog(), 326 callback.get())); 327 328 // Release the first one, this will allow the second to be created. 329 spdy_stream1->Cancel(); 330 spdy_stream1 = NULL; 331 332 session->CancelPendingCreateStreams(&spdy_stream2); 333 callback.reset(); 334 335 // Should not crash when running the pending callback. 336 MessageLoop::current()->RunAllPending(); 337 } 338 339 TEST_F(SpdySessionTest, SendSettingsOnNewSession) { 340 SpdySessionDependencies session_deps; 341 session_deps.host_resolver->set_synchronous_mode(true); 342 343 MockRead reads[] = { 344 MockRead(false, ERR_IO_PENDING) // Stall forever. 345 }; 346 347 // Create the bogus setting that we want to verify is sent out. 348 // Note that it will be marked as SETTINGS_FLAG_PERSISTED when sent out. But 349 // to set it into the SpdySettingsStorage, we need to mark as 350 // SETTINGS_FLAG_PLEASE_PERSIST. 351 spdy::SpdySettings settings; 352 const uint32 kBogusSettingId = 0xABAB; 353 const uint32 kBogusSettingValue = 0xCDCD; 354 spdy::SettingsFlagsAndId id(kBogusSettingId); 355 id.set_id(kBogusSettingId); 356 id.set_flags(spdy::SETTINGS_FLAG_PERSISTED); 357 settings.push_back(spdy::SpdySetting(id, kBogusSettingValue)); 358 MockConnect connect_data(false, OK); 359 scoped_ptr<spdy::SpdyFrame> settings_frame( 360 ConstructSpdySettings(settings)); 361 MockWrite writes[] = { 362 CreateMockWrite(*settings_frame), 363 }; 364 365 StaticSocketDataProvider data( 366 reads, arraysize(reads), writes, arraysize(writes)); 367 data.set_connect_data(connect_data); 368 session_deps.socket_factory->AddSocketDataProvider(&data); 369 370 SSLSocketDataProvider ssl(false, OK); 371 session_deps.socket_factory->AddSSLSocketDataProvider(&ssl); 372 373 scoped_refptr<HttpNetworkSession> http_session( 374 SpdySessionDependencies::SpdyCreateSession(&session_deps)); 375 376 const std::string kTestHost("www.foo.com"); 377 const int kTestPort = 80; 378 HostPortPair test_host_port_pair(kTestHost, kTestPort); 379 HostPortProxyPair pair(test_host_port_pair, ProxyServer::Direct()); 380 381 id.set_flags(spdy::SETTINGS_FLAG_PLEASE_PERSIST); 382 settings.clear(); 383 settings.push_back(spdy::SpdySetting(id, kBogusSettingValue)); 384 SpdySessionPool* spdy_session_pool(http_session->spdy_session_pool()); 385 spdy_session_pool->mutable_spdy_settings()->Set( 386 test_host_port_pair, settings); 387 EXPECT_FALSE(spdy_session_pool->HasSession(pair)); 388 scoped_refptr<SpdySession> session = 389 spdy_session_pool->Get(pair, BoundNetLog()); 390 EXPECT_TRUE(spdy_session_pool->HasSession(pair)); 391 392 scoped_refptr<TransportSocketParams> transport_params( 393 new TransportSocketParams(test_host_port_pair, 394 MEDIUM, 395 GURL(), 396 false, 397 false)); 398 scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle); 399 EXPECT_EQ(OK, 400 connection->Init(test_host_port_pair.ToString(), 401 transport_params, MEDIUM, 402 NULL, http_session->transport_socket_pool(), 403 BoundNetLog())); 404 EXPECT_EQ(OK, session->InitializeWithSocket(connection.release(), false, OK)); 405 MessageLoop::current()->RunAllPending(); 406 EXPECT_TRUE(data.at_write_eof()); 407 } 408 409 // This test has two variants, one for each style of closing the connection. 410 // If |clean_via_close_current_sessions| is false, the sessions are closed 411 // manually, calling SpdySessionPool::Remove() directly. If it is true, 412 // sessions are closed with SpdySessionPool::CloseCurrentSessions(). 413 void IPPoolingTest(bool clean_via_close_current_sessions) { 414 const int kTestPort = 80; 415 struct TestHosts { 416 std::string name; 417 std::string iplist; 418 HostPortProxyPair pair; 419 } test_hosts[] = { 420 { "www.foo.com", "192.168.0.1,192.168.0.5" }, 421 { "images.foo.com", "192.168.0.2,192.168.0.3,192.168.0.5" }, 422 { "js.foo.com", "192.168.0.4,192.168.0.3" }, 423 }; 424 425 SpdySessionDependencies session_deps; 426 session_deps.host_resolver->set_synchronous_mode(true); 427 for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_hosts); i++) { 428 session_deps.host_resolver->rules()->AddIPLiteralRule(test_hosts[i].name, 429 test_hosts[i].iplist, ""); 430 431 // This test requires that the HostResolver cache be populated. Normal 432 // code would have done this already, but we do it manually. 433 HostResolver::RequestInfo info(HostPortPair(test_hosts[i].name, kTestPort)); 434 AddressList result; 435 session_deps.host_resolver->Resolve( 436 info, &result, NULL, NULL, BoundNetLog()); 437 438 // Setup a HostPortProxyPair 439 test_hosts[i].pair = HostPortProxyPair( 440 HostPortPair(test_hosts[i].name, kTestPort), ProxyServer::Direct()); 441 } 442 443 MockConnect connect_data(false, OK); 444 MockRead reads[] = { 445 MockRead(false, ERR_IO_PENDING) // Stall forever. 446 }; 447 448 StaticSocketDataProvider data(reads, arraysize(reads), NULL, 0); 449 data.set_connect_data(connect_data); 450 session_deps.socket_factory->AddSocketDataProvider(&data); 451 452 SSLSocketDataProvider ssl(false, OK); 453 session_deps.socket_factory->AddSSLSocketDataProvider(&ssl); 454 455 scoped_refptr<HttpNetworkSession> http_session( 456 SpdySessionDependencies::SpdyCreateSession(&session_deps)); 457 458 // Setup the first session to the first host. 459 SpdySessionPool* spdy_session_pool(http_session->spdy_session_pool()); 460 EXPECT_FALSE(spdy_session_pool->HasSession(test_hosts[0].pair)); 461 scoped_refptr<SpdySession> session = 462 spdy_session_pool->Get(test_hosts[0].pair, BoundNetLog()); 463 EXPECT_TRUE(spdy_session_pool->HasSession(test_hosts[0].pair)); 464 465 HostPortPair test_host_port_pair(test_hosts[0].name, kTestPort); 466 scoped_refptr<TransportSocketParams> transport_params( 467 new TransportSocketParams(test_host_port_pair, 468 MEDIUM, 469 GURL(), 470 false, 471 false)); 472 scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle); 473 EXPECT_EQ(OK, 474 connection->Init(test_host_port_pair.ToString(), 475 transport_params, MEDIUM, 476 NULL, http_session->transport_socket_pool(), 477 BoundNetLog())); 478 EXPECT_EQ(OK, session->InitializeWithSocket(connection.release(), false, OK)); 479 480 // Flush the SpdySession::OnReadComplete() task. 481 MessageLoop::current()->RunAllPending(); 482 483 // The third host has no overlap with the first, so it can't pool IPs. 484 EXPECT_FALSE(spdy_session_pool->HasSession(test_hosts[2].pair)); 485 486 // The second host overlaps with the first, and should IP pool. 487 EXPECT_TRUE(spdy_session_pool->HasSession(test_hosts[1].pair)); 488 489 // Verify that the second host, through a proxy, won't share the IP. 490 HostPortProxyPair proxy_pair(test_hosts[1].pair.first, 491 ProxyServer::FromPacString("HTTP http://proxy.foo.com/")); 492 EXPECT_FALSE(spdy_session_pool->HasSession(proxy_pair)); 493 494 // Overlap between 2 and 3 does is not transitive to 1. 495 EXPECT_FALSE(spdy_session_pool->HasSession(test_hosts[2].pair)); 496 497 // Create a new session to host 2. 498 scoped_refptr<SpdySession> session2 = 499 spdy_session_pool->Get(test_hosts[2].pair, BoundNetLog()); 500 501 // Verify that we have sessions for everything. 502 EXPECT_TRUE(spdy_session_pool->HasSession(test_hosts[0].pair)); 503 EXPECT_TRUE(spdy_session_pool->HasSession(test_hosts[1].pair)); 504 EXPECT_TRUE(spdy_session_pool->HasSession(test_hosts[2].pair)); 505 506 // Cleanup the sessions. 507 if (!clean_via_close_current_sessions) { 508 spdy_session_pool->Remove(session); 509 session = NULL; 510 spdy_session_pool->Remove(session2); 511 session2 = NULL; 512 } else { 513 spdy_session_pool->CloseCurrentSessions(); 514 } 515 516 // Verify that the map is all cleaned up. 517 EXPECT_FALSE(spdy_session_pool->HasSession(test_hosts[0].pair)); 518 EXPECT_FALSE(spdy_session_pool->HasSession(test_hosts[1].pair)); 519 EXPECT_FALSE(spdy_session_pool->HasSession(test_hosts[2].pair)); 520 } 521 522 TEST_F(SpdySessionTest, IPPooling) { 523 IPPoolingTest(false); 524 } 525 526 TEST_F(SpdySessionTest, IPPoolingCloseCurrentSessions) { 527 IPPoolingTest(true); 528 } 529 530 } // namespace 531 532 } // namespace net 533