blob: adad8301b5586fa480902c5c10b77207f36f8058 [file] [log] [blame]
// 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