| // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "net/spdy/spdy_proxy_client_socket.h" |
| |
| #include <utility> |
| |
| #include "base/bind.h" |
| #include "base/bind_helpers.h" |
| #include "base/macros.h" |
| #include "base/run_loop.h" |
| #include "base/strings/string_piece.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "net/base/address_list.h" |
| #include "net/base/load_timing_info.h" |
| #include "net/base/proxy_server.h" |
| #include "net/base/test_completion_callback.h" |
| #include "net/base/winsock_init.h" |
| #include "net/dns/mock_host_resolver.h" |
| #include "net/http/http_proxy_connect_job.h" |
| #include "net/http/http_response_headers.h" |
| #include "net/http/http_response_info.h" |
| #include "net/log/net_log_event_type.h" |
| #include "net/log/net_log_source.h" |
| #include "net/log/test_net_log.h" |
| #include "net/log/test_net_log_util.h" |
| #include "net/socket/client_socket_factory.h" |
| #include "net/socket/connect_job_test_util.h" |
| #include "net/socket/socket_tag.h" |
| #include "net/socket/socket_test_util.h" |
| #include "net/socket/socks_connect_job.h" |
| #include "net/socket/ssl_client_socket.h" |
| #include "net/socket/ssl_connect_job.h" |
| #include "net/socket/stream_socket.h" |
| #include "net/socket/tcp_client_socket.h" |
| #include "net/socket/transport_connect_job.h" |
| #include "net/spdy/buffered_spdy_framer.h" |
| #include "net/spdy/spdy_http_utils.h" |
| #include "net/spdy/spdy_session_pool.h" |
| #include "net/spdy/spdy_test_util_common.h" |
| #include "net/test/cert_test_util.h" |
| #include "net/test/gtest_util.h" |
| #include "net/test/test_data_directory.h" |
| #include "net/test/test_with_task_environment.h" |
| #include "net/third_party/quiche/src/spdy/core/spdy_protocol.h" |
| #include "net/traffic_annotation/network_traffic_annotation_test_helper.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "testing/platform_test.h" |
| |
| using net::test::IsError; |
| using net::test::IsOk; |
| |
| //----------------------------------------------------------------------------- |
| |
| namespace net { |
| |
| namespace { |
| |
| static const char kRequestUrl[] = "https://www.google.com/"; |
| static const char kOriginHost[] = "www.google.com"; |
| static const int kOriginPort = 443; |
| static const char kOriginHostPort[] = "www.google.com:443"; |
| static const char kProxyUrl[] = "https://myproxy:6121/"; |
| static const char kProxyHost[] = "myproxy"; |
| static const int kProxyPort = 6121; |
| static const char kUserAgent[] = "Mozilla/1.0"; |
| |
| static const int kStreamId = 1; |
| |
| static const char kMsg1[] = "\0hello!\xff"; |
| static const int kLen1 = 8; |
| static const char kMsg2[] = "\0a2345678\0"; |
| static const int kLen2 = 10; |
| static const char kMsg3[] = "bye!"; |
| static const int kLen3 = 4; |
| static const char kMsg33[] = "bye!bye!"; |
| static const int kLen33 = kLen3 + kLen3; |
| static const char kMsg333[] = "bye!bye!bye!"; |
| static const int kLen333 = kLen3 + kLen3 + kLen3; |
| |
| static const char kRedirectUrl[] = "https://example.com/"; |
| |
| // Creates a SpdySession with a StreamSocket, instead of a ClientSocketHandle. |
| base::WeakPtr<SpdySession> CreateSpdyProxySession( |
| HttpNetworkSession* http_session, |
| SpdySessionDependencies* session_deps, |
| const SpdySessionKey& key, |
| const CommonConnectJobParams* common_connect_job_params) { |
| EXPECT_FALSE(http_session->spdy_session_pool()->FindAvailableSession( |
| key, true /* enable_ip_based_pooling */, false /* is_websocket */, |
| NetLogWithSource())); |
| |
| auto transport_params = base::MakeRefCounted<TransportSocketParams>( |
| key.host_port_pair(), NetworkIsolationKey(), |
| false /* disable_secure_dns */, OnHostResolutionCallback()); |
| |
| SSLConfig ssl_config; |
| auto ssl_params = base::MakeRefCounted<SSLSocketParams>( |
| transport_params, nullptr, nullptr, key.host_port_pair(), ssl_config, |
| key.privacy_mode(), key.network_isolation_key()); |
| TestConnectJobDelegate connect_job_delegate; |
| SSLConnectJob connect_job(MEDIUM, SocketTag(), common_connect_job_params, |
| ssl_params, &connect_job_delegate, |
| nullptr /* net_log */); |
| connect_job_delegate.StartJobExpectingResult(&connect_job, OK, |
| false /* expect_sync_result */); |
| |
| base::WeakPtr<SpdySession> spdy_session = |
| http_session->spdy_session_pool()->CreateAvailableSessionFromSocket( |
| key, false /* is_trusted_proxy */, |
| connect_job_delegate.ReleaseSocket(), LoadTimingInfo::ConnectTiming(), |
| NetLogWithSource()); |
| // Failure is reported asynchronously. |
| EXPECT_TRUE(spdy_session); |
| EXPECT_TRUE(HasSpdySession(http_session->spdy_session_pool(), key)); |
| return spdy_session; |
| } |
| |
| } // namespace |
| |
| class SpdyProxyClientSocketTest : public PlatformTest, |
| public WithTaskEnvironment, |
| public ::testing::WithParamInterface<bool> { |
| public: |
| SpdyProxyClientSocketTest(); |
| ~SpdyProxyClientSocketTest() override; |
| |
| void TearDown() override; |
| |
| protected: |
| void Initialize(base::span<const MockRead> reads, |
| base::span<const MockWrite> writes); |
| void PopulateConnectRequestIR(spdy::SpdyHeaderBlock* syn_ir); |
| void PopulateConnectReplyIR(spdy::SpdyHeaderBlock* block, const char* status); |
| spdy::SpdySerializedFrame ConstructConnectRequestFrame( |
| RequestPriority priority = LOWEST); |
| spdy::SpdySerializedFrame ConstructConnectAuthRequestFrame(); |
| spdy::SpdySerializedFrame ConstructConnectReplyFrame(); |
| spdy::SpdySerializedFrame ConstructConnectAuthReplyFrame(); |
| spdy::SpdySerializedFrame ConstructConnectRedirectReplyFrame(); |
| spdy::SpdySerializedFrame ConstructConnectErrorReplyFrame(); |
| spdy::SpdySerializedFrame ConstructBodyFrame(const char* data, int length); |
| scoped_refptr<IOBufferWithSize> CreateBuffer(const char* data, int size); |
| void AssertConnectSucceeds(); |
| void AssertConnectFails(int result); |
| void AssertConnectionEstablished(); |
| void AssertSyncReadEquals(const char* data, int len); |
| void AssertSyncReadEOF(); |
| void AssertAsyncReadEquals(const char* data, int len); |
| void AssertReadStarts(const char* data, int len); |
| void AssertReadReturns(const char* data, int len); |
| void AssertAsyncWriteSucceeds(const char* data, int len); |
| void AssertWriteReturns(const char* data, int len, int rv); |
| void AssertWriteLength(int len); |
| |
| void AddAuthToCache() { |
| const base::string16 kFoo(base::ASCIIToUTF16("foo")); |
| const base::string16 kBar(base::ASCIIToUTF16("bar")); |
| session_->http_auth_cache()->Add( |
| GURL(kProxyUrl), HttpAuth::AUTH_PROXY, "MyRealm1", |
| HttpAuth::AUTH_SCHEME_BASIC, NetworkIsolationKey(), |
| "Basic realm=MyRealm1", AuthCredentials(kFoo, kBar), "/"); |
| } |
| |
| void ResumeAndRun() { |
| // Run until the pause, if the provider isn't paused yet. |
| data_->RunUntilPaused(); |
| data_->Resume(); |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| void CloseSpdySession(Error error, const std::string& description) { |
| spdy_session_->CloseSessionOnError(error, description); |
| } |
| |
| // Whether to use net::Socket::ReadIfReady() instead of net::Socket::Read(). |
| bool use_read_if_ready() const { return GetParam(); } |
| |
| RecordingBoundTestNetLog net_log_; |
| SpdyTestUtil spdy_util_; |
| std::unique_ptr<SpdyProxyClientSocket> sock_; |
| TestCompletionCallback read_callback_; |
| TestCompletionCallback write_callback_; |
| std::unique_ptr<SequencedSocketData> data_; |
| |
| private: |
| scoped_refptr<IOBuffer> read_buf_; |
| SpdySessionDependencies session_deps_; |
| std::unique_ptr<HttpNetworkSession> session_; |
| MockConnect connect_data_; |
| base::WeakPtr<SpdySession> spdy_session_; |
| std::string user_agent_; |
| GURL url_; |
| HostPortPair proxy_host_port_; |
| HostPortPair endpoint_host_port_pair_; |
| ProxyServer proxy_; |
| SpdySessionKey endpoint_spdy_session_key_; |
| std::unique_ptr<CommonConnectJobParams> common_connect_job_params_; |
| SSLSocketDataProvider ssl_; |
| |
| DISALLOW_COPY_AND_ASSIGN(SpdyProxyClientSocketTest); |
| }; |
| |
| SpdyProxyClientSocketTest::SpdyProxyClientSocketTest() |
| : read_buf_(nullptr), |
| connect_data_(SYNCHRONOUS, OK), |
| user_agent_(kUserAgent), |
| url_(kRequestUrl), |
| proxy_host_port_(kProxyHost, kProxyPort), |
| endpoint_host_port_pair_(kOriginHost, kOriginPort), |
| proxy_(ProxyServer::SCHEME_HTTPS, proxy_host_port_), |
| endpoint_spdy_session_key_(endpoint_host_port_pair_, |
| proxy_, |
| PRIVACY_MODE_DISABLED, |
| SpdySessionKey::IsProxySession::kFalse, |
| SocketTag(), |
| NetworkIsolationKey(), |
| false /* disable_secure_dns */), |
| ssl_(SYNCHRONOUS, OK) { |
| session_deps_.net_log = net_log_.bound().net_log(); |
| } |
| |
| SpdyProxyClientSocketTest::~SpdyProxyClientSocketTest() { |
| if (data_) { |
| EXPECT_TRUE(data_->AllWriteDataConsumed()); |
| EXPECT_TRUE(data_->AllReadDataConsumed()); |
| } |
| } |
| |
| void SpdyProxyClientSocketTest::TearDown() { |
| if (session_) |
| session_->spdy_session_pool()->CloseAllSessions(); |
| |
| // Empty the current queue. |
| base::RunLoop().RunUntilIdle(); |
| PlatformTest::TearDown(); |
| } |
| |
| void SpdyProxyClientSocketTest::Initialize(base::span<const MockRead> reads, |
| base::span<const MockWrite> writes) { |
| data_ = std::make_unique<SequencedSocketData>(reads, writes); |
| data_->set_connect_data(connect_data_); |
| session_deps_.socket_factory->AddSocketDataProvider(data_.get()); |
| |
| ssl_.ssl_info.cert = |
| ImportCertFromFile(GetTestCertsDirectory(), "spdy_pooling.pem"); |
| ASSERT_TRUE(ssl_.ssl_info.cert); |
| session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl_); |
| |
| session_ = SpdySessionDependencies::SpdyCreateSession(&session_deps_); |
| common_connect_job_params_ = std::make_unique<CommonConnectJobParams>( |
| session_->CreateCommonConnectJobParams()); |
| |
| // Creates the SPDY session and stream. |
| spdy_session_ = CreateSpdyProxySession(session_.get(), &session_deps_, |
| endpoint_spdy_session_key_, |
| common_connect_job_params_.get()); |
| |
| base::WeakPtr<SpdyStream> spdy_stream( |
| CreateStreamSynchronously( |
| SPDY_BIDIRECTIONAL_STREAM, spdy_session_, url_, LOWEST, |
| net_log_.bound())); |
| ASSERT_TRUE(spdy_stream.get() != nullptr); |
| |
| // Create the SpdyProxyClientSocket. |
| sock_ = std::make_unique<SpdyProxyClientSocket>( |
| spdy_stream, ProxyServer(ProxyServer::SCHEME_HTTPS, proxy_host_port_), |
| user_agent_, endpoint_host_port_pair_, net_log_.bound(), |
| new HttpAuthController( |
| HttpAuth::AUTH_PROXY, GURL("https://" + proxy_host_port_.ToString()), |
| NetworkIsolationKey(), session_->http_auth_cache(), |
| session_->http_auth_handler_factory(), session_->host_resolver()), |
| // Testing with the proxy delegate is in HttpProxyConnectJobTest. |
| nullptr); |
| } |
| |
| scoped_refptr<IOBufferWithSize> SpdyProxyClientSocketTest::CreateBuffer( |
| const char* data, int size) { |
| scoped_refptr<IOBufferWithSize> buf = |
| base::MakeRefCounted<IOBufferWithSize>(size); |
| memcpy(buf->data(), data, size); |
| return buf; |
| } |
| |
| void SpdyProxyClientSocketTest::AssertConnectSucceeds() { |
| ASSERT_THAT(sock_->Connect(read_callback_.callback()), |
| IsError(ERR_IO_PENDING)); |
| ASSERT_THAT(read_callback_.WaitForResult(), IsOk()); |
| } |
| |
| void SpdyProxyClientSocketTest::AssertConnectFails(int result) { |
| ASSERT_THAT(sock_->Connect(read_callback_.callback()), |
| IsError(ERR_IO_PENDING)); |
| ASSERT_EQ(result, read_callback_.WaitForResult()); |
| } |
| |
| void SpdyProxyClientSocketTest::AssertConnectionEstablished() { |
| const HttpResponseInfo* response = sock_->GetConnectResponseInfo(); |
| ASSERT_TRUE(response != nullptr); |
| ASSERT_EQ(200, response->headers->response_code()); |
| } |
| |
| void SpdyProxyClientSocketTest::AssertSyncReadEquals(const char* data, |
| int len) { |
| scoped_refptr<IOBuffer> buf = base::MakeRefCounted<IOBuffer>(len); |
| if (use_read_if_ready()) { |
| ASSERT_EQ(len, |
| sock_->ReadIfReady(buf.get(), len, CompletionOnceCallback())); |
| } else { |
| ASSERT_EQ(len, sock_->Read(buf.get(), len, CompletionOnceCallback())); |
| } |
| ASSERT_EQ(std::string(data, len), std::string(buf->data(), len)); |
| ASSERT_TRUE(sock_->IsConnected()); |
| } |
| |
| void SpdyProxyClientSocketTest::AssertSyncReadEOF() { |
| if (use_read_if_ready()) { |
| ASSERT_EQ(0, sock_->ReadIfReady(nullptr, 1, read_callback_.callback())); |
| } else { |
| ASSERT_EQ(0, sock_->Read(nullptr, 1, read_callback_.callback())); |
| } |
| } |
| |
| void SpdyProxyClientSocketTest::AssertAsyncReadEquals(const char* data, |
| int len) { |
| // Issue the read, which will be completed asynchronously |
| scoped_refptr<IOBuffer> buf = base::MakeRefCounted<IOBuffer>(len); |
| if (use_read_if_ready()) { |
| ASSERT_EQ(ERR_IO_PENDING, |
| sock_->ReadIfReady(buf.get(), len, read_callback_.callback())); |
| } else { |
| ASSERT_EQ(ERR_IO_PENDING, |
| sock_->Read(buf.get(), len, read_callback_.callback())); |
| } |
| EXPECT_TRUE(sock_->IsConnected()); |
| |
| ResumeAndRun(); |
| |
| if (use_read_if_ready()) { |
| EXPECT_EQ(OK, read_callback_.WaitForResult()); |
| ASSERT_EQ(len, |
| sock_->ReadIfReady(buf.get(), len, read_callback_.callback())); |
| } else { |
| EXPECT_EQ(len, read_callback_.WaitForResult()); |
| } |
| EXPECT_TRUE(sock_->IsConnected()); |
| ASSERT_EQ(std::string(data, len), std::string(buf->data(), len)); |
| } |
| |
| void SpdyProxyClientSocketTest::AssertReadStarts(const char* data, int len) { |
| // Issue the read, which will be completed asynchronously. |
| read_buf_ = base::MakeRefCounted<IOBuffer>(len); |
| if (use_read_if_ready()) { |
| ASSERT_EQ(ERR_IO_PENDING, sock_->ReadIfReady(read_buf_.get(), len, |
| read_callback_.callback())); |
| } else { |
| ASSERT_EQ(ERR_IO_PENDING, |
| sock_->Read(read_buf_.get(), len, read_callback_.callback())); |
| } |
| EXPECT_TRUE(sock_->IsConnected()); |
| } |
| |
| void SpdyProxyClientSocketTest::AssertReadReturns(const char* data, int len) { |
| EXPECT_TRUE(sock_->IsConnected()); |
| |
| // Now the read will return |
| if (use_read_if_ready()) { |
| EXPECT_EQ(OK, read_callback_.WaitForResult()); |
| ASSERT_EQ(len, sock_->ReadIfReady(read_buf_.get(), len, |
| read_callback_.callback())); |
| } else { |
| EXPECT_EQ(len, read_callback_.WaitForResult()); |
| } |
| ASSERT_EQ(std::string(data, len), std::string(read_buf_->data(), len)); |
| } |
| |
| void SpdyProxyClientSocketTest::AssertAsyncWriteSucceeds(const char* data, |
| int len) { |
| AssertWriteReturns(data, len, ERR_IO_PENDING); |
| AssertWriteLength(len); |
| } |
| |
| void SpdyProxyClientSocketTest::AssertWriteReturns(const char* data, |
| int len, |
| int rv) { |
| scoped_refptr<IOBufferWithSize> buf(CreateBuffer(data, len)); |
| EXPECT_EQ(rv, sock_->Write(buf.get(), buf->size(), write_callback_.callback(), |
| TRAFFIC_ANNOTATION_FOR_TESTS)); |
| } |
| |
| void SpdyProxyClientSocketTest::AssertWriteLength(int len) { |
| EXPECT_EQ(len, write_callback_.WaitForResult()); |
| } |
| |
| void SpdyProxyClientSocketTest::PopulateConnectRequestIR( |
| spdy::SpdyHeaderBlock* block) { |
| (*block)[spdy::kHttp2MethodHeader] = "CONNECT"; |
| (*block)[spdy::kHttp2AuthorityHeader] = kOriginHostPort; |
| (*block)["user-agent"] = kUserAgent; |
| } |
| |
| void SpdyProxyClientSocketTest::PopulateConnectReplyIR( |
| spdy::SpdyHeaderBlock* block, |
| const char* status) { |
| (*block)[spdy::kHttp2StatusHeader] = status; |
| } |
| |
| // Constructs a standard SPDY HEADERS frame for a CONNECT request. |
| spdy::SpdySerializedFrame |
| SpdyProxyClientSocketTest::ConstructConnectRequestFrame( |
| RequestPriority priority) { |
| spdy::SpdyHeaderBlock block; |
| PopulateConnectRequestIR(&block); |
| return spdy_util_.ConstructSpdyHeaders(kStreamId, std::move(block), priority, |
| false); |
| } |
| |
| // Constructs a SPDY HEADERS frame for a CONNECT request which includes |
| // Proxy-Authorization headers. |
| spdy::SpdySerializedFrame |
| SpdyProxyClientSocketTest::ConstructConnectAuthRequestFrame() { |
| spdy::SpdyHeaderBlock block; |
| PopulateConnectRequestIR(&block); |
| block["proxy-authorization"] = "Basic Zm9vOmJhcg=="; |
| return spdy_util_.ConstructSpdyHeaders(kStreamId, std::move(block), LOWEST, |
| false); |
| } |
| |
| // Constructs a standard SPDY HEADERS frame to match the SPDY CONNECT. |
| spdy::SpdySerializedFrame |
| SpdyProxyClientSocketTest::ConstructConnectReplyFrame() { |
| spdy::SpdyHeaderBlock block; |
| PopulateConnectReplyIR(&block, "200"); |
| return spdy_util_.ConstructSpdyReply(kStreamId, std::move(block)); |
| } |
| |
| // Constructs a standard SPDY HEADERS frame to match the SPDY CONNECT, |
| // including Proxy-Authenticate headers. |
| spdy::SpdySerializedFrame |
| SpdyProxyClientSocketTest::ConstructConnectAuthReplyFrame() { |
| spdy::SpdyHeaderBlock block; |
| PopulateConnectReplyIR(&block, "407"); |
| block["proxy-authenticate"] = "Basic realm=\"MyRealm1\""; |
| return spdy_util_.ConstructSpdyReply(kStreamId, std::move(block)); |
| } |
| |
| // Constructs a SPDY HEADERS frame with an HTTP 302 redirect. |
| spdy::SpdySerializedFrame |
| SpdyProxyClientSocketTest::ConstructConnectRedirectReplyFrame() { |
| spdy::SpdyHeaderBlock block; |
| PopulateConnectReplyIR(&block, "302"); |
| block["location"] = kRedirectUrl; |
| block["set-cookie"] = "foo=bar"; |
| return spdy_util_.ConstructSpdyReply(kStreamId, std::move(block)); |
| } |
| |
| // Constructs a SPDY HEADERS frame with an HTTP 500 error. |
| spdy::SpdySerializedFrame |
| SpdyProxyClientSocketTest::ConstructConnectErrorReplyFrame() { |
| spdy::SpdyHeaderBlock block; |
| PopulateConnectReplyIR(&block, "500"); |
| return spdy_util_.ConstructSpdyReply(kStreamId, std::move(block)); |
| } |
| |
| spdy::SpdySerializedFrame SpdyProxyClientSocketTest::ConstructBodyFrame( |
| const char* data, |
| int length) { |
| return spdy_util_.ConstructSpdyDataFrame( |
| kStreamId, base::StringPiece(data, length), false /* fin */); |
| } |
| |
| // ----------- Connect |
| |
| INSTANTIATE_TEST_SUITE_P(All, |
| SpdyProxyClientSocketTest, |
| ::testing::Bool()); |
| |
| TEST_P(SpdyProxyClientSocketTest, ConnectSendsCorrectRequest) { |
| spdy::SpdySerializedFrame conn(ConstructConnectRequestFrame()); |
| MockWrite writes[] = { |
| CreateMockWrite(conn, 0, SYNCHRONOUS), |
| }; |
| |
| spdy::SpdySerializedFrame resp(ConstructConnectReplyFrame()); |
| MockRead reads[] = { |
| CreateMockRead(resp, 1, ASYNC), MockRead(SYNCHRONOUS, ERR_IO_PENDING, 2), |
| }; |
| |
| Initialize(reads, writes); |
| |
| ASSERT_FALSE(sock_->IsConnected()); |
| |
| AssertConnectSucceeds(); |
| |
| AssertConnectionEstablished(); |
| } |
| |
| TEST_P(SpdyProxyClientSocketTest, ConnectWithAuthRequested) { |
| spdy::SpdySerializedFrame conn(ConstructConnectRequestFrame()); |
| MockWrite writes[] = { |
| CreateMockWrite(conn, 0, SYNCHRONOUS), |
| }; |
| |
| spdy::SpdySerializedFrame resp(ConstructConnectAuthReplyFrame()); |
| MockRead reads[] = { |
| CreateMockRead(resp, 1, ASYNC), MockRead(SYNCHRONOUS, ERR_IO_PENDING, 2), |
| }; |
| |
| Initialize(reads, writes); |
| |
| AssertConnectFails(ERR_PROXY_AUTH_REQUESTED); |
| |
| const HttpResponseInfo* response = sock_->GetConnectResponseInfo(); |
| ASSERT_TRUE(response != nullptr); |
| ASSERT_EQ(407, response->headers->response_code()); |
| } |
| |
| TEST_P(SpdyProxyClientSocketTest, ConnectWithAuthCredentials) { |
| spdy::SpdySerializedFrame conn(ConstructConnectAuthRequestFrame()); |
| MockWrite writes[] = { |
| CreateMockWrite(conn, 0, SYNCHRONOUS), |
| }; |
| |
| spdy::SpdySerializedFrame resp(ConstructConnectReplyFrame()); |
| MockRead reads[] = { |
| CreateMockRead(resp, 1, ASYNC), MockRead(SYNCHRONOUS, ERR_IO_PENDING, 2), |
| }; |
| |
| Initialize(reads, writes); |
| AddAuthToCache(); |
| |
| AssertConnectSucceeds(); |
| |
| AssertConnectionEstablished(); |
| } |
| |
| TEST_P(SpdyProxyClientSocketTest, ConnectRedirects) { |
| spdy::SpdySerializedFrame conn(ConstructConnectRequestFrame()); |
| spdy::SpdySerializedFrame rst( |
| spdy_util_.ConstructSpdyRstStream(1, spdy::ERROR_CODE_CANCEL)); |
| MockWrite writes[] = { |
| CreateMockWrite(conn, 0, SYNCHRONOUS), |
| }; |
| |
| spdy::SpdySerializedFrame resp(ConstructConnectRedirectReplyFrame()); |
| MockRead reads[] = { |
| CreateMockRead(resp, 1, ASYNC), MockRead(SYNCHRONOUS, ERR_IO_PENDING, 2), |
| }; |
| |
| Initialize(reads, writes); |
| |
| AssertConnectFails(ERR_TUNNEL_CONNECTION_FAILED); |
| |
| const HttpResponseInfo* response = sock_->GetConnectResponseInfo(); |
| ASSERT_TRUE(response != nullptr); |
| |
| const HttpResponseHeaders* headers = response->headers.get(); |
| ASSERT_EQ(302, headers->response_code()); |
| ASSERT_TRUE(headers->HasHeader("set-cookie")); |
| |
| std::string location; |
| ASSERT_TRUE(headers->IsRedirect(&location)); |
| ASSERT_EQ(location, kRedirectUrl); |
| |
| // Let the RST_STREAM write while |rst| is in-scope. |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| TEST_P(SpdyProxyClientSocketTest, ConnectFails) { |
| spdy::SpdySerializedFrame conn(ConstructConnectRequestFrame()); |
| MockWrite writes[] = { |
| CreateMockWrite(conn, 0, SYNCHRONOUS), |
| }; |
| |
| spdy::SpdySerializedFrame resp(ConstructConnectReplyFrame()); |
| MockRead reads[] = { |
| MockRead(ASYNC, 0, 1), // EOF |
| }; |
| |
| Initialize(reads, writes); |
| |
| ASSERT_FALSE(sock_->IsConnected()); |
| |
| AssertConnectFails(ERR_CONNECTION_CLOSED); |
| |
| ASSERT_FALSE(sock_->IsConnected()); |
| } |
| |
| TEST_P(SpdyProxyClientSocketTest, SetStreamPriority) { |
| spdy::SpdySerializedFrame conn(ConstructConnectRequestFrame(LOWEST)); |
| MockWrite writes[] = { |
| CreateMockWrite(conn, 0, SYNCHRONOUS), |
| }; |
| |
| spdy::SpdySerializedFrame resp(ConstructConnectReplyFrame()); |
| MockRead reads[] = { |
| CreateMockRead(resp, 1, ASYNC), |
| MockRead(SYNCHRONOUS, ERR_IO_PENDING, 2), |
| }; |
| |
| Initialize(reads, writes); |
| |
| // Set the stream priority. Since a connection was already established, it's |
| // too late to adjust the HTTP2 stream's priority, and the request is ignored. |
| sock_->SetStreamPriority(HIGHEST); |
| |
| AssertConnectSucceeds(); |
| } |
| |
| // ----------- WasEverUsed |
| |
| TEST_P(SpdyProxyClientSocketTest, WasEverUsedReturnsCorrectValues) { |
| spdy::SpdySerializedFrame conn(ConstructConnectRequestFrame()); |
| spdy::SpdySerializedFrame rst( |
| spdy_util_.ConstructSpdyRstStream(1, spdy::ERROR_CODE_CANCEL)); |
| MockWrite writes[] = { |
| CreateMockWrite(conn, 0, SYNCHRONOUS), CreateMockWrite(rst, 3), |
| }; |
| |
| spdy::SpdySerializedFrame resp(ConstructConnectReplyFrame()); |
| MockRead reads[] = { |
| CreateMockRead(resp, 1, ASYNC), MockRead(SYNCHRONOUS, ERR_IO_PENDING, 2), |
| }; |
| |
| Initialize(reads, writes); |
| |
| EXPECT_FALSE(sock_->WasEverUsed()); |
| AssertConnectSucceeds(); |
| EXPECT_TRUE(sock_->WasEverUsed()); |
| sock_->Disconnect(); |
| EXPECT_TRUE(sock_->WasEverUsed()); |
| |
| // Let the RST_STREAM write while |rst| is in-scope. |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| // ----------- GetPeerAddress |
| |
| TEST_P(SpdyProxyClientSocketTest, GetPeerAddressReturnsCorrectValues) { |
| spdy::SpdySerializedFrame conn(ConstructConnectRequestFrame()); |
| MockWrite writes[] = { |
| CreateMockWrite(conn, 0, SYNCHRONOUS), |
| }; |
| |
| spdy::SpdySerializedFrame resp(ConstructConnectReplyFrame()); |
| MockRead reads[] = { |
| CreateMockRead(resp, 1, ASYNC), MockRead(ASYNC, ERR_IO_PENDING, 2), |
| MockRead(ASYNC, 0, 3), // EOF |
| }; |
| |
| Initialize(reads, writes); |
| |
| IPEndPoint addr; |
| EXPECT_THAT(sock_->GetPeerAddress(&addr), IsError(ERR_SOCKET_NOT_CONNECTED)); |
| |
| AssertConnectSucceeds(); |
| EXPECT_TRUE(sock_->IsConnected()); |
| EXPECT_THAT(sock_->GetPeerAddress(&addr), IsOk()); |
| |
| ResumeAndRun(); |
| |
| EXPECT_FALSE(sock_->IsConnected()); |
| EXPECT_THAT(sock_->GetPeerAddress(&addr), IsError(ERR_SOCKET_NOT_CONNECTED)); |
| |
| sock_->Disconnect(); |
| |
| EXPECT_THAT(sock_->GetPeerAddress(&addr), IsError(ERR_SOCKET_NOT_CONNECTED)); |
| } |
| |
| // ----------- Write |
| |
| TEST_P(SpdyProxyClientSocketTest, WriteSendsDataInDataFrame) { |
| spdy::SpdySerializedFrame conn(ConstructConnectRequestFrame()); |
| spdy::SpdySerializedFrame msg1(ConstructBodyFrame(kMsg1, kLen1)); |
| spdy::SpdySerializedFrame msg2(ConstructBodyFrame(kMsg2, kLen2)); |
| MockWrite writes[] = { |
| CreateMockWrite(conn, 0, SYNCHRONOUS), |
| CreateMockWrite(msg1, 3, SYNCHRONOUS), |
| CreateMockWrite(msg2, 4, SYNCHRONOUS), |
| }; |
| |
| spdy::SpdySerializedFrame resp(ConstructConnectReplyFrame()); |
| MockRead reads[] = { |
| CreateMockRead(resp, 1, ASYNC), MockRead(SYNCHRONOUS, ERR_IO_PENDING, 2), |
| }; |
| |
| Initialize(reads, writes); |
| |
| AssertConnectSucceeds(); |
| |
| AssertAsyncWriteSucceeds(kMsg1, kLen1); |
| AssertAsyncWriteSucceeds(kMsg2, kLen2); |
| } |
| |
| TEST_P(SpdyProxyClientSocketTest, WriteSplitsLargeDataIntoMultipleFrames) { |
| std::string chunk_data(kMaxSpdyFrameChunkSize, 'x'); |
| spdy::SpdySerializedFrame conn(ConstructConnectRequestFrame()); |
| spdy::SpdySerializedFrame chunk( |
| ConstructBodyFrame(chunk_data.data(), chunk_data.length())); |
| MockWrite writes[] = {CreateMockWrite(conn, 0, SYNCHRONOUS), |
| CreateMockWrite(chunk, 3, SYNCHRONOUS), |
| CreateMockWrite(chunk, 4, SYNCHRONOUS), |
| CreateMockWrite(chunk, 5, SYNCHRONOUS)}; |
| |
| spdy::SpdySerializedFrame resp(ConstructConnectReplyFrame()); |
| MockRead reads[] = { |
| CreateMockRead(resp, 1, ASYNC), MockRead(SYNCHRONOUS, ERR_IO_PENDING, 2), |
| }; |
| |
| Initialize(reads, writes); |
| |
| AssertConnectSucceeds(); |
| |
| std::string big_data(kMaxSpdyFrameChunkSize * 3, 'x'); |
| scoped_refptr<IOBufferWithSize> buf(CreateBuffer(big_data.data(), |
| big_data.length())); |
| |
| EXPECT_EQ(ERR_IO_PENDING, |
| sock_->Write(buf.get(), buf->size(), write_callback_.callback(), |
| TRAFFIC_ANNOTATION_FOR_TESTS)); |
| EXPECT_EQ(buf->size(), write_callback_.WaitForResult()); |
| } |
| |
| // ----------- Read |
| |
| TEST_P(SpdyProxyClientSocketTest, ReadReadsDataInDataFrame) { |
| spdy::SpdySerializedFrame conn(ConstructConnectRequestFrame()); |
| MockWrite writes[] = { |
| CreateMockWrite(conn, 0, SYNCHRONOUS), |
| }; |
| |
| spdy::SpdySerializedFrame resp(ConstructConnectReplyFrame()); |
| spdy::SpdySerializedFrame msg1(ConstructBodyFrame(kMsg1, kLen1)); |
| MockRead reads[] = { |
| CreateMockRead(resp, 1, ASYNC), MockRead(ASYNC, ERR_IO_PENDING, 2), |
| CreateMockRead(msg1, 3, ASYNC), MockRead(SYNCHRONOUS, ERR_IO_PENDING, 4), |
| }; |
| |
| Initialize(reads, writes); |
| |
| AssertConnectSucceeds(); |
| |
| // SpdySession consumes the next read and sends it to sock_ to be buffered. |
| ResumeAndRun(); |
| AssertSyncReadEquals(kMsg1, kLen1); |
| } |
| |
| TEST_P(SpdyProxyClientSocketTest, ReadDataFromBufferedFrames) { |
| spdy::SpdySerializedFrame conn(ConstructConnectRequestFrame()); |
| MockWrite writes[] = { |
| CreateMockWrite(conn, 0, SYNCHRONOUS), |
| }; |
| |
| spdy::SpdySerializedFrame resp(ConstructConnectReplyFrame()); |
| spdy::SpdySerializedFrame msg1(ConstructBodyFrame(kMsg1, kLen1)); |
| spdy::SpdySerializedFrame msg2(ConstructBodyFrame(kMsg2, kLen2)); |
| MockRead reads[] = { |
| CreateMockRead(resp, 1, ASYNC), MockRead(ASYNC, ERR_IO_PENDING, 2), |
| CreateMockRead(msg1, 3, ASYNC), MockRead(ASYNC, ERR_IO_PENDING, 4), |
| CreateMockRead(msg2, 5, ASYNC), MockRead(SYNCHRONOUS, ERR_IO_PENDING, 6), |
| }; |
| |
| Initialize(reads, writes); |
| |
| AssertConnectSucceeds(); |
| |
| // SpdySession consumes the next read and sends it to sock_ to be buffered. |
| ResumeAndRun(); |
| AssertSyncReadEquals(kMsg1, kLen1); |
| // SpdySession consumes the next read and sends it to sock_ to be buffered. |
| ResumeAndRun(); |
| AssertSyncReadEquals(kMsg2, kLen2); |
| } |
| |
| TEST_P(SpdyProxyClientSocketTest, ReadDataMultipleBufferedFrames) { |
| spdy::SpdySerializedFrame conn(ConstructConnectRequestFrame()); |
| MockWrite writes[] = { |
| CreateMockWrite(conn, 0, SYNCHRONOUS), |
| }; |
| |
| spdy::SpdySerializedFrame resp(ConstructConnectReplyFrame()); |
| spdy::SpdySerializedFrame msg1(ConstructBodyFrame(kMsg1, kLen1)); |
| spdy::SpdySerializedFrame msg2(ConstructBodyFrame(kMsg2, kLen2)); |
| MockRead reads[] = { |
| CreateMockRead(resp, 1, ASYNC), |
| MockRead(ASYNC, ERR_IO_PENDING, 2), |
| CreateMockRead(msg1, 3, ASYNC), |
| CreateMockRead(msg2, 4, ASYNC), |
| MockRead(SYNCHRONOUS, ERR_IO_PENDING, 5), |
| }; |
| |
| Initialize(reads, writes); |
| |
| AssertConnectSucceeds(); |
| |
| // SpdySession consumes the next two reads and sends then to sock_ to be |
| // buffered. |
| ResumeAndRun(); |
| AssertSyncReadEquals(kMsg1, kLen1); |
| AssertSyncReadEquals(kMsg2, kLen2); |
| } |
| |
| TEST_P(SpdyProxyClientSocketTest, LargeReadWillMergeDataFromDifferentFrames) { |
| spdy::SpdySerializedFrame conn(ConstructConnectRequestFrame()); |
| MockWrite writes[] = { |
| CreateMockWrite(conn, 0, SYNCHRONOUS), |
| }; |
| |
| spdy::SpdySerializedFrame resp(ConstructConnectReplyFrame()); |
| spdy::SpdySerializedFrame msg1(ConstructBodyFrame(kMsg1, kLen1)); |
| spdy::SpdySerializedFrame msg3(ConstructBodyFrame(kMsg3, kLen3)); |
| MockRead reads[] = { |
| CreateMockRead(resp, 1, ASYNC), |
| MockRead(ASYNC, ERR_IO_PENDING, 2), |
| CreateMockRead(msg3, 3, ASYNC), |
| CreateMockRead(msg3, 4, ASYNC), |
| MockRead(SYNCHRONOUS, ERR_IO_PENDING, 5), |
| }; |
| |
| Initialize(reads, writes); |
| |
| AssertConnectSucceeds(); |
| |
| // SpdySession consumes the next two reads and sends then to sock_ to be |
| // buffered. |
| ResumeAndRun(); |
| // The payload from two data frames, each with kMsg3 will be combined |
| // together into a single read(). |
| AssertSyncReadEquals(kMsg33, kLen33); |
| } |
| |
| TEST_P(SpdyProxyClientSocketTest, MultipleShortReadsThenMoreRead) { |
| spdy::SpdySerializedFrame conn(ConstructConnectRequestFrame()); |
| MockWrite writes[] = { |
| CreateMockWrite(conn, 0, SYNCHRONOUS), |
| }; |
| |
| spdy::SpdySerializedFrame resp(ConstructConnectReplyFrame()); |
| spdy::SpdySerializedFrame msg1(ConstructBodyFrame(kMsg1, kLen1)); |
| spdy::SpdySerializedFrame msg3(ConstructBodyFrame(kMsg3, kLen3)); |
| spdy::SpdySerializedFrame msg2(ConstructBodyFrame(kMsg2, kLen2)); |
| MockRead reads[] = { |
| CreateMockRead(resp, 1, ASYNC), |
| MockRead(ASYNC, ERR_IO_PENDING, 2), |
| CreateMockRead(msg1, 3, ASYNC), |
| CreateMockRead(msg3, 4, ASYNC), |
| CreateMockRead(msg3, 5, ASYNC), |
| CreateMockRead(msg2, 6, ASYNC), |
| MockRead(SYNCHRONOUS, ERR_IO_PENDING, 7), |
| }; |
| |
| Initialize(reads, writes); |
| |
| AssertConnectSucceeds(); |
| |
| // SpdySession consumes the next four reads and sends then to sock_ to be |
| // buffered. |
| ResumeAndRun(); |
| AssertSyncReadEquals(kMsg1, kLen1); |
| // The payload from two data frames, each with kMsg3 will be combined |
| // together into a single read(). |
| AssertSyncReadEquals(kMsg33, kLen33); |
| AssertSyncReadEquals(kMsg2, kLen2); |
| } |
| |
| TEST_P(SpdyProxyClientSocketTest, ReadWillSplitDataFromLargeFrame) { |
| spdy::SpdySerializedFrame conn(ConstructConnectRequestFrame()); |
| MockWrite writes[] = { |
| CreateMockWrite(conn, 0, SYNCHRONOUS), |
| }; |
| |
| spdy::SpdySerializedFrame resp(ConstructConnectReplyFrame()); |
| spdy::SpdySerializedFrame msg1(ConstructBodyFrame(kMsg1, kLen1)); |
| spdy::SpdySerializedFrame msg33(ConstructBodyFrame(kMsg33, kLen33)); |
| spdy::SpdySerializedFrame msg2(ConstructBodyFrame(kMsg2, kLen2)); |
| MockRead reads[] = { |
| CreateMockRead(resp, 1, ASYNC), |
| MockRead(ASYNC, ERR_IO_PENDING, 2), |
| CreateMockRead(msg1, 3, ASYNC), |
| CreateMockRead(msg33, 4, ASYNC), |
| MockRead(SYNCHRONOUS, ERR_IO_PENDING, 5), |
| }; |
| |
| Initialize(reads, writes); |
| |
| AssertConnectSucceeds(); |
| |
| // SpdySession consumes the next two reads and sends then to sock_ to be |
| // buffered. |
| ResumeAndRun(); |
| AssertSyncReadEquals(kMsg1, kLen1); |
| // The payload from the single large data frame will be read across |
| // two different reads. |
| AssertSyncReadEquals(kMsg3, kLen3); |
| AssertSyncReadEquals(kMsg3, kLen3); |
| } |
| |
| TEST_P(SpdyProxyClientSocketTest, MultipleReadsFromSameLargeFrame) { |
| spdy::SpdySerializedFrame conn(ConstructConnectRequestFrame()); |
| MockWrite writes[] = { |
| CreateMockWrite(conn, 0, SYNCHRONOUS), |
| }; |
| |
| spdy::SpdySerializedFrame resp(ConstructConnectReplyFrame()); |
| spdy::SpdySerializedFrame msg333(ConstructBodyFrame(kMsg333, kLen333)); |
| MockRead reads[] = { |
| CreateMockRead(resp, 1, ASYNC), MockRead(ASYNC, ERR_IO_PENDING, 2), |
| CreateMockRead(msg333, 3, ASYNC), |
| MockRead(SYNCHRONOUS, ERR_IO_PENDING, 4), |
| }; |
| |
| Initialize(reads, writes); |
| |
| AssertConnectSucceeds(); |
| |
| // SpdySession consumes the next read and sends it to sock_ to be buffered. |
| ResumeAndRun(); |
| // The payload from the single large data frame will be read across |
| // two different reads. |
| AssertSyncReadEquals(kMsg33, kLen33); |
| |
| // Now attempt to do a read of more data than remains buffered |
| AssertSyncReadEquals(kMsg3, kLen3); |
| |
| ASSERT_TRUE(sock_->IsConnected()); |
| } |
| |
| TEST_P(SpdyProxyClientSocketTest, ReadAuthResponseBody) { |
| spdy::SpdySerializedFrame conn(ConstructConnectRequestFrame()); |
| MockWrite writes[] = { |
| CreateMockWrite(conn, 0, SYNCHRONOUS), |
| }; |
| |
| spdy::SpdySerializedFrame resp(ConstructConnectAuthReplyFrame()); |
| spdy::SpdySerializedFrame msg1(ConstructBodyFrame(kMsg1, kLen1)); |
| spdy::SpdySerializedFrame msg2(ConstructBodyFrame(kMsg2, kLen2)); |
| MockRead reads[] = { |
| CreateMockRead(resp, 1, ASYNC), |
| MockRead(ASYNC, ERR_IO_PENDING, 2), |
| CreateMockRead(msg1, 3, ASYNC), |
| CreateMockRead(msg2, 4, ASYNC), |
| MockRead(SYNCHRONOUS, ERR_IO_PENDING, 5), |
| }; |
| |
| Initialize(reads, writes); |
| |
| AssertConnectFails(ERR_PROXY_AUTH_REQUESTED); |
| |
| // SpdySession consumes the next two reads and sends then to sock_ to be |
| // buffered. |
| ResumeAndRun(); |
| AssertSyncReadEquals(kMsg1, kLen1); |
| AssertSyncReadEquals(kMsg2, kLen2); |
| } |
| |
| TEST_P(SpdyProxyClientSocketTest, ReadErrorResponseBody) { |
| spdy::SpdySerializedFrame conn(ConstructConnectRequestFrame()); |
| MockWrite writes[] = { |
| CreateMockWrite(conn, 0, SYNCHRONOUS), |
| }; |
| |
| spdy::SpdySerializedFrame resp(ConstructConnectErrorReplyFrame()); |
| spdy::SpdySerializedFrame msg1(ConstructBodyFrame(kMsg1, kLen1)); |
| spdy::SpdySerializedFrame msg2(ConstructBodyFrame(kMsg2, kLen2)); |
| MockRead reads[] = { |
| CreateMockRead(resp, 1, ASYNC), CreateMockRead(msg1, 2, SYNCHRONOUS), |
| CreateMockRead(msg2, 3, SYNCHRONOUS), MockRead(SYNCHRONOUS, 0, 4), // EOF |
| }; |
| |
| Initialize(reads, writes); |
| |
| AssertConnectFails(ERR_TUNNEL_CONNECTION_FAILED); |
| } |
| |
| TEST_P(SpdyProxyClientSocketTest, SocketDestroyedWhenReadIsPending) { |
| spdy::SpdySerializedFrame conn(ConstructConnectRequestFrame()); |
| MockWrite writes[] = {CreateMockWrite(conn, 0, SYNCHRONOUS)}; |
| |
| spdy::SpdySerializedFrame resp(ConstructConnectReplyFrame()); |
| spdy::SpdySerializedFrame msg1(ConstructBodyFrame(kMsg1, kLen1)); |
| MockRead reads[] = { |
| CreateMockRead(resp, 1, ASYNC), CreateMockRead(msg1, 2, ASYNC), |
| MockRead(SYNCHRONOUS, ERR_IO_PENDING, 3), |
| }; |
| |
| Initialize(reads, writes); |
| |
| AssertConnectSucceeds(); |
| |
| // Make Read()/ReadIfReady() pending. |
| AssertReadStarts(kMsg1, kLen1); |
| |
| // Destroying socket. |
| sock_ = nullptr; |
| |
| // Read data is not consumed. |
| EXPECT_TRUE(data_->AllWriteDataConsumed()); |
| EXPECT_FALSE(data_->AllReadDataConsumed()); |
| |
| // Reset |data_| so the test destructor doesn't check it. |
| data_ = nullptr; |
| } |
| |
| // ----------- Reads and Writes |
| |
| TEST_P(SpdyProxyClientSocketTest, AsyncReadAroundWrite) { |
| spdy::SpdySerializedFrame conn(ConstructConnectRequestFrame()); |
| spdy::SpdySerializedFrame msg2(ConstructBodyFrame(kMsg2, kLen2)); |
| MockWrite writes[] = { |
| CreateMockWrite(conn, 0, SYNCHRONOUS), |
| CreateMockWrite(msg2, 4, SYNCHRONOUS), |
| }; |
| |
| spdy::SpdySerializedFrame resp(ConstructConnectReplyFrame()); |
| spdy::SpdySerializedFrame msg1(ConstructBodyFrame(kMsg1, kLen1)); |
| spdy::SpdySerializedFrame msg3(ConstructBodyFrame(kMsg3, kLen3)); |
| MockRead reads[] = { |
| CreateMockRead(resp, 1, ASYNC), |
| MockRead(ASYNC, ERR_IO_PENDING, 2), |
| CreateMockRead(msg1, 3, ASYNC), // sync read |
| MockRead(ASYNC, ERR_IO_PENDING, 5), |
| CreateMockRead(msg3, 6, ASYNC), // async read |
| MockRead(SYNCHRONOUS, ERR_IO_PENDING, 7), |
| }; |
| |
| Initialize(reads, writes); |
| |
| AssertConnectSucceeds(); |
| |
| ResumeAndRun(); |
| AssertSyncReadEquals(kMsg1, kLen1); |
| |
| AssertReadStarts(kMsg3, kLen3); |
| // Read should block until after the write succeeds. |
| |
| AssertAsyncWriteSucceeds(kMsg2, kLen2); // Advances past paused read. |
| |
| ASSERT_FALSE(read_callback_.have_result()); |
| ResumeAndRun(); |
| // Now the read will return. |
| AssertReadReturns(kMsg3, kLen3); |
| } |
| |
| TEST_P(SpdyProxyClientSocketTest, AsyncWriteAroundReads) { |
| spdy::SpdySerializedFrame conn(ConstructConnectRequestFrame()); |
| spdy::SpdySerializedFrame msg2(ConstructBodyFrame(kMsg2, kLen2)); |
| MockWrite writes[] = { |
| CreateMockWrite(conn, 0, SYNCHRONOUS), |
| MockWrite(ASYNC, ERR_IO_PENDING, 7), CreateMockWrite(msg2, 8, ASYNC), |
| }; |
| |
| spdy::SpdySerializedFrame resp(ConstructConnectReplyFrame()); |
| spdy::SpdySerializedFrame msg1(ConstructBodyFrame(kMsg1, kLen1)); |
| spdy::SpdySerializedFrame msg3(ConstructBodyFrame(kMsg3, kLen3)); |
| MockRead reads[] = { |
| CreateMockRead(resp, 1, ASYNC), MockRead(ASYNC, ERR_IO_PENDING, 2), |
| CreateMockRead(msg1, 3, ASYNC), MockRead(ASYNC, ERR_IO_PENDING, 4), |
| CreateMockRead(msg3, 5, ASYNC), MockRead(SYNCHRONOUS, ERR_IO_PENDING, 6), |
| }; |
| |
| Initialize(reads, writes); |
| |
| AssertConnectSucceeds(); |
| |
| ResumeAndRun(); |
| AssertSyncReadEquals(kMsg1, kLen1); |
| // Write should block until the read completes |
| AssertWriteReturns(kMsg2, kLen2, ERR_IO_PENDING); |
| |
| AssertAsyncReadEquals(kMsg3, kLen3); |
| |
| ASSERT_FALSE(write_callback_.have_result()); |
| |
| // Now the write will complete |
| ResumeAndRun(); |
| AssertWriteLength(kLen2); |
| } |
| |
| // ----------- Reading/Writing on Closed socket |
| |
| // Reading from an already closed socket should return 0 |
| TEST_P(SpdyProxyClientSocketTest, ReadOnClosedSocketReturnsZero) { |
| spdy::SpdySerializedFrame conn(ConstructConnectRequestFrame()); |
| MockWrite writes[] = { |
| CreateMockWrite(conn, 0, SYNCHRONOUS), |
| }; |
| |
| spdy::SpdySerializedFrame resp(ConstructConnectReplyFrame()); |
| MockRead reads[] = { |
| CreateMockRead(resp, 1, ASYNC), MockRead(ASYNC, ERR_IO_PENDING, 2), |
| MockRead(ASYNC, 0, 3), // EOF |
| }; |
| |
| Initialize(reads, writes); |
| |
| AssertConnectSucceeds(); |
| |
| ResumeAndRun(); |
| |
| ASSERT_FALSE(sock_->IsConnected()); |
| AssertSyncReadEOF(); |
| AssertSyncReadEOF(); |
| AssertSyncReadEOF(); |
| ASSERT_FALSE(sock_->IsConnectedAndIdle()); |
| } |
| |
| // Read pending when socket is closed should return 0 |
| TEST_P(SpdyProxyClientSocketTest, PendingReadOnCloseReturnsZero) { |
| spdy::SpdySerializedFrame conn(ConstructConnectRequestFrame()); |
| MockWrite writes[] = { |
| CreateMockWrite(conn, 0, SYNCHRONOUS), |
| }; |
| |
| spdy::SpdySerializedFrame resp(ConstructConnectReplyFrame()); |
| MockRead reads[] = { |
| CreateMockRead(resp, 1, ASYNC), MockRead(ASYNC, ERR_IO_PENDING, 2), |
| MockRead(ASYNC, 0, 3), // EOF |
| }; |
| |
| Initialize(reads, writes); |
| |
| AssertConnectSucceeds(); |
| |
| AssertReadStarts(kMsg1, kLen1); |
| |
| ResumeAndRun(); |
| |
| ASSERT_EQ(0, read_callback_.WaitForResult()); |
| } |
| |
| // Reading from a disconnected socket is an error |
| TEST_P(SpdyProxyClientSocketTest, ReadOnDisconnectSocketReturnsNotConnected) { |
| spdy::SpdySerializedFrame conn(ConstructConnectRequestFrame()); |
| spdy::SpdySerializedFrame rst( |
| spdy_util_.ConstructSpdyRstStream(1, spdy::ERROR_CODE_CANCEL)); |
| MockWrite writes[] = { |
| CreateMockWrite(conn, 0, SYNCHRONOUS), CreateMockWrite(rst, 3), |
| }; |
| |
| spdy::SpdySerializedFrame resp(ConstructConnectReplyFrame()); |
| MockRead reads[] = { |
| CreateMockRead(resp, 1, ASYNC), MockRead(SYNCHRONOUS, ERR_IO_PENDING, 2), |
| }; |
| |
| Initialize(reads, writes); |
| |
| AssertConnectSucceeds(); |
| |
| sock_->Disconnect(); |
| |
| if (use_read_if_ready()) { |
| ASSERT_EQ(ERR_SOCKET_NOT_CONNECTED, |
| sock_->ReadIfReady(nullptr, 1, CompletionOnceCallback())); |
| } else { |
| ASSERT_EQ(ERR_SOCKET_NOT_CONNECTED, |
| sock_->Read(nullptr, 1, CompletionOnceCallback())); |
| } |
| |
| // Let the RST_STREAM write while |rst| is in-scope. |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| // Reading buffered data from an already closed socket should return |
| // buffered data, then 0. |
| TEST_P(SpdyProxyClientSocketTest, ReadOnClosedSocketReturnsBufferedData) { |
| spdy::SpdySerializedFrame conn(ConstructConnectRequestFrame()); |
| MockWrite writes[] = { |
| CreateMockWrite(conn, 0, SYNCHRONOUS), |
| }; |
| |
| spdy::SpdySerializedFrame resp(ConstructConnectReplyFrame()); |
| spdy::SpdySerializedFrame msg1(ConstructBodyFrame(kMsg1, kLen1)); |
| MockRead reads[] = { |
| CreateMockRead(resp, 1, ASYNC), MockRead(ASYNC, ERR_IO_PENDING, 2), |
| CreateMockRead(msg1, 3, ASYNC), MockRead(ASYNC, 0, 4), // EOF |
| }; |
| |
| Initialize(reads, writes); |
| |
| AssertConnectSucceeds(); |
| |
| ResumeAndRun(); |
| |
| ASSERT_FALSE(sock_->IsConnected()); |
| scoped_refptr<IOBuffer> buf = base::MakeRefCounted<IOBuffer>(kLen1); |
| ASSERT_EQ(kLen1, sock_->Read(buf.get(), kLen1, CompletionOnceCallback())); |
| ASSERT_EQ(std::string(kMsg1, kLen1), std::string(buf->data(), kLen1)); |
| |
| ASSERT_EQ(0, sock_->Read(nullptr, 1, CompletionOnceCallback())); |
| ASSERT_EQ(0, sock_->Read(nullptr, 1, CompletionOnceCallback())); |
| sock_->Disconnect(); |
| ASSERT_EQ(ERR_SOCKET_NOT_CONNECTED, |
| sock_->Read(nullptr, 1, CompletionOnceCallback())); |
| } |
| |
| // Calling Write() on a closed socket is an error |
| TEST_P(SpdyProxyClientSocketTest, WriteOnClosedStream) { |
| spdy::SpdySerializedFrame conn(ConstructConnectRequestFrame()); |
| MockWrite writes[] = { |
| CreateMockWrite(conn, 0, SYNCHRONOUS), |
| }; |
| |
| spdy::SpdySerializedFrame resp(ConstructConnectReplyFrame()); |
| spdy::SpdySerializedFrame msg1(ConstructBodyFrame(kMsg1, kLen1)); |
| MockRead reads[] = { |
| CreateMockRead(resp, 1, ASYNC), MockRead(ASYNC, ERR_IO_PENDING, 2), |
| MockRead(ASYNC, 0, 3), // EOF |
| }; |
| |
| Initialize(reads, writes); |
| |
| AssertConnectSucceeds(); |
| |
| // Read EOF which will close the stream. |
| ResumeAndRun(); |
| scoped_refptr<IOBufferWithSize> buf(CreateBuffer(kMsg1, kLen1)); |
| EXPECT_EQ(ERR_SOCKET_NOT_CONNECTED, |
| sock_->Write(buf.get(), buf->size(), CompletionOnceCallback(), |
| TRAFFIC_ANNOTATION_FOR_TESTS)); |
| } |
| |
| // Calling Write() on a disconnected socket is an error. |
| TEST_P(SpdyProxyClientSocketTest, WriteOnDisconnectedSocket) { |
| spdy::SpdySerializedFrame conn(ConstructConnectRequestFrame()); |
| spdy::SpdySerializedFrame rst( |
| spdy_util_.ConstructSpdyRstStream(1, spdy::ERROR_CODE_CANCEL)); |
| MockWrite writes[] = { |
| CreateMockWrite(conn, 0, SYNCHRONOUS), CreateMockWrite(rst, 3), |
| }; |
| |
| spdy::SpdySerializedFrame resp(ConstructConnectReplyFrame()); |
| spdy::SpdySerializedFrame msg1(ConstructBodyFrame(kMsg1, kLen1)); |
| MockRead reads[] = { |
| CreateMockRead(resp, 1, ASYNC), MockRead(SYNCHRONOUS, ERR_IO_PENDING, 2), |
| }; |
| |
| Initialize(reads, writes); |
| |
| AssertConnectSucceeds(); |
| |
| sock_->Disconnect(); |
| |
| scoped_refptr<IOBufferWithSize> buf(CreateBuffer(kMsg1, kLen1)); |
| EXPECT_EQ(ERR_SOCKET_NOT_CONNECTED, |
| sock_->Write(buf.get(), buf->size(), CompletionOnceCallback(), |
| TRAFFIC_ANNOTATION_FOR_TESTS)); |
| |
| // Let the RST_STREAM write while |rst| is in-scope. |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| // If the socket is closed with a pending Write(), the callback |
| // should be called with ERR_CONNECTION_CLOSED. |
| TEST_P(SpdyProxyClientSocketTest, WritePendingOnClose) { |
| spdy::SpdySerializedFrame conn(ConstructConnectRequestFrame()); |
| MockWrite writes[] = { |
| CreateMockWrite(conn, 0, SYNCHRONOUS), |
| MockWrite(SYNCHRONOUS, ERR_IO_PENDING, 3), |
| }; |
| |
| spdy::SpdySerializedFrame resp(ConstructConnectReplyFrame()); |
| MockRead reads[] = { |
| CreateMockRead(resp, 1, ASYNC), MockRead(SYNCHRONOUS, ERR_IO_PENDING, 2), |
| }; |
| |
| Initialize(reads, writes); |
| |
| AssertConnectSucceeds(); |
| |
| EXPECT_TRUE(sock_->IsConnected()); |
| |
| scoped_refptr<IOBufferWithSize> buf(CreateBuffer(kMsg1, kLen1)); |
| EXPECT_EQ(ERR_IO_PENDING, |
| sock_->Write(buf.get(), buf->size(), write_callback_.callback(), |
| TRAFFIC_ANNOTATION_FOR_TESTS)); |
| // Make sure the write actually starts. |
| base::RunLoop().RunUntilIdle(); |
| |
| CloseSpdySession(ERR_ABORTED, std::string()); |
| |
| EXPECT_THAT(write_callback_.WaitForResult(), IsError(ERR_CONNECTION_CLOSED)); |
| } |
| |
| // If the socket is Disconnected with a pending Write(), the callback |
| // should not be called. |
| TEST_P(SpdyProxyClientSocketTest, DisconnectWithWritePending) { |
| spdy::SpdySerializedFrame conn(ConstructConnectRequestFrame()); |
| spdy::SpdySerializedFrame rst( |
| spdy_util_.ConstructSpdyRstStream(1, spdy::ERROR_CODE_CANCEL)); |
| MockWrite writes[] = { |
| CreateMockWrite(conn, 0, SYNCHRONOUS), CreateMockWrite(rst, 3), |
| }; |
| |
| spdy::SpdySerializedFrame resp(ConstructConnectReplyFrame()); |
| MockRead reads[] = { |
| CreateMockRead(resp, 1, ASYNC), MockRead(SYNCHRONOUS, ERR_IO_PENDING, 2), |
| }; |
| |
| Initialize(reads, writes); |
| |
| AssertConnectSucceeds(); |
| |
| EXPECT_TRUE(sock_->IsConnected()); |
| |
| scoped_refptr<IOBufferWithSize> buf(CreateBuffer(kMsg1, kLen1)); |
| EXPECT_EQ(ERR_IO_PENDING, |
| sock_->Write(buf.get(), buf->size(), write_callback_.callback(), |
| TRAFFIC_ANNOTATION_FOR_TESTS)); |
| |
| sock_->Disconnect(); |
| |
| EXPECT_FALSE(sock_->IsConnected()); |
| EXPECT_FALSE(write_callback_.have_result()); |
| |
| // Let the RST_STREAM write while |rst| is in-scope. |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| // If the socket is Disconnected with a pending Read(), the callback |
| // should not be called. |
| TEST_P(SpdyProxyClientSocketTest, DisconnectWithReadPending) { |
| spdy::SpdySerializedFrame conn(ConstructConnectRequestFrame()); |
| spdy::SpdySerializedFrame rst( |
| spdy_util_.ConstructSpdyRstStream(1, spdy::ERROR_CODE_CANCEL)); |
| MockWrite writes[] = { |
| CreateMockWrite(conn, 0, SYNCHRONOUS), CreateMockWrite(rst, 3), |
| }; |
| |
| spdy::SpdySerializedFrame resp(ConstructConnectReplyFrame()); |
| MockRead reads[] = { |
| CreateMockRead(resp, 1, ASYNC), MockRead(SYNCHRONOUS, ERR_IO_PENDING, 2), |
| }; |
| |
| Initialize(reads, writes); |
| |
| AssertConnectSucceeds(); |
| |
| EXPECT_TRUE(sock_->IsConnected()); |
| |
| scoped_refptr<IOBuffer> buf = base::MakeRefCounted<IOBuffer>(kLen1); |
| ASSERT_EQ(ERR_IO_PENDING, |
| sock_->Read(buf.get(), kLen1, read_callback_.callback())); |
| |
| sock_->Disconnect(); |
| |
| EXPECT_FALSE(sock_->IsConnected()); |
| EXPECT_FALSE(read_callback_.have_result()); |
| |
| // Let the RST_STREAM write while |rst| is in-scope. |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| // If the socket is Reset when both a read and write are pending, |
| // both should be called back. |
| TEST_P(SpdyProxyClientSocketTest, RstWithReadAndWritePending) { |
| spdy::SpdySerializedFrame conn(ConstructConnectRequestFrame()); |
| MockWrite writes[] = { |
| CreateMockWrite(conn, 0, SYNCHRONOUS), |
| }; |
| |
| spdy::SpdySerializedFrame resp(ConstructConnectReplyFrame()); |
| spdy::SpdySerializedFrame rst( |
| spdy_util_.ConstructSpdyRstStream(1, spdy::ERROR_CODE_CANCEL)); |
| MockRead reads[] = { |
| CreateMockRead(resp, 1, ASYNC), MockRead(ASYNC, ERR_IO_PENDING, 2), |
| CreateMockRead(rst, 3, ASYNC), MockRead(ASYNC, 0, 4) // EOF |
| }; |
| |
| Initialize(reads, writes); |
| |
| AssertConnectSucceeds(); |
| |
| EXPECT_TRUE(sock_->IsConnected()); |
| |
| scoped_refptr<IOBuffer> read_buf = base::MakeRefCounted<IOBuffer>(kLen1); |
| ASSERT_EQ(ERR_IO_PENDING, |
| sock_->Read(read_buf.get(), kLen1, read_callback_.callback())); |
| |
| scoped_refptr<IOBufferWithSize> write_buf(CreateBuffer(kMsg1, kLen1)); |
| EXPECT_EQ(ERR_IO_PENDING, sock_->Write(write_buf.get(), write_buf->size(), |
| write_callback_.callback(), |
| TRAFFIC_ANNOTATION_FOR_TESTS)); |
| |
| ResumeAndRun(); |
| |
| EXPECT_TRUE(sock_.get()); |
| EXPECT_TRUE(read_callback_.have_result()); |
| EXPECT_TRUE(write_callback_.have_result()); |
| |
| // Let the RST_STREAM write while |rst| is in-scope. |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| // Makes sure the proxy client socket's source gets the expected NetLog events |
| // and only the expected NetLog events (No SpdySession events). |
| TEST_P(SpdyProxyClientSocketTest, NetLog) { |
| spdy::SpdySerializedFrame conn(ConstructConnectRequestFrame()); |
| spdy::SpdySerializedFrame rst( |
| spdy_util_.ConstructSpdyRstStream(1, spdy::ERROR_CODE_CANCEL)); |
| MockWrite writes[] = { |
| CreateMockWrite(conn, 0, SYNCHRONOUS), CreateMockWrite(rst, 5), |
| }; |
| |
| spdy::SpdySerializedFrame resp(ConstructConnectReplyFrame()); |
| spdy::SpdySerializedFrame msg1(ConstructBodyFrame(kMsg1, kLen1)); |
| MockRead reads[] = { |
| CreateMockRead(resp, 1, ASYNC), MockRead(ASYNC, ERR_IO_PENDING, 2), |
| CreateMockRead(msg1, 3, ASYNC), MockRead(SYNCHRONOUS, ERR_IO_PENDING, 4), |
| }; |
| |
| Initialize(reads, writes); |
| |
| AssertConnectSucceeds(); |
| |
| // SpdySession consumes the next read and sends it to sock_ to be buffered. |
| ResumeAndRun(); |
| AssertSyncReadEquals(kMsg1, kLen1); |
| |
| NetLogSource sock_source = sock_->NetLog().source(); |
| sock_.reset(); |
| |
| auto entry_list = net_log_.GetEntriesForSource(sock_source); |
| |
| ASSERT_EQ(entry_list.size(), 10u); |
| EXPECT_TRUE( |
| LogContainsBeginEvent(entry_list, 0, NetLogEventType::SOCKET_ALIVE)); |
| EXPECT_TRUE(LogContainsEvent(entry_list, 1, |
| NetLogEventType::HTTP2_PROXY_CLIENT_SESSION, |
| NetLogEventPhase::NONE)); |
| EXPECT_TRUE(LogContainsBeginEvent( |
| entry_list, 2, NetLogEventType::HTTP_TRANSACTION_TUNNEL_SEND_REQUEST)); |
| EXPECT_TRUE(LogContainsEvent( |
| entry_list, 3, NetLogEventType::HTTP_TRANSACTION_SEND_TUNNEL_HEADERS, |
| NetLogEventPhase::NONE)); |
| EXPECT_TRUE(LogContainsEndEvent( |
| entry_list, 4, NetLogEventType::HTTP_TRANSACTION_TUNNEL_SEND_REQUEST)); |
| EXPECT_TRUE(LogContainsBeginEvent( |
| entry_list, 5, NetLogEventType::HTTP_TRANSACTION_TUNNEL_READ_HEADERS)); |
| EXPECT_TRUE(LogContainsEvent( |
| entry_list, 6, |
| NetLogEventType::HTTP_TRANSACTION_READ_TUNNEL_RESPONSE_HEADERS, |
| NetLogEventPhase::NONE)); |
| EXPECT_TRUE(LogContainsEndEvent( |
| entry_list, 7, NetLogEventType::HTTP_TRANSACTION_TUNNEL_READ_HEADERS)); |
| EXPECT_TRUE(LogContainsEvent(entry_list, 8, |
| NetLogEventType::SOCKET_BYTES_RECEIVED, |
| NetLogEventPhase::NONE)); |
| EXPECT_TRUE( |
| LogContainsEndEvent(entry_list, 9, NetLogEventType::SOCKET_ALIVE)); |
| |
| // Let the RST_STREAM write while |rst| is in-scope. |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| // A helper class that will delete |sock| when the callback is invoked. |
| class DeleteSockCallback : public TestCompletionCallbackBase { |
| public: |
| explicit DeleteSockCallback(std::unique_ptr<SpdyProxyClientSocket>* sock) |
| : sock_(sock) {} |
| |
| ~DeleteSockCallback() override = default; |
| |
| CompletionOnceCallback callback() { |
| return base::BindOnce(&DeleteSockCallback::OnComplete, |
| base::Unretained(this)); |
| } |
| |
| private: |
| void OnComplete(int result) { |
| sock_->reset(nullptr); |
| SetResult(result); |
| } |
| |
| std::unique_ptr<SpdyProxyClientSocket>* sock_; |
| |
| DISALLOW_COPY_AND_ASSIGN(DeleteSockCallback); |
| }; |
| |
| // If the socket is Reset when both a read and write are pending, and the |
| // read callback causes the socket to be deleted, the write callback should |
| // not be called. |
| TEST_P(SpdyProxyClientSocketTest, RstWithReadAndWritePendingDelete) { |
| spdy::SpdySerializedFrame conn(ConstructConnectRequestFrame()); |
| MockWrite writes[] = { |
| CreateMockWrite(conn, 0, SYNCHRONOUS), |
| }; |
| |
| spdy::SpdySerializedFrame resp(ConstructConnectReplyFrame()); |
| spdy::SpdySerializedFrame rst( |
| spdy_util_.ConstructSpdyRstStream(1, spdy::ERROR_CODE_CANCEL)); |
| MockRead reads[] = { |
| CreateMockRead(resp, 1, ASYNC), MockRead(ASYNC, ERR_IO_PENDING, 2), |
| CreateMockRead(rst, 3, ASYNC), MockRead(SYNCHRONOUS, ERR_IO_PENDING, 4), |
| }; |
| |
| Initialize(reads, writes); |
| |
| AssertConnectSucceeds(); |
| |
| EXPECT_TRUE(sock_->IsConnected()); |
| |
| DeleteSockCallback read_callback(&sock_); |
| |
| scoped_refptr<IOBuffer> read_buf = base::MakeRefCounted<IOBuffer>(kLen1); |
| ASSERT_EQ(ERR_IO_PENDING, |
| sock_->Read(read_buf.get(), kLen1, read_callback.callback())); |
| |
| scoped_refptr<IOBufferWithSize> write_buf(CreateBuffer(kMsg1, kLen1)); |
| EXPECT_EQ(ERR_IO_PENDING, sock_->Write(write_buf.get(), write_buf->size(), |
| write_callback_.callback(), |
| TRAFFIC_ANNOTATION_FOR_TESTS)); |
| |
| ResumeAndRun(); |
| |
| EXPECT_FALSE(sock_.get()); |
| EXPECT_TRUE(read_callback.have_result()); |
| EXPECT_FALSE(write_callback_.have_result()); |
| |
| // Let the RST_STREAM write while |rst| is in-scope. |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| // ----------- Canceling a ReadIfReady |
| TEST_P(SpdyProxyClientSocketTest, CancelReadIfReady) { |
| // Not relevant if not ReadIfReady(). |
| if (!use_read_if_ready()) |
| return; |
| |
| spdy::SpdySerializedFrame conn(ConstructConnectRequestFrame()); |
| MockWrite writes[] = {CreateMockWrite(conn, 0, SYNCHRONOUS)}; |
| |
| spdy::SpdySerializedFrame resp(ConstructConnectReplyFrame()); |
| spdy::SpdySerializedFrame msg1(ConstructBodyFrame(kMsg1, kLen1)); |
| spdy::SpdySerializedFrame msg3(ConstructBodyFrame(kMsg3, kLen3)); |
| MockRead reads[] = { |
| CreateMockRead(resp, 1, ASYNC), CreateMockRead(msg1, 2, ASYNC), |
| CreateMockRead(msg3, 3, ASYNC), MockRead(SYNCHRONOUS, ERR_IO_PENDING, 4), |
| }; |
| |
| Initialize(reads, writes); |
| |
| AssertConnectSucceeds(); |
| |
| AssertReadStarts(kMsg1, kLen1); |
| EXPECT_EQ(OK, sock_->CancelReadIfReady()); |
| |
| // Perform ReadIfReady again should succeed after cancelation. |
| AssertReadStarts(kMsg1, kLen1); |
| AssertReadReturns(kMsg1, kLen1); |
| AssertReadStarts(kMsg3, kLen3); |
| AssertReadReturns(kMsg3, kLen3); |
| |
| // Canceling ReadIfReady() when none is in progress is an no-op. |
| EXPECT_EQ(OK, sock_->CancelReadIfReady()); |
| } |
| |
| } // namespace net |