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:
TurnOffCompression()19 static void TurnOffCompression() {
20 spdy::SpdyFramer::set_enable_compression_default(false);
21 }
22 };
23
24 namespace {
25
26 // Test the SpdyIOBuffer class.
TEST_F(SpdySessionTest,SpdyIOBuffer)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
TEST_F(SpdySessionTest,GoAway)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:
StreamReleaserCallback(SpdySession * session,SpdyStream * first_stream)126 StreamReleaserCallback(SpdySession* session,
127 SpdyStream* first_stream)
128 : session_(session), first_stream_(first_stream) {}
~StreamReleaserCallback()129 ~StreamReleaserCallback() {}
130
WaitForResult()131 int WaitForResult() { return callback_.WaitForResult(); }
132
RunWithParams(const Tuple1<int> & params)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
stream()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
TEST_F(SpdySessionTest,OnSettings)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
TEST_F(SpdySessionTest,CancelPendingCreateStream)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
TEST_F(SpdySessionTest,SendSettingsOnNewSession)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().
IPPoolingTest(bool clean_via_close_current_sessions)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
TEST_F(SpdySessionTest,IPPooling)522 TEST_F(SpdySessionTest, IPPooling) {
523 IPPoolingTest(false);
524 }
525
TEST_F(SpdySessionTest,IPPoolingCloseCurrentSessions)526 TEST_F(SpdySessionTest, IPPoolingCloseCurrentSessions) {
527 IPPoolingTest(true);
528 }
529
530 } // namespace
531
532 } // namespace net
533