/* * Copyright 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #define LOG_TAG "async_fd_watcher_unittest" #include "async_fd_watcher.h" #include #include #include #include #include #include #include #include #include #include #include namespace android::hardware::bluetooth::async_test { using android::hardware::bluetooth::async::AsyncFdWatcher; class AsyncFdWatcherSocketTest : public ::testing::Test { public: static const uint16_t kPort = 6111; static const size_t kBufferSize = 16; bool CheckBufferEquals() { return strcmp(server_buffer_, client_buffer_) == 0; } protected: int StartServer() { ALOGD("%s", __func__); struct sockaddr_in serv_addr; int fd = socket(AF_INET, SOCK_STREAM, 0); EXPECT_FALSE(fd < 0); memset(&serv_addr, 0, sizeof(serv_addr)); serv_addr.sin_family = AF_INET; serv_addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); serv_addr.sin_port = htons(kPort); int reuse_flag = 1; EXPECT_FALSE(setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &reuse_flag, sizeof(reuse_flag)) < 0); EXPECT_FALSE(bind(fd, (sockaddr*)&serv_addr, sizeof(serv_addr)) < 0); ALOGD("%s before listen", __func__); listen(fd, 1); return fd; } int AcceptConnection(int fd) { ALOGD("%s", __func__); struct sockaddr_in cli_addr; memset(&cli_addr, 0, sizeof(cli_addr)); socklen_t clilen = sizeof(cli_addr); int connection_fd = accept(fd, (struct sockaddr*)&cli_addr, &clilen); EXPECT_FALSE(connection_fd < 0); return connection_fd; } void ReadIncomingMessage(int fd) { ALOGD("%s", __func__); int n = TEMP_FAILURE_RETRY(read(fd, server_buffer_, kBufferSize - 1)); EXPECT_FALSE(n < 0); if (n == 0) { // got EOF ALOGD("%s: EOF", __func__); } else { ALOGD("%s: Got something", __func__); n = write(fd, "1", 1); } } void SetUp() override { ALOGD("%s", __func__); memset(server_buffer_, 0, kBufferSize); memset(client_buffer_, 0, kBufferSize); } void ConfigureServer() { socket_fd_ = StartServer(); conn_watcher_.WatchFdForNonBlockingReads(socket_fd_, [this](int fd) { int connection_fd = AcceptConnection(fd); ALOGD("%s: Conn_watcher fd = %d", __func__, fd); conn_watcher_.ConfigureTimeout(std::chrono::seconds(0), []() { bool connection_timeout_cleared = false; ASSERT_TRUE(connection_timeout_cleared); }); ALOGD("%s: 3", __func__); async_fd_watcher_.WatchFdForNonBlockingReads( connection_fd, [this](int fd) { ReadIncomingMessage(fd); }); // Time out if it takes longer than a second. SetTimeout(std::chrono::seconds(1)); }); conn_watcher_.ConfigureTimeout(std::chrono::seconds(1), []() { bool connection_timeout = true; ASSERT_FALSE(connection_timeout); }); } void CleanUpServer() { async_fd_watcher_.StopWatchingFileDescriptors(); conn_watcher_.StopWatchingFileDescriptors(); close(socket_fd_); } void TearDown() override { ALOGD("%s 3", __func__); EXPECT_TRUE(CheckBufferEquals()); } void OnTimeout() { ALOGD("%s", __func__); timed_out_ = true; } void ClearTimeout() { ALOGD("%s", __func__); timed_out_ = false; } bool TimedOut() { ALOGD("%s %d", __func__, timed_out_ ? 1 : 0); return timed_out_; } void SetTimeout(std::chrono::milliseconds timeout_ms) { ALOGD("%s", __func__); async_fd_watcher_.ConfigureTimeout(timeout_ms, [this]() { OnTimeout(); }); ClearTimeout(); } int ConnectClient() { ALOGD("%s", __func__); int socket_cli_fd = socket(AF_INET, SOCK_STREAM, 0); EXPECT_FALSE(socket_cli_fd < 0); struct sockaddr_in serv_addr; memset((void*)&serv_addr, 0, sizeof(serv_addr)); serv_addr.sin_family = AF_INET; serv_addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); serv_addr.sin_port = htons(kPort); int result = connect(socket_cli_fd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)); EXPECT_FALSE(result < 0); return socket_cli_fd; } void WriteFromClient(int socket_cli_fd) { ALOGD("%s", __func__); strcpy(client_buffer_, "1"); int n = write(socket_cli_fd, client_buffer_, strlen(client_buffer_)); EXPECT_TRUE(n > 0); } void AwaitServerResponse(int socket_cli_fd) { ALOGD("%s", __func__); int n = read(socket_cli_fd, client_buffer_, 1); ALOGD("%s done", __func__); EXPECT_TRUE(n > 0); } private: AsyncFdWatcher async_fd_watcher_; AsyncFdWatcher conn_watcher_; int socket_fd_; char server_buffer_[kBufferSize]; char client_buffer_[kBufferSize]; bool timed_out_; }; // Use a single AsyncFdWatcher to signal a connection to the server socket. TEST_F(AsyncFdWatcherSocketTest, Connect) { int socket_fd = StartServer(); AsyncFdWatcher conn_watcher; conn_watcher.WatchFdForNonBlockingReads(socket_fd, [this](int fd) { int connection_fd = AcceptConnection(fd); close(connection_fd); }); // Fail if the client doesn't connect within 1 second. conn_watcher.ConfigureTimeout(std::chrono::seconds(1), []() { bool connection_timeout = true; ASSERT_FALSE(connection_timeout); }); int socket_cli_fd = ConnectClient(); conn_watcher.StopWatchingFileDescriptors(); close(socket_fd); close(socket_cli_fd); } // Use a single AsyncFdWatcher to signal a connection to the server socket. TEST_F(AsyncFdWatcherSocketTest, TimedOutConnect) { int socket_fd = StartServer(); bool timed_out = false; bool* timeout_ptr = &timed_out; AsyncFdWatcher conn_watcher; conn_watcher.WatchFdForNonBlockingReads(socket_fd, [this](int fd) { int connection_fd = AcceptConnection(fd); close(connection_fd); }); // Set the timeout flag after 100ms. conn_watcher.ConfigureTimeout(std::chrono::milliseconds(100), [timeout_ptr]() { *timeout_ptr = true; }); EXPECT_FALSE(timed_out); sleep(1); EXPECT_TRUE(timed_out); conn_watcher.StopWatchingFileDescriptors(); close(socket_fd); } // Modify the timeout in a timeout callback. TEST_F(AsyncFdWatcherSocketTest, TimedOutSchedulesTimeout) { int socket_fd = StartServer(); bool timed_out = false; bool timed_out2 = false; AsyncFdWatcher conn_watcher; conn_watcher.WatchFdForNonBlockingReads(socket_fd, [this](int fd) { int connection_fd = AcceptConnection(fd); close(connection_fd); }); // Set a timeout flag in each callback. conn_watcher.ConfigureTimeout(std::chrono::milliseconds(500), [&conn_watcher, &timed_out, &timed_out2]() { timed_out = true; conn_watcher.ConfigureTimeout( std::chrono::seconds(1), [&timed_out2]() { timed_out2 = true; }); }); EXPECT_FALSE(timed_out); EXPECT_FALSE(timed_out2); sleep(1); EXPECT_TRUE(timed_out); EXPECT_FALSE(timed_out2); sleep(1); EXPECT_TRUE(timed_out); EXPECT_TRUE(timed_out2); conn_watcher.StopWatchingFileDescriptors(); close(socket_fd); } MATCHER_P(ReadAndMatchSingleChar, byte, "Reads a byte from the file descriptor and matches the value against " "byte") { char inbuf[1] = {0}; int n = TEMP_FAILURE_RETRY(read(arg, inbuf, 1)); TEMP_FAILURE_RETRY(write(arg, inbuf, 1)); if (n != 1) { return false; } return inbuf[0] == byte; }; // Use a single AsyncFdWatcher to watch two file descriptors. TEST_F(AsyncFdWatcherSocketTest, WatchTwoFileDescriptors) { int sockfd1[2]; int sockfd2[2]; socketpair(AF_LOCAL, SOCK_STREAM, 0, sockfd1); socketpair(AF_LOCAL, SOCK_STREAM, 0, sockfd2); testing::MockFunction cb1; testing::MockFunction cb2; AsyncFdWatcher watcher; watcher.WatchFdForNonBlockingReads(sockfd1[0], cb1.AsStdFunction()); watcher.WatchFdForNonBlockingReads(sockfd2[0], cb2.AsStdFunction()); EXPECT_CALL(cb1, Call(ReadAndMatchSingleChar('1'))); char one_buf[1] = {'1'}; TEMP_FAILURE_RETRY(write(sockfd1[1], one_buf, sizeof(one_buf))); EXPECT_CALL(cb2, Call(ReadAndMatchSingleChar('2'))); char two_buf[1] = {'2'}; TEMP_FAILURE_RETRY(write(sockfd2[1], two_buf, sizeof(two_buf))); // Blocking read instead of a flush. TEMP_FAILURE_RETRY(read(sockfd1[1], one_buf, sizeof(one_buf))); TEMP_FAILURE_RETRY(read(sockfd2[1], two_buf, sizeof(two_buf))); watcher.StopWatchingFileDescriptors(); } // Use two AsyncFdWatchers to set up a server socket. TEST_F(AsyncFdWatcherSocketTest, ClientServer) { ConfigureServer(); int socket_cli_fd = ConnectClient(); WriteFromClient(socket_cli_fd); AwaitServerResponse(socket_cli_fd); close(socket_cli_fd); CleanUpServer(); } // Use two AsyncFdWatchers to set up a server socket, which times out. TEST_F(AsyncFdWatcherSocketTest, TimeOutTest) { ConfigureServer(); int socket_cli_fd = ConnectClient(); while (!TimedOut()) sleep(1); close(socket_cli_fd); CleanUpServer(); } // Use two AsyncFdWatchers to set up a server socket, which times out. TEST_F(AsyncFdWatcherSocketTest, RepeatedTimeOutTest) { ConfigureServer(); int socket_cli_fd = ConnectClient(); ClearTimeout(); // Time out when there are no writes. EXPECT_FALSE(TimedOut()); sleep(2); EXPECT_TRUE(TimedOut()); ClearTimeout(); // Don't time out when there is a write. WriteFromClient(socket_cli_fd); AwaitServerResponse(socket_cli_fd); EXPECT_FALSE(TimedOut()); ClearTimeout(); // Time out when the write is late. sleep(2); WriteFromClient(socket_cli_fd); AwaitServerResponse(socket_cli_fd); EXPECT_TRUE(TimedOut()); ClearTimeout(); // Time out when there is a pause after a write. WriteFromClient(socket_cli_fd); sleep(2); AwaitServerResponse(socket_cli_fd); EXPECT_TRUE(TimedOut()); ClearTimeout(); close(socket_cli_fd); CleanUpServer(); } } // namespace android::hardware::bluetooth::async_test