blob: 6d75f10279d1ddb9326ce012b4351ccc62b4033a [file] [log] [blame]
/*
* 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 <gmock/gmock.h>
#include <gtest/gtest.h>
#include <log/log.h>
#include <netdb.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#include <cstdint>
#include <cstring>
#include <vector>
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<void(int)> cb1;
testing::MockFunction<void(int)> 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