Start with async and HCI
Bug: 205758693 Test: unit tests Change-Id: I16a8be44bce5f2d233582ab6db17c30d068fa9c0
This commit is contained in:
parent
10d29e4934
commit
e4501e549c
12 changed files with 1550 additions and 0 deletions
|
@ -3,6 +3,7 @@
|
|||
aidl_interface {
|
||||
name: "android.hardware.bluetooth",
|
||||
vendor_available: true,
|
||||
host_supported: true,
|
||||
srcs: ["android/hardware/bluetooth/*.aidl"],
|
||||
stability: "vintf",
|
||||
backend: {
|
||||
|
|
41
bluetooth/async/Android.bp
Normal file
41
bluetooth/async/Android.bp
Normal file
|
@ -0,0 +1,41 @@
|
|||
package {
|
||||
default_applicable_licenses: ["hardware_interfaces_license"],
|
||||
}
|
||||
|
||||
cc_library_static {
|
||||
name: "android.hardware.bluetooth.async",
|
||||
vendor_available: true,
|
||||
host_supported: true,
|
||||
srcs: [
|
||||
"async_fd_watcher.cc",
|
||||
],
|
||||
export_include_dirs: ["."],
|
||||
shared_libs: [
|
||||
"liblog",
|
||||
],
|
||||
cflags: [
|
||||
"-Wall",
|
||||
"-Werror",
|
||||
],
|
||||
}
|
||||
|
||||
cc_test {
|
||||
name: "bluetooth-vendor-interface-async-test",
|
||||
host_supported: true,
|
||||
srcs: [
|
||||
"test/async_fd_watcher_unittest.cc",
|
||||
],
|
||||
shared_libs: [
|
||||
"liblog",
|
||||
"libutils",
|
||||
],
|
||||
static_libs: [
|
||||
"android.hardware.bluetooth.async",
|
||||
"libgmock",
|
||||
],
|
||||
cflags: [
|
||||
"-Wall",
|
||||
"-Werror",
|
||||
],
|
||||
test_suites: ["general-tests"],
|
||||
}
|
176
bluetooth/async/async_fd_watcher.cc
Normal file
176
bluetooth/async/async_fd_watcher.cc
Normal file
|
@ -0,0 +1,176 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "async_fd_watcher.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <condition_variable>
|
||||
#include <map>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
#include "fcntl.h"
|
||||
#include "log/log.h"
|
||||
#include "sys/select.h"
|
||||
#include "unistd.h"
|
||||
|
||||
static const int INVALID_FD = -1;
|
||||
|
||||
namespace android::hardware::bluetooth::async {
|
||||
|
||||
int AsyncFdWatcher::WatchFdForNonBlockingReads(
|
||||
int file_descriptor, const ReadCallback& on_read_fd_ready_callback) {
|
||||
// Add file descriptor and callback
|
||||
{
|
||||
std::unique_lock<std::mutex> guard(internal_mutex_);
|
||||
watched_fds_[file_descriptor] = on_read_fd_ready_callback;
|
||||
}
|
||||
|
||||
// Start the thread if not started yet
|
||||
return tryStartThread();
|
||||
}
|
||||
|
||||
int AsyncFdWatcher::ConfigureTimeout(
|
||||
const std::chrono::milliseconds timeout,
|
||||
const TimeoutCallback& on_timeout_callback) {
|
||||
// Add timeout and callback
|
||||
{
|
||||
std::unique_lock<std::mutex> guard(timeout_mutex_);
|
||||
timeout_cb_ = on_timeout_callback;
|
||||
timeout_ms_ = timeout;
|
||||
}
|
||||
|
||||
notifyThread();
|
||||
return 0;
|
||||
}
|
||||
|
||||
void AsyncFdWatcher::StopWatchingFileDescriptors() { stopThread(); }
|
||||
|
||||
AsyncFdWatcher::~AsyncFdWatcher() {}
|
||||
|
||||
// Make sure to call this with at least one file descriptor ready to be
|
||||
// watched upon or the thread routine will return immediately
|
||||
int AsyncFdWatcher::tryStartThread() {
|
||||
if (std::atomic_exchange(&running_, true)) return 0;
|
||||
|
||||
// Set up the communication channel
|
||||
int pipe_fds[2];
|
||||
if (pipe2(pipe_fds, O_NONBLOCK)) return -1;
|
||||
|
||||
notification_listen_fd_ = pipe_fds[0];
|
||||
notification_write_fd_ = pipe_fds[1];
|
||||
|
||||
thread_ = std::thread([this]() { ThreadRoutine(); });
|
||||
if (!thread_.joinable()) return -1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int AsyncFdWatcher::stopThread() {
|
||||
if (!std::atomic_exchange(&running_, false)) return 0;
|
||||
|
||||
notifyThread();
|
||||
if (std::this_thread::get_id() != thread_.get_id()) {
|
||||
thread_.join();
|
||||
}
|
||||
|
||||
{
|
||||
std::unique_lock<std::mutex> guard(internal_mutex_);
|
||||
watched_fds_.clear();
|
||||
}
|
||||
|
||||
{
|
||||
std::unique_lock<std::mutex> guard(timeout_mutex_);
|
||||
timeout_cb_ = nullptr;
|
||||
}
|
||||
|
||||
close(notification_listen_fd_);
|
||||
close(notification_write_fd_);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int AsyncFdWatcher::notifyThread() {
|
||||
uint8_t buffer[] = {0};
|
||||
if (TEMP_FAILURE_RETRY(write(notification_write_fd_, &buffer, 1)) < 0) {
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void AsyncFdWatcher::ThreadRoutine() {
|
||||
while (running_) {
|
||||
fd_set read_fds;
|
||||
FD_ZERO(&read_fds);
|
||||
FD_SET(notification_listen_fd_, &read_fds);
|
||||
int max_read_fd = INVALID_FD;
|
||||
for (auto& it : watched_fds_) {
|
||||
FD_SET(it.first, &read_fds);
|
||||
max_read_fd = std::max(max_read_fd, it.first);
|
||||
}
|
||||
|
||||
struct timeval timeout;
|
||||
struct timeval* timeout_ptr = NULL;
|
||||
if (timeout_ms_ > std::chrono::milliseconds(0)) {
|
||||
timeout.tv_sec = timeout_ms_.count() / 1000;
|
||||
timeout.tv_usec = (timeout_ms_.count() % 1000) * 1000;
|
||||
timeout_ptr = &timeout;
|
||||
}
|
||||
|
||||
// Wait until there is data available to read on some FD.
|
||||
int nfds = std::max(notification_listen_fd_, max_read_fd);
|
||||
int retval = select(nfds + 1, &read_fds, NULL, NULL, timeout_ptr);
|
||||
|
||||
// There was some error.
|
||||
if (retval < 0) continue;
|
||||
|
||||
// Timeout.
|
||||
if (retval == 0) {
|
||||
// Allow the timeout callback to modify the timeout.
|
||||
TimeoutCallback saved_cb;
|
||||
{
|
||||
std::unique_lock<std::mutex> guard(timeout_mutex_);
|
||||
if (timeout_ms_ > std::chrono::milliseconds(0)) saved_cb = timeout_cb_;
|
||||
}
|
||||
if (saved_cb != nullptr) saved_cb();
|
||||
continue;
|
||||
}
|
||||
|
||||
// Read data from the notification FD.
|
||||
if (FD_ISSET(notification_listen_fd_, &read_fds)) {
|
||||
char buffer[] = {0};
|
||||
TEMP_FAILURE_RETRY(read(notification_listen_fd_, buffer, 1));
|
||||
continue;
|
||||
}
|
||||
|
||||
// Invoke the data ready callbacks if appropriate.
|
||||
{
|
||||
// Hold the mutex to make sure that the callbacks are still valid.
|
||||
std::unique_lock<std::mutex> guard(internal_mutex_);
|
||||
for (auto& it : watched_fds_) {
|
||||
if (FD_ISSET(it.first, &read_fds)) {
|
||||
it.second(it.first);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace android::hardware::bluetooth::async
|
60
bluetooth/async/async_fd_watcher.h
Normal file
60
bluetooth/async/async_fd_watcher.h
Normal file
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
|
||||
namespace android::hardware::bluetooth::async {
|
||||
|
||||
using ReadCallback = std::function<void(int)>;
|
||||
using TimeoutCallback = std::function<void(void)>;
|
||||
|
||||
class AsyncFdWatcher {
|
||||
public:
|
||||
AsyncFdWatcher() = default;
|
||||
~AsyncFdWatcher();
|
||||
|
||||
int WatchFdForNonBlockingReads(int file_descriptor,
|
||||
const ReadCallback& on_read_fd_ready_callback);
|
||||
int ConfigureTimeout(const std::chrono::milliseconds timeout,
|
||||
const TimeoutCallback& on_timeout_callback);
|
||||
void StopWatchingFileDescriptors();
|
||||
|
||||
private:
|
||||
AsyncFdWatcher(const AsyncFdWatcher&) = delete;
|
||||
AsyncFdWatcher& operator=(const AsyncFdWatcher&) = delete;
|
||||
|
||||
int tryStartThread();
|
||||
int stopThread();
|
||||
int notifyThread();
|
||||
void ThreadRoutine();
|
||||
|
||||
std::atomic_bool running_{false};
|
||||
std::thread thread_;
|
||||
std::mutex internal_mutex_;
|
||||
std::mutex timeout_mutex_;
|
||||
|
||||
std::map<int, ReadCallback> watched_fds_;
|
||||
int notification_listen_fd_;
|
||||
int notification_write_fd_;
|
||||
TimeoutCallback timeout_cb_;
|
||||
std::chrono::milliseconds timeout_ms_;
|
||||
};
|
||||
|
||||
} // namespace android::hardware::bluetooth::async
|
377
bluetooth/async/test/async_fd_watcher_unittest.cc
Normal file
377
bluetooth/async/test/async_fd_watcher_unittest.cc
Normal file
|
@ -0,0 +1,377 @@
|
|||
/*
|
||||
* 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
|
46
bluetooth/hci/Android.bp
Normal file
46
bluetooth/hci/Android.bp
Normal file
|
@ -0,0 +1,46 @@
|
|||
package {
|
||||
default_applicable_licenses: ["hardware_interfaces_license"],
|
||||
}
|
||||
|
||||
cc_library_static {
|
||||
name: "android.hardware.bluetooth.hci",
|
||||
vendor_available: true,
|
||||
host_supported: true,
|
||||
defaults: ["hidl_defaults"],
|
||||
srcs: [
|
||||
"hci_packetizer.cc",
|
||||
"h4_protocol.cc",
|
||||
],
|
||||
export_include_dirs: ["."],
|
||||
shared_libs: [
|
||||
"libbase",
|
||||
"libhidlbase",
|
||||
"liblog",
|
||||
"libutils",
|
||||
],
|
||||
}
|
||||
|
||||
cc_test {
|
||||
name: "bluetooth-vendor-interface-hci-test",
|
||||
host_supported: true,
|
||||
defaults: ["hidl_defaults"],
|
||||
srcs: [
|
||||
"test/h4_protocol_unittest.cc",
|
||||
],
|
||||
shared_libs: [
|
||||
"libbase",
|
||||
"libhidlbase",
|
||||
"liblog",
|
||||
"libutils",
|
||||
],
|
||||
static_libs: [
|
||||
"android.hardware.bluetooth.async",
|
||||
"android.hardware.bluetooth.hci",
|
||||
"libgmock",
|
||||
],
|
||||
sanitize: {
|
||||
address: true,
|
||||
cfi: true,
|
||||
},
|
||||
test_suites: ["general-tests"],
|
||||
}
|
144
bluetooth/hci/h4_protocol.cc
Normal file
144
bluetooth/hci/h4_protocol.cc
Normal file
|
@ -0,0 +1,144 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "h4_protocol.h"
|
||||
|
||||
#define LOG_TAG "android.hardware.bluetooth.hci-h4"
|
||||
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <string.h>
|
||||
#include <sys/uio.h>
|
||||
|
||||
#include "log/log.h"
|
||||
|
||||
namespace android::hardware::bluetooth::hci {
|
||||
|
||||
H4Protocol::H4Protocol(int fd, PacketReadCallback cmd_cb,
|
||||
PacketReadCallback acl_cb, PacketReadCallback sco_cb,
|
||||
PacketReadCallback event_cb, PacketReadCallback iso_cb,
|
||||
DisconnectCallback disconnect_cb)
|
||||
: uart_fd_(fd),
|
||||
cmd_cb_(std::move(cmd_cb)),
|
||||
acl_cb_(std::move(acl_cb)),
|
||||
sco_cb_(std::move(sco_cb)),
|
||||
event_cb_(std::move(event_cb)),
|
||||
iso_cb_(std::move(iso_cb)),
|
||||
disconnect_cb_(std::move(disconnect_cb)) {}
|
||||
|
||||
size_t H4Protocol::Send(PacketType type, const std::vector<uint8_t>& vector) {
|
||||
return Send(type, vector.data(), vector.size());
|
||||
}
|
||||
|
||||
size_t H4Protocol::Send(PacketType type, const uint8_t* data, size_t length) {
|
||||
/* For HCI communication over USB dongle, multiple write results in
|
||||
* response timeout as driver expect type + data at once to process
|
||||
* the command, so using "writev"(for atomicity) here.
|
||||
*/
|
||||
struct iovec iov[2];
|
||||
ssize_t ret = 0;
|
||||
iov[0].iov_base = &type;
|
||||
iov[0].iov_len = sizeof(type);
|
||||
iov[1].iov_base = (void*)data;
|
||||
iov[1].iov_len = length;
|
||||
while (1) {
|
||||
ret = TEMP_FAILURE_RETRY(writev(uart_fd_, iov, 2));
|
||||
if (ret == -1) {
|
||||
if (errno == EAGAIN) {
|
||||
ALOGE("%s error writing to UART (%s)", __func__, strerror(errno));
|
||||
continue;
|
||||
}
|
||||
} else if (ret == 0) {
|
||||
// Nothing written :(
|
||||
ALOGE("%s zero bytes written - something went wrong...", __func__);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
size_t H4Protocol::OnPacketReady(const std::vector<uint8_t>& packet) {
|
||||
switch (hci_packet_type_) {
|
||||
case PacketType::COMMAND:
|
||||
cmd_cb_(packet);
|
||||
break;
|
||||
case PacketType::ACL_DATA:
|
||||
acl_cb_(packet);
|
||||
break;
|
||||
case PacketType::SCO_DATA:
|
||||
sco_cb_(packet);
|
||||
break;
|
||||
case PacketType::EVENT:
|
||||
event_cb_(packet);
|
||||
break;
|
||||
case PacketType::ISO_DATA:
|
||||
iso_cb_(packet);
|
||||
break;
|
||||
default: {
|
||||
LOG_ALWAYS_FATAL("Bad packet type 0x%x",
|
||||
static_cast<int>(hci_packet_type_));
|
||||
}
|
||||
}
|
||||
return packet.size();
|
||||
}
|
||||
|
||||
void H4Protocol::SendDataToPacketizer(uint8_t* buffer, size_t length) {
|
||||
std::vector<uint8_t> input_buffer{buffer, buffer + length};
|
||||
size_t buffer_offset = 0;
|
||||
while (buffer_offset < input_buffer.size()) {
|
||||
if (hci_packet_type_ == PacketType::UNKNOWN) {
|
||||
hci_packet_type_ =
|
||||
static_cast<PacketType>(input_buffer.data()[buffer_offset]);
|
||||
buffer_offset += 1;
|
||||
} else {
|
||||
bool packet_ready = hci_packetizer_.OnDataReady(
|
||||
hci_packet_type_, input_buffer, buffer_offset);
|
||||
if (packet_ready) {
|
||||
// Call packet callback and move offset.
|
||||
buffer_offset += OnPacketReady(hci_packetizer_.GetPacket());
|
||||
// Get ready for the next type byte.
|
||||
hci_packet_type_ = PacketType::UNKNOWN;
|
||||
} else {
|
||||
// The data was consumed, but there wasn't a packet.
|
||||
buffer_offset = input_buffer.size();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void H4Protocol::OnDataReady() {
|
||||
if (disconnected_) {
|
||||
return;
|
||||
}
|
||||
uint8_t buffer[kMaxPacketLength];
|
||||
ssize_t bytes_read =
|
||||
TEMP_FAILURE_RETRY(read(uart_fd_, buffer, kMaxPacketLength));
|
||||
if (bytes_read == 0) {
|
||||
ALOGI("No bytes read, calling the disconnect callback");
|
||||
disconnected_ = true;
|
||||
disconnect_cb_();
|
||||
return;
|
||||
}
|
||||
if (bytes_read < 0) {
|
||||
ALOGW("error reading from UART (%s)", strerror(errno));
|
||||
return;
|
||||
}
|
||||
SendDataToPacketizer(buffer, bytes_read);
|
||||
}
|
||||
|
||||
} // namespace android::hardware::bluetooth::hci
|
68
bluetooth/hci/h4_protocol.h
Normal file
68
bluetooth/hci/h4_protocol.h
Normal file
|
@ -0,0 +1,68 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "hci_internals.h"
|
||||
#include "hci_packetizer.h"
|
||||
|
||||
namespace android::hardware::bluetooth::hci {
|
||||
|
||||
using PacketReadCallback = std::function<void(const std::vector<uint8_t>&)>;
|
||||
using DisconnectCallback = std::function<void(void)>;
|
||||
|
||||
class H4Protocol {
|
||||
public:
|
||||
H4Protocol(int fd, PacketReadCallback cmd_cb, PacketReadCallback acl_cb,
|
||||
PacketReadCallback sco_cb, PacketReadCallback event_cb,
|
||||
PacketReadCallback iso_cb, DisconnectCallback disconnect_cb);
|
||||
|
||||
size_t Send(PacketType type, const uint8_t* data, size_t length);
|
||||
size_t Send(PacketType type, const std::vector<uint8_t>& data);
|
||||
|
||||
void OnDataReady();
|
||||
|
||||
protected:
|
||||
size_t OnPacketReady(const std::vector<uint8_t>& packet);
|
||||
void SendDataToPacketizer(uint8_t* buffer, size_t length);
|
||||
|
||||
private:
|
||||
int uart_fd_;
|
||||
bool disconnected_{false};
|
||||
|
||||
PacketReadCallback cmd_cb_;
|
||||
PacketReadCallback acl_cb_;
|
||||
PacketReadCallback sco_cb_;
|
||||
PacketReadCallback event_cb_;
|
||||
PacketReadCallback iso_cb_;
|
||||
DisconnectCallback disconnect_cb_;
|
||||
|
||||
PacketType hci_packet_type_{PacketType::UNKNOWN};
|
||||
HciPacketizer hci_packetizer_;
|
||||
|
||||
/**
|
||||
* Question : Why read in single chunk rather than multiple reads?
|
||||
* Answer: Using multiple reads does not work with some BT USB dongles.
|
||||
* Reading in single shot gives expected response.
|
||||
* ACL max length is 2 bytes, so using 64K as the buffer length.
|
||||
*/
|
||||
static constexpr size_t kMaxPacketLength = 64 * 1024;
|
||||
};
|
||||
|
||||
} // namespace android::hardware::bluetooth::hci
|
55
bluetooth/hci/hci_internals.h
Normal file
55
bluetooth/hci/hci_internals.h
Normal file
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdlib>
|
||||
|
||||
namespace android::hardware::bluetooth::hci {
|
||||
|
||||
// HCI UART transport packet types (Volume 4, Part A, 2)
|
||||
enum class PacketType : uint8_t {
|
||||
UNKNOWN = 0,
|
||||
COMMAND = 1,
|
||||
ACL_DATA = 2,
|
||||
SCO_DATA = 3,
|
||||
EVENT = 4,
|
||||
ISO_DATA = 5,
|
||||
};
|
||||
|
||||
// 2 bytes for opcode, 1 byte for parameter length (Volume 4, Part E, 5.4.1)
|
||||
static constexpr size_t kCommandHeaderSize = 3;
|
||||
static constexpr size_t kCommandLengthOffset = 2;
|
||||
|
||||
// 2 bytes for handle, 2 bytes for data length (Volume 4, Part E, 5.4.2)
|
||||
static constexpr size_t kAclHeaderSize = 4;
|
||||
static constexpr size_t kAclLengthOffset = 2;
|
||||
|
||||
// 2 bytes for handle, 1 byte for data length (Volume 4, Part E, 5.4.3)
|
||||
static constexpr size_t kScoHeaderSize = 3;
|
||||
static constexpr size_t kScoLengthOffset = 2;
|
||||
|
||||
// 1 byte for event code, 1 byte for parameter length (Volume 4, Part E, 5.4.4)
|
||||
static constexpr size_t kEventHeaderSize = 2;
|
||||
static constexpr size_t kEventLengthOffset = 1;
|
||||
|
||||
// 2 bytes for handle, 2 bytes for data length (Volume 4, Part E, 5.4.5)
|
||||
static constexpr size_t kIsoHeaderSize = 4;
|
||||
static constexpr size_t kIsoLengthOffset = 2;
|
||||
|
||||
static constexpr size_t kMaxHeaderSize = kAclHeaderSize;
|
||||
|
||||
} // namespace android::hardware::bluetooth::hci
|
100
bluetooth/hci/hci_packetizer.cc
Normal file
100
bluetooth/hci/hci_packetizer.cc
Normal file
|
@ -0,0 +1,100 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "hci_packetizer.h"
|
||||
|
||||
#define LOG_TAG "android.hardware.bluetooth.hci-packetizer"
|
||||
#include "log/log.h"
|
||||
|
||||
namespace android::hardware::bluetooth::hci {
|
||||
|
||||
namespace {
|
||||
|
||||
const size_t header_size_for_type[] = {0,
|
||||
kCommandHeaderSize,
|
||||
kAclHeaderSize,
|
||||
kScoHeaderSize,
|
||||
kEventHeaderSize,
|
||||
kIsoHeaderSize};
|
||||
const size_t packet_length_offset_for_type[] = {0,
|
||||
kCommandLengthOffset,
|
||||
kAclLengthOffset,
|
||||
kScoLengthOffset,
|
||||
kEventLengthOffset,
|
||||
kIsoLengthOffset};
|
||||
|
||||
size_t HciGetPacketLengthForType(PacketType type,
|
||||
const std::vector<uint8_t>& header) {
|
||||
size_t offset = packet_length_offset_for_type[static_cast<uint8_t>(type)];
|
||||
if (type != PacketType::ACL_DATA && type != PacketType::ISO_DATA) {
|
||||
return header[offset];
|
||||
}
|
||||
return (((header[offset + 1]) << 8) | header[offset]);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
const std::vector<uint8_t>& HciPacketizer::GetPacket() const { return packet_; }
|
||||
|
||||
bool HciPacketizer::OnDataReady(PacketType packet_type,
|
||||
const std::vector<uint8_t>& buffer,
|
||||
size_t offset) {
|
||||
bool packet_completed = false;
|
||||
size_t bytes_available = buffer.size() - offset;
|
||||
switch (state_) {
|
||||
case HCI_HEADER: {
|
||||
size_t header_size =
|
||||
header_size_for_type[static_cast<size_t>(packet_type)];
|
||||
if (bytes_remaining_ == 0) {
|
||||
bytes_remaining_ = header_size;
|
||||
packet_.clear();
|
||||
}
|
||||
size_t bytes_to_copy = std::min(bytes_remaining_, bytes_available);
|
||||
packet_.insert(packet_.end(), buffer.begin() + offset,
|
||||
buffer.begin() + offset + bytes_to_copy);
|
||||
bytes_remaining_ -= bytes_to_copy;
|
||||
bytes_available -= bytes_to_copy;
|
||||
if (bytes_remaining_ == 0) {
|
||||
bytes_remaining_ = HciGetPacketLengthForType(packet_type, packet_);
|
||||
if (bytes_remaining_ > 0) {
|
||||
state_ = HCI_PAYLOAD;
|
||||
if (bytes_available > 0) {
|
||||
packet_completed =
|
||||
OnDataReady(packet_type, buffer, offset + bytes_to_copy);
|
||||
}
|
||||
} else {
|
||||
packet_completed = true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case HCI_PAYLOAD: {
|
||||
size_t bytes_to_copy = std::min(bytes_remaining_, bytes_available);
|
||||
packet_.insert(packet_.end(), buffer.begin() + offset,
|
||||
buffer.begin() + offset + bytes_to_copy);
|
||||
bytes_remaining_ -= bytes_to_copy;
|
||||
if (bytes_remaining_ == 0) {
|
||||
state_ = HCI_HEADER;
|
||||
packet_completed = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return packet_completed;
|
||||
}
|
||||
|
||||
} // namespace android::hardware::bluetooth::hci
|
41
bluetooth/hci/hci_packetizer.h
Normal file
41
bluetooth/hci/hci_packetizer.h
Normal file
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "hci_internals.h"
|
||||
|
||||
namespace android::hardware::bluetooth::hci {
|
||||
|
||||
class HciPacketizer {
|
||||
public:
|
||||
HciPacketizer() = default;
|
||||
bool OnDataReady(PacketType packet_type, const std::vector<uint8_t>& data,
|
||||
size_t offset);
|
||||
const std::vector<uint8_t>& GetPacket() const;
|
||||
|
||||
protected:
|
||||
enum State { HCI_HEADER, HCI_PAYLOAD };
|
||||
State state_{HCI_HEADER};
|
||||
std::vector<uint8_t> packet_;
|
||||
size_t bytes_remaining_{0};
|
||||
};
|
||||
|
||||
} // namespace android::hardware::bluetooth::hci
|
441
bluetooth/hci/test/h4_protocol_unittest.cc
Normal file
441
bluetooth/hci/test/h4_protocol_unittest.cc
Normal file
|
@ -0,0 +1,441 @@
|
|||
/*
|
||||
* 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 "bt_h4_unittest"
|
||||
|
||||
#include "h4_protocol.h"
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <log/log.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <future>
|
||||
#include <vector>
|
||||
|
||||
#include "async_fd_watcher.h"
|
||||
#include "log/log.h"
|
||||
|
||||
using android::hardware::bluetooth::async::AsyncFdWatcher;
|
||||
using namespace android::hardware::bluetooth::hci;
|
||||
using ::testing::Eq;
|
||||
|
||||
static char sample_data1[100] = "A point is that which has no part.";
|
||||
static char sample_data2[100] = "A line is breadthless length.";
|
||||
static char sample_data3[100] = "The ends of a line are points.";
|
||||
static char sample_data4[100] =
|
||||
"A plane surface is a surface which lies evenly with the straight ...";
|
||||
static char acl_data[100] =
|
||||
"A straight line is a line which lies evenly with the points on itself.";
|
||||
static char sco_data[100] =
|
||||
"A surface is that which has length and breadth only.";
|
||||
static char event_data[100] = "The edges of a surface are lines.";
|
||||
static char iso_data[100] =
|
||||
"A plane angle is the inclination to one another of two lines in a ...";
|
||||
|
||||
MATCHER_P3(PacketMatches, header_, header_length, payload,
|
||||
"Match header_length bytes of header and then the payload") {
|
||||
size_t payload_length = strlen(payload);
|
||||
if (header_length + payload_length != arg.size()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (memcmp(header_, arg.data(), header_length) != 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return memcmp(payload, arg.data() + header_length, payload_length) == 0;
|
||||
};
|
||||
|
||||
ACTION_P(Notify, barrier) {
|
||||
ALOGD("%s", __func__);
|
||||
barrier->set_value();
|
||||
}
|
||||
|
||||
class H4ProtocolTest : public ::testing::Test {
|
||||
protected:
|
||||
void SetUp() override {
|
||||
ALOGD("%s", __func__);
|
||||
|
||||
int sockfd[2];
|
||||
socketpair(AF_LOCAL, SOCK_STREAM, 0, sockfd);
|
||||
chip_uart_fd_ = sockfd[1];
|
||||
stack_uart_fd_ = sockfd[0];
|
||||
h4_hci_ = std::make_shared<H4Protocol>(
|
||||
stack_uart_fd_, cmd_cb_.AsStdFunction(), acl_cb_.AsStdFunction(),
|
||||
sco_cb_.AsStdFunction(), event_cb_.AsStdFunction(),
|
||||
iso_cb_.AsStdFunction(), disconnect_cb_.AsStdFunction());
|
||||
}
|
||||
|
||||
void TearDown() override {
|
||||
close(stack_uart_fd_);
|
||||
close(chip_uart_fd_);
|
||||
}
|
||||
|
||||
virtual void CallDataReady() { h4_hci_->OnDataReady(); }
|
||||
|
||||
void SendAndReadUartOutbound(PacketType type, char* data) {
|
||||
ALOGD("%s sending", __func__);
|
||||
int data_length = strlen(data);
|
||||
h4_hci_->Send(type, (uint8_t*)data, data_length);
|
||||
|
||||
int uart_length = data_length + 1; // + 1 for data type code
|
||||
int i;
|
||||
|
||||
ALOGD("%s reading", __func__);
|
||||
for (i = 0; i < uart_length; i++) {
|
||||
fd_set read_fds;
|
||||
FD_ZERO(&read_fds);
|
||||
FD_SET(chip_uart_fd_, &read_fds);
|
||||
TEMP_FAILURE_RETRY(
|
||||
select(chip_uart_fd_ + 1, &read_fds, nullptr, nullptr, nullptr));
|
||||
|
||||
char byte;
|
||||
TEMP_FAILURE_RETRY(read(chip_uart_fd_, &byte, 1));
|
||||
|
||||
EXPECT_EQ(i == 0 ? static_cast<uint8_t>(type) : data[i - 1], byte);
|
||||
}
|
||||
|
||||
EXPECT_EQ(i, uart_length);
|
||||
}
|
||||
|
||||
void ExpectInboundAclData(char* payload, std::promise<void>* promise) {
|
||||
// h4 type[1] + handle[2] + size[2]
|
||||
header_[0] = static_cast<uint8_t>(PacketType::ACL_DATA);
|
||||
header_[1] = 19;
|
||||
header_[2] = 92;
|
||||
int length = strlen(payload);
|
||||
header_[3] = length & 0xFF;
|
||||
header_[4] = (length >> 8) & 0xFF;
|
||||
ALOGD("(%d bytes) %s", length, payload);
|
||||
|
||||
EXPECT_CALL(acl_cb_,
|
||||
Call(PacketMatches(header_ + 1, kAclHeaderSize, payload)))
|
||||
.WillOnce(Notify(promise));
|
||||
}
|
||||
|
||||
void WaitForTimeout(size_t timeout_ms, std::promise<void>* promise) {
|
||||
auto future = promise->get_future();
|
||||
auto status = future.wait_for(std::chrono::milliseconds(timeout_ms));
|
||||
EXPECT_EQ(status, std::future_status::ready);
|
||||
}
|
||||
|
||||
void WriteInboundAclData(char* payload) {
|
||||
// Use the header_ computed in ExpectInboundAclData
|
||||
TEMP_FAILURE_RETRY(write(chip_uart_fd_, header_, kAclHeaderSize + 1));
|
||||
TEMP_FAILURE_RETRY(write(chip_uart_fd_, payload, strlen(payload)));
|
||||
}
|
||||
|
||||
void ExpectInboundScoData(char* payload, std::promise<void>* promise) {
|
||||
// h4 type[1] + handle[2] + size[1]
|
||||
header_[0] = static_cast<uint8_t>(PacketType::SCO_DATA);
|
||||
header_[1] = 20;
|
||||
header_[2] = 17;
|
||||
header_[3] = strlen(payload) & 0xFF;
|
||||
EXPECT_CALL(sco_cb_,
|
||||
Call(PacketMatches(header_ + 1, kScoHeaderSize, payload)))
|
||||
.WillOnce(Notify(promise));
|
||||
}
|
||||
|
||||
void WriteInboundScoData(char* payload) {
|
||||
// Use the header_ computed in ExpectInboundScoData
|
||||
ALOGD("%s writing", __func__);
|
||||
TEMP_FAILURE_RETRY(write(chip_uart_fd_, header_, kScoHeaderSize + 1));
|
||||
TEMP_FAILURE_RETRY(write(chip_uart_fd_, payload, strlen(payload)));
|
||||
}
|
||||
|
||||
void ExpectInboundEvent(char* payload, std::promise<void>* promise) {
|
||||
// h4 type[1] + event_code[1] + size[1]
|
||||
header_[0] = static_cast<uint8_t>(PacketType::EVENT);
|
||||
header_[1] = 9;
|
||||
header_[2] = strlen(payload) & 0xFF;
|
||||
EXPECT_CALL(event_cb_,
|
||||
Call(PacketMatches(header_ + 1, kEventHeaderSize, payload)))
|
||||
.WillOnce(Notify(promise));
|
||||
}
|
||||
|
||||
void WriteInboundEvent(char* payload) {
|
||||
// Use the header_ computed in ExpectInboundEvent
|
||||
char preamble[3] = {static_cast<uint8_t>(PacketType::EVENT), 9, 0};
|
||||
preamble[2] = strlen(payload) & 0xFF;
|
||||
ALOGD("%s writing", __func__);
|
||||
TEMP_FAILURE_RETRY(write(chip_uart_fd_, header_, kEventHeaderSize + 1));
|
||||
TEMP_FAILURE_RETRY(write(chip_uart_fd_, payload, strlen(payload)));
|
||||
}
|
||||
|
||||
void ExpectInboundIsoData(char* payload, std::promise<void>* promise) {
|
||||
// h4 type[1] + handle[2] + size[1]
|
||||
header_[0] = static_cast<uint8_t>(PacketType::ISO_DATA);
|
||||
header_[1] = 19;
|
||||
header_[2] = 92;
|
||||
int length = strlen(payload);
|
||||
header_[3] = length & 0xFF;
|
||||
header_[4] = (length >> 8) & 0x3F;
|
||||
|
||||
EXPECT_CALL(iso_cb_,
|
||||
Call(PacketMatches(header_ + 1, kIsoHeaderSize, payload)))
|
||||
.WillOnce(Notify(promise));
|
||||
}
|
||||
|
||||
void WriteInboundIsoData(char* payload) {
|
||||
// Use the header_ computed in ExpectInboundIsoData
|
||||
ALOGD("%s writing", __func__);
|
||||
TEMP_FAILURE_RETRY(write(chip_uart_fd_, header_, kIsoHeaderSize + 1));
|
||||
TEMP_FAILURE_RETRY(write(chip_uart_fd_, payload, strlen(payload)));
|
||||
}
|
||||
|
||||
void WriteAndExpectManyInboundAclDataPackets(char* payload) {
|
||||
size_t kNumPackets = 20;
|
||||
// h4 type[1] + handle[2] + size[2]
|
||||
char preamble[5] = {static_cast<uint8_t>(PacketType::ACL_DATA), 19, 92, 0,
|
||||
0};
|
||||
int length = strlen(payload);
|
||||
preamble[3] = length & 0xFF;
|
||||
preamble[4] = (length >> 8) & 0xFF;
|
||||
|
||||
EXPECT_CALL(acl_cb_, Call(PacketMatches(preamble + 1, sizeof(preamble) - 1,
|
||||
payload)))
|
||||
.Times(kNumPackets);
|
||||
|
||||
for (size_t i = 0; i < kNumPackets; i++) {
|
||||
TEMP_FAILURE_RETRY(write(chip_uart_fd_, preamble, sizeof(preamble)));
|
||||
TEMP_FAILURE_RETRY(write(chip_uart_fd_, payload, strlen(payload)));
|
||||
}
|
||||
|
||||
CallDataReady();
|
||||
}
|
||||
|
||||
testing::MockFunction<void(const std::vector<uint8_t>&)> cmd_cb_;
|
||||
testing::MockFunction<void(const std::vector<uint8_t>&)> event_cb_;
|
||||
testing::MockFunction<void(const std::vector<uint8_t>&)> acl_cb_;
|
||||
testing::MockFunction<void(const std::vector<uint8_t>&)> sco_cb_;
|
||||
testing::MockFunction<void(const std::vector<uint8_t>&)> iso_cb_;
|
||||
testing::MockFunction<void(void)> disconnect_cb_;
|
||||
std::shared_ptr<H4Protocol> h4_hci_;
|
||||
int chip_uart_fd_;
|
||||
int stack_uart_fd_;
|
||||
|
||||
char header_[5];
|
||||
};
|
||||
|
||||
// Test sending data sends correct data onto the UART
|
||||
TEST_F(H4ProtocolTest, TestSends) {
|
||||
SendAndReadUartOutbound(PacketType::COMMAND, sample_data1);
|
||||
SendAndReadUartOutbound(PacketType::ACL_DATA, sample_data2);
|
||||
SendAndReadUartOutbound(PacketType::SCO_DATA, sample_data3);
|
||||
SendAndReadUartOutbound(PacketType::ISO_DATA, sample_data4);
|
||||
}
|
||||
|
||||
// Ensure we properly parse data coming from the UART
|
||||
TEST_F(H4ProtocolTest, TestReads) {
|
||||
std::promise<void> acl_promise;
|
||||
std::promise<void> sco_promise;
|
||||
std::promise<void> event_promise;
|
||||
std::promise<void> iso_promise;
|
||||
|
||||
ExpectInboundAclData(acl_data, &acl_promise);
|
||||
WriteInboundAclData(acl_data);
|
||||
CallDataReady();
|
||||
ExpectInboundScoData(sco_data, &sco_promise);
|
||||
WriteInboundScoData(sco_data);
|
||||
CallDataReady();
|
||||
ExpectInboundEvent(event_data, &event_promise);
|
||||
WriteInboundEvent(event_data);
|
||||
CallDataReady();
|
||||
ExpectInboundIsoData(iso_data, &iso_promise);
|
||||
WriteInboundIsoData(iso_data);
|
||||
CallDataReady();
|
||||
|
||||
WaitForTimeout(100, &acl_promise);
|
||||
WaitForTimeout(100, &sco_promise);
|
||||
WaitForTimeout(100, &event_promise);
|
||||
WaitForTimeout(100, &iso_promise);
|
||||
}
|
||||
|
||||
TEST_F(H4ProtocolTest, TestMultiplePackets) {
|
||||
WriteAndExpectManyInboundAclDataPackets(sco_data);
|
||||
}
|
||||
|
||||
TEST_F(H4ProtocolTest, TestDisconnect) {
|
||||
EXPECT_CALL(disconnect_cb_, Call());
|
||||
close(chip_uart_fd_);
|
||||
CallDataReady();
|
||||
}
|
||||
|
||||
TEST_F(H4ProtocolTest, TestPartialWrites) {
|
||||
size_t payload_len = strlen(acl_data);
|
||||
const size_t kNumIntervals = payload_len + 1;
|
||||
// h4 type[1] + handle[2] + size[2]
|
||||
header_[0] = static_cast<uint8_t>(PacketType::ACL_DATA);
|
||||
header_[1] = 19;
|
||||
header_[2] = 92;
|
||||
header_[3] = payload_len & 0xFF;
|
||||
header_[4] = (payload_len >> 8) & 0xFF;
|
||||
|
||||
EXPECT_CALL(acl_cb_,
|
||||
Call(PacketMatches(header_ + 1, sizeof(header_) - 1, acl_data)))
|
||||
.Times(kNumIntervals);
|
||||
|
||||
for (size_t interval = 1; interval < kNumIntervals + 1; interval++) {
|
||||
// Use the header_ data that expect already set up.
|
||||
if (interval < kAclHeaderSize) {
|
||||
TEMP_FAILURE_RETRY(write(chip_uart_fd_, header_, interval));
|
||||
CallDataReady();
|
||||
TEMP_FAILURE_RETRY(write(chip_uart_fd_, header_ + interval,
|
||||
kAclHeaderSize + 1 - interval));
|
||||
CallDataReady();
|
||||
} else {
|
||||
TEMP_FAILURE_RETRY(write(chip_uart_fd_, header_, kAclHeaderSize + 1));
|
||||
CallDataReady();
|
||||
}
|
||||
|
||||
for (size_t bytes = 0; bytes + interval <= payload_len; bytes += interval) {
|
||||
TEMP_FAILURE_RETRY(write(chip_uart_fd_, acl_data + bytes, interval));
|
||||
CallDataReady();
|
||||
}
|
||||
size_t extra_bytes = payload_len % interval;
|
||||
if (extra_bytes) {
|
||||
TEMP_FAILURE_RETRY(write(
|
||||
chip_uart_fd_, acl_data + payload_len - extra_bytes, extra_bytes));
|
||||
CallDataReady();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class H4ProtocolAsyncTest : public H4ProtocolTest {
|
||||
protected:
|
||||
void SetUp() override {
|
||||
H4ProtocolTest::SetUp();
|
||||
fd_watcher_.WatchFdForNonBlockingReads(
|
||||
stack_uart_fd_, [this](int) { h4_hci_->OnDataReady(); });
|
||||
}
|
||||
|
||||
void TearDown() override { fd_watcher_.StopWatchingFileDescriptors(); }
|
||||
|
||||
void CallDataReady() override {
|
||||
// The Async test can't call data ready.
|
||||
FAIL();
|
||||
}
|
||||
|
||||
void SendAndReadUartOutbound(PacketType type, char* data) {
|
||||
ALOGD("%s sending", __func__);
|
||||
int data_length = strlen(data);
|
||||
h4_hci_->Send(type, (uint8_t*)data, data_length);
|
||||
|
||||
int uart_length = data_length + 1; // + 1 for data type code
|
||||
int i;
|
||||
|
||||
ALOGD("%s reading", __func__);
|
||||
for (i = 0; i < uart_length; i++) {
|
||||
fd_set read_fds;
|
||||
FD_ZERO(&read_fds);
|
||||
FD_SET(chip_uart_fd_, &read_fds);
|
||||
TEMP_FAILURE_RETRY(
|
||||
select(chip_uart_fd_ + 1, &read_fds, nullptr, nullptr, nullptr));
|
||||
|
||||
char byte;
|
||||
TEMP_FAILURE_RETRY(read(chip_uart_fd_, &byte, 1));
|
||||
|
||||
EXPECT_EQ(i == 0 ? static_cast<uint8_t>(type) : data[i - 1], byte);
|
||||
}
|
||||
|
||||
EXPECT_EQ(i, uart_length);
|
||||
}
|
||||
|
||||
void WriteAndExpectInboundAclData(char* payload) {
|
||||
std::promise<void> promise;
|
||||
ExpectInboundAclData(payload, &promise);
|
||||
WriteInboundAclData(payload);
|
||||
WaitForTimeout(100, &promise);
|
||||
}
|
||||
|
||||
void WriteAndExpectInboundScoData(char* payload) {
|
||||
std::promise<void> promise;
|
||||
ExpectInboundScoData(payload, &promise);
|
||||
WriteInboundScoData(payload);
|
||||
WaitForTimeout(100, &promise);
|
||||
}
|
||||
|
||||
void WriteAndExpectInboundEvent(char* payload) {
|
||||
std::promise<void> promise;
|
||||
ExpectInboundEvent(payload, &promise);
|
||||
WriteInboundEvent(payload);
|
||||
WaitForTimeout(100, &promise);
|
||||
}
|
||||
|
||||
void WriteAndExpectInboundIsoData(char* payload) {
|
||||
std::promise<void> promise;
|
||||
ExpectInboundIsoData(payload, &promise);
|
||||
WriteInboundIsoData(payload);
|
||||
WaitForTimeout(100, &promise);
|
||||
}
|
||||
|
||||
void WriteAndExpectManyInboundAclDataPackets(char* payload) {
|
||||
const size_t kNumPackets = 20;
|
||||
// h4 type[1] + handle[2] + size[2]
|
||||
char preamble[5] = {static_cast<uint8_t>(PacketType::ACL_DATA), 19, 92, 0,
|
||||
0};
|
||||
int length = strlen(payload);
|
||||
preamble[3] = length & 0xFF;
|
||||
preamble[4] = (length >> 8) & 0xFF;
|
||||
|
||||
EXPECT_CALL(acl_cb_, Call(PacketMatches(preamble + 1, sizeof(preamble) - 1,
|
||||
payload)))
|
||||
.Times(kNumPackets);
|
||||
|
||||
for (size_t i = 0; i < kNumPackets; i++) {
|
||||
TEMP_FAILURE_RETRY(write(chip_uart_fd_, preamble, sizeof(preamble)));
|
||||
TEMP_FAILURE_RETRY(write(chip_uart_fd_, payload, strlen(payload)));
|
||||
}
|
||||
|
||||
WriteAndExpectInboundEvent(event_data);
|
||||
}
|
||||
|
||||
AsyncFdWatcher fd_watcher_;
|
||||
};
|
||||
|
||||
// Test sending data sends correct data onto the UART
|
||||
TEST_F(H4ProtocolAsyncTest, TestSends) {
|
||||
SendAndReadUartOutbound(PacketType::COMMAND, sample_data1);
|
||||
SendAndReadUartOutbound(PacketType::ACL_DATA, sample_data2);
|
||||
SendAndReadUartOutbound(PacketType::SCO_DATA, sample_data3);
|
||||
SendAndReadUartOutbound(PacketType::ISO_DATA, sample_data4);
|
||||
}
|
||||
|
||||
// Ensure we properly parse data coming from the UART
|
||||
TEST_F(H4ProtocolAsyncTest, TestReads) {
|
||||
WriteAndExpectInboundAclData(acl_data);
|
||||
WriteAndExpectInboundScoData(sco_data);
|
||||
WriteAndExpectInboundEvent(event_data);
|
||||
WriteAndExpectInboundIsoData(iso_data);
|
||||
}
|
||||
|
||||
TEST_F(H4ProtocolAsyncTest, TestMultiplePackets) {
|
||||
WriteAndExpectManyInboundAclDataPackets(sco_data);
|
||||
}
|
||||
|
||||
TEST_F(H4ProtocolAsyncTest, TestDisconnect) {
|
||||
std::promise<void> promise;
|
||||
EXPECT_CALL(disconnect_cb_, Call()).WillOnce(Notify(&promise));
|
||||
close(chip_uart_fd_);
|
||||
|
||||
// Fail if it takes longer than 100 ms.
|
||||
WaitForTimeout(100, &promise);
|
||||
}
|
Loading…
Reference in a new issue