blob: 1ead815c41c719d6f9009d7266d1c5a48dcd30a6 [file] [log] [blame]
// Copyright 2013 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "net/socket/tcp_socket.h"
#include <stddef.h>
#include <string.h>
#include <algorithm>
#include <memory>
#include <string>
#include <vector>
#include "base/containers/span.h"
#include "base/functional/bind.h"
#include "base/memory/ref_counted.h"
#include "base/test/bind.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/test_timeouts.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "net/base/address_list.h"
#include "net/base/features.h"
#include "net/base/io_buffer.h"
#include "net/base/ip_endpoint.h"
#include "net/base/net_errors.h"
#include "net/base/sockaddr_storage.h"
#include "net/base/sys_addrinfo.h"
#include "net/base/test_completion_callback.h"
#include "net/log/net_log_source.h"
#include "net/socket/socket_descriptor.h"
#include "net/socket/socket_performance_watcher.h"
#include "net/socket/socket_test_util.h"
#include "net/socket/tcp_client_socket.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "net/test/gtest_util.h"
#include "net/test/test_with_task_environment.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"
#if BUILDFLAG(IS_ANDROID)
#include "net/android/network_change_notifier_factory_android.h"
#include "net/base/network_change_notifier.h"
#endif // BUILDFLAG(IS_ANDROID)
// For getsockopt() call.
#if BUILDFLAG(IS_WIN)
#include <winsock2.h>
#include "net/socket/tcp_socket_io_completion_port_win.h"
#else // !BUILDFLAG(IS_WIN)
#include <sys/socket.h>
#endif // !BUILDFLAG(IS_WIN)
using net::test::IsError;
using net::test::IsOk;
namespace net {
namespace {
// IOBuffer with the ability to invoke a callback when destroyed. Useful for
// checking for leaks.
class IOBufferWithDestructionCallback : public IOBufferWithSize {
public:
explicit IOBufferWithDestructionCallback(base::OnceClosure on_destroy_closure)
: IOBufferWithSize(1024),
on_destroy_closure_(std::move(on_destroy_closure)) {
DCHECK(on_destroy_closure_);
}
protected:
~IOBufferWithDestructionCallback() override {
std::move(on_destroy_closure_).Run();
}
base::OnceClosure on_destroy_closure_;
};
class TestSocketPerformanceWatcher : public SocketPerformanceWatcher {
public:
explicit TestSocketPerformanceWatcher(bool should_notify_updated_rtt)
: should_notify_updated_rtt_(should_notify_updated_rtt) {}
TestSocketPerformanceWatcher(const TestSocketPerformanceWatcher&) = delete;
TestSocketPerformanceWatcher& operator=(const TestSocketPerformanceWatcher&) =
delete;
~TestSocketPerformanceWatcher() override = default;
bool ShouldNotifyUpdatedRTT() const override {
return should_notify_updated_rtt_;
}
void OnUpdatedRTTAvailable(const base::TimeDelta& rtt) override {
rtt_notification_count_++;
}
void OnConnectionChanged() override { connection_changed_count_++; }
size_t rtt_notification_count() const { return rtt_notification_count_; }
size_t connection_changed_count() const { return connection_changed_count_; }
private:
const bool should_notify_updated_rtt_;
size_t connection_changed_count_ = 0u;
size_t rtt_notification_count_ = 0u;
};
const int kListenBacklog = 5;
class TCPSocketTest
: public PlatformTest,
public WithTaskEnvironment,
// The params indicate whether the
// "TcpSocketIoCompletionPortWin" feature is enabled and
// whether we should use Read() or ReadIfReady() to read
// the data.
public testing::WithParamInterface<std::tuple<bool, bool>> {
protected:
TCPSocketTest() {
#if BUILDFLAG(IS_WIN)
scoped_feature_list_.InitWithFeatureState(
features::kTcpSocketIoCompletionPortWin,
IsTcpSocketIoCompletionPortWinEnabled());
#else
CHECK(!std::get<0>(GetParam()));
#endif // BUILDFLAG(IS_WIN)
socket_ = TCPSocket::Create(nullptr, nullptr, NetLogSource());
}
#if BUILDFLAG(IS_WIN)
bool IsTcpSocketIoCompletionPortWinEnabled() {
return std::get<0>(GetParam());
}
#endif // BUILDFLAG(IS_WIN)
bool ShouldUseReadIfReady() { return std::get<1>(GetParam()); }
void SetUpListenIPv4() {
ASSERT_THAT(socket_->Open(ADDRESS_FAMILY_IPV4), IsOk());
ASSERT_THAT(socket_->Bind(IPEndPoint(IPAddress::IPv4Localhost(), 0)),
IsOk());
ASSERT_THAT(socket_->Listen(kListenBacklog), IsOk());
ASSERT_THAT(socket_->GetLocalAddress(&local_address_), IsOk());
}
void SetUpListenIPv6(bool* success) {
*success = false;
if (socket_->Open(ADDRESS_FAMILY_IPV6) != OK ||
socket_->Bind(IPEndPoint(IPAddress::IPv6Localhost(), 0)) != OK ||
socket_->Listen(kListenBacklog) != OK) {
LOG(ERROR) << "Failed to listen on ::1 - probably because IPv6 is "
"disabled. Skipping the test";
return;
}
ASSERT_THAT(socket_->GetLocalAddress(&local_address_), IsOk());
*success = true;
}
std::pair<std::unique_ptr<TCPSocket>, std::unique_ptr<TCPSocket>>
CreateIPv4SocketPair() {
TestCompletionCallback connect_callback;
std::unique_ptr<TCPSocket> connecting_socket =
TCPSocket::Create(nullptr, nullptr, NetLogSource());
int result = connecting_socket->Open(ADDRESS_FAMILY_IPV4);
EXPECT_THAT(result, IsOk());
int connect_result =
connecting_socket->Connect(local_address_, connect_callback.callback());
TestCompletionCallback accept_callback;
std::unique_ptr<TCPSocket> accepted_socket;
IPEndPoint accepted_address;
result = socket_->Accept(&accepted_socket, &accepted_address,
accept_callback.callback());
EXPECT_THAT(accept_callback.GetResult(result), IsOk());
CHECK(accepted_socket.get());
EXPECT_THAT(connect_callback.GetResult(connect_result), IsOk());
// Both sockets should be on the loopback network interface.
EXPECT_EQ(accepted_address.address(), local_address_.address());
return std::make_pair(std::move(connecting_socket),
std::move(accepted_socket));
}
void TestAcceptAsync() {
TestCompletionCallback accept_callback;
std::unique_ptr<TCPSocket> accepted_socket;
IPEndPoint accepted_address;
ASSERT_THAT(socket_->Accept(&accepted_socket, &accepted_address,
accept_callback.callback()),
IsError(ERR_IO_PENDING));
TestCompletionCallback connect_callback;
TCPClientSocket connecting_socket(local_address_list(), nullptr, nullptr,
nullptr, NetLogSource());
int connect_result = connecting_socket.Connect(connect_callback.callback());
EXPECT_THAT(connect_callback.GetResult(connect_result), IsOk());
EXPECT_THAT(accept_callback.WaitForResult(), IsOk());
EXPECT_TRUE(accepted_socket.get());
// Both sockets should be on the loopback network interface.
EXPECT_EQ(accepted_address.address(), local_address_.address());
}
#if defined(TCP_INFO) || BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
// Tests that notifications to Socket Performance Watcher (SPW) are delivered
// correctly. |should_notify_updated_rtt| is true if the SPW is interested in
// receiving RTT notifications. |num_messages| is the number of messages that
// are written/read by the sockets. |expect_connection_changed_count| is the
// expected number of connection change notifications received by the SPW.
// |expect_rtt_notification_count| is the expected number of RTT
// notifications received by the SPW. This test works by writing
// |num_messages| to the socket. A different socket (with a SPW attached to
// it) reads the messages.
void TestSPWNotifications(bool should_notify_updated_rtt,
size_t num_messages,
size_t expect_connection_changed_count,
size_t expect_rtt_notification_count) {
ASSERT_NO_FATAL_FAILURE(SetUpListenIPv4());
TestCompletionCallback connect_callback;
auto watcher = std::make_unique<TestSocketPerformanceWatcher>(
should_notify_updated_rtt);
TestSocketPerformanceWatcher* watcher_ptr = watcher.get();
std::unique_ptr<TCPSocket> connecting_socket =
TCPSocket::Create(std::move(watcher), nullptr, NetLogSource());
int result = connecting_socket->Open(ADDRESS_FAMILY_IPV4);
ASSERT_THAT(result, IsOk());
int connect_result =
connecting_socket->Connect(local_address_, connect_callback.callback());
TestCompletionCallback accept_callback;
std::unique_ptr<TCPSocket> accepted_socket;
IPEndPoint accepted_address;
result = socket_->Accept(&accepted_socket, &accepted_address,
accept_callback.callback());
ASSERT_THAT(accept_callback.GetResult(result), IsOk());
ASSERT_TRUE(accepted_socket.get());
// Both sockets should be on the loopback network interface.
EXPECT_EQ(accepted_address.address(), local_address_.address());
ASSERT_THAT(connect_callback.GetResult(connect_result), IsOk());
for (size_t i = 0; i < num_messages; ++i) {
// Use a 1 byte message so that the watcher is notified at most once per
// message.
static constexpr std::string_view message = "t";
auto write_buffer =
base::MakeRefCounted<VectorIOBuffer>(base::as_byte_span(message));
TestCompletionCallback write_callback;
int write_result = accepted_socket->Write(
write_buffer.get(), write_buffer->size(), write_callback.callback(),
TRAFFIC_ANNOTATION_FOR_TESTS);
scoped_refptr<IOBufferWithSize> read_buffer =
base::MakeRefCounted<IOBufferWithSize>(message.size());
TestCompletionCallback read_callback;
int read_result = connecting_socket->Read(
read_buffer.get(), read_buffer->size(), read_callback.callback());
ASSERT_EQ(1, write_callback.GetResult(write_result));
ASSERT_EQ(1, read_callback.GetResult(read_result));
}
EXPECT_EQ(expect_connection_changed_count,
watcher_ptr->connection_changed_count());
EXPECT_EQ(expect_rtt_notification_count,
watcher_ptr->rtt_notification_count());
}
#endif // defined(TCP_INFO) || BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
AddressList local_address_list() const {
return AddressList(local_address_);
}
// Helper function to read `expected_size` bytes from the accepting
// socket using ReadIfReady(). Data will be temporarily read into
// `read_buffer` and then accumulated in `received_data_buffer`. On
// successful return, `received_data` will be modified to refer to
// the read data. On failure, `received_data` will be unmodified.
// Correctly handles cases where data is split across multiple
// ReadIfReady() calls.
void ReadAllAvailableData(TCPSocket* connecting_socket,
TCPSocket* accepted_socket,
IOBufferWithSize* read_buffer,
base::span<uint8_t> received_data_buffer,
size_t expected_size,
base::span<const uint8_t>* received_data) {
ASSERT_LE(expected_size, received_data_buffer.size());
size_t total_received = 0;
while (total_received < expected_size) {
EXPECT_TRUE(connecting_socket->IsConnected());
EXPECT_TRUE(accepted_socket->IsConnected());
TestCompletionCallback read_callback;
int read_result = connecting_socket->ReadIfReady(
read_buffer, read_buffer->size(), read_callback.callback());
if (read_result == ERR_IO_PENDING) {
read_result = read_callback.GetResult(read_result);
ASSERT_EQ(read_result, 0);
read_result = connecting_socket->ReadIfReady(
read_buffer, read_buffer->size(), read_callback.callback());
ASSERT_GT(read_result, 0);
DVLOG(1) << "Got data in while loop. Size " << read_result;
}
if (read_result > 0) {
ASSERT_LE(total_received + read_result, expected_size);
received_data_buffer.subspan(total_received)
.copy_prefix_from(
read_buffer->first(static_cast<size_t>(read_result)));
total_received += read_result;
DVLOG(1) << "Copied data in while loop. Size " << total_received;
} else {
FAIL() << "**** Failed to read data using ReadIfReady(). Return: "
<< read_result << " connecting socket connected "
<< connecting_socket->IsConnected()
<< " Accepting socket connected "
<< accepted_socket->IsConnected();
}
}
*received_data = received_data_buffer.first(total_received);
}
base::test::ScopedFeatureList scoped_feature_list_;
std::unique_ptr<TCPSocket> socket_;
IPEndPoint local_address_;
};
// Test listening and accepting with a socket bound to an IPv4 address.
TEST_P(TCPSocketTest, Accept) {
ASSERT_NO_FATAL_FAILURE(SetUpListenIPv4());
TestCompletionCallback connect_callback;
// TODO(yzshen): Switch to use TCPSocket when it supports client socket
// operations.
TCPClientSocket connecting_socket(local_address_list(), nullptr, nullptr,
nullptr, NetLogSource());
int connect_result = connecting_socket.Connect(connect_callback.callback());
TestCompletionCallback accept_callback;
std::unique_ptr<TCPSocket> accepted_socket;
IPEndPoint accepted_address;
int result = socket_->Accept(&accepted_socket, &accepted_address,
accept_callback.callback());
ASSERT_THAT(accept_callback.GetResult(result), IsOk());
EXPECT_TRUE(accepted_socket.get());
// Both sockets should be on the loopback network interface.
EXPECT_EQ(accepted_address.address(), local_address_.address());
EXPECT_THAT(connect_callback.GetResult(connect_result), IsOk());
}
// Test Accept() callback.
TEST_P(TCPSocketTest, AcceptAsync) {
ASSERT_NO_FATAL_FAILURE(SetUpListenIPv4());
TestAcceptAsync();
}
// Test AdoptConnectedSocket()
TEST_P(TCPSocketTest, AdoptConnectedSocket) {
std::unique_ptr<TCPSocket> accepting_socket =
TCPSocket::Create(nullptr, nullptr, NetLogSource());
ASSERT_THAT(accepting_socket->Open(ADDRESS_FAMILY_IPV4), IsOk());
ASSERT_THAT(accepting_socket->Bind(IPEndPoint(IPAddress::IPv4Localhost(), 0)),
IsOk());
ASSERT_THAT(accepting_socket->GetLocalAddress(&local_address_), IsOk());
ASSERT_THAT(accepting_socket->Listen(kListenBacklog), IsOk());
TestCompletionCallback connect_callback;
// TODO(yzshen): Switch to use TCPSocket when it supports client socket
// operations.
TCPClientSocket connecting_socket(local_address_list(), nullptr, nullptr,
nullptr, NetLogSource());
int connect_result = connecting_socket.Connect(connect_callback.callback());
TestCompletionCallback accept_callback;
std::unique_ptr<TCPSocket> accepted_socket;
IPEndPoint accepted_address;
int result = accepting_socket->Accept(&accepted_socket, &accepted_address,
accept_callback.callback());
ASSERT_THAT(accept_callback.GetResult(result), IsOk());
SocketDescriptor accepted_descriptor =
accepted_socket->ReleaseSocketDescriptorForTesting();
ASSERT_THAT(
socket_->AdoptConnectedSocket(accepted_descriptor, accepted_address),
IsOk());
// socket_ should now have the local address.
IPEndPoint adopted_address;
ASSERT_THAT(socket_->GetLocalAddress(&adopted_address), IsOk());
EXPECT_EQ(local_address_.address(), adopted_address.address());
EXPECT_THAT(connect_callback.GetResult(connect_result), IsOk());
}
// Test Accept() for AdoptUnconnectedSocket.
TEST_P(TCPSocketTest, AcceptForAdoptedUnconnectedSocket) {
SocketDescriptor existing_socket =
CreatePlatformSocket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
ASSERT_THAT(socket_->AdoptUnconnectedSocket(existing_socket), IsOk());
IPEndPoint address(IPAddress::IPv4Localhost(), 0);
SockaddrStorage storage;
ASSERT_TRUE(address.ToSockAddr(storage.addr(), &storage.addr_len));
ASSERT_EQ(0, bind(existing_socket, storage.addr(), storage.addr_len));
ASSERT_THAT(socket_->Listen(kListenBacklog), IsOk());
ASSERT_THAT(socket_->GetLocalAddress(&local_address_), IsOk());
TestAcceptAsync();
}
// Accept two connections simultaneously.
TEST_P(TCPSocketTest, Accept2Connections) {
ASSERT_NO_FATAL_FAILURE(SetUpListenIPv4());
TestCompletionCallback accept_callback;
std::unique_ptr<TCPSocket> accepted_socket;
IPEndPoint accepted_address;
ASSERT_THAT(socket_->Accept(&accepted_socket, &accepted_address,
accept_callback.callback()),
IsError(ERR_IO_PENDING));
TestCompletionCallback connect_callback;
TCPClientSocket connecting_socket(local_address_list(), nullptr, nullptr,
nullptr, NetLogSource());
int connect_result = connecting_socket.Connect(connect_callback.callback());
TestCompletionCallback connect_callback2;
TCPClientSocket connecting_socket2(local_address_list(), nullptr, nullptr,
nullptr, NetLogSource());
int connect_result2 =
connecting_socket2.Connect(connect_callback2.callback());
EXPECT_THAT(accept_callback.WaitForResult(), IsOk());
TestCompletionCallback accept_callback2;
std::unique_ptr<TCPSocket> accepted_socket2;
IPEndPoint accepted_address2;
int result = socket_->Accept(&accepted_socket2, &accepted_address2,
accept_callback2.callback());
ASSERT_THAT(accept_callback2.GetResult(result), IsOk());
EXPECT_THAT(connect_callback.GetResult(connect_result), IsOk());
EXPECT_THAT(connect_callback2.GetResult(connect_result2), IsOk());
EXPECT_TRUE(accepted_socket.get());
EXPECT_TRUE(accepted_socket2.get());
EXPECT_NE(accepted_socket.get(), accepted_socket2.get());
EXPECT_EQ(accepted_address.address(), local_address_.address());
EXPECT_EQ(accepted_address2.address(), local_address_.address());
}
// Test listening and accepting with a socket bound to an IPv6 address.
TEST_P(TCPSocketTest, AcceptIPv6) {
bool initialized = false;
ASSERT_NO_FATAL_FAILURE(SetUpListenIPv6(&initialized));
if (!initialized)
return;
TestCompletionCallback connect_callback;
TCPClientSocket connecting_socket(local_address_list(), nullptr, nullptr,
nullptr, NetLogSource());
int connect_result = connecting_socket.Connect(connect_callback.callback());
TestCompletionCallback accept_callback;
std::unique_ptr<TCPSocket> accepted_socket;
IPEndPoint accepted_address;
int result = socket_->Accept(&accepted_socket, &accepted_address,
accept_callback.callback());
ASSERT_THAT(accept_callback.GetResult(result), IsOk());
EXPECT_TRUE(accepted_socket.get());
// Both sockets should be on the loopback network interface.
EXPECT_EQ(accepted_address.address(), local_address_.address());
EXPECT_THAT(connect_callback.GetResult(connect_result), IsOk());
}
namespace {
void TestReadWrite(std::unique_ptr<TCPSocket> socket1,
std::unique_ptr<TCPSocket> socket2) {
const std::string message("test message");
const auto drainable_write_buffer = base::MakeRefCounted<DrainableIOBuffer>(
base::MakeRefCounted<StringIOBuffer>(message), message.size());
while (drainable_write_buffer->BytesRemaining() > 0) {
TestCompletionCallback write_callback;
int write_result = socket1->Write(
drainable_write_buffer.get(), drainable_write_buffer->BytesRemaining(),
write_callback.callback(), TRAFFIC_ANNOTATION_FOR_TESTS);
write_result = write_callback.GetResult(write_result);
ASSERT_GE(write_result, 0);
ASSERT_LE(write_result, drainable_write_buffer->BytesRemaining());
drainable_write_buffer->DidConsume(write_result);
}
const auto read_buffer =
base::MakeRefCounted<IOBufferWithSize>(message.size());
const auto drainable_read_buffer =
base::MakeRefCounted<DrainableIOBuffer>(read_buffer, read_buffer->size());
while (drainable_read_buffer->BytesRemaining() > 0) {
TestCompletionCallback read_callback;
int read_result = socket2->Read(drainable_read_buffer.get(),
drainable_read_buffer->BytesRemaining(),
read_callback.callback());
read_result = read_callback.GetResult(read_result);
ASSERT_GE(read_result, 0);
ASSERT_LE(read_result, drainable_read_buffer->BytesRemaining());
drainable_read_buffer->DidConsume(read_result);
}
const std::string received_message(read_buffer->data(), read_buffer->size());
EXPECT_EQ(message, received_message);
}
} // namespace
TEST_P(TCPSocketTest, ReadWrite) {
ASSERT_NO_FATAL_FAILURE(SetUpListenIPv4());
auto [socket1, socket2] = CreateIPv4SocketPair();
TestReadWrite(std::move(socket1), std::move(socket2));
}
#if BUILDFLAG(IS_WIN)
// Same test as above, but exercises the code used when
// `FILE_SKIP_COMPLETION_PORT_ON_SUCCESS` is not supported.
TEST_P(TCPSocketTest, ReadWriteNoSkipCompletionPortOnSuccess) {
if (!IsTcpSocketIoCompletionPortWinEnabled()) {
// FILE_SKIP_COMPLETION_PORT_ON_SUCCESS is only used by
// `TcpSocketIoCompletionPortWin`.
return;
}
TcpSocketIoCompletionPortWin::DisableSkipCompletionPortOnSuccessForTesting
disable_skip_completion_port_on_success;
ASSERT_NO_FATAL_FAILURE(SetUpListenIPv4());
auto [socket1, socket2] = CreateIPv4SocketPair();
TestReadWrite(std::move(socket1), std::move(socket2));
}
#endif // BUILDFLAG(IS_WIN)
// Destroy a TCPSocket while there's a pending read, and make sure the read
// IOBuffer that the socket was holding on to is destroyed.
// See https://crbug.com/804868.
TEST_P(TCPSocketTest, DestroyWithPendingRead) {
ASSERT_NO_FATAL_FAILURE(SetUpListenIPv4());
auto [connecting_socket, accepted_socket] = CreateIPv4SocketPair();
// Try to read from the socket, but never write anything to the other end.
base::RunLoop run_loop;
scoped_refptr<IOBufferWithDestructionCallback> read_buffer(
base::MakeRefCounted<IOBufferWithDestructionCallback>(
run_loop.QuitClosure()));
TestCompletionCallback read_callback;
EXPECT_EQ(ERR_IO_PENDING,
connecting_socket->Read(read_buffer.get(), read_buffer->size(),
read_callback.callback()));
// Release the handle to the read buffer and destroy the socket. Make sure the
// read buffer is destroyed.
read_buffer = nullptr;
connecting_socket.reset();
run_loop.Run();
}
// Destroy a TCPSocket while there's a pending write, and make sure the write
// IOBuffer that the socket was holding on to is destroyed.
TEST_P(TCPSocketTest, DestroyWithPendingWrite) {
ASSERT_NO_FATAL_FAILURE(SetUpListenIPv4());
auto [connecting_socket, accepted_socket] = CreateIPv4SocketPair();
// Repeatedly write to the socket until an operation does not complete
// synchronously.
base::RunLoop run_loop;
scoped_refptr<IOBufferWithDestructionCallback> write_buffer(
base::MakeRefCounted<IOBufferWithDestructionCallback>(
run_loop.QuitClosure()));
std::ranges::fill(write_buffer->span(), '1');
TestCompletionCallback write_callback;
while (true) {
int result = connecting_socket->Write(
write_buffer.get(), write_buffer->size(), write_callback.callback(),
TRAFFIC_ANNOTATION_FOR_TESTS);
if (result == ERR_IO_PENDING)
break;
ASSERT_LT(0, result);
}
// Release the handle to the read buffer and destroy the socket. Make sure the
// write buffer is destroyed.
write_buffer = nullptr;
connecting_socket.reset();
run_loop.Run();
}
// If a ReadIfReady is pending, it's legal to cancel it and start reading later.
TEST_P(TCPSocketTest, CancelPendingReadIfReady) {
ASSERT_NO_FATAL_FAILURE(SetUpListenIPv4());
auto [connecting_socket, accepted_socket] = CreateIPv4SocketPair();
// Try to read from the socket, but never write anything to the other end.
base::RunLoop run_loop;
scoped_refptr<IOBufferWithDestructionCallback> read_buffer(
base::MakeRefCounted<IOBufferWithDestructionCallback>(
run_loop.QuitClosure()));
TestCompletionCallback read_callback;
int read_if_ready_result = connecting_socket->ReadIfReady(
read_buffer.get(), read_buffer->size(), read_callback.callback());
EXPECT_EQ(ERR_IO_PENDING, read_if_ready_result);
// Now cancel the pending ReadIfReady().
ASSERT_EQ(OK, connecting_socket->CancelReadIfReady());
// Send data to |connecting_socket|.
static constexpr base::cstring_view kMsg = "hello!";
scoped_refptr<IOBufferWithSize> write_buffer =
base::MakeRefCounted<IOBufferWithSize>(kMsg.size());
write_buffer->span().copy_from(base::as_byte_span(kMsg));
TestCompletionCallback write_callback;
int write_result = accepted_socket->Write(write_buffer.get(), kMsg.size(),
write_callback.callback(),
TRAFFIC_ANNOTATION_FOR_TESTS);
ASSERT_EQ(static_cast<int>(kMsg.size()), write_result);
TestCompletionCallback read_callback2;
int read_result = connecting_socket->ReadIfReady(
read_buffer.get(), read_buffer->size(), read_callback2.callback());
if (read_result == ERR_IO_PENDING) {
ASSERT_EQ(OK, read_callback2.GetResult(read_result));
read_result = connecting_socket->ReadIfReady(
read_buffer.get(), read_buffer->size(), read_callback2.callback());
}
ASSERT_EQ(static_cast<int>(kMsg.size()), read_result);
ASSERT_EQ(read_buffer->first(static_cast<size_t>(read_result)),
base::as_byte_span(kMsg));
}
// Validates that we can successfully read data from the underlying socket after
// cancelling a pending read request when data is available and issuing a new
// ReadIfReady()/Read() request.
TEST_P(TCPSocketTest, CancelReadRetainedBufferedData) {
ASSERT_NO_FATAL_FAILURE(SetUpListenIPv4());
auto [connecting_socket, accepted_socket] = CreateIPv4SocketPair();
connecting_socket->SetDefaultOptionsForClient();
accepted_socket->SetDefaultOptionsForClient();
// The test setup is as below.
// 1. First issue a ReadIfReady() call on the connecting socket. This will
// return ERR_IO_PENDING.
// 2. Write data from the accepted socket (other side).
// 3. Cancel the first read.
// 4. Write more data from the accepted socket.
// 5. Attempt to read the data via ReadIfReady().
std::array<uint8_t, 1024> received_data = {};
size_t received_data_size = 0;
size_t bytes_written = 0;
// Set up a read buffer and initiate a read.
scoped_refptr<IOBufferWithSize> read_buffer1 =
base::MakeRefCounted<IOBufferWithSize>(1024);
TestCompletionCallback read_callback1;
int read_result = connecting_socket->ReadIfReady(
read_buffer1.get(), read_buffer1->size(), read_callback1.callback());
ASSERT_EQ(ERR_IO_PENDING, read_result);
// Write data to the socket before cancelling the read operation.
constexpr std::string_view kMsg = "test_data.Part 1";
scoped_refptr<IOBufferWithSize> write_buffer =
base::MakeRefCounted<IOBufferWithSize>(kMsg.size());
write_buffer->span().copy_from(base::as_byte_span(kMsg));
TestCompletionCallback write_callback;
int write_result1 = accepted_socket->Write(write_buffer.get(), kMsg.size(),
write_callback.callback(),
TRAFFIC_ANNOTATION_FOR_TESTS);
ASSERT_EQ(static_cast<int>(kMsg.size()),
write_callback.GetResult(write_result1));
bytes_written += kMsg.size();
// Cancel the pending read operation.
connecting_socket->CancelReadIfReady();
// Handle the case where the ReadIfRead() operation completed before the
// CancelReadIfReady call.
if (read_callback1.have_result()) {
EXPECT_EQ(net::OK, read_callback1.GetResult(read_result));
DVLOG(1) << "Got data after CancelReadIfReady() " << received_data_size;
}
constexpr std::string_view kMsg2 = "test_data. Part 2";
scoped_refptr<IOBufferWithSize> write_buffer2 =
base::MakeRefCounted<IOBufferWithSize>(kMsg2.size());
write_buffer2->span().copy_from(base::as_byte_span(kMsg2));
TestCompletionCallback write_callback2;
int write_result2 = accepted_socket->Write(write_buffer2.get(), kMsg2.size(),
write_callback2.callback(),
TRAFFIC_ANNOTATION_FOR_TESTS);
ASSERT_EQ(static_cast<int>(kMsg2.size()),
write_callback2.GetResult(write_result2));
bytes_written += kMsg2.size();
// Set up a new buffer and initiate a read.
scoped_refptr<IOBufferWithSize> read_buffer2 =
base::MakeRefCounted<IOBufferWithSize>(1024);
// The data read off the socket is copied into this buffer.
base::span<const uint8_t> received_data_buffer;
// Use `span` for `received_data` directly.
ReadAllAvailableData(connecting_socket.get(), accepted_socket.get(),
read_buffer2.get(), received_data, bytes_written,
&received_data_buffer);
received_data_size = received_data_buffer.size();
// Validate that the read operation successfully retrieved the expected data.
ASSERT_EQ(bytes_written, received_data_size);
std::string expected_data = std::string(kMsg) + std::string(kMsg2);
ASSERT_EQ(base::as_bytes(base::span(expected_data)),
received_data_buffer.first(received_data_size));
}
// Validates behavior when a pending ReadIfRead() is canceled and the socket
// remains active for further operations. This test ensures that data written to
// the socket before and after a cancel operation is read successfully by
// subsequent ReadIfReady() requests.
//
// The test setup is as follows:
// 1. Issue an initial ReadIfReady on the connecting socket.
// This will return ERR_IO_PENDING as there is no data available initially.
// 2. Write data from the accepted socket (peer).
// 3. Call CancelReadIfReady() on the connecting socket to cancel the pending
// read operation. If the read callback has already run, it captures the
// available data.
// 4. Write additional data from the accepted socket.
// 5. Issue subsequent read requests to ensure that all data written to the
// socket is received correctly.
//
// Special Considerations:
// - For ReadIfReady, after the callback is invoked, the test ensures that
// another ReadIfReady call is made to retrieve the available data.
//
TEST_P(TCPSocketTest, CancelThenImmediateReadIfReady) {
ASSERT_NO_FATAL_FAILURE(SetUpListenIPv4());
auto [connecting_socket, accepted_socket] = CreateIPv4SocketPair();
connecting_socket->SetDefaultOptionsForClient();
accepted_socket->SetDefaultOptionsForClient();
// Set up a read buffer and initiate a ReadIfReady or Read.
scoped_refptr<IOBufferWithSize> read_buffer =
base::MakeRefCounted<IOBufferWithSize>(1024);
TestCompletionCallback read_callback;
int read_result = connecting_socket->ReadIfReady(
read_buffer.get(), read_buffer->size(), read_callback.callback());
ASSERT_EQ(ERR_IO_PENDING, read_result);
// Write data to the socket.
static constexpr base::cstring_view kMsg = "test_data";
scoped_refptr<IOBufferWithSize> write_buffer =
base::MakeRefCounted<IOBufferWithSize>(kMsg.size());
write_buffer->span().copy_from(base::as_byte_span(kMsg));
TestCompletionCallback write_callback;
ASSERT_EQ(static_cast<int>(kMsg.size()),
accepted_socket->Write(write_buffer.get(), kMsg.size(),
write_callback.callback(),
TRAFFIC_ANNOTATION_FOR_TESTS));
// Small sleep to allow the write to be propagated. In some environments, e.g
// IOS simulator, Fuchsia, we have seen cases where the write does not
// propagate through even though the Write call returns that data was written
// to the socket. The test relies on the CancelReadIfReady() call executing
// just around the time the data becomes available for read.
//
// We may need to rewrite this test in the event it becomes flaky.
base::PlatformThread::Sleep(TestTimeouts::tiny_timeout());
// Cancel the ReadIfReady() operation and immediately issue another
// ReadIfReady().
connecting_socket->CancelReadIfReady();
std::array<uint8_t, 1024> received_data = {};
// The data read off the socket is copied into this buffer.
base::span<const uint8_t> received_data_buffer;
// Read may not return the entire data in one call. Handle the case where
// we may get partial data.
ReadAllAvailableData(connecting_socket.get(), accepted_socket.get(),
read_buffer.get(), received_data, kMsg.size(),
&received_data_buffer);
// Validate that the data was read correctly.
ASSERT_EQ(kMsg.size(), received_data_buffer.size());
ASSERT_EQ(base::as_byte_span(kMsg),
received_data_buffer.first(received_data_buffer.size()));
}
// Validates that large amounts of data can be read correctly in multiple
// chunks with interspersed CancelReadIfReady() calls This test ensures that
// when a large payload is written to the socket, the reading side can retrieve
// the data in several smaller read operations without data loss or corruption.
// The test verifies both Read and ReadIfReady behavior when handling large data
// transfers with the Read/ReadIfReady() calls cancelled periodically.
//
// The test setup is as follows:
// 1. Generate a large data payload (e.g., 10 KB).
// 2. Write the payload from the accepted socket (peer) to the connecting
// socket in 2K chunks.
// 3. On the connecting socket, repeatedly call ReadIfReady or Read with the
// read chunk size (1KB) to read the data.
// 4. Issue CancelReadIfReady() calls after every 2K chunk written.
// 5. Continue reading until all data has been received.
// 6. Verify that the concatenated received data matches the original payload.
//
// Special Considerations:
// - For ReadIfReady, after each callback indicating data readiness, the test
// issues another ReadIfReady call to retrieve the next chunk.
TEST_P(TCPSocketTest, LargeDataReadWithCancelReadIfReady) {
ASSERT_NO_FATAL_FAILURE(SetUpListenIPv4());
auto [connecting_socket, accepted_socket] = CreateIPv4SocketPair();
connecting_socket->SetDefaultOptionsForClient();
accepted_socket->SetDefaultOptionsForClient();
// Generate a large data payload (10 KB of 'x').
constexpr size_t chunk_size = 2048; // Write in 2 KB chunks.
constexpr size_t read_chunk_size = 1024; // Read in 1 KB chunks.
constexpr size_t total_size = 10 * 1024; // 10 KB.
std::vector<uint8_t> large_data(total_size, 0u);
base::RandBytes(large_data);
std::vector<uint8_t> received_data(total_size, 0);
size_t received_data_size = 0;
// Iterate over chunks to write and read in parts.
for (size_t offset = 0; offset < total_size; offset += chunk_size) {
// Determine the size of the current chunk.
size_t current_chunk_size = std::min(chunk_size, total_size - offset);
scoped_refptr<IOBufferWithSize> write_buffer =
base::MakeRefCounted<IOBufferWithSize>(current_chunk_size);
// Copy the data to be sent out into the write buffer.
write_buffer->span()
.first(current_chunk_size)
.copy_from(
base::as_byte_span(large_data).subspan(offset, current_chunk_size));
// Issue a read before writing the chunk.
scoped_refptr<IOBufferWithSize> read_buffer =
base::MakeRefCounted<IOBufferWithSize>(read_chunk_size);
TestCompletionCallback read_callback;
int read_result = connecting_socket->ReadIfReady(
read_buffer.get(), read_buffer->size(), read_callback.callback());
ASSERT_EQ(ERR_IO_PENDING, read_result);
// Write the chunk to the socket.
TestCompletionCallback write_callback;
ASSERT_EQ(static_cast<int>(current_chunk_size),
accepted_socket->Write(write_buffer.get(), current_chunk_size,
write_callback.callback(),
TRAFFIC_ANNOTATION_FOR_TESTS));
// Small sleep to allow the write to be propagated.
base::PlatformThread::Sleep(TestTimeouts::tiny_timeout());
// Cancel the pending read above.
connecting_socket->CancelReadIfReady();
// The CancelReadIfReady() call above clears out the completion callback
// passed in by the caller. This also means that the caller won't receive
// a notification about available data in the socket after the
// CancelReadIfReady() call. We have a small Run loop below to give
// sufficient time for the IO completion to be signaled. Please note that
// this only applies to the Windows (IO completion based socket). Other
// implementations just stop listening for read completions.
base::RunLoop run_loop;
base::OneShotTimer timeout_timer;
timeout_timer.Start(FROM_HERE, TestTimeouts::tiny_timeout(),
base::BindOnce(&base::RunLoop::QuitWhenIdle,
base::Unretained(&run_loop)));
run_loop.Run();
// Read until the entire chunk is retrieved.
size_t chunk_received = 0;
while (chunk_received < current_chunk_size) {
TestCompletionCallback retry_read_callback;
read_result =
connecting_socket->ReadIfReady(read_buffer.get(), read_buffer->size(),
retry_read_callback.callback());
if (read_result == ERR_IO_PENDING) {
read_result = retry_read_callback.GetResult(read_result);
ASSERT_GE(read_result, 0);
read_result = connecting_socket->ReadIfReady(
read_buffer.get(), read_buffer->size(),
retry_read_callback.callback());
}
ASSERT_GT(read_result, 0);
// Append received data to the buffer using spans.
base::span<uint8_t>(received_data)
.subspan(received_data_size, static_cast<size_t>(read_result))
.copy_from(read_buffer->first(static_cast<size_t>(read_result)));
received_data_size += read_result;
chunk_received += read_result;
}
}
// Validate that all the data was read correctly.
ASSERT_EQ(base::span<const uint8_t>(received_data).first(received_data_size),
base::as_bytes(base::span(large_data)));
}
TEST_P(TCPSocketTest, ReadComplete) {
ASSERT_NO_FATAL_FAILURE(SetUpListenIPv4());
auto [connecting_socket, accepted_socket] = CreateIPv4SocketPair();
// Send data to |connecting_socket|.
static constexpr base::cstring_view kMsg = "hello!";
scoped_refptr<StringIOBuffer> write_buffer =
base::MakeRefCounted<StringIOBuffer>(std::string(kMsg));
TestCompletionCallback write_callback;
int write_result = accepted_socket->Write(write_buffer.get(), kMsg.size(),
write_callback.callback(),
TRAFFIC_ANNOTATION_FOR_TESTS);
ASSERT_EQ(static_cast<int>(kMsg.size()), write_result);
// ReadIfReady should read all of the data.
auto read_buffer = base::MakeRefCounted<IOBufferWithSize>(kMsg.size());
TestCompletionCallback read_callback;
int read_result = 0;
if (ShouldUseReadIfReady()) {
read_result = connecting_socket->ReadIfReady(
read_buffer.get(), read_buffer->size(), read_callback.callback());
if (read_result == ERR_IO_PENDING) {
ASSERT_EQ(OK, read_callback.GetResult(read_result));
read_result = connecting_socket->ReadIfReady(
read_buffer.get(), read_buffer->size(), read_callback.callback());
}
} else {
read_result = connecting_socket->Read(
read_buffer.get(), read_buffer->size(), read_callback.callback());
if (read_result == ERR_IO_PENDING) {
// For read calls issuing GetResult is enough as the data will be copied.
read_result = read_callback.GetResult(read_result);
ASSERT_GE(read_result, 0);
}
}
ASSERT_EQ(static_cast<int>(kMsg.size()), read_result);
ASSERT_EQ(base::span(kMsg), read_buffer->span());
}
// This test is similar to ReadComplete, but with a larger buffer size than the
// available data.
TEST_P(TCPSocketTest, ReadBiggerRead) {
ASSERT_NO_FATAL_FAILURE(SetUpListenIPv4());
auto [connecting_socket, accepted_socket] = CreateIPv4SocketPair();
// Send data to |connecting_socket|.
static constexpr base::cstring_view kMsg = "hello!";
scoped_refptr<StringIOBuffer> write_buffer =
base::MakeRefCounted<StringIOBuffer>(std::string(kMsg));
TestCompletionCallback write_callback;
int write_result = accepted_socket->Write(write_buffer.get(), kMsg.size(),
write_callback.callback(),
TRAFFIC_ANNOTATION_FOR_TESTS);
ASSERT_EQ(static_cast<int>(kMsg.size()), write_result);
// Read/ReadIfReady should read all of the data.
auto read_buffer = base::MakeRefCounted<IOBufferWithSize>(kMsg.size() * 2);
TestCompletionCallback read_callback;
int read_result = 0;
if (ShouldUseReadIfReady()) {
read_result = connecting_socket->ReadIfReady(
read_buffer.get(), read_buffer->size(), read_callback.callback());
if (read_result == ERR_IO_PENDING) {
ASSERT_EQ(OK, read_callback.GetResult(read_result));
read_result = connecting_socket->ReadIfReady(
read_buffer.get(), read_buffer->size(), read_callback.callback());
}
} else {
read_result = connecting_socket->Read(
read_buffer.get(), read_buffer->size(), read_callback.callback());
if (read_result == ERR_IO_PENDING) {
// For read calls issuing GetResult is enough as the data will be copied.
read_result = read_callback.GetResult(read_result);
ASSERT_GE(read_result, 0);
}
}
ASSERT_EQ(static_cast<int>(kMsg.size()), read_result);
ASSERT_EQ(base::span(kMsg),
read_buffer->first(static_cast<uint8_t>(read_result)));
}
// This test is similar to ReadComplete, but with a smaller buffer size than the
// available data.
TEST_P(TCPSocketTest, ReadSmallerRead) {
ASSERT_NO_FATAL_FAILURE(SetUpListenIPv4());
auto [connecting_socket, accepted_socket] = CreateIPv4SocketPair();
// Send data to |connecting_socket|.
static constexpr base::cstring_view kMsg = "hello!";
scoped_refptr<StringIOBuffer> write_buffer =
base::MakeRefCounted<StringIOBuffer>(std::string(kMsg));
TestCompletionCallback write_callback;
int write_result = accepted_socket->Write(write_buffer.get(), kMsg.size(),
write_callback.callback(),
TRAFFIC_ANNOTATION_FOR_TESTS);
ASSERT_EQ(static_cast<int>(kMsg.size()), write_result);
// Read/ReadIfReady should read all of the data.
auto read_buffer = base::MakeRefCounted<IOBufferWithSize>(kMsg.size() / 2);
TestCompletionCallback read_callback;
int read_result = 0;
if (ShouldUseReadIfReady()) {
read_result = connecting_socket->ReadIfReady(
read_buffer.get(), read_buffer->size(), read_callback.callback());
if (read_result == ERR_IO_PENDING) {
ASSERT_EQ(OK, read_callback.GetResult(read_result));
read_result = connecting_socket->ReadIfReady(
read_buffer.get(), read_buffer->size(), read_callback.callback());
}
} else {
read_result = connecting_socket->Read(
read_buffer.get(), read_buffer->size(), read_callback.callback());
if (read_result == ERR_IO_PENDING) {
// For read calls issuing GetResult is enough as the data will be copied.
read_result = read_callback.GetResult(read_result);
ASSERT_GE(read_result, 0);
}
}
ASSERT_EQ(static_cast<int>(kMsg.size()) / 2, read_result);
ASSERT_EQ(base::span(kMsg).first(static_cast<uint8_t>(read_result)),
read_buffer->span());
}
TEST_P(TCPSocketTest, ReadNoDataThenClose) {
ASSERT_NO_FATAL_FAILURE(SetUpListenIPv4());
auto [connecting_socket, accepted_socket] = CreateIPv4SocketPair();
static constexpr base::cstring_view kMsg = "hello!";
// Read/ReadIfReady should report that a read is pending if there's no data
// yet.
auto read_buffer = base::MakeRefCounted<IOBufferWithSize>(kMsg.size());
TestCompletionCallback read_callback;
int read_result = 0;
if (ShouldUseReadIfReady()) {
read_result = connecting_socket->ReadIfReady(
read_buffer.get(), read_buffer->size(), read_callback.callback());
} else {
read_result = connecting_socket->Read(
read_buffer.get(), read_buffer->size(), read_callback.callback());
}
ASSERT_EQ(ERR_IO_PENDING, read_result);
// Gracefully close the connection.
accepted_socket->Close();
accepted_socket.reset();
read_result = read_callback.WaitForResult();
ASSERT_EQ(0, read_result);
}
TEST_P(TCPSocketTest, ReadDataAfter) {
ASSERT_NO_FATAL_FAILURE(SetUpListenIPv4());
auto [connecting_socket, accepted_socket] = CreateIPv4SocketPair();
static constexpr base::cstring_view kMsg = "hello!";
// ReadIfReady should report that a read is pending if there's no data yet.
auto read_buffer = base::MakeRefCounted<IOBufferWithSize>(kMsg.size());
TestCompletionCallback read_callback;
int read_result = 0;
if (ShouldUseReadIfReady()) {
read_result = connecting_socket->ReadIfReady(
read_buffer.get(), read_buffer->size(), read_callback.callback());
} else {
read_result = connecting_socket->Read(
read_buffer.get(), read_buffer->size(), read_callback.callback());
}
ASSERT_EQ(ERR_IO_PENDING, read_result);
ASSERT_FALSE(read_callback.have_result());
// Send data to |connecting_socket|.
scoped_refptr<StringIOBuffer> write_buffer =
base::MakeRefCounted<StringIOBuffer>(std::string(kMsg));
TestCompletionCallback write_callback;
int write_result = accepted_socket->Write(write_buffer.get(), kMsg.size(),
write_callback.callback(),
TRAFFIC_ANNOTATION_FOR_TESTS);
ASSERT_EQ(static_cast<int>(kMsg.size()), write_result);
read_result = read_callback.WaitForResult();
if (ShouldUseReadIfReady()) {
ASSERT_EQ(OK, read_result);
// Now the data can be read.
read_result = connecting_socket->ReadIfReady(
read_buffer.get(), read_buffer->size(), read_callback.callback());
} else {
// For read calls issuing GetResult is enough as the data will be copied.
ASSERT_GE(read_result, 0);
}
ASSERT_EQ(static_cast<int>(kMsg.size()), read_result);
ASSERT_EQ(base::span(kMsg), read_buffer->span());
}
TEST_P(TCPSocketTest, IsConnected) {
ASSERT_NO_FATAL_FAILURE(SetUpListenIPv4());
TestCompletionCallback accept_callback;
std::unique_ptr<TCPSocket> accepted_socket;
IPEndPoint accepted_address;
EXPECT_THAT(socket_->Accept(&accepted_socket, &accepted_address,
accept_callback.callback()),
IsError(ERR_IO_PENDING));
TestCompletionCallback connect_callback;
TCPClientSocket connecting_socket(local_address_list(), nullptr, nullptr,
nullptr, NetLogSource());
// Immediately after creation, the socket should not be connected.
EXPECT_FALSE(connecting_socket.IsConnected());
EXPECT_FALSE(connecting_socket.IsConnectedAndIdle());
int connect_result = connecting_socket.Connect(connect_callback.callback());
EXPECT_THAT(accept_callback.WaitForResult(), IsOk());
EXPECT_THAT(connect_callback.GetResult(connect_result), IsOk());
// |connecting_socket| and |accepted_socket| should now both be reported as
// connected, and idle
EXPECT_TRUE(accepted_socket->IsConnected());
EXPECT_TRUE(accepted_socket->IsConnectedAndIdle());
EXPECT_TRUE(connecting_socket.IsConnected());
EXPECT_TRUE(connecting_socket.IsConnectedAndIdle());
// Write one byte to the |accepted_socket|, then close it.
const char kSomeData[] = "!";
scoped_refptr<IOBuffer> some_data_buffer =
base::MakeRefCounted<StringIOBuffer>(kSomeData);
TestCompletionCallback write_callback;
EXPECT_THAT(write_callback.GetResult(accepted_socket->Write(
some_data_buffer.get(), 1, write_callback.callback(),
TRAFFIC_ANNOTATION_FOR_TESTS)),
1);
accepted_socket.reset();
// Wait until |connecting_socket| is signalled as having data to read.
fd_set read_fds;
// SAFETY: The implementations are different on different platforms. However,
// they are all operations on the read_fds object itself. No out-of-bounds
// behavior will occur.
UNSAFE_BUFFERS(FD_ZERO(&read_fds));
SocketDescriptor connecting_fd =
connecting_socket.SocketDescriptorForTesting();
// SAFETY: There is an fd array in read_fds, which has different sizes on
// different platforms, but is always greater than 1. It will record and check
// the number of current fds internally. Since the memory layout and structure
// of different platforms are inconsistent, we still use the system standard
// interface.
UNSAFE_BUFFERS(FD_SET(connecting_fd, &read_fds));
ASSERT_EQ(select(FD_SETSIZE, &read_fds, nullptr, nullptr, nullptr), 1);
// SAFETY: Check if this fd exists in read_fds. The system has already handled
// the edge cases.
ASSERT_TRUE(UNSAFE_BUFFERS(FD_ISSET(connecting_fd, &read_fds)));
// It should now be reported as connected, but not as idle.
EXPECT_TRUE(connecting_socket.IsConnected());
EXPECT_FALSE(connecting_socket.IsConnectedAndIdle());
// Read the message from |connecting_socket_|, then read the end-of-stream.
scoped_refptr<IOBufferWithSize> read_buffer =
base::MakeRefCounted<IOBufferWithSize>(2);
TestCompletionCallback read_callback;
EXPECT_THAT(
read_callback.GetResult(connecting_socket.Read(
read_buffer.get(), read_buffer->size(), read_callback.callback())),
1);
EXPECT_THAT(
read_callback.GetResult(connecting_socket.Read(
read_buffer.get(), read_buffer->size(), read_callback.callback())),
0);
// |connecting_socket| has no more data to read, so should noe be reported
// as disconnected.
EXPECT_FALSE(connecting_socket.IsConnected());
EXPECT_FALSE(connecting_socket.IsConnectedAndIdle());
}
// Tests that setting a socket option in the BeforeConnectCallback works. With
// real sockets, socket options often have to be set before the connect() call,
// and the BeforeConnectCallback is the only way to do that, with a
// TCPClientSocket.
TEST_P(TCPSocketTest, BeforeConnectCallback) {
// A receive buffer size that is between max and minimum buffer size limits,
// and weird enough to likely not be a default value.
const int kReceiveBufferSize = 32 * 1024 + 1117;
ASSERT_NO_FATAL_FAILURE(SetUpListenIPv4());
TestCompletionCallback accept_callback;
std::unique_ptr<TCPSocket> accepted_socket;
IPEndPoint accepted_address;
EXPECT_THAT(socket_->Accept(&accepted_socket, &accepted_address,
accept_callback.callback()),
IsError(ERR_IO_PENDING));
TestCompletionCallback connect_callback;
TCPClientSocket connecting_socket(local_address_list(), nullptr, nullptr,
nullptr, NetLogSource());
connecting_socket.SetBeforeConnectCallback(base::BindLambdaForTesting([&] {
EXPECT_FALSE(connecting_socket.IsConnected());
int result = connecting_socket.SetReceiveBufferSize(kReceiveBufferSize);
EXPECT_THAT(result, IsOk());
return result;
}));
int connect_result = connecting_socket.Connect(connect_callback.callback());
EXPECT_THAT(accept_callback.WaitForResult(), IsOk());
EXPECT_THAT(connect_callback.GetResult(connect_result), IsOk());
int actual_size = 0;
socklen_t actual_size_len = sizeof(actual_size);
int os_result = getsockopt(
connecting_socket.SocketDescriptorForTesting(), SOL_SOCKET, SO_RCVBUF,
reinterpret_cast<char*>(&actual_size), &actual_size_len);
ASSERT_EQ(0, os_result);
// Linux platforms generally allocate twice as much buffer size is requested to
// account for internal kernel data structures.
#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_ANDROID)
EXPECT_EQ(2 * kReceiveBufferSize, actual_size);
// Unfortunately, Apple platform behavior doesn't seem to be documented, and
// doesn't match behavior on any other platforms.
// Fuchsia doesn't currently implement SO_RCVBUF.
#elif !BUILDFLAG(IS_APPLE) && !BUILDFLAG(IS_FUCHSIA)
EXPECT_EQ(kReceiveBufferSize, actual_size);
#endif
}
TEST_P(TCPSocketTest, BeforeConnectCallbackFails) {
// Setting up a server isn't strictly necessary, but it does allow checking
// the server was never connected to.
ASSERT_NO_FATAL_FAILURE(SetUpListenIPv4());
TestCompletionCallback accept_callback;
std::unique_ptr<TCPSocket> accepted_socket;
IPEndPoint accepted_address;
EXPECT_THAT(socket_->Accept(&accepted_socket, &accepted_address,
accept_callback.callback()),
IsError(ERR_IO_PENDING));
TestCompletionCallback connect_callback;
TCPClientSocket connecting_socket(local_address_list(), nullptr, nullptr,
nullptr, NetLogSource());
// Set a callback that returns a nonsensical error, and make sure it's
// returned.
connecting_socket.SetBeforeConnectCallback(base::BindRepeating(
[] { return static_cast<int>(net::ERR_NAME_NOT_RESOLVED); }));
int connect_result = connecting_socket.Connect(connect_callback.callback());
EXPECT_THAT(connect_callback.GetResult(connect_result),
IsError(net::ERR_NAME_NOT_RESOLVED));
// Best effort check that the socket wasn't accepted - may flakily pass on
// regression, unfortunately.
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(accept_callback.have_result());
}
TEST_P(TCPSocketTest, SetKeepAlive) {
ASSERT_NO_FATAL_FAILURE(SetUpListenIPv4());
TestCompletionCallback accept_callback;
std::unique_ptr<TCPSocket> accepted_socket;
IPEndPoint accepted_address;
EXPECT_THAT(socket_->Accept(&accepted_socket, &accepted_address,
accept_callback.callback()),
IsError(ERR_IO_PENDING));
TestCompletionCallback connect_callback;
TCPClientSocket connecting_socket(local_address_list(), nullptr, nullptr,
nullptr, NetLogSource());
// Non-connected sockets should not be able to set KeepAlive.
ASSERT_FALSE(connecting_socket.IsConnected());
EXPECT_FALSE(
connecting_socket.SetKeepAlive(true /* enable */, 14 /* delay */));
// Connect.
int connect_result = connecting_socket.Connect(connect_callback.callback());
EXPECT_THAT(accept_callback.WaitForResult(), IsOk());
EXPECT_THAT(connect_callback.GetResult(connect_result), IsOk());
// Connected sockets should be able to enable and disable KeepAlive.
ASSERT_TRUE(connecting_socket.IsConnected());
EXPECT_TRUE(
connecting_socket.SetKeepAlive(true /* enable */, 22 /* delay */));
EXPECT_TRUE(
connecting_socket.SetKeepAlive(false /* enable */, 3 /* delay */));
}
TEST_P(TCPSocketTest, SetNoDelay) {
ASSERT_NO_FATAL_FAILURE(SetUpListenIPv4());
TestCompletionCallback accept_callback;
std::unique_ptr<TCPSocket> accepted_socket;
IPEndPoint accepted_address;
EXPECT_THAT(socket_->Accept(&accepted_socket, &accepted_address,
accept_callback.callback()),
IsError(ERR_IO_PENDING));
TestCompletionCallback connect_callback;
TCPClientSocket connecting_socket(local_address_list(), nullptr, nullptr,
nullptr, NetLogSource());
// Non-connected sockets should not be able to set NoDelay.
ASSERT_FALSE(connecting_socket.IsConnected());
EXPECT_FALSE(connecting_socket.SetNoDelay(true /* no_delay */));
// Connect.
int connect_result = connecting_socket.Connect(connect_callback.callback());
EXPECT_THAT(accept_callback.WaitForResult(), IsOk());
EXPECT_THAT(connect_callback.GetResult(connect_result), IsOk());
// Connected sockets should be able to enable and disable NoDelay.
ASSERT_TRUE(connecting_socket.IsConnected());
EXPECT_TRUE(connecting_socket.SetNoDelay(true /* no_delay */));
EXPECT_TRUE(connecting_socket.SetNoDelay(false /* no_delay */));
}
// These tests require kernel support for tcp_info struct, and so they are
// enabled only on certain platforms.
#if defined(TCP_INFO) || BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
// If SocketPerformanceWatcher::ShouldNotifyUpdatedRTT always returns false,
// then the wtatcher should not receive any notifications.
TEST_P(TCPSocketTest, SPWNotInterested) {
TestSPWNotifications(false, 2u, 0u, 0u);
}
// One notification should be received when the socket connects. One
// additional notification should be received for each message read.
TEST_P(TCPSocketTest, SPWNoAdvance) {
TestSPWNotifications(true, 2u, 0u, 3u);
}
#endif // defined(TCP_INFO) || BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
// On Android, where socket tagging is supported, verify that TCPSocket::Tag
// works as expected.
#if BUILDFLAG(IS_ANDROID)
TEST_P(TCPSocketTest, Tag) {
if (!CanGetTaggedBytes()) {
DVLOG(0) << "Skipping test - GetTaggedBytes unsupported.";
return;
}
// Start test server.
EmbeddedTestServer test_server;
test_server.AddDefaultHandlers(base::FilePath());
ASSERT_TRUE(test_server.Start());
AddressList addr_list;
ASSERT_TRUE(test_server.GetAddressList(&addr_list));
EXPECT_EQ(socket_->Open(addr_list[0].GetFamily()), OK);
// Verify TCP connect packets are tagged and counted properly.
int32_t tag_val1 = 0x12345678;
uint64_t old_traffic = GetTaggedBytes(tag_val1);
SocketTag tag1(SocketTag::UNSET_UID, tag_val1);
socket_->ApplySocketTag(tag1);
TestCompletionCallback connect_callback;
int connect_result =
socket_->Connect(addr_list[0], connect_callback.callback());
EXPECT_THAT(connect_callback.GetResult(connect_result), IsOk());
EXPECT_GT(GetTaggedBytes(tag_val1), old_traffic);
// Verify socket can be retagged with a new value and the current process's
// UID.
int32_t tag_val2 = 0x87654321;
old_traffic = GetTaggedBytes(tag_val2);
SocketTag tag2(getuid(), tag_val2);
socket_->ApplySocketTag(tag2);
const char kRequest1[] = "GET / HTTP/1.0";
scoped_refptr<IOBuffer> write_buffer1 =
base::MakeRefCounted<StringIOBuffer>(kRequest1);
TestCompletionCallback write_callback1;
EXPECT_EQ(
socket_->Write(write_buffer1.get(), strlen(kRequest1),
write_callback1.callback(), TRAFFIC_ANNOTATION_FOR_TESTS),
static_cast<int>(strlen(kRequest1)));
EXPECT_GT(GetTaggedBytes(tag_val2), old_traffic);
// Verify socket can be retagged with a new value and the current process's
// UID.
old_traffic = GetTaggedBytes(tag_val1);
socket_->ApplySocketTag(tag1);
const char kRequest2[] = "\n\n";
scoped_refptr<IOBuffer> write_buffer2 =
base::MakeRefCounted<StringIOBuffer>(kRequest2);
TestCompletionCallback write_callback2;
EXPECT_EQ(
socket_->Write(write_buffer2.get(), strlen(kRequest2),
write_callback2.callback(), TRAFFIC_ANNOTATION_FOR_TESTS),
static_cast<int>(strlen(kRequest2)));
EXPECT_GT(GetTaggedBytes(tag_val1), old_traffic);
socket_->Close();
}
TEST_P(TCPSocketTest, TagAfterConnect) {
if (!CanGetTaggedBytes()) {
DVLOG(0) << "Skipping test - GetTaggedBytes unsupported.";
return;
}
// Start test server.
EmbeddedTestServer test_server;
test_server.AddDefaultHandlers(base::FilePath());
ASSERT_TRUE(test_server.Start());
AddressList addr_list;
ASSERT_TRUE(test_server.GetAddressList(&addr_list));
EXPECT_EQ(socket_->Open(addr_list[0].GetFamily()), OK);
// Connect socket.
TestCompletionCallback connect_callback;
int connect_result =
socket_->Connect(addr_list[0], connect_callback.callback());
EXPECT_THAT(connect_callback.GetResult(connect_result), IsOk());
// Verify socket can be tagged with a new value and the current process's
// UID.
int32_t tag_val2 = 0x87654321;
uint64_t old_traffic = GetTaggedBytes(tag_val2);
SocketTag tag2(getuid(), tag_val2);
socket_->ApplySocketTag(tag2);
const char kRequest1[] = "GET / HTTP/1.0";
scoped_refptr<IOBuffer> write_buffer1 =
base::MakeRefCounted<StringIOBuffer>(kRequest1);
TestCompletionCallback write_callback1;
EXPECT_EQ(
socket_->Write(write_buffer1.get(), strlen(kRequest1),
write_callback1.callback(), TRAFFIC_ANNOTATION_FOR_TESTS),
static_cast<int>(strlen(kRequest1)));
EXPECT_GT(GetTaggedBytes(tag_val2), old_traffic);
// Verify socket can be retagged with a new value and the current process's
// UID.
int32_t tag_val1 = 0x12345678;
old_traffic = GetTaggedBytes(tag_val1);
SocketTag tag1(SocketTag::UNSET_UID, tag_val1);
socket_->ApplySocketTag(tag1);
const char kRequest2[] = "\n\n";
scoped_refptr<IOBuffer> write_buffer2 =
base::MakeRefCounted<StringIOBuffer>(kRequest2);
TestCompletionCallback write_callback2;
EXPECT_EQ(
socket_->Write(write_buffer2.get(), strlen(kRequest2),
write_callback2.callback(), TRAFFIC_ANNOTATION_FOR_TESTS),
static_cast<int>(strlen(kRequest2)));
EXPECT_GT(GetTaggedBytes(tag_val1), old_traffic);
socket_->Close();
}
TEST_P(TCPSocketTest, BindToNetwork) {
NetworkChangeNotifierFactoryAndroid ncn_factory;
NetworkChangeNotifier::DisableForTest ncn_disable_for_test;
std::unique_ptr<NetworkChangeNotifier> ncn(ncn_factory.CreateInstance());
if (!NetworkChangeNotifier::AreNetworkHandlesSupported())
GTEST_SKIP() << "Network handles are required to test BindToNetwork.";
const handles::NetworkHandle wrong_network_handle = 65536;
// Try binding to this IP to trigger the underlying BindToNetwork call.
const IPEndPoint ip(IPAddress::IPv4Localhost(), 0);
// TestCompletionCallback connect_callback;
TCPClientSocket wrong_socket(local_address_list(), nullptr, nullptr, nullptr,
NetLogSource(), wrong_network_handle);
// Different Android versions might report different errors. Hence, just check
// what shouldn't happen.
int rv = wrong_socket.Bind(ip);
EXPECT_NE(OK, rv);
EXPECT_NE(ERR_NOT_IMPLEMENTED, rv);
// Connecting using an existing network should succeed.
const handles::NetworkHandle network_handle =
NetworkChangeNotifier::GetDefaultNetwork();
if (network_handle != handles::kInvalidNetworkHandle) {
TCPClientSocket correct_socket(local_address_list(), nullptr, nullptr,
nullptr, NetLogSource(), network_handle);
EXPECT_EQ(OK, correct_socket.Bind(ip));
}
}
#endif // BUILDFLAG(IS_ANDROID)
// Tests error handling in write.
TEST_P(TCPSocketTest, WriteError) {
ASSERT_NO_FATAL_FAILURE(SetUpListenIPv4());
auto [socket1, socket2] = CreateIPv4SocketPair();
// Disallow send operations to make the next `Write` fail.
#if BUILDFLAG(IS_WIN)
shutdown(socket1->SocketDescriptorForTesting(), SD_SEND);
#else
shutdown(socket1->SocketDescriptorForTesting(), SHUT_WR);
#endif
// Attempt to write data. It should fail.
TestCompletionCallback write_callback;
auto buffer = base::MakeRefCounted<StringIOBuffer>("test");
int write_result =
socket1->Write(buffer.get(), buffer->size(), write_callback.callback(),
TRAFFIC_ANNOTATION_FOR_TESTS);
EXPECT_NE(write_result, net::OK);
write_callback.GetResult(write_result);
}
// Tests error handling in read.
TEST_P(TCPSocketTest, ReadError) {
ASSERT_NO_FATAL_FAILURE(SetUpListenIPv4());
auto [socket1, socket2] = CreateIPv4SocketPair();
// Disallow receive operations to make the next `Read` fail.
#if BUILDFLAG(IS_WIN)
shutdown(socket1->SocketDescriptorForTesting(), SD_RECEIVE);
#else
shutdown(socket1->SocketDescriptorForTesting(), SHUT_RD);
#endif
// Attempt to read data. It should fail.
TestCompletionCallback read_callback;
auto buffer = base::MakeRefCounted<IOBufferWithSize>(10);
int read_result =
socket1->Read(buffer.get(), buffer->size(), read_callback.callback());
#if BUILDFLAG(IS_WIN)
EXPECT_EQ(read_result, net::ERR_FAILED);
#else
// Ideally, this test should make the read return a failure code.
// Unfortunately, we haven't found a good way to do that.
EXPECT_EQ(read_result, net::OK);
#endif
read_callback.GetResult(read_result);
}
// Tests error in a read that returns `net::ERR_IO_PENDING`.
TEST_P(TCPSocketTest, PendingReadError) {
#if BUILDFLAG(IS_WIN)
if (!IsTcpSocketIoCompletionPortWinEnabled()) {
// With the default implementation, the read callback is not invoked after
// `CloseSocketDescriptorForTesting()` is invoked.
return;
}
#endif // BUILDFLAG(IS_WIN)
ASSERT_NO_FATAL_FAILURE(SetUpListenIPv4());
auto [socket1, socket2] = CreateIPv4SocketPair();
// Attempt to read data. It should return `net::ERR_IO_PENDING` because no
// data was written at the other end yet.
TestCompletionCallback read_callback;
auto buffer = base::MakeRefCounted<IOBufferWithSize>(10);
int read_result =
socket1->Read(buffer.get(), buffer->size(), read_callback.callback());
EXPECT_EQ(read_result, net::ERR_IO_PENDING);
#if BUILDFLAG(IS_WIN)
// Close the underlying socket to make the pending read fail.
socket1->CloseSocketDescriptorForTesting();
#else
// Disallow receive operations to make the pending read fail.
shutdown(socket1->SocketDescriptorForTesting(), SHUT_RD);
#endif
// The read operation should fail.
#if BUILDFLAG(IS_WIN)
EXPECT_EQ(read_callback.GetResult(read_result), net::ERR_FAILED);
#else
// Ideally, this test should make the read return a failure code.
// Unfortunately, we haven't found a good way to do that.
EXPECT_EQ(read_callback.GetResult(read_result), net::OK);
#endif
}
INSTANTIATE_TEST_SUITE_P(
Any,
TCPSocketTest,
::testing::Values(
// Base tests
std::make_tuple(false, false), // Base, Read
std::make_tuple(false, true) // Base, ReadIfReady
#if BUILDFLAG(IS_WIN)
// TcpSocketIoCompletionPortWin tests
,
std::make_tuple(true,
false), // TcpSocketIoCompletionPortWin, Read
std::make_tuple(true, true) // TcpSocketIoCompletionPortWin,
// ReadIfReady
#endif
),
[](::testing::TestParamInfo<std::tuple<bool, bool>> info) {
std::string name =
std::get<0>(info.param) ? "TcpSocketIoCompletionPortWin" : "Base";
name += std::get<1>(info.param) ? "_ReadIfReady" : "_Read";
return name;
});
} // namespace
} // namespace net