| // Copyright 2018 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "services/network/socket_data_pump.h" |
| |
| #include <stdint.h> |
| |
| #include <utility> |
| #include <vector> |
| |
| #include "base/containers/span.h" |
| #include "base/functional/callback_helpers.h" |
| #include "base/location.h" |
| #include "base/logging.h" |
| #include "base/run_loop.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/test/task_environment.h" |
| #include "mojo/public/cpp/system/simple_watcher.h" |
| #include "net/base/io_buffer.h" |
| #include "net/base/ip_address.h" |
| #include "net/base/ip_endpoint.h" |
| #include "net/base/net_errors.h" |
| #include "net/base/test_completion_callback.h" |
| #include "net/log/net_log_source.h" |
| #include "net/socket/socket_test_util.h" |
| #include "net/socket/tcp_client_socket.h" |
| #include "net/socket/tcp_server_socket.h" |
| #include "net/traffic_annotation/network_traffic_annotation_test_helper.h" |
| #include "net/url_request/url_request_test_util.h" |
| #include "services/network/mojo_socket_test_util.h" |
| #include "services/network/public/mojom/network_service.mojom.h" |
| #include "services/network/public/mojom/udp_socket.mojom.h" |
| #include "services/network/socket_factory.h" |
| #include "services/network/tcp_connected_socket.h" |
| #include "services/network/tcp_server_socket.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| namespace network { |
| |
| // Test delegate to wait on network read/write errors. |
| class TestSocketDataPumpDelegate : public SocketDataPump::Delegate { |
| public: |
| TestSocketDataPumpDelegate() {} |
| |
| TestSocketDataPumpDelegate(const TestSocketDataPumpDelegate&) = delete; |
| TestSocketDataPumpDelegate& operator=(const TestSocketDataPumpDelegate&) = |
| delete; |
| |
| ~TestSocketDataPumpDelegate() {} |
| |
| // Waits for read error. Returns the error observed. |
| int WaitForReadError() { |
| read_loop_.Run(); |
| int error = read_error_; |
| read_error_ = net::OK; |
| return error; |
| } |
| |
| // Waits for write error. Returns the error observed. |
| int WaitForWriteError() { |
| write_loop_.Run(); |
| int error = write_error_; |
| write_error_ = net::OK; |
| return error; |
| } |
| |
| // Waits for shutdown. |
| void WaitForShutdown() { shutdown_loop_.Run(); } |
| |
| private: |
| void OnNetworkReadError(int error) override { |
| read_error_ = error; |
| read_loop_.Quit(); |
| } |
| void OnNetworkWriteError(int error) override { |
| write_error_ = error; |
| write_loop_.Quit(); |
| } |
| void OnShutdown() override { shutdown_loop_.Quit(); } |
| |
| int read_error_ = net::OK; |
| int write_error_ = net::OK; |
| base::RunLoop read_loop_; |
| base::RunLoop write_loop_; |
| base::RunLoop shutdown_loop_; |
| }; |
| |
| class SocketDataPumpTest : public testing::Test, |
| public ::testing::WithParamInterface<net::IoMode> { |
| public: |
| SocketDataPumpTest() |
| : task_environment_(base::test::TaskEnvironment::MainThreadType::IO) {} |
| |
| SocketDataPumpTest(const SocketDataPumpTest&) = delete; |
| SocketDataPumpTest& operator=(const SocketDataPumpTest&) = delete; |
| |
| ~SocketDataPumpTest() override {} |
| |
| // Initializes the test case with a socket data provider, which will be used |
| // to populate the read/write data of the mock socket. |
| void Init(net::StaticSocketDataProvider* data_provider) { |
| mock_client_socket_factory_.AddSocketDataProvider(data_provider); |
| mock_client_socket_factory_.set_enable_read_if_ready(true); |
| |
| mojo::ScopedDataPipeConsumerHandle send_consumer_handle; |
| ASSERT_EQ(mojo::CreateDataPipe(nullptr, send_handle_, send_consumer_handle), |
| MOJO_RESULT_OK); |
| |
| mojo::ScopedDataPipeProducerHandle receive_producer_handle; |
| ASSERT_EQ( |
| mojo::CreateDataPipe(nullptr, receive_producer_handle, receive_handle_), |
| MOJO_RESULT_OK); |
| |
| socket_ = mock_client_socket_factory_.CreateTransportClientSocket( |
| net::AddressList(), |
| // No need to use a target network here. This is used only for testing |
| // in non-multi-network scenarios. |
| net::handles::kInvalidNetworkHandle, |
| nullptr /*socket_performance_watcher*/, |
| nullptr /*network_quality_estimator*/, nullptr /*netlog*/, |
| net::NetLogSource()); |
| net::TestCompletionCallback callback; |
| int result = socket_->Connect(callback.callback()); |
| if (result == net::ERR_IO_PENDING) |
| result = callback.WaitForResult(); |
| EXPECT_EQ(net::OK, result); |
| data_pump_ = std::make_unique<SocketDataPump>( |
| socket_.get(), delegate(), std::move(receive_producer_handle), |
| std::move(send_consumer_handle), TRAFFIC_ANNOTATION_FOR_TESTS); |
| } |
| |
| // Reads |num_bytes| from |handle| or reads until an error occurs. Returns the |
| // bytes read as a string. |
| std::string Read(mojo::ScopedDataPipeConsumerHandle* handle, |
| size_t num_bytes) { |
| std::string received_contents; |
| while (received_contents.size() < num_bytes) { |
| base::RunLoop().RunUntilIdle(); |
| std::string buffer(num_bytes, '\0'); |
| MojoResult result = handle->get().ReadData( |
| MOJO_READ_DATA_FLAG_NONE, base::as_writable_byte_span(buffer), |
| num_bytes); |
| if (result == MOJO_RESULT_SHOULD_WAIT) |
| continue; |
| if (result != MOJO_RESULT_OK) |
| return received_contents; |
| received_contents.append(std::string_view(buffer).substr(0, num_bytes)); |
| } |
| return received_contents; |
| } |
| |
| TestSocketDataPumpDelegate* delegate() { return &test_delegate_; } |
| |
| mojo::ScopedDataPipeConsumerHandle receive_handle_; |
| mojo::ScopedDataPipeProducerHandle send_handle_; |
| |
| private: |
| base::test::TaskEnvironment task_environment_; |
| net::MockClientSocketFactory mock_client_socket_factory_; |
| TestSocketDataPumpDelegate test_delegate_; |
| std::unique_ptr<net::StreamSocket> socket_; |
| std::unique_ptr<SocketDataPump> data_pump_; |
| }; |
| |
| INSTANTIATE_TEST_SUITE_P(All, |
| SocketDataPumpTest, |
| testing::Values(net::SYNCHRONOUS, net::ASYNC)); |
| |
| TEST_P(SocketDataPumpTest, ReadAndWriteMultiple) { |
| constexpr std::string_view kTestMsg = "abcdefghij"; |
| constexpr int kNumIterations = 3; |
| std::vector<net::MockRead> reads; |
| std::vector<net::MockWrite> writes; |
| int sequence_number = 0; |
| net::IoMode mode = GetParam(); |
| for (int j = 0; j < kNumIterations; ++j) { |
| for (const char& c : kTestMsg) { |
| reads.emplace_back(mode, sequence_number++, base::byte_span_from_ref(c)); |
| } |
| if (j == kNumIterations - 1) { |
| reads.emplace_back(mode, net::OK, sequence_number++); |
| } |
| for (const char& c : kTestMsg) { |
| writes.emplace_back(mode, sequence_number++, base::byte_span_from_ref(c)); |
| } |
| } |
| net::StaticSocketDataProvider data_provider(reads, writes); |
| Init(&data_provider); |
| // Loop kNumIterations times to test that writes can follow reads, and reads |
| // can follow writes. |
| for (int j = 0; j < kNumIterations; ++j) { |
| // Reading `kTestMsg.size()` should coalesce the 1-byte mock reads. |
| EXPECT_EQ(kTestMsg, Read(&receive_handle_, kTestMsg.size())); |
| // Write multiple times. |
| for (const char& c : kTestMsg) { |
| size_t actually_written_bytes = 0; |
| EXPECT_EQ(MOJO_RESULT_OK, |
| send_handle_->WriteData(base::byte_span_from_ref(c), |
| MOJO_WRITE_DATA_FLAG_NONE, |
| actually_written_bytes)); |
| // Flush the 1 byte write. |
| base::RunLoop().RunUntilIdle(); |
| } |
| } |
| EXPECT_TRUE(data_provider.AllReadDataConsumed()); |
| EXPECT_TRUE(data_provider.AllWriteDataConsumed()); |
| } |
| |
| TEST_P(SocketDataPumpTest, PartialStreamSocketWrite) { |
| constexpr std::string_view kTestMsg = "abcdefghij"; |
| constexpr int kNumIterations = 3; |
| std::vector<net::MockRead> reads; |
| std::vector<net::MockWrite> writes; |
| int sequence_number = 0; |
| net::IoMode mode = GetParam(); |
| for (int j = 0; j < kNumIterations; ++j) { |
| for (const char& c : kTestMsg) { |
| reads.emplace_back(mode, sequence_number++, base::byte_span_from_ref(c)); |
| } |
| if (j == kNumIterations - 1) { |
| reads.emplace_back(mode, net::OK, sequence_number++); |
| } |
| for (const char& c : kTestMsg) { |
| writes.emplace_back(mode, sequence_number++, base::byte_span_from_ref(c)); |
| } |
| } |
| net::StaticSocketDataProvider data_provider(reads, writes); |
| Init(&data_provider); |
| // Loop kNumIterations times to test that writes can follow reads, and reads |
| // can follow writes. |
| for (int j = 0; j < kNumIterations; ++j) { |
| // Reading `kTestMsg.size()` should coalesce the 1-byte mock reads. |
| EXPECT_EQ(kTestMsg, Read(&receive_handle_, kTestMsg.size())); |
| // Write twice, each with kMsgSize/2 bytes which is bigger than the 1-byte |
| // MockWrite(). This is to exercise that StreamSocket::Write() can do |
| // partial write. |
| auto [first_write, second_write] = |
| base::as_byte_span(kTestMsg).split_at(kTestMsg.size() / 2); |
| size_t actually_written_bytes = 0; |
| EXPECT_EQ(MOJO_RESULT_OK, |
| send_handle_->WriteData(first_write, MOJO_WRITE_DATA_FLAG_NONE, |
| actually_written_bytes)); |
| EXPECT_EQ(kTestMsg.size() / 2, actually_written_bytes); |
| // Flush the kMsgSize/2 byte write. |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_EQ(MOJO_RESULT_OK, |
| send_handle_->WriteData(second_write, MOJO_WRITE_DATA_FLAG_NONE, |
| actually_written_bytes)); |
| EXPECT_EQ(kTestMsg.size() - first_write.size(), actually_written_bytes); |
| // Flush the kMsgSize/2 byte write. |
| base::RunLoop().RunUntilIdle(); |
| } |
| EXPECT_TRUE(data_provider.AllReadDataConsumed()); |
| EXPECT_TRUE(data_provider.AllWriteDataConsumed()); |
| } |
| |
| TEST_P(SocketDataPumpTest, ReadEof) { |
| net::IoMode mode = GetParam(); |
| net::MockRead reads[] = {net::MockRead(mode, net::OK)}; |
| constexpr std::string_view kTestMsg = "hello!"; |
| net::MockWrite writes[] = {net::MockWrite(mode, 0, kTestMsg)}; |
| net::StaticSocketDataProvider data_provider(reads, writes); |
| Init(&data_provider); |
| EXPECT_EQ("", Read(&receive_handle_, 1)); |
| EXPECT_EQ(net::OK, delegate()->WaitForReadError()); |
| // Writes can proceed even though there is a read error. |
| size_t actually_written_bytes = 0; |
| EXPECT_EQ(MOJO_RESULT_OK, |
| send_handle_->WriteData(base::as_byte_span(kTestMsg), |
| MOJO_WRITE_DATA_FLAG_NONE, |
| actually_written_bytes)); |
| EXPECT_EQ(kTestMsg.size(), actually_written_bytes); |
| |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_TRUE(data_provider.AllReadDataConsumed()); |
| EXPECT_TRUE(data_provider.AllWriteDataConsumed()); |
| } |
| |
| TEST_P(SocketDataPumpTest, ReadError) { |
| net::IoMode mode = GetParam(); |
| net::MockRead reads[] = {net::MockRead(mode, net::ERR_FAILED)}; |
| constexpr std::string_view kTestMsg = "hello!"; |
| net::MockWrite writes[] = {net::MockWrite(mode, 0, kTestMsg)}; |
| net::StaticSocketDataProvider data_provider(reads, writes); |
| Init(&data_provider); |
| EXPECT_EQ("", Read(&receive_handle_, 1)); |
| EXPECT_EQ(net::ERR_FAILED, delegate()->WaitForReadError()); |
| // Writes can proceed even though there is a read error. |
| size_t actually_written_bytes = 0; |
| EXPECT_EQ(MOJO_RESULT_OK, |
| send_handle_->WriteData(base::as_byte_span(kTestMsg), |
| MOJO_WRITE_DATA_FLAG_NONE, |
| actually_written_bytes)); |
| EXPECT_EQ(kTestMsg.size(), actually_written_bytes); |
| |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_TRUE(data_provider.AllReadDataConsumed()); |
| EXPECT_TRUE(data_provider.AllWriteDataConsumed()); |
| } |
| |
| TEST_P(SocketDataPumpTest, WriteEof) { |
| net::IoMode mode = GetParam(); |
| constexpr std::string_view kTestMsg = "hello!"; |
| net::MockRead reads[] = {net::MockRead(mode, 0, kTestMsg), |
| net::MockRead(mode, net::OK)}; |
| net::MockWrite writes[] = {net::MockWrite(mode, net::OK)}; |
| net::StaticSocketDataProvider data_provider(reads, writes); |
| Init(&data_provider); |
| size_t actually_written_bytes = 0; |
| EXPECT_EQ(MOJO_RESULT_OK, |
| send_handle_->WriteData(base::as_byte_span(kTestMsg), |
| MOJO_WRITE_DATA_FLAG_NONE, |
| actually_written_bytes)); |
| EXPECT_EQ(kTestMsg.size(), actually_written_bytes); |
| EXPECT_EQ(net::OK, delegate()->WaitForWriteError()); |
| // Reads can proceed even though there is a read error. |
| EXPECT_EQ(kTestMsg, Read(&receive_handle_, kTestMsg.size())); |
| |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_TRUE(data_provider.AllReadDataConsumed()); |
| EXPECT_TRUE(data_provider.AllWriteDataConsumed()); |
| } |
| |
| TEST_P(SocketDataPumpTest, WriteError) { |
| net::IoMode mode = GetParam(); |
| constexpr std::string_view kTestMsg = "hello!"; |
| net::MockRead reads[] = {net::MockRead(mode, 0, kTestMsg), |
| net::MockRead(mode, net::OK)}; |
| net::MockWrite writes[] = {net::MockWrite(mode, net::ERR_FAILED)}; |
| net::StaticSocketDataProvider data_provider(reads, writes); |
| Init(&data_provider); |
| size_t actually_written_bytes = 0; |
| EXPECT_EQ(MOJO_RESULT_OK, |
| send_handle_->WriteData(base::as_byte_span(kTestMsg), |
| MOJO_WRITE_DATA_FLAG_NONE, |
| actually_written_bytes)); |
| EXPECT_EQ(kTestMsg.size(), actually_written_bytes); |
| EXPECT_EQ(net::ERR_FAILED, delegate()->WaitForWriteError()); |
| // Reads can proceed even though there is a read error. |
| EXPECT_EQ(kTestMsg, Read(&receive_handle_, kTestMsg.size())); |
| |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_TRUE(data_provider.AllReadDataConsumed()); |
| EXPECT_TRUE(data_provider.AllWriteDataConsumed()); |
| } |
| |
| TEST_P(SocketDataPumpTest, PipesShutdown) { |
| net::IoMode mode = GetParam(); |
| net::MockRead reads[] = {net::MockRead(mode, net::OK)}; |
| net::StaticSocketDataProvider data_provider(reads, |
| base::span<net::MockWrite>()); |
| Init(&data_provider); |
| send_handle_.reset(); |
| receive_handle_.reset(); |
| delegate()->WaitForShutdown(); |
| } |
| |
| namespace { |
| |
| class PumpDestroyingDelegate : public SocketDataPump::Delegate { |
| public: |
| PumpDestroyingDelegate() = default; |
| ~PumpDestroyingDelegate() = default; |
| |
| PumpDestroyingDelegate(const PumpDestroyingDelegate&) = delete; |
| PumpDestroyingDelegate& operator=(const PumpDestroyingDelegate&) = delete; |
| |
| void set_pump(std::unique_ptr<SocketDataPump> pump) { |
| pump_ = std::move(pump); |
| } |
| |
| void set_run_on_shutdown(base::OnceClosure closure) { |
| run_on_shutdown_ = std::move(closure); |
| } |
| |
| // SocketDataPump::Delegate implementation: |
| void OnNetworkReadError(int net_error) override {} |
| void OnNetworkWriteError(int net_error) override {} |
| void OnShutdown() override { |
| pump_ = nullptr; |
| if (run_on_shutdown_) { |
| std::move(run_on_shutdown_).Run(); |
| } |
| } |
| |
| private: |
| std::unique_ptr<SocketDataPump> pump_; |
| base::OnceClosure run_on_shutdown_; |
| }; |
| |
| // A dummy socket that behaves as if writes are blocked until the TakeWrite() |
| // method is explicitly called. |
| class BlockedStreamSocket : public net::StreamSocket { |
| public: |
| BlockedStreamSocket() = default; |
| ~BlockedStreamSocket() override = default; |
| |
| void set_run_on_write(base::OnceClosure closure) { |
| run_on_write_ = std::move(closure); |
| } |
| |
| std::vector<uint8_t> TakeWrite() { |
| size_t buf_len = pending_write_buf_len_; |
| std::vector data(std::from_range, pending_write_buf_->first(buf_len)); |
| pending_write_buf_ = nullptr; |
| pending_write_buf_len_ = 0; |
| std::move(pending_write_callback_).Run(base::checked_cast<int>(buf_len)); |
| return data; |
| } |
| |
| // net::StreamSocket implementation: |
| int Read(net::IOBuffer* buf, |
| int buf_len, |
| net::CompletionOnceCallback callback) override { |
| return net::ERR_IO_PENDING; |
| } |
| int ReadIfReady(net::IOBuffer* buf, |
| int buf_len, |
| net::CompletionOnceCallback callback) override { |
| return net::ERR_IO_PENDING; |
| } |
| int CancelReadIfReady() override { return net::OK; } |
| int Write( |
| net::IOBuffer* buf, |
| int buf_len, |
| net::CompletionOnceCallback callback, |
| const net::NetworkTrafficAnnotationTag& traffic_annotation) override { |
| pending_write_buf_ = buf; |
| pending_write_buf_len_ = base::checked_cast<size_t>(buf_len); |
| pending_write_callback_ = std::move(callback); |
| if (run_on_write_) { |
| std::move(run_on_write_).Run(); |
| } |
| return net::ERR_IO_PENDING; |
| } |
| int SetReceiveBufferSize(int32_t size) override { return net::OK; } |
| int SetSendBufferSize(int32_t size) override { return net::OK; } |
| int Connect(net::CompletionOnceCallback callback) override { return net::OK; } |
| void Disconnect() override {} |
| bool IsConnected() const override { return true; } |
| bool IsConnectedAndIdle() const override { return false; } |
| int GetPeerAddress(net::IPEndPoint* address) const override { |
| return net::OK; |
| } |
| int GetLocalAddress(net::IPEndPoint* address) const override { |
| return net::OK; |
| } |
| const net::NetLogWithSource& NetLog() const override { return net_log_; } |
| bool WasEverUsed() const override { return true; } |
| net::NextProto GetNegotiatedProtocol() const override { |
| return net::NextProto::kProtoUnknown; |
| } |
| bool GetSSLInfo(net::SSLInfo* ssl_info) override { return false; } |
| int64_t GetTotalReceivedBytes() const override { return 0; } |
| void ApplySocketTag(const net::SocketTag& tag) override {} |
| |
| private: |
| scoped_refptr<net::IOBuffer> pending_write_buf_; |
| size_t pending_write_buf_len_ = 0; |
| net::CompletionOnceCallback pending_write_callback_; |
| base::OnceClosure run_on_write_; |
| net::NetLogWithSource net_log_; |
| }; |
| |
| } // namespace |
| |
| TEST(SocketDataPumpTest, ShutdownWhileBlockedOnWrite) { |
| base::test::TaskEnvironment task_environment( |
| base::test::TaskEnvironment::MainThreadType::IO); |
| |
| BlockedStreamSocket socket; |
| base::RunLoop wait_for_write; |
| socket.set_run_on_write(wait_for_write.QuitClosure()); |
| |
| mojo::ScopedDataPipeProducerHandle send_producer; |
| mojo::ScopedDataPipeConsumerHandle send_consumer; |
| ASSERT_EQ(MOJO_RESULT_OK, |
| mojo::CreateDataPipe(nullptr, send_producer, send_consumer)); |
| |
| mojo::ScopedDataPipeProducerHandle receive_producer; |
| mojo::ScopedDataPipeConsumerHandle receive_consumer; |
| ASSERT_EQ(MOJO_RESULT_OK, |
| mojo::CreateDataPipe(nullptr, receive_producer, receive_consumer)); |
| |
| PumpDestroyingDelegate delegate; |
| auto pump = std::make_unique<SocketDataPump>( |
| &socket, &delegate, std::move(receive_producer), std::move(send_consumer), |
| TRAFFIC_ANNOTATION_FOR_TESTS); |
| delegate.set_pump(std::move(pump)); |
| base::RunLoop wait_for_shutdown; |
| delegate.set_run_on_shutdown(wait_for_shutdown.QuitClosure()); |
| |
| // Write data to the send pipe. |
| const std::string data = "secret"; |
| size_t actually_written = 0; |
| ASSERT_EQ(MOJO_RESULT_OK, send_producer->WriteData(base::as_byte_span(data), |
| MOJO_WRITE_DATA_FLAG_NONE, |
| actually_written)); |
| EXPECT_EQ(actually_written, data.size()); |
| |
| // Run until SocketDataPump reads from the pipe and calls socket.Write(). |
| wait_for_write.Run(); |
| |
| // Trigger OnShutdown() by closing the receive consumer. |
| // This will cause PumpDestroyingDelegate to destroy the SocketDataPump. |
| receive_consumer.reset(); |
| wait_for_shutdown.Run(); |
| |
| // Copy the data that was passed to Write(). |
| std::vector<uint8_t> written_data = socket.TakeWrite(); |
| EXPECT_EQ(data, base::as_string_view(written_data)); |
| } |
| |
| } // namespace network |