| // Copyright 2021 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "gatt2_client_server.h" |
| |
| #include "fuchsia/bluetooth/gatt2/cpp/fidl.h" |
| #include "src/connectivity/bluetooth/core/bt-host/fidl/fake_gatt_fixture.h" |
| #include "src/lib/testing/loop_fixture/test_loop_fixture.h" |
| |
| namespace bthost { |
| |
| namespace { |
| |
| namespace fb = fuchsia::bluetooth; |
| namespace fbg = fuchsia::bluetooth::gatt2; |
| |
| constexpr bt::PeerId kPeerId(1); |
| constexpr bt::UUID kTestServiceUuid0(uint16_t{0xdead}); |
| constexpr bt::UUID kTestServiceUuid1(uint16_t{0xbeef}); |
| constexpr bt::UUID kTestServiceUuid3(uint16_t{0xbaad}); |
| |
| class Gatt2ClientServerTest : public bt::fidl::testing::FakeGattFixture { |
| public: |
| Gatt2ClientServerTest() = default; |
| ~Gatt2ClientServerTest() override = default; |
| |
| void SetUp() override { |
| server_ = std::make_unique<Gatt2ClientServer>(kPeerId, gatt()->GetWeakPtr(), |
| proxy_.NewRequest(), [this]() { |
| error_cb_called_ = true; |
| server_.reset(); |
| }); |
| proxy_.set_error_handler([this](zx_status_t epitaph) { proxy_epitaph_ = epitaph; }); |
| } |
| |
| fbg::Client* proxy() const { return proxy_.get(); } |
| |
| void UnbindProxy() { proxy_.Unbind(); } |
| |
| std::optional<zx_status_t> proxy_epitaph() const { return proxy_epitaph_; } |
| |
| bool server_error_cb_called() const { return error_cb_called_; } |
| |
| private: |
| std::unique_ptr<Gatt2ClientServer> server_; |
| fbg::ClientPtr proxy_; |
| bool error_cb_called_ = false; |
| std::optional<zx_status_t> proxy_epitaph_; |
| |
| BT_DISALLOW_COPY_ASSIGN_AND_MOVE(Gatt2ClientServerTest); |
| }; |
| |
| TEST_F(Gatt2ClientServerTest, FidlClientClosingProxyCallsServerErrorCallback) { |
| UnbindProxy(); |
| RunLoopUntilIdle(); |
| EXPECT_TRUE(server_error_cb_called()); |
| } |
| |
| TEST_F(Gatt2ClientServerTest, WatchServicesListsServicesOnFirstRequestAndUpdatesOnSecondRequest) { |
| const bt::att::Handle kSvcStartHandle0(1); |
| const bt::att::Handle kSvcEndHandle0(kSvcStartHandle0); |
| bt::gatt::ServiceData svc_data_0(bt::gatt::ServiceKind::PRIMARY, kSvcStartHandle0, kSvcEndHandle0, |
| kTestServiceUuid0); |
| fake_gatt()->AddPeerService(kPeerId, svc_data_0); |
| |
| std::vector<fbg::ServiceInfo> updated; |
| std::vector<fbg::Handle> removed; |
| int watch_cb_count = 0; |
| proxy()->WatchServices(/*uuids=*/{}, [&](std::vector<fbg::ServiceInfo> cb_updated, |
| std::vector<fbg::Handle> cb_removed) { |
| watch_cb_count++; |
| updated = std::move(cb_updated); |
| removed = std::move(cb_removed); |
| }); |
| |
| RunLoopUntilIdle(); |
| EXPECT_EQ(watch_cb_count, 1); |
| EXPECT_EQ(removed.size(), 0u); |
| ASSERT_EQ(updated.size(), 1u); |
| ASSERT_TRUE(updated[0].has_handle()); |
| EXPECT_EQ(updated[0].handle().value, kSvcStartHandle0); |
| ASSERT_TRUE(updated[0].has_kind()); |
| EXPECT_EQ(updated[0].kind(), fbg::ServiceKind::PRIMARY); |
| ASSERT_TRUE(updated[0].has_type()); |
| EXPECT_EQ(bt::UUID(updated[0].type().value), kTestServiceUuid0); |
| EXPECT_FALSE(updated[0].has_characteristics()); |
| EXPECT_FALSE(updated[0].has_includes()); |
| |
| updated.clear(); |
| removed.clear(); |
| watch_cb_count = 0; |
| // WatchServices before service update. Request should be queued. |
| proxy()->WatchServices(/*uuids=*/{}, [&](std::vector<fbg::ServiceInfo> cb_updated, |
| std::vector<fbg::Handle> cb_removed) { |
| watch_cb_count++; |
| updated = std::move(cb_updated); |
| removed = std::move(cb_removed); |
| }); |
| |
| RunLoopUntilIdle(); |
| EXPECT_EQ(watch_cb_count, 0); |
| EXPECT_EQ(updated.size(), 0u); |
| EXPECT_EQ(removed.size(), 0u); |
| |
| const bt::att::Handle kSvcStartHandle1(2); |
| const bt::att::Handle kSvcEndHandle1(kSvcStartHandle1); |
| bt::gatt::ServiceData svc_data_1(bt::gatt::ServiceKind::PRIMARY, kSvcStartHandle1, kSvcEndHandle1, |
| kTestServiceUuid0); |
| fake_gatt()->AddPeerService(kPeerId, svc_data_1, /*notify=*/true); |
| |
| RunLoopUntilIdle(); |
| EXPECT_EQ(watch_cb_count, 1); |
| EXPECT_EQ(removed.size(), 0u); |
| EXPECT_EQ(updated.size(), 1u); |
| ASSERT_TRUE(updated[0].has_handle()); |
| EXPECT_EQ(updated[0].handle().value, kSvcStartHandle1); |
| ASSERT_TRUE(updated[0].has_kind()); |
| EXPECT_EQ(updated[0].kind(), fbg::ServiceKind::PRIMARY); |
| ASSERT_TRUE(updated[0].has_type()); |
| EXPECT_EQ(bt::UUID(updated[0].type().value), kTestServiceUuid0); |
| EXPECT_FALSE(updated[0].has_characteristics()); |
| EXPECT_FALSE(updated[0].has_includes()); |
| } |
| |
| TEST_F(Gatt2ClientServerTest, |
| WatchServicesWithUuidsListsServicesOnFirstRequestAndUpdatesOnSecondRequest) { |
| const bt::att::Handle kSvcStartHandle0(1); |
| const bt::att::Handle kSvcEndHandle0(kSvcStartHandle0); |
| bt::gatt::ServiceData svc_data_0(bt::gatt::ServiceKind::PRIMARY, kSvcStartHandle0, kSvcEndHandle0, |
| kTestServiceUuid0); |
| fake_gatt()->AddPeerService(kPeerId, svc_data_0); |
| |
| const bt::att::Handle kSvcStartHandle1(2); |
| const bt::att::Handle kSvcEndHandle1(kSvcStartHandle1); |
| bt::gatt::ServiceData svc_data_1(bt::gatt::ServiceKind::PRIMARY, kSvcStartHandle1, kSvcEndHandle1, |
| kTestServiceUuid1); |
| fake_gatt()->AddPeerService(kPeerId, svc_data_1); |
| |
| std::vector<fbg::ServiceInfo> updated; |
| std::vector<fbg::Handle> removed; |
| int watch_cb_count = 0; |
| proxy()->WatchServices( |
| /*uuids=*/{fb::Uuid{kTestServiceUuid0.value()}}, |
| [&](std::vector<fbg::ServiceInfo> cb_updated, std::vector<fbg::Handle> cb_removed) { |
| watch_cb_count++; |
| updated = std::move(cb_updated); |
| removed = std::move(cb_removed); |
| }); |
| |
| RunLoopUntilIdle(); |
| EXPECT_EQ(watch_cb_count, 1); |
| EXPECT_EQ(removed.size(), 0u); |
| ASSERT_EQ(updated.size(), 1u); |
| ASSERT_TRUE(updated[0].has_handle()); |
| EXPECT_EQ(updated[0].handle().value, kSvcStartHandle0); |
| |
| updated.clear(); |
| removed.clear(); |
| watch_cb_count = 0; |
| |
| // WatchServices before service update. Request should be queued. |
| proxy()->WatchServices( |
| /*uuids=*/{fb::Uuid{kTestServiceUuid0.value()}}, |
| [&](std::vector<fbg::ServiceInfo> cb_updated, std::vector<fbg::Handle> cb_removed) { |
| watch_cb_count++; |
| updated = std::move(cb_updated); |
| removed = std::move(cb_removed); |
| }); |
| |
| // Service should be ignored because UUID does not match. |
| const bt::att::Handle kSvcStartHandle2(3); |
| const bt::att::Handle kSvcEndHandle2(kSvcStartHandle2); |
| bt::gatt::ServiceData svc_data_2(bt::gatt::ServiceKind::PRIMARY, kSvcStartHandle2, kSvcEndHandle2, |
| kTestServiceUuid1); |
| fake_gatt()->AddPeerService(kPeerId, svc_data_2, /*notify=*/true); |
| RunLoopUntilIdle(); |
| EXPECT_EQ(watch_cb_count, 0); |
| EXPECT_EQ(updated.size(), 0u); |
| EXPECT_EQ(removed.size(), 0u); |
| |
| // Service UUID matches, so response should be received. |
| const bt::att::Handle kSvcStartHandle3(4); |
| const bt::att::Handle kSvcEndHandle3(kSvcStartHandle3); |
| bt::gatt::ServiceData svc_data_3(bt::gatt::ServiceKind::PRIMARY, kSvcStartHandle3, kSvcEndHandle3, |
| kTestServiceUuid0); |
| fake_gatt()->AddPeerService(kPeerId, svc_data_3, /*notify=*/true); |
| RunLoopUntilIdle(); |
| EXPECT_EQ(watch_cb_count, 1); |
| EXPECT_EQ(removed.size(), 0u); |
| EXPECT_EQ(updated.size(), 1u); |
| ASSERT_TRUE(updated[0].has_handle()); |
| EXPECT_EQ(updated[0].handle().value, kSvcStartHandle3); |
| } |
| |
| TEST_F(Gatt2ClientServerTest, |
| WatchServicesWithUuidsListsServicesOnFirstRequestAndListsAgainWhenUuidsChanged) { |
| const bt::att::Handle kSvcStartHandle0(1); |
| const bt::att::Handle kSvcEndHandle0(kSvcStartHandle0); |
| bt::gatt::ServiceData svc_data_0(bt::gatt::ServiceKind::PRIMARY, kSvcStartHandle0, kSvcEndHandle0, |
| kTestServiceUuid0); |
| fake_gatt()->AddPeerService(kPeerId, svc_data_0); |
| |
| const bt::att::Handle kSvcStartHandle1(2); |
| const bt::att::Handle kSvcEndHandle1(kSvcStartHandle1); |
| bt::gatt::ServiceData svc_data_1(bt::gatt::ServiceKind::PRIMARY, kSvcStartHandle1, kSvcEndHandle1, |
| kTestServiceUuid1); |
| fake_gatt()->AddPeerService(kPeerId, svc_data_1); |
| |
| std::vector<fbg::ServiceInfo> updated; |
| std::vector<fbg::Handle> removed; |
| int watch_cb_count = 0; |
| proxy()->WatchServices( |
| /*uuids=*/{fb::Uuid{kTestServiceUuid0.value()}}, |
| [&](std::vector<fbg::ServiceInfo> cb_updated, std::vector<fbg::Handle> cb_removed) { |
| watch_cb_count++; |
| updated = std::move(cb_updated); |
| removed = std::move(cb_removed); |
| }); |
| |
| RunLoopUntilIdle(); |
| EXPECT_EQ(watch_cb_count, 1); |
| EXPECT_EQ(removed.size(), 0u); |
| ASSERT_EQ(updated.size(), 1u); |
| ASSERT_TRUE(updated[0].has_handle()); |
| EXPECT_EQ(updated[0].handle().value, kSvcStartHandle0); |
| |
| updated.clear(); |
| removed.clear(); |
| watch_cb_count = 0; |
| |
| // WatchServices before service update. Request should be queued. |
| proxy()->WatchServices( |
| /*uuids=*/{fb::Uuid{kTestServiceUuid0.value()}}, |
| [&](std::vector<fbg::ServiceInfo> cb_updated, std::vector<fbg::Handle> cb_removed) { |
| watch_cb_count++; |
| updated = std::move(cb_updated); |
| removed = std::move(cb_removed); |
| }); |
| |
| // Service should be ignored because UUID does not match. |
| const bt::att::Handle kSvcStartHandle2(3); |
| const bt::att::Handle kSvcEndHandle2(kSvcStartHandle2); |
| bt::gatt::ServiceData svc_data_2(bt::gatt::ServiceKind::PRIMARY, kSvcStartHandle2, kSvcEndHandle2, |
| kTestServiceUuid1); |
| fake_gatt()->AddPeerService(kPeerId, svc_data_2, /*notify=*/true); |
| RunLoopUntilIdle(); |
| EXPECT_EQ(watch_cb_count, 0); |
| EXPECT_EQ(updated.size(), 0u); |
| EXPECT_EQ(removed.size(), 0u); |
| |
| // Changing the UUID matched means that the previous call completes, but with no new updates. |
| std::vector<fbg::ServiceInfo> updated_srv2; |
| std::vector<fbg::Handle> removed_srv2; |
| int watch_srv2_cb_count = 0; |
| proxy()->WatchServices( |
| /*uuids=*/{fb::Uuid{kTestServiceUuid1.value()}}, |
| [&](std::vector<fbg::ServiceInfo> cb_updated, std::vector<fbg::Handle> cb_removed) { |
| watch_srv2_cb_count++; |
| updated_srv2 = std::move(cb_updated); |
| removed_srv2 = std::move(cb_removed); |
| }); |
| |
| RunLoopUntilIdle(); |
| // Initial one should have been completed, with no new info. |
| EXPECT_EQ(watch_cb_count, 1); |
| EXPECT_EQ(updated.size(), 0u); |
| EXPECT_EQ(removed.size(), 0u); |
| watch_cb_count = 0; |
| |
| // Second service UUID completes with the services for the second UUID |
| EXPECT_EQ(watch_srv2_cb_count, 1); |
| EXPECT_EQ(updated_srv2.size(), 2u); |
| EXPECT_EQ(removed_srv2.size(), 0u); |
| watch_srv2_cb_count = 0; |
| updated_srv2.clear(); |
| |
| // Service UUID matches initial search, which is finished, so no response should be received. |
| const bt::att::Handle kSvcStartHandle3(4); |
| const bt::att::Handle kSvcEndHandle3(kSvcStartHandle3); |
| bt::gatt::ServiceData svc_data_3(bt::gatt::ServiceKind::PRIMARY, kSvcStartHandle3, kSvcEndHandle3, |
| kTestServiceUuid0); |
| fake_gatt()->AddPeerService(kPeerId, svc_data_3, /*notify=*/true); |
| RunLoopUntilIdle(); |
| EXPECT_EQ(watch_cb_count, 0); |
| EXPECT_EQ(watch_srv2_cb_count, 0); |
| |
| // Queueing up a new srv2 call should not reply immediately. |
| proxy()->WatchServices( |
| /*uuids=*/{fb::Uuid{kTestServiceUuid1.value()}}, |
| [&](std::vector<fbg::ServiceInfo> cb_updated, std::vector<fbg::Handle> cb_removed) { |
| watch_srv2_cb_count++; |
| updated_srv2 = std::move(cb_updated); |
| removed_srv2 = std::move(cb_removed); |
| }); |
| |
| RunLoopUntilIdle(); |
| EXPECT_EQ(watch_srv2_cb_count, 0); |
| |
| // Switching back to the original UUID at this point re-lists all the services again, and the |
| // second search finishes with an empty response as the end. |
| proxy()->WatchServices( |
| /*uuids=*/{fb::Uuid{kTestServiceUuid0.value()}}, |
| [&](std::vector<fbg::ServiceInfo> cb_updated, std::vector<fbg::Handle> cb_removed) { |
| watch_cb_count++; |
| updated = std::move(cb_updated); |
| removed = std::move(cb_removed); |
| }); |
| |
| RunLoopUntilIdle(); |
| EXPECT_EQ(watch_cb_count, 1); |
| EXPECT_EQ(removed.size(), 0u); |
| EXPECT_EQ(updated.size(), 2u); |
| |
| EXPECT_EQ(watch_srv2_cb_count, 1); |
| EXPECT_EQ(removed_srv2.size(), 0u); |
| EXPECT_EQ(updated_srv2.size(), 0u); |
| } |
| |
| TEST_F(Gatt2ClientServerTest, ServiceWatcherResultsIgnoredBeforeWatchServices) { |
| const bt::att::Handle kSvcStartHandle0(1); |
| const bt::att::Handle kSvcEndHandle0(kSvcStartHandle0); |
| bt::gatt::ServiceData svc_data_0(bt::gatt::ServiceKind::PRIMARY, kSvcStartHandle0, kSvcEndHandle0, |
| kTestServiceUuid0); |
| |
| const bt::att::Handle kSvcStartHandle1(2); |
| const bt::att::Handle kSvcEndHandle1(kSvcStartHandle1); |
| bt::gatt::ServiceData svc_data_1(bt::gatt::ServiceKind::PRIMARY, kSvcStartHandle1, kSvcEndHandle1, |
| kTestServiceUuid0); |
| |
| // Notifications should be ignored. |
| fake_gatt()->AddPeerService(kPeerId, svc_data_0, /*notify=*/true); |
| fake_gatt()->RemovePeerService(kPeerId, kSvcStartHandle0); |
| fake_gatt()->AddPeerService(kPeerId, svc_data_1, /*notify=*/true); |
| |
| std::vector<fbg::ServiceInfo> updated; |
| std::vector<fbg::Handle> removed; |
| int watch_cb_count = 0; |
| proxy()->WatchServices(/*uuids=*/{}, [&](std::vector<fbg::ServiceInfo> cb_updated, |
| std::vector<fbg::Handle> cb_removed) { |
| watch_cb_count++; |
| updated = std::move(cb_updated); |
| removed = std::move(cb_removed); |
| }); |
| |
| RunLoopUntilIdle(); |
| EXPECT_EQ(watch_cb_count, 1); |
| // Since removal notification was ignored, the removed list should be empty. |
| EXPECT_EQ(removed.size(), 0u); |
| ASSERT_EQ(updated.size(), 1u); |
| ASSERT_TRUE(updated[0].has_handle()); |
| EXPECT_EQ(updated[0].handle().value, kSvcStartHandle1); |
| } |
| |
| TEST_F(Gatt2ClientServerTest, RemoveConnectedServiceClosesRemoteServiceAndNotifiesServiceWatcher) { |
| const bt::att::Handle kSvcStartHandle0(1); |
| const bt::att::Handle kSvcEndHandle0(kSvcStartHandle0); |
| bt::gatt::ServiceData svc_data_0(bt::gatt::ServiceKind::PRIMARY, kSvcStartHandle0, kSvcEndHandle0, |
| kTestServiceUuid0); |
| fake_gatt()->AddPeerService(kPeerId, svc_data_0); |
| |
| std::vector<fbg::ServiceInfo> updated; |
| std::vector<fbg::Handle> removed; |
| int watch_cb_count = 0; |
| proxy()->WatchServices(/*uuids=*/{}, [&](std::vector<fbg::ServiceInfo> cb_updated, |
| std::vector<fbg::Handle> cb_removed) { |
| watch_cb_count++; |
| updated = std::move(cb_updated); |
| removed = std::move(cb_removed); |
| }); |
| |
| RunLoopUntilIdle(); |
| EXPECT_EQ(watch_cb_count, 1); |
| EXPECT_EQ(removed.size(), 0u); |
| ASSERT_EQ(updated.size(), 1u); |
| ASSERT_TRUE(updated[0].has_handle()); |
| EXPECT_EQ(updated[0].handle().value, kSvcStartHandle0); |
| |
| fbg::RemoteServicePtr service_ptr; |
| proxy()->ConnectToService(updated[0].handle(), service_ptr.NewRequest()); |
| std::optional<zx_status_t> service_error; |
| service_ptr.set_error_handler([&](zx_status_t status) { service_error = status; }); |
| |
| RunLoopUntilIdle(); |
| EXPECT_FALSE(service_error); |
| |
| fake_gatt()->RemovePeerService(kPeerId, kSvcStartHandle0); |
| |
| updated.clear(); |
| removed.clear(); |
| watch_cb_count = 0; |
| proxy()->WatchServices(/*uuids=*/{}, [&](std::vector<fbg::ServiceInfo> cb_updated, |
| std::vector<fbg::Handle> cb_removed) { |
| watch_cb_count++; |
| updated = std::move(cb_updated); |
| removed = std::move(cb_removed); |
| }); |
| RunLoopUntilIdle(); |
| EXPECT_EQ(watch_cb_count, 1); |
| EXPECT_EQ(removed.size(), 1u); |
| ASSERT_EQ(updated.size(), 0u); |
| EXPECT_EQ(removed[0].value, kSvcStartHandle0); |
| ASSERT_TRUE(service_error.has_value()); |
| EXPECT_EQ(service_error.value(), ZX_ERR_CONNECTION_RESET); |
| } |
| |
| TEST_F(Gatt2ClientServerTest, ModifiedService) { |
| const bt::att::Handle kSvcStartHandle0(1); |
| const bt::att::Handle kSvcEndHandle0(kSvcStartHandle0); |
| bt::gatt::ServiceData svc_data_0(bt::gatt::ServiceKind::PRIMARY, kSvcStartHandle0, kSvcEndHandle0, |
| kTestServiceUuid0); |
| fake_gatt()->AddPeerService(kPeerId, svc_data_0); |
| |
| std::vector<fbg::ServiceInfo> updated; |
| std::vector<fbg::Handle> removed; |
| int watch_cb_count = 0; |
| proxy()->WatchServices(/*uuids=*/{}, [&](std::vector<fbg::ServiceInfo> cb_updated, |
| std::vector<fbg::Handle> cb_removed) { |
| watch_cb_count++; |
| updated = std::move(cb_updated); |
| removed = std::move(cb_removed); |
| }); |
| RunLoopUntilIdle(); |
| EXPECT_EQ(watch_cb_count, 1); |
| EXPECT_EQ(removed.size(), 0u); |
| ASSERT_EQ(updated.size(), 1u); |
| ASSERT_TRUE(updated[0].has_handle()); |
| EXPECT_EQ(updated[0].handle().value, kSvcStartHandle0); |
| |
| updated.clear(); |
| removed.clear(); |
| watch_cb_count = 0; |
| |
| proxy()->WatchServices(/*uuids=*/{}, [&](std::vector<fbg::ServiceInfo> cb_updated, |
| std::vector<fbg::Handle> cb_removed) { |
| watch_cb_count++; |
| updated = std::move(cb_updated); |
| removed = std::move(cb_removed); |
| }); |
| RunLoopUntilIdle(); |
| EXPECT_EQ(watch_cb_count, 0); |
| |
| // Adding same service will send "modified" service to service watcher. |
| fake_gatt()->AddPeerService(kPeerId, svc_data_0, /*notify=*/true); |
| |
| RunLoopUntilIdle(); |
| EXPECT_EQ(watch_cb_count, 1); |
| EXPECT_EQ(removed.size(), 0u); |
| ASSERT_EQ(updated.size(), 1u); |
| ASSERT_TRUE(updated[0].has_handle()); |
| EXPECT_EQ(updated[0].handle().value, kSvcStartHandle0); |
| } |
| |
| TEST_F(Gatt2ClientServerTest, ReplacedService) { |
| const bt::att::Handle kSvcStartHandle0(1); |
| const bt::att::Handle kSvcEndHandle0(kSvcStartHandle0); |
| bt::gatt::ServiceData svc_data_0(bt::gatt::ServiceKind::PRIMARY, kSvcStartHandle0, kSvcEndHandle0, |
| kTestServiceUuid0); |
| fake_gatt()->AddPeerService(kPeerId, svc_data_0); |
| |
| std::vector<fbg::ServiceInfo> updated; |
| std::vector<fbg::Handle> removed; |
| int watch_cb_count = 0; |
| proxy()->WatchServices(/*uuids=*/{}, [&](std::vector<fbg::ServiceInfo> cb_updated, |
| std::vector<fbg::Handle> cb_removed) { |
| watch_cb_count++; |
| updated = std::move(cb_updated); |
| removed = std::move(cb_removed); |
| }); |
| RunLoopUntilIdle(); |
| EXPECT_EQ(watch_cb_count, 1); |
| EXPECT_EQ(removed.size(), 0u); |
| ASSERT_EQ(updated.size(), 1u); |
| ASSERT_TRUE(updated[0].has_handle()); |
| EXPECT_EQ(updated[0].handle().value, kSvcStartHandle0); |
| |
| updated.clear(); |
| removed.clear(); |
| watch_cb_count = 0; |
| |
| proxy()->WatchServices(/*uuids=*/{}, [&](std::vector<fbg::ServiceInfo> cb_updated, |
| std::vector<fbg::Handle> cb_removed) { |
| watch_cb_count++; |
| updated = std::move(cb_updated); |
| removed = std::move(cb_removed); |
| }); |
| RunLoopUntilIdle(); |
| EXPECT_EQ(watch_cb_count, 0); |
| |
| // Adding a service with the same handle but different type will send "removed" + "added" services |
| // to service watcher. |
| bt::gatt::ServiceData svc_data_1(bt::gatt::ServiceKind::PRIMARY, kSvcStartHandle0, kSvcEndHandle0, |
| kTestServiceUuid1); |
| fake_gatt()->AddPeerService(kPeerId, svc_data_1, /*notify=*/true); |
| |
| RunLoopUntilIdle(); |
| EXPECT_EQ(watch_cb_count, 1); |
| ASSERT_EQ(removed.size(), 1u); |
| ASSERT_EQ(updated.size(), 1u); |
| EXPECT_EQ(removed[0].value, kSvcStartHandle0); |
| ASSERT_TRUE(updated[0].has_handle()); |
| EXPECT_EQ(updated[0].handle().value, kSvcStartHandle0); |
| ASSERT_TRUE(updated[0].has_type()); |
| EXPECT_EQ(bt::UUID(updated[0].type().value), kTestServiceUuid1); |
| } |
| |
| // When a service is added and removed between calls to WatchServices, only the removed handle is |
| // sent in the response. |
| TEST_F(Gatt2ClientServerTest, ServiceAddedFollowedByServiceRemovedBetweenWatchServices) { |
| const bt::att::Handle kSvcStartHandle0(1); |
| const bt::att::Handle kSvcEndHandle0(kSvcStartHandle0); |
| bt::gatt::ServiceData svc_data_0(bt::gatt::ServiceKind::PRIMARY, kSvcStartHandle0, kSvcEndHandle0, |
| kTestServiceUuid0); |
| fake_gatt()->AddPeerService(kPeerId, svc_data_0); |
| |
| int watch_cb_count = 0; |
| proxy()->WatchServices(/*uuids=*/{}, [&](auto, auto) { watch_cb_count++; }); |
| RunLoopUntilIdle(); |
| EXPECT_EQ(watch_cb_count, 1); |
| |
| const bt::att::Handle kSvcStartHandle1(2); |
| const bt::att::Handle kSvcEndHandle1(kSvcStartHandle1); |
| bt::gatt::ServiceData svc_data_1(bt::gatt::ServiceKind::PRIMARY, kSvcStartHandle1, kSvcEndHandle1, |
| kTestServiceUuid1); |
| fake_gatt()->AddPeerService(kPeerId, svc_data_1); |
| fake_gatt()->RemovePeerService(kPeerId, kSvcStartHandle1); |
| RunLoopUntilIdle(); |
| |
| std::vector<fbg::ServiceInfo> updated; |
| std::vector<fbg::Handle> removed; |
| watch_cb_count = 0; |
| proxy()->WatchServices(/*uuids=*/{}, [&](std::vector<fbg::ServiceInfo> cb_updated, |
| std::vector<fbg::Handle> cb_removed) { |
| watch_cb_count++; |
| updated = std::move(cb_updated); |
| removed = std::move(cb_removed); |
| }); |
| |
| RunLoopUntilIdle(); |
| EXPECT_EQ(watch_cb_count, 1); |
| EXPECT_EQ(removed.size(), 1u); |
| ASSERT_EQ(updated.size(), 0u); |
| EXPECT_EQ(removed[0].value, kSvcStartHandle1); |
| } |
| |
| TEST_F(Gatt2ClientServerTest, WatchServicesCalledTwiceClosesServer) { |
| // Prevent GATT::ListServices() from completing so that we can queue a second WatchServices |
| // request. |
| fake_gatt()->stop_list_services(); |
| |
| int watch_cb_count_0 = 0; |
| int watch_cb_count_1 = 0; |
| proxy()->WatchServices(/*uuids=*/{}, [&](auto, auto) { watch_cb_count_0++; }); |
| proxy()->WatchServices(/*uuids=*/{}, [&](auto, auto) { watch_cb_count_1++; }); |
| RunLoopUntilIdle(); |
| EXPECT_EQ(watch_cb_count_0, 0); |
| EXPECT_EQ(watch_cb_count_1, 0); |
| EXPECT_TRUE(server_error_cb_called()); |
| ASSERT_TRUE(proxy_epitaph()); |
| EXPECT_EQ(proxy_epitaph().value(), ZX_ERR_ALREADY_BOUND); |
| } |
| |
| TEST_F(Gatt2ClientServerTest, ListServicesFails) { |
| const bt::att::Handle kSvcStartHandle0(1); |
| const bt::att::Handle kSvcEndHandle0(kSvcStartHandle0); |
| bt::gatt::ServiceData svc_data(bt::gatt::ServiceKind::PRIMARY, kSvcStartHandle0, kSvcEndHandle0, |
| kTestServiceUuid0); |
| fake_gatt()->AddPeerService(kPeerId, svc_data); |
| |
| fake_gatt()->set_list_services_status(bt::ToResult(bt::HostError::kPacketMalformed)); |
| |
| int watch_cb_count = 0; |
| proxy()->WatchServices(/*uuids=*/{}, [&](auto, auto) { watch_cb_count++; }); |
| RunLoopUntilIdle(); |
| EXPECT_EQ(watch_cb_count, 0); |
| EXPECT_TRUE(server_error_cb_called()); |
| ASSERT_TRUE(proxy_epitaph()); |
| EXPECT_EQ(proxy_epitaph().value(), ZX_ERR_CONNECTION_RESET); |
| } |
| |
| TEST_F(Gatt2ClientServerTest, ConnectToServiceInvalidHandle) { |
| fbg::ServiceHandle invalid_handle{static_cast<uint64_t>(bt::att::kHandleMax) + 1}; |
| fbg::RemoteServicePtr service_ptr; |
| proxy()->ConnectToService(invalid_handle, service_ptr.NewRequest()); |
| |
| std::optional<zx_status_t> service_epitaph; |
| service_ptr.set_error_handler([&](zx_status_t epitaph) { service_epitaph = epitaph; }); |
| |
| RunLoopUntilIdle(); |
| ASSERT_TRUE(service_epitaph); |
| EXPECT_EQ(service_epitaph.value(), ZX_ERR_INVALID_ARGS); |
| } |
| |
| TEST_F(Gatt2ClientServerTest, ConnectToServiceServiceAlreadyConnected) { |
| const bt::att::Handle kSvcStartHandle0(1); |
| const bt::att::Handle kSvcEndHandle0(kSvcStartHandle0); |
| bt::gatt::ServiceData svc_data(bt::gatt::ServiceKind::PRIMARY, kSvcStartHandle0, kSvcEndHandle0, |
| kTestServiceUuid0); |
| fake_gatt()->AddPeerService(kPeerId, svc_data); |
| |
| fbg::RemoteServicePtr service_ptr_0; |
| proxy()->ConnectToService(fbg::ServiceHandle{kSvcStartHandle0}, service_ptr_0.NewRequest()); |
| |
| std::optional<zx_status_t> service_epitaph_0; |
| service_ptr_0.set_error_handler([&](zx_status_t epitaph) { service_epitaph_0 = epitaph; }); |
| |
| RunLoopUntilIdle(); |
| EXPECT_FALSE(service_epitaph_0); |
| |
| fbg::RemoteServicePtr service_ptr_1; |
| proxy()->ConnectToService(fbg::ServiceHandle{kSvcStartHandle0}, service_ptr_1.NewRequest()); |
| |
| std::optional<zx_status_t> service_epitaph_1; |
| service_ptr_1.set_error_handler([&](zx_status_t epitaph) { service_epitaph_1 = epitaph; }); |
| |
| RunLoopUntilIdle(); |
| EXPECT_FALSE(service_epitaph_0); |
| ASSERT_TRUE(service_epitaph_1); |
| EXPECT_EQ(service_epitaph_1.value(), ZX_ERR_ALREADY_EXISTS); |
| } |
| |
| TEST_F(Gatt2ClientServerTest, ConnectToServiceNotFoundThenConnectToServiceWithSameHandleSucceeds) { |
| const bt::att::Handle kSvcStartHandle0(1); |
| const bt::att::Handle kSvcEndHandle0(kSvcStartHandle0); |
| |
| fbg::RemoteServicePtr service_ptr_0; |
| proxy()->ConnectToService(fbg::ServiceHandle{kSvcStartHandle0}, service_ptr_0.NewRequest()); |
| |
| std::optional<zx_status_t> service_epitaph_0; |
| service_ptr_0.set_error_handler([&](zx_status_t epitaph) { service_epitaph_0 = epitaph; }); |
| |
| RunLoopUntilIdle(); |
| ASSERT_TRUE(service_epitaph_0); |
| EXPECT_EQ(service_epitaph_0.value(), ZX_ERR_NOT_FOUND); |
| |
| // Add a service with the same handle as the service that was previously not found. |
| bt::gatt::ServiceData svc_data(bt::gatt::ServiceKind::PRIMARY, kSvcStartHandle0, kSvcEndHandle0, |
| kTestServiceUuid0); |
| fake_gatt()->AddPeerService(kPeerId, svc_data); |
| |
| // Connecting to the service after it is added should succeed. |
| fbg::RemoteServicePtr service_ptr_1; |
| proxy()->ConnectToService(fbg::ServiceHandle{kSvcStartHandle0}, service_ptr_1.NewRequest()); |
| |
| std::optional<zx_status_t> service_epitaph_1; |
| service_ptr_1.set_error_handler([&](zx_status_t epitaph) { service_epitaph_1 = epitaph; }); |
| |
| RunLoopUntilIdle(); |
| EXPECT_FALSE(service_epitaph_1); |
| } |
| |
| TEST_F(Gatt2ClientServerTest, ClientClosesRemoteServiceAndReconnectsFollowedByServiceRemoved) { |
| const bt::att::Handle kSvcStartHandle0(1); |
| const bt::att::Handle kSvcEndHandle0(kSvcStartHandle0); |
| bt::gatt::ServiceData svc_data(bt::gatt::ServiceKind::PRIMARY, kSvcStartHandle0, kSvcEndHandle0, |
| kTestServiceUuid0); |
| fake_gatt()->AddPeerService(kPeerId, svc_data); |
| |
| fbg::RemoteServicePtr service_ptr_0; |
| proxy()->ConnectToService(fbg::ServiceHandle{kSvcStartHandle0}, service_ptr_0.NewRequest()); |
| |
| std::optional<zx_status_t> service_epitaph_0; |
| service_ptr_0.set_error_handler([&](zx_status_t epitaph) { service_epitaph_0 = epitaph; }); |
| |
| RunLoopUntilIdle(); |
| EXPECT_FALSE(service_epitaph_0); |
| |
| service_ptr_0.Unbind(); |
| |
| fbg::RemoteServicePtr service_ptr_1; |
| proxy()->ConnectToService(fbg::ServiceHandle{kSvcStartHandle0}, service_ptr_1.NewRequest()); |
| |
| std::optional<zx_status_t> service_epitaph_1; |
| service_ptr_1.set_error_handler([&](zx_status_t epitaph) { service_epitaph_1 = epitaph; }); |
| |
| RunLoopUntilIdle(); |
| EXPECT_FALSE(service_epitaph_1); |
| |
| // Server should not crash when both service removed handlers are called (one was registered for |
| // each call to ConnectToService). The second handler should do nothing. |
| fake_gatt()->RemovePeerService(kPeerId, kSvcStartHandle0); |
| RunLoopUntilIdle(); |
| ASSERT_TRUE(service_epitaph_1); |
| EXPECT_EQ(service_epitaph_1.value(), ZX_ERR_CONNECTION_RESET); |
| } |
| |
| // The service removed handler should gracefully handle being called after the service has already |
| // been removed from the FIDL server. |
| TEST_F(Gatt2ClientServerTest, ClientClosesRemoteServiceFollowedByServiceRemoved) { |
| const bt::att::Handle kSvcStartHandle0(1); |
| const bt::att::Handle kSvcEndHandle0(kSvcStartHandle0); |
| bt::gatt::ServiceData svc_data(bt::gatt::ServiceKind::PRIMARY, kSvcStartHandle0, kSvcEndHandle0, |
| kTestServiceUuid0); |
| fake_gatt()->AddPeerService(kPeerId, svc_data); |
| |
| fbg::RemoteServicePtr service_ptr_0; |
| proxy()->ConnectToService(fbg::ServiceHandle{kSvcStartHandle0}, service_ptr_0.NewRequest()); |
| |
| std::optional<zx_status_t> service_epitaph_0; |
| service_ptr_0.set_error_handler([&](zx_status_t epitaph) { service_epitaph_0 = epitaph; }); |
| |
| RunLoopUntilIdle(); |
| EXPECT_FALSE(service_epitaph_0); |
| |
| // Unbinding the client end should remove the service from the server's map. |
| service_ptr_0.Unbind(); |
| RunLoopUntilIdle(); |
| |
| // Server should not crash when service removed handler is called and finds that the service isn't |
| // in the server's map. |
| fake_gatt()->RemovePeerService(kPeerId, kSvcStartHandle0); |
| RunLoopUntilIdle(); |
| } |
| |
| TEST_F(Gatt2ClientServerTest, |
| WatchServicesWithDifferentUuidsBetweenFirstAndSecondRequestListsServicesOnSecondRequest) { |
| const bt::att::Handle kSvcStartHandle0(1); |
| const bt::att::Handle kSvcEndHandle0(kSvcStartHandle0); |
| bt::gatt::ServiceData svc_data_0(bt::gatt::ServiceKind::PRIMARY, kSvcStartHandle0, kSvcEndHandle0, |
| kTestServiceUuid0); |
| fake_gatt()->AddPeerService(kPeerId, svc_data_0); |
| |
| const bt::att::Handle kSvcStartHandle1(2); |
| const bt::att::Handle kSvcEndHandle1(kSvcStartHandle1); |
| bt::gatt::ServiceData svc_data_1(bt::gatt::ServiceKind::PRIMARY, kSvcStartHandle1, kSvcEndHandle1, |
| kTestServiceUuid1); |
| fake_gatt()->AddPeerService(kPeerId, svc_data_1); |
| |
| std::vector<fbg::ServiceInfo> updated; |
| std::vector<fbg::Handle> removed; |
| int watch_cb_count = 0; |
| proxy()->WatchServices( |
| /*uuids=*/{fb::Uuid{kTestServiceUuid0.value()}}, |
| [&](std::vector<fbg::ServiceInfo> cb_updated, std::vector<fbg::Handle> cb_removed) { |
| watch_cb_count++; |
| updated = std::move(cb_updated); |
| removed = std::move(cb_removed); |
| }); |
| |
| RunLoopUntilIdle(); |
| EXPECT_EQ(watch_cb_count, 1); |
| EXPECT_EQ(removed.size(), 0u); |
| ASSERT_EQ(updated.size(), 1u); |
| ASSERT_TRUE(updated[0].has_handle()); |
| EXPECT_EQ(updated[0].handle().value, kSvcStartHandle0); |
| |
| updated.clear(); |
| removed.clear(); |
| watch_cb_count = 0; |
| |
| // Service with UUID not in next WatchServices() UUID list should be ignored. |
| const bt::att::Handle kSvcStartHandle2(3); |
| const bt::att::Handle kSvcEndHandle2(kSvcStartHandle2); |
| bt::gatt::ServiceData svc_data_2(bt::gatt::ServiceKind::PRIMARY, kSvcStartHandle2, kSvcEndHandle2, |
| kTestServiceUuid3); |
| fake_gatt()->AddPeerService(kPeerId, svc_data_2); |
| |
| // UUIDs changed, so WatchServices() should immediately receive a response with all existing |
| // services that match UUIDs. |
| proxy()->WatchServices( |
| /*uuids=*/{fb::Uuid{kTestServiceUuid0.value()}, fb::Uuid{kTestServiceUuid1.value()}}, |
| [&](std::vector<fbg::ServiceInfo> cb_updated, std::vector<fbg::Handle> cb_removed) { |
| watch_cb_count++; |
| updated = std::move(cb_updated); |
| removed = std::move(cb_removed); |
| }); |
| RunLoopUntilIdle(); |
| EXPECT_EQ(watch_cb_count, 1); |
| EXPECT_EQ(removed.size(), 0u); |
| EXPECT_EQ(updated.size(), 2u); |
| ASSERT_TRUE(updated[0].has_handle()); |
| ASSERT_TRUE(updated[1].has_handle()); |
| std::sort(updated.begin(), updated.end(), [](fbg::ServiceInfo& a, fbg::ServiceInfo& b) { |
| return a.handle().value < b.handle().value; |
| }); |
| EXPECT_EQ(updated[0].handle().value, kSvcStartHandle0); |
| EXPECT_EQ(updated[1].handle().value, kSvcStartHandle1); |
| } |
| |
| TEST_F( |
| Gatt2ClientServerTest, |
| WatchServicesWithReorderedUuidsBetweenFirstAndSecondRequestDoesNotListsServicesOnSecondRequest) { |
| const bt::att::Handle kSvcStartHandle0(1); |
| const bt::att::Handle kSvcEndHandle0(kSvcStartHandle0); |
| bt::gatt::ServiceData svc_data_0(bt::gatt::ServiceKind::PRIMARY, kSvcStartHandle0, kSvcEndHandle0, |
| kTestServiceUuid0); |
| fake_gatt()->AddPeerService(kPeerId, svc_data_0); |
| |
| const bt::att::Handle kSvcStartHandle1(2); |
| const bt::att::Handle kSvcEndHandle1(kSvcStartHandle1); |
| bt::gatt::ServiceData svc_data_1(bt::gatt::ServiceKind::PRIMARY, kSvcStartHandle1, kSvcEndHandle1, |
| kTestServiceUuid1); |
| fake_gatt()->AddPeerService(kPeerId, svc_data_1); |
| |
| std::vector<fbg::ServiceInfo> updated; |
| std::vector<fbg::Handle> removed; |
| int watch_cb_count = 0; |
| proxy()->WatchServices( |
| /*uuids=*/{fb::Uuid{kTestServiceUuid0.value()}, fb::Uuid{kTestServiceUuid1.value()}}, |
| [&](std::vector<fbg::ServiceInfo> cb_updated, std::vector<fbg::Handle> cb_removed) { |
| watch_cb_count++; |
| updated = std::move(cb_updated); |
| removed = std::move(cb_removed); |
| }); |
| |
| RunLoopUntilIdle(); |
| EXPECT_EQ(watch_cb_count, 1); |
| EXPECT_EQ(removed.size(), 0u); |
| EXPECT_EQ(updated.size(), 2u); |
| |
| updated.clear(); |
| removed.clear(); |
| watch_cb_count = 0; |
| |
| // UUIDs order changed but set of UUIDs is the same, so WatchServices() should not receive a |
| // response. |
| proxy()->WatchServices( |
| /*uuids=*/{fb::Uuid{kTestServiceUuid1.value()}, fb::Uuid{kTestServiceUuid0.value()}}, |
| [&](std::vector<fbg::ServiceInfo> cb_updated, std::vector<fbg::Handle> cb_removed) { |
| watch_cb_count++; |
| updated = std::move(cb_updated); |
| removed = std::move(cb_removed); |
| }); |
| RunLoopUntilIdle(); |
| EXPECT_EQ(watch_cb_count, 0); |
| } |
| |
| } // namespace |
| |
| } // namespace bthost |