| // 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 "remote_service_manager.h" |
| |
| #include <fbl/macros.h> |
| |
| #include <vector> |
| |
| #include "fake_client.h" |
| #include "lib/gtest/test_loop_fixture.h" |
| #include "src/connectivity/bluetooth/core/bt-host/common/test_helpers.h" |
| |
| namespace bt { |
| namespace gatt { |
| |
| // This must be in the correct namespace for it to be visible to EXPECT_EQ. |
| static bool operator==(const CharacteristicData& chrc1, |
| const CharacteristicData& chrc2) { |
| return chrc1.properties == chrc2.properties && chrc1.handle == chrc2.handle && |
| chrc1.value_handle == chrc2.value_handle && chrc1.type == chrc2.type; |
| } |
| |
| // This must be in the correct namespace for it to be visible to EXPECT_EQ. |
| static bool operator==(const DescriptorData& desc1, |
| const DescriptorData& desc2) { |
| return desc1.handle == desc2.handle && desc1.type == desc2.type; |
| } |
| |
| namespace internal { |
| namespace { |
| |
| constexpr UUID kTestServiceUuid1((uint16_t)0xbeef); |
| constexpr UUID kTestServiceUuid2((uint16_t)0xcafe); |
| constexpr UUID kTestUuid3((uint16_t)0xfefe); |
| constexpr UUID kTestUuid4((uint16_t)0xefef); |
| |
| const auto kCCCNotifyValue = CreateStaticByteBuffer(0x01, 0x00); |
| const auto kCCCIndicateValue = CreateStaticByteBuffer(0x02, 0x00); |
| |
| void NopStatusCallback(att::Status) {} |
| void NopValueCallback(const ByteBuffer&) {} |
| |
| class GATT_RemoteServiceManagerTest : public ::gtest::TestLoopFixture { |
| public: |
| GATT_RemoteServiceManagerTest() = default; |
| ~GATT_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), dispatcher()); |
| } |
| |
| void TearDown() override { |
| // Clear any previous expectations that are based on the ATT Write Request, |
| // so that write requests sent during RemoteService::ShutDown() are ignored. |
| fake_client()->set_write_request_callback({}); |
| mgr_ = nullptr; |
| } |
| |
| // Initializes a RemoteService based on |data|. |
| fbl::RefPtr<RemoteService> SetUpFakeService(const ServiceData& data) { |
| std::vector<ServiceData> fake_services{{data}}; |
| fake_client()->set_primary_services(std::move(fake_services)); |
| |
| mgr()->Initialize(NopStatusCallback); |
| |
| ServiceList services; |
| mgr()->ListServices(std::vector<UUID>(), |
| [&services](auto status, ServiceList cb_services) { |
| services = std::move(cb_services); |
| }); |
| |
| RunLoopUntilIdle(); |
| |
| ZX_DEBUG_ASSERT(services.size() == 1u); |
| return services[0]; |
| } |
| |
| // Discover the characteristics of |service| based on the given |fake_data|. |
| void SetupCharacteristics( |
| fbl::RefPtr<RemoteService> service, |
| std::vector<CharacteristicData> fake_chrs, |
| std::vector<DescriptorData> fake_descrs = std::vector<DescriptorData>()) { |
| ZX_DEBUG_ASSERT(service); |
| |
| fake_client()->set_characteristics(std::move(fake_chrs)); |
| fake_client()->set_descriptors(std::move(fake_descrs)); |
| fake_client()->set_characteristic_discovery_status(att::Status()); |
| |
| service->DiscoverCharacteristics([](auto, const auto&) {}); |
| RunLoopUntilIdle(); |
| } |
| |
| // Create a fake service with one notifiable characteristic. |
| fbl::RefPtr<RemoteService> SetupNotifiableService() { |
| ServiceData data(1, 4, kTestServiceUuid1); |
| auto service = SetUpFakeService(data); |
| |
| CharacteristicData chr(Property::kNotify, 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(att::Status()); |
| }); |
| |
| RunLoopUntilIdle(); |
| |
| return service; |
| } |
| |
| void EnableNotifications( |
| fbl::RefPtr<RemoteService> service, IdType chr_id, |
| att::Status* out_status, IdType* out_id, |
| RemoteService::ValueCallback callback = NopValueCallback) { |
| ZX_DEBUG_ASSERT(out_status); |
| ZX_DEBUG_ASSERT(out_id); |
| service->EnableNotifications(chr_id, std::move(callback), |
| [&](att::Status cb_status, IdType cb_id) { |
| *out_status = cb_status; |
| *out_id = cb_id; |
| }); |
| RunLoopUntilIdle(); |
| } |
| |
| 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_; |
| |
| DISALLOW_COPY_AND_ASSIGN_ALLOW_MOVE(GATT_RemoteServiceManagerTest); |
| }; |
| |
| TEST_F(GATT_RemoteServiceManagerTest, InitializeNoServices) { |
| std::vector<fbl::RefPtr<RemoteService>> services; |
| mgr()->set_service_watcher( |
| [&services](auto svc) { services.push_back(svc); }); |
| |
| att::Status status(HostError::kFailed); |
| mgr()->Initialize([&status](att::Status val) { status = val; }); |
| |
| RunLoopUntilIdle(); |
| |
| EXPECT_TRUE(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(GATT_RemoteServiceManagerTest, Initialize) { |
| ServiceData svc1(1, 1, kTestServiceUuid1); |
| ServiceData svc2(2, 2, kTestServiceUuid2); |
| std::vector<ServiceData> fake_services{{svc1, svc2}}; |
| fake_client()->set_primary_services(std::move(fake_services)); |
| |
| ServiceList services; |
| mgr()->set_service_watcher( |
| [&services](auto svc) { services.push_back(svc); }); |
| |
| att::Status status(HostError::kFailed); |
| mgr()->Initialize([&status](att::Status val) { status = val; }); |
| |
| RunLoopUntilIdle(); |
| |
| EXPECT_TRUE(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(GATT_RemoteServiceManagerTest, InitializeFailure) { |
| fake_client()->set_service_discovery_status( |
| att::Status(att::ErrorCode::kRequestNotSupported)); |
| |
| ServiceList watcher_services; |
| mgr()->set_service_watcher( |
| [&watcher_services](auto svc) { watcher_services.push_back(svc); }); |
| |
| ServiceList services; |
| mgr()->ListServices(std::vector<UUID>(), |
| [&services](auto status, ServiceList cb_services) { |
| services = std::move(cb_services); |
| }); |
| ASSERT_TRUE(services.empty()); |
| |
| att::Status status(HostError::kFailed); |
| mgr()->Initialize([&status](att::Status val) { status = val; }); |
| |
| RunLoopUntilIdle(); |
| |
| EXPECT_FALSE(status); |
| EXPECT_TRUE(status.is_protocol_error()); |
| EXPECT_EQ(att::ErrorCode::kRequestNotSupported, status.protocol_error()); |
| EXPECT_TRUE(services.empty()); |
| EXPECT_TRUE(watcher_services.empty()); |
| } |
| |
| TEST_F(GATT_RemoteServiceManagerTest, ListServicesBeforeInit) { |
| ServiceData svc(1, 1, kTestServiceUuid1); |
| std::vector<ServiceData> fake_services{{svc}}; |
| fake_client()->set_primary_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::Status status(HostError::kFailed); |
| mgr()->Initialize([&status](att::Status val) { status = val; }); |
| |
| RunLoopUntilIdle(); |
| |
| EXPECT_TRUE(status); |
| EXPECT_EQ(1u, services.size()); |
| EXPECT_EQ(svc.range_start, services[0]->handle()); |
| EXPECT_EQ(svc.type, services[0]->uuid()); |
| } |
| |
| TEST_F(GATT_RemoteServiceManagerTest, ListServicesAfterInit) { |
| ServiceData svc(1, 1, kTestServiceUuid1); |
| std::vector<ServiceData> fake_services{{svc}}; |
| fake_client()->set_primary_services(std::move(fake_services)); |
| |
| att::Status status(HostError::kFailed); |
| mgr()->Initialize([&status](att::Status val) { status = val; }); |
| |
| RunLoopUntilIdle(); |
| |
| ASSERT_TRUE(status); |
| |
| ServiceList services; |
| mgr()->ListServices(std::vector<UUID>(), |
| [&services](auto status, ServiceList cb_services) { |
| services = std::move(cb_services); |
| }); |
| EXPECT_EQ(1u, services.size()); |
| EXPECT_EQ(svc.range_start, services[0]->handle()); |
| EXPECT_EQ(svc.type, services[0]->uuid()); |
| } |
| |
| TEST_F(GATT_RemoteServiceManagerTest, ListServicesByUuid) { |
| std::vector<UUID> uuids{kTestServiceUuid1}; |
| |
| ServiceData svc1(1, 1, kTestServiceUuid1); |
| ServiceData svc2(2, 2, kTestServiceUuid2); |
| std::vector<ServiceData> fake_services{{svc1, svc2}}; |
| fake_client()->set_primary_services(std::move(fake_services)); |
| |
| att::Status list_services_status; |
| ServiceList services; |
| mgr()->set_service_watcher( |
| [&services](auto svc) { services.push_back(svc); }); |
| mgr()->ListServices(std::move(uuids), |
| [&](att::Status cb_status, ServiceList cb_services) { |
| list_services_status = cb_status; |
| services = std::move(cb_services); |
| }); |
| ASSERT_TRUE(services.empty()); |
| |
| att::Status status(HostError::kFailed); |
| mgr()->Initialize([&status](att::Status val) { status = val; }); |
| |
| RunLoopUntilIdle(); |
| |
| EXPECT_TRUE(status); |
| EXPECT_TRUE(list_services_status); |
| EXPECT_EQ(1u, services.size()); |
| EXPECT_EQ(svc1.range_start, services[0]->handle()); |
| EXPECT_EQ(svc1.type, services[0]->uuid()); |
| } |
| |
| TEST_F(GATT_RemoteServiceManagerTest, DiscoverCharacteristicsAfterShutDown) { |
| ServiceData data(1, 2, kTestServiceUuid1); |
| auto service = SetUpFakeService(data); |
| |
| service->ShutDown(); |
| |
| att::Status status; |
| size_t chrcs_size; |
| service->DiscoverCharacteristics( |
| [&](att::Status cb_status, const auto& chrcs) { |
| status = cb_status; |
| chrcs_size = chrcs.size(); |
| }); |
| |
| RunLoopUntilIdle(); |
| EXPECT_FALSE(status); |
| EXPECT_EQ(HostError::kFailed, status.error()); |
| EXPECT_EQ(0u, chrcs_size); |
| EXPECT_EQ(0u, fake_client()->chrc_discovery_count()); |
| EXPECT_FALSE(service->IsDiscovered()); |
| } |
| |
| TEST_F(GATT_RemoteServiceManagerTest, DiscoverCharacteristicsSuccess) { |
| ServiceData data(1, 5, kTestServiceUuid1); |
| auto service = SetUpFakeService(data); |
| |
| CharacteristicData fake_chrc1(0, 2, 3, kTestUuid3); |
| CharacteristicData fake_chrc2(0, 4, 5, kTestUuid4); |
| std::vector<CharacteristicData> fake_chrcs{{fake_chrc1, fake_chrc2}}; |
| fake_client()->set_characteristics(std::move(fake_chrcs)); |
| |
| att::Status status1(HostError::kFailed); |
| service->DiscoverCharacteristics( |
| [&](att::Status cb_status, const auto& chrcs) { |
| status1 = cb_status; |
| EXPECT_EQ(2u, chrcs.size()); |
| |
| EXPECT_EQ(0u, chrcs[0].id()); |
| EXPECT_EQ(1u, chrcs[1].id()); |
| |
| EXPECT_EQ(fake_chrc1, chrcs[0].info()); |
| EXPECT_EQ(fake_chrc2, chrcs[1].info()); |
| }); |
| |
| // Queue a second request. |
| att::Status status2(HostError::kFailed); |
| RemoteCharacteristicList chrcs2; |
| service->DiscoverCharacteristics( |
| [&](att::Status cb_status, const auto& chrcs) { |
| status2 = cb_status; |
| EXPECT_EQ(2u, chrcs.size()); |
| |
| EXPECT_EQ(0u, chrcs[0].id()); |
| EXPECT_EQ(1u, chrcs[1].id()); |
| |
| EXPECT_EQ(fake_chrc1, chrcs[0].info()); |
| EXPECT_EQ(fake_chrc2, chrcs[1].info()); |
| }); |
| |
| EXPECT_EQ(0u, fake_client()->chrc_discovery_count()); |
| RunLoopUntilIdle(); |
| |
| // Only one ATT request should have been made. |
| EXPECT_EQ(1u, fake_client()->chrc_discovery_count()); |
| |
| EXPECT_TRUE(service->IsDiscovered()); |
| EXPECT_TRUE(status1); |
| EXPECT_TRUE(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 = att::Status(HostError::kFailed); |
| service->DiscoverCharacteristics( |
| [&status1](att::Status cb_status, const auto&) { status1 = cb_status; }); |
| |
| RunLoopUntilIdle(); |
| |
| EXPECT_TRUE(status1); |
| EXPECT_EQ(1u, fake_client()->chrc_discovery_count()); |
| EXPECT_TRUE(service->IsDiscovered()); |
| } |
| |
| TEST_F(GATT_RemoteServiceManagerTest, DiscoverCharacteristicsError) { |
| ServiceData data(1, 5, kTestServiceUuid1); |
| auto service = SetUpFakeService(data); |
| |
| CharacteristicData chrc1(0, 2, 3, kTestUuid3); |
| CharacteristicData chrc2(0, 4, 5, kTestUuid4); |
| std::vector<CharacteristicData> fake_chrcs{{chrc1, chrc2}}; |
| fake_client()->set_characteristics(std::move(fake_chrcs)); |
| |
| fake_client()->set_characteristic_discovery_status( |
| att::Status(HostError::kNotSupported)); |
| |
| att::Status status1; |
| RemoteCharacteristicList chrcs1; |
| service->DiscoverCharacteristics( |
| [&](att::Status cb_status, const auto& chrcs) { |
| status1 = cb_status; |
| EXPECT_TRUE(chrcs.empty()); |
| }); |
| |
| // Queue a second request. |
| att::Status status2; |
| RemoteCharacteristicList chrcs2; |
| service->DiscoverCharacteristics( |
| [&](att::Status cb_status, const auto& chrcs) { |
| status2 = cb_status; |
| EXPECT_TRUE(chrcs.empty()); |
| }); |
| |
| EXPECT_EQ(0u, fake_client()->chrc_discovery_count()); |
| RunLoopUntilIdle(); |
| |
| // Onle one request should have been made. |
| EXPECT_EQ(1u, fake_client()->chrc_discovery_count()); |
| |
| EXPECT_FALSE(service->IsDiscovered()); |
| EXPECT_FALSE(status1); |
| EXPECT_FALSE(status2); |
| EXPECT_EQ(HostError::kNotSupported, status1.error()); |
| EXPECT_EQ(HostError::kNotSupported, status2.error()); |
| } |
| |
| // Discover descriptors of a service with one characteristic. |
| TEST_F(GATT_RemoteServiceManagerTest, DiscoverDescriptorsOfOneSuccess) { |
| 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; |
| |
| ServiceData data(kStart, kEnd, kTestServiceUuid1); |
| auto service = SetUpFakeService(data); |
| |
| CharacteristicData fake_chrc(0, 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::Status status(HostError::kFailed); |
| service->DiscoverCharacteristics( |
| [&](att::Status cb_status, const auto& chrcs) { |
| status = cb_status; |
| EXPECT_EQ(1u, chrcs.size()); |
| |
| EXPECT_EQ(2u, chrcs[0].descriptors().size()); |
| EXPECT_EQ(0u, chrcs[0].descriptors()[0].id()); |
| EXPECT_EQ(fake_desc1, chrcs[0].descriptors()[0].info()); |
| EXPECT_EQ(1u, chrcs[0].descriptors()[1].id()); |
| EXPECT_EQ(fake_desc2, chrcs[0].descriptors()[1].info()); |
| }); |
| |
| EXPECT_EQ(0u, fake_client()->chrc_discovery_count()); |
| RunLoopUntilIdle(); |
| |
| EXPECT_EQ(1u, fake_client()->chrc_discovery_count()); |
| EXPECT_EQ(1u, fake_client()->desc_discovery_count()); |
| EXPECT_TRUE(service->IsDiscovered()); |
| EXPECT_TRUE(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(GATT_RemoteServiceManagerTest, DiscoverDescriptorsOfOneError) { |
| 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; |
| |
| ServiceData data(kStart, kEnd, kTestServiceUuid1); |
| auto service = SetUpFakeService(data); |
| |
| CharacteristicData fake_chrc(0, 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( |
| att::Status(HostError::kNotSupported)); |
| |
| att::Status status; |
| service->DiscoverCharacteristics( |
| [&](att::Status cb_status, const auto& chrcs) { |
| status = cb_status; |
| EXPECT_TRUE(chrcs.empty()); |
| }); |
| |
| EXPECT_EQ(0u, fake_client()->chrc_discovery_count()); |
| RunLoopUntilIdle(); |
| |
| EXPECT_EQ(1u, fake_client()->chrc_discovery_count()); |
| EXPECT_EQ(1u, fake_client()->desc_discovery_count()); |
| EXPECT_FALSE(service->IsDiscovered()); |
| EXPECT_FALSE(status); |
| EXPECT_EQ(HostError::kNotSupported, status.error()); |
| } |
| |
| // Discover descriptors of a service with multiple characteristics |
| TEST_F(GATT_RemoteServiceManagerTest, DiscoverDescriptorsOfMultipleSuccess) { |
| // Has one descriptor |
| CharacteristicData fake_char1(0, 2, 3, kTestUuid3); |
| DescriptorData fake_desc1(4, kTestUuid4); |
| |
| // Has no descriptors |
| CharacteristicData fake_char2(0, 5, 6, kTestUuid3); |
| |
| // Has two descriptors |
| CharacteristicData fake_char3(0, 7, 8, kTestUuid3); |
| DescriptorData fake_desc2(9, kTestUuid4); |
| DescriptorData fake_desc3(10, kTestUuid4); |
| |
| ServiceData data(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::Status status(HostError::kFailed); |
| service->DiscoverCharacteristics( |
| [&](att::Status cb_status, const auto& chrcs) { |
| status = cb_status; |
| EXPECT_EQ(3u, chrcs.size()); |
| |
| // Characteristic #1 |
| EXPECT_EQ(1u, chrcs[0].descriptors().size()); |
| EXPECT_EQ(0u, chrcs[0].descriptors()[0].id()); |
| EXPECT_EQ(fake_desc1, chrcs[0].descriptors()[0].info()); |
| |
| // Characteristic #2 |
| EXPECT_TRUE(chrcs[1].descriptors().empty()); |
| |
| // Characteristic #3 |
| EXPECT_EQ(2u, chrcs[2].descriptors().size()); |
| EXPECT_EQ(2u, chrcs[2].id()); |
| EXPECT_EQ(0x020000u, chrcs[2].descriptors()[0].id()); |
| EXPECT_EQ(fake_desc2, chrcs[2].descriptors()[0].info()); |
| EXPECT_EQ(0x020001u, chrcs[2].descriptors()[1].id()); |
| EXPECT_EQ(fake_desc3, chrcs[2].descriptors()[1].info()); |
| }); |
| |
| EXPECT_EQ(0u, fake_client()->chrc_discovery_count()); |
| RunLoopUntilIdle(); |
| |
| 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_TRUE(status); |
| } |
| |
| // Discover descriptors of a service with multiple characteristics. The first |
| // request results in an error though others succeed. |
| TEST_F(GATT_RemoteServiceManagerTest, DiscoverDescriptorsOfMultipleEarlyFail) { |
| // Has one descriptor |
| CharacteristicData fake_char1(0, 2, 3, kTestUuid3); |
| DescriptorData fake_desc1(4, kTestUuid4); |
| |
| // Has no descriptors |
| CharacteristicData fake_char2(0, 5, 6, kTestUuid3); |
| |
| // Has two descriptors |
| CharacteristicData fake_char3(0, 7, 8, kTestUuid3); |
| DescriptorData fake_desc2(9, kTestUuid4); |
| DescriptorData fake_desc3(10, kTestUuid4); |
| |
| ServiceData data(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( |
| att::Status(HostError::kNotSupported), 1); |
| |
| att::Status status(HostError::kFailed); |
| service->DiscoverCharacteristics( |
| [&](att::Status cb_status, const auto& chrcs) { |
| status = cb_status; |
| EXPECT_TRUE(chrcs.empty()); |
| }); |
| |
| EXPECT_EQ(0u, fake_client()->chrc_discovery_count()); |
| RunLoopUntilIdle(); |
| |
| 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(HostError::kNotSupported, status.error()); |
| } |
| |
| // Discover descriptors of a service with multiple characteristics. The last |
| // request results in an error while the preceding ones succeed. |
| TEST_F(GATT_RemoteServiceManagerTest, DiscoverDescriptorsOfMultipleLateFail) { |
| // Has one descriptor |
| CharacteristicData fake_char1(0, 2, 3, kTestUuid3); |
| DescriptorData fake_desc1(4, kTestUuid4); |
| |
| // Has no descriptors |
| CharacteristicData fake_char2(0, 5, 6, kTestUuid3); |
| |
| // Has two descriptors |
| CharacteristicData fake_char3(0, 7, 8, kTestUuid3); |
| DescriptorData fake_desc2(9, kTestUuid4); |
| DescriptorData fake_desc3(10, kTestUuid4); |
| |
| ServiceData data(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( |
| att::Status(HostError::kNotSupported), 2); |
| |
| att::Status status(HostError::kFailed); |
| service->DiscoverCharacteristics( |
| [&](att::Status cb_status, const auto& chrcs) { |
| status = cb_status; |
| EXPECT_TRUE(chrcs.empty()); |
| }); |
| |
| EXPECT_EQ(0u, fake_client()->chrc_discovery_count()); |
| RunLoopUntilIdle(); |
| |
| 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(HostError::kNotSupported, status.error()); |
| } |
| |
| TEST_F(GATT_RemoteServiceManagerTest, ReadCharAfterShutDown) { |
| ServiceData data(1, 2, kTestServiceUuid1); |
| auto service = SetUpFakeService(data); |
| |
| service->ShutDown(); |
| |
| att::Status status; |
| service->ReadCharacteristic( |
| 0, [&](att::Status cb_status, const auto&) { status = cb_status; }); |
| |
| RunLoopUntilIdle(); |
| |
| EXPECT_EQ(HostError::kFailed, status.error()); |
| } |
| |
| TEST_F(GATT_RemoteServiceManagerTest, ReadCharWhileNotReady) { |
| ServiceData data(1, 2, kTestServiceUuid1); |
| auto service = SetUpFakeService(data); |
| |
| att::Status status; |
| service->ReadCharacteristic( |
| 0, [&](att::Status cb_status, const auto&) { status = cb_status; }); |
| |
| RunLoopUntilIdle(); |
| |
| EXPECT_EQ(HostError::kNotReady, status.error()); |
| } |
| |
| TEST_F(GATT_RemoteServiceManagerTest, ReadCharNotFound) { |
| ServiceData data(1, 2, kTestServiceUuid1); |
| auto service = SetUpFakeService(data); |
| SetupCharacteristics(service, std::vector<CharacteristicData>()); |
| |
| att::Status status; |
| service->ReadCharacteristic( |
| 0, [&](att::Status cb_status, const auto&) { status = cb_status; }); |
| |
| RunLoopUntilIdle(); |
| |
| EXPECT_EQ(HostError::kNotFound, status.error()); |
| } |
| |
| TEST_F(GATT_RemoteServiceManagerTest, ReadCharNotSupported) { |
| ServiceData data(1, 3, kTestServiceUuid1); |
| auto service = SetUpFakeService(data); |
| |
| // No "read" property set. |
| CharacteristicData chr(0, 2, 3, kTestUuid3); |
| SetupCharacteristics(service, {{chr}}); |
| |
| att::Status status; |
| service->ReadCharacteristic( |
| 0, [&](att::Status cb_status, const auto&) { status = cb_status; }); |
| |
| RunLoopUntilIdle(); |
| |
| EXPECT_EQ(HostError::kNotSupported, status.error()); |
| } |
| |
| TEST_F(GATT_RemoteServiceManagerTest, ReadCharSendsReadRequest) { |
| constexpr att::Handle kValueHandle = 3; |
| |
| ServiceData data(1, kValueHandle, kTestServiceUuid1); |
| auto service = SetUpFakeService(data); |
| |
| CharacteristicData chr(Property::kRead, 2, kValueHandle, kTestUuid3); |
| SetupCharacteristics(service, {{chr}}); |
| |
| const auto kValue = CreateStaticByteBuffer('t', 'e', 's', 't'); |
| |
| fake_client()->set_read_request_callback( |
| [&](att::Handle handle, auto callback) { |
| EXPECT_EQ(kValueHandle, handle); |
| callback(att::Status(), kValue); |
| }); |
| |
| att::Status status(HostError::kFailed); |
| service->ReadCharacteristic(0, [&](att::Status cb_status, const auto& value) { |
| status = cb_status; |
| EXPECT_TRUE(ContainersEqual(kValue, value)); |
| }); |
| |
| RunLoopUntilIdle(); |
| |
| EXPECT_TRUE(status); |
| } |
| |
| TEST_F(GATT_RemoteServiceManagerTest, ReadCharSendsReadRequestWithDispatcher) { |
| constexpr att::Handle kValueHandle = 3; |
| |
| ServiceData data(1, kValueHandle, kTestServiceUuid1); |
| auto service = SetUpFakeService(data); |
| |
| CharacteristicData chr(Property::kRead, 2, kValueHandle, kTestUuid3); |
| SetupCharacteristics(service, {{chr}}); |
| |
| const auto kValue = CreateStaticByteBuffer('t', 'e', 's', 't'); |
| |
| fake_client()->set_read_request_callback( |
| [&](att::Handle handle, auto callback) { |
| EXPECT_EQ(kValueHandle, handle); |
| callback(att::Status(), kValue); |
| }); |
| |
| att::Status status(HostError::kFailed); |
| service->ReadCharacteristic( |
| 0, |
| [&](att::Status cb_status, const auto& value) { |
| status = cb_status; |
| EXPECT_TRUE(ContainersEqual(kValue, value)); |
| }, |
| dispatcher()); |
| |
| RunLoopUntilIdle(); |
| |
| EXPECT_TRUE(status); |
| } |
| |
| TEST_F(GATT_RemoteServiceManagerTest, ReadLongAfterShutDown) { |
| ServiceData data(1, 2, kTestServiceUuid1); |
| auto service = SetUpFakeService(data); |
| |
| service->ShutDown(); |
| |
| att::Status status; |
| service->ReadLongCharacteristic( |
| 0, 0, 512, |
| [&](att::Status cb_status, const auto&) { status = cb_status; }); |
| |
| RunLoopUntilIdle(); |
| |
| EXPECT_EQ(HostError::kFailed, status.error()); |
| } |
| |
| TEST_F(GATT_RemoteServiceManagerTest, ReadLongWhileNotReady) { |
| ServiceData data(1, 2, kTestServiceUuid1); |
| auto service = SetUpFakeService(data); |
| |
| att::Status status; |
| service->ReadLongCharacteristic( |
| 0, 0, 512, |
| [&](att::Status cb_status, const auto&) { status = cb_status; }); |
| |
| RunLoopUntilIdle(); |
| |
| EXPECT_EQ(HostError::kNotReady, status.error()); |
| } |
| |
| TEST_F(GATT_RemoteServiceManagerTest, ReadLongNotFound) { |
| ServiceData data(1, 2, kTestServiceUuid1); |
| auto service = SetUpFakeService(data); |
| SetupCharacteristics(service, std::vector<CharacteristicData>()); |
| |
| att::Status status; |
| service->ReadLongCharacteristic( |
| 0, 0, 512, |
| [&](att::Status cb_status, const auto&) { status = cb_status; }); |
| |
| RunLoopUntilIdle(); |
| |
| EXPECT_EQ(HostError::kNotFound, status.error()); |
| } |
| |
| TEST_F(GATT_RemoteServiceManagerTest, ReadLongNotSupported) { |
| ServiceData data(1, 3, kTestServiceUuid1); |
| auto service = SetUpFakeService(data); |
| |
| // No "read" property set. |
| CharacteristicData chr(0, 2, 3, kTestUuid3); |
| SetupCharacteristics(service, {{chr}}); |
| |
| att::Status status; |
| service->ReadLongCharacteristic( |
| 0, 0, 512, |
| [&](att::Status cb_status, const auto&) { status = cb_status; }); |
| |
| RunLoopUntilIdle(); |
| |
| EXPECT_EQ(HostError::kNotSupported, status.error()); |
| } |
| |
| // 0 is not a valid parameter for the |max_size| field of ReadLongCharacteristic |
| TEST_F(GATT_RemoteServiceManagerTest, ReadLongMaxSizeZero) { |
| ServiceData data(1, 3, kTestServiceUuid1); |
| auto service = SetUpFakeService(data); |
| |
| CharacteristicData chr(Property::kRead, 2, 3, kTestUuid3); |
| SetupCharacteristics(service, {{chr}}); |
| |
| att::Status status; |
| service->ReadLongCharacteristic( |
| 0, 0, 0, [&](att::Status cb_status, const auto&) { status = cb_status; }); |
| |
| RunLoopUntilIdle(); |
| |
| EXPECT_EQ(HostError::kInvalidParameters, status.error()); |
| } |
| |
| // The complete attribute value is read in a single request. |
| TEST_F(GATT_RemoteServiceManagerTest, ReadLongSingleBlob) { |
| constexpr att::Handle kValueHandle = 3; |
| constexpr uint16_t kOffset = 0; |
| constexpr size_t kMaxBytes = 1000; |
| |
| ServiceData data(1, kValueHandle, kTestServiceUuid1); |
| auto service = SetUpFakeService(data); |
| |
| CharacteristicData chr(Property::kRead, 2, kValueHandle, kTestUuid3); |
| SetupCharacteristics(service, {{chr}}); |
| |
| const auto kValue = CreateStaticByteBuffer('t', 'e', 's', 't'); |
| |
| fake_client()->set_read_blob_request_callback( |
| [&](att::Handle handle, uint16_t offset, auto callback) { |
| EXPECT_EQ(kValueHandle, handle); |
| EXPECT_EQ(kOffset, offset); |
| callback(att::Status(), kValue); |
| }); |
| |
| att::Status status(HostError::kFailed); |
| service->ReadLongCharacteristic( |
| 0, kOffset, kMaxBytes, [&](att::Status cb_status, const auto& value) { |
| status = cb_status; |
| EXPECT_TRUE(ContainersEqual(kValue, value)); |
| }); |
| |
| RunLoopUntilIdle(); |
| EXPECT_TRUE(status); |
| } |
| |
| TEST_F(GATT_RemoteServiceManagerTest, ReadLongMultipleBlobs) { |
| constexpr att::Handle kValueHandle = 3; |
| constexpr uint16_t kOffset = 0; |
| constexpr size_t kMaxBytes = 1000; |
| constexpr int kExpectedBlobCount = 4; |
| |
| ServiceData data(1, kValueHandle, kTestServiceUuid1); |
| auto service = SetUpFakeService(data); |
| |
| CharacteristicData chr(Property::kRead, 2, kValueHandle, kTestUuid3); |
| SetupCharacteristics(service, {{chr}}); |
| |
| // 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_blob_count = 0; |
| fake_client()->set_read_blob_request_callback( |
| [&](att::Handle handle, uint16_t offset, auto callback) { |
| EXPECT_EQ(kValueHandle, handle); |
| read_blob_count++; |
| |
| // Return a blob at the given offset with at most MTU - 1 bytes. |
| auto blob = expected_value.view(offset, att::kLEMinMTU - 1); |
| if (read_blob_count == kExpectedBlobCount) { |
| // The final blob should contain 3 bytes. |
| EXPECT_EQ(3u, blob.size()); |
| } |
| |
| callback(att::Status(), blob); |
| }); |
| |
| att::Status status(HostError::kFailed); |
| service->ReadLongCharacteristic( |
| 0, kOffset, kMaxBytes, [&](att::Status cb_status, const auto& value) { |
| status = cb_status; |
| EXPECT_TRUE(ContainersEqual(expected_value, value)); |
| }); |
| |
| RunLoopUntilIdle(); |
| EXPECT_TRUE(status); |
| EXPECT_EQ(kExpectedBlobCount, read_blob_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(GATT_RemoteServiceManagerTest, ReadLongValueExactMultipleOfMTU) { |
| constexpr att::Handle kValueHandle = 3; |
| constexpr uint16_t kOffset = 0; |
| constexpr size_t kMaxBytes = 1000; |
| constexpr int kExpectedBlobCount = 4; |
| |
| ServiceData data(1, kValueHandle, kTestServiceUuid1); |
| auto service = SetUpFakeService(data); |
| |
| CharacteristicData chr(Property::kRead, 2, kValueHandle, kTestUuid3); |
| SetupCharacteristics(service, {{chr}}); |
| |
| // 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_blob_count = 0; |
| fake_client()->set_read_blob_request_callback( |
| [&](att::Handle handle, uint16_t offset, auto callback) { |
| EXPECT_EQ(kValueHandle, handle); |
| read_blob_count++; |
| |
| // Return a blob at the given offset with at most MTU - 1 bytes. |
| auto blob = expected_value.view(offset, att::kLEMinMTU - 1); |
| if (read_blob_count == kExpectedBlobCount) { |
| // The final blob should be empty. |
| EXPECT_EQ(0u, blob.size()); |
| } |
| |
| callback(att::Status(), blob); |
| }); |
| |
| att::Status status(HostError::kFailed); |
| service->ReadLongCharacteristic( |
| 0, kOffset, kMaxBytes, [&](att::Status cb_status, const auto& value) { |
| status = cb_status; |
| EXPECT_TRUE(ContainersEqual(expected_value, value)); |
| }); |
| |
| RunLoopUntilIdle(); |
| EXPECT_TRUE(status); |
| EXPECT_EQ(kExpectedBlobCount, read_blob_count); |
| } |
| |
| // Same as ReadLongMultipleBlobs but a maximum size is given that is smaller |
| // than the size of the attribute value. |
| TEST_F(GATT_RemoteServiceManagerTest, ReadLongMultipleBlobsWithMaxSize) { |
| constexpr att::Handle kValueHandle = 3; |
| constexpr uint16_t kOffset = 0; |
| constexpr size_t kMaxBytes = 40; |
| constexpr int kExpectedBlobCount = 2; |
| |
| ServiceData data(1, kValueHandle, kTestServiceUuid1); |
| auto service = SetUpFakeService(data); |
| |
| CharacteristicData chr(Property::kRead, 2, kValueHandle, kTestUuid3); |
| SetupCharacteristics(service, {{chr}}); |
| |
| 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(kValueHandle, handle); |
| read_blob_count++; |
| callback(att::Status(), |
| expected_value.view(offset, att::kLEMinMTU - 1)); |
| }); |
| |
| att::Status status(HostError::kFailed); |
| service->ReadLongCharacteristic( |
| 0, kOffset, kMaxBytes, [&](att::Status cb_status, const auto& value) { |
| status = cb_status; |
| EXPECT_TRUE(ContainersEqual(expected_value.view(0, kMaxBytes), value)); |
| }); |
| |
| RunLoopUntilIdle(); |
| EXPECT_TRUE(status); |
| EXPECT_EQ(kExpectedBlobCount, read_blob_count); |
| } |
| |
| // Same as ReadLongMultipleBlobs but a non-zero offset is given. |
| TEST_F(GATT_RemoteServiceManagerTest, ReadLongAtOffset) { |
| constexpr att::Handle kValueHandle = 3; |
| constexpr uint16_t kOffset = 30; |
| constexpr size_t kMaxBytes = 1000; |
| constexpr int kExpectedBlobCount = 2; |
| |
| ServiceData data(1, kValueHandle, kTestServiceUuid1); |
| auto service = SetUpFakeService(data); |
| |
| CharacteristicData chr(Property::kRead, 2, kValueHandle, kTestUuid3); |
| SetupCharacteristics(service, {{chr}}); |
| |
| // 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(kValueHandle, handle); |
| read_blob_count++; |
| callback(att::Status(), |
| expected_value.view(offset, att::kLEMinMTU - 1)); |
| }); |
| |
| att::Status status(HostError::kFailed); |
| service->ReadLongCharacteristic( |
| 0, kOffset, kMaxBytes, [&](att::Status cb_status, const auto& value) { |
| status = cb_status; |
| EXPECT_TRUE( |
| ContainersEqual(expected_value.view(kOffset, kMaxBytes), value)); |
| }); |
| |
| RunLoopUntilIdle(); |
| EXPECT_TRUE(status); |
| EXPECT_EQ(kExpectedBlobCount, read_blob_count); |
| } |
| |
| // Same as ReadLongAtOffset but a very small max size is given. |
| TEST_F(GATT_RemoteServiceManagerTest, ReadLongAtOffsetWithMaxBytes) { |
| constexpr att::Handle kValueHandle = 3; |
| constexpr uint16_t kOffset = 10; |
| constexpr size_t kMaxBytes = 34; |
| constexpr int kExpectedBlobCount = 2; |
| |
| ServiceData data(1, kValueHandle, kTestServiceUuid1); |
| auto service = SetUpFakeService(data); |
| |
| CharacteristicData chr(Property::kRead, 2, kValueHandle, kTestUuid3); |
| SetupCharacteristics(service, {{chr}}); |
| |
| // Size: 69. 4 bytes will be read in a single request starting at index 30. |
| // Reads starting at offset 10 will return 12 + 22 bytes across 2 requests. 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] = i; |
| } |
| |
| int read_blob_count = 0; |
| fake_client()->set_read_blob_request_callback( |
| [&](att::Handle handle, uint16_t offset, auto callback) { |
| EXPECT_EQ(kValueHandle, handle); |
| read_blob_count++; |
| callback(att::Status(), |
| expected_value.view(offset, att::kLEMinMTU - 1)); |
| }); |
| |
| att::Status status(HostError::kFailed); |
| service->ReadLongCharacteristic( |
| 0, kOffset, kMaxBytes, [&](att::Status cb_status, const auto& value) { |
| status = cb_status; |
| EXPECT_TRUE( |
| ContainersEqual(expected_value.view(kOffset, kMaxBytes), value)); |
| }); |
| |
| RunLoopUntilIdle(); |
| EXPECT_TRUE(status); |
| EXPECT_EQ(kExpectedBlobCount, read_blob_count); |
| } |
| |
| TEST_F(GATT_RemoteServiceManagerTest, ReadLongError) { |
| constexpr att::Handle kValueHandle = 3; |
| constexpr uint16_t kOffset = 0; |
| constexpr size_t kMaxBytes = 1000; |
| constexpr int kExpectedBlobCount = 2; // The second request will fail. |
| |
| ServiceData data(1, kValueHandle, kTestServiceUuid1); |
| auto service = SetUpFakeService(data); |
| |
| CharacteristicData chr(Property::kRead, 2, kValueHandle, kTestUuid3); |
| SetupCharacteristics(service, {{chr}}); |
| |
| // Make the first blob large enough that it will cause a second read blob |
| // request. |
| StaticByteBuffer<att::kLEMinMTU - 1> first_blob; |
| |
| int read_blob_count = 0; |
| fake_client()->set_read_blob_request_callback( |
| [&](att::Handle handle, uint16_t offset, auto callback) { |
| EXPECT_EQ(kValueHandle, handle); |
| read_blob_count++; |
| if (read_blob_count == kExpectedBlobCount) { |
| callback(att::Status(att::ErrorCode::kInvalidOffset), BufferView()); |
| } else { |
| callback(att::Status(), first_blob); |
| } |
| }); |
| |
| att::Status status; |
| service->ReadLongCharacteristic( |
| 0, kOffset, kMaxBytes, [&](att::Status cb_status, const auto& value) { |
| status = cb_status; |
| EXPECT_EQ(0u, value.size()); // No value should be returned on error. |
| }); |
| |
| RunLoopUntilIdle(); |
| EXPECT_EQ(att::ErrorCode::kInvalidOffset, status.protocol_error()); |
| EXPECT_EQ(kExpectedBlobCount, read_blob_count); |
| } |
| |
| // The service is shut down while before the first read blob response. The |
| // operation should get canceled. |
| TEST_F(GATT_RemoteServiceManagerTest, ReadLongShutDownWhileInProgress) { |
| constexpr att::Handle kValueHandle = 3; |
| constexpr uint16_t kOffset = 0; |
| constexpr size_t kMaxBytes = 1000; |
| constexpr int kExpectedBlobCount = 1; |
| |
| ServiceData data(1, kValueHandle, kTestServiceUuid1); |
| auto service = SetUpFakeService(data); |
| |
| CharacteristicData chr(Property::kRead, 2, kValueHandle, kTestUuid3); |
| SetupCharacteristics(service, {{chr}}); |
| |
| StaticByteBuffer<att::kLEMinMTU - 1> first_blob; |
| |
| int read_blob_count = 0; |
| fake_client()->set_read_blob_request_callback( |
| [&](att::Handle handle, uint16_t offset, auto callback) { |
| EXPECT_EQ(kValueHandle, handle); |
| read_blob_count++; |
| |
| service->ShutDown(); |
| callback(att::Status(), first_blob); |
| }); |
| |
| att::Status status; |
| service->ReadLongCharacteristic( |
| 0, kOffset, kMaxBytes, [&](att::Status cb_status, const auto& value) { |
| status = cb_status; |
| EXPECT_EQ(0u, value.size()); // No value should be returned on error. |
| }); |
| |
| RunLoopUntilIdle(); |
| EXPECT_EQ(HostError::kCanceled, status.error()); |
| EXPECT_EQ(kExpectedBlobCount, read_blob_count); |
| } |
| |
| TEST_F(GATT_RemoteServiceManagerTest, WriteCharAfterShutDown) { |
| ServiceData data(1, 2, kTestServiceUuid1); |
| auto service = SetUpFakeService(data); |
| |
| service->ShutDown(); |
| |
| att::Status status; |
| service->WriteCharacteristic( |
| 0, std::vector<uint8_t>(), |
| [&](att::Status cb_status) { status = cb_status; }); |
| |
| RunLoopUntilIdle(); |
| |
| EXPECT_EQ(HostError::kFailed, status.error()); |
| } |
| |
| TEST_F(GATT_RemoteServiceManagerTest, WriteCharWhileNotReady) { |
| ServiceData data(1, 2, kTestServiceUuid1); |
| auto service = SetUpFakeService(data); |
| |
| att::Status status; |
| service->WriteCharacteristic( |
| 0, std::vector<uint8_t>(), |
| [&](att::Status cb_status) { status = cb_status; }); |
| |
| RunLoopUntilIdle(); |
| |
| EXPECT_EQ(HostError::kNotReady, status.error()); |
| } |
| |
| TEST_F(GATT_RemoteServiceManagerTest, WriteCharNotFound) { |
| ServiceData data(1, 2, kTestServiceUuid1); |
| auto service = SetUpFakeService(data); |
| SetupCharacteristics(service, std::vector<CharacteristicData>()); |
| |
| att::Status status; |
| service->WriteCharacteristic( |
| 0, std::vector<uint8_t>(), |
| [&](att::Status cb_status) { status = cb_status; }); |
| |
| RunLoopUntilIdle(); |
| |
| EXPECT_EQ(HostError::kNotFound, status.error()); |
| } |
| |
| TEST_F(GATT_RemoteServiceManagerTest, WriteCharNotSupported) { |
| ServiceData data(1, 3, kTestServiceUuid1); |
| auto service = SetUpFakeService(data); |
| |
| // No "write" property set. |
| CharacteristicData chr(0, 2, 3, kTestUuid3); |
| SetupCharacteristics(service, {{chr}}); |
| |
| att::Status status; |
| service->WriteCharacteristic( |
| 0, std::vector<uint8_t>(), |
| [&](att::Status cb_status) { status = cb_status; }); |
| |
| RunLoopUntilIdle(); |
| |
| EXPECT_EQ(HostError::kNotSupported, status.error()); |
| } |
| |
| TEST_F(GATT_RemoteServiceManagerTest, WriteCharSendsWriteRequest) { |
| constexpr att::Handle kValueHandle = 3; |
| const std::vector<uint8_t> kValue{{'t', 'e', 's', 't'}}; |
| constexpr att::Status kStatus(att::ErrorCode::kWriteNotPermitted); |
| |
| ServiceData data(1, kValueHandle, kTestServiceUuid1); |
| auto service = SetUpFakeService(data); |
| |
| CharacteristicData chr(Property::kWrite, 2, kValueHandle, kTestUuid3); |
| SetupCharacteristics(service, {{chr}}); |
| |
| fake_client()->set_write_request_callback( |
| [&](att::Handle handle, const auto& value, auto status_callback) { |
| EXPECT_EQ(kValueHandle, handle); |
| EXPECT_TRUE(std::equal(kValue.begin(), kValue.end(), value.begin(), |
| value.end())); |
| status_callback(kStatus); |
| }); |
| |
| att::Status status; |
| service->WriteCharacteristic( |
| 0, kValue, [&](att::Status cb_status) { status = cb_status; }); |
| |
| RunLoopUntilIdle(); |
| |
| EXPECT_TRUE(status.is_protocol_error()); |
| EXPECT_EQ(kStatus, status); |
| } |
| |
| TEST_F(GATT_RemoteServiceManagerTest, WriteWithoutResponseNotSupported) { |
| ServiceData data(1, 3, kTestServiceUuid1); |
| auto service = SetUpFakeService(data); |
| |
| // No "write without response" property. |
| CharacteristicData chr(0, 2, 3, kTestUuid3); |
| SetupCharacteristics(service, {{chr}}); |
| |
| bool called = false; |
| fake_client()->set_write_without_rsp_callback( |
| [&](auto, const auto&) { called = true; }); |
| |
| service->WriteCharacteristicWithoutResponse(0, std::vector<uint8_t>()); |
| RunLoopUntilIdle(); |
| EXPECT_FALSE(called); |
| } |
| |
| TEST_F(GATT_RemoteServiceManagerTest, WriteWithoutResponseSuccess) { |
| constexpr att::Handle kValueHandle = 3; |
| const std::vector<uint8_t> kValue{{'t', 'e', 's', 't'}}; |
| |
| ServiceData data(1, kValueHandle, kTestServiceUuid1); |
| auto service = SetUpFakeService(data); |
| |
| CharacteristicData chr(Property::kWriteWithoutResponse, 2, kValueHandle, |
| kTestUuid3); |
| SetupCharacteristics(service, {{chr}}); |
| |
| bool called = false; |
| fake_client()->set_write_without_rsp_callback( |
| [&](att::Handle handle, const auto& value) { |
| EXPECT_EQ(kValueHandle, handle); |
| EXPECT_TRUE(std::equal(kValue.begin(), kValue.end(), value.begin(), |
| value.end())); |
| called = true; |
| }); |
| |
| service->WriteCharacteristicWithoutResponse(0, kValue); |
| RunLoopUntilIdle(); |
| |
| EXPECT_TRUE(called); |
| } |
| |
| TEST_F(GATT_RemoteServiceManagerTest, ReadDescAfterShutDown) { |
| ServiceData data(1, 2, kTestServiceUuid1); |
| auto service = SetUpFakeService(data); |
| |
| service->ShutDown(); |
| |
| att::Status status; |
| service->ReadDescriptor( |
| 0, [&](att::Status cb_status, const auto&) { status = cb_status; }); |
| |
| RunLoopUntilIdle(); |
| |
| EXPECT_EQ(HostError::kFailed, status.error()); |
| } |
| |
| TEST_F(GATT_RemoteServiceManagerTest, ReadDescWhileNotReady) { |
| ServiceData data(1, 2, kTestServiceUuid1); |
| auto service = SetUpFakeService(data); |
| |
| att::Status status; |
| service->ReadDescriptor( |
| 0, [&](att::Status cb_status, const auto&) { status = cb_status; }); |
| |
| RunLoopUntilIdle(); |
| |
| EXPECT_EQ(HostError::kNotReady, status.error()); |
| } |
| |
| TEST_F(GATT_RemoteServiceManagerTest, ReadDescriptorNotFound) { |
| ServiceData data(1, 2, kTestServiceUuid1); |
| auto service = SetUpFakeService(data); |
| SetupCharacteristics(service, std::vector<CharacteristicData>()); |
| |
| att::Status status; |
| service->ReadDescriptor( |
| 0, [&](att::Status cb_status, const auto&) { status = cb_status; }); |
| |
| RunLoopUntilIdle(); |
| |
| EXPECT_EQ(HostError::kNotFound, status.error()); |
| } |
| |
| TEST_F(GATT_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_unittest.cc). |
| constexpr att::Handle kValueHandle1 = 3; |
| constexpr att::Handle kValueHandle2 = 5; |
| constexpr att::Handle kDescrHandle = 6; |
| |
| ServiceData data(1, kDescrHandle, kTestServiceUuid1); |
| auto service = SetUpFakeService(data); |
| |
| CharacteristicData chr1(Property::kRead, 2, kValueHandle1, kTestUuid3); |
| CharacteristicData chr2(Property::kRead, 4, kValueHandle2, kTestUuid3); |
| DescriptorData desc(kDescrHandle, kTestUuid4); |
| SetupCharacteristics(service, {{chr1, chr2}}, {{desc}}); |
| |
| const auto kValue = CreateStaticByteBuffer('t', 'e', 's', 't'); |
| |
| fake_client()->set_read_request_callback( |
| [&](att::Handle handle, auto callback) { |
| EXPECT_EQ(kDescrHandle, handle); |
| callback(att::Status(), kValue); |
| }); |
| |
| // We read the first descriptor of the second characteristic. The descriptor |
| // has this ID based on the scheme described in remote_characteristic.h. |
| constexpr IdType kDescrId = 0x010000; |
| att::Status status(HostError::kFailed); |
| service->ReadDescriptor(kDescrId, |
| [&](att::Status cb_status, const auto& value) { |
| status = cb_status; |
| EXPECT_TRUE(ContainersEqual(kValue, value)); |
| }); |
| |
| RunLoopUntilIdle(); |
| |
| EXPECT_TRUE(status); |
| } |
| |
| TEST_F(GATT_RemoteServiceManagerTest, ReadDescSendsReadRequestWithDispatcher) { |
| constexpr att::Handle kValueHandle = 3; |
| constexpr att::Handle kDescrHandle = 4; |
| |
| ServiceData data(1, kDescrHandle, kTestServiceUuid1); |
| auto service = SetUpFakeService(data); |
| |
| CharacteristicData chr(Property::kRead, 2, kValueHandle, kTestUuid3); |
| DescriptorData desc(kDescrHandle, kTestUuid4); |
| SetupCharacteristics(service, {{chr}}, {{desc}}); |
| |
| const auto kValue = CreateStaticByteBuffer('t', 'e', 's', 't'); |
| |
| fake_client()->set_read_request_callback( |
| [&](att::Handle handle, auto callback) { |
| EXPECT_EQ(kDescrHandle, handle); |
| callback(att::Status(), kValue); |
| }); |
| |
| att::Status status(HostError::kFailed); |
| service->ReadDescriptor( |
| 0, |
| [&](att::Status cb_status, const auto& value) { |
| status = cb_status; |
| EXPECT_TRUE(ContainersEqual(kValue, value)); |
| }, |
| dispatcher()); |
| |
| RunLoopUntilIdle(); |
| |
| EXPECT_TRUE(status); |
| } |
| |
| TEST_F(GATT_RemoteServiceManagerTest, ReadLongDescWhileNotReady) { |
| ServiceData data(1, 2, kTestServiceUuid1); |
| auto service = SetUpFakeService(data); |
| |
| att::Status status; |
| service->ReadLongDescriptor( |
| 0, 0, 512, |
| [&](att::Status cb_status, const auto&) { status = cb_status; }); |
| |
| RunLoopUntilIdle(); |
| |
| EXPECT_EQ(HostError::kNotReady, status.error()); |
| } |
| |
| TEST_F(GATT_RemoteServiceManagerTest, ReadLongDescNotFound) { |
| ServiceData data(1, 2, kTestServiceUuid1); |
| auto service = SetUpFakeService(data); |
| SetupCharacteristics(service, std::vector<CharacteristicData>()); |
| |
| att::Status status; |
| service->ReadLongDescriptor( |
| 0, 0, 512, |
| [&](att::Status cb_status, const auto&) { status = cb_status; }); |
| |
| RunLoopUntilIdle(); |
| |
| EXPECT_EQ(HostError::kNotFound, status.error()); |
| } |
| |
| // 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(GATT_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(1, kDescrHandle, kTestServiceUuid1); |
| auto service = SetUpFakeService(data); |
| |
| CharacteristicData chr(Property::kRead, 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_blob_count = 0; |
| fake_client()->set_read_blob_request_callback( |
| [&](att::Handle handle, uint16_t offset, auto callback) { |
| EXPECT_EQ(kDescrHandle, handle); |
| read_blob_count++; |
| |
| // Return a blob at the given offset with at most MTU - 1 bytes. |
| auto blob = expected_value.view(offset, att::kLEMinMTU - 1); |
| if (read_blob_count == kExpectedBlobCount) { |
| // The final blob should contain 3 bytes. |
| EXPECT_EQ(3u, blob.size()); |
| } |
| |
| callback(att::Status(), blob); |
| }); |
| |
| att::Status status(HostError::kFailed); |
| service->ReadLongDescriptor( |
| 0, kOffset, kMaxBytes, [&](att::Status cb_status, const auto& value) { |
| status = cb_status; |
| EXPECT_TRUE(ContainersEqual(expected_value, value)); |
| }); |
| |
| RunLoopUntilIdle(); |
| EXPECT_TRUE(status); |
| EXPECT_EQ(kExpectedBlobCount, read_blob_count); |
| } |
| |
| TEST_F(GATT_RemoteServiceManagerTest, WriteDescAfterShutDown) { |
| ServiceData data(1, 2, kTestServiceUuid1); |
| auto service = SetUpFakeService(data); |
| |
| service->ShutDown(); |
| |
| att::Status status; |
| service->WriteDescriptor(0, std::vector<uint8_t>(), |
| [&](att::Status cb_status) { status = cb_status; }); |
| |
| RunLoopUntilIdle(); |
| |
| EXPECT_EQ(HostError::kFailed, status.error()); |
| } |
| |
| TEST_F(GATT_RemoteServiceManagerTest, WriteDescWhileNotReady) { |
| ServiceData data(1, 2, kTestServiceUuid1); |
| auto service = SetUpFakeService(data); |
| |
| att::Status status; |
| service->WriteDescriptor(0, std::vector<uint8_t>(), |
| [&](att::Status cb_status) { status = cb_status; }); |
| |
| RunLoopUntilIdle(); |
| |
| EXPECT_EQ(HostError::kNotReady, status.error()); |
| } |
| |
| TEST_F(GATT_RemoteServiceManagerTest, WriteDescNotFound) { |
| ServiceData data(1, 2, kTestServiceUuid1); |
| auto service = SetUpFakeService(data); |
| SetupCharacteristics(service, std::vector<CharacteristicData>()); |
| |
| att::Status status; |
| service->WriteDescriptor(0, std::vector<uint8_t>(), |
| [&](att::Status cb_status) { status = cb_status; }); |
| |
| RunLoopUntilIdle(); |
| |
| EXPECT_EQ(HostError::kNotFound, status.error()); |
| } |
| |
| TEST_F(GATT_RemoteServiceManagerTest, WriteDescNotAllowed) { |
| ServiceData data(1, 4, kTestServiceUuid1); |
| auto service = SetUpFakeService(data); |
| |
| // "CCC" characteristic cannot be written to. |
| CharacteristicData chr(0, 2, 3, kTestUuid3); |
| DescriptorData desc(4, types::kClientCharacteristicConfig); |
| SetupCharacteristics(service, {{chr}}, {{desc}}); |
| |
| att::Status status; |
| service->WriteDescriptor(0, std::vector<uint8_t>(), |
| [&](att::Status cb_status) { status = cb_status; }); |
| |
| RunLoopUntilIdle(); |
| |
| EXPECT_EQ(HostError::kNotSupported, status.error()); |
| } |
| |
| TEST_F(GATT_RemoteServiceManagerTest, WriteDescSendsWriteRequest) { |
| constexpr att::Handle kValueHandle = 3; |
| constexpr att::Handle kDescrHandle = 4; |
| const std::vector<uint8_t> kValue{{'t', 'e', 's', 't'}}; |
| const att::Status kStatus(HostError::kNotSupported); |
| |
| ServiceData data(1, kValueHandle, kTestServiceUuid1); |
| auto service = SetUpFakeService(data); |
| |
| CharacteristicData chr(Property::kWrite, 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(kValueHandle, handle); |
| EXPECT_TRUE(std::equal(kValue.begin(), kValue.end(), value.begin(), |
| value.end())); |
| status_callback(kStatus); |
| }); |
| |
| att::Status status; |
| service->WriteDescriptor(0, kValue, |
| [&](att::Status cb_status) { status = cb_status; }); |
| |
| RunLoopUntilIdle(); |
| EXPECT_EQ(kStatus, status); |
| } |
| |
| TEST_F(GATT_RemoteServiceManagerTest, EnableNotificationsAfterShutDown) { |
| ServiceData data(1, 2, kTestServiceUuid1); |
| auto service = SetUpFakeService(data); |
| |
| service->ShutDown(); |
| |
| att::Status status; |
| service->EnableNotifications( |
| 0, NopValueCallback, |
| [&](att::Status cb_status, IdType) { status = cb_status; }); |
| |
| RunLoopUntilIdle(); |
| |
| EXPECT_EQ(HostError::kFailed, status.error()); |
| } |
| |
| TEST_F(GATT_RemoteServiceManagerTest, EnableNotificationsWhileNotReady) { |
| ServiceData data(1, 2, kTestServiceUuid1); |
| auto service = SetUpFakeService(data); |
| |
| att::Status status; |
| service->EnableNotifications( |
| 0, NopValueCallback, |
| [&](att::Status cb_status, IdType) { status = cb_status; }); |
| |
| RunLoopUntilIdle(); |
| |
| EXPECT_EQ(HostError::kNotReady, status.error()); |
| } |
| |
| TEST_F(GATT_RemoteServiceManagerTest, EnableNotificationsCharNotFound) { |
| ServiceData data(1, 2, kTestServiceUuid1); |
| auto service = SetUpFakeService(data); |
| SetupCharacteristics(service, std::vector<CharacteristicData>()); |
| |
| att::Status status; |
| service->EnableNotifications( |
| 0, NopValueCallback, |
| [&](att::Status cb_status, IdType) { status = cb_status; }); |
| |
| RunLoopUntilIdle(); |
| |
| EXPECT_EQ(HostError::kNotFound, status.error()); |
| } |
| |
| TEST_F(GATT_RemoteServiceManagerTest, EnableNotificationsNoProperties) { |
| ServiceData data(1, 4, kTestServiceUuid1); |
| auto service = SetUpFakeService(data); |
| |
| // Has neither the "notify" nor "indicate" property but has a CCC descriptor. |
| CharacteristicData chr(Property::kRead, 2, 3, kTestUuid3); |
| DescriptorData desc(4, types::kClientCharacteristicConfig); |
| SetupCharacteristics(service, {{chr}}, {{desc}}); |
| |
| att::Status status; |
| service->EnableNotifications( |
| 0, NopValueCallback, |
| [&](att::Status cb_status, IdType) { status = cb_status; }); |
| |
| RunLoopUntilIdle(); |
| |
| EXPECT_EQ(HostError::kNotSupported, status.error()); |
| } |
| |
| TEST_F(GATT_RemoteServiceManagerTest, EnableNotificationsNoCCC) { |
| ServiceData data(1, 3, kTestServiceUuid1); |
| auto service = SetUpFakeService(data); |
| |
| // Has the "notify" property but no CCC descriptor. |
| CharacteristicData chr(Property::kNotify, 2, 3, kTestUuid3); |
| SetupCharacteristics(service, {{chr}}); |
| |
| att::Status status; |
| service->EnableNotifications( |
| 0, NopValueCallback, |
| [&](att::Status cb_status, IdType) { status = cb_status; }); |
| |
| RunLoopUntilIdle(); |
| |
| EXPECT_EQ(HostError::kNotSupported, status.error()); |
| } |
| |
| TEST_F(GATT_RemoteServiceManagerTest, EnableNotificationsSuccess) { |
| constexpr att::Handle kCCCHandle = 4; |
| |
| ServiceData data(1, kCCCHandle, kTestServiceUuid1); |
| auto service = SetUpFakeService(data); |
| |
| CharacteristicData chr(Property::kNotify, 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(att::Status()); |
| }); |
| |
| IdType id = kInvalidId; |
| att::Status status(HostError::kFailed); |
| service->EnableNotifications(0, NopValueCallback, |
| [&](att::Status cb_status, IdType cb_id) { |
| status = cb_status; |
| id = cb_id; |
| }); |
| |
| RunLoopUntilIdle(); |
| |
| EXPECT_TRUE(status); |
| EXPECT_NE(kInvalidId, id); |
| } |
| |
| TEST_F(GATT_RemoteServiceManagerTest, EnableIndications) { |
| constexpr att::Handle kCCCHandle = 4; |
| |
| ServiceData data(1, 4, kTestServiceUuid1); |
| auto service = SetUpFakeService(data); |
| |
| CharacteristicData chr(Property::kIndicate, 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(att::Status()); |
| }); |
| |
| IdType id = kInvalidId; |
| att::Status status(HostError::kFailed); |
| service->EnableNotifications(0, NopValueCallback, |
| [&](att::Status cb_status, IdType cb_id) { |
| status = cb_status; |
| id = cb_id; |
| }); |
| |
| RunLoopUntilIdle(); |
| |
| EXPECT_TRUE(status); |
| EXPECT_NE(kInvalidId, id); |
| } |
| |
| TEST_F(GATT_RemoteServiceManagerTest, EnableNotificationsError) { |
| constexpr att::Handle kCCCHandle = 4; |
| |
| ServiceData data(1, 4, kTestServiceUuid1); |
| auto service = SetUpFakeService(data); |
| |
| CharacteristicData chr(Property::kNotify, 2, 3, kTestUuid3); |
| DescriptorData desc(kCCCHandle, types::kClientCharacteristicConfig); |
| SetupCharacteristics(service, {{chr}}, {{desc}}); |
| |
| // Should enable notifications |
| const auto kExpectedValue = CreateStaticByteBuffer(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(att::Status(att::ErrorCode::kUnlikelyError)); |
| }); |
| |
| IdType id = kInvalidId; |
| att::Status status; |
| service->EnableNotifications(0, NopValueCallback, |
| [&](att::Status cb_status, IdType cb_id) { |
| status = cb_status; |
| id = cb_id; |
| }); |
| |
| RunLoopUntilIdle(); |
| |
| EXPECT_TRUE(status.is_protocol_error()); |
| EXPECT_EQ(att::ErrorCode::kUnlikelyError, status.protocol_error()); |
| EXPECT_EQ(kInvalidId, id); |
| } |
| |
| TEST_F(GATT_RemoteServiceManagerTest, EnableNotificationsRequestMany) { |
| constexpr att::Handle kCCCHandle1 = 4; |
| constexpr att::Handle kCCCHandle2 = 7; |
| |
| ServiceData data(1, 7, kTestServiceUuid1); |
| auto service = SetUpFakeService(data); |
| |
| // Set up two characteristics |
| CharacteristicData chr1(Property::kNotify, 2, 3, kTestUuid3); |
| DescriptorData desc1(kCCCHandle1, types::kClientCharacteristicConfig); |
| |
| CharacteristicData chr2(Property::kIndicate, 5, 6, kTestUuid3); |
| DescriptorData desc2(kCCCHandle2, types::kClientCharacteristicConfig); |
| |
| SetupCharacteristics(service, {{chr1, chr2}}, {{desc1, desc2}}); |
| |
| int ccc_write_count = 0; |
| att::StatusCallback 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(0, NopValueCallback, |
| [&](att::Status status, IdType id) { |
| cb_count++; |
| EXPECT_EQ(1u, id); |
| EXPECT_TRUE(status); |
| }); |
| service->EnableNotifications(0, NopValueCallback, |
| [&](att::Status status, IdType id) { |
| cb_count++; |
| EXPECT_EQ(2u, id); |
| EXPECT_TRUE(status); |
| }); |
| service->EnableNotifications(1, NopValueCallback, |
| [&](att::Status status, IdType id) { |
| cb_count++; |
| EXPECT_EQ(1u, id); |
| EXPECT_TRUE(status); |
| }); |
| service->EnableNotifications(1, NopValueCallback, |
| [&](att::Status status, IdType id) { |
| cb_count++; |
| EXPECT_EQ(2u, id); |
| EXPECT_TRUE(status); |
| }); |
| service->EnableNotifications(1, NopValueCallback, |
| [&](att::Status status, IdType id) { |
| cb_count++; |
| EXPECT_EQ(3u, id); |
| EXPECT_TRUE(status); |
| }); |
| |
| RunLoopUntilIdle(); |
| |
| // 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(att::Status()); |
| EXPECT_EQ(2u, cb_count); |
| status_callback2(att::Status()); |
| EXPECT_EQ(5u, cb_count); |
| |
| // An extra request should succeed without sending any PDUs. |
| service->EnableNotifications(0, NopValueCallback, |
| [&](att::Status status, IdType) { |
| cb_count++; |
| EXPECT_TRUE(status); |
| }); |
| |
| RunLoopUntilIdle(); |
| |
| EXPECT_EQ(2, ccc_write_count); |
| EXPECT_EQ(6u, cb_count); |
| } |
| |
| TEST_F(GATT_RemoteServiceManagerTest, EnableNotificationsRequestManyError) { |
| constexpr att::Handle kCCCHandle = 4; |
| |
| ServiceData data(1, 4, kTestServiceUuid1); |
| auto service = SetUpFakeService(data); |
| |
| // Set up two characteristics |
| CharacteristicData chr(Property::kNotify, 2, 3, kTestUuid3); |
| DescriptorData desc(kCCCHandle, types::kClientCharacteristicConfig); |
| |
| SetupCharacteristics(service, {{chr}}, {{desc}}); |
| |
| int ccc_write_count = 0; |
| att::StatusCallback 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::Status status; |
| auto cb = [&](att::Status cb_status, IdType id) { |
| status = cb_status; |
| cb_count++; |
| }; |
| |
| service->EnableNotifications(0, NopValueCallback, std::move(cb)); |
| service->EnableNotifications(0, NopValueCallback, std::move(cb)); |
| service->EnableNotifications(0, NopValueCallback, std::move(cb)); |
| |
| RunLoopUntilIdle(); |
| |
| // 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(att::Status(HostError::kNotSupported)); |
| EXPECT_EQ(3, cb_count); |
| EXPECT_EQ(HostError::kNotSupported, status.error()); |
| |
| // A new request should write to the descriptor again. |
| service->EnableNotifications(0, NopValueCallback, std::move(cb)); |
| |
| RunLoopUntilIdle(); |
| |
| EXPECT_EQ(2, ccc_write_count); |
| EXPECT_EQ(3, cb_count); |
| |
| status_callback(att::Status()); |
| EXPECT_EQ(2, ccc_write_count); |
| EXPECT_EQ(4, cb_count); |
| EXPECT_TRUE(status); |
| } |
| |
| // Notifications received when the remote service database is empty should be |
| // dropped and not cause a crash. |
| TEST_F(GATT_RemoteServiceManagerTest, NotificationWithoutServices) { |
| for (att::Handle i = 0; i < 10; ++i) { |
| fake_client()->SendNotification( |
| false, i, CreateStaticByteBuffer('n', 'o', 't', 'i', 'f', 'y')); |
| } |
| RunLoopUntilIdle(); |
| } |
| |
| TEST_F(GATT_RemoteServiceManagerTest, NotificationCallback) { |
| constexpr IdType kId1 = 0; |
| constexpr IdType kId2 = 1; |
| |
| ServiceData data(1, 7, kTestServiceUuid1); |
| auto service = SetUpFakeService(data); |
| |
| // Set up two characteristics |
| CharacteristicData chr1(Property::kNotify, 2, 3, kTestUuid3); |
| DescriptorData desc1(4, types::kClientCharacteristicConfig); |
| |
| CharacteristicData chr2(Property::kIndicate, 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(att::Status()); |
| }); |
| |
| IdType handler_id = kInvalidId; |
| att::Status status(HostError::kFailed); |
| |
| int chr1_count = 0; |
| auto chr1_cb = [&](const ByteBuffer& value) { |
| chr1_count++; |
| EXPECT_EQ("notify", value.AsString()); |
| }; |
| |
| int chr2_count = 0; |
| auto chr2_cb = [&](const ByteBuffer& value) { |
| chr2_count++; |
| EXPECT_EQ("indicate", value.AsString()); |
| }; |
| |
| // Notify both characteristics which should get dropped. |
| fake_client()->SendNotification( |
| false, 3, CreateStaticByteBuffer('n', 'o', 't', 'i', 'f', 'y')); |
| fake_client()->SendNotification( |
| true, 6, CreateStaticByteBuffer('i', 'n', 'd', 'i', 'c', 'a', 't', 'e')); |
| |
| EnableNotifications(service, kId1, &status, &handler_id, std::move(chr1_cb)); |
| ASSERT_TRUE(status); |
| EnableNotifications(service, kId2, &status, &handler_id, std::move(chr2_cb)); |
| ASSERT_TRUE(status); |
| |
| // Notify characteristic 1. |
| fake_client()->SendNotification( |
| false, 3, CreateStaticByteBuffer('n', 'o', 't', 'i', 'f', 'y')); |
| EXPECT_EQ(1, chr1_count); |
| EXPECT_EQ(0, chr2_count); |
| |
| // Notify characteristic 2. |
| fake_client()->SendNotification( |
| true, 6, CreateStaticByteBuffer('i', 'n', 'd', 'i', 'c', 'a', 't', 'e')); |
| EXPECT_EQ(1, chr1_count); |
| EXPECT_EQ(1, chr2_count); |
| |
| // Disable notifications from characteristic 1. |
| status = att::Status(HostError::kFailed); |
| service->DisableNotifications( |
| kId1, handler_id, [&](att::Status cb_status) { status = cb_status; }); |
| |
| RunLoopUntilIdle(); |
| EXPECT_TRUE(status); |
| |
| // Notifications for characteristic 1 should get dropped. |
| fake_client()->SendNotification( |
| false, 3, CreateStaticByteBuffer('n', 'o', 't', 'i', 'f', 'y')); |
| fake_client()->SendNotification( |
| true, 6, CreateStaticByteBuffer('i', 'n', 'd', 'i', 'c', 'a', 't', 'e')); |
| EXPECT_EQ(1, chr1_count); |
| EXPECT_EQ(2, chr2_count); |
| } |
| |
| TEST_F(GATT_RemoteServiceManagerTest, DisableNotificationsAfterShutDown) { |
| auto service = SetupNotifiableService(); |
| |
| IdType id = kInvalidId; |
| att::Status status(HostError::kFailed); |
| EnableNotifications(service, 0, &status, &id); |
| |
| EXPECT_TRUE(status); |
| EXPECT_NE(kInvalidId, id); |
| |
| service->ShutDown(); |
| |
| service->DisableNotifications( |
| 0, id, [&](att::Status cb_status) { status = cb_status; }); |
| |
| RunLoopUntilIdle(); |
| |
| EXPECT_EQ(HostError::kFailed, status.error()); |
| } |
| |
| TEST_F(GATT_RemoteServiceManagerTest, DisableNotificationsWhileNotReady) { |
| ServiceData data(1, 4, kTestServiceUuid1); |
| auto service = SetUpFakeService(data); |
| |
| att::Status status; |
| service->DisableNotifications( |
| 0, 1, [&](att::Status cb_status) { status = cb_status; }); |
| |
| RunLoopUntilIdle(); |
| |
| EXPECT_EQ(HostError::kNotReady, status.error()); |
| } |
| |
| TEST_F(GATT_RemoteServiceManagerTest, DisableNotificationsCharNotFound) { |
| auto service = SetupNotifiableService(); |
| |
| IdType id = kInvalidId; |
| att::Status status(HostError::kFailed); |
| EnableNotifications(service, 0, &status, &id); |
| |
| // "1" is an invalid characteristic ID. |
| service->DisableNotifications( |
| 1, id, [&](att::Status cb_status) { status = cb_status; }); |
| |
| RunLoopUntilIdle(); |
| |
| EXPECT_EQ(HostError::kNotFound, status.error()); |
| } |
| |
| TEST_F(GATT_RemoteServiceManagerTest, DisableNotificationsIdNotFound) { |
| auto service = SetupNotifiableService(); |
| |
| IdType id = kInvalidId; |
| att::Status status(HostError::kFailed); |
| EnableNotifications(service, 0, &status, &id); |
| |
| // Valid characteristic ID but invalid notification handler ID. |
| service->DisableNotifications( |
| 0, id + 1, [&](att::Status cb_status) { status = cb_status; }); |
| |
| RunLoopUntilIdle(); |
| |
| EXPECT_EQ(HostError::kNotFound, status.error()); |
| } |
| |
| TEST_F(GATT_RemoteServiceManagerTest, DisableNotificationsSingleHandler) { |
| constexpr att::Handle kCCCHandle = 4; |
| auto service = SetupNotifiableService(); |
| |
| IdType id = kInvalidId; |
| att::Status status(HostError::kFailed); |
| EnableNotifications(service, 0, &status, &id); |
| |
| // Should disable notifications |
| const auto kExpectedValue = CreateStaticByteBuffer(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(att::Status()); |
| }); |
| |
| status = att::Status(HostError::kFailed); |
| service->DisableNotifications( |
| 0, id, [&](att::Status cb_status) { status = cb_status; }); |
| |
| RunLoopUntilIdle(); |
| |
| EXPECT_TRUE(status); |
| EXPECT_EQ(1, ccc_write_count); |
| } |
| |
| TEST_F(GATT_RemoteServiceManagerTest, DisableNotificationsDuringShutDown) { |
| constexpr att::Handle kCCCHandle = 4; |
| auto service = SetupNotifiableService(); |
| |
| IdType id = kInvalidId; |
| att::Status status(HostError::kFailed); |
| EnableNotifications(service, 0, &status, &id); |
| ASSERT_TRUE(status); |
| |
| // Should disable notifications |
| const auto kExpectedValue = CreateStaticByteBuffer(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(att::Status()); |
| }); |
| |
| // Shutting down the service should clear the CCC. |
| service->ShutDown(); |
| RunLoopUntilIdle(); |
| |
| EXPECT_EQ(1, ccc_write_count); |
| } |
| |
| TEST_F(GATT_RemoteServiceManagerTest, DisableNotificationsManyHandlers) { |
| auto service = SetupNotifiableService(); |
| |
| IdType id = kInvalidId; |
| std::vector<IdType> handler_ids; |
| |
| for (int i = 0; i < 2; i++) { |
| att::Status status(HostError::kFailed); |
| EnableNotifications(service, 0, &status, &id); |
| ASSERT_TRUE(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(att::Status()); |
| }); |
| |
| // Disabling should succeed without an ATT transaction. |
| att::Status status(HostError::kFailed); |
| service->DisableNotifications( |
| 0, handler_ids.back(), |
| [&](att::Status cb_status) { status = cb_status; }); |
| handler_ids.pop_back(); |
| RunLoopUntilIdle(); |
| EXPECT_TRUE(status); |
| EXPECT_EQ(0, ccc_write_count); |
| |
| // Enabling should succeed without an ATT transaction. |
| status = att::Status(HostError::kFailed); |
| EnableNotifications(service, 0, &status, &id); |
| EXPECT_TRUE(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::Status status(HostError::kFailed); |
| service->DisableNotifications( |
| 0, handler_ids.back(), |
| [&](att::Status cb_status) { status = cb_status; }); |
| handler_ids.pop_back(); |
| RunLoopUntilIdle(); |
| EXPECT_TRUE(status); |
| } |
| |
| EXPECT_EQ(1, ccc_write_count); |
| } |
| |
| } // namespace |
| } // namespace internal |
| } // namespace gatt |
| } // namespace bt |