blob: 2a77d20f062bfe29041a748aab750694fd82c8b9 [file] [log] [blame]
// Copyright 2021 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "gatt2_remote_service_server.h"
#include <fuchsia/bluetooth/gatt2/cpp/fidl_test_base.h>
#include <algorithm>
#include <optional>
#include "fuchsia/bluetooth/gatt2/cpp/fidl.h"
#include "gtest/gtest.h"
#include "src/connectivity/bluetooth/core/bt-host/fidl/fake_gatt_fixture.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/gatt/remote_service.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/testing/test_helpers.h"
namespace bthost {
namespace {
namespace fbg = fuchsia::bluetooth::gatt2;
constexpr bt::PeerId kPeerId(1);
constexpr bt::att::Handle kServiceStartHandle = 0x0001;
constexpr bt::att::Handle kServiceEndHandle = 0xFFFE;
const bt::UUID kServiceUuid(uint16_t{0x180D});
const bt::UUID kCharacteristicUuid(uint16_t{0x180E});
const bt::UUID kDescriptorUuid(uint16_t{0x180F});
class Gatt2RemoteServiceServerTest : public bt::fidl::testing::FakeGattFixture {
public:
Gatt2RemoteServiceServerTest() = default;
~Gatt2RemoteServiceServerTest() override = default;
void SetUp() override {
{
auto [svc, client] = fake_gatt()->AddPeerService(
kPeerId, bt::gatt::ServiceData(bt::gatt::ServiceKind::PRIMARY, kServiceStartHandle,
kServiceEndHandle, kServiceUuid));
service_ = std::move(svc);
fake_client_ = std::move(client);
}
fidl::InterfaceHandle<fbg::RemoteService> handle;
server_ = std::make_unique<Gatt2RemoteServiceServer>(service_, gatt()->GetWeakPtr(), kPeerId,
handle.NewRequest());
proxy_.Bind(std::move(handle));
}
void TearDown() override {
// Clear any previous expectations that are based on the ATT Write Request,
// so that write requests sent during RemoteService::ShutDown() are ignored.
fake_client()->set_write_request_callback({});
bt::fidl::testing::FakeGattFixture::TearDown();
}
pw::async::HeapDispatcher& heap_dispatcher() { return heap_dispatcher_; }
protected:
const bt::gatt::testing::FakeClient::WeakPtr& fake_client() const {
BT_ASSERT(fake_client_.is_alive());
return fake_client_;
}
fbg::RemoteServicePtr& service_proxy() { return proxy_; }
bt::gatt::RemoteService::WeakPtr service() { return service_; }
void DestroyServer() { server_.reset(); }
private:
std::unique_ptr<Gatt2RemoteServiceServer> server_;
fbg::RemoteServicePtr proxy_;
bt::gatt::RemoteService::WeakPtr service_;
bt::gatt::testing::FakeClient::WeakPtr fake_client_;
pw::async::fuchsia::FuchsiaDispatcher pw_dispatcher_{dispatcher()};
pw::async::HeapDispatcher heap_dispatcher_{pw_dispatcher_};
BT_DISALLOW_COPY_ASSIGN_AND_MOVE(Gatt2RemoteServiceServerTest);
};
TEST_F(Gatt2RemoteServiceServerTest, DiscoverCharacteristics) {
bt::gatt::Properties properties =
static_cast<bt::gatt::Properties>(bt::gatt::Property::kAuthenticatedSignedWrites) |
static_cast<bt::gatt::Properties>(bt::gatt::Property::kExtendedProperties);
bt::gatt::ExtendedProperties ext_properties =
static_cast<bt::gatt::ExtendedProperties>(bt::gatt::ExtendedProperty::kReliableWrite) |
static_cast<bt::gatt::ExtendedProperties>(bt::gatt::ExtendedProperty::kWritableAuxiliaries);
constexpr bt::att::Handle kCharacteristicHandle(kServiceStartHandle + 1);
constexpr bt::att::Handle kCharacteristicValueHandle(kCharacteristicHandle + 1);
const bt::UUID kCharacteristicUuid(uint16_t{0x0000});
bt::gatt::CharacteristicData characteristic(properties, ext_properties, kCharacteristicHandle,
kCharacteristicValueHandle, kCharacteristicUuid);
fake_client()->set_characteristics({characteristic});
constexpr bt::att::Handle kDescriptorHandle(kCharacteristicValueHandle + 1);
const bt::UUID kDescriptorUuid(uint16_t{0x0001});
bt::gatt::DescriptorData descriptor(kDescriptorHandle, kDescriptorUuid);
fake_client()->set_descriptors({descriptor});
std::optional<std::vector<fbg::Characteristic>> fidl_characteristics;
service_proxy()->DiscoverCharacteristics(
[&](std::vector<fbg::Characteristic> chars) { fidl_characteristics = std::move(chars); });
RunLoopUntilIdle();
ASSERT_TRUE(fidl_characteristics.has_value());
ASSERT_EQ(fidl_characteristics->size(), 1u);
const fbg::Characteristic& fidl_characteristic = fidl_characteristics->front();
ASSERT_TRUE(fidl_characteristic.has_handle());
EXPECT_EQ(fidl_characteristic.handle().value, static_cast<uint64_t>(kCharacteristicValueHandle));
ASSERT_TRUE(fidl_characteristic.has_type());
EXPECT_EQ(fidl_characteristic.type().value, kCharacteristicUuid.value());
ASSERT_TRUE(fidl_characteristic.has_properties());
EXPECT_EQ(fidl_characteristic.properties(),
fbg::CharacteristicPropertyBits::AUTHENTICATED_SIGNED_WRITES |
fbg::CharacteristicPropertyBits::RELIABLE_WRITE |
fbg::CharacteristicPropertyBits::WRITABLE_AUXILIARIES);
EXPECT_FALSE(fidl_characteristic.has_permissions());
ASSERT_TRUE(fidl_characteristic.has_descriptors());
ASSERT_EQ(fidl_characteristic.descriptors().size(), 1u);
const fbg::Descriptor& fidl_descriptor = fidl_characteristic.descriptors().front();
ASSERT_TRUE(fidl_descriptor.has_handle());
EXPECT_EQ(fidl_descriptor.handle().value, static_cast<uint64_t>(kDescriptorHandle));
ASSERT_TRUE(fidl_descriptor.has_type());
EXPECT_EQ(fidl_descriptor.type().value, kDescriptorUuid.value());
EXPECT_FALSE(fidl_descriptor.has_permissions());
}
TEST_F(Gatt2RemoteServiceServerTest, DiscoverCharacteristicsWithNoDescriptors) {
bt::gatt::Properties properties = 0;
bt::gatt::ExtendedProperties ext_properties = 0;
constexpr bt::att::Handle kCharacteristicHandle(kServiceStartHandle + 1);
constexpr bt::att::Handle kCharacteristicValueHandle(kCharacteristicHandle + 1);
const bt::UUID kCharacteristicUuid(uint16_t{0x0000});
bt::gatt::CharacteristicData characteristic(properties, ext_properties, kCharacteristicHandle,
kCharacteristicValueHandle, kCharacteristicUuid);
fake_client()->set_characteristics({characteristic});
std::optional<std::vector<fbg::Characteristic>> fidl_characteristics;
service_proxy()->DiscoverCharacteristics(
[&](std::vector<fbg::Characteristic> chars) { fidl_characteristics = std::move(chars); });
RunLoopUntilIdle();
ASSERT_TRUE(fidl_characteristics.has_value());
ASSERT_EQ(fidl_characteristics->size(), 1u);
const fbg::Characteristic& fidl_characteristic = fidl_characteristics->front();
EXPECT_FALSE(fidl_characteristic.has_descriptors());
}
TEST_F(Gatt2RemoteServiceServerTest, ReadByTypeSuccess) {
constexpr bt::UUID kCharUuid(uint16_t{0xfefe});
constexpr bt::att::Handle kHandle = kServiceStartHandle;
const auto kValue = bt::StaticByteBuffer(0x00, 0x01, 0x02);
const std::vector<bt::gatt::Client::ReadByTypeValue> kValues = {
{kHandle, kValue.view(), /*maybe_truncated=*/false}};
size_t read_count = 0;
fake_client()->set_read_by_type_request_callback(
[&](const bt::UUID& type, bt::att::Handle start, bt::att::Handle end, auto callback) {
switch (read_count++) {
case 0:
callback(fit::ok(kValues));
break;
case 1:
callback(fit::error(bt::gatt::Client::ReadByTypeError{
bt::att::Error(bt::att::ErrorCode::kAttributeNotFound), start}));
break;
default:
FAIL();
}
});
std::optional<fbg::RemoteService_ReadByType_Result> fidl_result;
service_proxy()->ReadByType(fuchsia::bluetooth::Uuid{kCharUuid.value()},
[&](auto cb_result) { fidl_result = std::move(cb_result); });
RunLoopUntilIdle();
ASSERT_TRUE(fidl_result.has_value());
ASSERT_TRUE(fidl_result->is_response());
const auto& response = fidl_result->response();
ASSERT_EQ(1u, response.results.size());
const fbg::ReadByTypeResult& result0 = response.results[0];
ASSERT_TRUE(result0.has_handle());
EXPECT_EQ(result0.handle().value, static_cast<uint64_t>(kHandle));
EXPECT_FALSE(result0.has_error());
ASSERT_TRUE(result0.has_value());
const fbg::ReadValue& read_value = result0.value();
ASSERT_TRUE(read_value.has_handle());
EXPECT_EQ(read_value.handle().value, static_cast<uint64_t>(kHandle));
ASSERT_TRUE(read_value.has_maybe_truncated());
EXPECT_FALSE(read_value.maybe_truncated());
ASSERT_TRUE(read_value.has_value());
const std::vector<uint8_t>& value = read_value.value();
EXPECT_TRUE(ContainersEqual(bt::BufferView(value.data(), value.size()), kValue));
}
TEST_F(Gatt2RemoteServiceServerTest, ReadByTypeResultPermissionError) {
constexpr bt::UUID kCharUuid(uint16_t{0xfefe});
size_t read_count = 0;
fake_client()->set_read_by_type_request_callback(
[&](const bt::UUID& type, bt::att::Handle start, bt::att::Handle end, auto callback) {
ASSERT_EQ(0u, read_count++);
callback(fit::error(bt::gatt::Client::ReadByTypeError{
bt::att::Error(bt::att::ErrorCode::kInsufficientAuthorization), kServiceEndHandle}));
});
std::optional<fbg::RemoteService_ReadByType_Result> fidl_result;
service_proxy()->ReadByType(fuchsia::bluetooth::Uuid{kCharUuid.value()},
[&](auto cb_result) { fidl_result = std::move(cb_result); });
RunLoopUntilIdle();
ASSERT_TRUE(fidl_result.has_value());
ASSERT_TRUE(fidl_result->is_response());
const auto& response = fidl_result->response();
ASSERT_EQ(1u, response.results.size());
const fbg::ReadByTypeResult& result0 = response.results[0];
ASSERT_TRUE(result0.has_handle());
EXPECT_EQ(result0.handle().value, static_cast<uint64_t>(kServiceEndHandle));
EXPECT_FALSE(result0.has_value());
ASSERT_TRUE(result0.has_error());
EXPECT_EQ(fbg::Error::INSUFFICIENT_AUTHORIZATION, result0.error());
}
TEST_F(Gatt2RemoteServiceServerTest, ReadByTypeReturnsError) {
constexpr bt::UUID kCharUuid(uint16_t{0xfefe});
size_t read_count = 0;
fake_client()->set_read_by_type_request_callback(
[&](const bt::UUID& type, bt::att::Handle start, bt::att::Handle end, auto callback) {
switch (read_count++) {
case 0:
callback(fit::error(bt::gatt::Client::ReadByTypeError{
bt::Error(bt::HostError::kPacketMalformed), std::nullopt}));
break;
default:
FAIL();
}
});
std::optional<fbg::RemoteService_ReadByType_Result> fidl_result;
service_proxy()->ReadByType(fuchsia::bluetooth::Uuid{kCharUuid.value()},
[&](auto cb_result) { fidl_result = std::move(cb_result); });
RunLoopUntilIdle();
ASSERT_TRUE(fidl_result.has_value());
ASSERT_TRUE(fidl_result->is_err());
const auto& err = fidl_result->err();
EXPECT_EQ(fbg::Error::UNLIKELY_ERROR, err);
}
TEST_F(Gatt2RemoteServiceServerTest, ReadByTypeInvalidUuid) {
constexpr bt::UUID kCharUuid = bt::gatt::types::kCharacteristicDeclaration;
fake_client()->set_read_by_type_request_callback([&](const bt::UUID& type, bt::att::Handle start,
bt::att::Handle end,
auto callback) { FAIL(); });
std::optional<fbg::RemoteService_ReadByType_Result> fidl_result;
service_proxy()->ReadByType(fuchsia::bluetooth::Uuid{kCharUuid.value()},
[&](auto cb_result) { fidl_result = std::move(cb_result); });
RunLoopUntilIdle();
ASSERT_TRUE(fidl_result.has_value());
ASSERT_TRUE(fidl_result->is_err());
const auto& err = fidl_result->err();
EXPECT_EQ(fbg::Error::INVALID_PARAMETERS, err);
}
TEST_F(Gatt2RemoteServiceServerTest, ReadByTypeTooManyResults) {
constexpr bt::UUID kCharUuid(uint16_t{0xfefe});
const auto value = bt::StaticByteBuffer(0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06);
size_t read_count = 0;
fake_client()->set_read_by_type_request_callback(
[&](const bt::UUID& type, bt::att::Handle start, bt::att::Handle end, auto callback) {
read_count++;
// Ensure that more results are received than can fit in a channel. Each result is larger
// than the value payload, so receiving as many values as will fit in a channel is
// guaranteed to fill the channel and then some.
const size_t max_value_count = static_cast<size_t>(ZX_CHANNEL_MAX_MSG_BYTES) / value.size();
if (read_count == max_value_count) {
callback(fit::error(bt::gatt::Client::ReadByTypeError{
bt::att::Error(bt::att::ErrorCode::kAttributeNotFound), start}));
return;
}
// Dispatch callback to prevent recursing too deep and breaking the stack.
heap_dispatcher().Post([start, cb = std::move(callback), &value = value](
pw::async::Context /*ctx*/, pw::Status status) {
if (status.ok()) {
std::vector<bt::gatt::Client::ReadByTypeValue> values = {
{start, value.view(), /*maybe_truncated=*/false}};
cb(fit::ok(values));
}
});
});
std::optional<fbg::RemoteService_ReadByType_Result> fidl_result;
service_proxy()->ReadByType(fuchsia::bluetooth::Uuid{kCharUuid.value()},
[&](auto cb_result) { fidl_result = std::move(cb_result); });
RunLoopUntilIdle();
ASSERT_TRUE(fidl_result.has_value());
ASSERT_TRUE(fidl_result->is_err());
const auto& err = fidl_result->err();
EXPECT_EQ(fbg::Error::TOO_MANY_RESULTS, err);
}
TEST_F(Gatt2RemoteServiceServerTest, DiscoverAndReadShortCharacteristic) {
constexpr bt::att::Handle kHandle = 3;
constexpr bt::att::Handle kValueHandle = kHandle + 1;
const auto kValue = bt::StaticByteBuffer(0x00, 0x01, 0x02, 0x03, 0x04);
bt::gatt::CharacteristicData char_data(bt::gatt::Property::kRead, std::nullopt, kHandle,
kValueHandle, kServiceUuid);
fake_client()->set_characteristics({char_data});
std::optional<std::vector<fbg::Characteristic>> fidl_characteristics;
service_proxy()->DiscoverCharacteristics(
[&](std::vector<fbg::Characteristic> chars) { fidl_characteristics = std::move(chars); });
RunLoopUntilIdle();
ASSERT_TRUE(fidl_characteristics.has_value());
ASSERT_EQ(fidl_characteristics->size(), 1u);
const fbg::Characteristic& fidl_char = fidl_characteristics->front();
ASSERT_TRUE(fidl_char.has_handle());
size_t read_count = 0;
fake_client()->set_read_request_callback(
[&](bt::att::Handle handle, bt::gatt::Client::ReadCallback callback) {
read_count++;
EXPECT_EQ(handle, kValueHandle);
callback(fit::ok(), kValue, /*maybe_truncated=*/false);
});
fake_client()->set_read_blob_request_callback([](auto, auto, auto) { FAIL(); });
fbg::ReadOptions options = fbg::ReadOptions::WithShortRead(fbg::ShortReadOptions());
std::optional<fpromise::result<fbg::ReadValue, fbg::Error>> fidl_result;
service_proxy()->ReadCharacteristic(fidl_char.handle(), std::move(options),
[&](auto result) { fidl_result = std::move(result); });
RunLoopUntilIdle();
EXPECT_EQ(read_count, 1u);
ASSERT_TRUE(fidl_result.has_value());
ASSERT_TRUE(fidl_result->is_ok()) << static_cast<uint32_t>(fidl_result->error());
const fbg::ReadValue& read_value = fidl_result->value();
EXPECT_TRUE(ContainersEqual(kValue, read_value.value()));
EXPECT_FALSE(read_value.maybe_truncated());
}
TEST_F(Gatt2RemoteServiceServerTest, DiscoverAndReadLongCharacteristicWithOffsetAndMaxBytes) {
constexpr bt::att::Handle kHandle = 3;
constexpr bt::att::Handle kValueHandle = kHandle + 1;
const auto kValue = bt::StaticByteBuffer(0x00, 0x01, 0x02, 0x03, 0x04, 0x05);
constexpr uint16_t kOffset = 1;
constexpr uint16_t kMaxBytes = 3;
bt::gatt::CharacteristicData char_data(bt::gatt::Property::kRead, std::nullopt, kHandle,
kValueHandle, kServiceUuid);
fake_client()->set_characteristics({char_data});
std::optional<std::vector<fbg::Characteristic>> fidl_characteristics;
service_proxy()->DiscoverCharacteristics(
[&](std::vector<fbg::Characteristic> chars) { fidl_characteristics = std::move(chars); });
RunLoopUntilIdle();
ASSERT_TRUE(fidl_characteristics.has_value());
ASSERT_EQ(fidl_characteristics->size(), 1u);
const fbg::Characteristic& fidl_char = fidl_characteristics->front();
ASSERT_TRUE(fidl_char.has_handle());
fbg::LongReadOptions long_options;
long_options.set_offset(kOffset);
long_options.set_max_bytes(kMaxBytes);
fbg::ReadOptions read_options;
read_options.set_long_read(std::move(long_options));
size_t read_count = 0;
fake_client()->set_read_request_callback([](auto, auto) { FAIL(); });
fake_client()->set_read_blob_request_callback(
[&](bt::att::Handle handle, uint16_t offset, bt::gatt::Client::ReadCallback cb) {
read_count++;
EXPECT_EQ(handle, kValueHandle);
EXPECT_EQ(offset, kOffset);
cb(fit::ok(), kValue.view(offset), /*maybe_truncated=*/false);
});
std::optional<fpromise::result<fbg::ReadValue, fbg::Error>> fidl_result;
service_proxy()->ReadCharacteristic(fidl_char.handle(), std::move(read_options),
[&](auto result) { fidl_result = std::move(result); });
RunLoopUntilIdle();
EXPECT_EQ(read_count, 1u);
ASSERT_TRUE(fidl_result.has_value());
ASSERT_TRUE(fidl_result->is_ok()) << static_cast<uint32_t>(fidl_result->error());
const fbg::ReadValue& read_value = fidl_result->value();
EXPECT_TRUE(ContainersEqual(kValue.view(kOffset, kMaxBytes), read_value.value()));
EXPECT_TRUE(read_value.maybe_truncated());
}
TEST_F(Gatt2RemoteServiceServerTest, ReadCharacteristicHandleTooLarge) {
fbg::Handle handle;
handle.value = std::numeric_limits<bt::att::Handle>::max() + 1ULL;
fbg::ReadOptions options = fbg::ReadOptions::WithShortRead(fbg::ShortReadOptions());
std::optional<fpromise::result<fbg::ReadValue, fbg::Error>> fidl_result;
service_proxy()->ReadCharacteristic(handle, std::move(options),
[&](auto result) { fidl_result = std::move(result); });
RunLoopUntilIdle();
ASSERT_TRUE(fidl_result.has_value());
ASSERT_TRUE(fidl_result->is_error());
EXPECT_EQ(fidl_result->error(), fbg::Error::INVALID_HANDLE);
}
// Trying to read a characteristic that doesn't exist should return a FAILURE error.
TEST_F(Gatt2RemoteServiceServerTest, ReadCharacteristicFailure) {
constexpr bt::att::Handle kHandle = 3;
fbg::ReadOptions options = fbg::ReadOptions::WithShortRead(fbg::ShortReadOptions());
std::optional<fpromise::result<fbg::ReadValue, fbg::Error>> fidl_result;
service_proxy()->ReadCharacteristic(fbg::Handle{kHandle}, std::move(options),
[&](auto result) { fidl_result = std::move(result); });
RunLoopUntilIdle();
ASSERT_TRUE(fidl_result.has_value());
ASSERT_TRUE(fidl_result->is_error());
EXPECT_EQ(fidl_result->error(), fbg::Error::UNLIKELY_ERROR);
}
TEST_F(Gatt2RemoteServiceServerTest, DiscoverAndReadShortDescriptor) {
constexpr bt::att::Handle kCharacteristicHandle = 2;
constexpr bt::att::Handle kCharacteristicValueHandle = 3;
constexpr bt::att::Handle kDescriptorHandle = 4;
const auto kDescriptorValue = bt::StaticByteBuffer(0x00, 0x01, 0x02, 0x03, 0x04);
bt::gatt::CharacteristicData char_data(bt::gatt::Property::kRead, std::nullopt,
kCharacteristicHandle, kCharacteristicValueHandle,
kServiceUuid);
fake_client()->set_characteristics({char_data});
bt::gatt::DescriptorData desc_data(kDescriptorHandle, kDescriptorUuid);
fake_client()->set_descriptors({desc_data});
std::optional<std::vector<fbg::Characteristic>> fidl_characteristics;
service_proxy()->DiscoverCharacteristics(
[&](std::vector<fbg::Characteristic> chars) { fidl_characteristics = std::move(chars); });
RunLoopUntilIdle();
ASSERT_TRUE(fidl_characteristics.has_value());
ASSERT_EQ(fidl_characteristics->size(), 1u);
const fbg::Characteristic& fidl_char = fidl_characteristics->front();
ASSERT_TRUE(fidl_char.has_descriptors());
ASSERT_EQ(fidl_char.descriptors().size(), 1u);
const fbg::Descriptor& fidl_desc = fidl_char.descriptors().front();
ASSERT_TRUE(fidl_desc.has_handle());
size_t read_count = 0;
fake_client()->set_read_request_callback(
[&](bt::att::Handle handle, bt::gatt::Client::ReadCallback callback) {
read_count++;
EXPECT_EQ(handle, kDescriptorHandle);
callback(fit::ok(), kDescriptorValue, /*maybe_truncated=*/false);
});
fake_client()->set_read_blob_request_callback([](auto, auto, auto) { FAIL(); });
fbg::ReadOptions options = fbg::ReadOptions::WithShortRead(fbg::ShortReadOptions());
std::optional<fpromise::result<fbg::ReadValue, fbg::Error>> fidl_result;
service_proxy()->ReadDescriptor(fidl_desc.handle(), std::move(options),
[&](auto result) { fidl_result = std::move(result); });
RunLoopUntilIdle();
EXPECT_EQ(read_count, 1u);
ASSERT_TRUE(fidl_result.has_value());
ASSERT_TRUE(fidl_result->is_ok()) << static_cast<uint32_t>(fidl_result->error());
const fbg::ReadValue& read_value = fidl_result->value();
EXPECT_TRUE(ContainersEqual(kDescriptorValue, read_value.value()));
EXPECT_FALSE(read_value.maybe_truncated());
}
TEST_F(Gatt2RemoteServiceServerTest, DiscoverAndReadLongDescriptorWithOffsetAndMaxBytes) {
constexpr bt::att::Handle kCharacteristicHandle = 2;
constexpr bt::att::Handle kCharacteristicValueHandle = 3;
constexpr bt::att::Handle kDescriptorHandle = 4;
constexpr uint16_t kOffset = 1;
constexpr uint16_t kMaxBytes = 3;
const auto kDescriptorValue = bt::StaticByteBuffer(0x00, 0x01, 0x02, 0x03, 0x04);
bt::gatt::CharacteristicData char_data(bt::gatt::Property::kRead, std::nullopt,
kCharacteristicHandle, kCharacteristicValueHandle,
kServiceUuid);
fake_client()->set_characteristics({char_data});
bt::gatt::DescriptorData desc_data(kDescriptorHandle, kDescriptorUuid);
fake_client()->set_descriptors({desc_data});
std::optional<std::vector<fbg::Characteristic>> fidl_characteristics;
service_proxy()->DiscoverCharacteristics(
[&](std::vector<fbg::Characteristic> chars) { fidl_characteristics = std::move(chars); });
RunLoopUntilIdle();
ASSERT_TRUE(fidl_characteristics.has_value());
ASSERT_EQ(fidl_characteristics->size(), 1u);
const fbg::Characteristic& fidl_char = fidl_characteristics->front();
ASSERT_TRUE(fidl_char.has_descriptors());
ASSERT_EQ(fidl_char.descriptors().size(), 1u);
const fbg::Descriptor& fidl_desc = fidl_char.descriptors().front();
ASSERT_TRUE(fidl_desc.has_handle());
fbg::LongReadOptions long_options;
long_options.set_offset(kOffset);
long_options.set_max_bytes(kMaxBytes);
fbg::ReadOptions read_options;
read_options.set_long_read(std::move(long_options));
size_t read_count = 0;
fake_client()->set_read_request_callback([](auto, auto) { FAIL(); });
fake_client()->set_read_blob_request_callback(
[&](bt::att::Handle handle, uint16_t offset, bt::gatt::Client::ReadCallback cb) {
read_count++;
EXPECT_EQ(handle, kDescriptorHandle);
EXPECT_EQ(offset, kOffset);
cb(fit::ok(), kDescriptorValue.view(offset), /*maybe_truncated=*/false);
});
std::optional<fpromise::result<fbg::ReadValue, fbg::Error>> fidl_result;
service_proxy()->ReadDescriptor(fidl_desc.handle(), std::move(read_options),
[&](auto result) { fidl_result = std::move(result); });
RunLoopUntilIdle();
RunLoopUntilIdle();
EXPECT_EQ(read_count, 1u);
ASSERT_TRUE(fidl_result.has_value());
ASSERT_TRUE(fidl_result->is_ok()) << static_cast<uint32_t>(fidl_result->error());
const fbg::ReadValue& read_value = fidl_result->value();
EXPECT_TRUE(ContainersEqual(kDescriptorValue.view(kOffset, kMaxBytes), read_value.value()));
EXPECT_TRUE(read_value.maybe_truncated());
}
TEST_F(Gatt2RemoteServiceServerTest, ReadDescriptorHandleTooLarge) {
fbg::Handle handle;
handle.value = static_cast<uint64_t>(std::numeric_limits<bt::att::Handle>::max()) + 1;
fbg::ReadOptions options = fbg::ReadOptions::WithShortRead(fbg::ShortReadOptions());
std::optional<fpromise::result<fbg::ReadValue, fbg::Error>> fidl_result;
service_proxy()->ReadDescriptor(handle, std::move(options),
[&](auto result) { fidl_result = std::move(result); });
RunLoopUntilIdle();
ASSERT_TRUE(fidl_result.has_value());
ASSERT_TRUE(fidl_result->is_error());
EXPECT_EQ(fidl_result->error(), fbg::Error::INVALID_HANDLE);
}
// Trying to read a descriptor that doesn't exist should return a FAILURE error.
TEST_F(Gatt2RemoteServiceServerTest, ReadDescriptorFailure) {
constexpr bt::att::Handle kHandle = 3;
fbg::ReadOptions options = fbg::ReadOptions::WithShortRead(fbg::ShortReadOptions());
std::optional<fpromise::result<fbg::ReadValue, fbg::Error>> fidl_result;
service_proxy()->ReadDescriptor(fbg::Handle{kHandle}, std::move(options),
[&](auto result) { fidl_result = std::move(result); });
RunLoopUntilIdle();
ASSERT_TRUE(fidl_result.has_value());
ASSERT_TRUE(fidl_result->is_error());
EXPECT_EQ(fidl_result->error(), fbg::Error::UNLIKELY_ERROR);
}
TEST_F(Gatt2RemoteServiceServerTest, WriteCharacteristicHandleTooLarge) {
fbg::Handle handle;
handle.value = std::numeric_limits<bt::att::Handle>::max() + 1ULL;
std::optional<fpromise::result<void, fbg::Error>> fidl_result;
service_proxy()->WriteCharacteristic(handle, /*value=*/{}, fbg::WriteOptions(),
[&](auto result) { fidl_result = std::move(result); });
RunLoopUntilIdle();
ASSERT_TRUE(fidl_result.has_value());
ASSERT_TRUE(fidl_result->is_error());
EXPECT_EQ(fidl_result->error(), fbg::Error::INVALID_HANDLE);
}
TEST_F(Gatt2RemoteServiceServerTest,
WriteCharacteristicWithoutResponseAndNonZeroOffsetReturnsError) {
fbg::Handle handle;
handle.value = 3;
fbg::WriteOptions options;
options.set_write_mode(fbg::WriteMode::WITHOUT_RESPONSE);
options.set_offset(1);
std::optional<fpromise::result<void, fbg::Error>> fidl_result;
service_proxy()->WriteCharacteristic(handle, /*value=*/{}, std::move(options),
[&](auto result) { fidl_result = std::move(result); });
RunLoopUntilIdle();
ASSERT_TRUE(fidl_result.has_value());
ASSERT_TRUE(fidl_result->is_error());
EXPECT_EQ(fidl_result->error(), fbg::Error::INVALID_PARAMETERS);
}
TEST_F(Gatt2RemoteServiceServerTest, WriteCharacteristicWithoutResponse) {
constexpr bt::att::Handle kHandle = 3;
constexpr bt::att::Handle kValueHandle = kHandle + 1;
const auto kValue = bt::StaticByteBuffer(0x00, 0x01, 0x02, 0x03, 0x04);
bt::gatt::CharacteristicData char_data(bt::gatt::Property::kWriteWithoutResponse, std::nullopt,
kHandle, kValueHandle, kServiceUuid);
fake_client()->set_characteristics({char_data});
int write_count = 0;
fake_client()->set_write_without_rsp_callback(
[&](bt::att::Handle handle, const bt::ByteBuffer& value, bt::att::ResultFunction<> cb) {
write_count++;
EXPECT_EQ(handle, kValueHandle);
EXPECT_TRUE(ContainersEqual(value, kValue));
cb(fit::ok());
});
std::optional<std::vector<fbg::Characteristic>> fidl_characteristics;
service_proxy()->DiscoverCharacteristics(
[&](std::vector<fbg::Characteristic> chars) { fidl_characteristics = std::move(chars); });
RunLoopUntilIdle();
ASSERT_TRUE(fidl_characteristics.has_value());
ASSERT_EQ(fidl_characteristics->size(), 1u);
const fbg::Characteristic& fidl_char = fidl_characteristics->front();
ASSERT_TRUE(fidl_char.has_handle());
fbg::WriteOptions options;
options.set_write_mode(fbg::WriteMode::WITHOUT_RESPONSE);
std::optional<fpromise::result<void, fbg::Error>> fidl_result;
service_proxy()->WriteCharacteristic(fidl_char.handle(), kValue.ToVector(), std::move(options),
[&](auto result) { fidl_result = std::move(result); });
RunLoopUntilIdle();
ASSERT_TRUE(fidl_result.has_value());
EXPECT_TRUE(fidl_result->is_ok());
EXPECT_EQ(write_count, 1);
}
TEST_F(Gatt2RemoteServiceServerTest, WriteCharacteristicWithoutResponseValueTooLong) {
constexpr bt::att::Handle kHandle = 3;
constexpr bt::att::Handle kValueHandle = kHandle + 1;
ASSERT_EQ(fake_client()->mtu(), bt::att::kLEMinMTU);
bt::StaticByteBuffer<bt::att::kLEMinMTU> kValue;
kValue.Fill(0x03);
bt::gatt::CharacteristicData char_data(bt::gatt::Property::kWriteWithoutResponse, std::nullopt,
kHandle, kValueHandle, kServiceUuid);
fake_client()->set_characteristics({char_data});
int write_count = 0;
fake_client()->set_write_without_rsp_callback(
[&](bt::att::Handle handle, const bt::ByteBuffer& value, bt::att::ResultFunction<> callback) {
write_count++;
EXPECT_EQ(handle, kValueHandle);
EXPECT_TRUE(ContainersEqual(value, kValue));
callback(bt::ToResult(bt::HostError::kFailed));
});
std::optional<std::vector<fbg::Characteristic>> fidl_characteristics;
service_proxy()->DiscoverCharacteristics(
[&](std::vector<fbg::Characteristic> chars) { fidl_characteristics = std::move(chars); });
RunLoopUntilIdle();
ASSERT_TRUE(fidl_characteristics.has_value());
ASSERT_EQ(fidl_characteristics->size(), 1u);
const fbg::Characteristic& fidl_char = fidl_characteristics->front();
ASSERT_TRUE(fidl_char.has_handle());
fbg::WriteOptions options;
options.set_write_mode(fbg::WriteMode::WITHOUT_RESPONSE);
std::optional<fpromise::result<void, fbg::Error>> fidl_result;
service_proxy()->WriteCharacteristic(fidl_char.handle(), kValue.ToVector(), std::move(options),
[&](auto result) { fidl_result = std::move(result); });
RunLoopUntilIdle();
EXPECT_EQ(write_count, 1);
ASSERT_TRUE(fidl_result.has_value());
ASSERT_TRUE(fidl_result->is_error());
EXPECT_EQ(fidl_result->error(), fbg::Error::UNLIKELY_ERROR);
}
TEST_F(Gatt2RemoteServiceServerTest, WriteShortCharacteristic) {
constexpr bt::att::Handle kHandle = 3;
constexpr bt::att::Handle kValueHandle = kHandle + 1;
const auto kValue = bt::StaticByteBuffer(0x00, 0x01, 0x02, 0x03, 0x04);
bt::gatt::CharacteristicData char_data(bt::gatt::Property::kWrite, std::nullopt, kHandle,
kValueHandle, kServiceUuid);
fake_client()->set_characteristics({char_data});
int write_count = 0;
fake_client()->set_write_request_callback(
[&](bt::att::Handle handle, const bt::ByteBuffer& value, bt::att::ResultFunction<> callback) {
write_count++;
EXPECT_EQ(handle, kValueHandle);
EXPECT_TRUE(ContainersEqual(value, kValue));
callback(fit::ok());
});
std::optional<std::vector<fbg::Characteristic>> fidl_characteristics;
service_proxy()->DiscoverCharacteristics(
[&](std::vector<fbg::Characteristic> chars) { fidl_characteristics = std::move(chars); });
RunLoopUntilIdle();
ASSERT_TRUE(fidl_characteristics.has_value());
ASSERT_EQ(fidl_characteristics->size(), 1u);
const fbg::Characteristic& fidl_char = fidl_characteristics->front();
ASSERT_TRUE(fidl_char.has_handle());
fbg::WriteOptions options;
std::optional<fpromise::result<void, fbg::Error>> fidl_result;
service_proxy()->WriteCharacteristic(fidl_char.handle(), kValue.ToVector(), std::move(options),
[&](auto result) { fidl_result = std::move(result); });
RunLoopUntilIdle();
ASSERT_TRUE(fidl_result.has_value());
EXPECT_TRUE(fidl_result->is_ok());
EXPECT_EQ(write_count, 1);
}
TEST_F(Gatt2RemoteServiceServerTest, WriteShortCharacteristicWithNonZeroOffset) {
constexpr bt::att::Handle kHandle = 3;
constexpr bt::att::Handle kValueHandle = kHandle + 1;
const auto kValue = bt::StaticByteBuffer(0x00, 0x01, 0x02, 0x03, 0x04);
const uint16_t kOffset = 1;
bt::gatt::CharacteristicData char_data(bt::gatt::Property::kWrite, std::nullopt, kHandle,
kValueHandle, kServiceUuid);
fake_client()->set_characteristics({char_data});
int write_count = 0;
fake_client()->set_execute_prepare_writes_callback(
[&](bt::att::PrepareWriteQueue prep_write_queue, bt::gatt::ReliableMode reliable,
bt::att::ResultFunction<> callback) {
write_count++;
ASSERT_EQ(prep_write_queue.size(), 1u);
EXPECT_EQ(prep_write_queue.front().handle(), kValueHandle);
EXPECT_EQ(prep_write_queue.front().offset(), kOffset);
EXPECT_EQ(reliable, bt::gatt::ReliableMode::kDisabled);
EXPECT_TRUE(ContainersEqual(prep_write_queue.front().value(), kValue));
callback(fit::ok());
});
std::optional<std::vector<fbg::Characteristic>> fidl_characteristics;
service_proxy()->DiscoverCharacteristics(
[&](std::vector<fbg::Characteristic> chars) { fidl_characteristics = std::move(chars); });
RunLoopUntilIdle();
ASSERT_TRUE(fidl_characteristics.has_value());
ASSERT_EQ(fidl_characteristics->size(), 1u);
const fbg::Characteristic& fidl_char = fidl_characteristics->front();
ASSERT_TRUE(fidl_char.has_handle());
fbg::WriteOptions options;
options.set_offset(kOffset);
std::optional<fpromise::result<void, fbg::Error>> fidl_result;
service_proxy()->WriteCharacteristic(fidl_char.handle(), kValue.ToVector(), std::move(options),
[&](auto result) { fidl_result = std::move(result); });
RunLoopUntilIdle();
ASSERT_TRUE(fidl_result.has_value());
EXPECT_TRUE(fidl_result->is_ok());
EXPECT_EQ(write_count, 1);
}
TEST_F(Gatt2RemoteServiceServerTest, WriteShortCharacteristicWithReliableMode) {
constexpr bt::att::Handle kHandle = 3;
constexpr bt::att::Handle kValueHandle = kHandle + 1;
const auto kValue = bt::StaticByteBuffer(0x00, 0x01, 0x02, 0x03, 0x04);
bt::gatt::CharacteristicData char_data(bt::gatt::Property::kWrite, std::nullopt, kHandle,
kValueHandle, kServiceUuid);
fake_client()->set_characteristics({char_data});
int write_count = 0;
fake_client()->set_execute_prepare_writes_callback(
[&](bt::att::PrepareWriteQueue prep_write_queue, bt::gatt::ReliableMode reliable,
bt::att::ResultFunction<> callback) {
write_count++;
ASSERT_EQ(prep_write_queue.size(), 1u);
EXPECT_EQ(reliable, bt::gatt::ReliableMode::kEnabled);
EXPECT_EQ(prep_write_queue.front().handle(), kValueHandle);
EXPECT_EQ(prep_write_queue.front().offset(), 0u);
EXPECT_TRUE(ContainersEqual(prep_write_queue.front().value(), kValue));
callback(fit::ok());
});
std::optional<std::vector<fbg::Characteristic>> fidl_characteristics;
service_proxy()->DiscoverCharacteristics(
[&](std::vector<fbg::Characteristic> chars) { fidl_characteristics = std::move(chars); });
RunLoopUntilIdle();
ASSERT_TRUE(fidl_characteristics.has_value());
ASSERT_EQ(fidl_characteristics->size(), 1u);
const fbg::Characteristic& fidl_char = fidl_characteristics->front();
ASSERT_TRUE(fidl_char.has_handle());
fbg::WriteOptions options;
options.set_write_mode(fbg::WriteMode::RELIABLE);
std::optional<fpromise::result<void, fbg::Error>> fidl_result;
service_proxy()->WriteCharacteristic(fidl_char.handle(), kValue.ToVector(), std::move(options),
[&](auto result) { fidl_result = std::move(result); });
RunLoopUntilIdle();
ASSERT_TRUE(fidl_result.has_value());
EXPECT_TRUE(fidl_result->is_ok());
EXPECT_EQ(write_count, 1);
}
TEST_F(Gatt2RemoteServiceServerTest, WriteLongCharacteristicDefaultOptions) {
constexpr bt::att::Handle kHandle = 3;
constexpr bt::att::Handle kValueHandle = kHandle + 1;
constexpr size_t kHeaderSize =
sizeof(bt::att::OpCode) + sizeof(bt::att::PrepareWriteRequestParams);
const uint16_t kMtu = fake_client()->mtu();
const size_t kFirstPacketValueSize = kMtu - kHeaderSize;
bt::DynamicByteBuffer kValue(kMtu);
kValue.Fill(0x03);
bt::gatt::CharacteristicData char_data(bt::gatt::Property::kWrite, std::nullopt, kHandle,
kValueHandle, kServiceUuid);
fake_client()->set_characteristics({char_data});
int write_count = 0;
fake_client()->set_execute_prepare_writes_callback(
[&](bt::att::PrepareWriteQueue prep_write_queue, bt::gatt::ReliableMode reliable,
bt::att::ResultFunction<> callback) {
write_count++;
EXPECT_EQ(reliable, bt::gatt::ReliableMode::kDisabled);
ASSERT_EQ(prep_write_queue.size(), 2u);
EXPECT_EQ(prep_write_queue.front().handle(), kValueHandle);
EXPECT_EQ(prep_write_queue.front().offset(), 0u);
EXPECT_TRUE(ContainersEqual(kValue.view(0, kFirstPacketValueSize),
prep_write_queue.front().value()));
prep_write_queue.pop();
EXPECT_EQ(prep_write_queue.front().handle(), kValueHandle);
EXPECT_EQ(prep_write_queue.front().offset(), kFirstPacketValueSize);
EXPECT_TRUE(
ContainersEqual(kValue.view(kFirstPacketValueSize), prep_write_queue.front().value()));
callback(fit::ok());
});
std::optional<std::vector<fbg::Characteristic>> fidl_characteristics;
service_proxy()->DiscoverCharacteristics(
[&](std::vector<fbg::Characteristic> chars) { fidl_characteristics = std::move(chars); });
RunLoopUntilIdle();
ASSERT_TRUE(fidl_characteristics.has_value());
ASSERT_EQ(fidl_characteristics->size(), 1u);
const fbg::Characteristic& fidl_char = fidl_characteristics->front();
ASSERT_TRUE(fidl_char.has_handle());
std::optional<fpromise::result<void, fbg::Error>> fidl_result;
service_proxy()->WriteCharacteristic(fidl_char.handle(), kValue.ToVector(), fbg::WriteOptions(),
[&](auto result) { fidl_result = std::move(result); });
RunLoopUntilIdle();
ASSERT_TRUE(fidl_result.has_value());
EXPECT_TRUE(fidl_result->is_ok());
EXPECT_EQ(write_count, 1);
}
TEST_F(Gatt2RemoteServiceServerTest, WriteDescriptorHandleTooLarge) {
fbg::Handle handle;
handle.value = std::numeric_limits<bt::att::Handle>::max() + 1ULL;
std::optional<fpromise::result<void, fbg::Error>> fidl_result;
service_proxy()->WriteDescriptor(handle, /*value=*/{}, fbg::WriteOptions(),
[&](auto result) { fidl_result = std::move(result); });
RunLoopUntilIdle();
ASSERT_TRUE(fidl_result.has_value());
ASSERT_TRUE(fidl_result->is_error());
EXPECT_EQ(fidl_result->error(), fbg::Error::INVALID_HANDLE);
}
TEST_F(Gatt2RemoteServiceServerTest, WriteDescriptorWithoutResponseNotSupported) {
constexpr bt::att::Handle kHandle = 3;
fbg::WriteOptions options;
options.set_write_mode(fbg::WriteMode::WITHOUT_RESPONSE);
std::optional<fpromise::result<void, fbg::Error>> fidl_result;
service_proxy()->WriteDescriptor(fbg::Handle{kHandle}, /*value=*/{}, std::move(options),
[&](auto result) { fidl_result = std::move(result); });
RunLoopUntilIdle();
ASSERT_TRUE(fidl_result.has_value());
ASSERT_TRUE(fidl_result->is_error());
EXPECT_EQ(fidl_result->error(), fbg::Error::INVALID_PARAMETERS);
}
TEST_F(Gatt2RemoteServiceServerTest, WriteDescriptorReliableNotSupported) {
constexpr bt::att::Handle kHandle = 3;
fbg::WriteOptions options;
options.set_write_mode(fbg::WriteMode::RELIABLE);
std::optional<fpromise::result<void, fbg::Error>> fidl_result;
service_proxy()->WriteDescriptor(fbg::Handle{kHandle}, /*value=*/{}, std::move(options),
[&](auto result) { fidl_result = std::move(result); });
RunLoopUntilIdle();
ASSERT_TRUE(fidl_result.has_value());
ASSERT_TRUE(fidl_result->is_error());
EXPECT_EQ(fidl_result->error(), fbg::Error::INVALID_PARAMETERS);
}
TEST_F(Gatt2RemoteServiceServerTest, WriteShortDescriptor) {
constexpr bt::att::Handle kHandle = 3;
constexpr bt::att::Handle kCharacteristicValueHandle = kHandle + 1;
bt::gatt::CharacteristicData char_data(bt::gatt::Property::kWrite, std::nullopt, kHandle,
kCharacteristicValueHandle, kServiceUuid);
fake_client()->set_characteristics({char_data});
constexpr bt::att::Handle kDescriptorHandle(kCharacteristicValueHandle + 1);
const bt::UUID kDescriptorUuid(uint16_t{0x0001});
bt::gatt::DescriptorData descriptor(kDescriptorHandle, kDescriptorUuid);
const auto kValue = bt::StaticByteBuffer(0x00, 0x01, 0x02, 0x03, 0x04);
fake_client()->set_descriptors({descriptor});
int write_count = 0;
fake_client()->set_write_request_callback(
[&](bt::att::Handle handle, const bt::ByteBuffer& value, bt::att::ResultFunction<> callback) {
write_count++;
EXPECT_EQ(handle, kDescriptorHandle);
EXPECT_TRUE(ContainersEqual(value, kValue));
callback(fit::ok());
});
std::optional<std::vector<fbg::Characteristic>> fidl_characteristics;
service_proxy()->DiscoverCharacteristics(
[&](std::vector<fbg::Characteristic> chars) { fidl_characteristics = std::move(chars); });
RunLoopUntilIdle();
ASSERT_TRUE(fidl_characteristics.has_value());
ASSERT_EQ(fidl_characteristics->size(), 1u);
const fbg::Characteristic& fidl_char = fidl_characteristics->front();
ASSERT_TRUE(fidl_char.has_descriptors());
ASSERT_EQ(fidl_char.descriptors().size(), 1u);
fbg::WriteOptions options;
std::optional<fpromise::result<void, fbg::Error>> fidl_result;
service_proxy()->WriteDescriptor(fidl_char.descriptors().front().handle(), kValue.ToVector(),
std::move(options),
[&](auto result) { fidl_result = std::move(result); });
RunLoopUntilIdle();
ASSERT_TRUE(fidl_result.has_value());
EXPECT_TRUE(fidl_result->is_ok());
EXPECT_EQ(write_count, 1);
}
TEST_F(Gatt2RemoteServiceServerTest, WriteShortDescriptorWithNonZeroOffset) {
constexpr bt::att::Handle kHandle = 3;
constexpr bt::att::Handle kCharacteristicValueHandle = kHandle + 1;
bt::gatt::CharacteristicData char_data(bt::gatt::Property::kWrite, std::nullopt, kHandle,
kCharacteristicValueHandle, kServiceUuid);
fake_client()->set_characteristics({char_data});
constexpr bt::att::Handle kDescriptorHandle(kCharacteristicValueHandle + 1);
const bt::UUID kDescriptorUuid(uint16_t{0x0001});
bt::gatt::DescriptorData descriptor(kDescriptorHandle, kDescriptorUuid);
fake_client()->set_descriptors({descriptor});
const auto kValue = bt::StaticByteBuffer(0x00, 0x01, 0x02, 0x03, 0x04);
const uint16_t kOffset = 1;
int write_count = 0;
fake_client()->set_execute_prepare_writes_callback(
[&](bt::att::PrepareWriteQueue prep_write_queue, bt::gatt::ReliableMode reliable,
bt::att::ResultFunction<> callback) {
write_count++;
ASSERT_EQ(prep_write_queue.size(), 1u);
EXPECT_EQ(prep_write_queue.front().handle(), kDescriptorHandle);
EXPECT_EQ(prep_write_queue.front().offset(), kOffset);
EXPECT_EQ(reliable, bt::gatt::ReliableMode::kDisabled);
EXPECT_TRUE(ContainersEqual(prep_write_queue.front().value(), kValue));
callback(fit::ok());
});
std::optional<std::vector<fbg::Characteristic>> fidl_characteristics;
service_proxy()->DiscoverCharacteristics(
[&](std::vector<fbg::Characteristic> chars) { fidl_characteristics = std::move(chars); });
RunLoopUntilIdle();
ASSERT_TRUE(fidl_characteristics.has_value());
ASSERT_EQ(fidl_characteristics->size(), 1u);
const fbg::Characteristic& fidl_char = fidl_characteristics->front();
ASSERT_TRUE(fidl_char.has_descriptors());
ASSERT_EQ(fidl_char.descriptors().size(), 1u);
fbg::WriteOptions options;
options.set_offset(kOffset);
std::optional<fpromise::result<void, fbg::Error>> fidl_result;
service_proxy()->WriteDescriptor(fidl_char.descriptors().front().handle(), kValue.ToVector(),
std::move(options),
[&](auto result) { fidl_result = std::move(result); });
RunLoopUntilIdle();
ASSERT_TRUE(fidl_result.has_value());
EXPECT_TRUE(fidl_result->is_ok());
EXPECT_EQ(write_count, 1);
}
TEST_F(Gatt2RemoteServiceServerTest, WriteLongDescriptorDefaultOptions) {
constexpr bt::att::Handle kHandle = 3;
constexpr bt::att::Handle kCharacteristicValueHandle = kHandle + 1;
bt::gatt::CharacteristicData char_data(bt::gatt::Property::kWrite, std::nullopt, kHandle,
kCharacteristicValueHandle, kServiceUuid);
fake_client()->set_characteristics({char_data});
constexpr bt::att::Handle kDescriptorHandle(kCharacteristicValueHandle + 1);
const bt::UUID kDescriptorUuid(uint16_t{0x0001});
bt::gatt::DescriptorData descriptor(kDescriptorHandle, kDescriptorUuid);
fake_client()->set_descriptors({descriptor});
constexpr size_t kHeaderSize =
sizeof(bt::att::OpCode) + sizeof(bt::att::PrepareWriteRequestParams);
const uint16_t kMtu = fake_client()->mtu();
const size_t kFirstPacketValueSize = kMtu - kHeaderSize;
bt::DynamicByteBuffer kValue(kMtu);
kValue.Fill(0x03);
int write_count = 0;
fake_client()->set_execute_prepare_writes_callback(
[&](bt::att::PrepareWriteQueue prep_write_queue, bt::gatt::ReliableMode reliable,
bt::att::ResultFunction<> callback) {
write_count++;
EXPECT_EQ(reliable, bt::gatt::ReliableMode::kDisabled);
ASSERT_EQ(prep_write_queue.size(), 2u);
EXPECT_EQ(prep_write_queue.front().handle(), kDescriptorHandle);
EXPECT_EQ(prep_write_queue.front().offset(), 0u);
EXPECT_TRUE(ContainersEqual(kValue.view(0, kFirstPacketValueSize),
prep_write_queue.front().value()));
prep_write_queue.pop();
EXPECT_EQ(prep_write_queue.front().handle(), kDescriptorHandle);
EXPECT_EQ(prep_write_queue.front().offset(), kFirstPacketValueSize);
EXPECT_TRUE(
ContainersEqual(kValue.view(kFirstPacketValueSize), prep_write_queue.front().value()));
callback(fit::ok());
});
std::optional<std::vector<fbg::Characteristic>> fidl_characteristics;
service_proxy()->DiscoverCharacteristics(
[&](std::vector<fbg::Characteristic> chars) { fidl_characteristics = std::move(chars); });
RunLoopUntilIdle();
ASSERT_TRUE(fidl_characteristics.has_value());
ASSERT_EQ(fidl_characteristics->size(), 1u);
const fbg::Characteristic& fidl_char = fidl_characteristics->front();
ASSERT_TRUE(fidl_char.has_descriptors());
ASSERT_EQ(fidl_char.descriptors().size(), 1u);
std::optional<fpromise::result<void, fbg::Error>> fidl_result;
service_proxy()->WriteDescriptor(fidl_char.descriptors().front().handle(), kValue.ToVector(),
fbg::WriteOptions(),
[&](auto result) { fidl_result = std::move(result); });
RunLoopUntilIdle();
ASSERT_TRUE(fidl_result.has_value());
EXPECT_TRUE(fidl_result->is_ok());
EXPECT_EQ(write_count, 1);
}
class FakeCharacteristicNotifier : public fbg::testing::CharacteristicNotifier_TestBase {
public:
struct Notification {
fbg::ReadValue value;
OnNotificationCallback notification_cb;
};
explicit FakeCharacteristicNotifier(fidl::InterfaceRequest<fbg::CharacteristicNotifier> request)
: binding_(this, std::move(request)) {
binding_.set_error_handler([this](zx_status_t status) { error_ = status; });
}
void Unbind() { binding_.Unbind(); }
void OnNotification(fbg::ReadValue value, OnNotificationCallback callback) override {
notifications_.push_back(Notification{std::move(value), std::move(callback)});
}
std::vector<Notification>& notifications() { return notifications_; }
std::optional<zx_status_t> error() const { return error_; }
private:
void NotImplemented_(const std::string& name) override {
FAIL() << name << " is not implemented";
}
fidl::Binding<fbg::CharacteristicNotifier> binding_;
std::vector<Notification> notifications_;
std::optional<zx_status_t> error_;
};
class Gatt2RemoteServiceServerCharacteristicNotifierTest : public Gatt2RemoteServiceServerTest {
public:
void SetUp() override {
Gatt2RemoteServiceServerTest::SetUp();
bt::gatt::CharacteristicData characteristic(bt::gatt::Property::kNotify,
/*ext_props=*/std::nullopt, char_handle_,
char_value_handle_, kCharacteristicUuid);
fake_client()->set_characteristics({characteristic});
bt::gatt::DescriptorData ccc_descriptor(ccc_descriptor_handle_,
bt::gatt::types::kClientCharacteristicConfig);
fake_client()->set_descriptors({ccc_descriptor});
std::optional<std::vector<fbg::Characteristic>> fidl_characteristics;
service_proxy()->DiscoverCharacteristics(
[&](std::vector<fbg::Characteristic> chars) { fidl_characteristics = std::move(chars); });
RunLoopUntilIdle();
ASSERT_TRUE(fidl_characteristics.has_value());
ASSERT_EQ(fidl_characteristics->size(), 1u);
characteristic_ = std::move(fidl_characteristics->front());
}
protected:
const fbg::Characteristic& characteristic() const { return characteristic_; }
bt::att::Handle characteristic_handle() const { return char_handle_; }
bt::att::Handle characteristic_value_handle() const { return char_value_handle_; }
bt::att::Handle ccc_descriptor_handle() const { return ccc_descriptor_handle_; }
private:
fbg::Characteristic characteristic_;
const bt::att::Handle char_handle_ = 2;
const bt::att::Handle char_value_handle_ = 3;
const bt::att::Handle ccc_descriptor_handle_ = 4;
};
TEST_F(Gatt2RemoteServiceServerCharacteristicNotifierTest,
RegisterCharacteristicNotifierReceiveNotificationsAndUnregister) {
const auto kValue0 = bt::StaticByteBuffer(0x01, 0x02, 0x03);
const auto kValue1 = bt::StaticByteBuffer(0x04, 0x05, 0x06);
// Respond to CCC write with success status so that notifications are enabled.
fake_client()->set_write_request_callback(
[&](bt::att::Handle handle, const bt::ByteBuffer& /*value*/, auto status_callback) {
EXPECT_EQ(handle, ccc_descriptor_handle());
status_callback(fit::ok());
});
fidl::InterfaceHandle<fbg::CharacteristicNotifier> notifier_handle;
FakeCharacteristicNotifier notifier_server(notifier_handle.NewRequest());
auto& notifications = notifier_server.notifications();
std::optional<fpromise::result<void, fbg::Error>> register_result;
auto register_cb = [&](fpromise::result<void, fbg::Error> result) { register_result = result; };
service_proxy()->RegisterCharacteristicNotifier(
characteristic().handle(), std::move(notifier_handle), std::move(register_cb));
RunLoopUntilIdle();
ASSERT_TRUE(register_result.has_value());
EXPECT_TRUE(register_result->is_ok());
// Send 2 notifications to test flow control.
service()->HandleNotificationForTesting(characteristic_value_handle(), kValue0,
/*maybe_truncated=*/false);
service()->HandleNotificationForTesting(characteristic_value_handle(), kValue1,
/*maybe_truncated=*/true);
RunLoopUntilIdle();
ASSERT_EQ(notifications.size(), 1u);
fbg::ReadValue& notification_0 = notifications[0].value;
ASSERT_TRUE(notification_0.has_value());
EXPECT_TRUE(bt::ContainersEqual(notification_0.value(), kValue0));
ASSERT_TRUE(notification_0.has_handle());
EXPECT_EQ(notification_0.handle().value, static_cast<uint64_t>(characteristic_value_handle()));
ASSERT_TRUE(notification_0.has_maybe_truncated());
ASSERT_FALSE(notification_0.maybe_truncated());
notifications[0].notification_cb();
RunLoopUntilIdle();
ASSERT_EQ(notifications.size(), 2u);
fbg::ReadValue& notification_1 = notifications[1].value;
ASSERT_TRUE(notification_1.has_value());
EXPECT_TRUE(bt::ContainersEqual(notification_1.value(), kValue1));
ASSERT_TRUE(notification_1.has_handle());
EXPECT_EQ(notification_1.handle().value, static_cast<uint64_t>(characteristic_value_handle()));
ASSERT_TRUE(notification_1.has_maybe_truncated());
ASSERT_TRUE(notification_1.maybe_truncated());
notifications[1].notification_cb();
RunLoopUntilIdle();
EXPECT_EQ(notifications.size(), 2u);
notifier_server.Unbind();
RunLoopUntilIdle();
// Notifications should be ignored after notifier is unregistered.
service()->HandleNotificationForTesting(characteristic_value_handle(), kValue0,
/*maybe_truncated=*/false);
RunLoopUntilIdle();
}
TEST_F(Gatt2RemoteServiceServerCharacteristicNotifierTest,
QueueTooManyNotificationsAndCloseNotifier) {
const auto kValue = bt::StaticByteBuffer(0x01, 0x02, 0x03);
// Respond to CCC write with success status so that notifications are enabled.
fake_client()->set_write_request_callback(
[&](bt::att::Handle handle, const bt::ByteBuffer& /*value*/, auto status_callback) {
EXPECT_EQ(handle, ccc_descriptor_handle());
status_callback(fit::ok());
});
fidl::InterfaceHandle<fbg::CharacteristicNotifier> notifier_handle;
FakeCharacteristicNotifier notifier_server(notifier_handle.NewRequest());
auto& notifications = notifier_server.notifications();
std::optional<fpromise::result<void, fbg::Error>> register_result;
auto register_cb = [&](fpromise::result<void, fbg::Error> result) { register_result = result; };
service_proxy()->RegisterCharacteristicNotifier(
characteristic().handle(), std::move(notifier_handle), std::move(register_cb));
RunLoopUntilIdle();
ASSERT_TRUE(register_result.has_value());
EXPECT_TRUE(register_result->is_ok());
// Fill the pending notifier values queue.
for (size_t i = 0; i < Gatt2RemoteServiceServer::kMaxPendingNotifierValues; i++) {
service()->HandleNotificationForTesting(characteristic_value_handle(), kValue,
/*maybe_truncated=*/false);
}
RunLoopUntilIdle();
ASSERT_EQ(notifications.size(), 1u);
EXPECT_FALSE(notifier_server.error().has_value());
// This notification should exceed the max queue size.
service()->HandleNotificationForTesting(characteristic_value_handle(), kValue,
/*maybe_truncated=*/false);
RunLoopUntilIdle();
EXPECT_TRUE(notifier_server.error().has_value());
// Notifications should be ignored after notifier is unregistered due to an error.
service()->HandleNotificationForTesting(characteristic_value_handle(), kValue,
/*maybe_truncated=*/false);
RunLoopUntilIdle();
notifications[0].notification_cb();
EXPECT_EQ(notifications.size(), 1u);
}
TEST_F(Gatt2RemoteServiceServerCharacteristicNotifierTest,
RegisterCharacteristicNotifierWriteError) {
// Respond to CCC write with error status so that registration fails.
fake_client()->set_write_request_callback(
[&](bt::att::Handle handle, const bt::ByteBuffer& /*value*/, auto status_callback) {
EXPECT_EQ(handle, ccc_descriptor_handle());
status_callback(bt::ToResult(bt::att::ErrorCode::kInsufficientAuthentication));
});
fidl::InterfaceHandle<fbg::CharacteristicNotifier> notifier_handle;
FakeCharacteristicNotifier notifier_server(notifier_handle.NewRequest());
std::optional<fpromise::result<void, fbg::Error>> register_result;
auto register_cb = [&](fpromise::result<void, fbg::Error> result) { register_result = result; };
service_proxy()->RegisterCharacteristicNotifier(
characteristic().handle(), std::move(notifier_handle), std::move(register_cb));
RunLoopUntilIdle();
ASSERT_TRUE(register_result.has_value());
ASSERT_TRUE(register_result->is_error());
EXPECT_EQ(register_result->error(), fbg::Error::INSUFFICIENT_AUTHENTICATION);
EXPECT_TRUE(notifier_server.error().has_value());
}
TEST_F(
Gatt2RemoteServiceServerCharacteristicNotifierTest,
RegisterCharacteristicNotifierAndDestroyServerBeforeStatusCallbackCausesNotificationsToBeDisabled) {
// Respond to CCC write with success status so that notifications are enabled.
int ccc_write_count = 0;
bt::att::ResultFunction<> enable_notifications_status_cb = nullptr;
fake_client()->set_write_request_callback([&](bt::att::Handle handle, const bt::ByteBuffer& value,
bt::att::ResultFunction<> status_callback) {
ccc_write_count++;
EXPECT_EQ(handle, ccc_descriptor_handle());
if (ccc_write_count == 1) {
EXPECT_NE(value[0], 0u); // Enable value
enable_notifications_status_cb = std::move(status_callback);
} else {
EXPECT_EQ(value[0], 0u); // Disable value
status_callback(fit::ok());
}
});
fidl::InterfaceHandle<fbg::CharacteristicNotifier> notifier_handle;
FakeCharacteristicNotifier notifier_server(notifier_handle.NewRequest());
std::optional<fpromise::result<void, fbg::Error>> register_result;
auto register_cb = [&](fpromise::result<void, fbg::Error> result) { register_result = result; };
service_proxy()->RegisterCharacteristicNotifier(
characteristic().handle(), std::move(notifier_handle), std::move(register_cb));
RunLoopUntilIdle();
EXPECT_FALSE(register_result.has_value());
EXPECT_EQ(ccc_write_count, 1);
DestroyServer();
ASSERT_TRUE(enable_notifications_status_cb);
enable_notifications_status_cb(fit::ok());
RunLoopUntilIdle();
// Notifications should have been disabled in enable notifications status callback.
EXPECT_EQ(ccc_write_count, 2);
}
TEST_F(
Gatt2RemoteServiceServerCharacteristicNotifierTest,
RegisterCharacteristicNotifierAndDestroyServerAfterStatusCallbackCausesNotificationsToBeDisabled) {
// Respond to CCC write with success status so that notifications are enabled.
int ccc_write_count = 0;
bt::att::ResultFunction<> enable_notifications_status_cb = nullptr;
fake_client()->set_write_request_callback([&](bt::att::Handle handle, const bt::ByteBuffer& value,
bt::att::ResultFunction<> status_callback) {
ccc_write_count++;
EXPECT_EQ(handle, ccc_descriptor_handle());
if (ccc_write_count == 1) {
EXPECT_NE(value[0], 0u); // Enable value
} else {
EXPECT_EQ(value[0], 0u); // Disable value
}
status_callback(fit::ok());
});
fidl::InterfaceHandle<fbg::CharacteristicNotifier> notifier_handle;
FakeCharacteristicNotifier notifier_server(notifier_handle.NewRequest());
std::optional<fpromise::result<void, fbg::Error>> register_result;
auto register_cb = [&](fpromise::result<void, fbg::Error> result) { register_result = result; };
service_proxy()->RegisterCharacteristicNotifier(
characteristic().handle(), std::move(notifier_handle), std::move(register_cb));
RunLoopUntilIdle();
EXPECT_TRUE(register_result.has_value());
EXPECT_TRUE(register_result->is_ok());
EXPECT_EQ(ccc_write_count, 1);
DestroyServer();
RunLoopUntilIdle();
// Notifications should have been disabled in the server destructor.
EXPECT_EQ(ccc_write_count, 2);
}
} // namespace
} // namespace bthost