378 lines
10 KiB
C++
378 lines
10 KiB
C++
/*
|
|
* 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
|