| // Copyright 2017 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/gap/low_energy_connection_manager.h" |
| |
| #include <lib/fit/function.h> |
| |
| #include <cstddef> |
| #include <limits> |
| #include <memory> |
| #include <vector> |
| |
| #include <gmock/gmock.h> |
| |
| #include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/common/assert.h" |
| #include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/common/byte_buffer.h" |
| #include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/common/device_address.h" |
| #include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/common/macros.h" |
| #include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/common/random.h" |
| #include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/gap/fake_pairing_delegate.h" |
| #include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/gap/gap.h" |
| #include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/gap/low_energy_address_manager.h" |
| #include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/gap/peer.h" |
| #include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/gap/peer_cache.h" |
| #include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/gatt/fake_layer.h" |
| #include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/hci-spec/constants.h" |
| #include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/hci-spec/defaults.h" |
| #include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/hci-spec/util.h" |
| #include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/hci/fake_local_address_delegate.h" |
| #include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/hci/legacy_low_energy_scanner.h" |
| #include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/hci/low_energy_connection.h" |
| #include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/hci/low_energy_connector.h" |
| #include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/l2cap/fake_channel.h" |
| #include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/l2cap/fake_channel_test.h" |
| #include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/l2cap/fake_l2cap.h" |
| #include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/l2cap/l2cap_defs.h" |
| #include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/sm/smp.h" |
| #include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/sm/test_security_manager.h" |
| #include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/sm/types.h" |
| #include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/testing/controller_test.h" |
| #include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/testing/fake_controller.h" |
| #include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/testing/fake_peer.h" |
| #include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/testing/inspect.h" |
| #include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/testing/test_packets.h" |
| #include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/transport/fake_acl_connection.h" |
| |
| namespace bt::gap { |
| namespace { |
| |
| using namespace inspect::testing; |
| |
| using bt::sm::BondableMode; |
| using bt::testing::FakeController; |
| using bt::testing::FakePeer; |
| |
| using TestingBase = bt::testing::FakeDispatcherControllerTest<FakeController>; |
| using l2cap::testing::FakeChannel; |
| using TestSm = sm::testing::TestSecurityManager; |
| using TestSmFactory = sm::testing::TestSecurityManagerFactory; |
| using ConnectionResult = LowEnergyConnectionManager::ConnectionResult; |
| |
| const bt::sm::LTK kLTK; |
| |
| const DeviceAddress kAddress0(DeviceAddress::Type::kLEPublic, {1}); |
| const DeviceAddress kAddrAlias0(DeviceAddress::Type::kBREDR, kAddress0.value()); |
| const DeviceAddress kAddress1(DeviceAddress::Type::kLERandom, {2}); |
| const DeviceAddress kAddress2(DeviceAddress::Type::kBREDR, {3}); |
| const DeviceAddress kAddress3(DeviceAddress::Type::kLEPublic, {4}); |
| const DeviceAddress kAdapterAddress(DeviceAddress::Type::kLEPublic, {9}); |
| |
| const size_t kLEMaxNumPackets = 10; |
| const hci::DataBufferInfo kLEDataBufferInfo(hci_spec::kMaxACLPayloadSize, |
| kLEMaxNumPackets); |
| |
| constexpr std::array kConnectDelays = { |
| std::chrono::seconds(0), std::chrono::seconds(2), std::chrono::seconds(4)}; |
| |
| const LowEnergyConnectionOptions kConnectionOptions{}; |
| |
| class LowEnergyConnectionManagerTest : public TestingBase { |
| public: |
| LowEnergyConnectionManagerTest() = default; |
| ~LowEnergyConnectionManagerTest() override = default; |
| |
| protected: |
| void SetUp() override { |
| TestingBase::SetUp(); |
| |
| // Initialize with LE buffers only. |
| TestingBase::InitializeACLDataChannel(hci::DataBufferInfo(), |
| kLEDataBufferInfo); |
| |
| FakeController::Settings settings; |
| settings.ApplyLegacyLEConfig(); |
| test_device()->set_settings(settings); |
| |
| peer_cache_ = std::make_unique<PeerCache>(dispatcher()); |
| l2cap_ = std::make_unique<l2cap::testing::FakeL2cap>(dispatcher()); |
| |
| const hci::CommandChannel::WeakPtr cmd_weak = cmd_channel()->AsWeakPtr(); |
| |
| connector_ = std::make_unique<hci::LowEnergyConnector>( |
| transport()->GetWeakPtr(), |
| &addr_delegate_, |
| dispatcher(), |
| fit::bind_member<&LowEnergyConnectionManagerTest::OnIncomingConnection>( |
| this)); |
| |
| gatt_ = std::make_unique<gatt::testing::FakeLayer>(dispatcher()); |
| sm_factory_ = std::make_unique<TestSmFactory>(); |
| |
| address_manager_ = std::make_unique<LowEnergyAddressManager>( |
| kAdapterAddress, |
| /*delegate=*/[] { return false; }, |
| cmd_weak, |
| dispatcher()); |
| scanner_ = std::make_unique<hci::LegacyLowEnergyScanner>( |
| address_manager_.get(), transport()->GetWeakPtr(), dispatcher()); |
| discovery_manager_ = std::make_unique<LowEnergyDiscoveryManager>( |
| scanner_.get(), peer_cache_.get(), dispatcher()); |
| conn_mgr_ = std::make_unique<LowEnergyConnectionManager>( |
| cmd_weak, |
| &addr_delegate_, |
| connector_.get(), |
| peer_cache_.get(), |
| l2cap_.get(), |
| gatt_->GetWeakPtr(), |
| discovery_manager_->GetWeakPtr(), |
| fit::bind_member<&TestSmFactory::CreateSm>(sm_factory_.get()), |
| adapter_state_, |
| dispatcher()); |
| |
| test_device()->set_connection_state_callback( |
| fit::bind_member< |
| &LowEnergyConnectionManagerTest::OnConnectionStateChanged>(this)); |
| } |
| |
| void TearDown() override { |
| if (conn_mgr_) { |
| conn_mgr_ = nullptr; |
| } |
| discovery_manager_ = nullptr; |
| scanner_ = nullptr; |
| address_manager_ = nullptr; |
| gatt_ = nullptr; |
| connector_ = nullptr; |
| peer_cache_ = nullptr; |
| |
| l2cap_ = nullptr; |
| |
| TestingBase::TearDown(); |
| } |
| |
| // Deletes |conn_mgr_|. |
| void DeleteConnMgr() { conn_mgr_ = nullptr; } |
| |
| PeerCache* peer_cache() const { return peer_cache_.get(); } |
| LowEnergyConnectionManager* conn_mgr() const { return conn_mgr_.get(); } |
| l2cap::testing::FakeL2cap* fake_l2cap() const { return l2cap_.get(); } |
| gatt::testing::FakeLayer* fake_gatt() { return gatt_.get(); } |
| LowEnergyDiscoveryManager* discovery_mgr() { |
| return discovery_manager_.get(); |
| } |
| |
| // Addresses of currently connected fake peers. |
| using PeerList = std::unordered_set<DeviceAddress>; |
| const PeerList& connected_peers() const { return connected_peers_; } |
| |
| // Addresses of peers with a canceled connection attempt. |
| const PeerList& canceled_peers() const { return canceled_peers_; } |
| |
| std::unique_ptr<hci::LowEnergyConnection> MoveLastRemoteInitiated() { |
| return std::move(last_remote_initiated_); |
| } |
| |
| TestSm::WeakPtr TestSmByHandle(hci_spec::ConnectionHandle handle) { |
| return sm_factory_->GetTestSm(handle); |
| } |
| |
| private: |
| // Called by |connector_| when a new remote initiated connection is received. |
| void OnIncomingConnection( |
| hci_spec::ConnectionHandle handle, |
| pw::bluetooth::emboss::ConnectionRole role, |
| const DeviceAddress& peer_address, |
| const hci_spec::LEConnectionParameters& conn_params) { |
| DeviceAddress local_address(DeviceAddress::Type::kLEPublic, |
| {3, 2, 1, 1, 2, 3}); |
| |
| // Create a production connection object that can interact with the fake |
| // controller. |
| last_remote_initiated_ = |
| std::make_unique<hci::LowEnergyConnection>(handle, |
| local_address, |
| peer_address, |
| conn_params, |
| role, |
| transport()->GetWeakPtr()); |
| } |
| |
| // Called by FakeController on connection events. |
| void OnConnectionStateChanged(const DeviceAddress& address, |
| hci_spec::ConnectionHandle handle, |
| bool connected, |
| bool canceled) { |
| bt_log(DEBUG, |
| "gap-test", |
| "OnConnectionStateChanged: %s (handle: %#.4x) (connected: %s) " |
| "(canceled: %s):\n", |
| address.ToString().c_str(), |
| handle, |
| (connected ? "true" : "false"), |
| (canceled ? "true" : "false")); |
| if (canceled) { |
| canceled_peers_.insert(address); |
| } else if (connected) { |
| BT_DEBUG_ASSERT(connected_peers_.find(address) == connected_peers_.end()); |
| connected_peers_.insert(address); |
| } else { |
| BT_DEBUG_ASSERT(connected_peers_.find(address) != connected_peers_.end()); |
| connected_peers_.erase(address); |
| } |
| } |
| |
| std::unique_ptr<l2cap::testing::FakeL2cap> l2cap_; |
| hci::FakeLocalAddressDelegate addr_delegate_{dispatcher()}; |
| std::unique_ptr<PeerCache> peer_cache_; |
| std::unique_ptr<hci::LowEnergyConnector> connector_; |
| std::unique_ptr<gatt::testing::FakeLayer> gatt_; |
| std::unique_ptr<TestSmFactory> sm_factory_; |
| std::unique_ptr<hci::LegacyLowEnergyScanner> scanner_; |
| std::unique_ptr<LowEnergyAddressManager> address_manager_; |
| std::unique_ptr<LowEnergyDiscoveryManager> discovery_manager_; |
| std::unique_ptr<LowEnergyConnectionManager> conn_mgr_; |
| |
| AdapterState adapter_state_ = {}; |
| |
| // The most recent remote-initiated connection reported by |connector_|. |
| std::unique_ptr<hci::LowEnergyConnection> last_remote_initiated_; |
| |
| PeerList connected_peers_; |
| PeerList canceled_peers_; |
| |
| BT_DISALLOW_COPY_AND_ASSIGN_ALLOW_MOVE(LowEnergyConnectionManagerTest); |
| }; |
| |
| using GAP_LowEnergyConnectionManagerTest = LowEnergyConnectionManagerTest; |
| |
| TEST_F(LowEnergyConnectionManagerTest, ConnectUnknownPeer) { |
| constexpr PeerId kUnknownId(1); |
| ConnectionResult result = fit::ok(nullptr); |
| conn_mgr()->Connect( |
| kUnknownId, |
| [&result](auto res) { result = std::move(res); }, |
| kConnectionOptions); |
| ASSERT_TRUE(result.is_error()); |
| EXPECT_EQ(HostError::kNotFound, result.error_value()); |
| } |
| |
| TEST_F(LowEnergyConnectionManagerTest, ConnectClassicPeer) { |
| auto* peer = peer_cache()->NewPeer(kAddress2, /*connectable=*/true); |
| ConnectionResult result = fit::ok(nullptr); |
| conn_mgr()->Connect( |
| peer->identifier(), |
| [&result](auto res) { result = std::move(res); }, |
| kConnectionOptions); |
| ASSERT_TRUE(result.is_error()); |
| EXPECT_EQ(HostError::kNotFound, result.error_value()); |
| } |
| |
| TEST_F(LowEnergyConnectionManagerTest, ConnectNonConnectablePeer) { |
| auto* peer = peer_cache()->NewPeer(kAddress0, /*connectable=*/false); |
| ConnectionResult result = fit::ok(nullptr); |
| conn_mgr()->Connect( |
| peer->identifier(), |
| [&result](auto res) { result = std::move(res); }, |
| kConnectionOptions); |
| ASSERT_TRUE(result.is_error()); |
| EXPECT_EQ(HostError::kNotFound, result.error_value()); |
| } |
| |
| // An error is received via the HCI Command cb_status event |
| TEST_F(LowEnergyConnectionManagerTest, ConnectSinglePeerErrorStatus) { |
| auto* peer = peer_cache()->NewPeer(kAddress0, /*connectable=*/true); |
| auto fake_peer = std::make_unique<FakePeer>(kAddress0, dispatcher()); |
| fake_peer->set_connect_status( |
| pw::bluetooth::emboss::StatusCode::CONNECTION_FAILED_TO_BE_ESTABLISHED); |
| test_device()->AddPeer(std::move(fake_peer)); |
| |
| ASSERT_TRUE(peer->le()); |
| EXPECT_EQ(Peer::ConnectionState::kNotConnected, |
| peer->le()->connection_state()); |
| |
| ConnectionResult result = fit::ok(nullptr); |
| auto callback = [&result](auto res) { result = std::move(res); }; |
| |
| conn_mgr()->Connect(peer->identifier(), callback, kConnectionOptions); |
| EXPECT_EQ(Peer::ConnectionState::kInitializing, |
| peer->le()->connection_state()); |
| |
| RunUntilIdle(); |
| |
| ASSERT_TRUE(result.is_error()); |
| EXPECT_EQ(HostError::kFailed, result.error_value()); |
| EXPECT_EQ(Peer::ConnectionState::kNotConnected, |
| peer->le()->connection_state()); |
| } |
| |
| // LE Connection Complete event reports error |
| TEST_F(LowEnergyConnectionManagerTest, ConnectSinglePeerFailure) { |
| auto* peer = peer_cache()->NewPeer(kAddress0, /*connectable=*/true); |
| auto fake_peer = std::make_unique<FakePeer>(kAddress0, dispatcher()); |
| fake_peer->set_connect_response( |
| pw::bluetooth::emboss::StatusCode::CONNECTION_FAILED_TO_BE_ESTABLISHED); |
| test_device()->AddPeer(std::move(fake_peer)); |
| |
| ConnectionResult result = fit::ok(nullptr); |
| auto callback = [&result](auto res) { result = std::move(res); }; |
| |
| conn_mgr()->Connect(peer->identifier(), callback, kConnectionOptions); |
| ASSERT_TRUE(peer->le()); |
| EXPECT_EQ(Peer::ConnectionState::kInitializing, |
| peer->le()->connection_state()); |
| |
| RunUntilIdle(); |
| |
| ASSERT_TRUE(result.is_error()); |
| EXPECT_EQ(HostError::kFailed, result.error_value()); |
| EXPECT_EQ(Peer::ConnectionState::kNotConnected, |
| peer->le()->connection_state()); |
| } |
| |
| TEST_F(LowEnergyConnectionManagerTest, ConnectSinglePeerScanTimeout) { |
| auto* peer = peer_cache()->NewPeer(kAddress0, /*connectable=*/true); |
| |
| // We add no fake peers to cause the scan to time out. |
| |
| ConnectionResult result = fit::ok(nullptr); |
| auto callback = [&result](auto res) { result = std::move(res); }; |
| |
| conn_mgr()->Connect(peer->identifier(), callback, kConnectionOptions); |
| ASSERT_TRUE(peer->le()); |
| EXPECT_EQ(Peer::ConnectionState::kInitializing, |
| peer->le()->connection_state()); |
| |
| RunFor(kLEGeneralCepScanTimeout); |
| |
| ASSERT_TRUE(result.is_error()); |
| EXPECT_EQ(HostError::kTimedOut, result.error_value()); |
| EXPECT_EQ(Peer::ConnectionState::kNotConnected, |
| peer->le()->connection_state()); |
| } |
| |
| TEST_F(LowEnergyConnectionManagerTest, ConnectSinglePeerAlreadyInScanCache) { |
| auto* peer = peer_cache()->NewPeer(kAddress0, /*connectable=*/true); |
| auto fake_peer = std::make_unique<FakePeer>(kAddress0, dispatcher()); |
| test_device()->AddPeer(std::move(fake_peer)); |
| |
| // Ensure peer is in scan cache by doing active discovery. |
| LowEnergyDiscoverySessionPtr session; |
| discovery_mgr()->StartDiscovery(/*active=*/true, [&session](auto cb_session) { |
| session = std::move(cb_session); |
| }); |
| RunUntilIdle(); |
| |
| ConnectionResult result = fit::ok(nullptr); |
| auto callback = [&result](auto res) { result = std::move(res); }; |
| |
| conn_mgr()->Connect(peer->identifier(), callback, kConnectionOptions); |
| RunUntilIdle(); |
| ASSERT_EQ(fit::ok(), result); |
| } |
| |
| TEST_F(LowEnergyConnectionManagerTest, ConnectSinglePeerRequestTimeout) { |
| constexpr pw::chrono::SystemClock::duration kTestRequestTimeout = |
| std::chrono::seconds(20); |
| |
| auto* peer = peer_cache()->NewPeer(kAddress0, /*connectable=*/true); |
| |
| // Add a fake peer so that scan succeeds but connect stalls. |
| auto fake_peer = std::make_unique<FakePeer>(kAddress0, dispatcher()); |
| fake_peer->set_force_pending_connect(true); |
| test_device()->AddPeer(std::move(fake_peer)); |
| |
| ConnectionResult result = fit::ok(nullptr); |
| auto callback = [&result](auto res) { result = std::move(res); }; |
| |
| conn_mgr()->set_request_timeout_for_testing(kTestRequestTimeout); |
| conn_mgr()->Connect(peer->identifier(), callback, kConnectionOptions); |
| ASSERT_TRUE(peer->le()); |
| EXPECT_EQ(Peer::ConnectionState::kInitializing, |
| peer->le()->connection_state()); |
| |
| RunFor(kTestRequestTimeout); |
| RunUntilIdle(); |
| |
| ASSERT_TRUE(result.is_error()); |
| EXPECT_EQ(HostError::kTimedOut, result.error_value()); |
| EXPECT_EQ(Peer::ConnectionState::kNotConnected, |
| peer->le()->connection_state()); |
| } |
| |
| // Tests that an entry in the cache does not expire while a connection attempt |
| // is pending. |
| TEST_F(LowEnergyConnectionManagerTest, PeerDoesNotExpireDuringTimeout) { |
| // Set a connection timeout that is longer than the PeerCache expiry |
| // timeout. |
| // TODO(https://fxbug.dev/42087236): Consider configuring the cache timeout |
| // explicitly rather than relying on the kCacheTimeout constant. |
| constexpr pw::chrono::SystemClock::duration kTestRequestTimeout = |
| kCacheTimeout + std::chrono::seconds(1); |
| conn_mgr()->set_request_timeout_for_testing(kTestRequestTimeout); |
| |
| // Note: Use a random address so that the peer becomes temporary upon failure. |
| auto* peer = peer_cache()->NewPeer(kAddress1, /*connectable=*/true); |
| EXPECT_TRUE(peer->temporary()); |
| |
| ConnectionResult result = fit::ok(nullptr); |
| auto callback = [&result](auto res) { result = std::move(res); }; |
| |
| conn_mgr()->Connect(peer->identifier(), callback, kConnectionOptions); |
| ASSERT_TRUE(peer->le()); |
| EXPECT_EQ(Peer::ConnectionState::kInitializing, |
| peer->le()->connection_state()); |
| EXPECT_FALSE(peer->temporary()); |
| |
| RunFor(kTestRequestTimeout); |
| ASSERT_TRUE(result.is_error()); |
| EXPECT_EQ(HostError::kTimedOut, result.error_value()); |
| EXPECT_EQ(peer, peer_cache()->FindByAddress(kAddress1)); |
| EXPECT_EQ(Peer::ConnectionState::kNotConnected, |
| peer->le()->connection_state()); |
| EXPECT_TRUE(peer->temporary()); |
| } |
| |
| TEST_F(LowEnergyConnectionManagerTest, PeerDoesNotExpireDuringDelayedConnect) { |
| // Make the connection resolve after a delay that is longer than the cache |
| // timeout. |
| constexpr pw::chrono::SystemClock::duration kConnectionDelay = |
| kCacheTimeout + std::chrono::seconds(1); |
| FakeController::Settings settings; |
| settings.ApplyLegacyLEConfig(); |
| settings.le_connection_delay = kConnectionDelay; |
| test_device()->set_settings(settings); |
| |
| auto* peer = peer_cache()->NewPeer(kAddress0, /*connectable=*/true); |
| auto id = peer->identifier(); |
| EXPECT_TRUE(peer->temporary()); |
| |
| auto fake_peer = std::make_unique<FakePeer>(kAddress0, dispatcher()); |
| test_device()->AddPeer(std::move(fake_peer)); |
| |
| // Make sure the connection request doesn't time out while waiting for a |
| // response. |
| conn_mgr()->set_request_timeout_for_testing(kConnectionDelay + |
| std::chrono::seconds(1)); |
| |
| std::unique_ptr<LowEnergyConnectionHandle> conn_handle; |
| auto callback = [&conn_handle](auto result) { |
| ASSERT_EQ(fit::ok(), result); |
| conn_handle = std::move(result).value(); |
| ASSERT_TRUE(conn_handle); |
| EXPECT_TRUE(conn_handle->active()); |
| }; |
| conn_mgr()->Connect(id, callback, kConnectionOptions); |
| ASSERT_TRUE(peer->le()); |
| EXPECT_EQ(Peer::ConnectionState::kInitializing, |
| peer->le()->connection_state()); |
| |
| RunFor(kConnectionDelay); |
| ASSERT_TRUE(conn_handle); |
| |
| // The peer should not have expired during this time. |
| peer = peer_cache()->FindByAddress(kAddress0); |
| ASSERT_TRUE(peer); |
| EXPECT_EQ(id, peer->identifier()); |
| EXPECT_TRUE(peer->connected()); |
| EXPECT_FALSE(peer->temporary()); |
| } |
| |
| // Successful connection to single peer |
| TEST_F(LowEnergyConnectionManagerTest, ConnectSinglePeer) { |
| auto* peer = peer_cache()->NewPeer(kAddress0, /*connectable=*/true); |
| EXPECT_TRUE(peer->temporary()); |
| |
| auto fake_peer = std::make_unique<FakePeer>(kAddress0, dispatcher()); |
| test_device()->AddPeer(std::move(fake_peer)); |
| |
| // Use a StaticPacket so that the packet is copied. |
| std::optional< |
| StaticPacket<pw::bluetooth::emboss::LECreateConnectionCommandWriter>> |
| connect_params; |
| test_device()->set_le_create_connection_command_callback( |
| [&](pw::bluetooth::emboss::LECreateConnectionCommandView params) { |
| connect_params.emplace(params); |
| }); |
| |
| std::unique_ptr<LowEnergyConnectionHandle> conn_handle; |
| auto callback = [&conn_handle](auto result) { |
| ASSERT_EQ(fit::ok(), result); |
| conn_handle = std::move(result).value(); |
| EXPECT_TRUE(conn_handle->active()); |
| }; |
| |
| EXPECT_TRUE(connected_peers().empty()); |
| conn_mgr()->Connect(peer->identifier(), callback, kConnectionOptions); |
| ASSERT_TRUE(peer->le()); |
| EXPECT_EQ(Peer::ConnectionState::kInitializing, |
| peer->le()->connection_state()); |
| |
| RunUntilIdle(); |
| |
| EXPECT_EQ(1u, connected_peers().size()); |
| EXPECT_EQ(1u, connected_peers().count(kAddress0)); |
| |
| ASSERT_TRUE(conn_handle); |
| EXPECT_TRUE(conn_handle->active()); |
| EXPECT_EQ(peer->identifier(), conn_handle->peer_identifier()); |
| EXPECT_FALSE(peer->temporary()); |
| EXPECT_EQ(Peer::ConnectionState::kConnected, peer->le()->connection_state()); |
| ASSERT_TRUE(connect_params); |
| EXPECT_EQ(connect_params->view().le_scan_interval().Read(), |
| kLEScanFastInterval); |
| EXPECT_EQ(connect_params->view().le_scan_window().Read(), kLEScanFastWindow); |
| } |
| |
| struct TestObject final { |
| explicit TestObject(bool* d) : deleted(d) { |
| BT_DEBUG_ASSERT(deleted); |
| *deleted = false; |
| } |
| |
| ~TestObject() { *deleted = true; } |
| |
| bool* deleted; |
| }; |
| |
| TEST_F(LowEnergyConnectionManagerTest, DeleteRefInClosedCallback) { |
| auto* peer = peer_cache()->NewPeer(kAddress0, /*connectable=*/true); |
| test_device()->AddPeer(std::make_unique<FakePeer>(kAddress0, dispatcher())); |
| |
| bool deleted = false; |
| auto obj = std::make_shared<TestObject>(&deleted); |
| std::unique_ptr<LowEnergyConnectionHandle> conn_handle; |
| int closed_count = 0; |
| auto closed_cb = [&, obj = std::move(obj)] { |
| closed_count++; |
| conn_handle = nullptr; |
| |
| // The object should remain alive for the duration of this callback. |
| EXPECT_FALSE(deleted); |
| }; |
| |
| auto success_cb = [&conn_handle, &closed_cb](auto result) { |
| ASSERT_EQ(fit::ok(), result); |
| conn_handle = std::move(result).value(); |
| conn_handle->set_closed_callback(std::move(closed_cb)); |
| }; |
| |
| conn_mgr()->Connect(peer->identifier(), success_cb, kConnectionOptions); |
| RunUntilIdle(); |
| |
| ASSERT_TRUE(conn_handle); |
| ASSERT_TRUE(conn_handle->active()); |
| |
| // This will trigger the closed callback. |
| EXPECT_TRUE(conn_mgr()->Disconnect(peer->identifier())); |
| RunUntilIdle(); |
| |
| EXPECT_EQ(1, closed_count); |
| EXPECT_TRUE(connected_peers().empty()); |
| EXPECT_FALSE(conn_handle); |
| |
| // The object should be deleted. |
| EXPECT_TRUE(deleted); |
| } |
| |
| TEST_F(LowEnergyConnectionManagerTest, ReleaseRef) { |
| auto* peer = peer_cache()->NewPeer(kAddress0, /*connectable=*/true); |
| auto fake_peer = std::make_unique<FakePeer>(kAddress0, dispatcher()); |
| test_device()->AddPeer(std::move(fake_peer)); |
| |
| std::unique_ptr<LowEnergyConnectionHandle> conn_handle; |
| auto callback = [&conn_handle](auto result) { |
| ASSERT_EQ(fit::ok(), result); |
| conn_handle = std::move(result).value(); |
| EXPECT_TRUE(conn_handle->active()); |
| }; |
| |
| EXPECT_TRUE(connected_peers().empty()); |
| conn_mgr()->Connect(peer->identifier(), callback, kConnectionOptions); |
| |
| RunUntilIdle(); |
| |
| EXPECT_EQ(1u, connected_peers().size()); |
| ASSERT_TRUE(peer->le()); |
| EXPECT_EQ(Peer::ConnectionState::kConnected, peer->le()->connection_state()); |
| |
| ASSERT_TRUE(conn_handle); |
| conn_handle = nullptr; |
| |
| RunUntilIdle(); |
| |
| EXPECT_TRUE(connected_peers().empty()); |
| EXPECT_EQ(Peer::ConnectionState::kNotConnected, |
| peer->le()->connection_state()); |
| } |
| |
| TEST_F(LowEnergyConnectionManagerTest, OnePeerTwoPendingRequestsBothFail) { |
| constexpr size_t kRequestCount = 2; |
| |
| auto* peer = peer_cache()->NewPeer(kAddress0, /*connectable=*/true); |
| auto fake_peer = std::make_unique<FakePeer>(kAddress0, dispatcher()); |
| fake_peer->set_connect_response( |
| pw::bluetooth::emboss::StatusCode::CONNECTION_FAILED_TO_BE_ESTABLISHED); |
| test_device()->AddPeer(std::move(fake_peer)); |
| |
| std::vector<ConnectionResult> results; |
| |
| auto callback = [&results](auto result) { |
| results.push_back(std::move(result)); |
| }; |
| |
| for (size_t i = 0; i < kRequestCount; ++i) { |
| conn_mgr()->Connect(peer->identifier(), callback, kConnectionOptions); |
| } |
| |
| RunUntilIdle(); |
| |
| EXPECT_EQ(kRequestCount, results.size()); |
| for (size_t i = 0; i < results.size(); ++i) { |
| ASSERT_TRUE(results.at(i).is_error()); |
| EXPECT_EQ(HostError::kFailed, results.at(i).error_value()) |
| << "request count: " << i + 1; |
| } |
| } |
| |
| TEST_F(LowEnergyConnectionManagerTest, OnePeerManyPendingRequests) { |
| constexpr size_t kRequestCount = 50; |
| |
| auto* peer = peer_cache()->NewPeer(kAddress0, /*connectable=*/true); |
| auto fake_peer = std::make_unique<FakePeer>(kAddress0, dispatcher()); |
| test_device()->AddPeer(std::move(fake_peer)); |
| |
| std::vector<std::unique_ptr<LowEnergyConnectionHandle>> conn_handles; |
| auto callback = [&conn_handles](auto result) { |
| ASSERT_EQ(fit::ok(), result); |
| conn_handles.emplace_back(std::move(result).value()); |
| }; |
| |
| for (size_t i = 0; i < kRequestCount; ++i) { |
| conn_mgr()->Connect(peer->identifier(), callback, kConnectionOptions); |
| } |
| |
| RunUntilIdle(); |
| |
| EXPECT_EQ(1u, connected_peers().size()); |
| EXPECT_EQ(1u, connected_peers().count(kAddress0)); |
| |
| EXPECT_EQ(kRequestCount, conn_handles.size()); |
| for (size_t i = 0; i < kRequestCount; ++i) { |
| ASSERT_TRUE(conn_handles[i]); |
| EXPECT_TRUE(conn_handles[i]->active()); |
| EXPECT_EQ(peer->identifier(), conn_handles[i]->peer_identifier()); |
| } |
| |
| // Release one reference. The rest should be active. |
| conn_handles[0] = nullptr; |
| for (size_t i = 1; i < kRequestCount; ++i) |
| EXPECT_TRUE(conn_handles[i]->active()); |
| |
| // Release all but one reference. |
| for (size_t i = 1; i < kRequestCount - 1; ++i) |
| conn_handles[i] = nullptr; |
| EXPECT_TRUE(conn_handles[kRequestCount - 1]->active()); |
| |
| // Drop the last reference. |
| conn_handles[kRequestCount - 1] = nullptr; |
| |
| RunUntilIdle(); |
| |
| EXPECT_TRUE(connected_peers().empty()); |
| } |
| |
| TEST_F(LowEnergyConnectionManagerTest, AddRefAfterConnection) { |
| constexpr size_t kRefCount = 50; |
| |
| auto* peer = peer_cache()->NewPeer(kAddress0, /*connectable=*/true); |
| auto fake_peer = std::make_unique<FakePeer>(kAddress0, dispatcher()); |
| test_device()->AddPeer(std::move(fake_peer)); |
| |
| std::vector<std::unique_ptr<LowEnergyConnectionHandle>> conn_handles; |
| auto callback = [&conn_handles](auto result) { |
| ASSERT_EQ(fit::ok(), result); |
| conn_handles.emplace_back(std::move(result).value()); |
| }; |
| |
| conn_mgr()->Connect(peer->identifier(), callback, kConnectionOptions); |
| |
| RunUntilIdle(); |
| |
| EXPECT_EQ(1u, connected_peers().size()); |
| EXPECT_EQ(1u, connected_peers().count(kAddress0)); |
| EXPECT_EQ(1u, conn_handles.size()); |
| |
| // Add new references. |
| for (size_t i = 1; i < kRefCount; ++i) { |
| conn_mgr()->Connect(peer->identifier(), callback, kConnectionOptions); |
| RunUntilIdle(); |
| } |
| |
| EXPECT_EQ(1u, connected_peers().size()); |
| EXPECT_EQ(1u, connected_peers().count(kAddress0)); |
| EXPECT_EQ(kRefCount, conn_handles.size()); |
| |
| // Disconnect. |
| conn_handles.clear(); |
| |
| RunUntilIdle(); |
| |
| EXPECT_TRUE(connected_peers().empty()); |
| } |
| |
| TEST_F(LowEnergyConnectionManagerTest, PendingRequestsOnTwoPeers) { |
| auto* peer0 = peer_cache()->NewPeer(kAddress0, /*connectable=*/true); |
| auto* peer1 = peer_cache()->NewPeer(kAddress1, /*connectable=*/true); |
| |
| test_device()->AddPeer(std::make_unique<FakePeer>(kAddress0, dispatcher())); |
| test_device()->AddPeer(std::make_unique<FakePeer>(kAddress1, dispatcher())); |
| |
| std::vector<std::unique_ptr<LowEnergyConnectionHandle>> conn_handles; |
| auto callback = [&conn_handles](auto result) { |
| ASSERT_EQ(fit::ok(), result); |
| conn_handles.emplace_back(std::move(result).value()); |
| }; |
| |
| conn_mgr()->Connect(peer0->identifier(), callback, kConnectionOptions); |
| conn_mgr()->Connect(peer1->identifier(), callback, kConnectionOptions); |
| |
| RunUntilIdle(); |
| |
| EXPECT_EQ(2u, connected_peers().size()); |
| EXPECT_EQ(1u, connected_peers().count(kAddress0)); |
| EXPECT_EQ(1u, connected_peers().count(kAddress1)); |
| |
| ASSERT_EQ(2u, conn_handles.size()); |
| ASSERT_TRUE(conn_handles[0]); |
| ASSERT_TRUE(conn_handles[1]); |
| EXPECT_EQ(peer0->identifier(), conn_handles[0]->peer_identifier()); |
| EXPECT_EQ(peer1->identifier(), conn_handles[1]->peer_identifier()); |
| |
| // |peer1| should disconnect first. |
| conn_handles[1] = nullptr; |
| |
| RunUntilIdle(); |
| |
| EXPECT_EQ(1u, connected_peers().size()); |
| EXPECT_EQ(1u, connected_peers().count(kAddress0)); |
| |
| conn_handles.clear(); |
| |
| RunUntilIdle(); |
| EXPECT_TRUE(connected_peers().empty()); |
| } |
| |
| TEST_F(LowEnergyConnectionManagerTest, PendingRequestsOnTwoPeersOneFails) { |
| auto* peer0 = peer_cache()->NewPeer(kAddress0, /*connectable=*/true); |
| auto* peer1 = peer_cache()->NewPeer(kAddress1, /*connectable=*/true); |
| |
| auto fake_peer0 = std::make_unique<FakePeer>(kAddress0, dispatcher()); |
| fake_peer0->set_connect_response( |
| pw::bluetooth::emboss::StatusCode::CONNECTION_FAILED_TO_BE_ESTABLISHED); |
| test_device()->AddPeer(std::move(fake_peer0)); |
| test_device()->AddPeer(std::make_unique<FakePeer>(kAddress1, dispatcher())); |
| |
| std::vector<ConnectionResult> conn_results; |
| auto callback = [&conn_results](auto result) { |
| conn_results.emplace_back(std::move(result)); |
| }; |
| |
| conn_mgr()->Connect(peer0->identifier(), callback, kConnectionOptions); |
| conn_mgr()->Connect(peer1->identifier(), callback, kConnectionOptions); |
| |
| RunUntilIdle(); |
| |
| EXPECT_EQ(1u, connected_peers().size()); |
| EXPECT_EQ(1u, connected_peers().count(kAddress1)); |
| |
| ASSERT_EQ(2u, conn_results.size()); |
| EXPECT_TRUE(conn_results[0].is_error()); |
| ASSERT_EQ(fit::ok(), conn_results[1]); |
| EXPECT_EQ(peer1->identifier(), conn_results[1].value()->peer_identifier()); |
| |
| // Both connections should disconnect. |
| conn_results.clear(); |
| |
| RunUntilIdle(); |
| EXPECT_TRUE(connected_peers().empty()); |
| } |
| |
| TEST_F(LowEnergyConnectionManagerTest, Destructor) { |
| auto* peer0 = peer_cache()->NewPeer(kAddress0, /*connectable=*/true); |
| auto* peer1 = peer_cache()->NewPeer(kAddress1, /*connectable=*/true); |
| |
| // Connecting to this peer will succeed. |
| test_device()->AddPeer(std::make_unique<FakePeer>(kAddress0, dispatcher())); |
| |
| // Connecting to this peer will remain pending. |
| auto pending_peer = std::make_unique<FakePeer>(kAddress1, dispatcher()); |
| pending_peer->set_force_pending_connect(true); |
| test_device()->AddPeer(std::move(pending_peer)); |
| |
| // Below we create one connection and one pending request to have at the time |
| // of destruction. |
| |
| std::unique_ptr<LowEnergyConnectionHandle> conn_handle; |
| auto success_cb = [&conn_handle](auto result) { |
| ASSERT_EQ(fit::ok(), result); |
| conn_handle = std::move(result).value(); |
| }; |
| |
| conn_mgr()->Connect(peer0->identifier(), success_cb, kConnectionOptions); |
| RunUntilIdle(); |
| |
| ASSERT_TRUE(conn_handle); |
| bool conn_closed = false; |
| conn_handle->set_closed_callback([&conn_closed] { conn_closed = true; }); |
| |
| bool error_cb_called = false; |
| auto error_cb = [&error_cb_called](auto result) { |
| ASSERT_TRUE(result.is_error()); |
| EXPECT_EQ(HostError::kCanceled, result.error_value()); |
| error_cb_called = true; |
| }; |
| |
| // This will send an HCI command to the fake controller. We delete the |
| // connection manager before a connection event gets received which should |
| // cancel the connection. |
| conn_mgr()->Connect(peer1->identifier(), error_cb, kConnectionOptions); |
| RunUntilIdle(); |
| EXPECT_FALSE(error_cb_called); |
| |
| DeleteConnMgr(); |
| |
| RunUntilIdle(); |
| |
| EXPECT_TRUE(error_cb_called); |
| EXPECT_TRUE(conn_closed); |
| EXPECT_EQ(1u, canceled_peers().size()); |
| EXPECT_EQ(1u, canceled_peers().count(kAddress1)); |
| } |
| |
| TEST_F(LowEnergyConnectionManagerTest, |
| DisconnectPendingConnectionWhileAwaitingScanStart) { |
| auto peer_0 = peer_cache()->NewPeer(kAddress0, /*connectable=*/true); |
| auto peer_1 = peer_cache()->NewPeer(kAddress1, /*connectable=*/true); |
| test_device()->AddPeer(std::make_unique<FakePeer>(kAddress1, dispatcher())); |
| |
| int conn_cb_0_count = 0; |
| auto conn_cb_0 = [&](auto result) { |
| ASSERT_TRUE(result.is_error()); |
| EXPECT_EQ(HostError::kCanceled, result.error_value()); |
| EXPECT_EQ(peer_0->le()->connection_state(), |
| Peer::ConnectionState::kNotConnected); |
| conn_cb_0_count++; |
| }; |
| |
| std::unique_ptr<LowEnergyConnectionHandle> conn_handle; |
| auto conn_cb_1 = [&](auto result) { |
| ASSERT_EQ(fit::ok(), result); |
| conn_handle = std::move(result).value(); |
| }; |
| |
| conn_mgr()->Connect(peer_0->identifier(), conn_cb_0, kConnectionOptions); |
| conn_mgr()->Connect(peer_1->identifier(), conn_cb_1, kConnectionOptions); |
| EXPECT_EQ(Peer::ConnectionState::kInitializing, |
| peer_0->le()->connection_state()); |
| EXPECT_EQ(Peer::ConnectionState::kInitializing, |
| peer_1->le()->connection_state()); |
| |
| // Do NOT wait for scanning to start asynchronously before calling Disconnect |
| // synchronously. After peer_0's connection request is cancelled, peer_1's |
| // connection request should succeed. |
| EXPECT_TRUE(conn_mgr()->Disconnect(peer_0->identifier())); |
| RunUntilIdle(); |
| EXPECT_EQ(conn_cb_0_count, 1); |
| ASSERT_TRUE(conn_handle); |
| EXPECT_EQ(conn_handle->peer_identifier(), peer_1->identifier()); |
| EXPECT_EQ(Peer::ConnectionState::kNotConnected, |
| peer_0->le()->connection_state()); |
| EXPECT_EQ(Peer::ConnectionState::kConnected, |
| peer_1->le()->connection_state()); |
| } |
| |
| TEST_F(LowEnergyConnectionManagerTest, DisconnectPendingConnectionDuringScan) { |
| // Don't add FakePeer for peer_0 in order to stall during scanning. |
| auto peer_0 = peer_cache()->NewPeer(kAddress0, /*connectable=*/true); |
| auto peer_1 = peer_cache()->NewPeer(kAddress1, /*connectable=*/true); |
| test_device()->AddPeer(std::make_unique<FakePeer>(kAddress1, dispatcher())); |
| |
| int conn_cb_0_count = 0; |
| auto conn_cb_0 = [&](auto result) { |
| ASSERT_TRUE(result.is_error()); |
| EXPECT_EQ(HostError::kCanceled, result.error_value()); |
| EXPECT_EQ(peer_0->le()->connection_state(), |
| Peer::ConnectionState::kNotConnected); |
| conn_cb_0_count++; |
| }; |
| |
| std::unique_ptr<LowEnergyConnectionHandle> conn_handle; |
| auto conn_cb_1 = [&](auto result) { |
| ASSERT_EQ(fit::ok(), result); |
| conn_handle = std::move(result).value(); |
| }; |
| |
| conn_mgr()->Connect(peer_0->identifier(), conn_cb_0, kConnectionOptions); |
| conn_mgr()->Connect(peer_1->identifier(), conn_cb_1, kConnectionOptions); |
| |
| // Wait for scanning to start & OnScanStart callback to be called. |
| RunUntilIdle(); |
| EXPECT_EQ(Peer::ConnectionState::kInitializing, |
| peer_0->le()->connection_state()); |
| EXPECT_EQ(Peer::ConnectionState::kInitializing, |
| peer_1->le()->connection_state()); |
| |
| // After peer_0's connection request is cancelled, peer_1's connection request |
| // should succeed. |
| EXPECT_TRUE(conn_mgr()->Disconnect(peer_0->identifier())); |
| RunUntilIdle(); |
| EXPECT_EQ(conn_cb_0_count, 1); |
| ASSERT_TRUE(conn_handle); |
| EXPECT_EQ(conn_handle->peer_identifier(), peer_1->identifier()); |
| EXPECT_EQ(Peer::ConnectionState::kNotConnected, |
| peer_0->le()->connection_state()); |
| EXPECT_EQ(Peer::ConnectionState::kConnected, |
| peer_1->le()->connection_state()); |
| } |
| |
| TEST_F(LowEnergyConnectionManagerTest, LocalDisconnectWhileConnectorPending) { |
| auto peer_0 = peer_cache()->NewPeer(kAddress0, /*connectable=*/true); |
| auto fake_peer_0 = std::make_unique<FakePeer>(kAddress0, dispatcher()); |
| fake_peer_0->set_force_pending_connect(true); |
| test_device()->AddPeer(std::move(fake_peer_0)); |
| |
| auto peer_1 = peer_cache()->NewPeer(kAddress1, /*connectable=*/true); |
| test_device()->AddPeer(std::make_unique<FakePeer>(kAddress1, dispatcher())); |
| |
| int conn_cb_0_count = 0; |
| auto conn_cb_0 = [&](auto result) { |
| EXPECT_TRUE(result.is_error()); |
| EXPECT_EQ(HostError::kCanceled, result.error_value()); |
| conn_cb_0_count++; |
| }; |
| |
| std::unique_ptr<LowEnergyConnectionHandle> conn_handle; |
| auto conn_cb_1 = [&](auto result) { |
| ASSERT_EQ(fit::ok(), result); |
| conn_handle = std::move(result).value(); |
| }; |
| |
| conn_mgr()->Connect(peer_0->identifier(), conn_cb_0, kConnectionOptions); |
| conn_mgr()->Connect(peer_1->identifier(), conn_cb_1, kConnectionOptions); |
| EXPECT_EQ(Peer::ConnectionState::kInitializing, |
| peer_0->le()->connection_state()); |
| EXPECT_EQ(Peer::ConnectionState::kInitializing, |
| peer_1->le()->connection_state()); |
| |
| // Wait for peer_0 scanning to complete and kLECreateConnection command to be |
| // sent. |
| RunUntilIdle(); |
| |
| // After peer_0's connection request is cancelled, peer_1's connection request |
| // should succeed. |
| EXPECT_TRUE(conn_mgr()->Disconnect(peer_0->identifier())); |
| RunUntilIdle(); |
| EXPECT_EQ(conn_cb_0_count, 1); |
| ASSERT_TRUE(conn_handle); |
| EXPECT_EQ(conn_handle->peer_identifier(), peer_1->identifier()); |
| EXPECT_EQ(Peer::ConnectionState::kNotConnected, |
| peer_0->le()->connection_state()); |
| EXPECT_EQ(Peer::ConnectionState::kConnected, |
| peer_1->le()->connection_state()); |
| } |
| |
| TEST_F( |
| LowEnergyConnectionManagerTest, |
| DisconnectQueuedPendingConnectionAndThenPendingConnectionWithPendingConnector) { |
| auto peer_0 = peer_cache()->NewPeer(kAddress0, /*connectable=*/true); |
| auto fake_peer_0 = std::make_unique<FakePeer>(kAddress0, dispatcher()); |
| fake_peer_0->set_force_pending_connect(true); |
| test_device()->AddPeer(std::move(fake_peer_0)); |
| |
| auto peer_1 = peer_cache()->NewPeer(kAddress1, /*connectable=*/true); |
| test_device()->AddPeer(std::make_unique<FakePeer>(kAddress1, dispatcher())); |
| |
| int conn_cb_0_count = 0; |
| auto conn_cb_0 = [&](auto result) { |
| ASSERT_TRUE(result.is_error()); |
| EXPECT_EQ(HostError::kCanceled, result.error_value()); |
| EXPECT_EQ(peer_0->le()->connection_state(), |
| Peer::ConnectionState::kNotConnected); |
| conn_cb_0_count++; |
| }; |
| |
| int conn_cb_1_count = 0; |
| auto conn_cb_1 = [&](auto result) { |
| ASSERT_TRUE(result.is_error()); |
| EXPECT_EQ(HostError::kCanceled, result.error_value()); |
| EXPECT_EQ(peer_1->le()->connection_state(), |
| Peer::ConnectionState::kNotConnected); |
| conn_cb_1_count++; |
| }; |
| |
| conn_mgr()->Connect(peer_0->identifier(), conn_cb_0, kConnectionOptions); |
| conn_mgr()->Connect(peer_1->identifier(), conn_cb_1, kConnectionOptions); |
| EXPECT_EQ(Peer::ConnectionState::kInitializing, |
| peer_0->le()->connection_state()); |
| EXPECT_EQ(Peer::ConnectionState::kInitializing, |
| peer_1->le()->connection_state()); |
| |
| EXPECT_TRUE(conn_mgr()->Disconnect(peer_1->identifier())); |
| RunUntilIdle(); |
| EXPECT_EQ(conn_cb_0_count, 0); |
| EXPECT_EQ(conn_cb_1_count, 1); |
| EXPECT_EQ(Peer::ConnectionState::kInitializing, |
| peer_0->le()->connection_state()); |
| EXPECT_EQ(Peer::ConnectionState::kNotConnected, |
| peer_1->le()->connection_state()); |
| |
| EXPECT_TRUE(conn_mgr()->Disconnect(peer_0->identifier())); |
| RunUntilIdle(); |
| EXPECT_EQ(conn_cb_0_count, 1); |
| EXPECT_EQ(Peer::ConnectionState::kNotConnected, |
| peer_0->le()->connection_state()); |
| EXPECT_EQ(Peer::ConnectionState::kNotConnected, |
| peer_1->le()->connection_state()); |
| } |
| |
| TEST_F(LowEnergyConnectionManagerTest, DisconnectUnknownPeer) { |
| // Unknown peers are inherently "not connected." |
| EXPECT_TRUE(conn_mgr()->Disconnect(PeerId(999))); |
| } |
| |
| TEST_F(LowEnergyConnectionManagerTest, DisconnectUnconnectedPeer) { |
| auto* peer = peer_cache()->NewPeer(kAddress0, /*connectable=*/true); |
| test_device()->AddPeer(std::make_unique<FakePeer>(kAddress0, dispatcher())); |
| |
| // This returns true so long the peer is not connected. |
| EXPECT_TRUE(conn_mgr()->Disconnect(peer->identifier())); |
| } |
| |
| TEST_F(LowEnergyConnectionManagerTest, Disconnect) { |
| auto* peer = peer_cache()->NewPeer(kAddress0, /*connectable=*/true); |
| test_device()->AddPeer(std::make_unique<FakePeer>(kAddress0, dispatcher())); |
| |
| int closed_count = 0; |
| auto closed_cb = [&closed_count] { closed_count++; }; |
| |
| std::vector<std::unique_ptr<LowEnergyConnectionHandle>> conn_handles; |
| auto success_cb = [&conn_handles, &closed_cb](auto result) { |
| ASSERT_EQ(fit::ok(), result); |
| auto conn_handle = std::move(result).value(); |
| conn_handle->set_closed_callback(closed_cb); |
| conn_handles.push_back(std::move(conn_handle)); |
| }; |
| |
| // Issue two connection refs. |
| conn_mgr()->Connect(peer->identifier(), success_cb, kConnectionOptions); |
| conn_mgr()->Connect(peer->identifier(), success_cb, kConnectionOptions); |
| |
| RunUntilIdle(); |
| |
| ASSERT_EQ(2u, conn_handles.size()); |
| |
| EXPECT_TRUE(conn_mgr()->Disconnect(peer->identifier())); |
| |
| bool peer_removed = peer_cache()->RemoveDisconnectedPeer(peer->identifier()); |
| EXPECT_TRUE(peer_removed); |
| |
| RunUntilIdle(); |
| |
| EXPECT_EQ(2, closed_count); |
| EXPECT_TRUE(connected_peers().empty()); |
| EXPECT_TRUE(canceled_peers().empty()); |
| |
| // The central pause timeout handler should not run. |
| RunFor(kLEConnectionPauseCentral); |
| } |
| |
| TEST_F(LowEnergyConnectionManagerTest, |
| IntentionalDisconnectDisablesAutoConnectBehavior) { |
| auto* peer = peer_cache()->NewPeer(kAddress0, /*connectable=*/true); |
| test_device()->AddPeer(std::make_unique<FakePeer>(kAddress0, dispatcher())); |
| |
| std::vector<std::unique_ptr<LowEnergyConnectionHandle>> conn_handles; |
| auto success_cb = [&conn_handles](auto result) { |
| ASSERT_EQ(fit::ok(), result); |
| conn_handles.push_back(std::move(result).value()); |
| }; |
| |
| sm::PairingData data; |
| data.peer_ltk = sm::LTK(); |
| data.local_ltk = sm::LTK(); |
| EXPECT_TRUE(peer_cache()->StoreLowEnergyBond(peer->identifier(), data)); |
| |
| // Issue connection ref. |
| conn_mgr()->Connect(peer->identifier(), success_cb, kConnectionOptions); |
| RunUntilIdle(); |
| |
| // Bonded peer should have auto-connection enabled. |
| EXPECT_TRUE(peer->le()->should_auto_connect()); |
| |
| // Explicit disconnect should disable the auto-connection property. |
| EXPECT_TRUE(conn_mgr()->Disconnect(peer->identifier())); |
| RunUntilIdle(); |
| EXPECT_FALSE(peer->le()->should_auto_connect()); |
| |
| // Intentional re-connection should re-enable the auto-connection property. |
| conn_mgr()->Connect(peer->identifier(), success_cb, kConnectionOptions); |
| RunUntilIdle(); |
| EXPECT_TRUE(peer->le()->should_auto_connect()); |
| } |
| |
| TEST_F(LowEnergyConnectionManagerTest, |
| IncidentalDisconnectDoesNotAffectAutoConnectBehavior) { |
| auto* peer = peer_cache()->NewPeer(kAddress0, /*connectable=*/true); |
| test_device()->AddPeer(std::make_unique<FakePeer>(kAddress0, dispatcher())); |
| |
| std::vector<std::unique_ptr<LowEnergyConnectionHandle>> conn_handles; |
| auto success_cb = [&conn_handles](auto result) { |
| ASSERT_EQ(fit::ok(), result); |
| conn_handles.push_back(std::move(result).value()); |
| }; |
| |
| sm::PairingData data; |
| data.peer_ltk = sm::LTK(); |
| data.local_ltk = sm::LTK(); |
| EXPECT_TRUE(peer_cache()->StoreLowEnergyBond(peer->identifier(), data)); |
| |
| // Issue connection ref. |
| conn_mgr()->Connect(peer->identifier(), success_cb, kConnectionOptions); |
| RunUntilIdle(); |
| |
| // Bonded peer should have auto-connection enabled. |
| EXPECT_TRUE(peer->le()->should_auto_connect()); |
| |
| // Incidental disconnect should NOT disable the auto-connection property. |
| ASSERT_TRUE(conn_handles.size()); |
| conn_handles[0] = nullptr; |
| RunUntilIdle(); |
| EXPECT_TRUE(peer->le()->should_auto_connect()); |
| } |
| |
| TEST_F(LowEnergyConnectionManagerTest, DisconnectThrice) { |
| auto* peer = peer_cache()->NewPeer(kAddress0, /*connectable=*/true); |
| test_device()->AddPeer(std::make_unique<FakePeer>(kAddress0, dispatcher())); |
| |
| int closed_count = 0; |
| auto closed_cb = [&closed_count] { closed_count++; }; |
| |
| std::unique_ptr<LowEnergyConnectionHandle> conn_handle; |
| auto success_cb = [&closed_cb, &conn_handle](auto result) { |
| ASSERT_EQ(fit::ok(), result); |
| conn_handle = std::move(result).value(); |
| ASSERT_TRUE(conn_handle); |
| conn_handle->set_closed_callback(closed_cb); |
| }; |
| |
| conn_mgr()->Connect(peer->identifier(), success_cb, kConnectionOptions); |
| |
| RunUntilIdle(); |
| |
| EXPECT_TRUE(conn_mgr()->Disconnect(peer->identifier())); |
| |
| // Try to disconnect again while the first disconnection is in progress. |
| EXPECT_TRUE(conn_mgr()->Disconnect(peer->identifier())); |
| |
| RunUntilIdle(); |
| |
| // The single ref should get only one "closed" call. |
| EXPECT_EQ(1, closed_count); |
| EXPECT_TRUE(connected_peers().empty()); |
| EXPECT_TRUE(canceled_peers().empty()); |
| |
| // Try to disconnect once more, now that the link is gone. |
| EXPECT_TRUE(conn_mgr()->Disconnect(peer->identifier())); |
| } |
| |
| // Tests when a link is lost without explicitly disconnecting |
| TEST_F(LowEnergyConnectionManagerTest, DisconnectEvent) { |
| auto* peer = peer_cache()->NewPeer(kAddress0, /*connectable=*/true); |
| |
| test_device()->AddPeer(std::make_unique<FakePeer>(kAddress0, dispatcher())); |
| |
| int closed_count = 0; |
| auto closed_cb = [&closed_count] { closed_count++; }; |
| |
| std::vector<std::unique_ptr<LowEnergyConnectionHandle>> conn_handles; |
| auto success_cb = [&conn_handles, &closed_cb](auto result) { |
| ASSERT_EQ(fit::ok(), result); |
| auto conn_handle = std::move(result).value(); |
| conn_handle->set_closed_callback(closed_cb); |
| conn_handles.push_back(std::move(conn_handle)); |
| }; |
| |
| // Issue two connection refs. |
| conn_mgr()->Connect(peer->identifier(), success_cb, kConnectionOptions); |
| conn_mgr()->Connect(peer->identifier(), success_cb, kConnectionOptions); |
| |
| RunUntilIdle(); |
| |
| ASSERT_EQ(2u, conn_handles.size()); |
| |
| // This makes FakeController send us HCI Disconnection Complete events. |
| test_device()->Disconnect(kAddress0); |
| |
| RunUntilIdle(); |
| |
| EXPECT_EQ(2, closed_count); |
| } |
| |
| TEST_F(LowEnergyConnectionManagerTest, DisconnectAfterRefsReleased) { |
| auto* peer = peer_cache()->NewPeer(kAddress0, /*connectable=*/true); |
| test_device()->AddPeer(std::make_unique<FakePeer>(kAddress0, dispatcher())); |
| |
| std::unique_ptr<LowEnergyConnectionHandle> conn_handle; |
| auto success_cb = [&conn_handle](auto result) { |
| ASSERT_EQ(fit::ok(), result); |
| conn_handle = std::move(result).value(); |
| }; |
| |
| conn_mgr()->Connect(peer->identifier(), success_cb, kConnectionOptions); |
| |
| RunUntilIdle(); |
| |
| ASSERT_TRUE(conn_handle); |
| conn_handle.reset(); |
| |
| // Try to disconnect while the zero-refs connection is being disconnected. |
| EXPECT_TRUE(conn_mgr()->Disconnect(peer->identifier())); |
| |
| RunUntilIdle(); |
| |
| EXPECT_TRUE(connected_peers().empty()); |
| EXPECT_TRUE(canceled_peers().empty()); |
| } |
| |
| TEST_F(LowEnergyConnectionManagerTest, |
| DisconnectAfterSecondConnectionRequestInvalidatesRefs) { |
| auto* peer = peer_cache()->NewPeer(kAddress0, /*connectable=*/true); |
| test_device()->AddPeer(std::make_unique<FakePeer>(kAddress0, dispatcher())); |
| |
| std::unique_ptr<LowEnergyConnectionHandle> conn_handle_0; |
| auto success_cb = [&conn_handle_0](auto result) { |
| ASSERT_EQ(fit::ok(), result); |
| conn_handle_0 = std::move(result).value(); |
| ASSERT_TRUE(conn_handle_0); |
| EXPECT_TRUE(conn_handle_0->active()); |
| }; |
| |
| conn_mgr()->Connect(peer->identifier(), success_cb, kConnectionOptions); |
| RunUntilIdle(); |
| ASSERT_TRUE(conn_handle_0); |
| EXPECT_TRUE(conn_handle_0->active()); |
| |
| std::unique_ptr<LowEnergyConnectionHandle> conn_handle_1; |
| auto ref_cb = [&conn_handle_1](auto result) { |
| ASSERT_EQ(fit::ok(), result); |
| conn_handle_1 = std::move(result).value(); |
| }; |
| |
| // Callback should be run synchronously with success status because connection |
| // already exists. |
| conn_mgr()->Connect(peer->identifier(), ref_cb, kConnectionOptions); |
| EXPECT_TRUE(conn_handle_1); |
| EXPECT_TRUE(conn_handle_1->active()); |
| |
| // This should invalidate the refs. |
| EXPECT_TRUE(conn_mgr()->Disconnect(peer->identifier())); |
| EXPECT_FALSE(conn_handle_1->active()); |
| EXPECT_FALSE(conn_handle_0->active()); |
| |
| RunUntilIdle(); |
| } |
| |
| // This tests that a connection reference callback succeeds if a HCI |
| // Disconnection Complete event is received for the corresponding ACL link |
| // immediately after the callback gets run. |
| TEST_F(LowEnergyConnectionManagerTest, DisconnectCompleteEventAfterConnect) { |
| auto* peer = peer_cache()->NewPeer(kAddress0, /*connectable=*/true); |
| test_device()->AddPeer(std::make_unique<FakePeer>(kAddress0, dispatcher())); |
| |
| std::unique_ptr<LowEnergyConnectionHandle> conn_handle; |
| auto success_cb = [&conn_handle](auto result) { |
| ASSERT_EQ(fit::ok(), result); |
| conn_handle = std::move(result).value(); |
| EXPECT_TRUE(conn_handle->active()); |
| }; |
| |
| conn_mgr()->Connect(peer->identifier(), success_cb, kConnectionOptions); |
| RunUntilIdle(); |
| ASSERT_TRUE(conn_handle); |
| |
| // Request a new reference. Disconnect the link before the reference is |
| // received. |
| size_t ref_cb_count = 0; |
| auto ref_cb = [&ref_cb_count](auto result) { |
| ref_cb_count++; |
| EXPECT_EQ(fit::ok(), result); |
| }; |
| |
| size_t disconn_cb_count = 0; |
| auto disconn_cb = |
| [this, ref_cb, peer, &disconn_cb_count, &ref_cb_count](auto) { |
| disconn_cb_count++; |
| // The link is gone but conn_mgr() hasn't updated the connection state |
| // yet. The request to connect will attempt to add a new reference which |
| // will succeed because ref_cb is called synchronously. |
| EXPECT_EQ(0u, ref_cb_count); |
| conn_mgr()->Connect(peer->identifier(), ref_cb, kConnectionOptions); |
| EXPECT_EQ(1u, ref_cb_count); |
| }; |
| conn_mgr()->SetDisconnectCallbackForTesting(disconn_cb); |
| |
| test_device()->SendDisconnectionCompleteEvent(conn_handle->handle()); |
| |
| RunUntilIdle(); |
| |
| EXPECT_EQ(1u, ref_cb_count); |
| EXPECT_EQ(1u, disconn_cb_count); |
| } |
| |
| TEST_F(LowEnergyConnectionManagerTest, |
| RemovePeerFromPeerCacheDuringDisconnection) { |
| auto* peer = peer_cache()->NewPeer(kAddress0, /*connectable=*/true); |
| test_device()->AddPeer(std::make_unique<FakePeer>(kAddress0, dispatcher())); |
| |
| std::unique_ptr<LowEnergyConnectionHandle> conn_handle; |
| auto success_cb = [&conn_handle](auto result) { |
| ASSERT_EQ(fit::ok(), result); |
| conn_handle = std::move(result).value(); |
| EXPECT_TRUE(conn_handle->active()); |
| }; |
| |
| conn_mgr()->Connect(peer->identifier(), success_cb, kConnectionOptions); |
| RunUntilIdle(); |
| ASSERT_TRUE(conn_handle); |
| |
| // This should invalidate the ref that was bound to |ref_cb|. |
| const PeerId id = peer->identifier(); |
| EXPECT_TRUE(conn_mgr()->Disconnect(id)); |
| ASSERT_FALSE(peer->le()->connected()); |
| EXPECT_FALSE(conn_handle->active()); |
| |
| EXPECT_TRUE(peer_cache()->RemoveDisconnectedPeer(id)); |
| |
| RunUntilIdle(); |
| |
| EXPECT_FALSE(peer_cache()->FindById(id)); |
| EXPECT_FALSE(peer_cache()->FindByAddress(kAddress0)); |
| } |
| |
| // Listener receives remote initiated connection ref. |
| TEST_F(LowEnergyConnectionManagerTest, RegisterRemoteInitiatedLink) { |
| test_device()->AddPeer(std::make_unique<FakePeer>(kAddress0, dispatcher())); |
| |
| // First create a fake incoming connection. |
| test_device()->ConnectLowEnergy(kAddress0); |
| |
| RunUntilIdle(); |
| |
| auto link = MoveLastRemoteInitiated(); |
| ASSERT_TRUE(link); |
| |
| std::unique_ptr<LowEnergyConnectionHandle> conn_handle; |
| conn_mgr()->RegisterRemoteInitiatedLink( |
| std::move(link), BondableMode::Bondable, [&](auto result) { |
| ASSERT_EQ(fit::ok(), result); |
| conn_handle = std::move(result).value(); |
| }); |
| // A Peer should now exist in the cache. |
| auto* peer = peer_cache()->FindByAddress(kAddress0); |
| EXPECT_EQ(peer->le()->connection_state(), |
| Peer::ConnectionState::kInitializing); |
| |
| RunUntilIdle(); |
| |
| ASSERT_TRUE(conn_handle); |
| EXPECT_TRUE(conn_handle->active()); |
| ASSERT_TRUE(peer); |
| EXPECT_EQ(peer->identifier(), conn_handle->peer_identifier()); |
| EXPECT_TRUE(peer->connected()); |
| EXPECT_TRUE(peer->le()->connected()); |
| EXPECT_TRUE(peer->version().has_value()); |
| EXPECT_TRUE(peer->le()->features().has_value()); |
| |
| conn_handle = nullptr; |
| |
| RunUntilIdle(); |
| EXPECT_TRUE(connected_peers().empty()); |
| } |
| |
| TEST_F(LowEnergyConnectionManagerTest, |
| RegisterRemoteInitiatedLinkDuringLocalInitiatedLinkConnecting) { |
| auto* peer = peer_cache()->NewPeer(kAddress0, /*connectable=*/true); |
| auto fake_peer = std::make_unique<FakePeer>(kAddress0, dispatcher()); |
| fake_peer->set_force_pending_connect(true); |
| test_device()->AddPeer(std::move(fake_peer)); |
| |
| // Create a fake incoming connection. |
| test_device()->ConnectLowEnergy(kAddress0); |
| RunUntilIdle(); |
| auto link = MoveLastRemoteInitiated(); |
| ASSERT_TRUE(link); |
| |
| // Create a pending outgoing connection. |
| ConnectionResult result = fit::ok(nullptr); |
| auto callback = [&result](auto res) { result = std::move(res); }; |
| conn_mgr()->Connect(peer->identifier(), callback, kConnectionOptions); |
| |
| std::unique_ptr<LowEnergyConnectionHandle> conn_handle; |
| conn_mgr()->RegisterRemoteInitiatedLink( |
| std::move(link), BondableMode::Bondable, [&](auto result) { |
| ASSERT_EQ(fit::ok(), result); |
| conn_handle = std::move(result).value(); |
| }); |
| RunUntilIdle(); |
| ASSERT_TRUE(conn_handle); |
| |
| // Local connector result handler should not crash when it finds that |
| // connection to peer already exists. |
| RunFor(kLECreateConnectionTimeout); |
| // An error should be returned if the connection complete was incorrectly not |
| // matched to the pending connection request (see https://fxbug.dev/42148050). |
| // In the future it may make sense to return success because a link to the |
| // peer already exists. |
| ASSERT_TRUE(result.is_error()); |
| EXPECT_TRUE(peer->le()->connected()); |
| } |
| |
| TEST_F(LowEnergyConnectionManagerTest, |
| RegisterRemoteInitiatedLinkDuringLocalInitiatedConnectionScanning) { |
| auto* peer = peer_cache()->NewPeer(kAddress0, /*connectable=*/true); |
| auto fake_peer = std::make_unique<FakePeer>(kAddress0, dispatcher()); |
| fake_peer->set_advertising_enabled(false); |
| test_device()->AddPeer(std::move(fake_peer)); |
| |
| // Create a fake incoming connection. |
| test_device()->ConnectLowEnergy(kAddress0); |
| RunUntilIdle(); |
| auto link = MoveLastRemoteInitiated(); |
| ASSERT_TRUE(link); |
| |
| // Create a pending outgoing connection. |
| ConnectionResult result = fit::ok(nullptr); |
| auto callback = [&result](auto res) { result = std::move(res); }; |
| conn_mgr()->Connect(peer->identifier(), callback, kConnectionOptions); |
| |
| std::unique_ptr<LowEnergyConnectionHandle> conn_handle; |
| conn_mgr()->RegisterRemoteInitiatedLink( |
| std::move(link), BondableMode::Bondable, [&](auto result) { |
| ASSERT_EQ(fit::ok(), result); |
| conn_handle = std::move(result).value(); |
| }); |
| RunUntilIdle(); |
| ASSERT_TRUE(conn_handle); |
| |
| // Local connector result handler should not crash when it finds that |
| // connection to peer already exists. |
| RunFor(kLEGeneralCepScanTimeout); |
| ASSERT_TRUE(result.is_error()); |
| EXPECT_TRUE(peer->le()->connected()); |
| } |
| |
| // Listener receives remote initiated connection ref for a known peer with the |
| // same BR/EDR address. |
| TEST_F(LowEnergyConnectionManagerTest, |
| IncomingConnectionUpgradesKnownBrEdrPeerToDualMode) { |
| Peer* peer = peer_cache()->NewPeer(kAddrAlias0, /*connectable=*/true); |
| ASSERT_TRUE(peer); |
| ASSERT_EQ(peer, peer_cache()->FindByAddress(kAddress0)); |
| ASSERT_EQ(TechnologyType::kClassic, peer->technology()); |
| |
| test_device()->AddPeer(std::make_unique<FakePeer>(kAddress0, dispatcher())); |
| |
| // First create a fake incoming connection. |
| test_device()->ConnectLowEnergy(kAddress0); |
| |
| RunUntilIdle(); |
| |
| auto link = MoveLastRemoteInitiated(); |
| ASSERT_TRUE(link); |
| |
| std::unique_ptr<LowEnergyConnectionHandle> conn_handle; |
| conn_mgr()->RegisterRemoteInitiatedLink( |
| std::move(link), BondableMode::Bondable, [&conn_handle](auto result) { |
| ASSERT_EQ(fit::ok(), result); |
| conn_handle = std::move(result).value(); |
| }); |
| RunUntilIdle(); |
| ASSERT_TRUE(conn_handle); |
| |
| EXPECT_EQ(peer->identifier(), conn_handle->peer_identifier()); |
| EXPECT_EQ(TechnologyType::kDualMode, peer->technology()); |
| } |
| |
| // Successful connection to a peer whose address type is kBREDR. |
| // TODO(https://fxbug.dev/42102158): This test will likely become obsolete when |
| // LE connections are based on the presence of LowEnergyData in a Peer and no |
| // address type enum exists. |
| TEST_F(LowEnergyConnectionManagerTest, |
| ConnectAndDisconnectDualModeDeviceWithBrEdrAddress) { |
| Peer* peer = peer_cache()->NewPeer(kAddrAlias0, /*connectable=*/true); |
| ASSERT_TRUE(peer); |
| ASSERT_TRUE(peer->bredr()); |
| |
| peer->MutLe(); |
| ASSERT_EQ(TechnologyType::kDualMode, peer->technology()); |
| ASSERT_EQ(peer, peer_cache()->FindByAddress(kAddress0)); |
| ASSERT_EQ(DeviceAddress::Type::kBREDR, peer->address().type()); |
| |
| // Only the LE transport connects in this test, so only add an LE FakePeer to |
| // FakeController. |
| test_device()->AddPeer(std::make_unique<FakePeer>(kAddress0, dispatcher())); |
| |
| std::unique_ptr<LowEnergyConnectionHandle> conn_handle; |
| auto callback = [&conn_handle](auto result) { |
| ASSERT_EQ(fit::ok(), result); |
| conn_handle = std::move(result).value(); |
| }; |
| |
| EXPECT_TRUE(connected_peers().empty()); |
| conn_mgr()->Connect(peer->identifier(), callback, kConnectionOptions); |
| EXPECT_EQ(Peer::ConnectionState::kInitializing, |
| peer->le()->connection_state()); |
| |
| RunUntilIdle(); |
| |
| EXPECT_EQ(1u, connected_peers().size()); |
| EXPECT_EQ(1u, connected_peers().count(kAddress0)); |
| |
| ASSERT_TRUE(conn_handle); |
| EXPECT_TRUE(conn_handle->active()); |
| EXPECT_EQ(peer->identifier(), conn_handle->peer_identifier()); |
| EXPECT_FALSE(peer->temporary()); |
| EXPECT_EQ(Peer::ConnectionState::kConnected, peer->le()->connection_state()); |
| |
| conn_handle = nullptr; |
| RunUntilIdle(); |
| EXPECT_EQ(0u, connected_peers().size()); |
| } |
| |
| // Tests that the central accepts the connection parameters that are sent from |
| // a fake peripheral and eventually applies them to the link. |
| TEST_F(LowEnergyConnectionManagerTest, |
| CentralAppliesL2capConnectionParameterUpdateRequestParams) { |
| // Set up a fake peer and a connection over which to process the L2CAP |
| // request. |
| test_device()->AddPeer(std::make_unique<FakePeer>(kAddress0, dispatcher())); |
| auto* peer = peer_cache()->NewPeer(kAddress0, /*connectable=*/true); |
| ASSERT_TRUE(peer); |
| |
| std::unique_ptr<LowEnergyConnectionHandle> conn_handle; |
| auto conn_cb = [&conn_handle](auto result) { |
| ASSERT_EQ(fit::ok(), result); |
| conn_handle = std::move(result).value(); |
| }; |
| conn_mgr()->Connect(peer->identifier(), conn_cb, kConnectionOptions); |
| |
| RunUntilIdle(); |
| ASSERT_TRUE(conn_handle); |
| |
| hci_spec::LEPreferredConnectionParameters preferred( |
| hci_spec::kLEConnectionIntervalMin, |
| hci_spec::kLEConnectionIntervalMax, |
| hci_spec::kLEConnectionLatencyMax, |
| hci_spec::kLEConnectionSupervisionTimeoutMax); |
| |
| std::optional<hci_spec::LEConnectionParameters> actual; |
| |
| auto conn_params_updated_cb = [&](const auto& addr, const auto& params) { |
| actual = params; |
| }; |
| test_device()->set_le_connection_parameters_callback(conn_params_updated_cb); |
| |
| fake_l2cap()->TriggerLEConnectionParameterUpdate(conn_handle->handle(), |
| preferred); |
| |
| // These connection update events for the wrong handle should be ignored. |
| // Send twice: once before the parameter request is processed, and once after |
| // the request has been processed. |
| hci_spec::LEConnectionParameters wrong_handle_conn_params(0, 1, 2); |
| test_device()->SendLEConnectionUpdateCompleteSubevent( |
| conn_handle->handle() + 1, wrong_handle_conn_params); |
| RunUntilIdle(); |
| |
| test_device()->SendLEConnectionUpdateCompleteSubevent( |
| conn_handle->handle() + 1, wrong_handle_conn_params); |
| |
| RunUntilIdle(); |
| |
| ASSERT_TRUE(actual.has_value()); |
| ASSERT_TRUE(peer->le()); |
| EXPECT_EQ(preferred, *peer->le()->preferred_connection_parameters()); |
| EXPECT_EQ(actual.value(), *peer->le()->connection_parameters()); |
| } |
| |
| TEST_F(LowEnergyConnectionManagerTest, L2CAPSignalLinkError) { |
| // Set up a fake peer and a connection over which to process the L2CAP |
| // request. |
| test_device()->AddPeer(std::make_unique<FakePeer>(kAddress0, dispatcher())); |
| auto* peer = peer_cache()->NewPeer(kAddress0, /*connectable=*/true); |
| ASSERT_TRUE(peer); |
| |
| l2cap::testing::FakeChannel::WeakPtr smp_chan; |
| auto l2cap_chan_cb = [&smp_chan](auto chan) { smp_chan = chan; }; |
| fake_l2cap()->set_channel_callback(l2cap_chan_cb); |
| |
| std::unique_ptr<LowEnergyConnectionHandle> conn_handle; |
| auto conn_cb = [&conn_handle](auto result) { |
| ASSERT_EQ(fit::ok(), result); |
| conn_handle = std::move(result).value(); |
| }; |
| conn_mgr()->Connect(peer->identifier(), conn_cb, kConnectionOptions); |
| |
| RunUntilIdle(); |
| ASSERT_TRUE(conn_handle); |
| ASSERT_TRUE(smp_chan.is_alive()); |
| ASSERT_EQ(1u, connected_peers().size()); |
| |
| // Signaling a link error through the channel should disconnect the link. |
| smp_chan->SignalLinkError(); |
| |
| RunUntilIdle(); |
| EXPECT_TRUE(connected_peers().empty()); |
| } |
| |
| TEST_F(LowEnergyConnectionManagerTest, AttBearerSignalsLinkError) { |
| // Set up a fake peer and a connection over which to process the L2CAP |
| // request. |
| test_device()->AddPeer(std::make_unique<FakePeer>(kAddress0, dispatcher())); |
| auto* peer = peer_cache()->NewPeer(kAddress0, /*connectable=*/true); |
| ASSERT_TRUE(peer); |
| |
| l2cap::testing::FakeChannel::WeakPtr att_chan; |
| auto l2cap_chan_cb = [&att_chan](l2cap::testing::FakeChannel::WeakPtr chan) { |
| if (chan->id() == l2cap::kATTChannelId) { |
| att_chan = std::move(chan); |
| } |
| }; |
| fake_l2cap()->set_channel_callback(l2cap_chan_cb); |
| |
| std::unique_ptr<LowEnergyConnectionHandle> conn_handle; |
| auto conn_cb = [&conn_handle](auto result) { |
| ASSERT_EQ(fit::ok(), result); |
| conn_handle = std::move(result).value(); |
| }; |
| conn_mgr()->Connect(peer->identifier(), conn_cb, kConnectionOptions); |
| |
| RunUntilIdle(); |
| ASSERT_TRUE(conn_handle); |
| ASSERT_TRUE(att_chan.is_alive()); |
| ASSERT_EQ(1u, connected_peers().size()); |
| |
| // Receiving an invalid SDU should cause att::Bearer to signal a link error. |
| DynamicByteBuffer too_large_att_sdu(att::kLEMaxMTU + 1); |
| too_large_att_sdu.Fill(0x00); |
| att_chan->Receive(too_large_att_sdu); |
| |
| RunUntilIdle(); |
| ASSERT_FALSE(att_chan.is_alive()); |
| EXPECT_TRUE(connected_peers().empty()); |
| } |
| |
| TEST_F(LowEnergyConnectionManagerTest, OutboundConnectATTChannelActivateFails) { |
| test_device()->AddPeer(std::make_unique<FakePeer>(kAddress0, dispatcher())); |
| auto* peer = peer_cache()->NewPeer(kAddress0, /*connectable=*/true); |
| ASSERT_TRUE(peer); |
| |
| std::optional<l2cap::testing::FakeChannel::WeakPtr> att_chan; |
| auto l2cap_chan_cb = [&att_chan](l2cap::testing::FakeChannel::WeakPtr chan) { |
| if (chan->id() == l2cap::kATTChannelId) { |
| // Cause att::Bearer construction/activation to fail. |
| chan->set_activate_fails(true); |
| att_chan = std::move(chan); |
| } |
| }; |
| fake_l2cap()->set_channel_callback(l2cap_chan_cb); |
| |
| std::optional<LowEnergyConnectionManager::ConnectionResult> result; |
| auto conn_cb = [&](LowEnergyConnectionManager::ConnectionResult cb_result) { |
| result = std::move(cb_result); |
| }; |
| conn_mgr()->Connect(peer->identifier(), conn_cb, kConnectionOptions); |
| |
| RunUntilIdle(); |
| ASSERT_TRUE(att_chan.has_value()); |
| // The link should have been closed due to the error, invalidating the |
| // channel. |
| EXPECT_FALSE(att_chan.value().is_alive()); |
| ASSERT_TRUE(result.has_value()); |
| EXPECT_EQ(HostError::kFailed, result->error_value()); |
| EXPECT_TRUE(connected_peers().empty()); |
| } |
| |
| TEST_F(LowEnergyConnectionManagerTest, |
| InboundConnectionATTChannelActivateFails) { |
| test_device()->AddPeer(std::make_unique<FakePeer>(kAddress0, dispatcher())); |
| auto* peer = peer_cache()->NewPeer(kAddress0, /*connectable=*/true); |
| ASSERT_TRUE(peer); |
| |
| std::optional<l2cap::testing::FakeChannel::WeakPtr> att_chan; |
| auto l2cap_chan_cb = [&att_chan](l2cap::testing::FakeChannel::WeakPtr chan) { |
| if (chan->id() == l2cap::kATTChannelId) { |
| // Cause att::Bearer construction/activation to fail. |
| chan->set_activate_fails(true); |
| att_chan = std::move(chan); |
| } |
| }; |
| fake_l2cap()->set_channel_callback(l2cap_chan_cb); |
| |
| std::optional<LowEnergyConnectionManager::ConnectionResult> result; |
| auto conn_cb = [&](LowEnergyConnectionManager::ConnectionResult cb_result) { |
| result = std::move(cb_result); |
| }; |
| test_device()->ConnectLowEnergy(kAddress0); |
| RunUntilIdle(); |
| auto link = MoveLastRemoteInitiated(); |
| ASSERT_TRUE(link); |
| conn_mgr()->RegisterRemoteInitiatedLink( |
| std::move(link), BondableMode::Bondable, std::move(conn_cb)); |
| |
| RunUntilIdle(); |
| ASSERT_TRUE(att_chan.has_value()); |
| // The link should have been closed due to the error, invalidating the |
| // channel. |
| EXPECT_FALSE(att_chan.value().is_alive()); |
| ASSERT_TRUE(result.has_value()); |
| EXPECT_EQ(HostError::kFailed, result->error_value()); |
| EXPECT_TRUE(connected_peers().empty()); |
| } |
| |
| TEST_F(LowEnergyConnectionManagerTest, LinkErrorDuringInterrogation) { |
| test_device()->AddPeer(std::make_unique<FakePeer>(kAddress0, dispatcher())); |
| auto* peer = peer_cache()->NewPeer(kAddress0, /*connectable=*/true); |
| ASSERT_TRUE(peer); |
| |
| // Get an arbitrary channel in order to signal a link error. |
| l2cap::testing::FakeChannel::WeakPtr chan; |
| auto l2cap_chan_cb = [&chan](l2cap::testing::FakeChannel::WeakPtr cb_chan) { |
| chan = std::move(cb_chan); |
| }; |
| fake_l2cap()->set_channel_callback(l2cap_chan_cb); |
| |
| // Cause interrogation to stall so that we can simulate a link error. |
| fit::closure send_read_remote_features_rsp; |
| test_device()->pause_responses_for_opcode( |
| hci_spec::kLEReadRemoteFeatures, [&](fit::closure unpause) { |
| send_read_remote_features_rsp = std::move(unpause); |
| }); |
| |
| std::optional<LowEnergyConnectionManager::ConnectionResult> result; |
| auto conn_cb = [&](LowEnergyConnectionManager::ConnectionResult cb_result) { |
| result = std::move(cb_result); |
| }; |
| conn_mgr()->Connect(peer->identifier(), conn_cb, kConnectionOptions); |
| |
| RunUntilIdle(); |
| ASSERT_TRUE(chan.is_alive()); |
| fake_l2cap()->TriggerLinkError(chan->link_handle()); |
| |
| send_read_remote_features_rsp(); |
| |
| RunUntilIdle(); |
| ASSERT_TRUE(result.has_value()); |
| ASSERT_TRUE(result->is_error()); |
| EXPECT_EQ(HostError::kFailed, result->error_value()); |
| EXPECT_TRUE(connected_peers().empty()); |
| } |
| |
| TEST_F(LowEnergyConnectionManagerTest, PairUnconnectedPeer) { |
| auto* peer = peer_cache()->NewPeer(kAddress0, /*connectable=*/true); |
| EXPECT_TRUE(peer->temporary()); |
| ASSERT_EQ(peer_cache()->count(), 1u); |
| uint count_cb_called = 0; |
| auto cb = [&count_cb_called](sm::Result<> status) { |
| EXPECT_EQ(ToResult(bt::HostError::kNotFound), status); |
| count_cb_called++; |
| }; |
| conn_mgr()->Pair(peer->identifier(), |
| sm::SecurityLevel::kEncrypted, |
| sm::BondableMode::Bondable, |
| cb); |
| ASSERT_EQ(count_cb_called, 1u); |
| } |
| |
| TEST_F(LowEnergyConnectionManagerTest, PairWithBondableModes) { |
| // clang-format on |
| auto* peer = peer_cache()->NewPeer(kAddress0, /*connectable=*/true); |
| EXPECT_TRUE(peer->temporary()); |
| |
| auto fake_peer = std::make_unique<FakePeer>(kAddress0, dispatcher()); |
| test_device()->AddPeer(std::move(fake_peer)); |
| |
| std::unique_ptr<LowEnergyConnectionHandle> conn_handle; |
| auto callback = [&conn_handle](auto result) { |
| ASSERT_EQ(fit::ok(), result); |
| conn_handle = std::move(result).value(); |
| EXPECT_TRUE(conn_handle); |
| EXPECT_TRUE(conn_handle->active()); |
| }; |
| |
| conn_mgr()->Connect(peer->identifier(), callback, kConnectionOptions); |
| ASSERT_TRUE(peer->le()); |
| |
| RunUntilIdle(); |
| TestSm::WeakPtr mock_sm = TestSmByHandle(conn_handle->handle()); |
| ASSERT_TRUE(mock_sm.is_alive()); |
| |
| ASSERT_EQ(Peer::ConnectionState::kConnected, peer->le()->connection_state()); |
| |
| EXPECT_FALSE(mock_sm->last_requested_upgrade().has_value()); |
| conn_mgr()->Pair(peer->identifier(), |
| sm::SecurityLevel::kEncrypted, |
| sm::BondableMode::Bondable, |
| [](sm::Result<> cb_status) {}); |
| RunUntilIdle(); |
| |
| EXPECT_EQ(BondableMode::Bondable, mock_sm->bondable_mode()); |
| EXPECT_EQ(sm::SecurityLevel::kEncrypted, mock_sm->last_requested_upgrade()); |
| |
| conn_mgr()->Pair(peer->identifier(), |
| sm::SecurityLevel::kAuthenticated, |
| sm::BondableMode::NonBondable, |
| [](sm::Result<> cb_status) {}); |
| RunUntilIdle(); |
| |
| EXPECT_EQ(BondableMode::NonBondable, mock_sm->bondable_mode()); |
| EXPECT_EQ(sm::SecurityLevel::kAuthenticated, |
| mock_sm->last_requested_upgrade()); |
| } |
| |
| TEST_F(LowEnergyConnectionManagerTest, ConnectAndDiscoverByServiceWithoutUUID) { |
| auto* peer = peer_cache()->NewPeer(kAddress0, /*connectable=*/true); |
| |
| bool cb_called = false; |
| auto expect_uuids = [&cb_called](PeerId peer_id, auto uuids) { |
| ASSERT_TRUE(uuids.empty()); |
| cb_called = true; |
| }; |
| fake_gatt()->SetInitializeClientCallback(expect_uuids); |
| |
| auto fake_peer = std::make_unique<FakePeer>(kAddress0, dispatcher()); |
| test_device()->AddPeer(std::move(fake_peer)); |
| |
| std::unique_ptr<LowEnergyConnectionHandle> conn_handle; |
| auto callback = [&conn_handle](auto result) { |
| ASSERT_EQ(fit::ok(), result); |
| conn_handle = std::move(result).value(); |
| EXPECT_TRUE(conn_handle); |
| EXPECT_TRUE(conn_handle->active()); |
| }; |
| |
| LowEnergyConnectionOptions connection_options{.service_uuid = std::nullopt}; |
| conn_mgr()->Connect(peer->identifier(), callback, connection_options); |
| |
| RunUntilIdle(); |
| |
| ASSERT_TRUE(cb_called); |
| } |
| |
| TEST_F(LowEnergyConnectionManagerTest, ConnectAndDiscoverByServiceUuid) { |
| auto* peer = peer_cache()->NewPeer(kAddress0, /*connectable=*/true); |
| |
| UUID kConnectUuid({0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}); |
| std::array<UUID, 2> expected_uuids = {kConnectUuid, kGenericAccessService}; |
| |
| bool cb_called = false; |
| auto expect_uuid = [&cb_called, expected_uuids](PeerId peer_id, auto uuids) { |
| EXPECT_THAT(uuids, ::testing::UnorderedElementsAreArray(expected_uuids)); |
| cb_called = true; |
| }; |
| fake_gatt()->SetInitializeClientCallback(expect_uuid); |
| |
| auto fake_peer = std::make_unique<FakePeer>(kAddress0, dispatcher()); |
| test_device()->AddPeer(std::move(fake_peer)); |
| |
| std::unique_ptr<LowEnergyConnectionHandle> conn_handle; |
| auto callback = [&conn_handle](auto result) { |
| ASSERT_EQ(fit::ok(), result); |
| conn_handle = std::move(result).value(); |
| ASSERT_TRUE(conn_handle); |
| EXPECT_TRUE(conn_handle->active()); |
| }; |
| |
| LowEnergyConnectionOptions connection_options{ |
| .service_uuid = std::optional(kConnectUuid)}; |
| conn_mgr()->Connect(peer->identifier(), callback, connection_options); |
| |
| RunUntilIdle(); |
| |
| ASSERT_TRUE(cb_called); |
| } |
| |
| class ReadDeviceNameParameterizedFixture |
| : public LowEnergyConnectionManagerTest, |
| public ::testing::WithParamInterface<DynamicByteBuffer> {}; |
| |
| TEST_P(ReadDeviceNameParameterizedFixture, ReadDeviceNameParameterized) { |
| Peer* peer = peer_cache()->NewPeer(kAddress0, true); |
| std::unique_ptr<FakePeer> fake_peer = |
| std::make_unique<FakePeer>(kAddress0, dispatcher()); |
| test_device()->AddPeer(std::move(fake_peer)); |
| |
| // Set up GAP service |
| gatt::ServiceData service_data(gatt::ServiceKind::PRIMARY, |
| /*start=*/0x0001, |
| /*end=*/0x0009, |
| kGenericAccessService); |
| auto [remote_svc, service_client] = |
| fake_gatt()->AddPeerService(peer->identifier(), service_data); |
| |
| // Set up preferred connection parameters characteristic. |
| att::Handle char_handle = 0x0002; |
| att::Handle char_value_handle = 0x0003; |
| gatt::CharacteristicData char_data(gatt::kRead, |
| /*ext_props=*/std::nullopt, |
| char_handle, |
| char_value_handle, |
| kDeviceNameCharacteristic); |
| service_client->set_characteristics({char_data}); |
| |
| DynamicByteBuffer char_value = GetParam(); |
| service_client->set_read_request_callback( |
| [char_value_handle, char_value](att::Handle handle, auto read_cb) { |
| if (handle == char_value_handle) { |
| read_cb(fit::ok(), char_value, /*maybe_truncated=*/false); |
| } |
| }); |
| |
| std::unique_ptr<LowEnergyConnectionHandle> conn_ref; |
| auto callback = |
| [&conn_ref]( |
| fit::result<HostError, std::unique_ptr<LowEnergyConnectionHandle>> |
| result) { |
| ASSERT_EQ(fit::ok(), result); |
| conn_ref = std::move(result).value(); |
| }; |
| |
| conn_mgr()->Connect( |
| peer->identifier(), callback, LowEnergyConnectionOptions()); |
| |
| RunUntilIdle(); |
| EXPECT_TRUE(conn_ref); |
| ASSERT_TRUE(peer->name()); |
| EXPECT_EQ(peer->name_source(), Peer::NameSource::kGenericAccessService); |
| std::string device_name = peer->name().value(); |
| EXPECT_EQ(device_name, "abc"); |
| } |
| |
| StaticByteBuffer<3> b1{'a', 'b', 'c'}; |
| StaticByteBuffer<5> b2{'a', 'b', 'c', '\0', 'x'}; |
| INSTANTIATE_TEST_SUITE_P(ReadDeviceNameTest, |
| ReadDeviceNameParameterizedFixture, |
| ::testing::Values(DynamicByteBuffer(b1), |
| DynamicByteBuffer(b2))); |
| |
| TEST_F(LowEnergyConnectionManagerTest, ReadDeviceNameLong) { |
| Peer* peer = peer_cache()->NewPeer(kAddress0, true); |
| std::unique_ptr<FakePeer> fake_peer = |
| std::make_unique<FakePeer>(kAddress0, dispatcher()); |
| test_device()->AddPeer(std::move(fake_peer)); |
| |
| // Set up GAP service |
| gatt::ServiceData service_data(gatt::ServiceKind::PRIMARY, |
| /*start=*/0x0001, |
| /*end=*/0x0009, |
| kGenericAccessService); |
| auto [remote_svc, service_client] = |
| fake_gatt()->AddPeerService(peer->identifier(), service_data); |
| |
| // Set up preferred connection parameters characteristic. |
| att::Handle char_handle = 0x0002; |
| att::Handle char_value_handle = 0x0003; |
| gatt::CharacteristicData char_data(gatt::kRead, |
| /*ext_props=*/std::nullopt, |
| char_handle, |
| char_value_handle, |
| kDeviceNameCharacteristic); |
| service_client->set_characteristics({char_data}); |
| |
| // Max length read |
| StaticByteBuffer<att::kMaxAttributeValueLength> char_value; |
| char_value.Fill('a'); |
| service_client->set_read_request_callback( |
| [char_value_handle, char_value](att::Handle handle, auto read_cb) { |
| if (handle == char_value_handle) { |
| read_cb(fit::ok(), char_value, /*maybe_truncated=*/false); |
| } |
| }); |
| |
| std::unique_ptr<LowEnergyConnectionHandle> conn_ref; |
| auto callback = |
| [&conn_ref]( |
| fit::result<HostError, std::unique_ptr<LowEnergyConnectionHandle>> |
| result) { |
| ASSERT_EQ(fit::ok(), result); |
| conn_ref = std::move(result).value(); |
| }; |
| |
| conn_mgr()->Connect( |
| peer->identifier(), callback, LowEnergyConnectionOptions()); |
| |
| RunUntilIdle(); |
| EXPECT_TRUE(conn_ref); |
| ASSERT_TRUE(peer->name()); |
| EXPECT_EQ(peer->name_source(), Peer::NameSource::kGenericAccessService); |
| std::string device_name = peer->name().value(); |
| EXPECT_EQ(device_name, std::string(att::kMaxAttributeValueLength, 'a')); |
| } |
| |
| TEST_F(LowEnergyConnectionManagerTest, ReadAppearance) { |
| Peer* peer = peer_cache()->NewPeer(kAddress0, true); |
| std::unique_ptr<FakePeer> fake_peer = |
| std::make_unique<FakePeer>(kAddress0, dispatcher()); |
| test_device()->AddPeer(std::move(fake_peer)); |
| |
| // Set up GAP service |
| gatt::ServiceData service_data(gatt::ServiceKind::PRIMARY, |
| /*start=*/0x0001, |
| /*end=*/0x0009, |
| kGenericAccessService); |
| auto [remote_svc, service_client] = |
| fake_gatt()->AddPeerService(peer->identifier(), service_data); |
| |
| // Set up preferred connection parameters characteristic. |
| att::Handle char_handle = 0x0002; |
| att::Handle char_value_handle = 0x0003; |
| gatt::CharacteristicData char_data(gatt::kRead, |
| /*ext_props=*/std::nullopt, |
| char_handle, |
| char_value_handle, |
| kAppearanceCharacteristic); |
| service_client->set_characteristics({char_data}); |
| StaticByteBuffer char_value(0x01, 0x00); |
| service_client->set_read_request_callback( |
| [char_value_handle, char_value](att::Handle handle, auto read_cb) { |
| if (handle == char_value_handle) { |
| read_cb(fit::ok(), char_value, /*maybe_truncated=*/false); |
| } |
| }); |
| |
| std::unique_ptr<LowEnergyConnectionHandle> conn_ref; |
| auto callback = |
| [&conn_ref]( |
| fit::result<HostError, std::unique_ptr<LowEnergyConnectionHandle>> |
| result) { |
| ASSERT_EQ(fit::ok(), result); |
| conn_ref = std::move(result).value(); |
| }; |
| |
| conn_mgr()->Connect( |
| peer->identifier(), callback, LowEnergyConnectionOptions()); |
| |
| RunUntilIdle(); |
| EXPECT_TRUE(conn_ref); |
| ASSERT_TRUE(peer->appearance()); |
| uint16_t device_appearance = peer->appearance().value(); |
| EXPECT_EQ(device_appearance, 1u); |
| } |
| |
| TEST_F(LowEnergyConnectionManagerTest, ReadAppearanceInvalidSize) { |
| Peer* peer = peer_cache()->NewPeer(kAddress0, true); |
| std::unique_ptr<FakePeer> fake_peer = |
| std::make_unique<FakePeer>(kAddress0, dispatcher()); |
| test_device()->AddPeer(std::move(fake_peer)); |
| |
| // Set up GAP service |
| gatt::ServiceData service_data(gatt::ServiceKind::PRIMARY, |
| /*start=*/0x0001, |
| /*end=*/0x0009, |
| kGenericAccessService); |
| auto [remote_svc, service_client] = |
| fake_gatt()->AddPeerService(peer->identifier(), service_data); |
| |
| // Set up preferred connection parameters characteristic. |
| att::Handle char_handle = 0x0002; |
| att::Handle char_value_handle = 0x0003; |
| gatt::CharacteristicData char_data(gatt::kRead, |
| /*ext_props=*/std::nullopt, |
| char_handle, |
| char_value_handle, |
| kAppearanceCharacteristic); |
| service_client->set_characteristics({char_data}); |
| StaticByteBuffer invalid_char_value(0x01); // too small |
| service_client->set_read_request_callback( |
| [char_value_handle, invalid_char_value](att::Handle handle, |
| auto read_cb) { |
| if (handle == char_value_handle) { |
| read_cb(fit::ok(), invalid_char_value, /*maybe_truncated=*/false); |
| } |
| }); |
| |
| std::unique_ptr<LowEnergyConnectionHandle> conn_ref; |
| auto callback = |
| [&conn_ref]( |
| fit::result<HostError, std::unique_ptr<LowEnergyConnectionHandle>> |
| result) { |
| ASSERT_EQ(fit::ok(), result); |
| conn_ref = std::move(result).value(); |
| }; |
| |
| conn_mgr()->Connect( |
| peer->identifier(), callback, LowEnergyConnectionOptions()); |
| |
| RunUntilIdle(); |
| EXPECT_TRUE(conn_ref); |
| EXPECT_FALSE(peer->appearance()); |
| } |
| |
| TEST_F( |
| LowEnergyConnectionManagerTest, |
| ReadPeripheralPreferredConnectionParametersCharacteristicAndUpdateConnectionParameters) { |
| auto* peer = peer_cache()->NewPeer(kAddress0, /*connectable=*/true); |
| auto fake_peer = std::make_unique<FakePeer>(kAddress0, dispatcher()); |
| test_device()->AddPeer(std::move(fake_peer)); |
| |
| // Set up GAP service |
| gatt::ServiceData service_data(gatt::ServiceKind::PRIMARY, |
| /*start=*/0x0001, |
| /*end=*/0x0009, |
| kGenericAccessService); |
| auto [remote_svc, service_client] = |
| fake_gatt()->AddPeerService(peer->identifier(), service_data); |
| |
| // Set up preferred connection parameters characteristic. |
| att::Handle char_handle = 0x0002; |
| att::Handle char_value_handle = 0x0003; |
| gatt::CharacteristicData char_data( |
| gatt::kRead, |
| /*ext_props=*/std::nullopt, |
| char_handle, |
| char_value_handle, |
| kPeripheralPreferredConnectionParametersCharacteristic); |
| service_client->set_characteristics({char_data}); |
| |
| // TODO(https://fxbug.dev/42074287): These parameters are invalid, but this |
| // test passes because we fail to validate them before sending them to the |
| // controller. |
| StaticByteBuffer char_value(0x01, |
| 0x00, // min interval |
| 0x02, |
| 0x00, // max interval |
| 0x03, |
| 0x00, // max latency |
| 0x04, |
| 0x00); // supervision timeout |
| service_client->set_read_request_callback( |
| [char_value_handle, char_value](att::Handle handle, auto read_cb) { |
| if (handle == char_value_handle) { |
| read_cb(fit::ok(), char_value, /*maybe_truncated=*/false); |
| } |
| }); |
| |
| std::unique_ptr<LowEnergyConnectionHandle> conn_ref; |
| auto callback = [&conn_ref](auto result) { |
| ASSERT_EQ(fit::ok(), result); |
| conn_ref = std::move(result).value(); |
| }; |
| |
| conn_mgr()->Connect( |
| peer->identifier(), callback, LowEnergyConnectionOptions()); |
| |
| RunUntilIdle(); |
| EXPECT_TRUE(conn_ref); |
| ASSERT_TRUE(peer->le()->preferred_connection_parameters()); |
| auto params = peer->le()->preferred_connection_parameters().value(); |
| EXPECT_EQ(params.min_interval(), 1u); |
| EXPECT_EQ(params.max_interval(), 2u); |
| EXPECT_EQ(params.max_latency(), 3u); |
| EXPECT_EQ(params.supervision_timeout(), 4u); |
| |
| std::optional<hci_spec::LEConnectionParameters> conn_params; |
| test_device()->set_le_connection_parameters_callback( |
| [&](auto address, auto parameters) { conn_params = parameters; }); |
| |
| RunFor(kLEConnectionPauseCentral); |
| ASSERT_TRUE(conn_params.has_value()); |
| EXPECT_EQ(conn_params->interval(), |
| 1u); // FakeController will use min interval |
| EXPECT_EQ(conn_params->latency(), 3u); |
| EXPECT_EQ(conn_params->supervision_timeout(), 4u); |
| } |
| |
| TEST_F( |
| LowEnergyConnectionManagerTest, |
| ReadPeripheralPreferredConnectionParametersCharacteristicInvalidValueSize) { |
| auto* peer = peer_cache()->NewPeer(kAddress0, /*connectable=*/true); |
| auto fake_peer = std::make_unique<FakePeer>(kAddress0, dispatcher()); |
| test_device()->AddPeer(std::move(fake_peer)); |
| |
| // Set up GAP service |
| gatt::ServiceData service_data(gatt::ServiceKind::PRIMARY, |
| /*start=*/0x0001, |
| /*end=*/0x0003, |
| kGenericAccessService); |
| auto [remote_svc, service_client] = |
| fake_gatt()->AddPeerService(peer->identifier(), service_data); |
| |
| // Set up preferred connection parameters characteristic. |
| att::Handle char_handle = 0x0002; |
| att::Handle char_value_handle = 0x0003; |
| gatt::CharacteristicData char_data( |
| gatt::kRead, |
| /*ext_props=*/std::nullopt, |
| char_handle, |
| char_value_handle, |
| kPeripheralPreferredConnectionParametersCharacteristic); |
| service_client->set_characteristics({char_data}); |
| StaticByteBuffer invalid_char_value(0x01); // too small |
| service_client->set_read_request_callback( |
| [char_value_handle, invalid_char_value](auto handle, auto read_cb) { |
| if (handle == char_value_handle) { |
| read_cb(fit::ok(), invalid_char_value, /*maybe_truncated=*/false); |
| } |
| }); |
| |
| std::unique_ptr<LowEnergyConnectionHandle> conn_ref; |
| auto callback = [&conn_ref](auto result) { |
| ASSERT_EQ(fit::ok(), result); |
| conn_ref = std::move(result).value(); |
| }; |
| |
| conn_mgr()->Connect( |
| peer->identifier(), callback, LowEnergyConnectionOptions()); |
| |
| RunUntilIdle(); |
| EXPECT_TRUE(conn_ref); |
| EXPECT_FALSE(peer->le()->preferred_connection_parameters()); |
| } |
| |
| TEST_F(LowEnergyConnectionManagerTest, GapServiceCharacteristicDiscoveryError) { |
| auto* peer = peer_cache()->NewPeer(kAddress0, /*connectable=*/true); |
| auto fake_peer = std::make_unique<FakePeer>(kAddress0, dispatcher()); |
| test_device()->AddPeer(std::move(fake_peer)); |
| |
| // Set up GAP service |
| gatt::ServiceData service_data(gatt::ServiceKind::PRIMARY, |
| /*start=*/0x0001, |
| /*end=*/0x0003, |
| kGenericAccessService); |
| auto [remote_svc, service_client] = |
| fake_gatt()->AddPeerService(peer->identifier(), service_data); |
| |
| // Set up preferred connection parameters characteristic. |
| att::Handle char_handle = 0x0002; |
| att::Handle char_value_handle = 0x0003; |
| gatt::CharacteristicData char_data( |
| gatt::kRead, |
| /*ext_props=*/std::nullopt, |
| char_handle, |
| char_value_handle, |
| kPeripheralPreferredConnectionParametersCharacteristic); |
| service_client->set_characteristic_discovery_status( |
| ToResult(att::ErrorCode::kReadNotPermitted)); |
| |
| std::unique_ptr<LowEnergyConnectionHandle> conn_ref; |
| auto callback = [&conn_ref](auto result) { |
| ASSERT_EQ(fit::ok(), result); |
| conn_ref = std::move(result).value(); |
| }; |
| |
| conn_mgr()->Connect( |
| peer->identifier(), callback, LowEnergyConnectionOptions()); |
| |
| RunUntilIdle(); |
| EXPECT_TRUE(conn_ref); |
| EXPECT_FALSE(peer->le()->preferred_connection_parameters()); |
| } |
| |
| TEST_F(LowEnergyConnectionManagerTest, GapServiceListServicesError) { |
| auto* peer = peer_cache()->NewPeer(kAddress0, /*connectable=*/true); |
| auto fake_peer = std::make_unique<FakePeer>(kAddress0, dispatcher()); |
| test_device()->AddPeer(std::move(fake_peer)); |
| |
| fake_gatt()->set_list_services_status(ToResult(HostError::kFailed)); |
| |
| std::unique_ptr<LowEnergyConnectionHandle> conn_ref; |
| auto callback = [&conn_ref](auto result) { |
| ASSERT_EQ(fit::ok(), result); |
| conn_ref = std::move(result).value(); |
| }; |
| |
| conn_mgr()->Connect( |
| peer->identifier(), callback, LowEnergyConnectionOptions()); |
| |
| RunUntilIdle(); |
| EXPECT_TRUE(conn_ref); |
| EXPECT_FALSE(peer->le()->preferred_connection_parameters()); |
| } |
| |
| TEST_F(LowEnergyConnectionManagerTest, |
| PeerGapServiceMissingConnectionParameterCharacteristic) { |
| auto* peer = peer_cache()->NewPeer(kAddress0, /*connectable=*/true); |
| auto fake_peer = std::make_unique<FakePeer>(kAddress0, dispatcher()); |
| test_device()->AddPeer(std::move(fake_peer)); |
| |
| // Set up GAP service |
| gatt::ServiceData service_data(gatt::ServiceKind::PRIMARY, |
| /*start=*/0x0001, |
| /*end=*/0x0003, |
| kGenericAccessService); |
| auto [remote_svc, service_client] = |
| fake_gatt()->AddPeerService(peer->identifier(), service_data); |
| |
| std::unique_ptr<LowEnergyConnectionHandle> conn_ref; |
| auto callback = [&conn_ref](auto result) { |
| ASSERT_EQ(fit::ok(), result); |
| conn_ref = std::move(result).value(); |
| }; |
| |
| conn_mgr()->Connect( |
| peer->identifier(), callback, LowEnergyConnectionOptions()); |
| |
| RunUntilIdle(); |
| EXPECT_TRUE(conn_ref); |
| EXPECT_FALSE(peer->le()->preferred_connection_parameters()); |
| } |
| |
| // Listener receives remote initiated connection ref. |
| TEST_F(LowEnergyConnectionManagerTest, PassBondableThroughRemoteInitiatedLink) { |
| test_device()->AddPeer(std::make_unique<FakePeer>(kAddress0, dispatcher())); |
| |
| // First create a fake incoming connection. |
| test_device()->ConnectLowEnergy(kAddress0); |
| |
| RunUntilIdle(); |
| |
| auto link = MoveLastRemoteInitiated(); |
| ASSERT_TRUE(link); |
| |
| std::unique_ptr<LowEnergyConnectionHandle> conn_handle; |
| conn_mgr()->RegisterRemoteInitiatedLink( |
| std::move(link), BondableMode::Bondable, [&conn_handle](auto result) { |
| ASSERT_EQ(fit::ok(), result); |
| conn_handle = std::move(result).value(); |
| }); |
| RunUntilIdle(); |
| |
| ASSERT_TRUE(conn_handle); |
| EXPECT_TRUE(conn_handle->active()); |
| EXPECT_EQ(conn_handle->bondable_mode(), BondableMode::Bondable); |
| } |
| |
| TEST_F(LowEnergyConnectionManagerTest, |
| PassNonBondableThroughRemoteInitiatedLink) { |
| test_device()->AddPeer(std::make_unique<FakePeer>(kAddress0, dispatcher())); |
| |
| // First create a fake incoming connection. |
| test_device()->ConnectLowEnergy(kAddress0); |
| |
| RunUntilIdle(); |
| |
| auto link = MoveLastRemoteInitiated(); |
| ASSERT_TRUE(link); |
| |
| std::unique_ptr<LowEnergyConnectionHandle> conn_handle; |
| conn_mgr()->RegisterRemoteInitiatedLink( |
| std::move(link), BondableMode::NonBondable, [&conn_handle](auto result) { |
| ASSERT_EQ(fit::ok(), result); |
| conn_handle = std::move(result).value(); |
| }); |
| RunUntilIdle(); |
| |
| ASSERT_TRUE(conn_handle); |
| EXPECT_TRUE(conn_handle->active()); |
| EXPECT_EQ(conn_handle->bondable_mode(), BondableMode::NonBondable); |
| } |
| |
| // Successful connection to single peer |
| TEST_F(LowEnergyConnectionManagerTest, PassBondableThroughConnect) { |
| auto* peer = peer_cache()->NewPeer(kAddress0, /*connectable=*/true); |
| EXPECT_TRUE(peer->temporary()); |
| |
| auto fake_peer = std::make_unique<FakePeer>(kAddress0, dispatcher()); |
| test_device()->AddPeer(std::move(fake_peer)); |
| |
| std::unique_ptr<LowEnergyConnectionHandle> conn_handle; |
| auto callback = [&conn_handle](auto result) { |
| ASSERT_EQ(fit::ok(), result); |
| conn_handle = std::move(result).value(); |
| ASSERT_TRUE(conn_handle); |
| EXPECT_TRUE(conn_handle->active()); |
| }; |
| |
| EXPECT_TRUE(connected_peers().empty()); |
| conn_mgr()->Connect( |
| peer->identifier(), callback, {.bondable_mode = BondableMode::Bondable}); |
| |
| RunUntilIdle(); |
| |
| ASSERT_TRUE(conn_handle); |
| EXPECT_EQ(conn_handle->bondable_mode(), BondableMode::Bondable); |
| } |
| |
| // Successful connection to single peer |
| TEST_F(LowEnergyConnectionManagerTest, PassNonBondableThroughConnect) { |
| auto* peer = peer_cache()->NewPeer(kAddress0, /*connectable=*/true); |
| EXPECT_TRUE(peer->temporary()); |
| |
| auto fake_peer = std::make_unique<FakePeer>(kAddress0, dispatcher()); |
| test_device()->AddPeer(std::move(fake_peer)); |
| |
| std::unique_ptr<LowEnergyConnectionHandle> conn_handle; |
| auto callback = [&conn_handle](auto result) { |
| ASSERT_EQ(fit::ok(), result); |
| conn_handle = std::move(result).value(); |
| ASSERT_TRUE(conn_handle); |
| EXPECT_TRUE(conn_handle->active()); |
| }; |
| |
| EXPECT_TRUE(connected_peers().empty()); |
| conn_mgr()->Connect(peer->identifier(), |
| callback, |
| {.bondable_mode = BondableMode::NonBondable}); |
| |
| RunUntilIdle(); |
| |
| ASSERT_TRUE(conn_handle); |
| EXPECT_EQ(conn_handle->bondable_mode(), BondableMode::NonBondable); |
| } |
| |
| // Tests that the connection manager cleans up its connection map correctly |
| // following a disconnection due to encryption failure. |
| TEST_F(LowEnergyConnectionManagerTest, |
| ConnectionCleanUpFollowingEncryptionFailure) { |
| // Set up a connection. |
| auto* peer = peer_cache()->NewPeer(kAddress0, /*connectable=*/true); |
| EXPECT_TRUE(peer->temporary()); |
| |
| auto fake_peer = std::make_unique<FakePeer>(kAddress0, dispatcher()); |
| test_device()->AddPeer(std::move(fake_peer)); |
| |
| std::unique_ptr<LowEnergyConnectionHandle> conn; |
| conn_mgr()->Connect( |
| peer->identifier(), |
| [&](auto result) { |
| ASSERT_EQ(fit::ok(), result); |
| conn = std::move(result).value(); |
| }, |
| kConnectionOptions); |
| RunUntilIdle(); |
| ASSERT_TRUE(conn); |
| |
| hci_spec::ConnectionHandle handle = conn->handle(); |
| bool ref_cleaned_up = false; |
| bool disconnected = false; |
| conn->set_closed_callback([&] { ref_cleaned_up = true; }); |
| conn_mgr()->SetDisconnectCallbackForTesting( |
| [&](hci_spec::ConnectionHandle cb_handle) { |
| EXPECT_EQ(handle, cb_handle); |
| disconnected = true; |
| }); |
| |
| test_device()->SendEncryptionChangeEvent( |
| handle, |
| pw::bluetooth::emboss::StatusCode::CONNECTION_TERMINATED_MIC_FAILURE, |
| pw::bluetooth::emboss::EncryptionStatus::OFF); |
| test_device()->SendDisconnectionCompleteEvent(handle); |
| RunUntilIdle(); |
| |
| EXPECT_TRUE(ref_cleaned_up); |
| EXPECT_TRUE(disconnected); |
| } |
| |
| TEST_F(LowEnergyConnectionManagerTest, |
| SuccessfulInterrogationSetsPeerVersionAndFeatures) { |
| constexpr hci_spec::LESupportedFeatures kLEFeatures{static_cast<uint64_t>( |
| hci_spec::LESupportedFeature::kConnectionParametersRequestProcedure)}; |
| |
| // Set up a connection. |
| auto* peer = peer_cache()->NewPeer(kAddress0, /*connectable=*/true); |
| ASSERT_TRUE(peer->le()); |
| |
| auto fake_peer = std::make_unique<FakePeer>(kAddress0, dispatcher()); |
| fake_peer->set_le_features(kLEFeatures); |
| test_device()->AddPeer(std::move(fake_peer)); |
| |
| std::unique_ptr<LowEnergyConnectionHandle> conn; |
| conn_mgr()->Connect( |
| peer->identifier(), |
| [&](auto result) { |
| ASSERT_EQ(fit::ok(), result); |
| conn = std::move(result).value(); |
| }, |
| kConnectionOptions); |
| |
| EXPECT_FALSE(peer->version().has_value()); |
| EXPECT_FALSE(peer->le()->features().has_value()); |
| RunUntilIdle(); |
| EXPECT_TRUE(conn); |
| EXPECT_TRUE(peer->version().has_value()); |
| EXPECT_TRUE(peer->le()->features().has_value()); |
| EXPECT_EQ(kLEFeatures.le_features, peer->le()->features()->le_features); |
| EXPECT_FALSE(peer->temporary()); |
| } |
| |
| TEST_F(LowEnergyConnectionManagerTest, ConnectInterrogationFailure) { |
| // Set up a connection. |
| auto* peer = peer_cache()->NewPeer(kAddress0, /*connectable=*/true); |
| ASSERT_TRUE(peer->le()); |
| |
| auto fake_peer = std::make_unique<FakePeer>(kAddress0, dispatcher()); |
| test_device()->AddPeer(std::move(fake_peer)); |
| |
| std::optional<HostError> error; |
| conn_mgr()->Connect( |
| peer->identifier(), |
| [&](auto result) { |
| ASSERT_TRUE(result.is_error()); |
| error = result.error_value(); |
| }, |
| kConnectionOptions); |
| ASSERT_FALSE(peer->le()->features().has_value()); |
| |
| // Remove fake peer so LE Read Remote Features command fails during |
| // interrogation. |
| test_device()->set_le_read_remote_features_callback( |
| [this]() { test_device()->RemovePeer(kAddress0); }); |
| |
| RunUntilIdle(); |
| ASSERT_TRUE(error.has_value()); |
| EXPECT_FALSE(peer->connected()); |
| EXPECT_FALSE(peer->le()->connected()); |
| EXPECT_FALSE(peer->temporary()); |
| } |
| |
| TEST_F(LowEnergyConnectionManagerTest, |
| RemoteInitiatedLinkInterrogationFailure) { |
| test_device()->AddPeer(std::make_unique<FakePeer>(kAddress0, dispatcher())); |
| |
| // First create a fake incoming connection. |
| test_device()->ConnectLowEnergy(kAddress0); |
| |
| RunUntilIdle(); |
| |
| auto link = MoveLastRemoteInitiated(); |
| ASSERT_TRUE(link); |
| |
| std::optional<HostError> error; |
| conn_mgr()->RegisterRemoteInitiatedLink( |
| std::move(link), BondableMode::Bondable, [&](auto result) { |
| ASSERT_TRUE(result.is_error()); |
| error = result.error_value(); |
| }); |
| |
| // Remove fake peer so LE Read Remote Features command fails during |
| // interrogation. |
| test_device()->set_le_read_remote_features_callback( |
| [this]() { test_device()->RemovePeer(kAddress0); }); |
| |
| RunUntilIdle(); |
| ASSERT_TRUE(error.has_value()); |
| |
| // A Peer should now exist in the cache. |
| auto* peer = peer_cache()->FindByAddress(kAddress0); |
| ASSERT_TRUE(peer); |
| EXPECT_FALSE(peer->connected()); |
| EXPECT_FALSE(peer->le()->connected()); |
| EXPECT_FALSE(peer->temporary()); |
| } |
| |
| TEST_F(LowEnergyConnectionManagerTest, |
| L2capRequestConnParamUpdateAfterInterrogation) { |
| const hci_spec::LEPreferredConnectionParameters kConnParams( |
| hci_spec::defaults::kLEConnectionIntervalMin, |
| hci_spec::defaults::kLEConnectionIntervalMax, |
| /*max_latency=*/0, |
| hci_spec::defaults::kLESupervisionTimeout); |
| |
| // Connection Parameter Update procedure NOT supported. |
| constexpr hci_spec::LESupportedFeatures kLEFeatures{0}; |
| auto peer = std::make_unique<FakePeer>(kAddress0, dispatcher()); |
| peer->set_le_features(kLEFeatures); |
| test_device()->AddPeer(std::move(peer)); |
| |
| // First create a fake incoming connection as peripheral. |
| test_device()->ConnectLowEnergy( |
| kAddress0, pw::bluetooth::emboss::ConnectionRole::PERIPHERAL); |
| |
| RunUntilIdle(); |
| |
| auto link = MoveLastRemoteInitiated(); |
| ASSERT_TRUE(link); |
| |
| std::unique_ptr<LowEnergyConnectionHandle> conn_handle; |
| conn_mgr()->RegisterRemoteInitiatedLink( |
| std::move(link), BondableMode::Bondable, [&](auto result) { |
| ASSERT_EQ(fit::ok(), result); |
| conn_handle = std::move(result).value(); |
| }); |
| |
| size_t l2cap_conn_param_update_count = 0; |
| fake_l2cap()->set_connection_parameter_update_request_responder( |
| [&](auto handle, auto params) { |
| EXPECT_EQ(kConnParams, params); |
| l2cap_conn_param_update_count++; |
| return true; |
| }); |
| |
| size_t hci_update_conn_param_count = 0; |
| test_device()->set_le_connection_parameters_callback( |
| [&](auto address, auto parameters) { hci_update_conn_param_count++; }); |
| |
| RunUntilIdle(); |
| ASSERT_TRUE(conn_handle); |
| EXPECT_TRUE(conn_handle->active()); |
| EXPECT_EQ(0u, l2cap_conn_param_update_count); |
| EXPECT_EQ(0u, hci_update_conn_param_count); |
| |
| RunFor(kLEConnectionPausePeripheral); |
| EXPECT_EQ(1u, l2cap_conn_param_update_count); |
| EXPECT_EQ(0u, hci_update_conn_param_count); |
| } |
| |
| // Based on PTS L2CAP/LE/CPU/BV-01-C, in which the LE feature mask indicates |
| // support for the Connection Parameter Request Procedure, but sending the |
| // request results in a kUnsupportedRemoteFeature event status. PTS expects the |
| // host to retry with a L2cap connection parameter request. |
| // |
| // Test that this behavior is followed for 2 concurrent connections in order to |
| // ensure correct command/event handling. |
| TEST_F(LowEnergyConnectionManagerTest, |
| PeripheralsRetryLLConnectionUpdateWithL2capRequest) { |
| auto peer0 = std::make_unique<FakePeer>(kAddress0, dispatcher()); |
| auto peer1 = std::make_unique<FakePeer>(kAddress1, dispatcher()); |
| |
| // Connection Parameter Update procedure supported by controller. |
| constexpr hci_spec::LESupportedFeatures kLEFeatures{static_cast<uint64_t>( |
| hci_spec::LESupportedFeature::kConnectionParametersRequestProcedure)}; |
| |
| peer0->set_le_features(kLEFeatures); |
| peer1->set_le_features(kLEFeatures); |
| |
| // Simulate host rejection by causing FakeController to set LE Connection |
| // Update Complete status to kUnsupportedRemoteFeature, as PTS does. |
| peer0->set_supports_ll_conn_update_procedure(false); |
| peer1->set_supports_ll_conn_update_procedure(false); |
| |
| test_device()->AddPeer(std::move(peer0)); |
| test_device()->AddPeer(std::move(peer1)); |
| |
| // First create fake incoming connections with local host as peripheral. |
| test_device()->ConnectLowEnergy( |
| kAddress0, pw::bluetooth::emboss::ConnectionRole::PERIPHERAL); |
| RunUntilIdle(); |
| auto link0 = MoveLastRemoteInitiated(); |
| ASSERT_TRUE(link0); |
| |
| std::unique_ptr<LowEnergyConnectionHandle> conn_handle0; |
| conn_mgr()->RegisterRemoteInitiatedLink( |
| std::move(link0), BondableMode::Bondable, [&](auto result) { |
| ASSERT_EQ(fit::ok(), result); |
| conn_handle0 = std::move(result).value(); |
| }); |
| |
| test_device()->ConnectLowEnergy( |
| kAddress1, pw::bluetooth::emboss::ConnectionRole::PERIPHERAL); |
| RunUntilIdle(); |
| auto link1 = MoveLastRemoteInitiated(); |
| ASSERT_TRUE(link1); |
| |
| std::unique_ptr<LowEnergyConnectionHandle> conn_handle1; |
| conn_mgr()->RegisterRemoteInitiatedLink( |
| std::move(link1), BondableMode::Bondable, [&](auto result) { |
| ASSERT_EQ(fit::ok(), result); |
| conn_handle1 = std::move(result).value(); |
| }); |
| |
| size_t l2cap_conn_param_update_count0 = 0; |
| size_t l2cap_conn_param_update_count1 = 0; |
| size_t hci_update_conn_param_count0 = 0; |
| size_t hci_update_conn_param_count1 = 0; |
| |
| fake_l2cap()->set_connection_parameter_update_request_responder( |
| [&](auto handle, auto params) { |
| if (handle == conn_handle0->handle()) { |
| l2cap_conn_param_update_count0++; |
| // connection update commands should be sent before l2cap requests |
| EXPECT_EQ(hci_update_conn_param_count0, 1u); |
| } else if (handle == conn_handle1->handle()) { |
| l2cap_conn_param_update_count1++; |
| EXPECT_EQ(hci_update_conn_param_count1, 1u); |
| } else { |
| ADD_FAILURE(); |
| } |
| return true; |
| }); |
| |
| test_device()->set_le_connection_parameters_callback( |
| [&](auto address, auto params) { |
| if (address == kAddress0) { |
| hci_update_conn_param_count0++; |
| // l2cap requests should not be sent until after failed HCI connection |
| // update commands |
| EXPECT_EQ(l2cap_conn_param_update_count0, 0u); |
| } else if (address == kAddress1) { |
| hci_update_conn_param_count1++; |
| EXPECT_EQ(l2cap_conn_param_update_count1, 0u); |
| } else { |
| ADD_FAILURE(); |
| } |
| }); |
| |
| RunFor(kLEConnectionPausePeripheral); |
| ASSERT_TRUE(conn_handle0); |
| EXPECT_TRUE(conn_handle0->active()); |
| ASSERT_TRUE(conn_handle1); |
| EXPECT_TRUE(conn_handle1->active()); |
| |
| EXPECT_EQ(conn_handle0->role(), |
| pw::bluetooth::emboss::ConnectionRole::PERIPHERAL); |
| EXPECT_EQ(conn_handle1->role(), |
| pw::bluetooth::emboss::ConnectionRole::PERIPHERAL); |
| |
| EXPECT_EQ(1u, hci_update_conn_param_count0); |
| EXPECT_EQ(1u, l2cap_conn_param_update_count0); |
| EXPECT_EQ(1u, hci_update_conn_param_count1); |
| EXPECT_EQ(1u, l2cap_conn_param_update_count1); |
| |
| // l2cap requests should not be sent on subsequent events |
| test_device()->SendLEConnectionUpdateCompleteSubevent( |
| conn_handle1->handle(), |
| hci_spec::LEConnectionParameters(), |
| pw::bluetooth::emboss::StatusCode::UNSUPPORTED_REMOTE_FEATURE); |
| RunUntilIdle(); |
| EXPECT_EQ(1u, l2cap_conn_param_update_count0); |
| EXPECT_EQ(1u, l2cap_conn_param_update_count1); |
| } |
| |
| // Based on PTS L2CAP/LE/CPU/BV-01-C. When run twice, the controller caches the |
| // LE Connection Update Complete kUnsupportedRemoteFeature status and returns it |
| // directly in future LE Connection Update Command Status events. The host |
| // should retry with the L2CAP Connection Parameter Update Request after |
| // receiving this kUnsupportedRemoteFeature command status. |
| TEST_F( |
| LowEnergyConnectionManagerTest, |
| PeripheralSendsL2capConnParamReqAfterConnUpdateCommandStatusUnsupportedRemoteFeature) { |
| auto peer = std::make_unique<FakePeer>(kAddress0, dispatcher()); |
| |
| // Connection Parameter Update procedure supported by controller. |
| constexpr hci_spec::LESupportedFeatures kLEFeatures{static_cast<uint64_t>( |
| hci_spec::LESupportedFeature::kConnectionParametersRequestProcedure)}; |
| peer->set_le_features(kLEFeatures); |
| test_device()->AddPeer(std::move(peer)); |
| |
| // First create a fake incoming connection with local host as peripheral. |
| test_device()->ConnectLowEnergy( |
| kAddress0, pw::bluetooth::emboss::ConnectionRole::PERIPHERAL); |
| RunUntilIdle(); |
| |
| auto link = MoveLastRemoteInitiated(); |
| ASSERT_TRUE(link); |
| |
| std::unique_ptr<LowEnergyConnectionHandle> conn_handle; |
| conn_mgr()->RegisterRemoteInitiatedLink( |
| std::move(link), BondableMode::Bondable, [&](auto result) { |
| ASSERT_EQ(fit::ok(), result); |
| conn_handle = std::move(result).value(); |
| }); |
| |
| size_t l2cap_conn_param_update_count = 0; |
| size_t hci_update_conn_param_count = 0; |
| |
| fake_l2cap()->set_connection_parameter_update_request_responder( |
| [&](auto handle, auto params) { |
| l2cap_conn_param_update_count++; |
| return true; |
| }); |
| |
| test_device()->set_le_connection_parameters_callback( |
| [&](auto address, auto params) { hci_update_conn_param_count++; }); |
| |
| test_device()->SetDefaultCommandStatus( |
| hci_spec::kLEConnectionUpdate, |
| pw::bluetooth::emboss::StatusCode::UNSUPPORTED_REMOTE_FEATURE); |
| |
| RunFor(kLEConnectionPausePeripheral); |
| ASSERT_TRUE(conn_handle); |
| EXPECT_TRUE(conn_handle->active()); |
| EXPECT_EQ(0u, hci_update_conn_param_count); |
| EXPECT_EQ(1u, l2cap_conn_param_update_count); |
| |
| test_device()->ClearDefaultCommandStatus(hci_spec::kLEConnectionUpdate); |
| |
| // l2cap request should not be called on subsequent events |
| test_device()->SendLEConnectionUpdateCompleteSubevent( |
| conn_handle->handle(), |
| hci_spec::LEConnectionParameters(), |
| pw::bluetooth::emboss::StatusCode::UNSUPPORTED_REMOTE_FEATURE); |
| |
| RunUntilIdle(); |
| EXPECT_EQ(1u, l2cap_conn_param_update_count); |
| } |
| |
| // A peripheral should not attempt to handle the next LE Connection Update |
| // Complete event if the status of the LE Connection Update command is not |
| // success. |
| TEST_F( |
| LowEnergyConnectionManagerTest, |
| PeripheralDoesNotSendL2capConnParamReqAfterConnUpdateCommandStatusError) { |
| auto peer = std::make_unique<FakePeer>(kAddress0, dispatcher()); |
| |
| // Connection Parameter Update procedure supported by controller. |
| constexpr hci_spec::LESupportedFeatures kLEFeatures{static_cast<uint64_t>( |
| hci_spec::LESupportedFeature::kConnectionParametersRequestProcedure)}; |
| peer->set_le_features(kLEFeatures); |
| test_device()->AddPeer(std::move(peer)); |
| |
| // First create a fake incoming connection with local host as peripheral. |
| test_device()->ConnectLowEnergy( |
| kAddress0, pw::bluetooth::emboss::ConnectionRole::PERIPHERAL); |
| RunUntilIdle(); |
| |
| auto link = MoveLastRemoteInitiated(); |
| ASSERT_TRUE(link); |
| |
| std::unique_ptr<LowEnergyConnectionHandle> conn_handle; |
| conn_mgr()->RegisterRemoteInitiatedLink( |
| std::move(link), BondableMode::Bondable, [&](auto result) { |
| ASSERT_EQ(fit::ok(), result); |
| conn_handle = std::move(result).value(); |
| }); |
| |
| size_t l2cap_conn_param_update_count = 0; |
| size_t hci_update_conn_param_count = 0; |
| |
| fake_l2cap()->set_connection_parameter_update_request_responder( |
| [&](auto handle, auto params) { |
| l2cap_conn_param_update_count++; |
| return true; |
| }); |
| |
| test_device()->set_le_connection_parameters_callback( |
| [&](auto address, auto params) { hci_update_conn_param_count++; }); |
| |
| test_device()->SetDefaultCommandStatus( |
| hci_spec::kLEConnectionUpdate, |
| pw::bluetooth::emboss::StatusCode::UNSPECIFIED_ERROR); |
| |
| RunFor(kLEConnectionPausePeripheral); |
| ASSERT_TRUE(conn_handle); |
| EXPECT_TRUE(conn_handle->active()); |
| EXPECT_EQ(conn_handle->role(), |
| pw::bluetooth::emboss::ConnectionRole::PERIPHERAL); |
| EXPECT_EQ(0u, hci_update_conn_param_count); |
| EXPECT_EQ(0u, l2cap_conn_param_update_count); |
| |
| test_device()->ClearDefaultCommandStatus(hci_spec::kLEConnectionUpdate); |
| |
| // l2cap request should not be called on subsequent events |
| test_device()->SendLEConnectionUpdateCompleteSubevent( |
| conn_handle->handle(), |
| hci_spec::LEConnectionParameters(), |
| pw::bluetooth::emboss::StatusCode::UNSUPPORTED_REMOTE_FEATURE); |
| |
| RunUntilIdle(); |
| EXPECT_EQ(0u, l2cap_conn_param_update_count); |
| } |
| |
| TEST_F(LowEnergyConnectionManagerTest, HciUpdateConnParamsAfterInterrogation) { |
| constexpr hci_spec::LESupportedFeatures kLEFeatures{static_cast<uint64_t>( |
| hci_spec::LESupportedFeature::kConnectionParametersRequestProcedure)}; |
| |
| auto peer = std::make_unique<FakePeer>(kAddress0, dispatcher()); |
| peer->set_le_features(kLEFeatures); |
| test_device()->AddPeer(std::move(peer)); |
| |
| // First create a fake incoming connection. |
| test_device()->ConnectLowEnergy( |
| kAddress0, pw::bluetooth::emboss::ConnectionRole::PERIPHERAL); |
| |
| RunUntilIdle(); |
| |
| auto link = MoveLastRemoteInitiated(); |
| ASSERT_TRUE(link); |
| |
| std::unique_ptr<LowEnergyConnectionHandle> conn_handle; |
| conn_mgr()->RegisterRemoteInitiatedLink( |
| std::move(link), BondableMode::Bondable, [&](auto result) { |
| ASSERT_EQ(fit::ok(), result); |
| conn_handle = std::move(result).value(); |
| }); |
| |
| size_t l2cap_conn_param_update_count = 0; |
| fake_l2cap()->set_connection_parameter_update_request_responder( |
| [&](auto handle, const auto params) { |
| l2cap_conn_param_update_count++; |
| return true; |
| }); |
| |
| size_t hci_update_conn_param_count = 0; |
| test_device()->set_le_connection_parameters_callback( |
| [&](auto address, const hci_spec::LEConnectionParameters& params) { |
| // FakeController will pick an interval between min and max interval. |
| EXPECT_TRUE( |
| params.interval() >= hci_spec::defaults::kLEConnectionIntervalMin && |
| params.interval() <= hci_spec::defaults::kLEConnectionIntervalMax); |
| EXPECT_EQ(0u, params.latency()); |
| EXPECT_EQ(hci_spec::defaults::kLESupervisionTimeout, |
| params.supervision_timeout()); |
| hci_update_conn_param_count++; |
| }); |
| |
| RunUntilIdle(); |
| ASSERT_TRUE(conn_handle); |
| EXPECT_TRUE(conn_handle->active()); |
| EXPECT_EQ(conn_handle->role(), |
| pw::bluetooth::emboss::ConnectionRole::PERIPHERAL); |
| EXPECT_EQ(0u, l2cap_conn_param_update_count); |
| EXPECT_EQ(0u, hci_update_conn_param_count); |
| |
| RunFor(kLEConnectionPausePeripheral); |
| EXPECT_EQ(0u, l2cap_conn_param_update_count); |
| EXPECT_EQ(1u, hci_update_conn_param_count); |
| } |
| |
| TEST_F(LowEnergyConnectionManagerTest, |
| CentralUpdatesConnectionParametersToDefaultsAfterInitialization) { |
| // Set up a connection. |
| auto* peer = peer_cache()->NewPeer(kAddress0, /*connectable=*/true); |
| ASSERT_TRUE(peer->le()); |
| |
| test_device()->AddPeer(std::make_unique<FakePeer>(kAddress0, dispatcher())); |
| |
| size_t hci_update_conn_param_count = 0; |
| test_device()->set_le_connection_parameters_callback( |
| [&](auto address, const hci_spec::LEConnectionParameters& params) { |
| // FakeController will pick an interval between min and max interval. |
| EXPECT_TRUE( |
| params.interval() >= hci_spec::defaults::kLEConnectionIntervalMin && |
| params.interval() <= hci_spec::defaults::kLEConnectionIntervalMax); |
| EXPECT_EQ(0u, params.latency()); |
| EXPECT_EQ(hci_spec::defaults::kLESupervisionTimeout, |
| params.supervision_timeout()); |
| hci_update_conn_param_count++; |
| }); |
| |
| std::unique_ptr<LowEnergyConnectionHandle> conn; |
| conn_mgr()->Connect( |
| peer->identifier(), |
| [&](auto result) { |
| ASSERT_EQ(fit::ok(), result); |
| conn = std::move(result).value(); |
| }, |
| kConnectionOptions); |
| |
| RunUntilIdle(); |
| EXPECT_EQ(0u, hci_update_conn_param_count); |
| |
| RunFor(kLEConnectionPauseCentral); |
| EXPECT_EQ(1u, hci_update_conn_param_count); |
| EXPECT_TRUE(conn); |
| } |
| |
| TEST_F(LowEnergyConnectionManagerTest, ConnectCalledForPeerBeingInterrogated) { |
| // Set up a connection. |
| auto* peer = peer_cache()->NewPeer(kAddress0, /*connectable=*/true); |
| ASSERT_TRUE(peer->le()); |
| |
| auto fake_peer = std::make_unique<FakePeer>(kAddress0, dispatcher()); |
| test_device()->AddPeer(std::move(fake_peer)); |
| |
| // Prevent remote features event from being received. |
| test_device()->SetDefaultCommandStatus( |
| hci_spec::kLEReadRemoteFeatures, |
| pw::bluetooth::emboss::StatusCode::SUCCESS); |
| |
| conn_mgr()->Connect( |
| peer->identifier(), |
| [&](auto result) { ASSERT_TRUE(result.is_error()); }, |
| kConnectionOptions); |
| |
| RunUntilIdle(); |
| // Interrogation should not complete. |
| EXPECT_FALSE(peer->le()->features().has_value()); |
| |
| // Connect to same peer again, before interrogation has completed. |
| // No asserts should fail. |
| conn_mgr()->Connect( |
| peer->identifier(), |
| [&](auto result) { ASSERT_TRUE(result.is_error()); }, |
| kConnectionOptions); |
| RunUntilIdle(); |
| } |
| |
| LowEnergyConnectionManager::ConnectionResultCallback |
| MakeConnectionResultCallback( |
| std::unique_ptr<LowEnergyConnectionHandle>& conn_handle) { |
| return [&conn_handle](auto result) { |
| ASSERT_EQ(fit::ok(), result); |
| conn_handle = std::move(result).value(); |
| EXPECT_TRUE(conn_handle); |
| EXPECT_TRUE(conn_handle->active()); |
| }; |
| } |
| |
| // Test that active connections not meeting the requirements for Secure |
| // Connections Only mode are disconnected when the security mode is changed to |
| // SC Only. |
| TEST_F(LowEnergyConnectionManagerTest, |
| SecureConnectionsOnlyDisconnectsInsufficientSecurity) { |
| Peer* encrypted_peer = peer_cache()->NewPeer(kAddress0, /*connectable=*/true); |
| Peer* unencrypted_peer = |
| peer_cache()->NewPeer(kAddress1, /*connectable=*/true); |
| Peer* secure_authenticated_peer = |
| peer_cache()->NewPeer(kAddress3, /*connectable=*/true); |
| test_device()->AddPeer(std::make_unique<FakePeer>(kAddress0, dispatcher())); |
| test_device()->AddPeer(std::make_unique<FakePeer>(kAddress1, dispatcher())); |
| test_device()->AddPeer(std::make_unique<FakePeer>(kAddress3, dispatcher())); |
| |
| std::unique_ptr<LowEnergyConnectionHandle> unencrypted_conn_handle, |
| encrypted_conn_handle, secure_authenticated_conn_handle; |
| EXPECT_TRUE(connected_peers().empty()); |
| conn_mgr()->Connect(unencrypted_peer->identifier(), |
| MakeConnectionResultCallback(unencrypted_conn_handle), |
| kConnectionOptions); |
| conn_mgr()->Connect(encrypted_peer->identifier(), |
| MakeConnectionResultCallback(encrypted_conn_handle), |
| kConnectionOptions); |
| conn_mgr()->Connect( |
| secure_authenticated_peer->identifier(), |
| MakeConnectionResultCallback(secure_authenticated_conn_handle), |
| kConnectionOptions); |
| RunUntilIdle(); |
| std::function<void(sm::Result<>)> pair_cb = [](sm::Result<> s) { |
| EXPECT_EQ(fit::ok(), s); |
| }; |
| EXPECT_EQ(3u, connected_peers().size()); |
| ASSERT_TRUE(unencrypted_conn_handle); |
| ASSERT_TRUE(encrypted_conn_handle); |
| ASSERT_TRUE(secure_authenticated_conn_handle); |
| EXPECT_TRUE(unencrypted_conn_handle->active()); |
| EXPECT_TRUE(secure_authenticated_conn_handle->active()); |
| EXPECT_TRUE(encrypted_conn_handle->active()); |
| |
| // "Pair" to the encrypted peers to get to the correct security level. |
| conn_mgr()->Pair(encrypted_peer->identifier(), |
| sm::SecurityLevel::kEncrypted, |
| sm::BondableMode::Bondable, |
| pair_cb); |
| conn_mgr()->Pair(secure_authenticated_peer->identifier(), |
| sm::SecurityLevel::kSecureAuthenticated, |
| sm::BondableMode::Bondable, |
| pair_cb); |
| RunUntilIdle(); |
| EXPECT_EQ(sm::SecurityLevel::kNoSecurity, |
| unencrypted_conn_handle->security().level()); |
| EXPECT_EQ(sm::SecurityLevel::kEncrypted, |
| encrypted_conn_handle->security().level()); |
| EXPECT_EQ(sm::SecurityLevel::kSecureAuthenticated, |
| secure_authenticated_conn_handle->security().level()); |
| |
| // Setting Secure Connections Only mode causes connections not allowed under |
| // this mode to be disconnected (in this case, `encrypted_peer` is encrypted, |
| // SC-generated, and with max encryption key size, but not authenticated). |
| conn_mgr()->SetSecurityMode(LESecurityMode::SecureConnectionsOnly); |
| RunUntilIdle(); |
| EXPECT_EQ(LESecurityMode::SecureConnectionsOnly, conn_mgr()->security_mode()); |
| EXPECT_EQ(2u, connected_peers().size()); |
| EXPECT_TRUE(unencrypted_conn_handle->active()); |
| EXPECT_TRUE(secure_authenticated_conn_handle->active()); |
| EXPECT_FALSE(encrypted_conn_handle->active()); |
| } |
| |
| // Test that both existing and new peers pick up on a change to Secure |
| // Connections Only mode. |
| TEST_F(LowEnergyConnectionManagerTest, SetSecureConnectionsOnlyModeWorks) { |
| // LE Connection Manager defaults to Mode 1. |
| EXPECT_EQ(LESecurityMode::Mode1, conn_mgr()->security_mode()); |
| |
| // This peer will already be connected when we set LE Secure Connections Only |
| // mode. |
| Peer* existing_peer = peer_cache()->NewPeer(kAddress1, /*connectable=*/true); |
| test_device()->AddPeer(std::make_unique<FakePeer>(kAddress1, dispatcher())); |
| std::unique_ptr<LowEnergyConnectionHandle> existing_conn_handle; |
| RunUntilIdle(); |
| |
| conn_mgr()->Connect(existing_peer->identifier(), |
| MakeConnectionResultCallback(existing_conn_handle), |
| kConnectionOptions); |
| RunUntilIdle(); |
| TestSm::WeakPtr existing_peer_sm = |
| TestSmByHandle(existing_conn_handle->handle()); |
| ASSERT_TRUE(existing_peer_sm.is_alive()); |
| EXPECT_EQ(LESecurityMode::Mode1, existing_peer_sm->security_mode()); |
| EXPECT_EQ(1u, connected_peers().size()); |
| |
| conn_mgr()->SetSecurityMode(LESecurityMode::SecureConnectionsOnly); |
| RunUntilIdle(); |
| |
| EXPECT_EQ(LESecurityMode::SecureConnectionsOnly, |
| existing_peer_sm->security_mode()); |
| |
| // This peer is connected after setting LE Secure Connections Only mode. |
| Peer* new_peer = peer_cache()->NewPeer(kAddress3, /*connectable=*/true); |
| test_device()->AddPeer(std::make_unique<FakePeer>(kAddress3, dispatcher())); |
| std::unique_ptr<LowEnergyConnectionHandle> new_conn_handle; |
| |
| conn_mgr()->Connect(new_peer->identifier(), |
| MakeConnectionResultCallback(new_conn_handle), |
| kConnectionOptions); |
| RunUntilIdle(); |
| TestSm::WeakPtr new_peer_sm = TestSmByHandle(new_conn_handle->handle()); |
| ASSERT_TRUE(new_peer_sm.is_alive()); |
| EXPECT_EQ(2u, connected_peers().size()); |
| |
| EXPECT_EQ(LESecurityMode::SecureConnectionsOnly, |
| new_peer_sm->security_mode()); |
| } |
| |
| TEST_F(LowEnergyConnectionManagerTest, |
| ConnectAndInterrogateSecondPeerDuringInterrogationOfFirstPeer) { |
| auto* peer_0 = peer_cache()->NewPeer(kAddress0, /*connectable=*/true); |
| ASSERT_TRUE(peer_0->le()); |
| |
| auto fake_peer_0 = std::make_unique<FakePeer>(kAddress0, dispatcher()); |
| auto fake_peer_0_ptr = fake_peer_0.get(); |
| test_device()->AddPeer(std::move(fake_peer_0)); |
| |
| // Prevent remote features event from being received. |
| test_device()->SetDefaultCommandStatus( |
| hci_spec::kLEReadRemoteFeatures, |
| pw::bluetooth::emboss::StatusCode::SUCCESS); |
| |
| std::unique_ptr<LowEnergyConnectionHandle> conn_0; |
| conn_mgr()->Connect( |
| peer_0->identifier(), |
| [&conn_0](auto result) { |
| ASSERT_EQ(fit::ok(), result); |
| conn_0 = std::move(result).value(); |
| ASSERT_TRUE(conn_0); |
| }, |
| kConnectionOptions); |
| |
| RunUntilIdle(); |
| // Interrogation should not complete. |
| EXPECT_FALSE(peer_0->le()->connected()); |
| EXPECT_FALSE(conn_0); |
| |
| auto* peer_1 = peer_cache()->NewPeer(kAddress1, /*connectable=*/true); |
| ASSERT_TRUE(peer_1->le()); |
| |
| auto fake_peer_1 = std::make_unique<FakePeer>(kAddress1, dispatcher()); |
| auto fake_peer_1_ptr = fake_peer_1.get(); |
| test_device()->AddPeer(std::move(fake_peer_1)); |
| |
| // Connect to different peer, before interrogation has completed. |
| std::unique_ptr<LowEnergyConnectionHandle> conn_1; |
| conn_mgr()->Connect( |
| peer_1->identifier(), |
| [&conn_1](auto result) { |
| ASSERT_EQ(fit::ok(), result); |
| conn_1 = std::move(result).value(); |
| ASSERT_TRUE(conn_1); |
| }, |
| kConnectionOptions); |
| RunUntilIdle(); |
| |
| // Complete interrogation of peer_0 |
| ASSERT_FALSE(fake_peer_0_ptr->logical_links().empty()); |
| auto handle_0 = *fake_peer_0_ptr->logical_links().begin(); |
| |
| auto response = hci::EmbossEventPacket::New< |
| pw::bluetooth::emboss::LEReadRemoteFeaturesCompleteSubeventWriter>( |
| hci_spec::kLEMetaEventCode); |
| auto view = response.view_t(); |
| view.le_meta_event().subevent_code().Write( |
| hci_spec::kLEReadRemoteFeaturesCompleteSubeventCode); |
| view.connection_handle().Write(handle_0); |
| view.status().Write(pw::bluetooth::emboss::StatusCode::SUCCESS); |
| view.le_features().BackingStorage().WriteUInt(0u); |
| test_device()->SendCommandChannelPacket(response.data()); |
| RunUntilIdle(); |
| EXPECT_TRUE(conn_0); |
| EXPECT_TRUE(peer_0->le()->connected()); |
| |
| // Complete interrogation of peer_1 |
| ASSERT_FALSE(fake_peer_1_ptr->logical_links().empty()); |
| auto handle_1 = *fake_peer_0_ptr->logical_links().begin(); |
| view.connection_handle().Write(handle_1); |
| test_device()->SendCommandChannelPacket(response.data()); |
| RunUntilIdle(); |
| EXPECT_TRUE(conn_1); |
| EXPECT_TRUE(peer_1->le()->connected()); |
| } |
| |
| TEST_F(LowEnergyConnectionManagerTest, |
| ConnectSecondPeerDuringInterrogationOfFirstPeer) { |
| auto* peer_0 = peer_cache()->NewPeer(kAddress0, /*connectable=*/true); |
| ASSERT_TRUE(peer_0->le()); |
| |
| auto fake_peer_0 = std::make_unique<FakePeer>(kAddress0, dispatcher()); |
| auto fake_peer_0_ptr = fake_peer_0.get(); |
| test_device()->AddPeer(std::move(fake_peer_0)); |
| |
| // Prevent remote features event from being received. |
| test_device()->SetDefaultCommandStatus( |
| hci_spec::kLEReadRemoteFeatures, |
| pw::bluetooth::emboss::StatusCode::SUCCESS); |
| |
| std::unique_ptr<LowEnergyConnectionHandle> conn_0; |
| conn_mgr()->Connect( |
| peer_0->identifier(), |
| [&](auto result) { |
| ASSERT_EQ(fit::ok(), result); |
| conn_0 = std::move(result).value(); |
| }, |
| kConnectionOptions); |
| |
| RunUntilIdle(); |
| // Interrogation should not complete. |
| EXPECT_FALSE(peer_0->le()->connected()); |
| EXPECT_FALSE(conn_0); |
| |
| test_device()->ClearDefaultCommandStatus(hci_spec::kLEReadRemoteFeatures); |
| // Stall connection complete for peer 1. |
| test_device()->SetDefaultCommandStatus( |
| hci_spec::kLECreateConnection, |
| pw::bluetooth::emboss::StatusCode::SUCCESS); |
| |
| auto* peer_1 = peer_cache()->NewPeer(kAddress1, /*connectable=*/true); |
| ASSERT_TRUE(peer_1->le()); |
| |
| auto fake_peer_1 = std::make_unique<FakePeer>(kAddress1, dispatcher()); |
| test_device()->AddPeer(std::move(fake_peer_1)); |
| |
| // Connect to different peer, before interrogation has completed. |
| conn_mgr()->Connect( |
| peer_1->identifier(), |
| [&](auto result) { EXPECT_TRUE(result.is_error()); }, |
| kConnectionOptions); |
| RunUntilIdle(); |
| |
| // Complete interrogation of peer_0. No asserts should fail. |
| ASSERT_FALSE(fake_peer_0_ptr->logical_links().empty()); |
| auto handle_0 = *fake_peer_0_ptr->logical_links().begin(); |
| auto response = hci::EmbossEventPacket::New< |
| pw::bluetooth::emboss::LEReadRemoteFeaturesCompleteSubeventWriter>( |
| hci_spec::kLEMetaEventCode); |
| auto view = response.view_t(); |
| view.le_meta_event().subevent_code().Write( |
| hci_spec::kLEReadRemoteFeaturesCompleteSubeventCode); |
| view.connection_handle().Write(handle_0); |
| view.status().Write(pw::bluetooth::emboss::StatusCode::SUCCESS); |
| view.le_features().BackingStorage().WriteUInt(0u); |
| test_device()->SendCommandChannelPacket(response.data()); |
| RunUntilIdle(); |
| EXPECT_TRUE(conn_0); |
| EXPECT_TRUE(peer_0->le()->connected()); |
| } |
| |
| TEST_F(LowEnergyConnectionManagerTest, |
| SynchonousInterrogationAndNoCallbackRetainsConnectionRef) { |
| auto* peer = peer_cache()->NewPeer(kAddress0, /*connectable=*/true); |
| ASSERT_TRUE(peer->le()); |
| |
| auto fake_peer = std::make_unique<FakePeer>(kAddress0, dispatcher()); |
| test_device()->AddPeer(std::move(fake_peer)); |
| |
| std::unique_ptr<LowEnergyConnectionHandle> conn; |
| conn_mgr()->Connect( |
| peer->identifier(), |
| [&](auto result) { |
| ASSERT_EQ(fit::ok(), result); |
| conn = std::move(result).value(); |
| }, |
| kConnectionOptions); |
| |
| RunUntilIdle(); |
| EXPECT_TRUE(peer->le()->connected()); |
| EXPECT_TRUE(conn); |
| |
| // Disconnect |
| conn = nullptr; |
| RunUntilIdle(); |
| |
| // Second interrogation will complete synchronously because peer has already |
| // been interrogated. |
| bool conn_cb_called = false; |
| conn_mgr()->Connect( |
| peer->identifier(), |
| [&](auto result) { |
| conn_cb_called = true; |
| EXPECT_EQ(fit::ok(), result); |
| // Don't retain ref. |
| }, |
| kConnectionOptions); |
| // Wait for connect complete event. |
| RunUntilIdle(); |
| EXPECT_TRUE(conn_cb_called); |
| } |
| |
| TEST_F(LowEnergyConnectionManagerTest, AutoConnectSkipsScanning) { |
| auto* peer = peer_cache()->NewPeer(kAddress0, /*connectable=*/true); |
| EXPECT_TRUE(peer->temporary()); |
| |
| auto fake_peer = std::make_unique<FakePeer>(kAddress0, dispatcher()); |
| test_device()->AddPeer(std::move(fake_peer)); |
| |
| size_t scan_cb_count = 0; |
| test_device()->set_scan_state_callback( |
| [&scan_cb_count](bool enabled) { scan_cb_count++; }); |
| |
| std::unique_ptr<LowEnergyConnectionHandle> conn_handle; |
| auto callback = [&conn_handle](auto result) { |
| ASSERT_EQ(fit::ok(), result); |
| conn_handle = std::move(result).value(); |
| EXPECT_TRUE(conn_handle->active()); |
| }; |
| |
| EXPECT_TRUE(connected_peers().empty()); |
| LowEnergyConnectionOptions options{.auto_connect = true}; |
| conn_mgr()->Connect(peer->identifier(), callback, options); |
| ASSERT_TRUE(peer->le()); |
| EXPECT_EQ(Peer::ConnectionState::kInitializing, |
| peer->le()->connection_state()); |
| |
| RunUntilIdle(); |
| |
| EXPECT_EQ(1u, connected_peers().size()); |
| EXPECT_EQ(1u, connected_peers().count(kAddress0)); |
| |
| ASSERT_TRUE(conn_handle); |
| EXPECT_TRUE(conn_handle->active()); |
| EXPECT_EQ(peer->identifier(), conn_handle->peer_identifier()); |
| EXPECT_FALSE(peer->temporary()); |
| EXPECT_EQ(Peer::ConnectionState::kConnected, peer->le()->connection_state()); |
| EXPECT_EQ(scan_cb_count, 0u); |
| } |
| |
| TEST_F(LowEnergyConnectionManagerTest, ConnectSinglePeerStartDiscoveryFailed) { |
| auto* peer = peer_cache()->NewPeer(kAddress0, /*connectable=*/true); |
| EXPECT_TRUE(peer->temporary()); |
| |
| auto fake_peer = std::make_unique<FakePeer>(kAddress0, dispatcher()); |
| test_device()->AddPeer(std::move(fake_peer)); |
| |
| size_t connect_cb_count = 0; |
| auto callback = [&connect_cb_count](auto result) { |
| EXPECT_TRUE(result.is_error()); |
| connect_cb_count++; |
| }; |
| |
| // Cause discovery to fail. |
| test_device()->SetDefaultCommandStatus( |
| hci_spec::kLESetScanEnable, |
| pw::bluetooth::emboss::StatusCode::COMMAND_DISALLOWED); |
| |
| EXPECT_TRUE(connected_peers().empty()); |
| conn_mgr()->Connect(peer->identifier(), callback, kConnectionOptions); |
| ASSERT_TRUE(peer->le()); |
| EXPECT_EQ(Peer::ConnectionState::kInitializing, |
| peer->le()->connection_state()); |
| |
| RunUntilIdle(); |
| EXPECT_EQ(connect_cb_count, 1u); |
| EXPECT_FALSE(peer->temporary()); |
| EXPECT_EQ(Peer::ConnectionState::kNotConnected, |
| peer->le()->connection_state()); |
| } |
| |
| TEST_F(LowEnergyConnectionManagerTest, |
| ConnectSinglePeerDiscoveryFailedDuringScan) { |
| auto* peer = peer_cache()->NewPeer(kAddress0, /*connectable=*/true); |
| EXPECT_TRUE(peer->temporary()); |
| |
| // Don't add peer to FakeController to prevent scan from completing. |
| |
| size_t connect_cb_count = 0; |
| auto callback = [&connect_cb_count](auto result) { |
| EXPECT_TRUE(result.is_error()); |
| connect_cb_count++; |
| }; |
| |
| EXPECT_TRUE(connected_peers().empty()); |
| conn_mgr()->Connect(peer->identifier(), callback, kConnectionOptions); |
| ASSERT_TRUE(peer->le()); |
| EXPECT_EQ(Peer::ConnectionState::kInitializing, |
| peer->le()->connection_state()); |
| RunUntilIdle(); |
| EXPECT_EQ(connect_cb_count, 0u); |
| |
| // Cause discovery to fail when attempting to restart scan after scan period |
| // ends. |
| test_device()->SetDefaultCommandStatus( |
| hci_spec::kLESetScanEnable, |
| pw::bluetooth::emboss::StatusCode::COMMAND_DISALLOWED); |
| RunFor(kLEGeneralDiscoveryScanMin); |
| EXPECT_EQ(connect_cb_count, 1u); |
| EXPECT_FALSE(peer->temporary()); |
| EXPECT_EQ(Peer::ConnectionState::kNotConnected, |
| peer->le()->connection_state()); |
| } |
| |
| TEST_F(LowEnergyConnectionManagerTest, |
| PeerDisconnectBeforeInterrogationCompletes) { |
| auto* peer = peer_cache()->NewPeer(kAddress0, /*connectable=*/true); |
| EXPECT_TRUE(peer->temporary()); |
| |
| auto fake_peer = std::make_unique<FakePeer>(kAddress0, dispatcher()); |
| auto fake_peer_ptr = fake_peer.get(); |
| test_device()->AddPeer(std::move(fake_peer)); |
| |
| // Cause interrogation to stall by not responding with a Read Remote Version |
| // complete event. |
| test_device()->SetDefaultCommandStatus( |
| hci_spec::kReadRemoteVersionInfo, |
| pw::bluetooth::emboss::StatusCode::SUCCESS); |
| |
| int connect_count = 0; |
| auto callback = [&connect_count](auto result) { |
| ASSERT_TRUE(result.is_error()); |
| connect_count++; |
| }; |
| |
| EXPECT_TRUE(connected_peers().empty()); |
| conn_mgr()->Connect(peer->identifier(), callback, kConnectionOptions); |
| ASSERT_TRUE(peer->le()); |
| EXPECT_EQ(Peer::ConnectionState::kInitializing, |
| peer->le()->connection_state()); |
| |
| RunUntilIdle(); |
| |
| ASSERT_FALSE(fake_peer_ptr->logical_links().empty()); |
| auto handle = *fake_peer_ptr->logical_links().begin(); |
| |
| test_device()->Disconnect(peer->address()); |
| |
| RunUntilIdle(); |
| |
| // Complete interrogation so that callback gets called. |
| auto response = hci::EmbossEventPacket::New< |
| pw::bluetooth::emboss::ReadRemoteVersionInfoCompleteEventWriter>( |
| hci_spec::kReadRemoteVersionInfoCompleteEventCode); |
| auto view = response.view_t(); |
| view.status().Write(pw::bluetooth::emboss::StatusCode::SUCCESS); |
| view.connection_handle().Write(handle); |
| test_device()->SendCommandChannelPacket(response.data()); |
| |
| RunUntilIdle(); |
| EXPECT_EQ(0u, connected_peers().size()); |
| EXPECT_EQ(1, connect_count); |
| EXPECT_EQ(Peer::ConnectionState::kNotConnected, |
| peer->le()->connection_state()); |
| } |
| |
| TEST_F(LowEnergyConnectionManagerTest, |
| LocalDisconnectBeforeInterrogationCompletes) { |
| auto* peer = peer_cache()->NewPeer(kAddress0, /*connectable=*/true); |
| EXPECT_TRUE(peer->temporary()); |
| |
| auto fake_peer = std::make_unique<FakePeer>(kAddress0, dispatcher()); |
| auto fake_peer_ptr = fake_peer.get(); |
| test_device()->AddPeer(std::move(fake_peer)); |
| |
| // Cause interrogation to stall by not responding with a Read Remote Version |
| // complete event. |
| test_device()->SetDefaultCommandStatus( |
| hci_spec::kReadRemoteVersionInfo, |
| pw::bluetooth::emboss::StatusCode::SUCCESS); |
| |
| int connect_count = 0; |
| auto callback = [&connect_count](auto result) { |
| ASSERT_TRUE(result.is_error()); |
| connect_count++; |
| }; |
| |
| EXPECT_TRUE(connected_peers().empty()); |
| conn_mgr()->Connect(peer->identifier(), callback, kConnectionOptions); |
| ASSERT_TRUE(peer->le()); |
| EXPECT_EQ(Peer::ConnectionState::kInitializing, |
| peer->le()->connection_state()); |
| |
| RunUntilIdle(); |
| |
| ASSERT_FALSE(fake_peer_ptr->logical_links().empty()); |
| auto handle = *fake_peer_ptr->logical_links().begin(); |
| |
| conn_mgr()->Disconnect(peer->identifier()); |
| |
| RunUntilIdle(); |
| |
| // Complete interrogation so that callback gets called. |
| auto response = hci::EmbossEventPacket::New< |
| pw::bluetooth::emboss::ReadRemoteVersionInfoCompleteEventWriter>( |
| hci_spec::kReadRemoteVersionInfoCompleteEventCode); |
| auto view = response.view_t(); |
| view.status().Write(pw::bluetooth::emboss::StatusCode::SUCCESS); |
| view.connection_handle().Write(handle); |
| test_device()->SendCommandChannelPacket(response.data()); |
| |
| RunUntilIdle(); |
| EXPECT_EQ(0u, connected_peers().size()); |
| EXPECT_EQ(1, connect_count); |
| EXPECT_EQ(Peer::ConnectionState::kNotConnected, |
| peer->le()->connection_state()); |
| } |
| |
| TEST_F(LowEnergyConnectionManagerTest, |
| ConnectionFailedToBeEstablishedRetriesTwiceAndFails) { |
| auto* peer = peer_cache()->NewPeer(kAddress0, /*connectable=*/true); |
| EXPECT_TRUE(peer->temporary()); |
| |
| auto fake_peer = std::make_unique<FakePeer>(kAddress0, dispatcher()); |
| test_device()->AddPeer(std::move(fake_peer)); |
| |
| size_t connected_count = 0; |
| test_device()->set_connection_state_callback( |
| [&](auto, auto, bool connected, bool) { |
| if (connected) { |
| connected_count++; |
| } |
| }); |
| |
| int connect_cb_count = 0; |
| auto callback = [&connect_cb_count](auto result) { |
| ASSERT_TRUE(result.is_error()); |
| connect_cb_count++; |
| }; |
| |
| EXPECT_TRUE(connected_peers().empty()); |
| |
| // Cause interrogation to fail. |
| test_device()->SetDefaultCommandStatus( |
| hci_spec::kReadRemoteVersionInfo, |
| pw::bluetooth::emboss::StatusCode::CONNECTION_FAILED_TO_BE_ESTABLISHED); |
| |
| conn_mgr()->Connect(peer->identifier(), callback, kConnectionOptions); |
| ASSERT_TRUE(peer->le()); |
| EXPECT_EQ(Peer::ConnectionState::kInitializing, |
| peer->le()->connection_state()); |
| |
| // Exhaust retries and cause connection to fail. |
| for (size_t i = 0; i < kConnectDelays.size(); i++) { |
| SCOPED_TRACE(i); |
| if (i != 0) { |
| RunFor(kConnectDelays[i] - std::chrono::nanoseconds(1)); |
| EXPECT_EQ(connected_count, i); |
| RunFor(std::chrono::nanoseconds(1)); |
| } else { |
| RunFor(kConnectDelays[i]); |
| } |
| EXPECT_EQ(connected_count, i + 1); |
| EXPECT_EQ(Peer::ConnectionState::kInitializing, |
| peer->le()->connection_state()); |
| |
| test_device()->Disconnect( |
| kAddress0, |
| pw::bluetooth::emboss::StatusCode::CONNECTION_FAILED_TO_BE_ESTABLISHED); |
| RunUntilIdle(); |
| EXPECT_EQ(connected_count, i + 1); |
| // A connect command should be sent in connect_delays[i+1] |
| } |
| |
| RunUntilIdle(); |
| EXPECT_TRUE(connected_peers().empty()); |
| EXPECT_EQ(connect_cb_count, 1); |
| EXPECT_FALSE(peer->temporary()); |
| EXPECT_EQ(Peer::ConnectionState::kNotConnected, |
| peer->le()->connection_state()); |
| } |
| |
| TEST_F(LowEnergyConnectionManagerTest, |
| ConnectionFailedToBeEstablishedRetriesAndSucceeds) { |
| auto* peer = peer_cache()->NewPeer(kAddress0, /*connectable=*/true); |
| EXPECT_TRUE(peer->temporary()); |
| |
| auto fake_peer = std::make_unique<FakePeer>(kAddress0, dispatcher()); |
| test_device()->AddPeer(std::move(fake_peer)); |
| |
| std::unique_ptr<LowEnergyConnectionHandle> conn_handle; |
| auto callback = [&conn_handle](auto result) { |
| ASSERT_EQ(fit::ok(), result); |
| conn_handle = std::move(result).value(); |
| EXPECT_TRUE(conn_handle->active()); |
| }; |
| |
| EXPECT_TRUE(connected_peers().empty()); |
| |
| // Cause interrogation to fail. |
| test_device()->SetDefaultCommandStatus( |
| hci_spec::kReadRemoteVersionInfo, |
| pw::bluetooth::emboss::StatusCode::CONNECTION_FAILED_TO_BE_ESTABLISHED); |
| |
| conn_mgr()->Connect(peer->identifier(), callback, kConnectionOptions); |
| ASSERT_TRUE(peer->le()); |
| EXPECT_EQ(Peer::ConnectionState::kInitializing, |
| peer->le()->connection_state()); |
| |
| RunUntilIdle(); |
| EXPECT_EQ(Peer::ConnectionState::kInitializing, |
| peer->le()->connection_state()); |
| EXPECT_FALSE(conn_handle); |
| |
| // Allow the next interrogation to succeed. |
| test_device()->ClearDefaultCommandStatus(hci_spec::kReadRemoteVersionInfo); |
| |
| // Disconnect should initiate retry #2 after a pause. |
| test_device()->Disconnect( |
| kAddress0, |
| pw::bluetooth::emboss::StatusCode::CONNECTION_FAILED_TO_BE_ESTABLISHED); |
| RunFor(std::chrono::seconds(2)); |
| EXPECT_EQ(1u, connected_peers().size()); |
| EXPECT_EQ(1u, connected_peers().count(kAddress0)); |
| ASSERT_TRUE(conn_handle); |
| EXPECT_TRUE(conn_handle->active()); |
| EXPECT_FALSE(peer->temporary()); |
| EXPECT_EQ(Peer::ConnectionState::kConnected, peer->le()->connection_state()); |
| } |
| |
| TEST_F(LowEnergyConnectionManagerTest, |
| ConnectionFailedToBeEstablishedAndDisconnectDuringRetryPauseTimeout) { |
| auto* peer = peer_cache()->NewPeer(kAddress0, /*connectable=*/true); |
| EXPECT_TRUE(peer->temporary()); |
| |
| auto fake_peer = std::make_unique<FakePeer>(kAddress0, dispatcher()); |
| test_device()->AddPeer(std::move(fake_peer)); |
| |
| int connect_cb_count = 0; |
| auto callback = [&](auto result) { |
| ASSERT_TRUE(result.is_error()); |
| EXPECT_EQ(HostError::kCanceled, result.error_value()); |
| connect_cb_count++; |
| }; |
| |
| EXPECT_TRUE(connected_peers().empty()); |
| |
| // Cause interrogation to fail. |
| test_device()->SetDefaultCommandStatus( |
| hci_spec::kReadRemoteVersionInfo, |
| pw::bluetooth::emboss::StatusCode::CONNECTION_FAILED_TO_BE_ESTABLISHED); |
| |
| conn_mgr()->Connect(peer->identifier(), callback, kConnectionOptions); |
| ASSERT_TRUE(peer->le()); |
| EXPECT_EQ(Peer::ConnectionState::kInitializing, |
| peer->le()->connection_state()); |
| |
| RunUntilIdle(); |
| EXPECT_EQ(Peer::ConnectionState::kInitializing, |
| peer->le()->connection_state()); |
| EXPECT_EQ(connect_cb_count, 0); |
| |
| // Allow the next interrogation to succeed (even though it shouldn't happen). |
| test_device()->ClearDefaultCommandStatus(hci_spec::kReadRemoteVersionInfo); |
| |
| // Peer disconnection during interrogation should also cause retry (after a |
| // pause) |
| test_device()->Disconnect( |
| kAddress0, |
| pw::bluetooth::emboss::StatusCode::CONNECTION_FAILED_TO_BE_ESTABLISHED); |
| RunUntilIdle(); |
| // Disconnect will cancel request. |
| conn_mgr()->Disconnect(peer->identifier()); |
| // Ensure timer is canceled. |
| // TODO(saeedali): run repeatedly? |
| // RunLoopRepeatedlyFor(std::chrono::seconds(1)); |
| RunFor(std::chrono::seconds(1)); |
| EXPECT_EQ(connect_cb_count, 1); |
| EXPECT_EQ(0u, connected_peers().size()); |
| EXPECT_FALSE(peer->temporary()); |
| EXPECT_EQ(Peer::ConnectionState::kNotConnected, |
| peer->le()->connection_state()); |
| } |
| |
| // Tests that receiving a peer kConnectionFailedToBeEstablished disconnect event |
| // before interrogation fails does not crash. |
| TEST_F(LowEnergyConnectionManagerTest, |
| ConnectionFailedToBeEstablishedDisconnectionBeforeInterrogationFails) { |
| auto* peer = peer_cache()->NewPeer(kAddress0, /*connectable=*/true); |
| EXPECT_TRUE(peer->temporary()); |
| |
| auto fake_peer = std::make_unique<FakePeer>(kAddress0, dispatcher()); |
| test_device()->AddPeer(std::move(fake_peer)); |
| |
| int connect_cb_count = 0; |
| std::unique_ptr<LowEnergyConnectionHandle> conn_handle; |
| auto callback = [&](auto result) { |
| ASSERT_EQ(fit::ok(), result); |
| connect_cb_count++; |
| conn_handle = std::move(result).value(); |
| }; |
| |
| EXPECT_TRUE(connected_peers().empty()); |
| |
| // Cause interrogation to stall waiting for command complete event. |
| test_device()->SetDefaultCommandStatus( |
| hci_spec::kReadRemoteVersionInfo, |
| pw::bluetooth::emboss::StatusCode::SUCCESS); |
| |
| conn_mgr()->Connect(peer->identifier(), callback, kConnectionOptions); |
| ASSERT_TRUE(peer->le()); |
| EXPECT_EQ(Peer::ConnectionState::kInitializing, |
| peer->le()->connection_state()); |
| |
| RunUntilIdle(); |
| EXPECT_EQ(Peer::ConnectionState::kInitializing, |
| peer->le()->connection_state()); |
| EXPECT_EQ(connect_cb_count, 0); |
| |
| // Let retries succeed. |
| test_device()->ClearDefaultCommandStatus(hci_spec::kReadRemoteVersionInfo); |
| |
| // Peer disconnection during interrogation should also cause retry (after a |
| // pause). |
| test_device()->Disconnect( |
| kAddress0, |
| pw::bluetooth::emboss::StatusCode::CONNECTION_FAILED_TO_BE_ESTABLISHED); |
| RunUntilIdle(); |
| |
| // Complete interrogation with an error that will be received after the |
| // disconnect event. Event params other than status will be ignored because |
| // status is an error. |
| auto response = hci::EmbossEventPacket::New< |
| pw::bluetooth::emboss::ReadRemoteVersionInfoCompleteEventWriter>( |
| hci_spec::kReadRemoteVersionInfoCompleteEventCode); |
| auto view = response.view_t(); |
| view.status().Write(pw::bluetooth::emboss::StatusCode::UNKNOWN_CONNECTION_ID); |
| test_device()->SendCommandChannelPacket(response.data()); |
| |
| RunUntilIdle(); |
| EXPECT_EQ(Peer::ConnectionState::kInitializing, |
| peer->le()->connection_state()); |
| EXPECT_EQ(connect_cb_count, 0); |
| |
| // Wait for retry. |
| RunFor(kConnectDelays[1]); |
| EXPECT_EQ(connect_cb_count, 1); |
| EXPECT_TRUE(conn_handle); |
| EXPECT_EQ(1u, connected_peers().size()); |
| EXPECT_FALSE(peer->temporary()); |
| EXPECT_EQ(Peer::ConnectionState::kConnected, peer->le()->connection_state()); |
| } |
| |
| // Behavior verified in this test: |
| // 1. After a successful connection + bond to establish auto-connect for a peer, |
| // an auto-connect- |
| // initiated connection attempt to that peer that fails with any of |
| // `statuses_that_disable_ autoconnect` disables auto-connect to that peer. |
| // 2. After a successful ..., NON-autoconnect-inititated connection attempts |
| // (inbound or outbound) |
| // to that peer that fail with any of `statuses_that_disable_autoconnect` do |
| // NOT disable auto- connect to that peer. |
| TEST_F(LowEnergyConnectionManagerTest, |
| ConnectSucceedsThenAutoConnectFailsDisablesAutoConnect) { |
| // If an auto-connect attempt fails with any of these status codes, we disable |
| // the auto-connect behavior until the next successful connection to avoid |
| // looping. |
| // clang-format off |
| std::array statuses_that_disable_autoconnect = { |
| pw::bluetooth::emboss::StatusCode::CONNECTION_TIMEOUT, |
| pw::bluetooth::emboss::StatusCode::CONNECTION_REJECTED_SECURITY, |
| pw::bluetooth::emboss::StatusCode::CONNECTION_ACCEPT_TIMEOUT_EXCEEDED, |
| pw::bluetooth::emboss::StatusCode::CONNECTION_TERMINATED_BY_LOCAL_HOST, |
| pw::bluetooth::emboss::StatusCode::CONNECTION_FAILED_TO_BE_ESTABLISHED |
| }; |
| // clang-format on |
| // Validate that looping with a uint8_t is safe, it makes the rest of the code |
| // simpler. |
| static_assert(statuses_that_disable_autoconnect.size() < |
| std::numeric_limits<uint8_t>::max()); |
| for (uint8_t i = 0; |
| i < static_cast<uint8_t>(statuses_that_disable_autoconnect.size()); |
| ++i) { |
| SCOPED_TRACE( |
| hci_spec::StatusCodeToString(statuses_that_disable_autoconnect[i])); |
| const DeviceAddress kAddressI(DeviceAddress::Type::kLEPublic, {i}); |
| auto* peer = peer_cache()->NewPeer(kAddressI, /*connectable=*/true); |
| auto fake_peer = std::make_unique<FakePeer>(kAddressI, dispatcher()); |
| test_device()->AddPeer(std::move(fake_peer)); |
| |
| std::unique_ptr<LowEnergyConnectionHandle> conn_handle; |
| auto success_cb = [&conn_handle](auto result) { |
| ASSERT_EQ(fit::ok(), result); |
| conn_handle = std::move(result).value(); |
| EXPECT_TRUE(conn_handle->active()); |
| }; |
| |
| conn_mgr()->Connect(peer->identifier(), success_cb, kConnectionOptions); |
| RunUntilIdle(); |
| // Peer needs to be bonded to set auto connect |
| peer->MutLe().SetBondData(sm::PairingData{}); |
| |
| EXPECT_EQ(1u, connected_peers().count(kAddressI)); |
| ASSERT_TRUE(conn_handle); |
| EXPECT_EQ(peer->identifier(), conn_handle->peer_identifier()); |
| EXPECT_TRUE(peer->le()->should_auto_connect()); |
| EXPECT_EQ(Peer::ConnectionState::kConnected, |
| peer->le()->connection_state()); |
| |
| // Disconnect has to be initiated by the "remote" device - locally initiated |
| // disconnects will unset auto connect behavior. |
| test_device()->Disconnect(peer->address()); |
| |
| RunUntilIdle(); |
| |
| EXPECT_EQ(Peer::ConnectionState::kNotConnected, |
| peer->le()->connection_state()); |
| EXPECT_TRUE(peer->le()->should_auto_connect()); |
| |
| // Causes interrogation to fail, so inbound connections will fail to |
| // establish. This complexity is needed because inbound connections are |
| // already HCI-connected when passed to the LECM. |
| test_device()->SetDefaultCommandStatus( |
| hci_spec::kReadRemoteVersionInfo, statuses_that_disable_autoconnect[i]); |
| |
| ConnectionResult result = fit::ok(nullptr); |
| auto failure_cb = [&result](auto res) { result = std::move(res); }; |
| // Create an inbound HCI connection and try to register it with the LECM |
| test_device()->ConnectLowEnergy(kAddressI); |
| RunUntilIdle(); |
| auto link = MoveLastRemoteInitiated(); |
| ASSERT_TRUE(link); |
| result = fit::ok(nullptr); |
| conn_mgr()->RegisterRemoteInitiatedLink( |
| std::move(link), BondableMode::Bondable, failure_cb); |
| RunUntilIdle(); |
| // We always wait until the peer disconnects to relay connection failure |
| // when dealing with the 0x3e kConnectionFailedToBeEstablished error. |
| if (statuses_that_disable_autoconnect[i] == |
| pw::bluetooth::emboss::StatusCode:: |
| CONNECTION_FAILED_TO_BE_ESTABLISHED) { |
| test_device()->Disconnect(kAddressI, |
| pw::bluetooth::emboss::StatusCode:: |
| CONNECTION_FAILED_TO_BE_ESTABLISHED); |
| RunUntilIdle(); |
| } |
| // Remote-initiated connection attempts that fail should not disable the |
| // auto-connect flag. |
| ASSERT_TRUE(result.is_error()); |
| EXPECT_EQ(Peer::ConnectionState::kNotConnected, |
| peer->le()->connection_state()); |
| EXPECT_TRUE(peer->le()->should_auto_connect()); |
| // Allow successful interrogation later in the test |
| test_device()->ClearDefaultCommandStatus(hci_spec::kReadRemoteVersionInfo); |
| |
| // Set this peer to reject all connections with |
| // statuses_that_disable_autoconnect[i] |
| FakePeer* peer_ref = test_device()->FindPeer(peer->address()); |
| ASSERT_TRUE(peer); |
| peer_ref->set_connect_response(statuses_that_disable_autoconnect[i]); |
| |
| // User-initiated connection attempts that fail should not disable the |
| // auto-connect flag. |
| const LowEnergyConnectionOptions kNotAutoConnectOptions{.auto_connect = |
| false}; |
| conn_mgr()->Connect(peer->identifier(), failure_cb, kNotAutoConnectOptions); |
| RunUntilIdle(); |
| |
| ASSERT_TRUE(result.is_error()); |
| EXPECT_EQ(Peer::ConnectionState::kNotConnected, |
| peer->le()->connection_state()); |
| EXPECT_TRUE(peer->le()->should_auto_connect()); |
| |
| // Emulate an auto-connection here, as we disable the auto-connect behavior |
| // only for auto-connect-initiated attempts that fail, NOT for |
| // user-initiated or remote-initiated connection attempts that fail. |
| result = fit::ok(nullptr); |
| const LowEnergyConnectionOptions kAutoConnectOptions{.auto_connect = true}; |
| conn_mgr()->Connect(peer->identifier(), failure_cb, kAutoConnectOptions); |
| ASSERT_TRUE(peer->le()); |
| EXPECT_EQ(Peer::ConnectionState::kInitializing, |
| peer->le()->connection_state()); |
| |
| RunUntilIdle(); |
| |
| ASSERT_TRUE(result.is_error()); |
| EXPECT_EQ(Peer::ConnectionState::kNotConnected, |
| peer->le()->connection_state()); |
| EXPECT_FALSE(peer->le()->should_auto_connect()); |
| } |
| } |
| |
| #ifndef NINSPECT |
| TEST_F(LowEnergyConnectionManagerTest, Inspect) { |
| inspect::Inspector inspector; |
| conn_mgr()->AttachInspect(inspector.GetRoot(), |
| "low_energy_connection_manager"); |
| |
| auto* peer = peer_cache()->NewPeer(kAddress0, /*connectable=*/true); |
| EXPECT_TRUE(peer->temporary()); |
| |
| auto fake_peer = std::make_unique<FakePeer>(kAddress0, dispatcher()); |
| test_device()->AddPeer(std::move(fake_peer)); |
| |
| std::unique_ptr<LowEnergyConnectionHandle> conn_handle; |
| auto callback = [&conn_handle](auto result) { |
| ASSERT_EQ(fit::ok(), result); |
| conn_handle = std::move(result).value(); |
| }; |
| conn_mgr()->Connect(peer->identifier(), callback, kConnectionOptions); |
| |
| auto requests_matcher = |
| AllOf(NodeMatches(NameMatches("pending_requests")), |
| ChildrenMatch(ElementsAre(NodeMatches( |
| AllOf(NameMatches("pending_request_0x0"), |
| PropertyList(UnorderedElementsAre( |
| StringIs("peer_id", peer->identifier().ToString()), |
| IntIs("callbacks", 1)))))))); |
| |
| auto outbound_connector_matcher_attempt_0 = AllOf( |
| NodeMatches(AllOf(NameMatches("outbound_connector"), |
| PropertyList(UnorderedElementsAre( |
| StringIs("peer_id", peer->identifier().ToString()), |
| IntIs("connection_attempt", 0), |
| BoolIs("is_outbound", true), |
| StringIs("state", "StartingScanning")))))); |
| |
| auto empty_connections_matcher = |
| AllOf(NodeMatches(NameMatches("connections")), |
| ChildrenMatch(::testing::IsEmpty())); |
| |
| auto conn_mgr_property_matcher = PropertyList( |
| UnorderedElementsAre(UintIs("disconnect_explicit_disconnect_count", 0), |
| UintIs("disconnect_link_error_count", 0), |
| UintIs("disconnect_remote_disconnection_count", 0), |
| UintIs("disconnect_zero_ref_count", 0), |
| UintIs("incoming_connection_failure_count", 0), |
| UintIs("incoming_connection_success_count", 0), |
| UintIs("outgoing_connection_failure_count", 0), |
| UintIs("outgoing_connection_success_count", 0), |
| IntIs("recent_connection_failures", 0))); |
| |
| auto conn_mgr_during_connecting_matcher = |
| AllOf(NodeMatches(AllOf(NameMatches("low_energy_connection_manager"), |
| conn_mgr_property_matcher)), |
| ChildrenMatch( |
| UnorderedElementsAre(requests_matcher, |
| empty_connections_matcher, |
| outbound_connector_matcher_attempt_0))); |
| |
| auto hierarchy = inspect::ReadFromVmo(inspector.DuplicateVmo()); |
| EXPECT_THAT(hierarchy.value(), |
| ChildrenMatch(ElementsAre(conn_mgr_during_connecting_matcher))); |
| |
| // Finish connecting. |
| RunUntilIdle(); |
| |
| auto empty_requests_matcher = |
| AllOf(NodeMatches(NameMatches("pending_requests")), |
| ChildrenMatch(::testing::IsEmpty())); |
| |
| auto conn_matcher = NodeMatches( |
| AllOf(NameMatches("connection_0x1"), |
| PropertyList(UnorderedElementsAre( |
| StringIs("peer_id", peer->identifier().ToString()), |
| StringIs("peer_address", peer->address().ToString()), |
| IntIs("ref_count", 1))))); |
| |
| auto connections_matcher = AllOf(NodeMatches(NameMatches("connections")), |
| ChildrenMatch(ElementsAre(conn_matcher))); |
| |
| auto conn_mgr_property_matcher_after_connecting = PropertyList( |
| UnorderedElementsAre(UintIs("disconnect_explicit_disconnect_count", 0), |
| UintIs("disconnect_link_error_count", 0), |
| UintIs("disconnect_remote_disconnection_count", 0), |
| UintIs("disconnect_zero_ref_count", 0), |
| UintIs("incoming_connection_failure_count", 0), |
| UintIs("incoming_connection_success_count", 0), |
| UintIs("outgoing_connection_failure_count", 0), |
| UintIs("outgoing_connection_success_count", 1), |
| IntIs("recent_connection_failures", 0))); |
| |
| auto conn_mgr_after_connecting_matcher = |
| AllOf(NodeMatches(conn_mgr_property_matcher_after_connecting), |
| ChildrenMatch(UnorderedElementsAre(empty_requests_matcher, |
| connections_matcher))); |
| |
| hierarchy = inspect::ReadFromVmo(inspector.DuplicateVmo()); |
| EXPECT_THAT(hierarchy.value(), |
| ChildrenMatch(ElementsAre(conn_mgr_after_connecting_matcher))); |
| |
| // LECM must be destroyed before the inspector to avoid a page fault on |
| // destruction of inspect properties (they try to update the inspect VMO, |
| // which is deleted on inspector destruction). |
| DeleteConnMgr(); |
| } |
| #endif // NINSPECT |
| |
| #ifndef NINSPECT |
| TEST_F(LowEnergyConnectionManagerTest, InspectFailedConnection) { |
| inspect::Inspector inspector; |
| conn_mgr()->AttachInspect(inspector.GetRoot(), |
| "low_energy_connection_manager"); |
| |
| auto* peer = peer_cache()->NewPeer(kAddress0, /*connectable=*/true); |
| EXPECT_TRUE(peer->temporary()); |
| |
| auto fake_peer = std::make_unique<FakePeer>(kAddress0, dispatcher()); |
| fake_peer->set_connect_status( |
| pw::bluetooth::emboss::StatusCode::CONNECTION_LIMIT_EXCEEDED); |
| test_device()->AddPeer(std::move(fake_peer)); |
| |
| auto callback = [](auto result) { ASSERT_TRUE(result.is_error()); }; |
| conn_mgr()->Connect(peer->identifier(), callback, kConnectionOptions); |
| RunUntilIdle(); |
| |
| auto conn_mgr_property_matcher = PropertyList( |
| UnorderedElementsAre(UintIs("disconnect_explicit_disconnect_count", 0), |
| UintIs("disconnect_link_error_count", 0), |
| UintIs("disconnect_remote_disconnection_count", 0), |
| UintIs("disconnect_zero_ref_count", 0), |
| UintIs("incoming_connection_failure_count", 0), |
| UintIs("incoming_connection_success_count", 0), |
| UintIs("outgoing_connection_failure_count", 1), |
| UintIs("outgoing_connection_success_count", 0), |
| IntIs("recent_connection_failures", 1))); |
| |
| auto hierarchy = inspect::ReadFromVmo(inspector.DuplicateVmo()); |
| EXPECT_THAT( |
| hierarchy.value(), |
| ChildrenMatch(ElementsAre(NodeMatches(conn_mgr_property_matcher)))); |
| |
| RunFor(LowEnergyConnectionManager:: |
| kInspectRecentConnectionFailuresExpiryDuration - |
| std::chrono::nanoseconds(1)); |
| hierarchy = inspect::ReadFromVmo(inspector.DuplicateVmo()); |
| EXPECT_THAT( |
| hierarchy.value(), |
| ChildrenMatch(ElementsAre(NodeMatches(conn_mgr_property_matcher)))); |
| |
| // Failures should revert to 0 after expiry duration. |
| RunFor(std::chrono::nanoseconds(1)); |
| conn_mgr_property_matcher = PropertyList( |
| UnorderedElementsAre(UintIs("disconnect_explicit_disconnect_count", 0), |
| UintIs("disconnect_link_error_count", 0), |
| UintIs("disconnect_remote_disconnection_count", 0), |
| UintIs("disconnect_zero_ref_count", 0), |
| UintIs("incoming_connection_failure_count", 0), |
| UintIs("incoming_connection_success_count", 0), |
| UintIs("outgoing_connection_failure_count", 1), |
| UintIs("outgoing_connection_success_count", 0), |
| IntIs("recent_connection_failures", 0))); |
| hierarchy = inspect::ReadFromVmo(inspector.DuplicateVmo()); |
| EXPECT_THAT( |
| hierarchy.value(), |
| ChildrenMatch(ElementsAre(NodeMatches(conn_mgr_property_matcher)))); |
| |
| // LECM must be destroyed before the inspector to avoid a page fault on |
| // destruction of inspect properties (they try to update the inspect VMO, |
| // which is deleted on inspector destruction). |
| DeleteConnMgr(); |
| } |
| #endif // NINSPECT |
| |
| TEST_F( |
| LowEnergyConnectionManagerTest, |
| RegisterRemoteInitiatedLinkWithAddressDifferentFromIdentityAddressDoesNotCrash) { |
| DeviceAddress kIdentityAddress(DeviceAddress::Type::kLEPublic, |
| {1, 0, 0, 0, 0, 0}); |
| DeviceAddress kRandomAddress(DeviceAddress::Type::kLERandom, |
| {2, 0, 0, 0, 0, 0}); |
| Peer* peer = peer_cache()->NewPeer(kRandomAddress, /*connectable=*/true); |
| sm::PairingData data; |
| data.peer_ltk = kLTK; |
| data.local_ltk = kLTK; |
| data.irk = sm::Key(sm::SecurityProperties(), Random<UInt128>()); |
| data.identity_address = kIdentityAddress; |
| EXPECT_TRUE(peer_cache()->StoreLowEnergyBond(peer->identifier(), data)); |
| EXPECT_EQ(peer->address(), kIdentityAddress); |
| |
| test_device()->AddPeer( |
| std::make_unique<FakePeer>(kRandomAddress, dispatcher())); |
| test_device()->ConnectLowEnergy(kRandomAddress); |
| RunUntilIdle(); |
| |
| auto link = MoveLastRemoteInitiated(); |
| ASSERT_TRUE(link); |
| |
| std::unique_ptr<LowEnergyConnectionHandle> conn_handle; |
| conn_mgr()->RegisterRemoteInitiatedLink( |
| std::move(link), BondableMode::Bondable, [&](auto result) { |
| ASSERT_EQ(fit::ok(), result); |
| conn_handle = std::move(result).value(); |
| }); |
| EXPECT_EQ(peer->le()->connection_state(), |
| Peer::ConnectionState::kInitializing); |
| |
| RunUntilIdle(); |
| |
| ASSERT_TRUE(conn_handle); |
| EXPECT_TRUE(conn_handle->active()); |
| EXPECT_EQ(peer->identifier(), conn_handle->peer_identifier()); |
| EXPECT_TRUE(peer->connected()); |
| } |
| |
| TEST_F(LowEnergyConnectionManagerTest, |
| ConnectSinglePeerWithInterrogationLongerThanCentralPauseTimeout) { |
| auto* peer = peer_cache()->NewPeer(kAddress0, /*connectable=*/true); |
| EXPECT_TRUE(peer->temporary()); |
| |
| auto fake_peer = std::make_unique<FakePeer>(kAddress0, dispatcher()); |
| test_device()->AddPeer(std::move(fake_peer)); |
| |
| // Cause interrogation to stall so that we can expire the central pause |
| // timeout. |
| fit::closure send_read_remote_features_rsp; |
| test_device()->pause_responses_for_opcode( |
| hci_spec::kLEReadRemoteFeatures, [&](fit::closure unpause) { |
| send_read_remote_features_rsp = std::move(unpause); |
| }); |
| |
| size_t hci_update_conn_param_count = 0; |
| test_device()->set_le_connection_parameters_callback( |
| [&](auto address, auto parameters) { hci_update_conn_param_count++; }); |
| |
| std::unique_ptr<LowEnergyConnectionHandle> conn_handle; |
| auto callback = [&conn_handle](auto result) { |
| ASSERT_EQ(fit::ok(), result); |
| conn_handle = std::move(result).value(); |
| EXPECT_TRUE(conn_handle->active()); |
| }; |
| |
| EXPECT_TRUE(connected_peers().empty()); |
| conn_mgr()->Connect(peer->identifier(), callback, kConnectionOptions); |
| ASSERT_TRUE(peer->le()); |
| EXPECT_EQ(Peer::ConnectionState::kInitializing, |
| peer->le()->connection_state()); |
| |
| RunUntilIdle(); |
| EXPECT_EQ(1u, connected_peers().size()); |
| EXPECT_EQ(1u, connected_peers().count(kAddress0)); |
| EXPECT_FALSE(conn_handle); |
| EXPECT_EQ(hci_update_conn_param_count, 0u); |
| |
| RunFor(kLEConnectionPausePeripheral); |
| EXPECT_FALSE(conn_handle); |
| EXPECT_EQ(hci_update_conn_param_count, 0u); |
| |
| // Allow interrogation to complete. |
| send_read_remote_features_rsp(); |
| RunUntilIdle(); |
| EXPECT_EQ(hci_update_conn_param_count, 1u); |
| ASSERT_TRUE(conn_handle); |
| EXPECT_TRUE(conn_handle->active()); |
| EXPECT_EQ(Peer::ConnectionState::kConnected, peer->le()->connection_state()); |
| } |
| |
| TEST_F( |
| LowEnergyConnectionManagerTest, |
| RegisterRemoteInitiatedLinkWithInterrogationLongerThanPeripheralPauseTimeout) { |
| // A FakePeer does not support the HCI connection parameter update procedure |
| // by default, so the L2CAP procedure will be used. |
| test_device()->AddPeer(std::make_unique<FakePeer>(kAddress0, dispatcher())); |
| |
| // Cause interrogation to stall so that we can expire the peripheral pause |
| // timeout. |
| fit::closure send_read_remote_features_rsp; |
| test_device()->pause_responses_for_opcode( |
| hci_spec::kLEReadRemoteFeatures, [&](fit::closure unpause) { |
| send_read_remote_features_rsp = std::move(unpause); |
| }); |
| |
| size_t l2cap_conn_param_update_count = 0; |
| fake_l2cap()->set_connection_parameter_update_request_responder( |
| [&](auto, auto) { |
| l2cap_conn_param_update_count++; |
| return true; |
| }); |
| |
| // First create a fake incoming connection. |
| test_device()->ConnectLowEnergy(kAddress0); |
| RunUntilIdle(); |
| auto link = MoveLastRemoteInitiated(); |
| ASSERT_TRUE(link); |
| |
| std::unique_ptr<LowEnergyConnectionHandle> conn_handle; |
| conn_mgr()->RegisterRemoteInitiatedLink( |
| std::move(link), BondableMode::Bondable, [&](auto result) { |
| ASSERT_EQ(fit::ok(), result); |
| conn_handle = std::move(result).value(); |
| }); |
| |
| // A Peer should now exist in the cache. |
| auto* peer = peer_cache()->FindByAddress(kAddress0); |
| ASSERT_TRUE(peer); |
| EXPECT_EQ(peer->le()->connection_state(), |
| Peer::ConnectionState::kInitializing); |
| |
| RunUntilIdle(); |
| EXPECT_EQ(1u, connected_peers().size()); |
| EXPECT_EQ(1u, connected_peers().count(kAddress0)); |
| EXPECT_FALSE(conn_handle); |
| EXPECT_EQ(l2cap_conn_param_update_count, 0u); |
| |
| RunFor(kLEConnectionPausePeripheral); |
| EXPECT_FALSE(conn_handle); |
| EXPECT_EQ(l2cap_conn_param_update_count, 0u); |
| |
| // Allow interrogation to complete. |
| send_read_remote_features_rsp(); |
| RunUntilIdle(); |
| EXPECT_EQ(l2cap_conn_param_update_count, 1u); |
| ASSERT_TRUE(conn_handle); |
| EXPECT_TRUE(conn_handle->active()); |
| EXPECT_EQ(Peer::ConnectionState::kConnected, peer->le()->connection_state()); |
| } |
| |
| // Test fixture for tests that disconnect a connection in various ways and |
| // expect that controller packet counts are not cleared on disconnecting, but |
| // are cleared on disconnection complete. Tests should disconnect |
| // conn_handle0(). |
| class PendingPacketsTest : public LowEnergyConnectionManagerTest { |
| public: |
| PendingPacketsTest() = default; |
| ~PendingPacketsTest() override = default; |
| |
| void SetUp() override { |
| LowEnergyConnectionManagerTest::SetUp(); |
| const DeviceAddress kPeerAddr0(DeviceAddress::Type::kLEPublic, {1}); |
| const DeviceAddress kPeerAddr1(DeviceAddress::Type::kLEPublic, {2}); |
| |
| peer0_ = peer_cache()->NewPeer(kPeerAddr0, /*connectable=*/true); |
| EXPECT_TRUE(peer0_->temporary()); |
| test_device()->AddPeer( |
| std::make_unique<FakePeer>(kPeerAddr0, dispatcher())); |
| |
| peer1_ = peer_cache()->NewPeer(kPeerAddr1, /*connectable=*/true); |
| EXPECT_TRUE(peer1_->temporary()); |
| test_device()->AddPeer( |
| std::make_unique<FakePeer>(kPeerAddr1, dispatcher())); |
| |
| // Connect |peer0| |
| conn_handle0_.reset(); |
| auto callback0 = [this](auto result) { |
| ASSERT_EQ(fit::ok(), result); |
| conn_handle0_ = std::move(result).value(); |
| EXPECT_TRUE(conn_handle0_->active()); |
| }; |
| conn_mgr()->Connect(peer0_->identifier(), callback0, kConnectionOptions); |
| RunUntilIdle(); |
| |
| // Connect |peer1| |
| conn_handle1_.reset(); |
| auto callback1 = [this](auto result) { |
| ASSERT_EQ(fit::ok(), result); |
| conn_handle1_ = std::move(result).value(); |
| EXPECT_TRUE(conn_handle1_->active()); |
| }; |
| conn_mgr()->Connect(peer1_->identifier(), callback1, kConnectionOptions); |
| RunUntilIdle(); |
| |
| packet_count_ = 0; |
| test_device()->SetDataCallback([&](const auto&) { packet_count_++; }, |
| dispatcher()); |
| test_device()->set_auto_completed_packets_event_enabled(false); |
| test_device()->set_auto_disconnection_complete_event_enabled(false); |
| } |
| |
| void TearDown() override { |
| peer0_ = nullptr; |
| peer1_ = nullptr; |
| conn_handle0_.reset(); |
| conn_handle1_.reset(); |
| |
| LowEnergyConnectionManagerTest::TearDown(); |
| } |
| |
| Peer* peer0() { return peer0_; } |
| std::unique_ptr<LowEnergyConnectionHandle>& conn_handle0() { |
| return conn_handle0_; |
| } |
| |
| protected: |
| hci_spec::ConnectionHandle handle0_; |
| std::unique_ptr<LowEnergyConnectionHandle> conn_handle0_; |
| std::unique_ptr<LowEnergyConnectionHandle> conn_handle1_; |
| |
| private: |
| size_t packet_count_; |
| Peer* peer0_; |
| Peer* peer1_; |
| }; |
| |
| using LowEnergyConnectionManagerPendingPacketsTest = PendingPacketsTest; |
| |
| TEST_F(LowEnergyConnectionManagerPendingPacketsTest, Disconnect) { |
| hci::FakeAclConnection connection_0( |
| acl_data_channel(), conn_handle0_->handle(), bt::LinkType::kLE); |
| hci::FakeAclConnection connection_1( |
| acl_data_channel(), conn_handle1_->handle(), bt::LinkType::kLE); |
| |
| acl_data_channel()->RegisterConnection(connection_0.GetWeakPtr()); |
| acl_data_channel()->RegisterConnection(connection_1.GetWeakPtr()); |
| |
| // Fill controller buffer by sending |kLEMaxNumPackets| packets to |peer0| |
| for (size_t i = 0; i < kLEMaxNumPackets; i++) { |
| hci::ACLDataPacketPtr packet = hci::ACLDataPacket::New( |
| conn_handle0_->handle(), |
| hci_spec::ACLPacketBoundaryFlag::kFirstNonFlushable, |
| hci_spec::ACLBroadcastFlag::kPointToPoint, |
| /*payload_size=*/1); |
| connection_0.QueuePacket(std::move(packet)); |
| RunUntilIdle(); |
| } |
| |
| // Queue packet for |peer1| |
| hci::ACLDataPacketPtr packet = hci::ACLDataPacket::New( |
| conn_handle1_->handle(), |
| hci_spec::ACLPacketBoundaryFlag::kFirstNonFlushable, |
| hci_spec::ACLBroadcastFlag::kPointToPoint, |
| /*payload_size=*/1); |
| connection_1.QueuePacket(std::move(packet)); |
| RunUntilIdle(); |
| |
| // Packet for |peer1| should not have been sent because controller buffer is |
| // full |
| EXPECT_EQ(connection_0.queued_packets().size(), 0u); |
| EXPECT_EQ(connection_1.queued_packets().size(), 1u); |
| |
| handle0_ = conn_handle0_->handle(); |
| |
| // Send HCI Disconnect to controller |
| EXPECT_TRUE(conn_mgr()->Disconnect(peer0()->identifier())); |
| |
| RunUntilIdle(); |
| |
| // Packet for |peer1| should not have been sent before Disconnection Complete |
| // event |
| EXPECT_EQ(connection_0.queued_packets().size(), 0u); |
| EXPECT_EQ(connection_1.queued_packets().size(), 1u); |
| |
| acl_data_channel()->UnregisterConnection(conn_handle0_->handle()); |
| |
| // FakeController send us the HCI Disconnection Complete event |
| test_device()->SendDisconnectionCompleteEvent(handle0_); |
| RunUntilIdle(); |
| |
| // |peer0|'s link should have been unregistered and packet for |peer1| should |
| // have been sent |
| EXPECT_EQ(connection_0.queued_packets().size(), 0u); |
| EXPECT_EQ(connection_1.queued_packets().size(), 0u); |
| } |
| |
| TEST_F(LowEnergyConnectionManagerPendingPacketsTest, ReleaseRef) { |
| hci::FakeAclConnection connection_0( |
| acl_data_channel(), conn_handle0_->handle(), bt::LinkType::kLE); |
| hci::FakeAclConnection connection_1( |
| acl_data_channel(), conn_handle1_->handle(), bt::LinkType::kLE); |
| |
| acl_data_channel()->RegisterConnection(connection_0.GetWeakPtr()); |
| acl_data_channel()->RegisterConnection(connection_1.GetWeakPtr()); |
| |
| // Fill controller buffer by sending |kLEMaxNumPackets| packets to |peer0| |
| for (size_t i = 0; i < kLEMaxNumPackets; i++) { |
| hci::ACLDataPacketPtr packet = hci::ACLDataPacket::New( |
| conn_handle0_->handle(), |
| hci_spec::ACLPacketBoundaryFlag::kFirstNonFlushable, |
| hci_spec::ACLBroadcastFlag::kPointToPoint, |
| /*payload_size=*/1); |
| connection_0.QueuePacket(std::move(packet)); |
| RunUntilIdle(); |
| } |
| |
| // Queue packet for |peer1| |
| hci::ACLDataPacketPtr packet = hci::ACLDataPacket::New( |
| conn_handle1_->handle(), |
| hci_spec::ACLPacketBoundaryFlag::kFirstNonFlushable, |
| hci_spec::ACLBroadcastFlag::kPointToPoint, |
| /*payload_size=*/1); |
| connection_1.QueuePacket(std::move(packet)); |
| RunUntilIdle(); |
| |
| // Packet for |peer1| should not have been sent before Disconnection Complete |
| // event |
| EXPECT_EQ(connection_0.queued_packets().size(), 0u); |
| EXPECT_EQ(connection_1.queued_packets().size(), 1u); |
| |
| handle0_ = conn_handle0_->handle(); |
| |
| // Releasing ref should send HCI Disconnect to controller |
| conn_handle0().reset(); |
| |
| RunUntilIdle(); |
| |
| // Packet for |peer1| should not have been sent before Disconnection Complete |
| // event |
| EXPECT_EQ(connection_0.queued_packets().size(), 0u); |
| EXPECT_EQ(connection_1.queued_packets().size(), 1u); |
| |
| acl_data_channel()->UnregisterConnection(handle0_); |
| |
| // FakeController send us the HCI Disconnection Complete event |
| test_device()->SendDisconnectionCompleteEvent(handle0_); |
| RunUntilIdle(); |
| |
| // |peer0|'s link should have been unregistered and packet for |peer1| should |
| // have been sent |
| EXPECT_EQ(connection_0.queued_packets().size(), 0u); |
| EXPECT_EQ(connection_1.queued_packets().size(), 0u); |
| } |
| |
| } // namespace |
| } // namespace bt::gap |