blob: fca06b2d23c3a4f4a0d09e5f3eee42bb2899f570 [file] [log] [blame]
// Copyright 2018 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 "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/gatt/remote_service_manager.h"
#include <vector>
#include <gmock/gmock.h>
#include "pw_async/fake_dispatcher_fixture.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/att/att.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/att/error.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/common/macros.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/gatt/fake_client.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/gatt/gatt_defs.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/testing/test_helpers.h"
#pragma clang diagnostic ignored "-Wshadow"
namespace bt::gatt::internal {
namespace {
using namespace ::testing;
constexpr UUID kTestServiceUuid1(uint16_t{0xbeef});
constexpr UUID kTestServiceUuid2(uint16_t{0xcafe});
constexpr UUID kTestServiceUuid3(uint16_t{0xface});
constexpr UUID kTestUuid3(uint16_t{0xfefe});
constexpr UUID kTestUuid4(uint16_t{0xefef});
// Buffers for descriptor responses.
// ExtendedProperty::kReliableWrite enabled.
const StaticByteBuffer kExtendedPropValue(0x01, 0x00);
const StaticByteBuffer kCCCNotifyValue(0x01, 0x00);
const StaticByteBuffer kCCCIndicateValue(0x02, 0x00);
// Constants used for initializing fake characteristic data.
constexpr att::Handle kStart = 1;
constexpr att::Handle kCharDecl = 2;
constexpr att::Handle kCharValue = 3;
constexpr att::Handle kDesc1 = 4;
constexpr att::Handle kDesc2 = 5;
constexpr att::Handle kEnd = 5;
void NopStatusCallback(att::Result<>) {}
void NopMtuCallback(uint16_t) {}
void NopValueCallback(const ByteBuffer& /*value*/, bool /*maybe_truncated*/) {}
class RemoteServiceManagerTest : public pw::async::test::FakeDispatcherFixture {
public:
RemoteServiceManagerTest() = default;
~RemoteServiceManagerTest() override = default;
protected:
void SetUp() override {
auto client = std::make_unique<testing::FakeClient>(dispatcher());
fake_client_ = client.get();
mgr_ = std::make_unique<RemoteServiceManager>(std::move(client));
}
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.
if (fake_client_) {
fake_client()->set_write_request_callback({});
}
mgr_ = nullptr;
}
// Initializes a RemoteService based on |data|.
RemoteService::WeakPtr SetUpFakeService(const ServiceData& data) {
std::vector<ServiceData> fake_services{{data}};
fake_client()->set_services(std::move(fake_services));
mgr()->Initialize(NopStatusCallback, NopMtuCallback);
ServiceList services;
mgr()->ListServices(std::vector<UUID>(),
[&services](auto status, ServiceList cb_services) {
services = std::move(cb_services);
});
RunUntilIdle();
BT_DEBUG_ASSERT(services.size() == 1u);
return services[0];
}
void SetCharacteristicsAndDescriptors(
std::vector<CharacteristicData> fake_chrs,
std::vector<DescriptorData> fake_descrs = std::vector<DescriptorData>()) {
fake_client()->set_characteristics(std::move(fake_chrs));
fake_client()->set_descriptors(std::move(fake_descrs));
fake_client()->set_characteristic_discovery_status(fit::ok());
}
// Discover the characteristics of |service| based on the given |fake_data|.
void SetupCharacteristics(
RemoteService::WeakPtr service,
std::vector<CharacteristicData> fake_chrs,
std::vector<DescriptorData> fake_descrs = std::vector<DescriptorData>()) {
BT_DEBUG_ASSERT(service.is_alive());
SetCharacteristicsAndDescriptors(std::move(fake_chrs),
std::move(fake_descrs));
service->DiscoverCharacteristics([](auto, const auto&) {});
RunUntilIdle();
}
RemoteService::WeakPtr SetupServiceWithChrcs(
const ServiceData& data,
std::vector<CharacteristicData> fake_chrs,
std::vector<DescriptorData> fake_descrs = std::vector<DescriptorData>()) {
auto service = SetUpFakeService(data);
SetupCharacteristics(service, fake_chrs, fake_descrs);
return service;
}
// Create a fake service with one notifiable characteristic.
RemoteService::WeakPtr SetupNotifiableService() {
ServiceData data(ServiceKind::PRIMARY, 1, 4, kTestServiceUuid1);
auto service = SetUpFakeService(data);
CharacteristicData chr(Property::kNotify, std::nullopt, 2, 3, kTestUuid3);
DescriptorData desc(4, types::kClientCharacteristicConfig);
SetupCharacteristics(service, {{chr}}, {{desc}});
fake_client()->set_write_request_callback(
[&](att::Handle, const auto&, auto status_callback) {
status_callback(fit::ok());
});
RunUntilIdle();
return service;
}
void EnableNotifications(
RemoteService::WeakPtr service,
CharacteristicHandle chr_id,
att::Result<>* out_status,
IdType* out_id,
RemoteService::ValueCallback callback = NopValueCallback) {
BT_DEBUG_ASSERT(out_status);
BT_DEBUG_ASSERT(out_id);
service->EnableNotifications(chr_id,
std::move(callback),
[&](att::Result<> cb_status, IdType cb_id) {
*out_status = cb_status;
*out_id = cb_id;
});
RunUntilIdle();
}
void DestroyServiceManager() {
mgr_.reset();
fake_client_ = nullptr;
}
RemoteServiceManager* mgr() const { return mgr_.get(); }
testing::FakeClient* fake_client() const { return fake_client_; }
private:
std::unique_ptr<RemoteServiceManager> mgr_;
// The memory is owned by |mgr_|.
testing::FakeClient* fake_client_;
BT_DISALLOW_COPY_AND_ASSIGN_ALLOW_MOVE(RemoteServiceManagerTest);
};
TEST_F(RemoteServiceManagerTest, InitializeNoServices) {
ServiceList services;
mgr()->set_service_watcher(
[&services](auto /*removed*/, ServiceList added, auto /*modified*/) {
services.insert(services.end(), added.begin(), added.end());
});
att::Result<> status = ToResult(HostError::kFailed);
mgr()->Initialize([&status](att::Result<> val) { status = val; },
NopMtuCallback);
RunUntilIdle();
EXPECT_EQ(fit::ok(), status);
EXPECT_TRUE(services.empty());
mgr()->ListServices(std::vector<UUID>(),
[&services](auto status, ServiceList cb_services) {
services = std::move(cb_services);
});
EXPECT_TRUE(services.empty());
}
TEST_F(RemoteServiceManagerTest, Initialize) {
ServiceData svc1(ServiceKind::PRIMARY, 1, 1, kTestServiceUuid1);
ServiceData svc2(ServiceKind::PRIMARY, 2, 2, kTestServiceUuid2);
std::vector<ServiceData> fake_services{{svc1, svc2}};
fake_client()->set_services(std::move(fake_services));
ServiceList services;
mgr()->set_service_watcher(
[&services](auto /*removed*/, ServiceList added, auto /*modified*/) {
services.insert(services.end(), added.begin(), added.end());
});
const uint16_t kArbitraryMtu = att::kLEMinMTU + 10;
fake_client()->set_server_mtu(kArbitraryMtu);
uint16_t new_mtu = 0;
auto mtu_cb = [&](uint16_t cb_mtu) { new_mtu = cb_mtu; };
att::Result<> status = ToResult(HostError::kFailed);
mgr()->Initialize([&status](att::Result<> val) { status = val; },
std::move(mtu_cb));
RunUntilIdle();
EXPECT_EQ(kArbitraryMtu, new_mtu);
EXPECT_EQ(fit::ok(), status);
EXPECT_EQ(2u, services.size());
EXPECT_EQ(svc1.range_start, services[0]->handle());
EXPECT_EQ(svc2.range_start, services[1]->handle());
EXPECT_EQ(svc1.type, services[0]->uuid());
EXPECT_EQ(svc2.type, services[1]->uuid());
}
TEST_F(RemoteServiceManagerTest, InitializeFailure) {
fake_client()->set_discover_services_callback([](ServiceKind kind) {
if (kind == ServiceKind::PRIMARY) {
return ToResult(att::ErrorCode::kRequestNotSupported);
}
return att::Result<>(fit::ok());
});
ServiceList watcher_services;
mgr()->set_service_watcher([&watcher_services](auto /*removed*/,
ServiceList added,
auto /*modified*/) {
watcher_services.insert(watcher_services.end(), added.begin(), added.end());
});
ServiceList services;
mgr()->ListServices(std::vector<UUID>(),
[&services](auto status, ServiceList cb_services) {
services = std::move(cb_services);
});
ASSERT_TRUE(services.empty());
att::Result<> status = ToResult(HostError::kFailed);
mgr()->Initialize([&status](att::Result<> val) { status = val; },
NopMtuCallback);
RunUntilIdle();
EXPECT_EQ(ToResult(att::ErrorCode::kRequestNotSupported), status);
EXPECT_TRUE(services.empty());
EXPECT_TRUE(watcher_services.empty());
}
TEST_F(RemoteServiceManagerTest, InitializeMtuExchangeNotSupportedSucceeds) {
ServiceData svc(ServiceKind::PRIMARY, 1, 1, kTestServiceUuid1);
fake_client()->set_services({svc});
// The MTU exchange is an optional procedure, so if the peer tells us that
// they do not support it, we should continue with initialization.
fake_client()->set_exchange_mtu_status(
ToResult(att::ErrorCode::kRequestNotSupported));
ServiceList services;
mgr()->set_service_watcher(
[&services](auto /*removed*/, ServiceList added, auto /*modified*/) {
services.insert(services.end(), added.begin(), added.end());
});
std::optional<uint16_t> mtu;
auto mtu_cb = [&mtu](uint16_t cb_mtu) { mtu = cb_mtu; };
att::Result<> status = ToResult(HostError::kFailed);
mgr()->Initialize([&status](att::Result<> val) { status = val; },
std::move(mtu_cb));
RunUntilIdle();
EXPECT_EQ(fit::ok(), status);
ASSERT_EQ(1u, services.size());
EXPECT_EQ(svc.range_start, services[0]->handle());
EXPECT_EQ(svc.type, services[0]->uuid());
// If the MTU exchange isn't supported, "the default MTU shall be used" (v5.3
// Vol. 3 Part G 4.3.1)
ASSERT_TRUE(mtu.has_value());
EXPECT_EQ(att::kLEMinMTU, *mtu);
}
TEST_F(RemoteServiceManagerTest, InitializeMtuExchangeFailure) {
fake_client()->set_exchange_mtu_status(
ToResult(att::ErrorCode::kUnlikelyError));
bool mtu_updated = false;
auto mtu_cb = [&](uint16_t /*ignore*/) { mtu_updated = true; };
att::Result<> status = fit::ok();
mgr()->Initialize([&status](att::Result<> val) { status = val; },
std::move(mtu_cb));
RunUntilIdle();
EXPECT_EQ(ToResult(att::ErrorCode::kUnlikelyError), status);
EXPECT_FALSE(mtu_updated);
}
TEST_F(RemoteServiceManagerTest, InitializeByUUIDNoServices) {
ServiceList services;
mgr()->set_service_watcher(
[&services](auto /*removed*/, ServiceList added, auto /*modified*/) {
services.insert(services.end(), added.begin(), added.end());
});
att::Result<> status = ToResult(HostError::kFailed);
mgr()->Initialize([&status](att::Result<> val) { status = val; },
NopMtuCallback,
{kTestServiceUuid1});
RunUntilIdle();
EXPECT_EQ(fit::ok(), status);
EXPECT_TRUE(services.empty());
mgr()->ListServices(std::vector<UUID>(),
[&services](auto status, ServiceList cb_services) {
services = std::move(cb_services);
});
EXPECT_TRUE(services.empty());
}
TEST_F(RemoteServiceManagerTest, InitializeWithUuids) {
ServiceData svc1(ServiceKind::PRIMARY, 1, 1, kTestServiceUuid1);
ServiceData svc2(ServiceKind::PRIMARY, 2, 2, kTestServiceUuid2);
ServiceData svc3(ServiceKind::PRIMARY, 3, 3, kTestServiceUuid3);
std::vector<ServiceData> fake_services{{svc1, svc2, svc3}};
fake_client()->set_services(std::move(fake_services));
ServiceList services;
mgr()->set_service_watcher(
[&services](auto /*removed*/, ServiceList added, auto /*modified*/) {
services.insert(services.end(), added.begin(), added.end());
});
att::Result<> status = ToResult(HostError::kFailed);
mgr()->Initialize([&status](att::Result<> val) { status = val; },
NopMtuCallback,
{kTestServiceUuid1, kTestServiceUuid3});
RunUntilIdle();
EXPECT_EQ(fit::ok(), status);
std::vector<ServiceData> found_services;
for (const auto& ptr : services) {
found_services.push_back(ptr->info());
}
EXPECT_THAT(found_services, UnorderedElementsAre(Eq(svc1), Eq(svc3)));
}
TEST_F(RemoteServiceManagerTest, InitializeByUUIDFailure) {
fake_client()->set_discover_services_callback([](ServiceKind kind) {
if (kind == ServiceKind::PRIMARY) {
return ToResult(att::ErrorCode::kRequestNotSupported);
}
return att::Result<>(fit::ok());
});
ServiceList watcher_services;
mgr()->set_service_watcher([&watcher_services](auto /*removed*/,
ServiceList added,
auto /*modified*/) {
watcher_services.insert(watcher_services.end(), added.begin(), added.end());
});
ServiceList services;
mgr()->ListServices(std::vector<UUID>(),
[&services](auto status, ServiceList cb_services) {
services = std::move(cb_services);
});
ASSERT_TRUE(services.empty());
att::Result<> status = ToResult(HostError::kFailed);
mgr()->Initialize([&status](att::Result<> val) { status = val; },
NopMtuCallback,
{kTestServiceUuid1});
RunUntilIdle();
EXPECT_EQ(ToResult(att::ErrorCode::kRequestNotSupported), status);
EXPECT_TRUE(services.empty());
EXPECT_TRUE(watcher_services.empty());
}
TEST_F(RemoteServiceManagerTest, InitializeSecondaryServices) {
ServiceData svc(ServiceKind::SECONDARY, 1, 1, kTestServiceUuid1);
std::vector<ServiceData> fake_services{{svc}};
fake_client()->set_services(std::move(fake_services));
ServiceList services;
mgr()->set_service_watcher(
[&services](auto /*removed*/, ServiceList added, auto /*modified*/) {
services.insert(services.end(), added.begin(), added.end());
});
att::Result<> status = ToResult(HostError::kFailed);
mgr()->Initialize([&status](att::Result<> val) { status = val; },
NopMtuCallback);
RunUntilIdle();
EXPECT_EQ(fit::ok(), status);
ASSERT_EQ(1u, services.size());
EXPECT_EQ(svc.range_start, services[0]->handle());
EXPECT_EQ(svc.type, services[0]->uuid());
EXPECT_EQ(ServiceKind::SECONDARY, services[0]->info().kind);
}
TEST_F(RemoteServiceManagerTest, InitializePrimaryAndSecondaryServices) {
ServiceData svc1(ServiceKind::PRIMARY, 1, 1, kTestServiceUuid1);
ServiceData svc2(ServiceKind::SECONDARY, 2, 2, kTestServiceUuid2);
std::vector<ServiceData> fake_services{{svc1, svc2}};
fake_client()->set_services(std::move(fake_services));
ServiceList services;
mgr()->set_service_watcher(
[&services](auto /*removed*/, ServiceList added, auto /*modified*/) {
services.insert(services.end(), added.begin(), added.end());
});
att::Result<> status = ToResult(HostError::kFailed);
mgr()->Initialize([&status](att::Result<> val) { status = val; },
NopMtuCallback);
RunUntilIdle();
EXPECT_EQ(fit::ok(), status);
EXPECT_EQ(2u, services.size());
EXPECT_EQ(ServiceKind::PRIMARY, services[0]->info().kind);
EXPECT_EQ(ServiceKind::SECONDARY, services[1]->info().kind);
}
TEST_F(RemoteServiceManagerTest,
InitializePrimaryAndSecondaryServicesOutOfOrder) {
// RemoteServiceManager discovers primary services first, followed by
// secondary services. Test that the results are stored and represented in the
// correct order when a secondary service precedes a primary service.
ServiceData svc1(ServiceKind::SECONDARY, 1, 1, kTestServiceUuid1);
ServiceData svc2(ServiceKind::PRIMARY, 2, 2, kTestServiceUuid2);
fake_client()->set_services({{svc1, svc2}});
ServiceList services;
mgr()->set_service_watcher(
[&services](auto /*removed*/, ServiceList added, auto /*modified*/) {
services.insert(services.end(), added.begin(), added.end());
});
att::Result<> status = ToResult(HostError::kFailed);
mgr()->Initialize([&status](att::Result<> val) { status = val; },
NopMtuCallback);
RunUntilIdle();
EXPECT_EQ(fit::ok(), status);
EXPECT_EQ(2u, services.size());
EXPECT_EQ(ServiceKind::SECONDARY, services[0]->info().kind);
EXPECT_EQ(ServiceKind::PRIMARY, services[1]->info().kind);
}
// Tests that an ATT error that occurs during secondary service aborts
// initialization.
TEST_F(RemoteServiceManagerTest, InitializeSecondaryServicesFailure) {
fake_client()->set_discover_services_callback([](ServiceKind kind) {
if (kind == ServiceKind::SECONDARY) {
return ToResult(att::ErrorCode::kRequestNotSupported);
}
return att::Result<>(fit::ok());
});
ServiceList services;
mgr()->set_service_watcher(
[&services](auto /*removed*/, ServiceList added, auto /*modified*/) {
services.insert(services.end(), added.begin(), added.end());
});
att::Result<> status = fit::ok();
mgr()->Initialize([&status](att::Result<> val) { status = val; },
NopMtuCallback);
RunUntilIdle();
EXPECT_EQ(ToResult(att::ErrorCode::kRequestNotSupported), status);
EXPECT_TRUE(services.empty());
}
// Tests that the "unsupported group type" error is treated as a failure for
// primary services.
TEST_F(RemoteServiceManagerTest,
InitializePrimaryServicesErrorUnsupportedGroupType) {
fake_client()->set_discover_services_callback([](ServiceKind kind) {
if (kind == ServiceKind::PRIMARY) {
return ToResult(att::ErrorCode::kUnsupportedGroupType);
}
return att::Result<>(fit::ok());
});
ServiceList services;
mgr()->set_service_watcher(
[&services](auto /*removed*/, ServiceList added, auto /*modified*/) {
services.insert(services.end(), added.begin(), added.end());
});
att::Result<> status = fit::ok();
mgr()->Initialize([&status](att::Result<> val) { status = val; },
NopMtuCallback);
RunUntilIdle();
EXPECT_EQ(ToResult(att::ErrorCode::kUnsupportedGroupType), status);
EXPECT_TRUE(services.empty());
}
// Tests that the "unsupported group type" error is NOT treated as a failure for
// secondary services.
TEST_F(RemoteServiceManagerTest,
InitializeSecondaryServicesErrorUnsupportedGroupTypeIsIgnored) {
ServiceData svc1(ServiceKind::PRIMARY, 1, 1, kTestServiceUuid1);
fake_client()->set_services({{svc1}});
fake_client()->set_discover_services_callback([](ServiceKind kind) {
if (kind == ServiceKind::SECONDARY) {
return ToResult(att::ErrorCode::kUnsupportedGroupType);
}
return att::Result<>(fit::ok());
});
ServiceList services;
mgr()->set_service_watcher(
[&services](auto /*removed*/, ServiceList added, auto /*modified*/) {
services.insert(services.end(), added.begin(), added.end());
});
att::Result<> status = fit::ok();
mgr()->Initialize([&status](att::Result<> val) { status = val; },
NopMtuCallback);
RunUntilIdle();
EXPECT_EQ(fit::ok(), status);
ASSERT_EQ(1u, services.size());
EXPECT_EQ(svc1, services[0]->info());
}
TEST_F(RemoteServiceManagerTest, ListServicesBeforeInit) {
ServiceData svc(ServiceKind::PRIMARY, 1, 1, kTestServiceUuid1);
std::vector<ServiceData> fake_services{{svc}};
fake_client()->set_services(std::move(fake_services));
ServiceList services;
mgr()->ListServices(std::vector<UUID>(),
[&services](auto status, ServiceList cb_services) {
services = std::move(cb_services);
});
EXPECT_TRUE(services.empty());
att::Result<> status = ToResult(HostError::kFailed);
mgr()->Initialize([&status](att::Result<> val) { status = val; },
NopMtuCallback);
RunUntilIdle();
EXPECT_EQ(fit::ok(), status);
ASSERT_EQ(1u, services.size());
EXPECT_EQ(svc.range_start, services[0]->handle());
EXPECT_EQ(svc.type, services[0]->uuid());
}
TEST_F(RemoteServiceManagerTest, ListServicesAfterInit) {
ServiceData svc(ServiceKind::PRIMARY, 1, 1, kTestServiceUuid1);
std::vector<ServiceData> fake_services{{svc}};
fake_client()->set_services(std::move(fake_services));
att::Result<> status = ToResult(HostError::kFailed);
mgr()->Initialize([&status](att::Result<> val) { status = val; },
NopMtuCallback);
RunUntilIdle();
ASSERT_EQ(fit::ok(), status);
ServiceList services;
mgr()->ListServices(std::vector<UUID>(),
[&services](auto status, ServiceList cb_services) {
services = std::move(cb_services);
});
ASSERT_EQ(1u, services.size());
EXPECT_EQ(svc.range_start, services[0]->handle());
EXPECT_EQ(svc.type, services[0]->uuid());
}
TEST_F(RemoteServiceManagerTest, ListServicesByUuid) {
std::vector<UUID> uuids{kTestServiceUuid1};
ServiceData svc1(ServiceKind::PRIMARY, 1, 1, kTestServiceUuid1);
ServiceData svc2(ServiceKind::PRIMARY, 2, 2, kTestServiceUuid2);
std::vector<ServiceData> fake_services{{svc1, svc2}};
fake_client()->set_services(std::move(fake_services));
ServiceList service_watcher_services;
mgr()->set_service_watcher([&service_watcher_services](auto /*removed*/,
ServiceList added,
auto /*modified*/) {
service_watcher_services.insert(
service_watcher_services.end(), added.begin(), added.end());
});
att::Result<> list_services_status = fit::ok();
ServiceList list_services;
mgr()->ListServices(std::move(uuids),
[&](att::Result<> cb_status, ServiceList cb_services) {
list_services_status = cb_status;
list_services = std::move(cb_services);
});
ASSERT_TRUE(service_watcher_services.empty());
att::Result<> status = ToResult(HostError::kFailed);
mgr()->Initialize([&status](att::Result<> val) { status = val; },
NopMtuCallback);
RunUntilIdle();
EXPECT_EQ(fit::ok(), status);
EXPECT_EQ(fit::ok(), list_services_status);
// Only svc1 has a type in |uuids|.
EXPECT_EQ(1u, list_services.size());
EXPECT_EQ(svc1.range_start, list_services[0]->handle());
EXPECT_EQ(svc1.type, list_services[0]->uuid());
// All services should be discovered and returned to service watcher because
// Initialize() was not called with a list of uuids to discover.
EXPECT_EQ(2u, service_watcher_services.size());
}
TEST_F(RemoteServiceManagerTest, DiscoverCharacteristicsSuccess) {
auto data = ServiceData(ServiceKind::PRIMARY, 1, 5, kTestServiceUuid1);
auto service = SetUpFakeService(data);
CharacteristicData fake_chrc1(0, std::nullopt, 2, 3, kTestUuid3);
CharacteristicData fake_chrc2(0, std::nullopt, 4, 5, kTestUuid4);
std::vector<CharacteristicData> fake_chrcs{{fake_chrc1, fake_chrc2}};
fake_client()->set_characteristics(std::move(fake_chrcs));
std::map<
CharacteristicHandle,
std::pair<CharacteristicData, std::map<DescriptorHandle, DescriptorData>>>
expected = {{CharacteristicHandle(3), {fake_chrc1, {}}},
{CharacteristicHandle(5), {fake_chrc2, {}}}};
att::Result<> status1 = ToResult(HostError::kFailed);
auto cb = [expected](att::Result<>* status) {
return [status, expected](att::Result<> cb_status, const auto& chrcs) {
*status = cb_status;
EXPECT_EQ(expected, chrcs);
};
};
service->DiscoverCharacteristics(
[&](att::Result<> cb_status, const auto& chrcs) {
status1 = cb_status;
EXPECT_EQ(expected, chrcs);
});
// Queue a second request.
att::Result<> status2 = ToResult(HostError::kFailed);
service->DiscoverCharacteristics(
[&](att::Result<> cb_status, const auto& chrcs) {
status2 = cb_status;
EXPECT_EQ(expected, chrcs);
});
RunUntilIdle();
// Only one ATT request should have been made.
EXPECT_EQ(1u, fake_client()->chrc_discovery_count());
EXPECT_TRUE(service->IsDiscovered());
EXPECT_EQ(fit::ok(), status1);
EXPECT_EQ(fit::ok(), status2);
EXPECT_EQ(data.range_start,
fake_client()->last_chrc_discovery_start_handle());
EXPECT_EQ(data.range_end, fake_client()->last_chrc_discovery_end_handle());
// Request discovery again. This should succeed without an ATT request.
status1 = ToResult(HostError::kFailed);
service->DiscoverCharacteristics(
[&status1](att::Result<> cb_status, const auto&) {
status1 = cb_status;
});
RunUntilIdle();
EXPECT_EQ(fit::ok(), status1);
EXPECT_EQ(1u, fake_client()->chrc_discovery_count());
EXPECT_TRUE(service->IsDiscovered());
}
TEST_F(RemoteServiceManagerTest, DiscoverCharacteristicsError) {
auto service = SetUpFakeService(
ServiceData(ServiceKind::PRIMARY, 1, 5, kTestServiceUuid1));
CharacteristicData chrc1(0, std::nullopt, 2, 3, kTestUuid3);
CharacteristicData chrc2(0, std::nullopt, 4, 5, kTestUuid4);
std::vector<CharacteristicData> fake_chrcs{{chrc1, chrc2}};
fake_client()->set_characteristics(std::move(fake_chrcs));
fake_client()->set_characteristic_discovery_status(
ToResult(HostError::kNotSupported));
att::Result<> status1 = fit::ok();
service->DiscoverCharacteristics(
[&](att::Result<> cb_status, const auto& chrcs) {
status1 = cb_status;
EXPECT_TRUE(chrcs.empty());
});
// Queue a second request.
att::Result<> status2 = fit::ok();
service->DiscoverCharacteristics(
[&](att::Result<> cb_status, const auto& chrcs) {
status2 = cb_status;
EXPECT_TRUE(chrcs.empty());
});
RunUntilIdle();
// Only one request should have been made.
EXPECT_EQ(1u, fake_client()->chrc_discovery_count());
EXPECT_FALSE(service->IsDiscovered());
EXPECT_EQ(ToResult(HostError::kNotSupported), status1);
EXPECT_EQ(ToResult(HostError::kNotSupported), status2);
}
// Discover descriptors of a service with one characteristic.
TEST_F(RemoteServiceManagerTest, DiscoverDescriptorsOfOneSuccess) {
ServiceData data(ServiceKind::PRIMARY, kStart, kEnd, kTestServiceUuid1);
auto service = SetUpFakeService(data);
CharacteristicData fake_chrc(
0, std::nullopt, kCharDecl, kCharValue, kTestUuid3);
fake_client()->set_characteristics({{fake_chrc}});
DescriptorData fake_desc1(kDesc1, kTestUuid3);
DescriptorData fake_desc2(kDesc2, kTestUuid4);
fake_client()->set_descriptors({{fake_desc1, fake_desc2}});
att::Result<> status = ToResult(HostError::kFailed);
service->DiscoverCharacteristics(
[&](att::Result<> cb_status, const auto chrcs) {
status = cb_status;
EXPECT_EQ(1u, chrcs.size());
std::map<CharacteristicHandle,
std::pair<CharacteristicData,
std::map<DescriptorHandle, DescriptorData>>>
expected = {
{CharacteristicHandle(kCharValue),
{fake_chrc, {{kDesc1, fake_desc1}, {kDesc2, fake_desc2}}}}};
EXPECT_EQ(expected, chrcs);
});
RunUntilIdle();
EXPECT_EQ(1u, fake_client()->chrc_discovery_count());
EXPECT_EQ(1u, fake_client()->desc_discovery_count());
EXPECT_TRUE(service->IsDiscovered());
EXPECT_EQ(fit::ok(), status);
EXPECT_EQ(kDesc1, fake_client()->last_desc_discovery_start_handle());
EXPECT_EQ(kEnd, fake_client()->last_desc_discovery_end_handle());
}
// Discover descriptors of a service with one characteristic.
TEST_F(RemoteServiceManagerTest, DiscoverDescriptorsOfOneError) {
ServiceData data(ServiceKind::PRIMARY, kStart, kEnd, kTestServiceUuid1);
auto service = SetUpFakeService(data);
CharacteristicData fake_chrc(
0, std::nullopt, kCharDecl, kCharValue, kTestUuid3);
fake_client()->set_characteristics({{fake_chrc}});
DescriptorData fake_desc1(kDesc1, kTestUuid3);
DescriptorData fake_desc2(kDesc2, kTestUuid4);
fake_client()->set_descriptors({{fake_desc1, fake_desc2}});
fake_client()->set_descriptor_discovery_status(
ToResult(HostError::kNotSupported));
att::Result<> status = fit::ok();
service->DiscoverCharacteristics(
[&](att::Result<> cb_status, const auto& chrcs) {
status = cb_status;
EXPECT_TRUE(chrcs.empty());
});
RunUntilIdle();
EXPECT_EQ(1u, fake_client()->chrc_discovery_count());
EXPECT_EQ(1u, fake_client()->desc_discovery_count());
EXPECT_FALSE(service->IsDiscovered());
EXPECT_EQ(ToResult(HostError::kNotSupported), status);
}
// Discover descriptors of a service with multiple characteristics
TEST_F(RemoteServiceManagerTest, DiscoverDescriptorsOfMultipleSuccess) {
// Has one descriptor
CharacteristicData fake_char1(0, std::nullopt, 2, 3, kTestUuid3);
DescriptorData fake_desc1(4, kTestUuid4);
// Has no descriptors
CharacteristicData fake_char2(0, std::nullopt, 5, 6, kTestUuid3);
// Has two descriptors
CharacteristicData fake_char3(0, std::nullopt, 7, 8, kTestUuid3);
DescriptorData fake_desc2(9, kTestUuid4);
DescriptorData fake_desc3(10, kTestUuid4);
ServiceData data(
ServiceKind::PRIMARY, 1, fake_desc3.handle, kTestServiceUuid1);
auto service = SetUpFakeService(data);
fake_client()->set_characteristics({{fake_char1, fake_char2, fake_char3}});
fake_client()->set_descriptors({{fake_desc1, fake_desc2, fake_desc3}});
att::Result<> status = ToResult(HostError::kFailed);
service->DiscoverCharacteristics([&](att::Result<> cb_status,
const auto& chrcs) {
status = cb_status;
std::map<CharacteristicHandle,
std::pair<CharacteristicData,
std::map<DescriptorHandle, DescriptorData>>>
expected = {{CharacteristicHandle(3), {fake_char1, {{4, fake_desc1}}}},
{CharacteristicHandle(6), {fake_char2, {}}},
{CharacteristicHandle(8),
{fake_char3, {{9, fake_desc2}, {10, fake_desc3}}}}};
EXPECT_EQ(expected, chrcs);
});
RunUntilIdle();
EXPECT_EQ(1u, fake_client()->chrc_discovery_count());
// There should have been two descriptor discovery requests as discovery
// should have been skipped for characteristic #2 due to its handles.
EXPECT_EQ(2u, fake_client()->desc_discovery_count());
EXPECT_TRUE(service->IsDiscovered());
EXPECT_EQ(fit::ok(), status);
}
// Discover descriptors of a service with multiple characteristics. The first
// request results in an error though others succeed.
TEST_F(RemoteServiceManagerTest, DiscoverDescriptorsOfMultipleEarlyFail) {
// Has one descriptor
CharacteristicData fake_char1(0, std::nullopt, 2, 3, kTestUuid3);
DescriptorData fake_desc1(4, kTestUuid4);
// Has no descriptors
CharacteristicData fake_char2(0, std::nullopt, 5, 6, kTestUuid3);
// Has two descriptors
CharacteristicData fake_char3(0, std::nullopt, 7, 8, kTestUuid3);
DescriptorData fake_desc2(9, kTestUuid4);
DescriptorData fake_desc3(10, kTestUuid4);
ServiceData data(
ServiceKind::PRIMARY, 1, fake_desc3.handle, kTestServiceUuid1);
auto service = SetUpFakeService(data);
fake_client()->set_characteristics({{fake_char1, fake_char2, fake_char3}});
fake_client()->set_descriptors({{fake_desc1, fake_desc2, fake_desc3}});
// The first request will fail
fake_client()->set_descriptor_discovery_status(
ToResult(HostError::kNotSupported), 1);
att::Result<> status = ToResult(HostError::kFailed);
service->DiscoverCharacteristics(
[&](att::Result<> cb_status, const auto& chrcs) {
status = cb_status;
EXPECT_TRUE(chrcs.empty());
});
RunUntilIdle();
EXPECT_EQ(1u, fake_client()->chrc_discovery_count());
// There should have been two descriptor discovery requests as discovery
// should have been skipped for characteristic #2 due to its handles.
EXPECT_EQ(2u, fake_client()->desc_discovery_count());
EXPECT_FALSE(service->IsDiscovered());
EXPECT_EQ(ToResult(HostError::kNotSupported), status);
}
// Discover descriptors of a service with multiple characteristics. The last
// request results in an error while the preceding ones succeed.
TEST_F(RemoteServiceManagerTest, DiscoverDescriptorsOfMultipleLateFail) {
// Has one descriptor
CharacteristicData fake_char1(0, std::nullopt, 2, 3, kTestUuid3);
DescriptorData fake_desc1(4, kTestUuid4);
// Has no descriptors
CharacteristicData fake_char2(0, std::nullopt, 5, 6, kTestUuid3);
// Has two descriptors
CharacteristicData fake_char3(0, std::nullopt, 7, 8, kTestUuid3);
DescriptorData fake_desc2(9, kTestUuid4);
DescriptorData fake_desc3(10, kTestUuid4);
ServiceData data(
ServiceKind::PRIMARY, 1, fake_desc3.handle, kTestServiceUuid1);
auto service = SetUpFakeService(data);
fake_client()->set_characteristics({{fake_char1, fake_char2, fake_char3}});
fake_client()->set_descriptors({{fake_desc1, fake_desc2, fake_desc3}});
// The last request will fail
fake_client()->set_descriptor_discovery_status(
ToResult(HostError::kNotSupported), 2);
att::Result<> status = ToResult(HostError::kFailed);
service->DiscoverCharacteristics(
[&](att::Result<> cb_status, const auto& chrcs) {
status = cb_status;
EXPECT_TRUE(chrcs.empty());
});
RunUntilIdle();
EXPECT_EQ(1u, fake_client()->chrc_discovery_count());
// There should have been two descriptor discovery requests as discovery
// should have been skipped for characteristic #2 due to its handles.
EXPECT_EQ(2u, fake_client()->desc_discovery_count());
EXPECT_FALSE(service->IsDiscovered());
EXPECT_EQ(ToResult(HostError::kNotSupported), status);
}
// Discover descriptors of a service with extended properties set.
TEST_F(RemoteServiceManagerTest,
DiscoverDescriptorsWithExtendedPropertiesSuccess) {
ServiceData data(ServiceKind::PRIMARY, kStart, kEnd, kTestServiceUuid1);
auto service = SetUpFakeService(data);
// The ExtendedProperties of the characteristic is set.
const Properties props = Property::kExtendedProperties;
CharacteristicData fake_chrc(
props, std::nullopt, kCharDecl, kCharValue, kTestUuid3);
DescriptorData fake_desc1(kDesc1, types::kCharacteristicExtProperties);
DescriptorData fake_desc2(kDesc2, kTestUuid4);
SetCharacteristicsAndDescriptors({fake_chrc}, {fake_desc1, fake_desc2});
// The callback should be triggered once to read the value of the descriptor
// containing the ExtendedProperties bitfield.
size_t read_cb_count = 0;
auto extended_prop_read_cb = [&](att::Handle handle, auto callback) {
EXPECT_EQ(kDesc1, handle);
callback(fit::ok(), kExtendedPropValue, /*maybe_truncated=*/false);
read_cb_count++;
};
fake_client()->set_read_request_callback(std::move(extended_prop_read_cb));
att::Result<> status = ToResult(HostError::kFailed);
service->DiscoverCharacteristics([&](att::Result<> cb_status,
const auto chrcs) {
status = cb_status;
EXPECT_EQ(1u, chrcs.size());
CharacteristicData expected_chrc(
props, kReliableWrite, kCharDecl, kCharValue, kTestUuid3);
std::map<CharacteristicHandle,
std::pair<CharacteristicData,
std::map<DescriptorHandle, DescriptorData>>>
expected = {
{CharacteristicHandle(kCharValue),
{expected_chrc, {{kDesc1, fake_desc1}, {kDesc2, fake_desc2}}}}};
EXPECT_EQ(expected, chrcs);
// Validate that the ExtendedProperties have been written to the |chrcs|
// returned in the callback.
CharacteristicData chrc_data =
chrcs.at(CharacteristicHandle(kCharValue)).first;
EXPECT_TRUE(chrc_data.extended_properties.has_value());
EXPECT_EQ(ExtendedProperty::kReliableWrite,
chrc_data.extended_properties.value());
});
RunUntilIdle();
EXPECT_EQ(1u, fake_client()->chrc_discovery_count());
EXPECT_EQ(1u, fake_client()->desc_discovery_count());
EXPECT_EQ(1u, read_cb_count);
EXPECT_TRUE(service->IsDiscovered());
EXPECT_EQ(fit::ok(), status);
EXPECT_EQ(kDesc1, fake_client()->last_desc_discovery_start_handle());
EXPECT_EQ(kEnd, fake_client()->last_desc_discovery_end_handle());
}
// Discover descriptors of a service that doesn't contain the ExtendedProperties
// bit set, but with a descriptor containing an ExtendedProperty value. This is
// not invalid, as per the spec, and so discovery shouldn't fail.
TEST_F(RemoteServiceManagerTest, DiscoverDescriptorsExtendedPropertiesNotSet) {
ServiceData data(ServiceKind::PRIMARY, kStart, kEnd, kTestServiceUuid1);
auto service = SetUpFakeService(data);
// The ExtendedProperties of the characteristic is not set.
CharacteristicData fake_chrc(
0, std::nullopt, kCharDecl, kCharValue, kTestUuid3);
DescriptorData fake_desc1(kDesc1, types::kCharacteristicExtProperties);
SetCharacteristicsAndDescriptors({fake_chrc}, {fake_desc1});
// Callback should not be executed.
size_t read_cb_count = 0;
auto extended_prop_read_cb = [&](att::Handle handle, auto callback) {
callback(fit::ok(), kExtendedPropValue, /*maybe_truncated=*/false);
read_cb_count++;
};
fake_client()->set_read_request_callback(std::move(extended_prop_read_cb));
att::Result<> status = ToResult(HostError::kFailed);
service->DiscoverCharacteristics(
[&](att::Result<> cb_status, const auto chrcs) {
status = cb_status;
EXPECT_EQ(1u, chrcs.size());
std::map<CharacteristicHandle,
std::pair<CharacteristicData,
std::map<DescriptorHandle, DescriptorData>>>
expected = {{CharacteristicHandle(kCharValue),
{fake_chrc, {{kDesc1, fake_desc1}}}}};
EXPECT_EQ(expected, chrcs);
// Validate that the ExtendedProperties has not been updated.
CharacteristicData chrc_data =
chrcs.at(CharacteristicHandle(kCharValue)).first;
EXPECT_FALSE(chrc_data.extended_properties.has_value());
});
RunUntilIdle();
EXPECT_EQ(1u, fake_client()->chrc_discovery_count());
EXPECT_EQ(1u, fake_client()->desc_discovery_count());
EXPECT_EQ(0u, read_cb_count);
EXPECT_TRUE(service->IsDiscovered());
EXPECT_EQ(fit::ok(), status);
}
// Discover descriptors of a service with two descriptors containing
// ExtendedProperties. This is invalid, and discovery should fail.
TEST_F(RemoteServiceManagerTest,
DiscoverDescriptorsMultipleExtendedPropertiesError) {
ServiceData data(ServiceKind::PRIMARY, kStart, kEnd, kTestServiceUuid1);
auto service = SetUpFakeService(data);
// The ExtendedProperties of the characteristic is set.
const Properties props = Property::kExtendedProperties;
CharacteristicData fake_chrc(
props, std::nullopt, kCharDecl, kCharValue, kTestUuid3);
// Two descriptors with ExtProperties.
DescriptorData fake_desc1(kDesc1, types::kCharacteristicExtProperties);
DescriptorData fake_desc2(kDesc2, types::kCharacteristicExtProperties);
SetCharacteristicsAndDescriptors({fake_chrc}, {fake_desc1, fake_desc2});
size_t read_cb_count = 0;
auto extended_prop_read_cb = [&](att::Handle handle, auto callback) {
callback(fit::ok(), kExtendedPropValue, /*maybe_truncated=*/false);
read_cb_count++;
};
fake_client()->set_read_request_callback(std::move(extended_prop_read_cb));
att::Result<> status = ToResult(HostError::kFailed);
service->DiscoverCharacteristics(
[&](att::Result<> cb_status, const auto chrcs) {
status = cb_status;
EXPECT_TRUE(chrcs.empty());
});
RunUntilIdle();
EXPECT_EQ(1u, fake_client()->chrc_discovery_count());
EXPECT_EQ(1u, fake_client()->desc_discovery_count());
EXPECT_EQ(0u, read_cb_count);
EXPECT_FALSE(service->IsDiscovered());
EXPECT_EQ(ToResult(HostError::kFailed), status);
}
// Discover descriptors of a service with ExtendedProperties set, but with
// an error when reading the descriptor value. Discovery should fail.
TEST_F(RemoteServiceManagerTest,
DiscoverDescriptorsExtendedPropertiesReadDescValueError) {
ServiceData data(ServiceKind::PRIMARY, kStart, kEnd, kTestServiceUuid1);
auto service = SetUpFakeService(data);
// The ExtendedProperties of the characteristic is set.
const Properties props = Property::kExtendedProperties;
CharacteristicData fake_chrc(
props, std::nullopt, kCharDecl, kCharValue, kTestUuid3);
DescriptorData fake_desc1(kDesc1, types::kCharacteristicExtProperties);
DescriptorData fake_desc2(kDesc2, kTestUuid4);
SetCharacteristicsAndDescriptors({fake_chrc}, {fake_desc1, fake_desc2});
// The callback should be triggered once to read the value of the descriptor
// containing the ExtendedProperties bitfield.
size_t read_cb_count = 0;
auto extended_prop_read_cb = [&](att::Handle handle, auto callback) {
EXPECT_EQ(kDesc1, handle);
callback(ToResult(att::ErrorCode::kReadNotPermitted),
BufferView(),
/*maybe_truncated=*/false);
read_cb_count++;
};
fake_client()->set_read_request_callback(std::move(extended_prop_read_cb));
att::Result<> status = ToResult(HostError::kFailed);
service->DiscoverCharacteristics(
[&](att::Result<> cb_status, const auto chrcs) {
status = cb_status;
EXPECT_TRUE(chrcs.empty());
});
RunUntilIdle();
EXPECT_EQ(1u, read_cb_count);
EXPECT_FALSE(service->IsDiscovered());
ASSERT_TRUE(status.is_error());
EXPECT_TRUE(status.error_value().is_protocol_error());
}
// Discover descriptors of a service with ExtendedProperties set, but with
// a malformed response when reading the descriptor value. Discovery should
// fail.
TEST_F(RemoteServiceManagerTest,
DiscoverDescriptorsExtendedPropertiesReadDescInvalidValue) {
ServiceData data(ServiceKind::PRIMARY, kStart, kEnd, kTestServiceUuid1);
auto service = SetUpFakeService(data);
// The ExtendedProperties of the characteristic is set.
const Properties props = Property::kExtendedProperties;
CharacteristicData fake_chrc(
props, std::nullopt, kCharDecl, kCharValue, kTestUuid3);
DescriptorData fake_desc1(kDesc1, types::kCharacteristicExtProperties);
DescriptorData fake_desc2(kDesc2, kTestUuid4);
SetCharacteristicsAndDescriptors({fake_chrc}, {fake_desc1, fake_desc2});
// The callback should be triggered once to read the value of the descriptor
// containing the ExtendedProperties bitfield.
size_t read_cb_count = 0;
auto extended_prop_read_cb = [&](att::Handle handle, auto callback) {
EXPECT_EQ(kDesc1, handle);
callback(fit::ok(),
BufferView(),
/*maybe_truncated=*/false); // Invalid return buf
read_cb_count++;
};
fake_client()->set_read_request_callback(std::move(extended_prop_read_cb));
att::Result<> status = ToResult(HostError::kFailed);
service->DiscoverCharacteristics(
[&](att::Result<> cb_status, const auto chrcs) {
status = cb_status;
EXPECT_TRUE(chrcs.empty());
});
RunUntilIdle();
EXPECT_EQ(1u, fake_client()->chrc_discovery_count());
EXPECT_EQ(1u, fake_client()->desc_discovery_count());
EXPECT_EQ(1u, read_cb_count);
EXPECT_FALSE(service->IsDiscovered());
EXPECT_EQ(ToResult(HostError::kPacketMalformed), status);
}
constexpr CharacteristicHandle kDefaultCharacteristic(3);
constexpr CharacteristicHandle kSecondCharacteristic(6);
constexpr CharacteristicHandle kInvalidCharacteristic(1);
constexpr att::Handle kDefaultChrcValueHandle = 3;
CharacteristicData UnreadableChrc() {
return CharacteristicData(
0, std::nullopt, 2, kDefaultChrcValueHandle, kTestUuid3);
}
CharacteristicData ReadableChrc() {
return CharacteristicData(
Property::kRead, std::nullopt, 2, kDefaultChrcValueHandle, kTestUuid3);
}
CharacteristicData WritableChrc() {
return CharacteristicData(
Property::kWrite, std::nullopt, 2, kDefaultChrcValueHandle, kTestUuid3);
}
CharacteristicData WriteableExtendedPropChrc() {
auto props = Property::kWrite | Property::kExtendedProperties;
return CharacteristicData(
props, std::nullopt, 2, kDefaultChrcValueHandle, kTestUuid3);
}
TEST_F(RemoteServiceManagerTest, ReadCharWhileNotReady) {
auto service = SetUpFakeService(
ServiceData(ServiceKind::PRIMARY, 1, 2, kTestServiceUuid1));
att::Result<> status = fit::ok();
service->ReadCharacteristic(
kDefaultCharacteristic,
[&](att::Result<> cb_status, const auto&, auto) { status = cb_status; });
RunUntilIdle();
EXPECT_EQ(ToResult(HostError::kNotReady), status);
}
TEST_F(RemoteServiceManagerTest, ReadCharNotFound) {
auto service = SetupServiceWithChrcs(
ServiceData(ServiceKind::PRIMARY, 1, 2, kTestServiceUuid1), {});
att::Result<> status = fit::ok();
service->ReadCharacteristic(
kDefaultCharacteristic,
[&](att::Result<> cb_status, const auto&, auto) { status = cb_status; });
RunUntilIdle();
EXPECT_EQ(ToResult(HostError::kNotFound), status);
}
TEST_F(RemoteServiceManagerTest, ReadCharNotSupported) {
auto service = SetupServiceWithChrcs(
ServiceData(ServiceKind::PRIMARY, 1, 3, kTestServiceUuid1),
{UnreadableChrc()});
att::Result<> status = fit::ok();
service->ReadCharacteristic(
kDefaultCharacteristic,
[&](att::Result<> cb_status, const auto&, auto) { status = cb_status; });
RunUntilIdle();
EXPECT_EQ(ToResult(HostError::kNotSupported), status);
}
TEST_F(RemoteServiceManagerTest, ReadCharSendsReadRequest) {
auto service = SetupServiceWithChrcs(
ServiceData(
ServiceKind::PRIMARY, 1, kDefaultChrcValueHandle, kTestServiceUuid1),
{ReadableChrc()});
const StaticByteBuffer kValue('t', 'e', 's', 't');
fake_client()->set_read_request_callback(
[&](att::Handle handle, auto callback) {
EXPECT_EQ(kDefaultChrcValueHandle, handle);
callback(fit::ok(), kValue, /*maybe_truncated=*/false);
});
att::Result<> status = ToResult(HostError::kFailed);
service->ReadCharacteristic(
kDefaultCharacteristic,
[&](att::Result<> cb_status, const auto& value, bool maybe_truncated) {
status = cb_status;
EXPECT_TRUE(ContainersEqual(kValue, value));
EXPECT_FALSE(maybe_truncated);
});
RunUntilIdle();
EXPECT_EQ(fit::ok(), status);
}
TEST_F(RemoteServiceManagerTest, ReadLongWhileNotReady) {
auto service = SetUpFakeService(
ServiceData(ServiceKind::PRIMARY, 1, 2, kTestServiceUuid1));
att::Result<> status = fit::ok();
service->ReadLongCharacteristic(
CharacteristicHandle(0),
0,
512,
[&](att::Result<> cb_status, const auto&, auto) { status = cb_status; });
RunUntilIdle();
EXPECT_EQ(ToResult(HostError::kNotReady), status);
}
TEST_F(RemoteServiceManagerTest, ReadLongNotFound) {
auto service = SetupServiceWithChrcs(
ServiceData(ServiceKind::PRIMARY, 1, 2, kTestServiceUuid1), {});
att::Result<> status = fit::ok();
service->ReadLongCharacteristic(
CharacteristicHandle(0),
0,
512,
[&](att::Result<> cb_status, const auto&, auto) { status = cb_status; });
RunUntilIdle();
EXPECT_EQ(ToResult(HostError::kNotFound), status);
}
TEST_F(RemoteServiceManagerTest, ReadLongNotSupported) {
auto service = SetupServiceWithChrcs(
ServiceData(ServiceKind::PRIMARY, 1, 3, kTestServiceUuid1),
{UnreadableChrc()});
att::Result<> status = fit::ok();
service->ReadLongCharacteristic(
kDefaultCharacteristic,
0,
512,
[&](att::Result<> cb_status, const auto&, auto) { status = cb_status; });
RunUntilIdle();
EXPECT_EQ(ToResult(HostError::kNotSupported), status);
}
// 0 is not a valid parameter for the |max_size| field of ReadLongCharacteristic
TEST_F(RemoteServiceManagerTest, ReadLongMaxSizeZero) {
auto service = SetupServiceWithChrcs(
ServiceData(ServiceKind::PRIMARY, 1, 3, kTestServiceUuid1),
{ReadableChrc()});
att::Result<> status = fit::ok();
service->ReadLongCharacteristic(
kDefaultCharacteristic,
0,
0,
[&](att::Result<> cb_status, const auto&, auto) { status = cb_status; });
RunUntilIdle();
EXPECT_EQ(ToResult(HostError::kInvalidParameters), status);
}
// The complete attribute value is read in a single request.
TEST_F(RemoteServiceManagerTest, ReadLongSingleBlob) {
constexpr uint16_t kOffset = 0;
constexpr size_t kMaxBytes = 1000;
auto service = SetupServiceWithChrcs(
ServiceData(
ServiceKind::PRIMARY, 1, kDefaultChrcValueHandle, kTestServiceUuid1),
{ReadableChrc()});
const StaticByteBuffer kValue('t', 'e', 's', 't');
int request_count = 0;
fake_client()->set_read_request_callback(
[&](att::Handle handle, auto callback) {
request_count++;
EXPECT_EQ(request_count, 1);
EXPECT_EQ(kDefaultChrcValueHandle, handle);
callback(fit::ok(), kValue, /*maybe_truncated=*/false);
});
att::Result<> status = ToResult(HostError::kFailed);
service->ReadLongCharacteristic(
kDefaultCharacteristic,
kOffset,
kMaxBytes,
[&](att::Result<> cb_status, const auto& value, bool maybe_truncated) {
status = cb_status;
EXPECT_TRUE(ContainersEqual(kValue, value));
EXPECT_FALSE(maybe_truncated);
});
RunUntilIdle();
EXPECT_EQ(fit::ok(), status);
}
TEST_F(RemoteServiceManagerTest, ReadLongMultipleBlobs) {
constexpr uint16_t kOffset = 0;
constexpr size_t kMaxBytes = 1000;
constexpr int kExpectedBlobCount = 4;
auto service = SetupServiceWithChrcs(
ServiceData(
ServiceKind::PRIMARY, 1, kDefaultChrcValueHandle, kTestServiceUuid1),
{ReadableChrc()});
// Create a buffer that will take 4 requests to read. Since the default MTU is
// 23:
// a. The size of |expected_value| is 69.
// b. We should read 22 + 22 + 22 + 3 bytes across 4 requests.
StaticByteBuffer<att::kLEMinMTU * 3> expected_value;
// Initialize the contents.
for (size_t i = 0; i < expected_value.size(); ++i) {
expected_value[i] = i;
}
int read_count = 0;
fake_client()->set_read_request_callback(
[&](att::Handle handle, auto callback) {
read_count++;
EXPECT_EQ(read_count, 1);
EXPECT_EQ(kDefaultChrcValueHandle, handle);
auto blob = expected_value.view(0, att::kLEMinMTU - 1);
callback(fit::ok(), blob, /*maybe_truncated=*/true);
});
fake_client()->set_read_blob_request_callback(
[&](att::Handle handle, uint16_t offset, auto callback) {
read_count++;
EXPECT_GT(read_count, 1);
EXPECT_EQ(kDefaultChrcValueHandle, handle);
bool maybe_truncated = true;
// Return a blob at the given offset with at most MTU - 1 bytes.
auto blob = expected_value.view(offset, att::kLEMinMTU - 1);
if (read_count == kExpectedBlobCount) {
// The final blob should contain 3 bytes.
EXPECT_EQ(3u, blob.size());
maybe_truncated = false;
}
callback(fit::ok(), blob, maybe_truncated);
});
att::Result<> status = ToResult(HostError::kFailed);
service->ReadLongCharacteristic(
kDefaultCharacteristic,
kOffset,
kMaxBytes,
[&](att::Result<> cb_status, const auto& value, bool maybe_truncated) {
status = cb_status;
EXPECT_TRUE(ContainersEqual(expected_value, value));
EXPECT_FALSE(maybe_truncated);
});
RunUntilIdle();
EXPECT_EQ(fit::ok(), status);
EXPECT_EQ(kExpectedBlobCount, read_count);
}
// Simulates a peer that rejects a read blob request with a kAttributeNotLong
// error. The initial read request completes successfully and contains the
// entire value. The specification implies that the peer can either respond with
// an empty buffer or a kAttributeNotLong error for the second request.
TEST_F(RemoteServiceManagerTest,
ReadLongCharacteristicAttributeNotLongErrorIgnored) {
constexpr uint16_t kOffset = 0;
constexpr size_t kMaxBytes = 1000;
constexpr int kExpectedBlobCount = 2;
auto service = SetupServiceWithChrcs(
ServiceData(
ServiceKind::PRIMARY, 1, kDefaultChrcValueHandle, kTestServiceUuid1),
{ReadableChrc()});
StaticByteBuffer<att::kLEMinMTU - 1> expected_value;
for (size_t i = 0; i < expected_value.size(); ++i) {
expected_value[i] = i;
}
int read_count = 0;
fake_client()->set_read_request_callback(
[&](att::Handle handle, auto callback) {
read_count++;
EXPECT_EQ(read_count, 1);
callback(fit::ok(), expected_value.view(), /*maybe_truncated=*/true);
});
fake_client()->set_read_blob_request_callback(
[&](att::Handle handle, uint16_t offset, auto callback) {
read_count++;
EXPECT_EQ(read_count, 2);
callback(ToResult(att::ErrorCode::kAttributeNotLong),
BufferView(),
/*maybe_truncated=*/false);
});
att::Result<> status = ToResult(HostError::kFailed);
service->ReadLongCharacteristic(
kDefaultCharacteristic,
kOffset,
kMaxBytes,
[&](att::Result<> cb_status, const auto& value, bool maybe_truncated) {
status = cb_status;
EXPECT_TRUE(ContainersEqual(expected_value, value));
EXPECT_FALSE(maybe_truncated);
});
RunUntilIdle();
EXPECT_EQ(fit::ok(), status);
EXPECT_EQ(kExpectedBlobCount, read_count);
}
TEST_F(RemoteServiceManagerTest,
ReadLongCharacteristicAttributeNotLongErrorOnFirstReadRequest) {
constexpr uint16_t kOffset = 0;
constexpr size_t kMaxBytes = 1000;
constexpr int kExpectedBlobCount = 1;
auto service = SetupServiceWithChrcs(
ServiceData(
ServiceKind::PRIMARY, 1, kDefaultChrcValueHandle, kTestServiceUuid1),
{ReadableChrc()});
int read_count = 0;
fake_client()->set_read_request_callback(
[&](att::Handle handle, auto callback) {
read_count++;
EXPECT_EQ(read_count, 1);
callback(ToResult(att::ErrorCode::kAttributeNotLong),
BufferView(),
/*maybe_truncated=*/false);
});
fake_client()->set_read_blob_request_callback(
[&](auto, auto, auto) { FAIL(); });
att::Result<> status = fit::ok();
service->ReadLongCharacteristic(
kDefaultCharacteristic,
kOffset,
kMaxBytes,
[&](att::Result<> cb_status, const auto& value, bool maybe_truncated) {
status = cb_status;
EXPECT_FALSE(maybe_truncated);
});
RunUntilIdle();
EXPECT_EQ(ToResult(att::ErrorCode::kAttributeNotLong), status);
EXPECT_EQ(kExpectedBlobCount, read_count);
}
// Same as ReadLongMultipleBlobs except the characteristic value has a size that
// is a multiple of (ATT_MTU - 1), so that the last read blob request returns 0
// bytes.
TEST_F(RemoteServiceManagerTest, ReadLongValueExactMultipleOfMTU) {
constexpr uint16_t kOffset = 0;
constexpr size_t kMaxBytes = 1000;
constexpr int kExpectedBlobCount = 4;
auto service = SetupServiceWithChrcs(
ServiceData(
ServiceKind::PRIMARY, 1, kDefaultChrcValueHandle, kTestServiceUuid1),
{ReadableChrc()});
// Create a buffer that will take 4 requests to read. Since the default MTU is
// 23:
// a. The size of |expected_value| is 66.
// b. We should read 22 + 22 + 22 + 0 bytes across 4 requests.
StaticByteBuffer<(att::kLEMinMTU - 1) * 3> expected_value;
// Initialize the contents.
for (size_t i = 0; i < expected_value.size(); ++i) {
expected_value[i] = i;
}
int read_count = 0;
fake_client()->set_read_request_callback(
[&](att::Handle handle, auto callback) {
read_count++;
EXPECT_EQ(read_count, 1);
EXPECT_EQ(kDefaultChrcValueHandle, handle);
auto blob = expected_value.view(0, att::kLEMinMTU - 1);
callback(fit::ok(), blob, /*maybe_truncated=*/true);
});
fake_client()->set_read_blob_request_callback(
[&](att::Handle handle, uint16_t offset, auto callback) {
read_count++;
EXPECT_GT(read_count, 1);
EXPECT_EQ(kDefaultChrcValueHandle, handle);
bool maybe_truncated = true;
// Return a blob at the given offset with at most MTU - 1 bytes.
auto blob = expected_value.view(offset, att::kLEMinMTU - 1);
if (read_count == kExpectedBlobCount) {
// The final blob should be empty.
EXPECT_EQ(0u, blob.size());
maybe_truncated = false;
}
callback(fit::ok(), blob, maybe_truncated);
});
att::Result<> status = ToResult(HostError::kFailed);
service->ReadLongCharacteristic(
kDefaultCharacteristic,
kOffset,
kMaxBytes,
[&](att::Result<> cb_status, const auto& value, bool maybe_truncated) {
status = cb_status;
EXPECT_TRUE(ContainersEqual(expected_value, value));
EXPECT_FALSE(maybe_truncated);
});
RunUntilIdle();
EXPECT_EQ(fit::ok(), status);
EXPECT_EQ(kExpectedBlobCount, read_count);
}
// Same as ReadLongMultipleBlobs but a maximum size is given that is smaller
// than the size of the attribute value.
TEST_F(RemoteServiceManagerTest, ReadLongMultipleBlobsWithMaxSize) {
constexpr uint16_t kOffset = 0;
constexpr size_t kMaxBytes = 40;
constexpr int kExpectedBlobCount = 2;
auto service = SetupServiceWithChrcs(
ServiceData(
ServiceKind::PRIMARY, 1, kDefaultChrcValueHandle, kTestServiceUuid1),
{ReadableChrc()});
// Reads will return 22 + 22 bytes across 2 requests, but only 18 bytes of the
// second read will be reported to ReadLongCharacteristic (the value will be
// truncated).
StaticByteBuffer<att::kLEMinMTU * 3> expected_value;
// Initialize the contents.
for (size_t i = 0; i < expected_value.size(); ++i) {
expected_value[i] = i;
}
int read_count = 0;
fake_client()->set_read_request_callback(
[&](att::Handle handle, auto callback) {
read_count++;
EXPECT_EQ(read_count, 1);
EXPECT_EQ(kDefaultChrcValueHandle, handle);
BufferView blob = expected_value.view(0, att::kLEMinMTU - 1);
callback(fit::ok(), blob, /*maybe_truncated=*/true);
});
fake_client()->set_read_blob_request_callback(
[&](att::Handle handle, uint16_t offset, auto callback) {
read_count++;
EXPECT_GT(read_count, 1);
EXPECT_EQ(kDefaultChrcValueHandle, handle);
BufferView blob = expected_value.view(offset, att::kLEMinMTU - 1);
callback(fit::ok(), blob, /*maybe_truncated=*/true);
});
att::Result<> status = ToResult(HostError::kFailed);
service->ReadLongCharacteristic(
kDefaultCharacteristic,
kOffset,
kMaxBytes,
[&](att::Result<> cb_status, const auto& value, bool maybe_truncated) {
status = cb_status;
EXPECT_TRUE(ContainersEqual(expected_value.view(0, kMaxBytes), value));
EXPECT_TRUE(maybe_truncated);
});
RunUntilIdle();
EXPECT_EQ(fit::ok(), status);
EXPECT_EQ(kExpectedBlobCount, read_count);
}
// Same as ReadLongMultipleBlobs but a non-zero offset is given.
TEST_F(RemoteServiceManagerTest, ReadLongAtOffset) {
constexpr uint16_t kOffset = 30;
constexpr size_t kMaxBytes = 1000;
constexpr int kExpectedBlobCount = 2;
auto service = SetupServiceWithChrcs(
ServiceData(
ServiceKind::PRIMARY, 1, kDefaultChrcValueHandle, kTestServiceUuid1),
{ReadableChrc()});
// Size: 69.
// Reads starting at offset 30 will return 22 + 17 bytes across 2 requests.
StaticByteBuffer<att::kLEMinMTU * 3> expected_value;
// Initialize the contents.
for (size_t i = 0; i < expected_value.size(); ++i) {
expected_value[i] = i;
}
int read_blob_count = 0;
fake_client()->set_read_blob_request_callback(
[&](att::Handle handle, uint16_t offset, auto callback) {
EXPECT_EQ(kDefaultChrcValueHandle, handle);
read_blob_count++;
BufferView blob = expected_value.view(offset, att::kLEMinMTU - 1);
bool maybe_truncated = (read_blob_count != kExpectedBlobCount);
callback(fit::ok(), blob, maybe_truncated);
});
att::Result<> status = ToResult(HostError::kFailed);
service->ReadLongCharacteristic(
kDefaultCharacteristic,
kOffset,
kMaxBytes,
[&](att::Result<> cb_status, const auto& value, bool maybe_truncated) {
status = cb_status;
EXPECT_TRUE(
ContainersEqual(expected_value.view(kOffset, kMaxBytes), value));
EXPECT_FALSE(maybe_truncated);
});
RunUntilIdle();
EXPECT_EQ(fit::ok(), status);
EXPECT_EQ(kExpectedBlobCount, read_blob_count);
}
// Same as ReadLongAtOffset but a very small max size is given.
TEST_F(RemoteServiceManagerTest, ReadLongAtOffsetWithMaxBytes) {
constexpr uint16_t kOffset = 10;
constexpr size_t kMaxBytes = 34;
constexpr int kExpectedBlobCount = 2;
auto service = SetupServiceWithChrcs(
ServiceData(
ServiceKind::PRIMARY, 1, kDefaultChrcValueHandle, kTestServiceUuid1),
{ReadableChrc()});
// Size: 69.
// Reads starting at offset 10 will return 22 + 22 bytes across 2 requests,
// but the second read value will be truncated to 12 bytes by RemoteService
// due to |kMaxBytes|. A third read blob should not be sent since this should
// satisfy |kMaxBytes|.
StaticByteBuffer<att::kLEMinMTU * 3> expected_value;
// Initialize the contents.
for (size_t i = 0; i < expected_value.size(); ++i) {
expected_value[i] = static_cast<uint8_t>(i);
}
int read_blob_count = 0;
fake_client()->set_read_blob_request_callback(
[&](att::Handle handle, uint16_t offset, auto callback) {
EXPECT_EQ(kDefaultChrcValueHandle, handle);
read_blob_count++;
BufferView blob = expected_value.view(offset, att::kLEMinMTU - 1);
callback(fit::ok(), blob, /*maybe_truncated=*/true);
});
att::Result<> status = ToResult(HostError::kFailed);
service->ReadLongCharacteristic(
kDefaultCharacteristic,
kOffset,
kMaxBytes,
[&](att::Result<> cb_status, const auto& value, bool maybe_truncated) {
status = cb_status;
EXPECT_TRUE(
ContainersEqual(expected_value.view(kOffset, kMaxBytes), value));
EXPECT_TRUE(maybe_truncated);
});
RunUntilIdle();
EXPECT_EQ(fit::ok(), status);
EXPECT_EQ(kExpectedBlobCount, read_blob_count);
}
TEST_F(RemoteServiceManagerTest, ReadLongError) {
constexpr uint16_t kOffset = 0;
constexpr size_t kMaxBytes = 1000;
constexpr int kExpectedBlobCount = 2; // The second request will fail.
auto service = SetupServiceWithChrcs(
ServiceData(
ServiceKind::PRIMARY, 1, kDefaultChrcValueHandle, kTestServiceUuid1),
{ReadableChrc()});
// Make the first blob large enough that it will cause a second read blob
// request.
StaticByteBuffer<att::kLEMinMTU - 1> first_blob;
int read_count = 0;
fake_client()->set_read_request_callback(
[&](att::Handle handle, auto callback) {
read_count++;
EXPECT_EQ(read_count, 1);
EXPECT_EQ(kDefaultChrcValueHandle, handle);
callback(fit::ok(), first_blob, /*maybe_truncated=*/true);
});
fake_client()->set_read_blob_request_callback(
[&](att::Handle handle, uint16_t offset, auto callback) {
read_count++;
EXPECT_EQ(read_count, 2);
EXPECT_EQ(kDefaultChrcValueHandle, handle);
callback(ToResult(att::ErrorCode::kInvalidOffset),
BufferView(),
/*maybe_truncated=*/false);
});
att::Result<> status = fit::ok();
service->ReadLongCharacteristic(
kDefaultCharacteristic,
kOffset,
kMaxBytes,
[&](att::Result<> cb_status, const auto& value, bool maybe_truncated) {
status = cb_status;
EXPECT_EQ(0u, value.size()); // No value should be returned on error.
EXPECT_FALSE(maybe_truncated);
});
RunUntilIdle();
EXPECT_EQ(ToResult(att::ErrorCode::kInvalidOffset), status);
EXPECT_EQ(kExpectedBlobCount, read_count);
}
TEST_F(RemoteServiceManagerTest,
ReadByTypeSendsReadRequestsUntilAttributeNotFound) {
constexpr att::Handle kStartHandle = 1;
constexpr att::Handle kEndHandle = 5;
auto service = SetUpFakeService(ServiceData(
ServiceKind::PRIMARY, kStartHandle, kEndHandle, kTestServiceUuid1));
constexpr UUID kCharUuid(uint16_t{0xfefe});
constexpr att::Handle kHandle0 = 2;
const StaticByteBuffer kValue0(0x00, 0x01, 0x02);
const std::vector<Client::ReadByTypeValue> kValues0 = {
{kHandle0, kValue0.view(), /*maybe_truncated=*/false}};
constexpr att::Handle kHandle1 = 3;
const StaticByteBuffer kValue1(0x03, 0x04, 0x05);
const std::vector<Client::ReadByTypeValue> kValues1 = {
{kHandle1, kValue1.view(), /*maybe_truncated=*/true}};
size_t read_count = 0;
fake_client()->set_read_by_type_request_callback(
[&](const UUID& type, att::Handle start, att::Handle end, auto callback) {
switch (read_count++) {
case 0:
EXPECT_EQ(kStartHandle, start);
callback(fit::ok(kValues0));
break;
case 1:
EXPECT_EQ(kHandle0 + 1, start);
callback(fit::ok(kValues1));
break;
case 2:
EXPECT_EQ(kHandle1 + 1, start);
callback(fit::error(Client::ReadByTypeError{
att::Error(att::ErrorCode::kAttributeNotFound), start}));
break;
default:
FAIL();
}
});
std::optional<att::Result<>> status;
service->ReadByType(
kCharUuid,
[&](att::Result<> cb_status,
std::vector<RemoteService::ReadByTypeResult> values) {
status = cb_status;
EXPECT_EQ(fit::ok(), *status);
ASSERT_EQ(2u, values.size());
EXPECT_EQ(CharacteristicHandle(kHandle0), values[0].handle);
ASSERT_EQ(fit::ok(), values[0].result);
EXPECT_TRUE(ContainersEqual(kValue0, *values[0].result.value()));
EXPECT_FALSE(values[0].maybe_truncated);
EXPECT_EQ(CharacteristicHandle(kHandle1), values[1].handle);
ASSERT_EQ(fit::ok(), values[1].result);
EXPECT_TRUE(ContainersEqual(kValue1, *values[1].result.value()));
EXPECT_TRUE(values[1].maybe_truncated);
});
RunUntilIdle();
ASSERT_TRUE(status.has_value());
// kAttributeNotFound error should be treated as success.
EXPECT_EQ(fit::ok(), *status);
}
TEST_F(RemoteServiceManagerTest,
ReadByTypeSendsReadRequestsUntilServiceEndHandle) {
constexpr att::Handle kStartHandle = 1;
constexpr att::Handle kEndHandle = 2;
auto service = SetUpFakeService(ServiceData(
ServiceKind::PRIMARY, kStartHandle, kEndHandle, kTestServiceUuid1));
constexpr UUID kCharUuid(uint16_t{0xfefe});
constexpr att::Handle kHandle = kEndHandle;
const StaticByteBuffer kValue(0x00, 0x01, 0x02);
const std::vector<Client::ReadByTypeValue> kValues = {
{kHandle, kValue.view(), /*maybe_truncated=*/false}};
size_t read_count = 0;
fake_client()->set_read_by_type_request_callback(
[&](const UUID& type, att::Handle start, att::Handle end, auto callback) {
EXPECT_EQ(kStartHandle, start);
EXPECT_EQ(0u, read_count++);
callback(fit::ok(kValues));
});
std::optional<att::Result<>> status;
service->ReadByType(kCharUuid, [&](att::Result<> cb_status, auto values) {
status = cb_status;
ASSERT_EQ(1u, values.size());
EXPECT_EQ(CharacteristicHandle(kHandle), values[0].handle);
ASSERT_EQ(fit::ok(), values[0].result);
EXPECT_TRUE(ContainersEqual(kValue, *values[0].result.value()));
});
RunUntilIdle();
ASSERT_TRUE(status.has_value());
EXPECT_EQ(fit::ok(), *status);
}
TEST_F(RemoteServiceManagerTest, ReadByTypeReturnsReadErrorsWithResults) {
constexpr att::Handle kStartHandle = 1;
constexpr att::Handle kEndHandle = 5;
auto service = SetUpFakeService(ServiceData(
ServiceKind::PRIMARY, kStartHandle, kEndHandle, kTestServiceUuid1));
constexpr UUID kCharUuid(uint16_t{0xfefe});
const std::array<att::ErrorCode, 5> errors = {
att::ErrorCode::kInsufficientAuthorization,
att::ErrorCode::kInsufficientAuthentication,
att::ErrorCode::kInsufficientEncryptionKeySize,
att::ErrorCode::kInsufficientEncryption,
att::ErrorCode::kReadNotPermitted};
size_t read_count = 0;
fake_client()->set_read_by_type_request_callback(
[&](const UUID& type, att::Handle start, att::Handle end, auto callback) {
if (read_count < errors.size()) {
EXPECT_EQ(kStartHandle + read_count, start);
callback(fit::error(Client::ReadByTypeError{
att::Error(errors[read_count++]), start}));
} else {
FAIL();
}
});
std::optional<att::Result<>> status;
service->ReadByType(kCharUuid, [&](att::Result<> cb_status, auto values) {
status = cb_status;
ASSERT_EQ(errors.size(), values.size());
for (size_t i = 0; i < values.size(); i++) {
SCOPED_TRACE(bt_lib_cpp_string::StringPrintf("i: %zu", i));
EXPECT_EQ(CharacteristicHandle(kStartHandle + i), values[i].handle);
ASSERT_TRUE(values[i].result.is_error());
EXPECT_EQ(errors[i], values[i].result.error_value());
EXPECT_FALSE(values[i].maybe_truncated);
}
});
RunUntilIdle();
ASSERT_TRUE(status.has_value());
ASSERT_EQ(fit::ok(), *status);
}
TEST_F(RemoteServiceManagerTest, ReadByTypeReturnsProtocolErrorAfterRead) {
constexpr att::Handle kStartHandle = 1;
constexpr att::Handle kEndHandle = 5;
auto service = SetUpFakeService(ServiceData(
ServiceKind::PRIMARY, kStartHandle, kEndHandle, kTestServiceUuid1));
constexpr UUID kCharUuid(uint16_t{0xfefe});
constexpr att::Handle kHandle = kEndHandle;
const auto kValue = StaticByteBuffer(0x00, 0x01, 0x02);
const std::vector<Client::ReadByTypeValue> kValues = {
{kHandle, kValue.view(), /*maybe_truncated=*/false}};
const std::vector<std::pair<const char*, att::ErrorCode>>
general_protocol_errors = {
{"kRequestNotSupported", att::ErrorCode::kRequestNotSupported},
{"kInsufficientResources", att::ErrorCode::kInsufficientResources},
{"kInvalidPDU", att::ErrorCode::kInvalidPDU}};
for (const auto& [name, code] : general_protocol_errors) {
SCOPED_TRACE(bt_lib_cpp_string::StringPrintf("Error Code: %s", name));
size_t read_count = 0;
fake_client()->set_read_by_type_request_callback(
[&, code = code](const UUID& type,
att::Handle start,
att::Handle end,
auto callback) {
ASSERT_EQ(0u, read_count++);
switch (read_count++) {
case 0:
callback(fit::ok(kValues));
break;
case 1:
callback(fit::error(
Client::ReadByTypeError{att::Error(code), std::nullopt}));
break;
default:
FAIL();
}
});
std::optional<att::Result<>> status;
service->ReadByType(kCharUuid, [&](att::Result<> cb_status, auto values) {
status = cb_status;
EXPECT_EQ(0u, values.size());
});
RunUntilIdle();
ASSERT_TRUE(status.has_value());
EXPECT_EQ(ToResult(code), status);
}
}
TEST_F(RemoteServiceManagerTest, ReadByTypeHandlesReadErrorWithMissingHandle) {
constexpr att::Handle kStartHandle = 1;
constexpr att::Handle kEndHandle = 5;
auto service = SetUpFakeService(ServiceData(
ServiceKind::PRIMARY, kStartHandle, kEndHandle, kTestServiceUuid1));
constexpr UUID kCharUuid(uint16_t{0xfefe});
size_t read_count = 0;
fake_client()->set_read_by_type_request_callback(
[&](const UUID& type, att::Handle start, att::Handle end, auto callback) {
ASSERT_EQ(0u, read_count++);
callback(fit::error(Client::ReadByTypeError{
att::Error(att::ErrorCode::kReadNotPermitted), std::nullopt}));
});
std::optional<att::Result<>> status;
service->ReadByType(kCharUuid, [&](att::Result<> cb_status, auto values) {
status = cb_status;
});
RunUntilIdle();
ASSERT_TRUE(status.has_value());
EXPECT_EQ(ToResult(att::ErrorCode::kReadNotPermitted), *status);
}
TEST_F(RemoteServiceManagerTest,
ReadByTypeHandlesReadErrorWithOutOfRangeHandle) {
constexpr att::Handle kStartHandle = 1;
constexpr att::Handle kEndHandle = 5;
auto service = SetUpFakeService(ServiceData(
ServiceKind::PRIMARY, kStartHandle, kEndHandle, kTestServiceUuid1));
constexpr UUID kCharUuid(uint16_t{0xfefe});
size_t read_count = 0;
fake_client()->set_read_by_type_request_callback(
[&](const UUID& type, att::Handle start, att::Handle end, auto callback) {
ASSERT_EQ(0u, read_count++);
callback(fit::error(Client::ReadByTypeError{
att::Error(att::ErrorCode::kReadNotPermitted), kEndHandle + 1}));
});
std::optional<att::Result<>> status;
service->ReadByType(kCharUuid, [&](att::Result<> cb_status, auto values) {
status = cb_status;
});
RunUntilIdle();
ASSERT_TRUE(status.has_value());
EXPECT_EQ(ToResult(HostError::kPacketMalformed), *status);
}
TEST_F(RemoteServiceManagerTest, ReadByTypeReturnsErrorIfUuidIsInternal) {
const std::array<UUID, 10> kInternalUuids = {
types::kPrimaryService,
types::kSecondaryService,
types::kIncludeDeclaration,
types::kCharacteristicDeclaration,
types::kCharacteristicExtProperties,
types::kCharacteristicUserDescription,
types::kClientCharacteristicConfig,
types::kServerCharacteristicConfig,
types::kCharacteristicFormat,
types::kCharacteristicAggregateFormat};
auto service = SetUpFakeService(ServiceData(
ServiceKind::PRIMARY, 1, kDefaultChrcValueHandle, kTestServiceUuid1));
fake_client()->set_read_by_type_request_callback(
[&](auto, auto, auto, auto) { ADD_FAILURE(); });
for (const UUID& uuid : kInternalUuids) {
std::optional<att::Result<>> status;
service->ReadByType(uuid, [&](att::Result<> cb_status, auto values) {
status = cb_status;
EXPECT_EQ(0u, values.size());
});
RunUntilIdle();
ASSERT_TRUE(status.has_value()) << "UUID: " << uuid;
EXPECT_EQ(ToResult(HostError::kInvalidParameters), *status);
}
}
TEST_F(RemoteServiceManagerTest, WriteCharWhileNotReady) {
auto service = SetUpFakeService(
ServiceData(ServiceKind::PRIMARY, 1, 2, kTestServiceUuid1));
att::Result<> status = fit::ok();
service->WriteCharacteristic(
kDefaultCharacteristic,
std::vector<uint8_t>(),
[&](att::Result<> cb_status) { status = cb_status; });
RunUntilIdle();
EXPECT_EQ(ToResult(HostError::kNotReady), status);
}
TEST_F(RemoteServiceManagerTest, WriteCharNotFound) {
auto service = SetupServiceWithChrcs(
ServiceData(ServiceKind::PRIMARY, 1, 2, kTestServiceUuid1), {});
att::Result<> status = fit::ok();
service->WriteCharacteristic(
kDefaultCharacteristic,
std::vector<uint8_t>(),
[&](att::Result<> cb_status) { status = cb_status; });
RunUntilIdle();
EXPECT_EQ(ToResult(HostError::kNotFound), status);
}
TEST_F(RemoteServiceManagerTest, WriteCharNotSupported) {
// No "write" property set.
auto service = SetupServiceWithChrcs(
ServiceData(ServiceKind::PRIMARY, 1, 3, kTestServiceUuid1),
{ReadableChrc()});
att::Result<> status = fit::ok();
service->WriteCharacteristic(
kDefaultCharacteristic,
std::vector<uint8_t>(),
[&](att::Result<> cb_status) { status = cb_status; });
RunUntilIdle();
EXPECT_EQ(ToResult(HostError::kNotSupported), status);
}
TEST_F(RemoteServiceManagerTest, WriteCharSendsWriteRequest) {
const std::vector<uint8_t> kValue{{'t', 'e', 's', 't'}};
constexpr att::Result<> kStatus =
ToResult(att::ErrorCode::kWriteNotPermitted);
auto service = SetupServiceWithChrcs(
ServiceData(
ServiceKind::PRIMARY, 1, kDefaultChrcValueHandle, kTestServiceUuid1),
{WritableChrc()});
fake_client()->set_write_request_callback(
[&](att::Handle handle, const auto& value, auto status_callback) {
EXPECT_EQ(kDefaultChrcValueHandle, handle);
EXPECT_TRUE(std::equal(
kValue.begin(), kValue.end(), value.begin(), value.end()));
status_callback(kStatus);
});
att::Result<> status = fit::ok();
service->WriteCharacteristic(
kDefaultCharacteristic, kValue, [&](att::Result<> cb_status) {
status = cb_status;
});
RunUntilIdle();
EXPECT_EQ(kStatus, status);
}
// Tests that a long write is chunked up properly into a series of QueuedWrites
// that will be processed by the client. This tests a non-zero offset.
TEST_F(RemoteServiceManagerTest, WriteCharLongOffsetSuccess) {
constexpr uint16_t kOffset = 5;
constexpr uint16_t kExpectedQueueSize = 4;
constexpr uint16_t kExpectedFullWriteSize = 18;
constexpr uint16_t kExpectedFinalWriteSize = 15;
auto service = SetupServiceWithChrcs(
ServiceData(
ServiceKind::PRIMARY, 1, kDefaultChrcValueHandle, kTestServiceUuid1),
{WritableChrc()});
// Create a vector that will take 4 requests to write. Since the default MTU
// is 23:
// a. The size of |full_write_value| is 69.
// b. att:Handle, |kOffset|, and att::OpCode size is 5 bytes total.
// c. We should write 18 + 18 + 18 + 15 bytes across 4 requests.
// d. These bytes will be written with offset 5, from (5) to (5+69)
std::vector<uint8_t> full_write_value(att::kLEMinMTU * 3);
// Initialize the contents.
for (size_t i = 0; i < full_write_value.size(); ++i) {
full_write_value[i] = i;
}
uint8_t process_long_write_count = 0;
fake_client()->set_execute_prepare_writes_callback(
[&](att::PrepareWriteQueue write_queue,
auto /*reliable_mode*/,
auto callback) {
EXPECT_EQ(write_queue.size(), kExpectedQueueSize);
for (int i = 0; i < kExpectedQueueSize; i++) {
auto write = std::move(write_queue.front());
write_queue.pop();
EXPECT_EQ(write.handle(), kDefaultChrcValueHandle);
EXPECT_EQ(write.offset(), kOffset + (i * kExpectedFullWriteSize));
// All writes expect the final should be full, the final should be
// the remainder.
if (i < kExpectedQueueSize - 1) {
EXPECT_EQ(write.value().size(), kExpectedFullWriteSize);
} else {
EXPECT_EQ(write.value().size(), kExpectedFinalWriteSize);
}
}
process_long_write_count++;
callback(fit::ok());
});
ReliableMode mode = ReliableMode::kDisabled;
att::Result<> status = ToResult(HostError::kFailed);
service->WriteLongCharacteristic(
kDefaultCharacteristic,
kOffset,
full_write_value,
mode,
[&](att::Result<> cb_status) { status = cb_status; });
RunUntilIdle();
EXPECT_EQ(fit::ok(), status);
EXPECT_EQ(1u, process_long_write_count);
}
TEST_F(RemoteServiceManagerTest, WriteCharLongAtExactMultipleOfMtu) {
constexpr uint16_t kOffset = 0;
constexpr uint16_t kExpectedQueueSize = 4;
constexpr uint16_t kExpectedFullWriteSize = 18;
auto service = SetupServiceWithChrcs(
ServiceData(
ServiceKind::PRIMARY, 1, kDefaultChrcValueHandle, kTestServiceUuid1),
{WritableChrc()});
// Create a vector that will take 4 requests to write. Since the default MTU
// is 23:
// a. The size of |full_write_value| is 72.
// b. att:Handle, |kOffset|, and att::OpCode size is 5 bytes total.
// c. We should write 18 + 18 + 18 + 18 bytes across 4 requests.
// d. These bytes will be written with offset 0, from (0) to (72)
std::vector<uint8_t> full_write_value((att::kLEMinMTU - 5) * 4);
// Initialize the contents.
for (size_t i = 0; i < full_write_value.size(); ++i) {
full_write_value[i] = i;
}
uint8_t process_long_write_count = 0;
fake_client()->set_execute_prepare_writes_callback(
[&](att::PrepareWriteQueue write_queue,
auto /*reliable_mode*/,
auto callback) {
EXPECT_EQ(write_queue.size(), kExpectedQueueSize);
for (int i = 0; i < kExpectedQueueSize; i++) {
auto write = std::move(write_queue.front());
write_queue.pop();
EXPECT_EQ(write.handle(), kDefaultChrcValueHandle);
EXPECT_EQ(write.offset(), kOffset + (i * kExpectedFullWriteSize));
// All writes should be full
EXPECT_EQ(write.value().size(), kExpectedFullWriteSize);
}
process_long_write_count++;
callback(fit::ok());
});
ReliableMode mode = ReliableMode::kDisabled;
att::Result<> status = ToResult(HostError::kFailed);
service->WriteLongCharacteristic(
kDefaultCharacteristic,
kOffset,
full_write_value,
mode,
[&](att::Result<> cb_status) { status = cb_status; });
RunUntilIdle();
EXPECT_EQ(fit::ok(), status);
EXPECT_EQ(1u, process_long_write_count);
}
// Writing a long characteristic with ReliableMode::Enabled should succeed.
TEST_F(RemoteServiceManagerTest, WriteCharLongReliableWrite) {
constexpr uint16_t kOffset = 0;
constexpr uint16_t kExpectedQueueSize = 1;
DescriptorData fake_desc1(kDesc1, types::kCharacteristicExtProperties);
DescriptorData fake_desc2(kDesc2, kTestUuid4);
// The callback should be triggered once to read the value of the descriptor
// containing the ExtendedProperties bitfield.
auto extended_prop_read_cb = [&](att::Handle handle, auto callback) {
callback(fit::ok(), kExtendedPropValue, /*maybe_truncated=*/false);
};
fake_client()->set_read_request_callback(std::move(extended_prop_read_cb));
auto service = SetupServiceWithChrcs(
ServiceData(ServiceKind::PRIMARY, kStart, kEnd, kTestServiceUuid1),
{WriteableExtendedPropChrc()},
{fake_desc1, fake_desc2});
// Create a vector that will take 1 request to write. Since the default MTU
// is 23:
// a. The size of |full_write_value| is 18.
// b. att:Handle, |kOffset|, and att::OpCode size is 5 bytes total.
// c. We should write 18 bytes.
std::vector<uint8_t> full_write_value((att::kLEMinMTU - 5));
// Initialize the contents.
for (size_t i = 0; i < full_write_value.size(); ++i) {
full_write_value[i] = i;
}
uint8_t process_long_write_count = 0;
fake_client()->set_execute_prepare_writes_callback(
[&](att::PrepareWriteQueue write_queue,
auto /*reliable_mode*/,
auto callback) {
EXPECT_EQ(write_queue.size(), kExpectedQueueSize);
process_long_write_count++;
callback(fit::ok());
});
ReliableMode mode = ReliableMode::kEnabled;
att::Result<> status = ToResult(HostError::kFailed);
service->WriteLongCharacteristic(
kDefaultCharacteristic,
kOffset,
full_write_value,
mode,
[&](att::Result<> cb_status) { status = cb_status; });
RunUntilIdle();
EXPECT_EQ(fit::ok(), status);
EXPECT_EQ(1u, process_long_write_count);
}
TEST_F(RemoteServiceManagerTest, WriteWithoutResponseNotSupported) {
ServiceData data(ServiceKind::PRIMARY, 1, 3, kTestServiceUuid1);
auto service = SetUpFakeService(data);
// No "write" or "write without response" property.
CharacteristicData chr(0, std::nullopt, 2, 3, kTestUuid3);
SetupCharacteristics(service, {{chr}});
bool called = false;
fake_client()->set_write_without_rsp_callback(
[&](auto, const auto&, auto) { called = true; });
std::optional<att::Result<>> status;
service->WriteCharacteristicWithoutResponse(
kDefaultCharacteristic,
std::vector<uint8_t>(),
[&](att::Result<> cb_status) { status = cb_status; });
RunUntilIdle();
EXPECT_FALSE(called);
ASSERT_TRUE(status.has_value());
EXPECT_EQ(ToResult(HostError::kNotSupported), *status);
}
TEST_F(RemoteServiceManagerTest,
WriteWithoutResponseBeforeCharacteristicDiscovery) {
ServiceData data(ServiceKind::PRIMARY, 1, 3, kTestServiceUuid1);
auto service = SetUpFakeService(data);
bool called = false;
fake_client()->set_write_without_rsp_callback(
[&](auto, const auto&, auto) { called = true; });
std::optional<att::Result<>> status;
service->WriteCharacteristicWithoutResponse(
kDefaultCharacteristic,
std::vector<uint8_t>(),
[&](att::Result<> cb_status) { status = cb_status; });
RunUntilIdle();
EXPECT_FALSE(called);
ASSERT_TRUE(status.has_value());
EXPECT_EQ(ToResult(HostError::kNotReady), *status);
}
TEST_F(RemoteServiceManagerTest,
WriteWithoutResponseSuccessWithWriteWithoutResponseProperty) {
const std::vector<uint8_t> kValue{{'t', 'e', 's', 't'}};
CharacteristicData chr(Property::kWriteWithoutResponse,
std::nullopt,
2,
kDefaultChrcValueHandle,
kTestUuid3);
auto service = SetupServiceWithChrcs(
ServiceData(
ServiceKind::PRIMARY, 1, kDefaultChrcValueHandle, kTestServiceUuid1),
{chr});
bool called = false;
fake_client()->set_write_without_rsp_callback(
[&](att::Handle handle, const auto& value, att::ResultFunction<> cb) {
EXPECT_EQ(kDefaultChrcValueHandle, handle);
EXPECT_TRUE(std::equal(
kValue.begin(), kValue.end(), value.begin(), value.end()));
called = true;
cb(fit::ok());
});
std::optional<att::Result<>> status;
service->WriteCharacteristicWithoutResponse(
kDefaultCharacteristic, kValue, [&](att::Result<> cb_status) {
status = cb_status;
});
RunUntilIdle();
EXPECT_TRUE(called);
ASSERT_TRUE(status.has_value());
EXPECT_EQ(fit::ok(), *status);
}
TEST_F(RemoteServiceManagerTest, WriteWithoutResponseSuccessWithWriteProperty) {
const std::vector<uint8_t> kValue{{'t', 'e', 's', 't'}};
CharacteristicData chr(
Property::kWrite, std::nullopt, 2, kDefaultChrcValueHandle, kTestUuid3);
auto service = SetupServiceWithChrcs(
ServiceData(
ServiceKind::PRIMARY, 1, kDefaultChrcValueHandle, kTestServiceUuid1),
{chr});
bool called = false;
fake_client()->set_write_without_rsp_callback(
[&](att::Handle handle, const auto& value, att::ResultFunction<> cb) {
EXPECT_EQ(kDefaultChrcValueHandle, handle);
EXPECT_TRUE(std::equal(
kValue.begin(), kValue.end(), value.begin(), value.end()));
called = true;
cb(fit::ok());
});
std::optional<att::Result<>> status;
service->WriteCharacteristicWithoutResponse(
kDefaultCharacteristic, kValue, [&](att::Result<> cb_status) {
status = cb_status;
});
RunUntilIdle();
EXPECT_TRUE(called);
ASSERT_TRUE(status.has_value());
EXPECT_EQ(fit::ok(), *status);
}
TEST_F(RemoteServiceManagerTest, ReadDescWhileNotReady) {
auto service = SetUpFakeService(
ServiceData(ServiceKind::PRIMARY, 1, 2, kTestServiceUuid1));
att::Result<> status = fit::ok();
service->ReadDescriptor(0, [&](att::Result<> cb_status, const auto&, auto) {
status = cb_status;
});
RunUntilIdle();
EXPECT_EQ(ToResult(HostError::kNotReady), status);
}
TEST_F(RemoteServiceManagerTest, ReadDescriptorNotFound) {
auto service = SetupServiceWithChrcs(
ServiceData(ServiceKind::PRIMARY, 1, 2, kTestServiceUuid1), {});
att::Result<> status = fit::ok();
service->ReadDescriptor(0, [&](att::Result<> cb_status, const auto&, auto) {
status = cb_status;
});
RunUntilIdle();
EXPECT_EQ(ToResult(HostError::kNotFound), status);
}
TEST_F(RemoteServiceManagerTest, ReadDescSendsReadRequest) {
// TODO(armansito): Some of the service set up and |status| verification
// boilerplate could be reduced by factoring them out into helpers on the test
// harness (also see code review comment in
// https://fuchsia-review.googlesource.com/c/garnet/+/213794/6/drivers/bluetooth/lib/gatt/remote_service_manager_test.cc).
constexpr att::Handle kValueHandle1 = 3;
constexpr att::Handle kValueHandle2 = 5;
constexpr att::Handle kDescrHandle = 6;
ServiceData data(ServiceKind::PRIMARY, 1, kDescrHandle, kTestServiceUuid1);
auto service = SetUpFakeService(data);
CharacteristicData chr1(
Property::kRead, std::nullopt, 2, kValueHandle1, kTestUuid3);
CharacteristicData chr2(
Property::kRead, std::nullopt, 4, kValueHandle2, kTestUuid3);
DescriptorData desc(kDescrHandle, kTestUuid4);
SetupCharacteristics(service, {{chr1, chr2}}, {{desc}});
const StaticByteBuffer kValue('t', 'e', 's', 't');
fake_client()->set_read_request_callback(
[&](att::Handle handle, auto callback) {
EXPECT_EQ(kDescrHandle, handle);
callback(fit::ok(), kValue, /*maybe_truncated=*/false);
});
att::Result<> status = ToResult(HostError::kFailed);
service->ReadDescriptor(
DescriptorHandle(kDescrHandle),
[&](att::Result<> cb_status, const auto& value, auto) {
status = cb_status;
EXPECT_TRUE(ContainersEqual(kValue, value));
});
RunUntilIdle();
EXPECT_EQ(fit::ok(), status);
}
TEST_F(RemoteServiceManagerTest, ReadLongDescWhileNotReady) {
auto service = SetUpFakeService(
ServiceData(ServiceKind::PRIMARY, 1, 2, kTestServiceUuid1));
att::Result<> status = fit::ok();
service->ReadLongDescriptor(
0, 0, 512, [&](att::Result<> cb_status, const auto&, auto) {
status = cb_status;
});
RunUntilIdle();
EXPECT_EQ(ToResult(HostError::kNotReady), status);
}
TEST_F(RemoteServiceManagerTest, ReadLongDescNotFound) {
auto service = SetupServiceWithChrcs(
ServiceData(ServiceKind::PRIMARY, 1, 2, kTestServiceUuid1), {});
att::Result<> status = fit::ok();
service->ReadLongDescriptor(
0, 0, 512, [&](att::Result<> cb_status, const auto&, auto) {
status = cb_status;
});
RunUntilIdle();
EXPECT_EQ(ToResult(HostError::kNotFound), status);
}
// Tests that ReadLongDescriptor sends Read Blob requests. Other conditions
// around the long read procedure are already covered by the tests for
// ReadLongCharacteristic as the implementations are shared.
TEST_F(RemoteServiceManagerTest, ReadLongDescriptor) {
constexpr att::Handle kValueHandle = 3;
constexpr att::Handle kDescrHandle = 4;
constexpr uint16_t kOffset = 0;
constexpr size_t kMaxBytes = 1000;
constexpr int kExpectedBlobCount = 4;
ServiceData data(ServiceKind::PRIMARY, 1, kDescrHandle, kTestServiceUuid1);
auto service = SetUpFakeService(data);
CharacteristicData chr(
Property::kRead, std::nullopt, 2, kValueHandle, kTestUuid3);
DescriptorData desc(kDescrHandle, kTestUuid4);
SetupCharacteristics(service, {{chr}}, {{desc}});
// Create a buffer that will take 4 requests to read. Since the default MTU is
// 23:
// a. The size of |expected_value| is 69.
// b. We should read 22 + 22 + 22 + 3 bytes across 4 requests.
StaticByteBuffer<att::kLEMinMTU * 3> expected_value;
// Initialize the contents.
for (size_t i = 0; i < expected_value.size(); ++i) {
expected_value[i] = i;
}
int read_count = 0;
fake_client()->set_read_request_callback(
[&](att::Handle handle, auto callback) {
read_count++;
EXPECT_EQ(read_count, 1);
EXPECT_EQ(kDescrHandle, handle);
auto blob = expected_value.view(0, att::kLEMinMTU - 1);
callback(fit::ok(), blob, /*maybe_truncated=*/true);
});
fake_client()->set_read_blob_request_callback(
[&](att::Handle handle, uint16_t offset, auto callback) {
read_count++;
EXPECT_GT(read_count, 1);
EXPECT_EQ(kDescrHandle, handle);
bool maybe_truncated = true;
// Return a blob at the given offset with at most MTU - 1 bytes.
auto blob = expected_value.view(offset, att::kLEMinMTU - 1);
if (read_count == kExpectedBlobCount) {
// The final blob should contain 3 bytes.
EXPECT_EQ(3u, blob.size());
maybe_truncated = false;
}
callback(fit::ok(), blob, maybe_truncated);
});
att::Result<> status = ToResult(HostError::kFailed);
service->ReadLongDescriptor(
DescriptorHandle(kDescrHandle),
kOffset,
kMaxBytes,
[&](att::Result<> cb_status, const auto& value, bool maybe_truncated) {
status = cb_status;
EXPECT_TRUE(ContainersEqual(expected_value, value));
EXPECT_FALSE(maybe_truncated);
});
RunUntilIdle();
EXPECT_EQ(fit::ok(), status);
EXPECT_EQ(kExpectedBlobCount, read_count);
}
TEST_F(RemoteServiceManagerTest, WriteDescWhileNotReady) {
auto service = SetUpFakeService(
ServiceData(ServiceKind::PRIMARY, 1, 2, kTestServiceUuid1));
att::Result<> status = fit::ok();
service->WriteDescriptor(
0, std::vector<uint8_t>(), [&](att::Result<> cb_status) {
status = cb_status;
});
RunUntilIdle();
EXPECT_EQ(ToResult(HostError::kNotReady), status);
}
TEST_F(RemoteServiceManagerTest, WriteDescNotFound) {
auto service = SetUpFakeService(
ServiceData(ServiceKind::PRIMARY, 1, 2, kTestServiceUuid1));
SetupCharacteristics(service, std::vector<CharacteristicData>());
att::Result<> status = fit::ok();
service->WriteDescriptor(
0, std::vector<uint8_t>(), [&](att::Result<> cb_status) {
status = cb_status;
});
RunUntilIdle();
EXPECT_EQ(ToResult(HostError::kNotFound), status);
}
TEST_F(RemoteServiceManagerTest, WriteDescNotAllowed) {
auto service = SetUpFakeService(
ServiceData(ServiceKind::PRIMARY, 1, 4, kTestServiceUuid1));
// "CCC" characteristic cannot be written to.
CharacteristicData chr(0, std::nullopt, 2, 3, kTestUuid3);
DescriptorData desc(4, types::kClientCharacteristicConfig);
SetupCharacteristics(service, {{chr}}, {{desc}});
att::Result<> status = fit::ok();
service->WriteDescriptor(
4, std::vector<uint8_t>(), [&](att::Result<> cb_status) {
status = cb_status;
});
RunUntilIdle();
EXPECT_EQ(ToResult(HostError::kNotSupported), status);
}
TEST_F(RemoteServiceManagerTest, WriteDescSendsWriteRequest) {
constexpr att::Handle kValueHandle = 3;
constexpr att::Handle kDescrHandle = 4;
const std::vector<uint8_t> kValue{{'t', 'e', 's', 't'}};
const att::Result<> kStatus = ToResult(HostError::kNotSupported);
ServiceData data(ServiceKind::PRIMARY, 1, kDescrHandle, kTestServiceUuid1);
auto service = SetUpFakeService(data);
CharacteristicData chr(
Property::kWrite, std::nullopt, 2, kValueHandle, kTestUuid3);
DescriptorData desc(kDescrHandle, kTestUuid4);
SetupCharacteristics(service, {{chr}}, {{desc}});
fake_client()->set_write_request_callback(
[&](att::Handle handle, const auto& value, auto status_callback) {
EXPECT_EQ(kDescrHandle, handle);
EXPECT_TRUE(std::equal(
kValue.begin(), kValue.end(), value.begin(), value.end()));
status_callback(kStatus);
});
att::Result<> status = fit::ok();
service->WriteDescriptor(kDescrHandle, kValue, [&](att::Result<> cb_status) {
status = cb_status;
});
RunUntilIdle();
EXPECT_EQ(kStatus, status);
}
// Tests that WriteDescriptor with a long vector is prepared correctly.
// Other conditions around the long write procedure are already covered by the
// tests for WriteCharacteristic as the implementations are shared.
TEST_F(RemoteServiceManagerTest, WriteDescLongSuccess) {
constexpr att::Handle kValueHandle = 3;
constexpr att::Handle kDescrHandle = 4;
constexpr uint16_t kOffset = 0;
constexpr uint16_t kExpectedQueueSize = 4;
constexpr uint16_t kExpectedFullWriteSize = 18;
constexpr uint16_t kExpectedFinalWriteSize = 15;
ServiceData data(ServiceKind::PRIMARY, 1, kDescrHandle, kTestServiceUuid1);
auto service = SetUpFakeService(data);
CharacteristicData chr(
Property::kWrite, std::nullopt, 2, kValueHandle, kTestUuid3);
DescriptorData desc(kDescrHandle, kTestUuid4);
SetupCharacteristics(service, {{chr}}, {{desc}});
// Create a vector that will take 4 requests to write. Since the default MTU
// is 23:
// a. The size of |full_write_value| is 69.
// b. att:Handle, |kOffset|, and att::OpCode size is 5 bytes total.
// c. We should write 18 + 18 + 18 + 15 bytes across 4 requests.
// d. These bytes will be written with offset 5, from (5) to (5+69)
std::vector<uint8_t> full_write_value(att::kLEMinMTU * 3);
// Initialize the contents.
for (size_t i = 0; i < full_write_value.size(); ++i) {
full_write_value[i] = i;
}
uint8_t process_long_write_count = 0;
fake_client()->set_execute_prepare_writes_callback(
[&](att::PrepareWriteQueue write_queue,
auto /*reliable_mode*/,
auto callback) {
EXPECT_EQ(write_queue.size(), kExpectedQueueSize);
att::QueuedWrite prepare_write;
for (int i = 0; i < kExpectedQueueSize; i++) {
auto write = std::move(write_queue.front());
write_queue.pop();
EXPECT_EQ(write.handle(), kDescrHandle);
EXPECT_EQ(write.offset(), kOffset + (i * kExpectedFullWriteSize));
// All writes expect the final should be full, the final should be
// the remainder.
if (i < kExpectedQueueSize - 1) {
EXPECT_EQ(write.value().size(), kExpectedFullWriteSize);
} else {
EXPECT_EQ(write.value().size(), kExpectedFinalWriteSize);
}
}
process_long_write_count++;
callback(fit::ok());
});
att::Result<> status = ToResult(HostError::kFailed);
service->WriteLongDescriptor(
DescriptorHandle(kDescrHandle),
kOffset,
full_write_value,
[&](att::Result<> cb_status) { status = cb_status; });
RunUntilIdle();
EXPECT_EQ(fit::ok(), status);
EXPECT_EQ(1u, process_long_write_count);
}
TEST_F(RemoteServiceManagerTest, EnableNotificationsWhileNotReady) {
auto service = SetUpFakeService(
ServiceData(ServiceKind::PRIMARY, 1, 2, kTestServiceUuid1));
att::Result<> status = fit::ok();
service->EnableNotifications(
kDefaultCharacteristic,
NopValueCallback,
[&](att::Result<> cb_status, IdType) { status = cb_status; });
RunUntilIdle();
EXPECT_EQ(ToResult(HostError::kNotReady), status);
}
TEST_F(RemoteServiceManagerTest, EnableNotificationsCharNotFound) {
auto service = SetupServiceWithChrcs(
ServiceData(ServiceKind::PRIMARY, 1, 2, kTestServiceUuid1), {});
att::Result<> status = fit::ok();
service->EnableNotifications(
kDefaultCharacteristic,
NopValueCallback,
[&](att::Result<> cb_status, IdType) { status = cb_status; });
RunUntilIdle();
EXPECT_EQ(ToResult(HostError::kNotFound), status);
}
TEST_F(RemoteServiceManagerTest, EnableNotificationsNoProperties) {
auto service = SetUpFakeService(
ServiceData(ServiceKind::PRIMARY, 1, 4, kTestServiceUuid1));
// Has neither the "notify" nor "indicate" property but has a CCC descriptor.
CharacteristicData chr(Property::kRead, std::nullopt, 2, 3, kTestUuid3);
DescriptorData desc(4, types::kClientCharacteristicConfig);
SetupCharacteristics(service, {{chr}}, {{desc}});
att::Result<> status = fit::ok();
service->EnableNotifications(
kDefaultCharacteristic,
NopValueCallback,
[&](att::Result<> cb_status, IdType) { status = cb_status; });
RunUntilIdle();
EXPECT_EQ(ToResult(HostError::kNotSupported), status);
}
TEST_F(RemoteServiceManagerTest, EnableNotificationsSuccess) {
constexpr att::Handle kCCCHandle = 4;
auto service = SetUpFakeService(
ServiceData(ServiceKind::PRIMARY, 1, kCCCHandle, kTestServiceUuid1));
CharacteristicData chr(Property::kNotify, std::nullopt, 2, 3, kTestUuid3);
DescriptorData desc(kCCCHandle, types::kClientCharacteristicConfig);
SetupCharacteristics(service, {{chr}}, {{desc}});
fake_client()->set_write_request_callback(
[&](att::Handle handle, const auto& value, auto status_callback) {
EXPECT_EQ(kCCCHandle, handle);
EXPECT_TRUE(ContainersEqual(kCCCNotifyValue, value));
status_callback(fit::ok());
});
IdType id = kInvalidId;
att::Result<> status = ToResult(HostError::kFailed);
service->EnableNotifications(kDefaultCharacteristic,
NopValueCallback,
[&](att::Result<> cb_status, IdType cb_id) {
status = cb_status;
id = cb_id;
});
RunUntilIdle();
EXPECT_EQ(fit::ok(), status);
EXPECT_NE(kInvalidId, id);
}
TEST_F(RemoteServiceManagerTest, EnableIndications) {
constexpr att::Handle kCCCHandle = 4;
auto service = SetUpFakeService(
ServiceData(ServiceKind::PRIMARY, 1, kCCCHandle, kTestServiceUuid1));
CharacteristicData chr(Property::kIndicate, std::nullopt, 2, 3, kTestUuid3);
DescriptorData desc(kCCCHandle, types::kClientCharacteristicConfig);
SetupCharacteristics(service, {{chr}}, {{desc}});
fake_client()->set_write_request_callback(
[&](att::Handle handle, const auto& value, auto status_callback) {
EXPECT_EQ(kCCCHandle, handle);
EXPECT_TRUE(ContainersEqual(kCCCIndicateValue, value));
status_callback(fit::ok());
});
IdType id = kInvalidId;
att::Result<> status = ToResult(HostError::kFailed);
service->EnableNotifications(kDefaultCharacteristic,
NopValueCallback,
[&](att::Result<> cb_status, IdType cb_id) {
status = cb_status;
id = cb_id;
});
RunUntilIdle();
EXPECT_EQ(fit::ok(), status);
EXPECT_NE(kInvalidId, id);
}
TEST_F(RemoteServiceManagerTest, EnableNotificationsError) {
constexpr att::Handle kCCCHandle = 4;
auto service = SetUpFakeService(
ServiceData(ServiceKind::PRIMARY, 1, 4, kTestServiceUuid1));
CharacteristicData chr(Property::kNotify, std::nullopt, 2, 3, kTestUuid3);
DescriptorData desc(kCCCHandle, types::kClientCharacteristicConfig);
SetupCharacteristics(service, {{chr}}, {{desc}});
// Should enable notifications
const StaticByteBuffer kExpectedValue(0x01, 0x00);
fake_client()->set_write_request_callback(
[&](att::Handle handle, const auto& value, auto status_callback) {
EXPECT_EQ(kCCCHandle, handle);
EXPECT_TRUE(ContainersEqual(kExpectedValue, value));
status_callback(ToResult(att::ErrorCode::kUnlikelyError));
});
IdType id = kInvalidId;
att::Result<> status = fit::ok();
service->EnableNotifications(kDefaultCharacteristic,
NopValueCallback,
[&](att::Result<> cb_status, IdType cb_id) {
status = cb_status;
id = cb_id;
});
RunUntilIdle();
EXPECT_EQ(ToResult(att::ErrorCode::kUnlikelyError), status);
EXPECT_EQ(kInvalidId, id);
}
TEST_F(RemoteServiceManagerTest, EnableNotificationsRequestMany) {
constexpr att::Handle kCCCHandle1 = 4;
constexpr att::Handle kCCCHandle2 = 7;
auto service = SetUpFakeService(
ServiceData(ServiceKind::PRIMARY, 1, 7, kTestServiceUuid1));
// Set up two characteristics
CharacteristicData chr1(Property::kNotify, std::nullopt, 2, 3, kTestUuid3);
DescriptorData desc1(kCCCHandle1, types::kClientCharacteristicConfig);
CharacteristicData chr2(Property::kIndicate, std::nullopt, 5, 6, kTestUuid3);
DescriptorData desc2(kCCCHandle2, types::kClientCharacteristicConfig);
SetupCharacteristics(service, {{chr1, chr2}}, {{desc1, desc2}});
int ccc_write_count = 0;
att::ResultFunction<> status_callback1, status_callback2;
fake_client()->set_write_request_callback(
[&](att::Handle handle, const auto& value, auto status_cb) {
if (handle == kCCCHandle1) {
EXPECT_TRUE(ContainersEqual(kCCCNotifyValue, value));
status_callback1 = std::move(status_cb);
} else if (handle == kCCCHandle2) {
EXPECT_TRUE(ContainersEqual(kCCCIndicateValue, value));
status_callback2 = std::move(status_cb);
} else {
ADD_FAILURE() << "Unexpected handle: " << handle;
}
ccc_write_count++;
});
size_t cb_count = 0u;
service->EnableNotifications(kDefaultCharacteristic,
NopValueCallback,
[&](att::Result<> status, IdType id) {
cb_count++;
EXPECT_EQ(1u, id);
EXPECT_EQ(fit::ok(), status);
});
service->EnableNotifications(kDefaultCharacteristic,
NopValueCallback,
[&](att::Result<> status, IdType id) {
cb_count++;
EXPECT_EQ(2u, id);
EXPECT_EQ(fit::ok(), status);
});
service->EnableNotifications(kSecondCharacteristic,
NopValueCallback,
[&](att::Result<> status, IdType id) {
cb_count++;
EXPECT_EQ(1u, id);
EXPECT_EQ(fit::ok(), status);
});
service->EnableNotifications(kSecondCharacteristic,
NopValueCallback,
[&](att::Result<> status, IdType id) {
cb_count++;
EXPECT_EQ(2u, id);
EXPECT_EQ(fit::ok(), status);
});
service->EnableNotifications(kSecondCharacteristic,
NopValueCallback,
[&](att::Result<> status, IdType id) {
cb_count++;
EXPECT_EQ(3u, id);
EXPECT_EQ(fit::ok(), status);
});
RunUntilIdle();
// ATT write requests should be sent but none of the notification requests
// should be resolved.
EXPECT_EQ(2, ccc_write_count);
EXPECT_EQ(0u, cb_count);
// An ATT response should resolve all pending requests for the right
// characteristic.
status_callback1(fit::ok());
EXPECT_EQ(2u, cb_count);
status_callback2(fit::ok());
EXPECT_EQ(5u, cb_count);
// An extra request should succeed without sending any PDUs.
service->EnableNotifications(kDefaultCharacteristic,
NopValueCallback,
[&](att::Result<> status, IdType) {
cb_count++;
EXPECT_EQ(fit::ok(), status);
});
RunUntilIdle();
EXPECT_EQ(2, ccc_write_count);
EXPECT_EQ(6u, cb_count);
}
TEST_F(RemoteServiceManagerTest, EnableNotificationsRequestManyError) {
constexpr att::Handle kCCCHandle = 4;
auto service = SetUpFakeService(
ServiceData(ServiceKind::PRIMARY, 1, 4, kTestServiceUuid1));
// Set up two characteristics
CharacteristicData chr(Property::kNotify, std::nullopt, 2, 3, kTestUuid3);
DescriptorData desc(kCCCHandle, types::kClientCharacteristicConfig);
SetupCharacteristics(service, {{chr}}, {{desc}});
int ccc_write_count = 0;
att::ResultFunction<> status_callback;
fake_client()->set_write_request_callback(
[&](att::Handle handle, const auto& value, auto status_cb) {
EXPECT_EQ(kCCCHandle, handle);
EXPECT_TRUE(ContainersEqual(kCCCNotifyValue, value));
ccc_write_count++;
status_callback = std::move(status_cb);
});
int cb_count = 0;
att::Result<> status = fit::ok();
auto cb = [&](att::Result<> cb_status, IdType id) {
status = cb_status;
cb_count++;
};
service->EnableNotifications(
kDefaultCharacteristic, NopValueCallback, std::move(cb));
service->EnableNotifications(
kDefaultCharacteristic, NopValueCallback, std::move(cb));
service->EnableNotifications(
kDefaultCharacteristic, NopValueCallback, std::move(cb));
RunUntilIdle();
// Requests should be buffered and only one ATT request should have been sent
// out.
EXPECT_EQ(1, ccc_write_count);
EXPECT_EQ(0, cb_count);
status_callback(ToResult(HostError::kNotSupported));
EXPECT_EQ(3, cb_count);
EXPECT_EQ(ToResult(HostError::kNotSupported), status);
// A new request should write to the descriptor again.
service->EnableNotifications(
kDefaultCharacteristic, NopValueCallback, std::move(cb));
RunUntilIdle();
EXPECT_EQ(2, ccc_write_count);
EXPECT_EQ(3, cb_count);
status_callback(fit::ok());
EXPECT_EQ(2, ccc_write_count);
EXPECT_EQ(4, cb_count);
EXPECT_EQ(fit::ok(), status);
}
// Enabling notifications should succeed without a descriptor write.
TEST_F(RemoteServiceManagerTest, EnableNotificationsWithoutCCC) {
// Has the "notify" property but no CCC descriptor.
CharacteristicData chr(Property::kNotify, std::nullopt, 2, 3, kTestUuid3);
auto service = SetupServiceWithChrcs(
ServiceData(ServiceKind::PRIMARY, 1, 3, kTestServiceUuid1), {chr});
bool write_requested = false;
fake_client()->set_write_request_callback(
[&](auto, auto&, auto) { write_requested = true; });
int notify_count = 0;
auto notify_cb = [&](const auto& value, bool /*maybe_truncated*/) {
notify_count++;
};
att::Result<> status = fit::ok();
IdType id;
service->EnableNotifications(kDefaultCharacteristic,
std::move(notify_cb),
[&](att::Result<> _status, IdType _id) {
status = _status;
id = _id;
});
RunUntilIdle();
EXPECT_EQ(fit::ok(), status);
EXPECT_FALSE(write_requested);
fake_client()->SendNotification(/*indicate=*/false,
3,
StaticByteBuffer('y', 'e'),
/*maybe_truncated=*/false);
EXPECT_EQ(1, notify_count);
// Disabling notifications should not result in a write request.
service->DisableNotifications(
kDefaultCharacteristic, id, [&](auto _status) { status = _status; });
RunUntilIdle();
EXPECT_EQ(fit::ok(), status);
EXPECT_FALSE(write_requested);
// The handler should no longer receive notifications.
fake_client()->SendNotification(/*indicate=*/false,
3,
StaticByteBuffer('o', 'y', 'e'),
/*maybe_truncated=*/false);
EXPECT_EQ(1, notify_count);
}
// Notifications received when the remote service database is empty should be
// dropped and not cause a crash.
TEST_F(RemoteServiceManagerTest, NotificationWithoutServices) {
for (att::Handle i = 0; i < 10; ++i) {
fake_client()->SendNotification(
/*indicate=*/false,
i,
StaticByteBuffer('n', 'o', 't', 'i', 'f', 'y'),
/*maybe_truncated=*/false);
}
RunUntilIdle();
}
TEST_F(RemoteServiceManagerTest, NotificationCallback) {
auto service = SetUpFakeService(
ServiceData(ServiceKind::PRIMARY, 1, 7, kTestServiceUuid1));
// Set up two characteristics
CharacteristicData chr1(Property::kNotify, std::nullopt, 2, 3, kTestUuid3);
DescriptorData desc1(4, types::kClientCharacteristicConfig);
CharacteristicData chr2(Property::kIndicate, std::nullopt, 5, 6, kTestUuid3);
DescriptorData desc2(7, types::kClientCharacteristicConfig);
SetupCharacteristics(service, {{chr1, chr2}}, {{desc1, desc2}});
fake_client()->set_write_request_callback(
[&](att::Handle, const auto&, auto status_callback) {
status_callback(fit::ok());
});
IdType handler_id = kInvalidId;
att::Result<> status = ToResult(HostError::kFailed);
int chr1_count = 0;
auto chr1_cb = [&](const ByteBuffer& value, bool maybe_truncated) {
chr1_count++;
EXPECT_EQ("notify", value.AsString());
EXPECT_FALSE(maybe_truncated);
};
int chr2_count = 0;
auto chr2_cb = [&](const ByteBuffer& value, bool maybe_truncated) {
chr2_count++;
EXPECT_EQ("indicate", value.AsString());
EXPECT_TRUE(maybe_truncated);
};
// Notify both characteristics which should get dropped.
fake_client()->SendNotification(
/*indicate=*/false,
3,
StaticByteBuffer('n', 'o', 't', 'i', 'f', 'y'),
/*maybe_truncated=*/false);
fake_client()->SendNotification(
/*indicate=*/true,
6,
StaticByteBuffer('i', 'n', 'd', 'i', 'c', 'a', 't', 'e'),
/*maybe_truncated=*/true);
EnableNotifications(service,
kDefaultCharacteristic,
&status,
&handler_id,
std::move(chr1_cb));
ASSERT_EQ(fit::ok(), status);
EnableNotifications(
service, kSecondCharacteristic, &status, &handler_id, std::move(chr2_cb));
ASSERT_EQ(fit::ok(), status);
// Notify characteristic 1.
fake_client()->SendNotification(
/*indicate=*/false,
3,
StaticByteBuffer('n', 'o', 't', 'i', 'f', 'y'),
/*maybe_truncated=*/false);
EXPECT_EQ(1, chr1_count);
EXPECT_EQ(0, chr2_count);
// Notify characteristic 2.
fake_client()->SendNotification(
/*indicate=*/true,
6,
StaticByteBuffer('i', 'n', 'd', 'i', 'c', 'a', 't', 'e'),
/*maybe_truncated=*/true);
EXPECT_EQ(1, chr1_count);
EXPECT_EQ(1, chr2_count);
// Disable notifications from characteristic 1.
status = ToResult(HostError::kFailed);
service->DisableNotifications(
kDefaultCharacteristic, handler_id, [&](att::Result<> cb_status) {
status = cb_status;
});
RunUntilIdle();
EXPECT_EQ(fit::ok(), status);
// Notifications for characteristic 1 should get dropped.
fake_client()->SendNotification(
/*indicate=*/false,
3,
StaticByteBuffer('n', 'o', 't', 'i', 'f', 'y'),
/*maybe_truncated=*/false);
fake_client()->SendNotification(
/*indicate=*/true,
6,
StaticByteBuffer('i', 'n', 'd', 'i', 'c', 'a', 't', 'e'),
/*maybe_truncated=*/true);
EXPECT_EQ(1, chr1_count);
EXPECT_EQ(2, chr2_count);
}
TEST_F(RemoteServiceManagerTest, DisableNotificationsWhileNotReady) {
ServiceData data(ServiceKind::PRIMARY, 1, 4, kTestServiceUuid1);
auto service = SetUpFakeService(data);
att::Result<> status = fit::ok();
service->DisableNotifications(
kDefaultCharacteristic, 1, [&](att::Result<> cb_status) {
status = cb_status;
});
RunUntilIdle();
EXPECT_EQ(ToResult(HostError::kNotReady), status);
}
TEST_F(RemoteServiceManagerTest, DisableNotificationsCharNotFound) {
auto service = SetupNotifiableService();
IdType id = kInvalidId;
att::Result<> status = ToResult(HostError::kFailed);
EnableNotifications(service, kDefaultCharacteristic, &status, &id);
// "1" is an invalid characteristic ID.
service->DisableNotifications(
kInvalidCharacteristic, id, [&](att::Result<> cb_status) {
status = cb_status;
});
RunUntilIdle();
EXPECT_EQ(ToResult(HostError::kNotFound), status);
}
TEST_F(RemoteServiceManagerTest, DisableNotificationsIdNotFound) {
auto service = SetupNotifiableService();
IdType id = kInvalidId;
att::Result<> status = ToResult(HostError::kFailed);
EnableNotifications(service, kDefaultCharacteristic, &status, &id);
// Valid characteristic ID but invalid notification handler ID.
service->DisableNotifications(
kDefaultCharacteristic, id + 1, [&](att::Result<> cb_status) {
status = cb_status;
});
RunUntilIdle();
EXPECT_EQ(ToResult(HostError::kNotFound), status);
}
TEST_F(RemoteServiceManagerTest, DisableNotificationsSingleHandler) {
constexpr att::Handle kCCCHandle = 4;
auto service = SetupNotifiableService();
IdType id = kInvalidId;
att::Result<> status = ToResult(HostError::kFailed);
EnableNotifications(service, kDefaultCharacteristic, &status, &id);
// Should disable notifications
const StaticByteBuffer kExpectedValue(0x00, 0x00);
int ccc_write_count = 0;
fake_client()->set_write_request_callback(
[&](att::Handle handle, const auto& value, auto status_callback) {
EXPECT_EQ(kCCCHandle, handle);
EXPECT_TRUE(ContainersEqual(kExpectedValue, value));
ccc_write_count++;
status_callback(fit::ok());
});
status = ToResult(HostError::kFailed);
service->DisableNotifications(
kDefaultCharacteristic, id, [&](att::Result<> cb_status) {
status = cb_status;
});
RunUntilIdle();
EXPECT_EQ(fit::ok(), status);
EXPECT_EQ(1, ccc_write_count);
}
TEST_F(RemoteServiceManagerTest, DisableNotificationsOnDestruction) {
constexpr att::Handle kCCCHandle = 4;
auto service = SetupNotifiableService();
IdType id = kInvalidId;
att::Result<> status = ToResult(HostError::kFailed);
EnableNotifications(service, kDefaultCharacteristic, &status, &id);
ASSERT_EQ(fit::ok(), status);
// Should disable notifications
const StaticByteBuffer kExpectedValue(0x00, 0x00);
int ccc_write_count = 0;
fake_client()->set_write_request_callback(
[&](att::Handle handle, const auto& value, auto status_callback) {
EXPECT_EQ(kCCCHandle, handle);
EXPECT_TRUE(ContainersEqual(kExpectedValue, value));
ccc_write_count++;
status_callback(fit::ok());
});
// Destroying the service manager (which destroys the service characteristics)
// should clear the CCC.
DestroyServiceManager();
RunUntilIdle();
EXPECT_EQ(1, ccc_write_count);
}
TEST_F(RemoteServiceManagerTest, DisableNotificationsManyHandlers) {
auto service = SetupNotifiableService();
IdType id = kInvalidId;
std::vector<IdType> handler_ids;
for (int i = 0; i < 2; i++) {
att::Result<> status = ToResult(HostError::kFailed);
EnableNotifications(service, kDefaultCharacteristic, &status, &id);
ASSERT_EQ(fit::ok(), status);
handler_ids.push_back(id);
}
int ccc_write_count = 0;
fake_client()->set_write_request_callback(
[&](att::Handle handle, const auto& value, auto status_callback) {
ccc_write_count++;
status_callback(fit::ok());
});
// Disabling should succeed without an ATT transaction.
att::Result<> status = ToResult(HostError::kFailed);
service->DisableNotifications(
kDefaultCharacteristic, handler_ids.back(), [&](att::Result<> cb_status) {
status = cb_status;
});
handler_ids.pop_back();
RunUntilIdle();
EXPECT_EQ(fit::ok(), status);
EXPECT_EQ(0, ccc_write_count);
// Enabling should succeed without an ATT transaction.
status = ToResult(HostError::kFailed);
EnableNotifications(service, kDefaultCharacteristic, &status, &id);
EXPECT_EQ(fit::ok(), status);
EXPECT_EQ(0, ccc_write_count);
handler_ids.push_back(id);
// Disabling all should send out an ATT transaction.
while (!handler_ids.empty()) {
att::Result<> status = ToResult(HostError::kFailed);
service->DisableNotifications(
kDefaultCharacteristic,
handler_ids.back(),
[&](att::Result<> cb_status) { status = cb_status; });
handler_ids.pop_back();
RunUntilIdle();
EXPECT_EQ(fit::ok(), status);
}
EXPECT_EQ(1, ccc_write_count);
}
TEST_F(RemoteServiceManagerTest,
ReadByTypeErrorOnLastHandleDoesNotOverflowHandle) {
constexpr att::Handle kStartHandle = 0xFFFE;
constexpr att::Handle kEndHandle = 0xFFFF;
auto service = SetUpFakeService(ServiceData(
ServiceKind::PRIMARY, kStartHandle, kEndHandle, kTestServiceUuid1));
constexpr UUID kCharUuid(uint16_t{0xfefe});
size_t read_count = 0;
fake_client()->set_read_by_type_request_callback(
[&](const UUID& type, att::Handle start, att::Handle end, auto callback) {
ASSERT_EQ(0u, read_count++);
EXPECT_EQ(kStartHandle, start);
callback(fit::error(Client::ReadByTypeError{
att::Error(att::ErrorCode::kReadNotPermitted), kEndHandle}));
});
std::optional<att::Result<>> status;
std::vector<RemoteService::ReadByTypeResult> results;
service->ReadByType(kCharUuid, [&](att::Result<> cb_status, auto cb_results) {
status = cb_status;
results = std::move(cb_results);
});
RunUntilIdle();
ASSERT_TRUE(status.has_value());
EXPECT_EQ(fit::ok(), *status);
ASSERT_EQ(1u, results.size());
EXPECT_EQ(CharacteristicHandle(kEndHandle), results[0].handle);
EXPECT_EQ(att::ErrorCode::kReadNotPermitted, results[0].result.error_value());
}
TEST_F(RemoteServiceManagerTest,
ReadByTypeResultOnLastHandleDoesNotOverflowHandle) {
constexpr att::Handle kStartHandle = 0xFFFE;
constexpr att::Handle kEndHandle = 0xFFFF;
auto service = SetUpFakeService(ServiceData(
ServiceKind::PRIMARY, kStartHandle, kEndHandle, kTestServiceUuid1));
constexpr UUID kCharUuid(uint16_t{0xfefe});
constexpr att::Handle kHandle = kEndHandle;
const auto kValue = StaticByteBuffer(0x00, 0x01, 0x02);
const std::vector<Client::ReadByTypeValue> kValues = {
{kHandle, kValue.view(), /*maybe_truncated=*/false}};
size_t read_count = 0;
fake_client()->set_read_by_type_request_callback(
[&](const UUID& type, att::Handle start, att::Handle end, auto callback) {
ASSERT_EQ(0u, read_count++);
EXPECT_EQ(kStartHandle, start);
callback(fit::ok(kValues));
});
std::optional<att::Result<>> status;
service->ReadByType(kCharUuid, [&](att::Result<> cb_status, auto values) {
status = cb_status;
ASSERT_EQ(1u, values.size());
EXPECT_EQ(CharacteristicHandle(kHandle), values[0].handle);
ASSERT_EQ(fit::ok(), values[0].result);
EXPECT_TRUE(ContainersEqual(kValue, *values[0].result.value()));
});
RunUntilIdle();
ASSERT_TRUE(status.has_value());
EXPECT_EQ(fit::ok(), *status);
}
class RemoteServiceManagerServiceChangedTest : public RemoteServiceManagerTest {
public:
RemoteServiceManagerServiceChangedTest() = default;
~RemoteServiceManagerServiceChangedTest() override = default;
protected:
struct ServiceWatcherData {
std::vector<att::Handle> removed;
ServiceList added;
ServiceList modified;
};
void SetUp() override {
RemoteServiceManagerTest::SetUp();
fake_client()->set_services({gatt_service()});
fake_client()->set_characteristics({service_changed_characteristic()});
fake_client()->set_descriptors({ccc_descriptor()});
mgr()->set_service_watcher(
[this](auto removed, ServiceList added, ServiceList modified) {
svc_watcher_data_.push_back({removed, added, modified});
});
// Expect a Service Changed Client Characteristic Config descriptor write
// that enables indications.
fake_client()->set_write_request_callback(
[this](att::Handle handle, const auto& value, auto status_callback) {
write_request_count_++;
EXPECT_EQ(ccc_descriptor_handle_, handle);
EXPECT_TRUE(ContainersEqual(kCCCIndicateValue, value));
status_callback(fit::ok());
});
att::Result<> status = ToResult(HostError::kFailed);
mgr()->Initialize([&status](att::Result<> val) { status = val; },
NopMtuCallback);
RunUntilIdle();
EXPECT_EQ(fit::ok(), status);
EXPECT_EQ(write_request_count_, 1);
ASSERT_EQ(1u, svc_watcher_data_.size());
ASSERT_EQ(1u, svc_watcher_data_[0].added.size());
EXPECT_EQ(gatt_svc_start_handle_, svc_watcher_data_[0].added[0]->handle());
EXPECT_EQ(types::kGenericAttributeService,
svc_watcher_data_[0].added[0]->uuid());
// Clear data so that tests start with index 0
svc_watcher_data_.clear();
}
void TearDown() override { RemoteServiceManagerTest::TearDown(); }
ServiceData gatt_service() const {
return ServiceData(ServiceKind::PRIMARY,
gatt_svc_start_handle_,
gatt_svc_end_handle_,
types::kGenericAttributeService);
}
CharacteristicData service_changed_characteristic() const {
return CharacteristicData(Property::kIndicate,
std::nullopt,
svc_changed_char_handle_,
svc_changed_char_value_handle_,
types::kServiceChangedCharacteristic);
}
DescriptorData ccc_descriptor() const {
return DescriptorData(ccc_descriptor_handle_,
types::kClientCharacteristicConfig);
}
const std::vector<ServiceWatcherData>& svc_watcher_data() {
return svc_watcher_data_;
}
int service_changed_ccc_write_count() const { return write_request_count_; }
private:
std::vector<ServiceWatcherData> svc_watcher_data_;
int write_request_count_ = 0;
const att::Handle gatt_svc_start_handle_ = 1;
const att::Handle svc_changed_char_handle_ = 2;
const att::Handle svc_changed_char_value_handle_ = 3;
const att::Handle ccc_descriptor_handle_ = 4;
const att::Handle gatt_svc_end_handle_ = 4;
};
TEST_F(RemoteServiceManagerServiceChangedTest,
ServiceChangedNotificationWrongSizeBuffer) {
const att::Handle kSvc1StartHandle(5);
const att::Handle kSvc1EndHandle(kSvc1StartHandle);
ServiceData svc1(ServiceKind::PRIMARY,
kSvc1StartHandle,
kSvc1EndHandle,
kTestServiceUuid1);
fake_client()->set_services({gatt_service(), svc1});
// Send a too small notification.
auto svc_changed_range_buffer_too_small = StaticByteBuffer(0x01);
fake_client()->SendNotification(/*indicate=*/true,
service_changed_characteristic().value_handle,
svc_changed_range_buffer_too_small,
/*maybe_truncated=*/false);
RunUntilIdle();
// The notification should have been safely ignored.
ASSERT_EQ(0u, svc_watcher_data().size());
// Send a too large notification.
StaticByteBuffer<sizeof(ServiceChangedCharacteristicValue) + 1>
svc_changed_range_buffer_too_large =
StaticByteBuffer(0x01, 0x02, 0x03, 0x04, 0x05);
fake_client()->SendNotification(/*indicate=*/true,
service_changed_characteristic().value_handle,
svc_changed_range_buffer_too_large,
/*maybe_truncated=*/false);
RunUntilIdle();
// The notification should have been safely ignored.
ASSERT_EQ(0u, svc_watcher_data().size());
}
TEST_F(RemoteServiceManagerServiceChangedTest,
ServiceChangedNotificationRangeStartGreaterThanRangeEnd) {
const att::Handle kSvc1StartHandle(6);
const att::Handle kSvc1EndHandle(7);
ServiceData svc1(ServiceKind::PRIMARY,
kSvc1StartHandle,
kSvc1EndHandle,
kTestServiceUuid1);
fake_client()->set_services({gatt_service(), svc1});
// Send notification with start/end handles swapped.
auto svc_changed_range_buffer = StaticByteBuffer(
LowerBits(kSvc1EndHandle),
UpperBits(kSvc1EndHandle), // start handle of affected range
LowerBits(kSvc1StartHandle),
UpperBits(kSvc1StartHandle) // end handle of affected range
);
fake_client()->SendNotification(/*indicate=*/true,
service_changed_characteristic().value_handle,
svc_changed_range_buffer,
/*maybe_truncated=*/false);
RunUntilIdle();
// The notification should have been safely ignored.
ASSERT_EQ(0u, svc_watcher_data().size());
}
TEST_F(RemoteServiceManagerServiceChangedTest, AddModifyAndRemoveService) {
// Add a test service to ensure that service discovery occurs after the
// Service Changed characteristic is configured. The test service has a
// characteristic that supports indications in order to test that
// notifications aren't disabled when the service is modified or removed.
const att::Handle kSvc1StartHandle(5);
const att::Handle kSvc1ChrcHandle(6);
const att::Handle kSvc1ChrcValueHandle(7);
const att::Handle kSvc1CCCHandle(8);
const UUID kSvc1ChrcUuid(kTestUuid3);
const att::Handle kSvc1EndHandle(kSvc1CCCHandle);
ServiceData svc1(ServiceKind::PRIMARY,
kSvc1StartHandle,
kSvc1EndHandle,
kTestServiceUuid1);
CharacteristicData svc1_characteristic(Property::kIndicate,
std::nullopt,
kSvc1ChrcHandle,
kSvc1ChrcValueHandle,
kSvc1ChrcUuid);
DescriptorData svc1_descriptor(kSvc1CCCHandle,
types::kClientCharacteristicConfig);
fake_client()->set_services({gatt_service(), svc1});
fake_client()->set_characteristics(
{service_changed_characteristic(), svc1_characteristic});
fake_client()->set_descriptors({ccc_descriptor(), svc1_descriptor});
// Send a notification that svc1 has been added.
auto svc_changed_range_buffer = StaticByteBuffer(
LowerBits(kSvc1StartHandle),
UpperBits(kSvc1StartHandle), // start handle of affected range
LowerBits(kSvc1EndHandle),
UpperBits(kSvc1EndHandle) // end handle of affected range
);
fake_client()->SendNotification(/*indicate=*/true,
service_changed_characteristic().value_handle,
svc_changed_range_buffer,
/*maybe_truncated=*/false);
RunUntilIdle();
ASSERT_EQ(1u, svc_watcher_data().size());
ASSERT_EQ(1u, svc_watcher_data()[0].added.size());
EXPECT_EQ(0u, svc_watcher_data()[0].removed.size());
EXPECT_EQ(0u, svc_watcher_data()[0].modified.size());
EXPECT_EQ(kSvc1StartHandle, svc_watcher_data()[0].added[0]->handle());
bool original_service_removed = false;
svc_watcher_data()[0].added[0]->AddRemovedHandler(
[&]() { original_service_removed = true; });
// Discover the characteristic with the CCC descriptor before enabling
// characteristic value notifications.
svc_watcher_data()[0].added[0]->DiscoverCharacteristics(
[&](att::Result<> status, const CharacteristicMap& characteristics) {
EXPECT_EQ(fit::ok(), status);
EXPECT_EQ(characteristics.size(), 1u);
});
RunUntilIdle();
// Expect writes to the service's CCC descriptor when notifications are
// enabled.
int svc1_ccc_write_request_count = 0;
fake_client()->set_write_request_callback(
[&](att::Handle handle, const auto& value, auto status_callback) {
svc1_ccc_write_request_count++;
EXPECT_EQ(kSvc1CCCHandle, handle);
EXPECT_TRUE(ContainersEqual(kCCCIndicateValue, value));
status_callback(fit::ok());
});
std::optional<att::Result<>> original_notification_status;
svc_watcher_data()[0].added[0]->EnableNotifications(
bt::gatt::CharacteristicHandle(kSvc1ChrcValueHandle),
NopValueCallback,
[&](att::Result<> cb_status, IdType cb_id) {
original_notification_status = cb_status;
});
RunUntilIdle();
ASSERT_TRUE(original_notification_status);
EXPECT_EQ(fit::ok(), *original_notification_status);
EXPECT_EQ(svc1_ccc_write_request_count, 1);
// Send a notification that svc1 has been modified. Service Changed
// notifications guarantee that all services within their range have been
// modified if they are still present after a fresh service discovery, so we
// can just send the same range again. (Core Spec v5.3, Vol 3, Part G,
// Sec 7.1)
fake_client()->SendNotification(/*indicate=*/true,
service_changed_characteristic().value_handle,
svc_changed_range_buffer,
/*maybe_truncated=*/false);
RunUntilIdle();
EXPECT_TRUE(original_service_removed);
// CCC should not be written to when the service is modified.
EXPECT_EQ(svc1_ccc_write_request_count, 1);
ASSERT_EQ(2u, svc_watcher_data().size());
EXPECT_EQ(0u, svc_watcher_data()[1].added.size());
EXPECT_EQ(0u, svc_watcher_data()[1].removed.size());
ASSERT_EQ(1u, svc_watcher_data()[1].modified.size());
EXPECT_EQ(kSvc1StartHandle, svc_watcher_data()[1].modified[0]->handle());
bool modified_service_removed = false;
svc_watcher_data()[1].modified[0]->AddRemovedHandler(
[&]() { modified_service_removed = true; });
svc_watcher_data()[1].modified[0]->DiscoverCharacteristics(
[&](att::Result<> status, const CharacteristicMap& characteristics) {
EXPECT_EQ(fit::ok(), status);
EXPECT_EQ(characteristics.size(), 1u);
});
RunUntilIdle();
std::optional<att::Result<>> modified_notification_status;
svc_watcher_data()[1].modified[0]->EnableNotifications(
bt::gatt::CharacteristicHandle(kSvc1ChrcValueHandle),
NopValueCallback,
[&](att::Result<> cb_status, IdType cb_id) {
modified_notification_status = cb_status;
});
RunUntilIdle();
ASSERT_TRUE(modified_notification_status);
EXPECT_EQ(fit::ok(), *modified_notification_status);
EXPECT_EQ(svc1_ccc_write_request_count, 2);
// Remove svc1.
fake_client()->set_services({gatt_service()});
// Send a notification that svc1 has been removed.
fake_client()->SendNotification(/*indicate=*/true,
service_changed_characteristic().value_handle,
svc_changed_range_buffer,
/*maybe_truncated=*/false);
RunUntilIdle();
EXPECT_TRUE(modified_service_removed);
// CCC should not be written to when the service is removed.
EXPECT_EQ(svc1_ccc_write_request_count, 2);
ASSERT_EQ(3u, svc_watcher_data().size());
EXPECT_EQ(0u, svc_watcher_data()[2].added.size());
ASSERT_EQ(1u, svc_watcher_data()[2].removed.size());
EXPECT_EQ(0u, svc_watcher_data()[2].modified.size());
EXPECT_EQ(kSvc1StartHandle, svc_watcher_data()[2].removed[0]);
EXPECT_EQ(svc1_ccc_write_request_count, 2);
}
// A Service Changed notification received during initialization (service
// discovery) should be queued and processed after service discovery completes
// (as the last step of initialization). The service watcher should only be
// called once.
TEST_F(RemoteServiceManagerTest, ServiceChangedDuringInitialization) {
const att::Handle kGattSvcStartHandle(1);
const att::Handle kSvcChangedChrcHandle(2);
const att::Handle kSvcChangedChrcValueHandle(3);
const att::Handle kCCCDescriptorHandle(4);
const att::Handle kGattSvcEndHandle(kCCCDescriptorHandle);
const att::Handle kSvc1StartHandle(5);
const att::Handle kSvc1EndHandle(kSvc1StartHandle);
ServiceData gatt_svc(ServiceKind::PRIMARY,
kGattSvcStartHandle,
kGattSvcEndHandle,
types::kGenericAttributeService);
CharacteristicData service_changed_chrc(Property::kIndicate,
std::nullopt,
kSvcChangedChrcHandle,
kSvcChangedChrcValueHandle,
types::kServiceChangedCharacteristic);
DescriptorData ccc_descriptor(kCCCDescriptorHandle,
types::kClientCharacteristicConfig);
ServiceData svc1(ServiceKind::PRIMARY,
kSvc1StartHandle,
kSvc1EndHandle,
kTestServiceUuid1);
fake_client()->set_services({gatt_svc, svc1});
fake_client()->set_characteristics({service_changed_chrc});
fake_client()->set_descriptors({ccc_descriptor});
int svc_watcher_count = 0;
mgr()->set_service_watcher([&](std::vector<att::Handle> removed,
ServiceList added,
ServiceList modified) {
EXPECT_EQ(0u, removed.size());
EXPECT_EQ(2u, added.size());
EXPECT_EQ(0u, modified.size());
svc_watcher_count++;
});
// Send a notification during primary service discovery (i.e. the second
// discovery, as the first is for discovering the GATT Service).
int discover_services_count = 0;
fake_client()->set_discover_services_callback([&](ServiceKind /*kind*/) {
if (discover_services_count == 1) {
auto svc_changed_range_buffer = StaticByteBuffer(
LowerBits(kSvc1StartHandle),
UpperBits(kSvc1StartHandle), // start handle of affected range
LowerBits(kSvc1EndHandle),
UpperBits(kSvc1EndHandle) // end handle of affected range
);
fake_client()->SendNotification(/*indicate=*/true,
kSvcChangedChrcValueHandle,
svc_changed_range_buffer,
/*maybe_truncated=*/false);
}
discover_services_count++;
return fit::ok();
});
// Expect a Service Changed Client Characteristic Config descriptor write that
// enables indications.
int write_request_count = 0;
fake_client()->set_write_request_callback(
[&](att::Handle handle, const auto& value, auto status_callback) {
write_request_count++;
EXPECT_EQ(kCCCDescriptorHandle, handle);
EXPECT_TRUE(ContainersEqual(kCCCIndicateValue, value));
status_callback(fit::ok());
});
att::Result<> status = ToResult(HostError::kFailed);
mgr()->Initialize(
[&](att::Result<> val) {
status = val;
EXPECT_EQ(1, svc_watcher_count);
},
NopMtuCallback);
RunUntilIdle();
EXPECT_EQ(fit::ok(), status);
EXPECT_EQ(1, write_request_count);
EXPECT_EQ(1, svc_watcher_count);
}
TEST_F(RemoteServiceManagerServiceChangedTest,
SecondServiceChangedNotificationIsQueued) {
const att::Handle kSvc1StartHandle(5);
const att::Handle kSvc1EndHandle(kSvc1StartHandle);
// Add a test service to ensure that service discovery occurs after the
// Service Changed characteristic is configured.
ServiceData svc1(ServiceKind::PRIMARY,
kSvc1StartHandle,
kSvc1EndHandle,
kTestServiceUuid1);
fake_client()->set_services({gatt_service(), svc1});
// Send a notification that svc1 has been added.
auto svc_changed_range_buffer = StaticByteBuffer(
LowerBits(kSvc1StartHandle),
UpperBits(kSvc1StartHandle), // start handle of affected range
LowerBits(kSvc1EndHandle),
UpperBits(kSvc1EndHandle) // end handle of affected range
);
fake_client()->SendNotification(/*indicate=*/true,
service_changed_characteristic().value_handle,
svc_changed_range_buffer,
/*maybe_truncated=*/false);
// Send a notification that svc1 has been modified.
fake_client()->SendNotification(/*indicate=*/true,
service_changed_characteristic().value_handle,
svc_changed_range_buffer,
/*maybe_truncated=*/false);
RunUntilIdle();
ASSERT_EQ(2u, svc_watcher_data().size());
ASSERT_EQ(1u, svc_watcher_data()[0].added.size());
EXPECT_EQ(0u, svc_watcher_data()[0].removed.size());
EXPECT_EQ(0u, svc_watcher_data()[0].modified.size());
EXPECT_FALSE(svc_watcher_data()[0]
.added[0]
.is_alive()); // WeakPtr should already be invalidated
EXPECT_EQ(0u, svc_watcher_data()[1].added.size());
EXPECT_EQ(0u, svc_watcher_data()[1].removed.size());
ASSERT_EQ(1u, svc_watcher_data()[1].modified.size());
EXPECT_EQ(kSvc1StartHandle, svc_watcher_data()[1].modified[0]->handle());
}
TEST_F(RemoteServiceManagerServiceChangedTest, ServiceUuidChanged) {
const att::Handle kSvcStartHandle(5);
const att::Handle kSvcEndHandle(kSvcStartHandle);
ServiceData svc1(
ServiceKind::PRIMARY, kSvcStartHandle, kSvcEndHandle, kTestServiceUuid1);
fake_client()->set_services({gatt_service(), svc1});
// Send a notification that svc1 has been added.
auto svc_changed_range_buffer = StaticByteBuffer(
LowerBits(kSvcStartHandle),
UpperBits(kSvcStartHandle), // start handle of affected range
LowerBits(kSvcEndHandle),
UpperBits(kSvcEndHandle) // end handle of affected range
);
fake_client()->SendNotification(/*indicate=*/true,
service_changed_characteristic().value_handle,
svc_changed_range_buffer,
/*maybe_truncated=*/false);
RunUntilIdle();
ASSERT_EQ(1u, svc_watcher_data().size());
ASSERT_EQ(1u, svc_watcher_data()[0].added.size());
EXPECT_EQ(0u, svc_watcher_data()[0].removed.size());
EXPECT_EQ(0u, svc_watcher_data()[0].modified.size());
EXPECT_EQ(kSvcStartHandle, svc_watcher_data()[0].added[0]->handle());
EXPECT_EQ(kTestServiceUuid1, svc_watcher_data()[0].added[0]->uuid());
ServiceData svc2(
ServiceKind::PRIMARY, kSvcStartHandle, kSvcEndHandle, kTestServiceUuid2);
fake_client()->set_services({gatt_service(), svc2});
// Send a notification that svc2 has replaced svc1.
fake_client()->SendNotification(/*indicate=*/true,
service_changed_characteristic().value_handle,
svc_changed_range_buffer,
/*maybe_truncated=*/false);
RunUntilIdle();
ASSERT_EQ(2u, svc_watcher_data().size());
ASSERT_EQ(1u, svc_watcher_data()[1].added.size());
ASSERT_EQ(1u, svc_watcher_data()[1].removed.size());
EXPECT_EQ(0u, svc_watcher_data()[1].modified.size());
EXPECT_EQ(kSvcStartHandle, svc_watcher_data()[1].removed[0]);
EXPECT_EQ(kSvcStartHandle, svc_watcher_data()[1].added[0]->handle());
EXPECT_EQ(kTestServiceUuid2, svc_watcher_data()[1].added[0]->uuid());
}
TEST_F(
RemoteServiceManagerServiceChangedTest,
AddServiceThenRemoveServiceAndAddTwoMoreServicesBeforeAndAfterRemovedServiceWithSameNotification) {
const att::Handle kSvc1StartHandle(7);
const att::Handle kSvc1EndHandle(kSvc1StartHandle);
const att::Handle kSvc2StartHandle(6);
const att::Handle kSvc2EndHandle(kSvc2StartHandle);
const att::Handle kSvc3StartHandle(8);
const att::Handle kSvc3EndHandle(kSvc3StartHandle);
ServiceData svc1(ServiceKind::PRIMARY,
kSvc1StartHandle,
kSvc1EndHandle,
kTestServiceUuid1);
fake_client()->set_services({gatt_service(), svc1});
// Send a notification that svc1 has been added.
auto svc_changed_range_buffer_0 = StaticByteBuffer(
LowerBits(kSvc1StartHandle),
UpperBits(kSvc1StartHandle), // start handle of affected range
LowerBits(kSvc1EndHandle),
UpperBits(kSvc1EndHandle) // end handle of affected range
);
fake_client()->SendNotification(/*indicate=*/true,
service_changed_characteristic().value_handle,
svc_changed_range_buffer_0,
/*maybe_truncated=*/false);
RunUntilIdle();
ASSERT_EQ(1u, svc_watcher_data().size());
ASSERT_EQ(1u, svc_watcher_data()[0].added.size());
EXPECT_EQ(0u, svc_watcher_data()[0].removed.size());
EXPECT_EQ(0u, svc_watcher_data()[0].modified.size());
EXPECT_EQ(kSvc1StartHandle, svc_watcher_data()[0].added[0]->handle());
ServiceData svc2(ServiceKind::PRIMARY,
kSvc2StartHandle,
kSvc2EndHandle,
kTestServiceUuid2);
ServiceData svc3(ServiceKind::PRIMARY,
kSvc3StartHandle,
kSvc3EndHandle,
kTestServiceUuid3);
fake_client()->set_services({gatt_service(), svc2, svc3});
// Range includes all 3 services.
auto svc_changed_range_buffer_1 = StaticByteBuffer(
LowerBits(kSvc2StartHandle),
UpperBits(kSvc2StartHandle), // start handle of affected range
LowerBits(kSvc3EndHandle),
UpperBits(kSvc3EndHandle) // end handle of affected range
);
fake_client()->SendNotification(/*indicate=*/true,
service_changed_characteristic().value_handle,
svc_changed_range_buffer_1,
/*maybe_truncated=*/false);
RunUntilIdle();
ASSERT_EQ(2u, svc_watcher_data().size());
ASSERT_EQ(2u, svc_watcher_data()[1].added.size());
ASSERT_EQ(1u, svc_watcher_data()[1].removed.size());
EXPECT_EQ(0u, svc_watcher_data()[1].modified.size());
EXPECT_EQ(kSvc1StartHandle, svc_watcher_data()[1].removed[0]);
EXPECT_EQ(kSvc2StartHandle, svc_watcher_data()[1].added[0]->handle());
EXPECT_EQ(kSvc3StartHandle, svc_watcher_data()[1].added[1]->handle());
}
class RemoteServiceManagerServiceChangedTestWithServiceKindParam
: public RemoteServiceManagerServiceChangedTest,
public ::testing::WithParamInterface<ServiceKind> {};
TEST_P(RemoteServiceManagerServiceChangedTestWithServiceKindParam,
ServiceChangedServiceDiscoveryFailureThenSuccess) {
const ServiceKind kKind = GetParam();
const att::Handle kSvc1StartHandle(5);
const att::Handle kSvc1EndHandle(kSvc1StartHandle);
ServiceData svc1(kKind, kSvc1StartHandle, kSvc1EndHandle, kTestServiceUuid1);
const att::Handle kSvc2StartHandle(7);
const att::Handle kSvc2EndHandle(kSvc2StartHandle);
ServiceData svc2(kKind, kSvc2StartHandle, kSvc2EndHandle, kTestServiceUuid2);
fake_client()->set_services({gatt_service(), svc1, svc2});
// Cause only the first service discovery to fail.
int discover_services_count = 0;
fake_client()->set_discover_services_callback(
[&](ServiceKind kind) -> att::Result<> {
if (kind == kKind) {
discover_services_count++;
if (discover_services_count == 1) {
return ToResult(HostError::kFailed);
}
}
return fit::ok();
});
auto svc1_changed_range_buffer = StaticByteBuffer(
LowerBits(kSvc1StartHandle),
UpperBits(kSvc1StartHandle), // start handle of affected range
LowerBits(kSvc1EndHandle),
UpperBits(kSvc1EndHandle) // end handle of affected range
);
fake_client()->SendNotification(/*indicate=*/true,
service_changed_characteristic().value_handle,
svc1_changed_range_buffer,
/*maybe_truncated=*/false);
// This second notification should be queued and processed after the first
// service discovery fails.
auto svc2_changed_range_buffer = StaticByteBuffer(
LowerBits(kSvc2StartHandle),
UpperBits(kSvc2StartHandle), // start handle of affected range
LowerBits(kSvc2EndHandle),
UpperBits(kSvc2EndHandle) // end handle of affected range
);
fake_client()->SendNotification(/*indicate=*/true,
service_changed_characteristic().value_handle,
svc2_changed_range_buffer,
/*maybe_truncated=*/false);
RunUntilIdle();
ASSERT_EQ(1u, svc_watcher_data().size());
ASSERT_EQ(1u, svc_watcher_data()[0].added.size());
EXPECT_EQ(0u, svc_watcher_data()[0].removed.size());
EXPECT_EQ(0u, svc_watcher_data()[0].modified.size());
EXPECT_EQ(kSvc2StartHandle, svc_watcher_data()[0].added[0]->handle());
}
INSTANTIATE_TEST_SUITE_P(
ServiceKind,
RemoteServiceManagerServiceChangedTestWithServiceKindParam,
::testing::Values(ServiceKind::PRIMARY, ServiceKind::SECONDARY));
TEST_F(RemoteServiceManagerServiceChangedTest,
SecondaryServiceDiscoveryIgnoresUnsupportedGroupTypeError) {
const att::Handle kSvc1StartHandle(5);
const att::Handle kSvc1EndHandle(kSvc1StartHandle);
ServiceData svc1(ServiceKind::PRIMARY,
kSvc1StartHandle,
kSvc1EndHandle,
kTestServiceUuid1);
fake_client()->set_services({gatt_service(), svc1});
fake_client()->set_discover_services_callback([](ServiceKind kind) {
if (kind == ServiceKind::SECONDARY) {
return ToResult(att::ErrorCode::kUnsupportedGroupType);
}
return att::Result<>(fit::ok());
});
// Send a notification that svc1 has been added.
auto svc_changed_range_buffer = StaticByteBuffer(
LowerBits(kSvc1StartHandle),
UpperBits(kSvc1StartHandle), // start handle of affected range
LowerBits(kSvc1EndHandle),
UpperBits(kSvc1EndHandle) // end handle of affected range
);
fake_client()->SendNotification(/*indicate=*/true,
service_changed_characteristic().value_handle,
svc_changed_range_buffer,
/*maybe_truncated=*/false);
RunUntilIdle();
ASSERT_EQ(1u, svc_watcher_data().size());
ASSERT_EQ(1u, svc_watcher_data()[0].added.size());
EXPECT_EQ(0u, svc_watcher_data()[0].removed.size());
EXPECT_EQ(0u, svc_watcher_data()[0].modified.size());
EXPECT_EQ(kSvc1StartHandle, svc_watcher_data()[0].added[0]->handle());
}
TEST_F(RemoteServiceManagerServiceChangedTest, GattProfileServiceChanged) {
EXPECT_EQ(1, service_changed_ccc_write_count());
StaticByteBuffer svc_changed_range_buffer(
LowerBits(gatt_service().range_start),
UpperBits(gatt_service().range_start), // start handle of affected range
LowerBits(gatt_service().range_end),
UpperBits(gatt_service().range_end) // end handle of affected range
);
fake_client()->SendNotification(/*indicate=*/true,
service_changed_characteristic().value_handle,
svc_changed_range_buffer,
/*maybe_truncated=*/false);
RunUntilIdle();
ASSERT_EQ(1u, svc_watcher_data().size());
EXPECT_EQ(0u, svc_watcher_data()[0].added.size());
EXPECT_EQ(0u, svc_watcher_data()[0].removed.size());
ASSERT_EQ(1u, svc_watcher_data()[0].modified.size());
EXPECT_EQ(gatt_service().range_start,
svc_watcher_data()[0].modified[0]->handle());
EXPECT_EQ(1, service_changed_ccc_write_count());
// The handler for notifications should remain configured.
fake_client()->SendNotification(/*indicate=*/true,
service_changed_characteristic().value_handle,
svc_changed_range_buffer,
/*maybe_truncated=*/false);
RunUntilIdle();
ASSERT_EQ(2u, svc_watcher_data().size());
EXPECT_EQ(0u, svc_watcher_data()[1].added.size());
EXPECT_EQ(0u, svc_watcher_data()[1].removed.size());
ASSERT_EQ(1u, svc_watcher_data()[1].modified.size());
EXPECT_EQ(gatt_service().range_start,
svc_watcher_data()[1].modified[0]->handle());
EXPECT_EQ(1, service_changed_ccc_write_count());
// A new service should not have been created for the modified GATT Profile
// service.
EXPECT_EQ(&svc_watcher_data()[0].modified[0].get(),
&svc_watcher_data()[1].modified[0].get());
}
TEST_F(RemoteServiceManagerTest, ErrorDiscoveringGattProfileService) {
ServiceData gatt_svc(
ServiceKind::PRIMARY, 1, 1, types::kGenericAttributeService);
ServiceData svc1(ServiceKind::PRIMARY, 2, 2, kTestServiceUuid1);
std::vector<ServiceData> fake_services{{gatt_svc, svc1}};
fake_client()->set_services(std::move(fake_services));
fake_client()->set_discover_services_callback([](ServiceKind kind) {
if (kind == ServiceKind::PRIMARY) {
return ToResult(att::ErrorCode::kRequestNotSupported);
}
return att::Result<>(fit::ok());
});
std::optional<att::Result<>> status;
mgr()->Initialize([&status](att::Result<> val) { status = val; },
NopMtuCallback);
RunUntilIdle();
EXPECT_EQ(ToResult(att::ErrorCode::kRequestNotSupported), *status);
}
TEST_F(RemoteServiceManagerTest,
MultipleGattProfileServicesFailsInitialization) {
ServiceData gatt_svc0(
ServiceKind::PRIMARY, 1, 1, types::kGenericAttributeService);
ServiceData gatt_svc1(
ServiceKind::PRIMARY, 2, 2, types::kGenericAttributeService);
std::vector<ServiceData> fake_services{{gatt_svc0, gatt_svc1}};
fake_client()->set_services(std::move(fake_services));
std::optional<att::Result<>> status;
mgr()->Initialize([&status](att::Result<> val) { status = val; },
NopMtuCallback);
RunUntilIdle();
EXPECT_EQ(ToResult(HostError::kFailed), *status);
}
TEST_F(RemoteServiceManagerTest, InitializeEmptyGattProfileService) {
ServiceData gatt_svc(
ServiceKind::PRIMARY, 1, 1, types::kGenericAttributeService);
ServiceData svc1(ServiceKind::PRIMARY, 2, 2, kTestServiceUuid1);
std::vector<ServiceData> fake_services{{gatt_svc, svc1}};
fake_client()->set_services(std::move(fake_services));
ServiceList services;
mgr()->set_service_watcher(
[&services](auto /*removed*/, ServiceList added, auto /*modified*/) {
services.insert(services.end(), added.begin(), added.end());
});
att::Result<> status = ToResult(HostError::kFailed);
mgr()->Initialize([&status](att::Result<> val) { status = val; },
NopMtuCallback);
RunUntilIdle();
EXPECT_EQ(fit::ok(), status);
ASSERT_EQ(2u, services.size());
EXPECT_EQ(gatt_svc.range_start, services[0]->handle());
EXPECT_EQ(gatt_svc.type, services[0]->uuid());
EXPECT_EQ(svc1.range_start, services[1]->handle());
EXPECT_EQ(svc1.type, services[1]->uuid());
}
TEST_F(RemoteServiceManagerTest,
EnableServiceChangedNotificationsReturnsError) {
const att::Handle kGattSvcStartHandle(1);
const att::Handle kSvcChangedChrcHandle(2);
const att::Handle kSvcChangedChrcValueHandle(3);
const att::Handle kCCCDescriptorHandle(4);
const att::Handle kGattSvcEndHandle(kCCCDescriptorHandle);
ServiceData gatt_svc(ServiceKind::PRIMARY,
kGattSvcStartHandle,
kGattSvcEndHandle,
types::kGenericAttributeService);
CharacteristicData service_changed_chrc(Property::kIndicate,
std::nullopt,
kSvcChangedChrcHandle,
kSvcChangedChrcValueHandle,
types::kServiceChangedCharacteristic);
DescriptorData ccc_descriptor(kCCCDescriptorHandle,
types::kClientCharacteristicConfig);
fake_client()->set_services({gatt_svc});
fake_client()->set_characteristics({service_changed_chrc});
fake_client()->set_descriptors({ccc_descriptor});
ServiceList services;
mgr()->set_service_watcher(
[&services](auto /*removed*/, ServiceList added, auto /*modified*/) {
services.insert(services.end(), added.begin(), added.end());
});
// Return an error when a Service Changed Client Characteristic Config
// descriptor write is performed.
int write_request_count = 0;
fake_client()->set_write_request_callback(
[&](att::Handle handle, const auto& value, auto status_callback) {
write_request_count++;
EXPECT_EQ(kCCCDescriptorHandle, handle);
status_callback(ToResult(att::ErrorCode::kWriteNotPermitted));
});
std::optional<att::Result<>> status;
mgr()->Initialize([&status](att::Result<> val) { status = val; },
NopMtuCallback);
RunUntilIdle();
EXPECT_EQ(ToResult(att::ErrorCode::kWriteNotPermitted), *status);
EXPECT_EQ(write_request_count, 1);
}
TEST_F(RemoteServiceManagerTest,
ErrorDiscoveringGattProfileServiceCharacteristics) {
ServiceData gatt_svc(
ServiceKind::PRIMARY, 1, 3, types::kGenericAttributeService);
ServiceData svc1(ServiceKind::PRIMARY, 4, 4, kTestServiceUuid1);
std::vector<ServiceData> fake_services{{gatt_svc, svc1}};
fake_client()->set_services(std::move(fake_services));
fake_client()->set_characteristic_discovery_status(
ToResult(att::ErrorCode::kRequestNotSupported));
std::optional<att::Result<>> status;
mgr()->Initialize([&status](att::Result<> val) { status = val; },
NopMtuCallback);
RunUntilIdle();
EXPECT_EQ(ToResult(att::ErrorCode::kRequestNotSupported), *status);
}
TEST_F(RemoteServiceManagerTest, DisableNotificationInHandlerCallback) {
const CharacteristicHandle kChrcValueHandle(3);
RemoteService::WeakPtr svc = SetupNotifiableService();
std::optional<IdType> handler_id;
RemoteCharacteristic::NotifyStatusCallback status_cb =
[&](att::Result<> status, IdType cb_handler_id) {
EXPECT_EQ(fit::ok(), status);
handler_id = cb_handler_id;
};
int value_cb_count = 0;
RemoteCharacteristic::ValueCallback value_cb = [&](auto&, auto) {
value_cb_count++;
ASSERT_TRUE(handler_id);
// Disabling notifications in handler should not crash.
svc->DisableNotifications(
kChrcValueHandle, handler_id.value(), [](auto) {});
};
svc->EnableNotifications(
kChrcValueHandle, std::move(value_cb), std::move(status_cb));
fake_client()->SendNotification(/*indicate=*/false,
kChrcValueHandle.value,
StaticByteBuffer('y', 'e'),
/*maybe_truncated=*/false);
RunUntilIdle();
EXPECT_EQ(value_cb_count, 1);
// Second notification should not notify disabled handler.
fake_client()->SendNotification(/*indicate=*/false,
kChrcValueHandle.value,
StaticByteBuffer('y', 'e'),
/*maybe_truncated=*/false);
RunUntilIdle();
EXPECT_EQ(value_cb_count, 1);
}
TEST_F(RemoteServiceManagerServiceChangedTest,
ServiceRemovedDuringReadLongCharacteristic) {
const att::Handle kSvc1StartHandle(5);
const att::Handle kSvc1ChrcHandle(6);
const att::Handle kSvc1ChrcValueHandle(7);
const att::Handle kSvc1EndHandle(kSvc1ChrcValueHandle);
ServiceData svc1(ServiceKind::PRIMARY,
kSvc1StartHandle,
kSvc1EndHandle,
kTestServiceUuid1);
const UUID kSvc1ChrcUuid(kTestUuid3);
CharacteristicData svc1_characteristic(Property::kRead,
std::nullopt,
kSvc1ChrcHandle,
kSvc1ChrcValueHandle,
kSvc1ChrcUuid);
fake_client()->set_services({gatt_service(), svc1});
fake_client()->set_characteristics(
{service_changed_characteristic(), svc1_characteristic});
fake_client()->set_descriptors({ccc_descriptor()});
// Send a notification that svc1 has been added.
auto svc_changed_range_buffer = StaticByteBuffer(
LowerBits(kSvc1StartHandle),
UpperBits(kSvc1StartHandle), // start handle of affected range
LowerBits(kSvc1EndHandle),
UpperBits(kSvc1EndHandle) // end handle of affected range
);
fake_client()->SendNotification(/*indicate=*/true,
service_changed_characteristic().value_handle,
svc_changed_range_buffer,
/*maybe_truncated=*/false);
RunUntilIdle();
ASSERT_EQ(1u, svc_watcher_data().size());
ASSERT_EQ(1u, svc_watcher_data()[0].added.size());
EXPECT_EQ(kSvc1StartHandle, svc_watcher_data()[0].added[0]->handle());
RemoteService::WeakPtr service = svc_watcher_data()[0].added[0];
service->DiscoverCharacteristics([](auto, const auto&) {});
RunUntilIdle();
int read_req_count = 0;
RemoteService::ReadValueCallback read_callback = nullptr;
fake_client()->set_read_request_callback(
[&](auto, RemoteService::ReadValueCallback callback) {
EXPECT_EQ(read_req_count, 0);
read_req_count++;
read_callback = std::move(callback);
});
fake_client()->set_read_blob_request_callback(
[](auto, auto, auto) { FAIL(); });
int read_long_cb_count = 0;
service->ReadLongCharacteristic(
CharacteristicHandle(kSvc1ChrcValueHandle),
/*offset=*/0,
att::kMaxAttributeValueLength,
[&](auto, auto&, auto) { read_long_cb_count++; });
// Remove svc1.
fake_client()->set_services({gatt_service()});
fake_client()->SendNotification(/*indicate=*/true,
service_changed_characteristic().value_handle,
svc_changed_range_buffer,
/*maybe_truncated=*/false);
RunUntilIdle();
EXPECT_FALSE(service.is_alive());
ASSERT_EQ(2u, svc_watcher_data().size());
ASSERT_EQ(1u, svc_watcher_data()[1].removed.size());
EXPECT_EQ(read_req_count, 1);
ASSERT_TRUE(read_callback);
// Now that the service has been destroyed, the read long procedure should be
// aborted when the first read request callback is called.
StaticByteBuffer<att::kLEMinMTU - 1> expected_value;
expected_value.Fill(0x02);
read_callback(fit::ok(), expected_value.view(), /*maybe_truncated=*/true);
RunUntilIdle();
EXPECT_EQ(read_long_cb_count, 0);
}
} // namespace
} // namespace bt::gatt::internal