#include <stdint.h>
#include <string>
#include <utility>
#include <vector>
#include "base/functional/bind.h"
#include "base/run_loop.h"
#include "base/strings/stringprintf.h"
#include "base/test/bind.h"
#include "base/test/task_environment.h"
#include "build/build_config.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "mojo/public/cpp/system/data_pipe_utils.h"
#include "mojo/public/cpp/system/simple_watcher.h"
#include "mojo/public/cpp/system/wait.h"
#include "net/base/address_list.h"
#include "net/base/ip_address.h"
#include "net/base/ip_endpoint.h"
#include "net/base/net_errors.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "net/test/embedded_test_server/http_request.h"
#include "net/test/embedded_test_server/http_response.h"
#include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
#include "net/url_request/url_request_context.h"
#include "net/url_request/url_request_context_builder.h"
#include "net/url_request/url_request_test_util.h"
#include "services/network/mojo_socket_test_util.h"
#include "services/network/public/mojom/tcp_socket.mojom.h"
#include "services/network/public/mojom/tls_socket.mojom.h"
#include "services/network/socket_factory.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace network {
namespace {
class TCPBoundSocketTest : public testing::Test {
public:
TCPBoundSocketTest()
: task_environment_(base::test::TaskEnvironment::MainThreadType::IO),
url_request_context_(
net::CreateTestURLRequestContextBuilder()->Build()),
factory_(nullptr, url_request_context_.get()) {}
TCPBoundSocketTest(const TCPBoundSocketTest&) = delete;
TCPBoundSocketTest& operator=(const TCPBoundSocketTest&) = delete;
~TCPBoundSocketTest() override {}
SocketFactory* factory() { return &factory_; }
int BindSocket(const net::IPEndPoint& ip_endpoint_in,
mojo::Remote<mojom::TCPBoundSocket>* bound_socket,
net::IPEndPoint* ip_endpoint_out) {
base::RunLoop run_loop;
int bind_result = net::ERR_IO_PENDING;
factory()->CreateTCPBoundSocket(
ip_endpoint_in, TRAFFIC_ANNOTATION_FOR_TESTS,
bound_socket->BindNewPipeAndPassReceiver(),
base::BindLambdaForTesting(
[&](int net_error,
const absl::optional<net::IPEndPoint>& local_addr) {
bind_result = net_error;
if (net_error == net::OK) {
*ip_endpoint_out = *local_addr;
} else {
EXPECT_FALSE(local_addr);
}
run_loop.Quit();
}));
run_loop.Run();
if (bind_result != net::OK && bound_socket->is_connected()) {
base::RunLoop close_pipe_run_loop;
bound_socket->set_disconnect_handler(close_pipe_run_loop.QuitClosure());
close_pipe_run_loop.Run();
}
return bind_result;
}
int Listen(mojo::Remote<mojom::TCPBoundSocket> bound_socket,
mojo::Remote<mojom::TCPServerSocket>* server_socket) {
base::RunLoop bound_socket_destroyed_run_loop;
bound_socket.set_disconnect_handler(
bound_socket_destroyed_run_loop.QuitClosure());
base::RunLoop run_loop;
int listen_result = net::ERR_IO_PENDING;
bound_socket->Listen(1 ,
server_socket->BindNewPipeAndPassReceiver(),
base::BindLambdaForTesting([&](int net_error) {
listen_result = net_error;
run_loop.Quit();
}));
run_loop.Run();
bound_socket_destroyed_run_loop.Run();
if (listen_result != net::OK && server_socket->is_connected()) {
base::RunLoop close_pipe_run_loop;
server_socket->set_disconnect_handler(close_pipe_run_loop.QuitClosure());
close_pipe_run_loop.Run();
}
return listen_result;
}
int Connect(mojo::Remote<mojom::TCPBoundSocket> bound_socket,
const net::IPEndPoint& expected_local_addr,
const net::IPEndPoint& connect_to_addr,
mojom::TCPConnectedSocketOptionsPtr tcp_connected_socket_options,
mojo::Remote<mojom::TCPConnectedSocket>* connected_socket,
mojo::PendingRemote<mojom::SocketObserver> socket_observer,
mojo::ScopedDataPipeConsumerHandle* client_socket_receive_handle,
mojo::ScopedDataPipeProducerHandle* client_socket_send_handle) {
base::RunLoop bound_socket_destroyed_run_loop;
bound_socket.set_disconnect_handler(
bound_socket_destroyed_run_loop.QuitClosure());
int connect_result = net::ERR_IO_PENDING;
base::RunLoop run_loop;
bound_socket->Connect(
net::AddressList(connect_to_addr),
std::move(tcp_connected_socket_options),
connected_socket->BindNewPipeAndPassReceiver(),
std::move(socket_observer),
base::BindLambdaForTesting(
[&](int net_error,
const absl::optional<net::IPEndPoint>& local_addr,
const absl::optional<net::IPEndPoint>& remote_addr,
mojo::ScopedDataPipeConsumerHandle receive_stream,
mojo::ScopedDataPipeProducerHandle send_stream) {
connect_result = net_error;
if (net_error == net::OK) {
EXPECT_EQ(expected_local_addr, *local_addr);
EXPECT_EQ(connect_to_addr, *remote_addr);
*client_socket_receive_handle = std::move(receive_stream);
*client_socket_send_handle = std::move(send_stream);
} else {
EXPECT_FALSE(local_addr);
EXPECT_FALSE(remote_addr);
EXPECT_FALSE(receive_stream.is_valid());
EXPECT_FALSE(send_stream.is_valid());
}
run_loop.Quit();
}));
run_loop.Run();
bound_socket_destroyed_run_loop.Run();
if (connect_result != net::OK && connected_socket->is_connected()) {
base::RunLoop close_pipe_run_loop;
connected_socket->set_disconnect_handler(
close_pipe_run_loop.QuitClosure());
close_pipe_run_loop.Run();
}
return connect_result;
}
std::string ReadData(mojo::DataPipeConsumerHandle receive_handle,
uint32_t expected_bytes = 0) {
std::string read_data;
while (expected_bytes == 0 || read_data.size() < expected_bytes) {
const void* buffer;
uint32_t num_bytes = expected_bytes - read_data.size();
MojoResult result = receive_handle.BeginReadData(
&buffer, &num_bytes, MOJO_READ_DATA_FLAG_NONE);
if (result == MOJO_RESULT_SHOULD_WAIT) {
task_environment_.RunUntilIdle();
continue;
}
if (result != MOJO_RESULT_OK) {
if (expected_bytes != 0)
ADD_FAILURE() << "Read failed";
return read_data;
}
read_data.append(static_cast<const char*>(buffer), num_bytes);
receive_handle.EndReadData(num_bytes);
}
return read_data;
}
static net::IPEndPoint LocalHostWithAnyPort() {
return net::IPEndPoint(net::IPAddress::IPv4Localhost(), 0 );
}
base::test::TaskEnvironment* task_environment() { return &task_environment_; }
private:
base::test::TaskEnvironment task_environment_;
std::unique_ptr<net::URLRequestContext> url_request_context_;
SocketFactory factory_;
};
TEST_F(TCPBoundSocketTest, BindError) {
mojo::Remote<mojom::TCPBoundSocket> bound_socket1;
net::IPEndPoint bound_address1;
ASSERT_EQ(net::OK, BindSocket(LocalHostWithAnyPort(), &bound_socket1,
&bound_address1));
mojo::Remote<mojom::TCPServerSocket> server_socket;
ASSERT_EQ(net::OK, Listen(std::move(bound_socket1), &server_socket));
mojo::Remote<mojom::TCPBoundSocket> bound_socket2;
net::IPEndPoint bound_address2;
int result = BindSocket(bound_address1, &bound_socket2, &bound_address2);
EXPECT_TRUE(result == net::ERR_ADDRESS_IN_USE ||
result == net::ERR_INVALID_ARGUMENT);
}
#if !BUILDFLAG(IS_APPLE)
TEST_F(TCPBoundSocketTest, ConnectError) {
mojo::Remote<mojom::TCPBoundSocket> bound_socket1;
net::IPEndPoint bound_address1;
ASSERT_EQ(net::OK, BindSocket(LocalHostWithAnyPort(), &bound_socket1,
&bound_address1));
mojo::Remote<mojom::TCPBoundSocket> bound_socket2;
net::IPEndPoint bound_address2;
ASSERT_EQ(net::OK, BindSocket(LocalHostWithAnyPort(), &bound_socket2,
&bound_address2));
mojo::Remote<mojom::TCPConnectedSocket> connected_socket;
mojo::ScopedDataPipeConsumerHandle client_socket_receive_handle;
mojo::ScopedDataPipeProducerHandle client_socket_send_handle;
EXPECT_EQ(net::ERR_CONNECTION_REFUSED,
Connect(std::move(bound_socket2), bound_address2, bound_address1,
nullptr ,
&connected_socket, mojo::NullRemote(),
&client_socket_receive_handle, &client_socket_send_handle));
}
#endif
#if !BUILDFLAG(IS_WIN) && !BUILDFLAG(IS_APPLE)
TEST_F(TCPBoundSocketTest, ListenError) {
mojo::Remote<mojom::TCPBoundSocket> bound_socket1;
net::IPEndPoint bound_address1;
ASSERT_EQ(net::OK, BindSocket(LocalHostWithAnyPort(), &bound_socket1,
&bound_address1));
mojo::Remote<mojom::TCPBoundSocket> bound_socket2;
net::IPEndPoint bound_address2;
ASSERT_EQ(net::OK,
BindSocket(bound_address1, &bound_socket2, &bound_address2));
mojo::Remote<mojom::TCPServerSocket> server_socket1;
ASSERT_EQ(net::OK, Listen(std::move(bound_socket1), &server_socket1));
mojo::Remote<mojom::TCPServerSocket> server_socket2;
int result = Listen(std::move(bound_socket2), &server_socket2);
EXPECT_TRUE(result == net::ERR_ADDRESS_IN_USE ||
result == net::ERR_INVALID_ARGUMENT);
}
#endif
TEST_F(TCPBoundSocketTest, ReadWrite) {
mojo::Remote<mojom::TCPBoundSocket> bound_socket1;
net::IPEndPoint server_address;
ASSERT_EQ(net::OK, BindSocket(LocalHostWithAnyPort(), &bound_socket1,
&server_address));
mojo::Remote<mojom::TCPServerSocket> server_socket;
ASSERT_EQ(net::OK, Listen(std::move(bound_socket1), &server_socket));
mojo::Remote<mojom::TCPBoundSocket> bound_socket2;
net::IPEndPoint client_address;
ASSERT_EQ(net::OK, BindSocket(LocalHostWithAnyPort(), &bound_socket2,
&client_address));
mojo::Remote<mojom::TCPConnectedSocket> client_socket;
TestSocketObserver socket_observer;
mojo::ScopedDataPipeConsumerHandle client_socket_receive_handle;
mojo::ScopedDataPipeProducerHandle client_socket_send_handle;
EXPECT_EQ(net::OK,
Connect(std::move(bound_socket2), client_address, server_address,
nullptr , &client_socket,
socket_observer.GetObserverRemote(),
&client_socket_receive_handle, &client_socket_send_handle));
base::RunLoop run_loop;
mojo::Remote<mojom::TCPConnectedSocket> accept_socket;
mojo::ScopedDataPipeConsumerHandle accept_socket_receive_handle;
mojo::ScopedDataPipeProducerHandle accept_socket_send_handle;
server_socket->Accept(
mojo::NullRemote() ,
base::BindLambdaForTesting(
[&](int net_error, const absl::optional<net::IPEndPoint>& remote_addr,
mojo::PendingRemote<mojom::TCPConnectedSocket> connected_socket,
mojo::ScopedDataPipeConsumerHandle receive_stream,
mojo::ScopedDataPipeProducerHandle send_stream) {
EXPECT_EQ(net_error, net::OK);
EXPECT_EQ(*remote_addr, client_address);
accept_socket.Bind(std::move(connected_socket));
accept_socket_receive_handle = std::move(receive_stream);
accept_socket_send_handle = std::move(send_stream);
run_loop.Quit();
}));
run_loop.Run();
const std::string kData = "Jumbo Shrimp";
ASSERT_TRUE(mojo::BlockingCopyFromString(kData, client_socket_send_handle));
EXPECT_EQ(kData, ReadData(accept_socket_receive_handle.get(), kData.size()));
ASSERT_TRUE(mojo::BlockingCopyFromString(kData, accept_socket_send_handle));
EXPECT_EQ(kData, ReadData(client_socket_receive_handle.get(), kData.size()));
accept_socket.reset();
EXPECT_EQ(net::OK, socket_observer.WaitForReadError());
while (true) {
void* buffer = nullptr;
uint32_t buffer_num_bytes = 0;
MojoResult result = client_socket_send_handle->BeginWriteData(
&buffer, &buffer_num_bytes, MOJO_WRITE_DATA_FLAG_NONE);
if (result == MOJO_RESULT_SHOULD_WAIT) {
task_environment()->RunUntilIdle();
continue;
}
if (result != MOJO_RESULT_OK)
break;
memset(buffer, 0, buffer_num_bytes);
client_socket_send_handle->EndWriteData(buffer_num_bytes);
}
EXPECT_LT(socket_observer.WaitForWriteError(), 0);
}
TEST_F(TCPBoundSocketTest, ConnectWithOptions) {
mojo::Remote<mojom::TCPBoundSocket> bound_socket1;
net::IPEndPoint server_address;
ASSERT_EQ(net::OK, BindSocket(LocalHostWithAnyPort(), &bound_socket1,
&server_address));
mojo::Remote<mojom::TCPServerSocket> server_socket;
ASSERT_EQ(net::OK, Listen(std::move(bound_socket1), &server_socket));
mojo::Remote<mojom::TCPBoundSocket> bound_socket2;
net::IPEndPoint client_address;
ASSERT_EQ(net::OK, BindSocket(LocalHostWithAnyPort(), &bound_socket2,
&client_address));
mojo::Remote<mojom::TCPConnectedSocket> client_socket;
TestSocketObserver socket_observer;
mojo::ScopedDataPipeConsumerHandle client_socket_receive_handle;
mojo::ScopedDataPipeProducerHandle client_socket_send_handle;
mojom::TCPConnectedSocketOptionsPtr tcp_connected_socket_options =
mojom::TCPConnectedSocketOptions::New();
tcp_connected_socket_options->send_buffer_size = 32 * 1024;
tcp_connected_socket_options->receive_buffer_size = 64 * 1024;
tcp_connected_socket_options->no_delay = false;
EXPECT_EQ(net::OK,
Connect(std::move(bound_socket2), client_address, server_address,
std::move(tcp_connected_socket_options), &client_socket,
socket_observer.GetObserverRemote(),
&client_socket_receive_handle, &client_socket_send_handle));
base::RunLoop run_loop;
mojo::Remote<mojom::TCPConnectedSocket> accept_socket;
mojo::ScopedDataPipeConsumerHandle accept_socket_receive_handle;
mojo::ScopedDataPipeProducerHandle accept_socket_send_handle;
server_socket->Accept(
mojo::NullRemote() ,
base::BindLambdaForTesting(
[&](int net_error, const absl::optional<net::IPEndPoint>& remote_addr,
mojo::PendingRemote<mojom::TCPConnectedSocket> connected_socket,
mojo::ScopedDataPipeConsumerHandle receive_stream,
mojo::ScopedDataPipeProducerHandle send_stream) {
EXPECT_EQ(net_error, net::OK);
EXPECT_EQ(*remote_addr, client_address);
accept_socket.Bind(std::move(connected_socket));
accept_socket_receive_handle = std::move(receive_stream);
accept_socket_send_handle = std::move(send_stream);
run_loop.Quit();
}));
run_loop.Run();
const std::string kData = "Jumbo Shrimp";
ASSERT_TRUE(mojo::BlockingCopyFromString(kData, client_socket_send_handle));
EXPECT_EQ(kData, ReadData(accept_socket_receive_handle.get(), kData.size()));
ASSERT_TRUE(mojo::BlockingCopyFromString(kData, accept_socket_send_handle));
EXPECT_EQ(kData, ReadData(client_socket_receive_handle.get(), kData.size()));
}
TEST_F(TCPBoundSocketTest, UpgradeToTLS) {
net::test_server::EmbeddedTestServer test_server(
net::test_server::EmbeddedTestServer::TYPE_HTTPS);
test_server.RegisterRequestHandler(base::BindRepeating(
[](const net::test_server::HttpRequest& request)
-> std::unique_ptr<net::test_server::HttpResponse> {
std::unique_ptr<net::test_server::BasicHttpResponse> basic_response =
std::make_unique<net::test_server::BasicHttpResponse>();
basic_response->set_content(request.relative_url);
return basic_response;
}));
ASSERT_TRUE(test_server.Start());
mojo::Remote<mojom::TCPBoundSocket> bound_socket;
net::IPEndPoint client_address;
ASSERT_EQ(net::OK,
BindSocket(LocalHostWithAnyPort(), &bound_socket, &client_address));
mojo::Remote<mojom::TCPConnectedSocket> client_socket;
TestSocketObserver socket_observer;
mojo::ScopedDataPipeConsumerHandle client_socket_receive_handle;
mojo::ScopedDataPipeProducerHandle client_socket_send_handle;
EXPECT_EQ(net::OK,
Connect(std::move(bound_socket), client_address,
net::IPEndPoint(net::IPAddress::IPv4Localhost(),
test_server.host_port_pair().port()),
nullptr , &client_socket,
socket_observer.GetObserverRemote(),
&client_socket_receive_handle, &client_socket_send_handle));
client_socket_receive_handle.reset();
client_socket_send_handle.reset();
base::RunLoop run_loop;
mojo::Remote<mojom::TLSClientSocket> tls_client_socket;
client_socket->UpgradeToTLS(
test_server.host_port_pair(), nullptr ,
net::MutableNetworkTrafficAnnotationTag(TRAFFIC_ANNOTATION_FOR_TESTS),
tls_client_socket.BindNewPipeAndPassReceiver(),
mojo::NullRemote() ,
base::BindLambdaForTesting(
[&](int net_error,
mojo::ScopedDataPipeConsumerHandle receive_pipe_handle,
mojo::ScopedDataPipeProducerHandle send_pipe_handle,
const absl::optional<net::SSLInfo>& ssl_info) {
EXPECT_EQ(net::OK, net_error);
client_socket_receive_handle = std::move(receive_pipe_handle);
client_socket_send_handle = std::move(send_pipe_handle);
run_loop.Quit();
}));
run_loop.Run();
const char kPath[] = "/foo";
std::string request = base::StringPrintf("GET %s HTTP/1.0\r\n\r\n", kPath);
EXPECT_TRUE(mojo::BlockingCopyFromString(request, client_socket_send_handle));
std::string response = ReadData(client_socket_receive_handle.get());
EXPECT_EQ("HTTP/", response.substr(0, 5));
EXPECT_EQ(kPath, response.substr(response.length() - strlen(kPath)));
}
}
}