| // 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 |