// Copyright (c) 2010 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_network_transaction.h" #include "base/basictypes.h" #include "base/ref_counted.h" #include "net/base/completion_callback.h" #include "net/base/load_log_unittest.h" #include "net/base/mock_host_resolver.h" #include "net/base/ssl_config_service_defaults.h" #include "net/base/test_completion_callback.h" #include "net/base/upload_data.h" #include "net/http/http_auth_handler_factory.h" #include "net/http/http_network_session.h" #include "net/http/http_transaction_unittest.h" #include "net/proxy/proxy_config_service_fixed.h" #include "net/socket/socket_test_util.h" #include "net/spdy/spdy_protocol.h" #include "testing/platform_test.h" //----------------------------------------------------------------------------- namespace net { namespace { // Helper to manage the lifetimes of the dependencies for a // SpdyNetworkTransaction. class SessionDependencies { public: // Default set of dependencies -- "null" proxy service. SessionDependencies() : host_resolver(new MockHostResolver), proxy_service(ProxyService::CreateNull()), ssl_config_service(new SSLConfigServiceDefaults), http_auth_handler_factory(HttpAuthHandlerFactory::CreateDefault()), spdy_session_pool(new SpdySessionPool) { // Note: The CancelledTransaction test does cleanup by running all tasks // in the message loop (RunAllPending). Unfortunately, that doesn't clean // up tasks on the host resolver thread; and TCPConnectJob is currently // not cancellable. Using synchronous lookups allows the test to shutdown // cleanly. Until we have cancellable TCPConnectJobs, use synchronous // lookups. host_resolver->set_synchronous_mode(true); } // Custom proxy service dependency. explicit SessionDependencies(ProxyService* proxy_service) : host_resolver(new MockHostResolver), proxy_service(proxy_service), ssl_config_service(new SSLConfigServiceDefaults), http_auth_handler_factory(HttpAuthHandlerFactory::CreateDefault()), spdy_session_pool(new SpdySessionPool) {} scoped_refptr host_resolver; scoped_refptr proxy_service; scoped_refptr ssl_config_service; MockClientSocketFactory socket_factory; scoped_ptr http_auth_handler_factory; scoped_refptr spdy_session_pool; }; HttpNetworkSession* CreateSession(SessionDependencies* session_deps) { return new HttpNetworkSession(NULL, session_deps->host_resolver, session_deps->proxy_service, &session_deps->socket_factory, session_deps->ssl_config_service, session_deps->spdy_session_pool, session_deps->http_auth_handler_factory.get()); } // Chop a frame into an array of MockWrites. // |data| is the frame to chop. // |length| is the length of the frame to chop. // |num_chunks| is the number of chunks to create. MockWrite* ChopFrame(const char* data, int length, int num_chunks) { MockWrite* chunks = new MockWrite[num_chunks]; int chunk_size = length / num_chunks; for (int index = 0; index < num_chunks; index++) { const char* ptr = data + (index * chunk_size); if (index == num_chunks - 1) chunk_size += length % chunk_size; // The last chunk takes the remainder. chunks[index] = MockWrite(true, ptr, chunk_size); } return chunks; } // ---------------------------------------------------------------------------- static const unsigned char kGetSyn[] = { 0x80, 0x01, 0x00, 0x01, // header 0x01, 0x00, 0x00, 0x49, // FIN, len 0x00, 0x00, 0x00, 0x01, // stream id 0x00, 0x00, 0x00, 0x00, // associated 0xc0, 0x00, 0x00, 0x03, // 4 headers 0x00, 0x06, 'm', 'e', 't', 'h', 'o', 'd', 0x00, 0x03, 'G', 'E', 'T', 0x00, 0x03, 'u', 'r', 'l', 0x00, 0x16, 'h', 't', 't', 'p', ':', '/', '/', 'w', 'w', 'w', '.', 'g', 'o', 'o', 'g', 'l', 'e', '.', 'c', 'o', 'm', '/', 0x00, 0x07, 'v', 'e', 'r', 's', 'i', 'o', 'n', 0x00, 0x08, 'H', 'T', 'T', 'P', '/', '1', '.', '1', }; static const unsigned char kGetSynCompressed[] = { 0x80, 0x01, 0x00, 0x01, 0x01, 0x00, 0x00, 0x47, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x38, 0xea, 0xdf, 0xa2, 0x51, 0xb2, 0x62, 0x60, 0x66, 0x60, 0xcb, 0x05, 0xe6, 0xc3, 0xfc, 0x14, 0x06, 0x66, 0x77, 0xd7, 0x10, 0x06, 0x66, 0x90, 0xa0, 0x58, 0x46, 0x49, 0x49, 0x81, 0x95, 0xbe, 0x3e, 0x30, 0xe2, 0xf5, 0xd2, 0xf3, 0xf3, 0xd3, 0x73, 0x52, 0xf5, 0x92, 0xf3, 0x73, 0xf5, 0x19, 0xd8, 0xa1, 0x1a, 0x19, 0x38, 0x60, 0xe6, 0x01, 0x00, 0x00, 0x00, 0xff, 0xff }; static const unsigned char kGetSynReply[] = { 0x80, 0x01, 0x00, 0x02, // header 0x00, 0x00, 0x00, 0x45, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x04, // 4 headers 0x00, 0x05, 'h', 'e', 'l', 'l', 'o', // "hello" 0x00, 0x03, 'b', 'y', 'e', // "bye" 0x00, 0x06, 's', 't', 'a', 't', 'u', 's', // "status" 0x00, 0x03, '2', '0', '0', // "200" 0x00, 0x03, 'u', 'r', 'l', // "url" 0x00, 0x0a, '/', 'i', 'n', 'd', 'e', 'x', '.', 'p', 'h', 'p', // "/index... 0x00, 0x07, 'v', 'e', 'r', 's', 'i', 'o', 'n', // "version" 0x00, 0x08, 'H', 'T', 'T', 'P', '/', '1', '.', '1', // "HTTP/1.1" }; static const unsigned char kGetBodyFrame[] = { 0x00, 0x00, 0x00, 0x01, // header 0x01, 0x00, 0x00, 0x06, // FIN, length 'h', 'e', 'l', 'l', 'o', '!', // "hello" }; static const unsigned char kPostSyn[] = { 0x80, 0x01, 0x00, 0x01, // header 0x00, 0x00, 0x00, 0x4a, // flags, len 0x00, 0x00, 0x00, 0x01, // stream id 0x00, 0x00, 0x00, 0x00, // associated 0xc0, 0x00, 0x00, 0x03, // 4 headers 0x00, 0x06, 'm', 'e', 't', 'h', 'o', 'd', 0x00, 0x04, 'P', 'O', 'S', 'T', 0x00, 0x03, 'u', 'r', 'l', 0x00, 0x16, 'h', 't', 't', 'p', ':', '/', '/', 'w', 'w', 'w', '.', 'g', 'o', 'o', 'g', 'l', 'e', '.', 'c', 'o', 'm', '/', 0x00, 0x07, 'v', 'e', 'r', 's', 'i', 'o', 'n', 0x00, 0x08, 'H', 'T', 'T', 'P', '/', '1', '.', '1', }; static const unsigned char kPostUploadFrame[] = { 0x00, 0x00, 0x00, 0x01, // header 0x01, 0x00, 0x00, 0x0c, // FIN flag 'h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd', '\0' }; // The response static const unsigned char kPostSynReply[] = { 0x80, 0x01, 0x00, 0x02, // header 0x00, 0x00, 0x00, 0x45, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x04, // 4 headers 0x00, 0x05, 'h', 'e', 'l', 'l', 'o', // "hello" 0x00, 0x03, 'b', 'y', 'e', // "bye" 0x00, 0x06, 's', 't', 'a', 't', 'u', 's', // "status" 0x00, 0x03, '2', '0', '0', // "200" 0x00, 0x03, 'u', 'r', 'l', // "url" // "/index.php" 0x00, 0x0a, '/', 'i', 'n', 'd', 'e', 'x', '.', 'p', 'h', 'p', 0x00, 0x07, 'v', 'e', 'r', 's', 'i', 'o', 'n', // "version" 0x00, 0x08, 'H', 'T', 'T', 'P', '/', '1', '.', '1', // "HTTP/1.1" }; static const unsigned char kPostBodyFrame[] = { 0x00, 0x00, 0x00, 0x01, // header 0x01, 0x00, 0x00, 0x06, // FIN, length 'h', 'e', 'l', 'l', 'o', '!', // "hello" }; } // namespace // A DataProvider where the client must write a request before the reads (e.g. // the response) will complete. class DelayedSocketData : public StaticSocketDataProvider, public base::RefCounted { public: // |write_delay| the number of MockWrites to complete before allowing // a MockRead to complete. // |reads| the list of MockRead completions. // |writes| the list of MockWrite completions. // Note: All MockReads and MockWrites must be async. // Note: The MockRead and MockWrite lists musts end with a EOF // e.g. a MockRead(true, 0, 0); DelayedSocketData(int write_delay, MockRead* reads, size_t reads_count, MockWrite* writes, size_t writes_count) : StaticSocketDataProvider(reads, reads_count, writes, writes_count), write_delay_(write_delay), ALLOW_THIS_IN_INITIALIZER_LIST(factory_(this)) { DCHECK_GE(write_delay_, 0); } // |connect| the result for the connect phase. // |reads| the list of MockRead completions. // |write_delay| the number of MockWrites to complete before allowing // a MockRead to complete. // |writes| the list of MockWrite completions. // Note: All MockReads and MockWrites must be async. // Note: The MockRead and MockWrite lists musts end with a EOF // e.g. a MockRead(true, 0, 0); DelayedSocketData(const MockConnect& connect, int write_delay, MockRead* reads, size_t reads_count, MockWrite* writes, size_t writes_count) : StaticSocketDataProvider(reads, reads_count, writes, writes_count), write_delay_(write_delay), ALLOW_THIS_IN_INITIALIZER_LIST(factory_(this)) { DCHECK_GE(write_delay_, 0); set_connect_data(connect); } virtual MockRead GetNextRead() { if (write_delay_) return MockRead(true, ERR_IO_PENDING); return StaticSocketDataProvider::GetNextRead(); } virtual MockWriteResult OnWrite(const std::string& data) { MockWriteResult rv = StaticSocketDataProvider::OnWrite(data); // Now that our write has completed, we can allow reads to continue. if (!--write_delay_) MessageLoop::current()->PostDelayedTask(FROM_HERE, factory_.NewRunnableMethod(&DelayedSocketData::CompleteRead), 100); return rv; } virtual void Reset() { set_socket(NULL); factory_.RevokeAll(); StaticSocketDataProvider::Reset(); } void CompleteRead() { if (socket()) socket()->OnReadComplete(GetNextRead()); } private: int write_delay_; ScopedRunnableMethodFactory factory_; }; class SpdyNetworkTransactionTest : public PlatformTest { protected: virtual void SetUp() { // By default, all tests turn off compression. EnableCompression(false); } virtual void TearDown() { // Empty the current queue. MessageLoop::current()->RunAllPending(); PlatformTest::TearDown(); } void KeepAliveConnectionResendRequestTest(const MockRead& read_failure); struct TransactionHelperResult { int rv; std::string status_line; std::string response_data; HttpResponseInfo response_info; }; void EnableCompression(bool enabled) { spdy::SpdyFramer::set_enable_compression_default(enabled); } TransactionHelperResult TransactionHelper(const HttpRequestInfo& request, DelayedSocketData* data, LoadLog* log) { TransactionHelperResult out; // We disable SSL for this test. SpdySession::SetSSLMode(false); SessionDependencies session_deps; scoped_ptr trans( new SpdyNetworkTransaction(CreateSession(&session_deps))); session_deps.socket_factory.AddSocketDataProvider(data); TestCompletionCallback callback; int rv = trans->Start(&request, &callback, log); EXPECT_EQ(ERR_IO_PENDING, rv); out.rv = callback.WaitForResult(); if (out.rv != OK) return out; const HttpResponseInfo* response = trans->GetResponseInfo(); EXPECT_TRUE(response->headers != NULL); EXPECT_TRUE(response->was_fetched_via_spdy); out.status_line = response->headers->GetStatusLine(); out.response_info = *response; // Make a copy so we can verify. rv = ReadTransaction(trans.get(), &out.response_data); EXPECT_EQ(OK, rv); // Verify that we consumed all test data. EXPECT_TRUE(data->at_read_eof()); EXPECT_TRUE(data->at_write_eof()); return out; } void ConnectStatusHelperWithExpectedStatus(const MockRead& status, int expected_status); void ConnectStatusHelper(const MockRead& status); }; //----------------------------------------------------------------------------- // Verify SpdyNetworkTransaction constructor. TEST_F(SpdyNetworkTransactionTest, Constructor) { SessionDependencies session_deps; scoped_refptr session = CreateSession(&session_deps); scoped_ptr trans(new SpdyNetworkTransaction(session)); } TEST_F(SpdyNetworkTransactionTest, Get) { MockWrite writes[] = { MockWrite(true, reinterpret_cast(kGetSyn), arraysize(kGetSyn)), }; MockRead reads[] = { MockRead(true, reinterpret_cast(kGetSynReply), arraysize(kGetSynReply)), MockRead(true, reinterpret_cast(kGetBodyFrame), arraysize(kGetBodyFrame)), MockRead(true, 0, 0) // EOF }; HttpRequestInfo request; request.method = "GET"; request.url = GURL("http://www.google.com/"); request.load_flags = 0; scoped_refptr data( new DelayedSocketData(1, reads, arraysize(reads), writes, arraysize(writes))); TransactionHelperResult out = TransactionHelper(request, data.get(), NULL); EXPECT_EQ(OK, out.rv); EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); EXPECT_EQ("hello!", out.response_data); } // Test that a simple POST works. TEST_F(SpdyNetworkTransactionTest, Post) { static const char upload[] = { "hello world" }; // Setup the request HttpRequestInfo request; request.method = "POST"; request.url = GURL("http://www.google.com/"); request.upload_data = new UploadData(); request.upload_data->AppendBytes(upload, sizeof(upload)); MockWrite writes[] = { MockWrite(true, reinterpret_cast(kPostSyn), arraysize(kPostSyn)), MockWrite(true, reinterpret_cast(kPostUploadFrame), arraysize(kPostUploadFrame)), }; MockRead reads[] = { MockRead(true, reinterpret_cast(kPostSynReply), arraysize(kPostSynReply)), MockRead(true, reinterpret_cast(kPostBodyFrame), arraysize(kPostBodyFrame)), MockRead(true, 0, 0) // EOF }; scoped_refptr data( new DelayedSocketData(2, reads, arraysize(reads), writes, arraysize(writes))); TransactionHelperResult out = TransactionHelper(request, data.get(), NULL); EXPECT_EQ(OK, out.rv); EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); EXPECT_EQ("hello!", out.response_data); } // Test that a simple POST works. TEST_F(SpdyNetworkTransactionTest, EmptyPost) { static const unsigned char kEmptyPostSyn[] = { 0x80, 0x01, 0x00, 0x01, // header 0x01, 0x00, 0x00, 0x4a, // flags, len 0x00, 0x00, 0x00, 0x01, // stream id 0x00, 0x00, 0x00, 0x00, // associated 0xc0, 0x00, 0x00, 0x03, // 4 headers 0x00, 0x06, 'm', 'e', 't', 'h', 'o', 'd', 0x00, 0x04, 'P', 'O', 'S', 'T', 0x00, 0x03, 'u', 'r', 'l', 0x00, 0x16, 'h', 't', 't', 'p', ':', '/', '/', 'w', 'w', 'w', '.', 'g', 'o', 'o', 'g', 'l', 'e', '.', 'c', 'o', 'm', '/', 0x00, 0x07, 'v', 'e', 'r', 's', 'i', 'o', 'n', 0x00, 0x08, 'H', 'T', 'T', 'P', '/', '1', '.', '1', }; // Setup the request HttpRequestInfo request; request.method = "POST"; request.url = GURL("http://www.google.com/"); // Create an empty UploadData. request.upload_data = new UploadData(); MockWrite writes[] = { MockWrite(true, reinterpret_cast(kEmptyPostSyn), arraysize(kEmptyPostSyn)), }; MockRead reads[] = { MockRead(true, reinterpret_cast(kPostSynReply), arraysize(kPostSynReply)), MockRead(true, reinterpret_cast(kPostBodyFrame), arraysize(kGetBodyFrame)), MockRead(true, 0, 0) // EOF }; scoped_refptr data( new DelayedSocketData(1, reads, arraysize(reads), writes, arraysize(writes))); TransactionHelperResult out = TransactionHelper(request, data, NULL); EXPECT_EQ(OK, out.rv); EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); EXPECT_EQ("hello!", out.response_data); } // Test that the transaction doesn't crash when we don't have a reply. TEST_F(SpdyNetworkTransactionTest, ResponseWithoutSynReply) { MockRead reads[] = { MockRead(true, reinterpret_cast(kPostBodyFrame), arraysize(kPostBodyFrame)), MockRead(true, 0, 0) // EOF }; HttpRequestInfo request; request.method = "GET"; request.url = GURL("http://www.google.com/"); request.load_flags = 0; scoped_refptr data( new DelayedSocketData(1, reads, arraysize(reads), NULL, 0)); TransactionHelperResult out = TransactionHelper(request, data.get(), NULL); EXPECT_EQ(ERR_SYN_REPLY_NOT_RECEIVED, out.rv); } TEST_F(SpdyNetworkTransactionTest, CancelledTransaction) { MockWrite writes[] = { MockWrite(true, reinterpret_cast(kGetSyn), arraysize(kGetSyn)), MockRead(true, 0, 0) // EOF }; MockRead reads[] = { MockRead(true, reinterpret_cast(kGetSynReply), arraysize(kGetSynReply)), // This following read isn't used by the test, except during the // RunAllPending() call at the end since the SpdySession survives the // SpdyNetworkTransaction and still tries to continue Read()'ing. Any // MockRead will do here. MockRead(true, 0, 0) // EOF }; HttpRequestInfo request; request.method = "GET"; request.url = GURL("http://www.google.com/"); request.load_flags = 0; // We disable SSL for this test. SpdySession::SetSSLMode(false); SessionDependencies session_deps; scoped_ptr trans( new SpdyNetworkTransaction(CreateSession(&session_deps))); StaticSocketDataProvider data(reads, arraysize(reads), writes, arraysize(writes)); session_deps.socket_factory.AddSocketDataProvider(&data); TestCompletionCallback callback; int rv = trans->Start(&request, &callback, NULL); EXPECT_EQ(ERR_IO_PENDING, rv); trans.reset(); // Cancel the transaction. // Flush the MessageLoop while the SessionDependencies (in particular, the // MockClientSocketFactory) are still alive. MessageLoop::current()->RunAllPending(); } // Verify that various SynReply headers parse correctly through the // HTTP layer. TEST_F(SpdyNetworkTransactionTest, SynReplyHeaders) { // This uses a multi-valued cookie header. static const unsigned char syn_reply1[] = { 0x80, 0x01, 0x00, 0x02, 0x00, 0x00, 0x00, 0x4c, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x00, 0x06, 'c', 'o', 'o', 'k', 'i', 'e', 0x00, 0x09, 'v', 'a', 'l', '1', '\0', 'v', 'a', 'l', '2', 0x00, 0x06, 's', 't', 'a', 't', 'u', 's', 0x00, 0x03, '2', '0', '0', 0x00, 0x03, 'u', 'r', 'l', 0x00, 0x0a, '/', 'i', 'n', 'd', 'e', 'x', '.', 'p', 'h', 'p', 0x00, 0x07, 'v', 'e', 'r', 's', 'i', 'o', 'n', 0x00, 0x08, 'H', 'T', 'T', 'P', '/', '1', '.', '1', }; // This is the minimalist set of headers. static const unsigned char syn_reply2[] = { 0x80, 0x01, 0x00, 0x02, 0x00, 0x00, 0x00, 0x39, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x00, 0x06, 's', 't', 'a', 't', 'u', 's', 0x00, 0x03, '2', '0', '0', 0x00, 0x03, 'u', 'r', 'l', 0x00, 0x0a, '/', 'i', 'n', 'd', 'e', 'x', '.', 'p', 'h', 'p', 0x00, 0x07, 'v', 'e', 'r', 's', 'i', 'o', 'n', 0x00, 0x08, 'H', 'T', 'T', 'P', '/', '1', '.', '1', }; // Headers with a comma separated list. static const unsigned char syn_reply3[] = { 0x80, 0x01, 0x00, 0x02, 0x00, 0x00, 0x00, 0x4c, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x00, 0x06, 'c', 'o', 'o', 'k', 'i', 'e', 0x00, 0x09, 'v', 'a', 'l', '1', ',', 'v', 'a', 'l', '2', 0x00, 0x06, 's', 't', 'a', 't', 'u', 's', 0x00, 0x03, '2', '0', '0', 0x00, 0x03, 'u', 'r', 'l', 0x00, 0x0a, '/', 'i', 'n', 'd', 'e', 'x', '.', 'p', 'h', 'p', 0x00, 0x07, 'v', 'e', 'r', 's', 'i', 'o', 'n', 0x00, 0x08, 'H', 'T', 'T', 'P', '/', '1', '.', '1', }; struct SynReplyTests { const unsigned char* syn_reply; int syn_reply_length; const char* expected_headers; } test_cases[] = { // Test the case of a multi-valued cookie. When the value is delimited // with NUL characters, it needs to be unfolded into multiple headers. { syn_reply1, sizeof(syn_reply1), "cookie: val1\n" "cookie: val2\n" "status: 200\n" "url: /index.php\n" "version: HTTP/1.1\n" }, // This is the simplest set of headers possible. { syn_reply2, sizeof(syn_reply2), "status: 200\n" "url: /index.php\n" "version: HTTP/1.1\n" }, // Test that a comma delimited list is NOT interpreted as a multi-value // name/value pair. The comma-separated list is just a single value. { syn_reply3, sizeof(syn_reply3), "cookie: val1,val2\n" "status: 200\n" "url: /index.php\n" "version: HTTP/1.1\n" } }; for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_cases); ++i) { MockWrite writes[] = { MockWrite(true, reinterpret_cast(kGetSyn), arraysize(kGetSyn)), }; MockRead reads[] = { MockRead(true, reinterpret_cast(test_cases[i].syn_reply), test_cases[i].syn_reply_length), MockRead(true, reinterpret_cast(kGetBodyFrame), arraysize(kGetBodyFrame)), MockRead(true, 0, 0) // EOF }; HttpRequestInfo request; request.method = "GET"; request.url = GURL("http://www.google.com/"); request.load_flags = 0; scoped_refptr data( new DelayedSocketData(1, reads, arraysize(reads), writes, arraysize(writes))); TransactionHelperResult out = TransactionHelper(request, data.get(), NULL); EXPECT_EQ(OK, out.rv); EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); EXPECT_EQ("hello!", out.response_data); scoped_refptr headers = out.response_info.headers; EXPECT_TRUE(headers.get() != NULL); void* iter = NULL; std::string name, value, lines; while (headers->EnumerateHeaderLines(&iter, &name, &value)) { lines.append(name); lines.append(": "); lines.append(value); lines.append("\n"); } EXPECT_EQ(std::string(test_cases[i].expected_headers), lines); } } // Verify that we don't crash on invalid SynReply responses. TEST_F(SpdyNetworkTransactionTest, InvalidSynReply) { static const unsigned char kSynReplyMissingStatus[] = { 0x80, 0x01, 0x00, 0x02, 0x00, 0x00, 0x00, 0x3f, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x00, 0x06, 'c', 'o', 'o', 'k', 'i', 'e', 0x00, 0x09, 'v', 'a', 'l', '1', '\0', 'v', 'a', 'l', '2', 0x00, 0x03, 'u', 'r', 'l', 0x00, 0x0a, '/', 'i', 'n', 'd', 'e', 'x', '.', 'p', 'h', 'p', 0x00, 0x07, 'v', 'e', 'r', 's', 'i', 'o', 'n', 0x00, 0x08, 'H', 'T', 'T', 'P', '/', '1', '.', '1', }; static const unsigned char kSynReplyMissingVersion[] = { 0x80, 0x01, 0x00, 0x02, 0x00, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x00, 0x06, 's', 't', 'a', 't', 'u', 's', 0x00, 0x03, '2', '0', '0', 0x00, 0x03, 'u', 'r', 'l', 0x00, 0x0a, '/', 'i', 'n', 'd', 'e', 'x', '.', 'p', 'h', 'p', }; struct SynReplyTests { const unsigned char* syn_reply; int syn_reply_length; } test_cases[] = { { kSynReplyMissingStatus, arraysize(kSynReplyMissingStatus) }, { kSynReplyMissingVersion, arraysize(kSynReplyMissingVersion) } }; for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_cases); ++i) { MockWrite writes[] = { MockWrite(true, reinterpret_cast(kGetSyn), arraysize(kGetSyn)), MockWrite(true, 0, 0) // EOF }; MockRead reads[] = { MockRead(true, reinterpret_cast(test_cases[i].syn_reply), test_cases[i].syn_reply_length), MockRead(true, reinterpret_cast(kGetBodyFrame), arraysize(kGetBodyFrame)), MockRead(true, 0, 0) // EOF }; HttpRequestInfo request; request.method = "GET"; request.url = GURL("http://www.google.com/"); request.load_flags = 0; scoped_refptr data( new DelayedSocketData(1, reads, arraysize(reads), writes, arraysize(writes))); TransactionHelperResult out = TransactionHelper(request, data.get(), NULL); EXPECT_EQ(ERR_INVALID_RESPONSE, out.rv); } } // Verify that we don't crash on some corrupt frames. TEST_F(SpdyNetworkTransactionTest, CorruptFrameSessionError) { static const unsigned char kSynReplyMassiveLength[] = { 0x80, 0x01, 0x00, 0x02, 0x0f, 0x11, 0x11, 0x26, // This is the length field with a big number 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x00, 0x06, 's', 't', 'a', 't', 'u', 's', 0x00, 0x03, '2', '0', '0', 0x00, 0x03, 'u', 'r', 'l', 0x00, 0x0a, '/', 'i', 'n', 'd', 'e', 'x', '.', 'p', 'h', 'p', }; struct SynReplyTests { const unsigned char* syn_reply; int syn_reply_length; } test_cases[] = { { kSynReplyMassiveLength, arraysize(kSynReplyMassiveLength) } }; for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_cases); ++i) { MockWrite writes[] = { MockWrite(true, reinterpret_cast(kGetSyn), arraysize(kGetSyn)), MockWrite(true, 0, 0) // EOF }; MockRead reads[] = { MockRead(true, reinterpret_cast(test_cases[i].syn_reply), test_cases[i].syn_reply_length), MockRead(true, reinterpret_cast(kGetBodyFrame), arraysize(kGetBodyFrame)), MockRead(true, 0, 0) // EOF }; HttpRequestInfo request; request.method = "GET"; request.url = GURL("http://www.google.com/"); request.load_flags = 0; scoped_refptr data( new DelayedSocketData(1, reads, arraysize(reads), writes, arraysize(writes))); TransactionHelperResult out = TransactionHelper(request, data.get(), NULL); EXPECT_EQ(ERR_SPDY_PROTOCOL_ERROR, out.rv); } } TEST_F(SpdyNetworkTransactionTest, ServerPush) { // Reply with the X-Associated-Content header. static const unsigned char syn_reply[] = { 0x80, 0x01, 0x00, 0x02, 0x00, 0x00, 0x00, 0x71, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x00, 0x14, 'X', '-', 'A', 's', 's', 'o', 'c', 'i', 'a', 't', 'e', 'd', '-', 'C', 'o', 'n', 't', 'e', 'n', 't', 0x00, 0x20, '1', '?', '?', 'h', 't', 't', 'p', ':', '/', '/', 'w', 'w', 'w', '.', 'g', 'o', 'o', 'g', 'l', 'e', '.', 'c', 'o', 'm', '/', 'f', 'o', 'o', '.', 'd', 'a', 't', 0x00, 0x06, 's', 't', 'a', 't', 'u', 's', 0x00, 0x03, '2', '0', '0', 0x00, 0x03, 'u', 'r', 'l', 0x00, 0x0a, '/', 'i', 'n', 'd', 'e', 'x', '.', 'p', 'h', 'p', 0x00, 0x07, 'v', 'e', 'r', 's', 'i', 'o', 'n', 0x00, 0x08, 'H', 'T', 'T', 'P', '/', '1', '.', '1', }; // Syn for the X-Associated-Content (foo.dat) static const unsigned char syn_push[] = { 0x80, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x4b, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, // TODO(mbelshe): use new server push protocol. 0x00, 0x00, 0x00, 0x04, 0x00, 0x04, 'p', 'a', 't', 'h', 0x00, 0x08, '/', 'f', 'o', 'o', '.', 'd', 'a', 't', 0x00, 0x06, 's', 't', 'a', 't', 'u', 's', 0x00, 0x03, '2', '0', '0', 0x00, 0x03, 'u', 'r', 'l', 0x00, 0x08, '/', 'f', 'o', 'o', '.', 'd', 'a', 't', 0x00, 0x07, 'v', 'e', 'r', 's', 'i', 'o', 'n', 0x00, 0x08, 'H', 'T', 'T', 'P', '/', '1', '.', '1', }; // Body for stream 2 static const unsigned char body_frame_2[] = { 0x00, 0x00, 0x00, 0x02, 0x01, 0x00, 0x00, 0x07, 'g', 'o', 'o', 'd', 'b', 'y', 'e', }; MockWrite writes[] = { MockWrite(true, reinterpret_cast(kGetSyn), arraysize(kGetSyn)), }; MockRead reads[] = { MockRead(true, reinterpret_cast(syn_reply), arraysize(syn_reply)), MockRead(true, reinterpret_cast(kGetBodyFrame), arraysize(kGetBodyFrame)), MockRead(true, ERR_IO_PENDING), // Force a pause MockRead(true, reinterpret_cast(syn_push), arraysize(syn_push)), MockRead(true, reinterpret_cast(body_frame_2), arraysize(body_frame_2)), MockRead(true, ERR_IO_PENDING), // Force a pause MockRead(true, 0, 0) // EOF }; // We disable SSL for this test. SpdySession::SetSSLMode(false); enum TestTypes { // Simulate that the server sends the first request, notifying the client // that it *will* push the second stream. But the client issues the // request for the second stream before the push data arrives. PUSH_AFTER_REQUEST, // Simulate that the server is sending the pushed stream data before the // client requests it. The SpdySession will buffer the response and then // deliver the data when the client does make the request. PUSH_BEFORE_REQUEST, DONE }; for (int test_type = PUSH_AFTER_REQUEST; test_type != DONE; ++test_type) { // Setup a mock session. SessionDependencies session_deps; scoped_refptr session(CreateSession(&session_deps)); scoped_refptr data( new DelayedSocketData(1, reads, arraysize(reads), writes, arraysize(writes))); session_deps.socket_factory.AddSocketDataProvider(data.get()); // Issue the first request { SpdyNetworkTransaction trans(session.get()); // Issue the first request. HttpRequestInfo request; request.method = "GET"; request.url = GURL("http://www.google.com/"); request.load_flags = 0; TestCompletionCallback callback; int rv = trans.Start(&request, &callback, NULL); EXPECT_EQ(ERR_IO_PENDING, rv); rv = callback.WaitForResult(); EXPECT_EQ(rv, OK); // Verify the SYN_REPLY. const HttpResponseInfo* response = trans.GetResponseInfo(); EXPECT_TRUE(response->headers != NULL); EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine()); if (test_type == PUSH_BEFORE_REQUEST) data->CompleteRead(); // Verify the body. std::string response_data; rv = ReadTransaction(&trans, &response_data); EXPECT_EQ(OK, rv); EXPECT_EQ("hello!", response_data); } // Issue a second request for the X-Associated-Content. { SpdyNetworkTransaction trans(session.get()); HttpRequestInfo request; request.method = "GET"; request.url = GURL("http://www.google.com/foo.dat"); request.load_flags = 0; TestCompletionCallback callback; int rv = trans.Start(&request, &callback, NULL); EXPECT_EQ(ERR_IO_PENDING, rv); // In the case where we are Complete the next read now. if (test_type == PUSH_AFTER_REQUEST) data->CompleteRead(); rv = callback.WaitForResult(); EXPECT_EQ(rv, OK); // Verify the SYN_REPLY. const HttpResponseInfo* response = trans.GetResponseInfo(); EXPECT_TRUE(response->headers != NULL); EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine()); // Verify the body. std::string response_data; rv = ReadTransaction(&trans, &response_data); EXPECT_EQ(OK, rv); EXPECT_EQ("goodbye", response_data); } // Complete the next read now and teardown. data->CompleteRead(); // Verify that we consumed all test data. EXPECT_TRUE(data->at_read_eof()); EXPECT_TRUE(data->at_write_eof()); } } // Test that we shutdown correctly on write errors. TEST_F(SpdyNetworkTransactionTest, WriteError) { MockWrite writes[] = { // We'll write 10 bytes successfully MockWrite(true, reinterpret_cast(kGetSyn), 10), // Followed by ERROR! MockWrite(true, ERR_FAILED), MockWrite(true, 0, 0) // EOF }; MockRead reads[] = { MockRead(true, reinterpret_cast(kGetSynReply), arraysize(kGetSynReply)), MockRead(true, reinterpret_cast(kGetBodyFrame), arraysize(kGetBodyFrame)), MockRead(true, 0, 0) // EOF }; HttpRequestInfo request; request.method = "GET"; request.url = GURL("http://www.google.com/"); request.load_flags = 0; scoped_refptr data( new DelayedSocketData(2, reads, arraysize(reads), writes, arraysize(writes))); TransactionHelperResult out = TransactionHelper(request, data.get(), NULL); EXPECT_EQ(ERR_FAILED, out.rv); data->Reset(); } // Test that partial writes work. TEST_F(SpdyNetworkTransactionTest, PartialWrite) { // Chop the SYN_STREAM frame into 5 chunks. const int kChunks = 5; scoped_array writes(ChopFrame( reinterpret_cast(kGetSyn), arraysize(kGetSyn), kChunks)); MockRead reads[] = { MockRead(true, reinterpret_cast(kGetSynReply), arraysize(kGetSynReply)), MockRead(true, reinterpret_cast(kGetBodyFrame), arraysize(kGetBodyFrame)), MockRead(true, 0, 0) // EOF }; HttpRequestInfo request; request.method = "GET"; request.url = GURL("http://www.google.com/"); request.load_flags = 0; scoped_refptr data( new DelayedSocketData(kChunks, reads, arraysize(reads), writes.get(), kChunks)); TransactionHelperResult out = TransactionHelper(request, data.get(), NULL); EXPECT_EQ(OK, out.rv); EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); EXPECT_EQ("hello!", out.response_data); } TEST_F(SpdyNetworkTransactionTest, DISABLED_ConnectFailure) { MockConnect connects[] = { MockConnect(true, ERR_NAME_NOT_RESOLVED), MockConnect(false, ERR_NAME_NOT_RESOLVED), MockConnect(true, ERR_INTERNET_DISCONNECTED), MockConnect(false, ERR_INTERNET_DISCONNECTED) }; for (size_t index = 0; index < arraysize(connects); ++index) { MockWrite writes[] = { MockWrite(true, reinterpret_cast(kGetSyn), arraysize(kGetSyn)), MockWrite(true, 0, 0) // EOF }; MockRead reads[] = { MockRead(true, reinterpret_cast(kGetSynReply), arraysize(kGetSynReply)), MockRead(true, reinterpret_cast(kGetBodyFrame), arraysize(kGetBodyFrame)), MockRead(true, 0, 0) // EOF }; HttpRequestInfo request; request.method = "GET"; request.url = GURL("http://www.google.com/"); request.load_flags = 0; scoped_refptr data( new DelayedSocketData(connects[index], 1, reads, arraysize(reads), writes, arraysize(writes))); TransactionHelperResult out = TransactionHelper(request, data.get(), NULL); EXPECT_EQ(connects[index].result, out.rv); } } // In this test, we enable compression, but get a uncompressed SynReply from // the server. Verify that teardown is all clean. TEST_F(SpdyNetworkTransactionTest, DecompressFailureOnSynReply) { MockWrite writes[] = { MockWrite(true, reinterpret_cast(kGetSynCompressed), arraysize(kGetSynCompressed)), MockWrite(true, 0, 0) // EOF }; MockRead reads[] = { MockRead(true, reinterpret_cast(kGetSynReply), arraysize(kGetSynReply)), MockRead(true, reinterpret_cast(kGetBodyFrame), arraysize(kGetBodyFrame)), MockRead(true, 0, 0) // EOF }; // For this test, we turn on the normal compression. EnableCompression(true); HttpRequestInfo request; request.method = "GET"; request.url = GURL("http://www.google.com/"); request.load_flags = 0; scoped_refptr data( new DelayedSocketData(1, reads, arraysize(reads), writes, arraysize(writes))); TransactionHelperResult out = TransactionHelper(request, data.get(), NULL); EXPECT_EQ(ERR_SYN_REPLY_NOT_RECEIVED, out.rv); data->Reset(); EnableCompression(false); } // Test that the LoadLog contains good data for a simple GET request. TEST_F(SpdyNetworkTransactionTest, LoadLog) { MockWrite writes[] = { MockWrite(true, reinterpret_cast(kGetSyn), arraysize(kGetSyn)), }; MockRead reads[] = { MockRead(true, reinterpret_cast(kGetSynReply), arraysize(kGetSynReply)), MockRead(true, reinterpret_cast(kGetBodyFrame), arraysize(kGetBodyFrame)), MockRead(true, 0, 0) // EOF }; scoped_refptr log(new net::LoadLog(net::LoadLog::kUnbounded)); HttpRequestInfo request; request.method = "GET"; request.url = GURL("http://www.google.com/"); request.load_flags = 0; scoped_refptr data( new DelayedSocketData(1, reads, arraysize(reads), writes, arraysize(writes))); TransactionHelperResult out = TransactionHelper(request, data.get(), log); EXPECT_EQ(OK, out.rv); EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); EXPECT_EQ("hello!", out.response_data); // Check that the LoadLog was filled reasonably. // This test is intentionally non-specific about the exact ordering of // the log; instead we just check to make sure that certain events exist. EXPECT_LT(0u, log->entries().size()); int pos = 0; // We know the first event at position 0. EXPECT_TRUE(net::LogContainsBeginEvent( *log, 0, net::LoadLog::TYPE_SPDY_TRANSACTION_INIT_CONNECTION)); // For the rest of the events, allow additional events in the middle, // but expect these to be logged in order. pos = net::ExpectLogContainsSomewhere(log, 0, net::LoadLog::TYPE_SPDY_TRANSACTION_INIT_CONNECTION, net::LoadLog::PHASE_END); pos = net::ExpectLogContainsSomewhere(log, pos + 1, net::LoadLog::TYPE_SPDY_TRANSACTION_SEND_REQUEST, net::LoadLog::PHASE_BEGIN); pos = net::ExpectLogContainsSomewhere(log, pos + 1, net::LoadLog::TYPE_SPDY_TRANSACTION_SEND_REQUEST, net::LoadLog::PHASE_END); pos = net::ExpectLogContainsSomewhere(log, pos + 1, net::LoadLog::TYPE_SPDY_TRANSACTION_READ_HEADERS, net::LoadLog::PHASE_BEGIN); pos = net::ExpectLogContainsSomewhere(log, pos + 1, net::LoadLog::TYPE_SPDY_TRANSACTION_READ_HEADERS, net::LoadLog::PHASE_END); pos = net::ExpectLogContainsSomewhere(log, pos + 1, net::LoadLog::TYPE_SPDY_TRANSACTION_READ_BODY, net::LoadLog::PHASE_BEGIN); pos = net::ExpectLogContainsSomewhere(log, pos + 1, net::LoadLog::TYPE_SPDY_TRANSACTION_READ_BODY, net::LoadLog::PHASE_END); } // Since we buffer the IO from the stream to the renderer, this test verifies // that when we read out the maximum amount of data (e.g. we received 50 bytes // on the network, but issued a Read for only 5 of those bytes) that the data // flow still works correctly. TEST_F(SpdyNetworkTransactionTest, BufferFull) { MockWrite writes[] = { MockWrite(true, reinterpret_cast(kGetSyn), arraysize(kGetSyn)), }; static const unsigned char kCombinedDataFrames[] = { 0x00, 0x00, 0x00, 0x01, // header 0x00, 0x00, 0x00, 0x06, // length 'g', 'o', 'o', 'd', 'b', 'y', 0x00, 0x00, 0x00, 0x01, // header 0x00, 0x00, 0x00, 0x06, // length 'e', ' ', 'w', 'o', 'r', 'l', }; static const unsigned char kLastFrame[] = { 0x00, 0x00, 0x00, 0x01, // header 0x01, 0x00, 0x00, 0x01, // FIN, length 'd', }; MockRead reads[] = { MockRead(true, reinterpret_cast(kGetSynReply), arraysize(kGetSynReply)), MockRead(true, ERR_IO_PENDING), // Force a pause MockRead(true, reinterpret_cast(kCombinedDataFrames), arraysize(kCombinedDataFrames)), MockRead(true, ERR_IO_PENDING), // Force a pause MockRead(true, reinterpret_cast(kLastFrame), arraysize(kLastFrame)), MockRead(true, 0, 0) // EOF }; HttpRequestInfo request; request.method = "GET"; request.url = GURL("http://www.google.com/"); request.load_flags = 0; scoped_refptr data( new DelayedSocketData(1, reads, arraysize(reads), writes, arraysize(writes))); // For this test, we can't use the TransactionHelper, because we are // going to tightly control how the IOs fly. TransactionHelperResult out; // We disable SSL for this test. SpdySession::SetSSLMode(false); SessionDependencies session_deps; scoped_ptr trans( new SpdyNetworkTransaction(CreateSession(&session_deps))); session_deps.socket_factory.AddSocketDataProvider(data); TestCompletionCallback callback; int rv = trans->Start(&request, &callback, NULL); EXPECT_EQ(ERR_IO_PENDING, rv); out.rv = callback.WaitForResult(); EXPECT_EQ(out.rv, OK); const HttpResponseInfo* response = trans->GetResponseInfo(); EXPECT_TRUE(response->headers != NULL); EXPECT_TRUE(response->was_fetched_via_spdy); out.status_line = response->headers->GetStatusLine(); out.response_info = *response; // Make a copy so we can verify. // Read Data TestCompletionCallback read_callback; std::string content; do { // Read small chunks at a time. const int kSmallReadSize = 3; scoped_refptr buf = new net::IOBuffer(kSmallReadSize); rv = trans->Read(buf, kSmallReadSize, &read_callback); if (rv == net::ERR_IO_PENDING) { data->CompleteRead(); rv = read_callback.WaitForResult(); } if (rv > 0) { content.append(buf->data(), rv); } else if (rv < 0) { NOTREACHED(); } } while (rv > 0); out.response_data.swap(content); // Flush the MessageLoop while the SessionDependencies (in particular, the // MockClientSocketFactory) are still alive. MessageLoop::current()->RunAllPending(); // Verify that we consumed all test data. EXPECT_TRUE(data->at_read_eof()); EXPECT_TRUE(data->at_write_eof()); EXPECT_EQ(OK, out.rv); EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); EXPECT_EQ("goodbye world", out.response_data); } // Verify that basic buffering works; when multiple data frames arrive // at the same time, ensure that we don't notify a read completion for // each data frame individually. TEST_F(SpdyNetworkTransactionTest, Buffering) { MockWrite writes[] = { MockWrite(true, reinterpret_cast(kGetSyn), arraysize(kGetSyn)), }; // 4 data frames in a single read. static const unsigned char kCombinedDataFrames[] = { 0x00, 0x00, 0x00, 0x01, // header 0x00, 0x00, 0x00, 0x07, // length 'm', 'e', 's', 's', 'a', 'g', 'e', 0x00, 0x00, 0x00, 0x01, // header 0x00, 0x00, 0x00, 0x07, // length 'm', 'e', 's', 's', 'a', 'g', 'e', 0x00, 0x00, 0x00, 0x01, // header 0x00, 0x00, 0x00, 0x07, // length 'm', 'e', 's', 's', 'a', 'g', 'e', 0x00, 0x00, 0x00, 0x01, // header 0x01, 0x00, 0x00, 0x07, // FIN, length 'm', 'e', 's', 's', 'a', 'g', 'e', }; MockRead reads[] = { MockRead(true, reinterpret_cast(kGetSynReply), arraysize(kGetSynReply)), MockRead(true, ERR_IO_PENDING), // Force a pause MockRead(true, reinterpret_cast(kCombinedDataFrames), arraysize(kCombinedDataFrames)), MockRead(true, 0, 0) // EOF }; HttpRequestInfo request; request.method = "GET"; request.url = GURL("http://www.google.com/"); request.load_flags = 0; scoped_refptr data( new DelayedSocketData(1, reads, arraysize(reads), writes, arraysize(writes))); // For this test, we can't use the TransactionHelper, because we are // going to tightly control how the IOs fly. TransactionHelperResult out; // We disable SSL for this test. SpdySession::SetSSLMode(false); SessionDependencies session_deps; scoped_ptr trans( new SpdyNetworkTransaction(CreateSession(&session_deps))); session_deps.socket_factory.AddSocketDataProvider(data); TestCompletionCallback callback; int rv = trans->Start(&request, &callback, NULL); EXPECT_EQ(ERR_IO_PENDING, rv); out.rv = callback.WaitForResult(); EXPECT_EQ(out.rv, OK); const HttpResponseInfo* response = trans->GetResponseInfo(); EXPECT_TRUE(response->headers != NULL); EXPECT_TRUE(response->was_fetched_via_spdy); out.status_line = response->headers->GetStatusLine(); out.response_info = *response; // Make a copy so we can verify. // Read Data TestCompletionCallback read_callback; std::string content; int reads_completed = 0; do { // Read small chunks at a time. const int kSmallReadSize = 14; scoped_refptr buf = new net::IOBuffer(kSmallReadSize); rv = trans->Read(buf, kSmallReadSize, &read_callback); if (rv == net::ERR_IO_PENDING) { data->CompleteRead(); rv = read_callback.WaitForResult(); } if (rv > 0) { EXPECT_EQ(kSmallReadSize, rv); content.append(buf->data(), rv); } else if (rv < 0) { FAIL() << "Unexpected read error: " << rv; } reads_completed++; } while (rv > 0); EXPECT_EQ(3, reads_completed); // Reads are: 14 bytes, 14 bytes, 0 bytes. out.response_data.swap(content); // Flush the MessageLoop while the SessionDependencies (in particular, the // MockClientSocketFactory) are still alive. MessageLoop::current()->RunAllPending(); // Verify that we consumed all test data. EXPECT_TRUE(data->at_read_eof()); EXPECT_TRUE(data->at_write_eof()); EXPECT_EQ(OK, out.rv); EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); EXPECT_EQ("messagemessagemessagemessage", out.response_data); } // Verify the case where we buffer data but read it after it has been buffered. TEST_F(SpdyNetworkTransactionTest, BufferedAll) { MockWrite writes[] = { MockWrite(true, reinterpret_cast(kGetSyn), arraysize(kGetSyn)), }; // The Syn Reply and all data frames in a single read. static const unsigned char kCombinedFrames[] = { 0x80, 0x01, 0x00, 0x02, // header 0x00, 0x00, 0x00, 0x45, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x04, // 4 headers 0x00, 0x05, 'h', 'e', 'l', 'l', 'o', 0x00, 0x03, 'b', 'y', 'e', 0x00, 0x06, 's', 't', 'a', 't', 'u', 's', 0x00, 0x03, '2', '0', '0', 0x00, 0x03, 'u', 'r', 'l', 0x00, 0x0a, '/', 'i', 'n', 'd', 'e', 'x', '.', 'p', 'h', 'p', 0x00, 0x07, 'v', 'e', 'r', 's', 'i', 'o', 'n', 0x00, 0x08, 'H', 'T', 'T', 'P', '/', '1', '.', '1', 0x00, 0x00, 0x00, 0x01, // header 0x00, 0x00, 0x00, 0x07, // length 'm', 'e', 's', 's', 'a', 'g', 'e', 0x00, 0x00, 0x00, 0x01, // header 0x00, 0x00, 0x00, 0x07, // length 'm', 'e', 's', 's', 'a', 'g', 'e', 0x00, 0x00, 0x00, 0x01, // header 0x00, 0x00, 0x00, 0x07, // length 'm', 'e', 's', 's', 'a', 'g', 'e', 0x00, 0x00, 0x00, 0x01, // header 0x01, 0x00, 0x00, 0x07, // FIN, length 'm', 'e', 's', 's', 'a', 'g', 'e', }; MockRead reads[] = { MockRead(true, reinterpret_cast(kCombinedFrames), arraysize(kCombinedFrames)), MockRead(true, 0, 0) // EOF }; HttpRequestInfo request; request.method = "GET"; request.url = GURL("http://www.google.com/"); request.load_flags = 0; scoped_refptr data( new DelayedSocketData(1, reads, arraysize(reads), writes, arraysize(writes))); // For this test, we can't use the TransactionHelper, because we are // going to tightly control how the IOs fly. TransactionHelperResult out; // We disable SSL for this test. SpdySession::SetSSLMode(false); SessionDependencies session_deps; scoped_ptr trans( new SpdyNetworkTransaction(CreateSession(&session_deps))); session_deps.socket_factory.AddSocketDataProvider(data); TestCompletionCallback callback; int rv = trans->Start(&request, &callback, NULL); EXPECT_EQ(ERR_IO_PENDING, rv); out.rv = callback.WaitForResult(); EXPECT_EQ(out.rv, OK); const HttpResponseInfo* response = trans->GetResponseInfo(); EXPECT_TRUE(response->headers != NULL); EXPECT_TRUE(response->was_fetched_via_spdy); out.status_line = response->headers->GetStatusLine(); out.response_info = *response; // Make a copy so we can verify. // Read Data TestCompletionCallback read_callback; std::string content; int reads_completed = 0; do { // Read small chunks at a time. const int kSmallReadSize = 14; scoped_refptr buf = new net::IOBuffer(kSmallReadSize); rv = trans->Read(buf, kSmallReadSize, &read_callback); if (rv > 0) { EXPECT_EQ(kSmallReadSize, rv); content.append(buf->data(), rv); } else if (rv < 0) { FAIL() << "Unexpected read error: " << rv; } reads_completed++; } while (rv > 0); EXPECT_EQ(3, reads_completed); out.response_data.swap(content); // Flush the MessageLoop while the SessionDependencies (in particular, the // MockClientSocketFactory) are still alive. MessageLoop::current()->RunAllPending(); // Verify that we consumed all test data. EXPECT_TRUE(data->at_read_eof()); EXPECT_TRUE(data->at_write_eof()); EXPECT_EQ(OK, out.rv); EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); EXPECT_EQ("messagemessagemessagemessage", out.response_data); } // Verify the case where we buffer data and close the connection. TEST_F(SpdyNetworkTransactionTest, BufferedClosed) { MockWrite writes[] = { MockWrite(true, reinterpret_cast(kGetSyn), arraysize(kGetSyn)), }; // All data frames in a single read. static const unsigned char kCombinedFrames[] = { 0x00, 0x00, 0x00, 0x01, // header 0x00, 0x00, 0x00, 0x07, // length 'm', 'e', 's', 's', 'a', 'g', 'e', 0x00, 0x00, 0x00, 0x01, // header 0x00, 0x00, 0x00, 0x07, // length 'm', 'e', 's', 's', 'a', 'g', 'e', 0x00, 0x00, 0x00, 0x01, // header 0x00, 0x00, 0x00, 0x07, // length 'm', 'e', 's', 's', 'a', 'g', 'e', 0x00, 0x00, 0x00, 0x01, // header 0x00, 0x00, 0x00, 0x07, // length 'm', 'e', 's', 's', 'a', 'g', 'e', // NOTE: We didn't FIN the stream. }; MockRead reads[] = { MockRead(true, reinterpret_cast(kGetSynReply), arraysize(kGetSynReply)), MockRead(true, ERR_IO_PENDING), // Force a wait MockRead(true, reinterpret_cast(kCombinedFrames), arraysize(kCombinedFrames)), MockRead(true, 0, 0) // EOF }; HttpRequestInfo request; request.method = "GET"; request.url = GURL("http://www.google.com/"); request.load_flags = 0; scoped_refptr data( new DelayedSocketData(1, reads, arraysize(reads), writes, arraysize(writes))); // For this test, we can't use the TransactionHelper, because we are // going to tightly control how the IOs fly. TransactionHelperResult out; // We disable SSL for this test. SpdySession::SetSSLMode(false); SessionDependencies session_deps; scoped_ptr trans( new SpdyNetworkTransaction(CreateSession(&session_deps))); session_deps.socket_factory.AddSocketDataProvider(data); TestCompletionCallback callback; int rv = trans->Start(&request, &callback, NULL); EXPECT_EQ(ERR_IO_PENDING, rv); out.rv = callback.WaitForResult(); EXPECT_EQ(out.rv, OK); const HttpResponseInfo* response = trans->GetResponseInfo(); EXPECT_TRUE(response->headers != NULL); EXPECT_TRUE(response->was_fetched_via_spdy); out.status_line = response->headers->GetStatusLine(); out.response_info = *response; // Make a copy so we can verify. // Read Data TestCompletionCallback read_callback; std::string content; int reads_completed = 0; do { // Read small chunks at a time. const int kSmallReadSize = 14; scoped_refptr buf = new net::IOBuffer(kSmallReadSize); rv = trans->Read(buf, kSmallReadSize, &read_callback); if (rv == net::ERR_IO_PENDING) { data->CompleteRead(); rv = read_callback.WaitForResult(); } if (rv > 0) { content.append(buf->data(), rv); } else if (rv < 0) { // This test intentionally closes the connection, and will get an error. EXPECT_EQ(ERR_CONNECTION_CLOSED, rv); break; } reads_completed++; } while (rv > 0); EXPECT_EQ(0, reads_completed); out.response_data.swap(content); // Flush the MessageLoop while the SessionDependencies (in particular, the // MockClientSocketFactory) are still alive. MessageLoop::current()->RunAllPending(); // Verify that we consumed all test data. EXPECT_TRUE(data->at_read_eof()); EXPECT_TRUE(data->at_write_eof()); } // Verify the case where we buffer data and cancel the transaction. TEST_F(SpdyNetworkTransactionTest, BufferedCancelled) { MockWrite writes[] = { MockWrite(true, reinterpret_cast(kGetSyn), arraysize(kGetSyn)), }; static const unsigned char kDataFrame[] = { 0x00, 0x00, 0x00, 0x01, // header 0x00, 0x00, 0x00, 0x07, // length 'm', 'e', 's', 's', 'a', 'g', 'e', // NOTE: We didn't FIN the stream. }; MockRead reads[] = { MockRead(true, reinterpret_cast(kGetSynReply), arraysize(kGetSynReply)), MockRead(true, ERR_IO_PENDING), // Force a wait MockRead(true, reinterpret_cast(kDataFrame), arraysize(kDataFrame)), MockRead(true, 0, 0) // EOF }; HttpRequestInfo request; request.method = "GET"; request.url = GURL("http://www.google.com/"); request.load_flags = 0; scoped_refptr data( new DelayedSocketData(1, reads, arraysize(reads), writes, arraysize(writes))); // For this test, we can't use the TransactionHelper, because we are // going to tightly control how the IOs fly. TransactionHelperResult out; // We disable SSL for this test. SpdySession::SetSSLMode(false); SessionDependencies session_deps; scoped_ptr trans( new SpdyNetworkTransaction(CreateSession(&session_deps))); session_deps.socket_factory.AddSocketDataProvider(data); TestCompletionCallback callback; int rv = trans->Start(&request, &callback, NULL); EXPECT_EQ(ERR_IO_PENDING, rv); out.rv = callback.WaitForResult(); EXPECT_EQ(out.rv, OK); const HttpResponseInfo* response = trans->GetResponseInfo(); EXPECT_TRUE(response->headers != NULL); EXPECT_TRUE(response->was_fetched_via_spdy); out.status_line = response->headers->GetStatusLine(); out.response_info = *response; // Make a copy so we can verify. // Read Data TestCompletionCallback read_callback; do { const int kReadSize = 256; scoped_refptr buf = new net::IOBuffer(kReadSize); rv = trans->Read(buf, kReadSize, &read_callback); if (rv == net::ERR_IO_PENDING) { // Complete the read now, which causes buffering to start. data->CompleteRead(); // Destroy the transaction, causing the stream to get cancelled // and orphaning the buffered IO task. trans.reset(); break; } // We shouldn't get here in this test. FAIL() << "Unexpected read: " << rv; } while (rv > 0); // Flush the MessageLoop; this will cause the buffered IO task // to run for the final time. MessageLoop::current()->RunAllPending(); } } // namespace net