| // Copyright 2020 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "src/connectivity/bluetooth/core/bt-host/gap/low_energy_interrogator.h" |
| |
| #include <lib/async/default.h> |
| |
| #include "src/connectivity/bluetooth/core/bt-host/common/test_helpers.h" |
| #include "src/connectivity/bluetooth/core/bt-host/data/fake_domain.h" |
| #include "src/connectivity/bluetooth/core/bt-host/gap/peer_cache.h" |
| #include "src/connectivity/bluetooth/core/bt-host/hci/hci.h" |
| #include "src/connectivity/bluetooth/core/bt-host/hci/status.h" |
| #include "src/connectivity/bluetooth/core/bt-host/l2cap/l2cap.h" |
| #include "src/connectivity/bluetooth/core/bt-host/testing/fake_controller_test.h" |
| #include "src/connectivity/bluetooth/core/bt-host/testing/test_controller.h" |
| #include "src/connectivity/bluetooth/core/bt-host/testing/test_packets.h" |
| |
| namespace bt::gap { |
| |
| constexpr hci::ConnectionHandle kConnectionHandle = 0x0BAA; |
| const DeviceAddress kTestDevAddr(DeviceAddress::Type::kLERandom, {1}); |
| |
| using bt::testing::CommandTransaction; |
| |
| const auto kReadRemoteVersionInfoRsp = |
| testing::CommandStatusPacket(hci::kReadRemoteVersionInfo, hci::StatusCode::kSuccess); |
| const auto kLEReadRemoteFeaturesRsp = |
| testing::CommandStatusPacket(hci::kLEReadRemoteFeatures, hci::StatusCode::kSuccess); |
| |
| using TestingBase = bt::testing::FakeControllerTest<bt::testing::TestController>; |
| |
| class LowEnergyInterrogatorTest : public TestingBase { |
| public: |
| LowEnergyInterrogatorTest() = default; |
| ~LowEnergyInterrogatorTest() override = default; |
| |
| void SetUp() override { |
| TestingBase::SetUp(); |
| |
| peer_cache_ = |
| std::make_unique<PeerCache>(inspector_.GetRoot().CreateChild(PeerCache::kInspectNodeName)); |
| auto hci = transport(); |
| |
| interrogator_ = std::make_unique<LowEnergyInterrogator>(peer_cache_.get(), hci, |
| async_get_default_dispatcher()); |
| |
| StartTestDevice(); |
| } |
| |
| void TearDown() override { |
| RunLoopUntilIdle(); |
| test_device()->Stop(); |
| interrogator_ = nullptr; |
| peer_cache_ = nullptr; |
| TestingBase::TearDown(); |
| } |
| |
| protected: |
| void QueueSuccessfulInterrogation(hci::ConnectionHandle conn, |
| hci::LESupportedFeatures features = {0}) const { |
| const auto remote_version_complete_packet = testing::ReadRemoteVersionInfoCompletePacket(conn); |
| const auto le_remote_features_complete_packet = |
| testing::LEReadRemoteFeaturesCompletePacket(conn, features); |
| |
| test_device()->QueueCommandTransaction( |
| CommandTransaction(testing::ReadRemoteVersionInfoPacket(conn), |
| {&kReadRemoteVersionInfoRsp, &remote_version_complete_packet})); |
| test_device()->QueueCommandTransaction( |
| CommandTransaction(testing::LEReadRemoteFeaturesPacket(conn), |
| {&kLEReadRemoteFeaturesRsp, &le_remote_features_complete_packet})); |
| } |
| |
| PeerCache* peer_cache() const { return peer_cache_.get(); } |
| |
| LowEnergyInterrogator* interrogator() const { return interrogator_.get(); } |
| |
| private: |
| inspect::Inspector inspector_; |
| std::unique_ptr<PeerCache> peer_cache_; |
| std::unique_ptr<LowEnergyInterrogator> interrogator_; |
| |
| DISALLOW_COPY_AND_ASSIGN_ALLOW_MOVE(LowEnergyInterrogatorTest); |
| }; |
| |
| using GAP_LowEnergyInterrogatorTest = LowEnergyInterrogatorTest; |
| |
| TEST_F(GAP_LowEnergyInterrogatorTest, SuccessfulInterrogation) { |
| const hci::LESupportedFeatures kFeatures{0x0123456789abcdef}; |
| QueueSuccessfulInterrogation(kConnectionHandle, kFeatures); |
| |
| auto* peer = peer_cache()->NewPeer(kTestDevAddr, true); |
| ASSERT_TRUE(peer->le()); |
| EXPECT_FALSE(peer->version()); |
| EXPECT_FALSE(peer->le()->features()); |
| |
| std::optional<hci::Status> status; |
| interrogator()->Start(peer->identifier(), kConnectionHandle, |
| [&status](hci::Status cb_status) { status = cb_status; }); |
| RunLoopUntilIdle(); |
| |
| ASSERT_TRUE(status.has_value()); |
| EXPECT_TRUE(status->is_success()); |
| |
| EXPECT_TRUE(peer->version()); |
| ASSERT_TRUE(peer->le()->features()); |
| EXPECT_EQ(kFeatures.le_features, peer->le()->features()->le_features); |
| } |
| |
| TEST_F(GAP_LowEnergyInterrogatorTest, SuccessfulInterrogationPeerAlreadyHasLEFeatures) { |
| const hci::LESupportedFeatures kFeatures{0x0123456789abcdef}; |
| |
| const auto remote_version_complete_packet = |
| testing::ReadRemoteVersionInfoCompletePacket(kConnectionHandle); |
| test_device()->QueueCommandTransaction( |
| CommandTransaction(testing::ReadRemoteVersionInfoPacket(kConnectionHandle), |
| {&kReadRemoteVersionInfoRsp, &remote_version_complete_packet})); |
| |
| auto* peer = peer_cache()->NewPeer(kTestDevAddr, true); |
| ASSERT_TRUE(peer->le()); |
| EXPECT_FALSE(peer->le()->features()); |
| peer->MutLe().SetFeatures(kFeatures); |
| EXPECT_TRUE(peer->le()->features()); |
| |
| std::optional<hci::Status> status; |
| interrogator()->Start(peer->identifier(), kConnectionHandle, |
| [&status](hci::Status cb_status) { status = cb_status; }); |
| RunLoopUntilIdle(); |
| ASSERT_TRUE(status.has_value()); |
| EXPECT_TRUE(status->is_success()); |
| ASSERT_TRUE(peer->le()->features()); |
| EXPECT_EQ(kFeatures.le_features, peer->le()->features()->le_features); |
| } |
| |
| TEST_F(GAP_LowEnergyInterrogatorTest, SuccessfulReinterrogation) { |
| QueueSuccessfulInterrogation(kConnectionHandle); |
| |
| auto* peer = peer_cache()->NewPeer(kTestDevAddr, true); |
| |
| std::optional<hci::Status> status; |
| interrogator()->Start(peer->identifier(), kConnectionHandle, |
| [&status](hci::Status cb_status) { status = cb_status; }); |
| RunLoopUntilIdle(); |
| |
| ASSERT_TRUE(status.has_value()); |
| EXPECT_TRUE(status->is_success()); |
| status = std::nullopt; |
| |
| interrogator()->Start(peer->identifier(), kConnectionHandle, |
| [&status](hci::Status cb_status) { status = cb_status; }); |
| |
| // No commands should be sent on reinterrogation |
| RunLoopUntilIdle(); |
| ASSERT_TRUE(status.has_value()); |
| EXPECT_TRUE(status->is_success()); |
| } |
| |
| TEST_F(GAP_LowEnergyInterrogatorTest, LEReadRemoteFeaturesErrorStatus) { |
| const auto remote_version_complete_packet = |
| testing::ReadRemoteVersionInfoCompletePacket(kConnectionHandle); |
| const auto le_read_remote_features_error_status_packet = |
| testing::CommandStatusPacket(hci::kLEReadRemoteFeatures, hci::StatusCode::kUnknownCommand); |
| test_device()->QueueCommandTransaction( |
| CommandTransaction(testing::ReadRemoteVersionInfoPacket(kConnectionHandle), |
| {&kReadRemoteVersionInfoRsp, &remote_version_complete_packet})); |
| test_device()->QueueCommandTransaction( |
| CommandTransaction(testing::LEReadRemoteFeaturesPacket(kConnectionHandle), |
| {&le_read_remote_features_error_status_packet})); |
| |
| auto* peer = peer_cache()->NewPeer(kTestDevAddr, true); |
| ASSERT_TRUE(peer->le()); |
| EXPECT_FALSE(peer->le()->features()); |
| |
| std::optional<hci::Status> status; |
| interrogator()->Start(peer->identifier(), kConnectionHandle, |
| [&status](hci::Status cb_status) { status = cb_status; }); |
| RunLoopUntilIdle(); |
| ASSERT_TRUE(status.has_value()); |
| EXPECT_FALSE(status->is_success()); |
| EXPECT_FALSE(peer->le()->features().has_value()); |
| } |
| |
| TEST_F(GAP_LowEnergyInterrogatorTest, PeerRemovedBeforeLEReadRemoteFeaturesComplete) { |
| const auto remote_version_complete_packet = |
| testing::ReadRemoteVersionInfoCompletePacket(kConnectionHandle); |
| const auto le_remote_features_complete_packet = |
| testing::LEReadRemoteFeaturesCompletePacket(kConnectionHandle, hci::LESupportedFeatures{0}); |
| |
| test_device()->QueueCommandTransaction( |
| CommandTransaction(testing::ReadRemoteVersionInfoPacket(kConnectionHandle), |
| {&kReadRemoteVersionInfoRsp, &remote_version_complete_packet})); |
| test_device()->QueueCommandTransaction(CommandTransaction( |
| testing::LEReadRemoteFeaturesPacket(kConnectionHandle), {&kLEReadRemoteFeaturesRsp})); |
| |
| auto* peer = peer_cache()->NewPeer(kTestDevAddr, true); |
| ASSERT_TRUE(peer->le()); |
| EXPECT_FALSE(peer->le()->features()); |
| |
| std::optional<hci::Status> status; |
| interrogator()->Start(peer->identifier(), kConnectionHandle, |
| [&status](hci::Status cb_status) { status = cb_status; }); |
| RunLoopUntilIdle(); |
| EXPECT_FALSE(status.has_value()); |
| |
| ASSERT_TRUE(peer_cache()->RemoveDisconnectedPeer(peer->identifier())); |
| |
| test_device()->SendCommandChannelPacket(le_remote_features_complete_packet); |
| RunLoopUntilIdle(); |
| ASSERT_TRUE(status.has_value()); |
| EXPECT_FALSE(status->is_success()); |
| } |
| |
| TEST_F(GAP_LowEnergyInterrogatorTest, ReadLERemoteFeaturesCallbackHandlesCanceledInterrogation) { |
| const auto remote_version_complete_packet = |
| testing::ReadRemoteVersionInfoCompletePacket(kConnectionHandle); |
| const auto le_remote_features_complete_packet = |
| testing::LEReadRemoteFeaturesCompletePacket(kConnectionHandle, hci::LESupportedFeatures{0}); |
| |
| test_device()->QueueCommandTransaction( |
| CommandTransaction(testing::ReadRemoteVersionInfoPacket(kConnectionHandle), |
| {&kReadRemoteVersionInfoRsp, &remote_version_complete_packet})); |
| test_device()->QueueCommandTransaction(CommandTransaction( |
| testing::LEReadRemoteFeaturesPacket(kConnectionHandle), {&kLEReadRemoteFeaturesRsp})); |
| |
| auto* peer = peer_cache()->NewPeer(kTestDevAddr, true); |
| ASSERT_TRUE(peer->le()); |
| EXPECT_FALSE(peer->version()); |
| EXPECT_FALSE(peer->le()->features()); |
| |
| std::optional<hci::Status> status; |
| interrogator()->Start(peer->identifier(), kConnectionHandle, |
| [&status](hci::Status cb_status) { status = cb_status; }); |
| RunLoopUntilIdle(); |
| EXPECT_FALSE(status.has_value()); |
| |
| interrogator()->Cancel(peer->identifier()); |
| RunLoopUntilIdle(); |
| EXPECT_TRUE(status.has_value()); |
| EXPECT_FALSE(status->is_success()); |
| |
| test_device()->SendCommandChannelPacket(le_remote_features_complete_packet); |
| RunLoopUntilIdle(); |
| EXPECT_TRUE(status.has_value()); |
| EXPECT_FALSE(status->is_success()); |
| // The read remote features handler should not update the features of a canceled interrogation. |
| EXPECT_FALSE(peer->le()->features().has_value()); |
| } |
| |
| } // namespace bt::gap |