Start with async and HCI

Bug: 205758693
Test: unit tests
Change-Id: I16a8be44bce5f2d233582ab6db17c30d068fa9c0
This commit is contained in:
Myles Watson 2022-09-30 06:20:50 -07:00
parent 10d29e4934
commit e4501e549c
12 changed files with 1550 additions and 0 deletions

View file

@ -3,6 +3,7 @@
aidl_interface {
name: "android.hardware.bluetooth",
vendor_available: true,
host_supported: true,
srcs: ["android/hardware/bluetooth/*.aidl"],
stability: "vintf",
backend: {

View 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"],
}

View 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

View 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

View 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
View 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"],
}

View 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

View 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

View 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

View 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

View 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

View 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);
}