| // 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/gap/low_energy_connection_manager.h" |
| |
| #include <zircon/assert.h> |
| |
| #include <cstddef> |
| #include <memory> |
| #include <vector> |
| |
| #include <fbl/macros.h> |
| #include <gtest/gtest.h> |
| |
| #include "src/connectivity/bluetooth/core/bt-host/common/byte_buffer.h" |
| #include "src/connectivity/bluetooth/core/bt-host/common/random.h" |
| #include "src/connectivity/bluetooth/core/bt-host/data/fake_domain.h" |
| #include "src/connectivity/bluetooth/core/bt-host/gap/peer.h" |
| #include "src/connectivity/bluetooth/core/bt-host/gap/peer_cache.h" |
| #include "src/connectivity/bluetooth/core/bt-host/gatt/fake_layer.h" |
| #include "src/connectivity/bluetooth/core/bt-host/hci/defaults.h" |
| #include "src/connectivity/bluetooth/core/bt-host/hci/fake_local_address_delegate.h" |
| #include "src/connectivity/bluetooth/core/bt-host/hci/hci_constants.h" |
| #include "src/connectivity/bluetooth/core/bt-host/hci/low_energy_connector.h" |
| #include "src/connectivity/bluetooth/core/bt-host/l2cap/fake_channel.h" |
| #include "src/connectivity/bluetooth/core/bt-host/l2cap/fake_channel_test.h" |
| #include "src/connectivity/bluetooth/core/bt-host/l2cap/l2cap.h" |
| #include "src/connectivity/bluetooth/core/bt-host/sm/smp.h" |
| #include "src/connectivity/bluetooth/core/bt-host/sm/types.h" |
| #include "src/connectivity/bluetooth/core/bt-host/testing/fake_controller.h" |
| #include "src/connectivity/bluetooth/core/bt-host/testing/fake_controller_test.h" |
| #include "src/connectivity/bluetooth/core/bt-host/testing/fake_peer.h" |
| |
| namespace bt { |
| namespace gap { |
| namespace { |
| |
| using bt::sm::BondableMode; |
| using bt::testing::FakeController; |
| using bt::testing::FakePeer; |
| |
| using TestingBase = bt::testing::FakeControllerTest<FakeController>; |
| using l2cap::testing::FakeChannel; |
| |
| 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 size_t kLEMaxNumPackets = 10; |
| const hci::DataBufferInfo kLEDataBufferInfo(hci::kMaxACLPayloadSize, kLEMaxNumPackets); |
| |
| 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>(inspector_.GetRoot().CreateChild(PeerCache::kInspectNodeName)); |
| l2cap_ = data::testing::FakeDomain::Create(); |
| |
| connector_ = std::make_unique<hci::LowEnergyConnector>( |
| transport(), &addr_delegate_, dispatcher(), |
| fit::bind_member(this, &LowEnergyConnectionManagerTest::OnIncomingConnection)); |
| |
| conn_mgr_ = std::make_unique<LowEnergyConnectionManager>( |
| transport(), &addr_delegate_, connector_.get(), peer_cache_.get(), l2cap_, |
| gatt::testing::FakeLayer::Create()); |
| |
| test_device()->set_connection_state_callback( |
| fit::bind_member(this, &LowEnergyConnectionManagerTest::OnConnectionStateChanged)); |
| StartTestDevice(); |
| } |
| |
| void TearDown() override { |
| if (conn_mgr_) |
| conn_mgr_ = 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(); } |
| data::testing::FakeDomain* fake_l2cap() const { return l2cap_.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_; } |
| |
| hci::ConnectionPtr MoveLastRemoteInitiated() { return std::move(last_remote_initiated_); } |
| |
| private: |
| // Called by |connector_| when a new remote initiated connection is received. |
| void OnIncomingConnection(hci::ConnectionHandle handle, hci::Connection::Role role, |
| const DeviceAddress& peer_address, |
| const hci::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_ = hci::Connection::CreateLE(handle, role, local_address, peer_address, |
| conn_params, transport()); |
| } |
| |
| // Called by FakeController on connection events. |
| void OnConnectionStateChanged(const DeviceAddress& address, hci::ConnectionHandle handle, |
| bool connected, bool canceled) { |
| bt_log(TRACE, "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) { |
| ZX_DEBUG_ASSERT(connected_peers_.find(address) == connected_peers_.end()); |
| connected_peers_.insert(address); |
| } else { |
| ZX_DEBUG_ASSERT(connected_peers_.find(address) != connected_peers_.end()); |
| connected_peers_.erase(address); |
| } |
| } |
| |
| inspect::Inspector inspector_; |
| |
| fbl::RefPtr<data::testing::FakeDomain> l2cap_; |
| |
| hci::FakeLocalAddressDelegate addr_delegate_; |
| std::unique_ptr<PeerCache> peer_cache_; |
| std::unique_ptr<hci::LowEnergyConnector> connector_; |
| std::unique_ptr<LowEnergyConnectionManager> conn_mgr_; |
| |
| // The most recent remote-initiated connection reported by |connector_|. |
| hci::ConnectionPtr last_remote_initiated_; |
| |
| PeerList connected_peers_; |
| PeerList canceled_peers_; |
| |
| DISALLOW_COPY_AND_ASSIGN_ALLOW_MOVE(LowEnergyConnectionManagerTest); |
| }; |
| |
| using GAP_LowEnergyConnectionManagerTest = LowEnergyConnectionManagerTest; |
| |
| TEST_F(GAP_LowEnergyConnectionManagerTest, ConnectUnknownPeer) { |
| constexpr PeerId kUnknownId(1); |
| EXPECT_FALSE(conn_mgr()->Connect(kUnknownId, {})); |
| } |
| |
| TEST_F(GAP_LowEnergyConnectionManagerTest, ConnectClassicPeer) { |
| auto* peer = peer_cache()->NewPeer(kAddress2, true); |
| EXPECT_FALSE(conn_mgr()->Connect(peer->identifier(), {})); |
| } |
| |
| TEST_F(GAP_LowEnergyConnectionManagerTest, ConnectNonConnectablePeer) { |
| auto* peer = peer_cache()->NewPeer(kAddress0, false); |
| EXPECT_FALSE(conn_mgr()->Connect(peer->identifier(), {})); |
| } |
| |
| // An error is received via the HCI Command cb_status event |
| TEST_F(GAP_LowEnergyConnectionManagerTest, ConnectSinglePeerErrorStatus) { |
| auto* peer = peer_cache()->NewPeer(kAddress0, true); |
| auto fake_peer = std::make_unique<FakePeer>(kAddress0); |
| fake_peer->set_connect_status(hci::StatusCode::kConnectionFailedToBeEstablished); |
| test_device()->AddPeer(std::move(fake_peer)); |
| |
| ASSERT_TRUE(peer->le()); |
| EXPECT_EQ(Peer::ConnectionState::kNotConnected, peer->le()->connection_state()); |
| |
| hci::Status status; |
| auto callback = [&status](auto cb_status, auto conn_ref) { |
| EXPECT_FALSE(conn_ref); |
| status = cb_status; |
| }; |
| |
| EXPECT_TRUE(conn_mgr()->Connect(peer->identifier(), callback)); |
| EXPECT_EQ(Peer::ConnectionState::kInitializing, peer->le()->connection_state()); |
| |
| RunLoopUntilIdle(); |
| |
| EXPECT_TRUE(status.is_protocol_error()); |
| EXPECT_EQ(hci::StatusCode::kConnectionFailedToBeEstablished, status.protocol_error()); |
| EXPECT_EQ(Peer::ConnectionState::kNotConnected, peer->le()->connection_state()); |
| } |
| |
| // LE Connection Complete event reports error |
| TEST_F(GAP_LowEnergyConnectionManagerTest, ConnectSinglePeerFailure) { |
| auto* peer = peer_cache()->NewPeer(kAddress0, true); |
| auto fake_peer = std::make_unique<FakePeer>(kAddress0); |
| fake_peer->set_connect_response(hci::StatusCode::kConnectionFailedToBeEstablished); |
| test_device()->AddPeer(std::move(fake_peer)); |
| |
| hci::Status status; |
| auto callback = [&status](auto cb_status, auto conn_ref) { |
| EXPECT_FALSE(conn_ref); |
| status = cb_status; |
| }; |
| |
| EXPECT_TRUE(conn_mgr()->Connect(peer->identifier(), callback)); |
| ASSERT_TRUE(peer->le()); |
| EXPECT_EQ(Peer::ConnectionState::kInitializing, peer->le()->connection_state()); |
| |
| RunLoopUntilIdle(); |
| |
| EXPECT_TRUE(status.is_protocol_error()); |
| EXPECT_EQ(hci::StatusCode::kConnectionFailedToBeEstablished, status.protocol_error()); |
| EXPECT_EQ(Peer::ConnectionState::kNotConnected, peer->le()->connection_state()); |
| } |
| |
| TEST_F(GAP_LowEnergyConnectionManagerTest, ConnectSinglePeerTimeout) { |
| constexpr zx::duration kTestRequestTimeout = zx::sec(20); |
| |
| auto* peer = peer_cache()->NewPeer(kAddress0, true); |
| |
| // We add no fake peers to cause the request to time out. |
| |
| hci::Status status; |
| auto callback = [&status](auto cb_status, auto conn_ref) { |
| EXPECT_FALSE(conn_ref); |
| status = cb_status; |
| }; |
| |
| conn_mgr()->set_request_timeout_for_testing(kTestRequestTimeout); |
| EXPECT_TRUE(conn_mgr()->Connect(peer->identifier(), callback)); |
| ASSERT_TRUE(peer->le()); |
| EXPECT_EQ(Peer::ConnectionState::kInitializing, peer->le()->connection_state()); |
| |
| RunLoopFor(kTestRequestTimeout); |
| |
| EXPECT_FALSE(status); |
| EXPECT_EQ(HostError::kTimedOut, status.error()) << status.ToString(); |
| 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(GAP_LowEnergyConnectionManagerTest, PeerDoesNotExpireDuringTimeout) { |
| // Set a connection timeout that is longer than the PeerCache expiry |
| // timeout. |
| // TODO(BT-825): Consider configuring the cache timeout explicitly rather than |
| // relying on the kCacheTimeout constant. |
| constexpr zx::duration kTestRequestTimeout = kCacheTimeout + zx::sec(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, true); |
| EXPECT_TRUE(peer->temporary()); |
| |
| hci::Status status; |
| auto callback = [&status](auto cb_status, auto conn_ref) { |
| EXPECT_FALSE(conn_ref); |
| status = cb_status; |
| }; |
| EXPECT_TRUE(conn_mgr()->Connect(peer->identifier(), callback)); |
| ASSERT_TRUE(peer->le()); |
| EXPECT_EQ(Peer::ConnectionState::kInitializing, peer->le()->connection_state()); |
| EXPECT_FALSE(peer->temporary()); |
| |
| RunLoopFor(kTestRequestTimeout); |
| EXPECT_EQ(HostError::kTimedOut, status.error()) << status.ToString(); |
| EXPECT_EQ(peer, peer_cache()->FindByAddress(kAddress1)); |
| EXPECT_EQ(Peer::ConnectionState::kNotConnected, peer->le()->connection_state()); |
| EXPECT_TRUE(peer->temporary()); |
| } |
| |
| TEST_F(GAP_LowEnergyConnectionManagerTest, PeerDoesNotExpireDuringDelayedConnect) { |
| // Make the connection resolve after a delay that is longer than the cache |
| // timeout. |
| constexpr zx::duration kConnectionDelay = kCacheTimeout + zx::sec(1); |
| FakeController::Settings settings; |
| settings.ApplyLegacyLEConfig(); |
| settings.le_connection_delay = kConnectionDelay; |
| test_device()->set_settings(settings); |
| |
| auto* peer = peer_cache()->NewPeer(kAddress0, true); |
| auto id = peer->identifier(); |
| EXPECT_TRUE(peer->temporary()); |
| |
| auto fake_peer = std::make_unique<FakePeer>(kAddress0); |
| 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 + zx::sec(1)); |
| |
| // Initialize as error to verify that |callback| assigns success. |
| hci::Status status(HostError::kFailed); |
| LowEnergyConnectionRefPtr conn_ref; |
| auto callback = [&status, &conn_ref](auto cb_status, auto cb_conn_ref) { |
| status = cb_status; |
| conn_ref = std::move(cb_conn_ref); |
| |
| ASSERT_TRUE(status); |
| ASSERT_TRUE(conn_ref); |
| EXPECT_TRUE(conn_ref->active()); |
| }; |
| EXPECT_TRUE(conn_mgr()->Connect(id, callback)); |
| ASSERT_TRUE(peer->le()); |
| EXPECT_EQ(Peer::ConnectionState::kInitializing, peer->le()->connection_state()); |
| |
| RunLoopFor(kConnectionDelay); |
| ASSERT_TRUE(conn_ref); |
| EXPECT_TRUE(status); |
| |
| // 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(GAP_LowEnergyConnectionManagerTest, ConnectSinglePeer) { |
| auto* peer = peer_cache()->NewPeer(kAddress0, true); |
| EXPECT_TRUE(peer->temporary()); |
| |
| auto fake_peer = std::make_unique<FakePeer>(kAddress0); |
| test_device()->AddPeer(std::move(fake_peer)); |
| |
| // Initialize as error to verify that |callback| assigns success. |
| hci::Status status(HostError::kFailed); |
| LowEnergyConnectionRefPtr conn_ref; |
| auto callback = [&status, &conn_ref](auto cb_status, auto cb_conn_ref) { |
| EXPECT_TRUE(cb_conn_ref); |
| status = cb_status; |
| conn_ref = std::move(cb_conn_ref); |
| EXPECT_TRUE(conn_ref->active()); |
| }; |
| |
| EXPECT_TRUE(connected_peers().empty()); |
| EXPECT_TRUE(conn_mgr()->Connect(peer->identifier(), callback)); |
| ASSERT_TRUE(peer->le()); |
| EXPECT_EQ(Peer::ConnectionState::kInitializing, peer->le()->connection_state()); |
| |
| RunLoopUntilIdle(); |
| |
| EXPECT_TRUE(status); |
| EXPECT_EQ(1u, connected_peers().size()); |
| EXPECT_EQ(1u, connected_peers().count(kAddress0)); |
| |
| ASSERT_TRUE(conn_ref); |
| EXPECT_TRUE(conn_ref->active()); |
| EXPECT_EQ(peer->identifier(), conn_ref->peer_identifier()); |
| EXPECT_FALSE(peer->temporary()); |
| EXPECT_EQ(Peer::ConnectionState::kConnected, peer->le()->connection_state()); |
| } |
| |
| struct TestObject final : fbl::RefCounted<TestObject> { |
| explicit TestObject(bool* d) : deleted(d) { |
| ZX_DEBUG_ASSERT(deleted); |
| *deleted = false; |
| } |
| |
| ~TestObject() { *deleted = true; } |
| |
| bool* deleted; |
| }; |
| |
| TEST_F(GAP_LowEnergyConnectionManagerTest, DeleteRefInClosedCallback) { |
| auto* peer = peer_cache()->NewPeer(kAddress0, true); |
| test_device()->AddPeer(std::make_unique<FakePeer>(kAddress0)); |
| |
| bool deleted = false; |
| auto obj = fbl::AdoptRef(new TestObject(&deleted)); |
| LowEnergyConnectionRefPtr conn_ref; |
| int closed_count = 0; |
| auto closed_cb = [&, obj = std::move(obj)] { |
| closed_count++; |
| conn_ref = nullptr; |
| |
| // The object should remain alive for the duration of this callback. |
| EXPECT_FALSE(deleted); |
| }; |
| |
| auto success_cb = [&conn_ref, &closed_cb](auto status, auto cb_conn_ref) { |
| EXPECT_TRUE(status); |
| ASSERT_TRUE(cb_conn_ref); |
| conn_ref = std::move(cb_conn_ref); |
| conn_ref->set_closed_callback(std::move(closed_cb)); |
| }; |
| |
| ASSERT_TRUE(conn_mgr()->Connect(peer->identifier(), success_cb)); |
| RunLoopUntilIdle(); |
| |
| ASSERT_TRUE(conn_ref); |
| ASSERT_TRUE(conn_ref->active()); |
| |
| // This will trigger the closed callback. |
| EXPECT_TRUE(conn_mgr()->Disconnect(peer->identifier())); |
| RunLoopUntilIdle(); |
| |
| EXPECT_EQ(1, closed_count); |
| EXPECT_TRUE(connected_peers().empty()); |
| EXPECT_FALSE(conn_ref); |
| |
| // The object should be deleted. |
| EXPECT_TRUE(deleted); |
| } |
| |
| TEST_F(GAP_LowEnergyConnectionManagerTest, ReleaseRef) { |
| auto* peer = peer_cache()->NewPeer(kAddress0, true); |
| auto fake_peer = std::make_unique<FakePeer>(kAddress0); |
| test_device()->AddPeer(std::move(fake_peer)); |
| |
| // Initialize as error to verify that |callback| assigns success. |
| hci::Status status(HostError::kFailed); |
| LowEnergyConnectionRefPtr conn_ref; |
| auto callback = [&status, &conn_ref](auto cb_status, auto cb_conn_ref) { |
| EXPECT_TRUE(cb_conn_ref); |
| status = cb_status; |
| conn_ref = std::move(cb_conn_ref); |
| EXPECT_TRUE(conn_ref->active()); |
| }; |
| |
| EXPECT_TRUE(connected_peers().empty()); |
| EXPECT_TRUE(conn_mgr()->Connect(peer->identifier(), callback)); |
| |
| RunLoopUntilIdle(); |
| |
| EXPECT_TRUE(status); |
| EXPECT_EQ(1u, connected_peers().size()); |
| ASSERT_TRUE(peer->le()); |
| EXPECT_EQ(Peer::ConnectionState::kConnected, peer->le()->connection_state()); |
| |
| ASSERT_TRUE(conn_ref); |
| conn_ref = nullptr; |
| |
| RunLoopUntilIdle(); |
| |
| EXPECT_TRUE(connected_peers().empty()); |
| EXPECT_EQ(Peer::ConnectionState::kNotConnected, peer->le()->connection_state()); |
| } |
| |
| TEST_F(GAP_LowEnergyConnectionManagerTest, OnePeerTwoPendingRequestsBothFail) { |
| constexpr int kRequestCount = 2; |
| |
| auto* peer = peer_cache()->NewPeer(kAddress0, true); |
| auto fake_peer = std::make_unique<FakePeer>(kAddress0); |
| fake_peer->set_connect_response(hci::StatusCode::kConnectionFailedToBeEstablished); |
| test_device()->AddPeer(std::move(fake_peer)); |
| |
| hci::Status statuses[kRequestCount]; |
| |
| int cb_count = 0; |
| auto callback = [&statuses, &cb_count](auto cb_status, auto conn_ref) { |
| EXPECT_FALSE(conn_ref); |
| statuses[cb_count++] = cb_status; |
| }; |
| |
| for (int i = 0; i < kRequestCount; ++i) { |
| EXPECT_TRUE(conn_mgr()->Connect(peer->identifier(), callback)) << "request count: " << i + 1; |
| } |
| |
| RunLoopUntilIdle(); |
| |
| ASSERT_EQ(kRequestCount, cb_count); |
| for (int i = 0; i < kRequestCount; ++i) { |
| EXPECT_TRUE(statuses[i].is_protocol_error()); |
| EXPECT_EQ(hci::StatusCode::kConnectionFailedToBeEstablished, statuses[i].protocol_error()) |
| << "request count: " << i + 1; |
| } |
| } |
| |
| TEST_F(GAP_LowEnergyConnectionManagerTest, OnePeerManyPendingRequests) { |
| constexpr size_t kRequestCount = 50; |
| |
| auto* peer = peer_cache()->NewPeer(kAddress0, true); |
| auto fake_peer = std::make_unique<FakePeer>(kAddress0); |
| test_device()->AddPeer(std::move(fake_peer)); |
| |
| std::vector<LowEnergyConnectionRefPtr> conn_refs; |
| auto callback = [&conn_refs](auto cb_status, auto conn_ref) { |
| EXPECT_TRUE(conn_ref); |
| EXPECT_TRUE(cb_status); |
| conn_refs.emplace_back(std::move(conn_ref)); |
| }; |
| |
| for (size_t i = 0; i < kRequestCount; ++i) { |
| EXPECT_TRUE(conn_mgr()->Connect(peer->identifier(), callback)) << "request count: " << i + 1; |
| } |
| |
| RunLoopUntilIdle(); |
| |
| EXPECT_EQ(1u, connected_peers().size()); |
| EXPECT_EQ(1u, connected_peers().count(kAddress0)); |
| |
| EXPECT_EQ(kRequestCount, conn_refs.size()); |
| for (size_t i = 0; i < kRequestCount; ++i) { |
| ASSERT_TRUE(conn_refs[i]); |
| EXPECT_TRUE(conn_refs[i]->active()); |
| EXPECT_EQ(peer->identifier(), conn_refs[i]->peer_identifier()); |
| } |
| |
| // Release one reference. The rest should be active. |
| conn_refs[0] = nullptr; |
| for (size_t i = 1; i < kRequestCount; ++i) |
| EXPECT_TRUE(conn_refs[i]->active()); |
| |
| // Release all but one reference. |
| for (size_t i = 1; i < kRequestCount - 1; ++i) |
| conn_refs[i] = nullptr; |
| EXPECT_TRUE(conn_refs[kRequestCount - 1]->active()); |
| |
| // Drop the last reference. |
| conn_refs[kRequestCount - 1] = nullptr; |
| |
| RunLoopUntilIdle(); |
| |
| EXPECT_TRUE(connected_peers().empty()); |
| } |
| |
| TEST_F(GAP_LowEnergyConnectionManagerTest, AddRefAfterConnection) { |
| constexpr size_t kRefCount = 50; |
| |
| auto* peer = peer_cache()->NewPeer(kAddress0, true); |
| auto fake_peer = std::make_unique<FakePeer>(kAddress0); |
| test_device()->AddPeer(std::move(fake_peer)); |
| |
| std::vector<LowEnergyConnectionRefPtr> conn_refs; |
| auto callback = [&conn_refs](auto cb_status, auto conn_ref) { |
| EXPECT_TRUE(conn_ref); |
| EXPECT_TRUE(cb_status); |
| conn_refs.emplace_back(std::move(conn_ref)); |
| }; |
| |
| EXPECT_TRUE(conn_mgr()->Connect(peer->identifier(), callback)); |
| |
| RunLoopUntilIdle(); |
| |
| EXPECT_EQ(1u, connected_peers().size()); |
| EXPECT_EQ(1u, connected_peers().count(kAddress0)); |
| EXPECT_EQ(1u, conn_refs.size()); |
| |
| // Add new references. |
| for (size_t i = 1; i < kRefCount; ++i) { |
| EXPECT_TRUE(conn_mgr()->Connect(peer->identifier(), callback)) << "request count: " << i + 1; |
| RunLoopUntilIdle(); |
| } |
| |
| EXPECT_EQ(1u, connected_peers().size()); |
| EXPECT_EQ(1u, connected_peers().count(kAddress0)); |
| EXPECT_EQ(kRefCount, conn_refs.size()); |
| |
| // Disconnect. |
| conn_refs.clear(); |
| |
| RunLoopUntilIdle(); |
| |
| EXPECT_TRUE(connected_peers().empty()); |
| } |
| |
| TEST_F(GAP_LowEnergyConnectionManagerTest, PendingRequestsOnTwoPeers) { |
| auto* peer0 = peer_cache()->NewPeer(kAddress0, true); |
| auto* peer1 = peer_cache()->NewPeer(kAddress1, true); |
| |
| test_device()->AddPeer(std::make_unique<FakePeer>(kAddress0)); |
| test_device()->AddPeer(std::make_unique<FakePeer>(kAddress1)); |
| |
| std::vector<LowEnergyConnectionRefPtr> conn_refs; |
| auto callback = [&conn_refs](auto cb_status, auto conn_ref) { |
| EXPECT_TRUE(conn_ref); |
| EXPECT_TRUE(cb_status); |
| conn_refs.emplace_back(std::move(conn_ref)); |
| }; |
| |
| EXPECT_TRUE(conn_mgr()->Connect(peer0->identifier(), callback)); |
| EXPECT_TRUE(conn_mgr()->Connect(peer1->identifier(), callback)); |
| |
| RunLoopUntilIdle(); |
| |
| EXPECT_EQ(2u, connected_peers().size()); |
| EXPECT_EQ(1u, connected_peers().count(kAddress0)); |
| EXPECT_EQ(1u, connected_peers().count(kAddress1)); |
| |
| ASSERT_EQ(2u, conn_refs.size()); |
| ASSERT_TRUE(conn_refs[0]); |
| ASSERT_TRUE(conn_refs[1]); |
| EXPECT_EQ(peer0->identifier(), conn_refs[0]->peer_identifier()); |
| EXPECT_EQ(peer1->identifier(), conn_refs[1]->peer_identifier()); |
| |
| // |peer1| should disconnect first. |
| conn_refs[1] = nullptr; |
| |
| RunLoopUntilIdle(); |
| |
| EXPECT_EQ(1u, connected_peers().size()); |
| EXPECT_EQ(1u, connected_peers().count(kAddress0)); |
| |
| conn_refs.clear(); |
| |
| RunLoopUntilIdle(); |
| EXPECT_TRUE(connected_peers().empty()); |
| } |
| |
| TEST_F(GAP_LowEnergyConnectionManagerTest, PendingRequestsOnTwoPeersOneFails) { |
| auto* peer0 = peer_cache()->NewPeer(kAddress0, true); |
| auto* peer1 = peer_cache()->NewPeer(kAddress1, true); |
| |
| auto fake_peer0 = std::make_unique<FakePeer>(kAddress0); |
| fake_peer0->set_connect_response(hci::StatusCode::kConnectionFailedToBeEstablished); |
| test_device()->AddPeer(std::move(fake_peer0)); |
| test_device()->AddPeer(std::make_unique<FakePeer>(kAddress1)); |
| |
| std::vector<LowEnergyConnectionRefPtr> conn_refs; |
| auto callback = [&conn_refs](auto, auto conn_ref) { |
| conn_refs.emplace_back(std::move(conn_ref)); |
| }; |
| |
| EXPECT_TRUE(conn_mgr()->Connect(peer0->identifier(), callback)); |
| EXPECT_TRUE(conn_mgr()->Connect(peer1->identifier(), callback)); |
| |
| RunLoopUntilIdle(); |
| |
| EXPECT_EQ(1u, connected_peers().size()); |
| EXPECT_EQ(1u, connected_peers().count(kAddress1)); |
| |
| ASSERT_EQ(2u, conn_refs.size()); |
| EXPECT_FALSE(conn_refs[0]); |
| ASSERT_TRUE(conn_refs[1]); |
| EXPECT_EQ(peer1->identifier(), conn_refs[1]->peer_identifier()); |
| |
| // Both connections should disconnect. |
| conn_refs.clear(); |
| |
| RunLoopUntilIdle(); |
| EXPECT_TRUE(connected_peers().empty()); |
| } |
| |
| TEST_F(GAP_LowEnergyConnectionManagerTest, Destructor) { |
| auto* peer0 = peer_cache()->NewPeer(kAddress0, true); |
| auto* peer1 = peer_cache()->NewPeer(kAddress1, true); |
| |
| // Connecting to this peer will succeed. |
| test_device()->AddPeer(std::make_unique<FakePeer>(kAddress0)); |
| |
| // Connecting to this peer will remain pending. |
| auto pending_peer = std::make_unique<FakePeer>(kAddress1); |
| 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. |
| |
| LowEnergyConnectionRefPtr conn_ref; |
| auto success_cb = [&conn_ref](auto status, auto cb_conn_ref) { |
| EXPECT_TRUE(cb_conn_ref); |
| EXPECT_TRUE(status); |
| |
| conn_ref = std::move(cb_conn_ref); |
| }; |
| |
| EXPECT_TRUE(conn_mgr()->Connect(peer0->identifier(), success_cb)); |
| RunLoopUntilIdle(); |
| |
| ASSERT_TRUE(conn_ref); |
| bool conn_closed = false; |
| conn_ref->set_closed_callback([&conn_closed] { conn_closed = true; }); |
| |
| bool error_cb_called = false; |
| auto error_cb = [&error_cb_called](auto status, auto conn_ref) { |
| EXPECT_FALSE(conn_ref); |
| EXPECT_EQ(HostError::kFailed, status.error()); |
| 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. |
| EXPECT_TRUE(conn_mgr()->Connect(peer1->identifier(), error_cb)); |
| DeleteConnMgr(); |
| |
| RunLoopUntilIdle(); |
| |
| EXPECT_TRUE(error_cb_called); |
| EXPECT_TRUE(conn_closed); |
| EXPECT_EQ(1u, canceled_peers().size()); |
| EXPECT_EQ(1u, canceled_peers().count(kAddress1)); |
| } |
| |
| TEST_F(GAP_LowEnergyConnectionManagerTest, DisconnectPendingConnections) { |
| auto* dev0 = peer_cache()->NewPeer(kAddress0, true); |
| auto* dev1 = peer_cache()->NewPeer(kAddress1, true); |
| |
| auto callback = [](auto, auto) {}; // no-op |
| |
| EXPECT_TRUE(conn_mgr()->Connect(dev0->identifier(), callback)); |
| EXPECT_TRUE(conn_mgr()->Connect(dev1->identifier(), callback)); |
| EXPECT_EQ(Peer::ConnectionState::kInitializing, dev0->le()->connection_state()); |
| |
| EXPECT_FALSE(conn_mgr()->Disconnect(dev0->identifier())); |
| EXPECT_FALSE(conn_mgr()->Disconnect(dev1->identifier())); |
| } |
| |
| TEST_F(GAP_LowEnergyConnectionManagerTest, DisconnectUnknownPeer) { |
| // Unknown peers are inherently "not connected." |
| EXPECT_TRUE(conn_mgr()->Disconnect(PeerId(999))); |
| } |
| |
| TEST_F(GAP_LowEnergyConnectionManagerTest, DisconnectUnconnectedPeer) { |
| auto* peer = peer_cache()->NewPeer(kAddress0, true); |
| test_device()->AddPeer(std::make_unique<FakePeer>(kAddress0)); |
| |
| // This returns true so long the peer is not connected. |
| EXPECT_TRUE(conn_mgr()->Disconnect(peer->identifier())); |
| } |
| |
| TEST_F(GAP_LowEnergyConnectionManagerTest, Disconnect) { |
| auto* peer = peer_cache()->NewPeer(kAddress0, true); |
| test_device()->AddPeer(std::make_unique<FakePeer>(kAddress0)); |
| |
| int closed_count = 0; |
| auto closed_cb = [&closed_count] { closed_count++; }; |
| |
| std::vector<LowEnergyConnectionRefPtr> conn_refs; |
| auto success_cb = [&conn_refs, &closed_cb](auto status, auto conn_ref) { |
| EXPECT_TRUE(status); |
| ASSERT_TRUE(conn_ref); |
| conn_ref->set_closed_callback(closed_cb); |
| conn_refs.push_back(std::move(conn_ref)); |
| }; |
| |
| // Issue two connection refs. |
| EXPECT_TRUE(conn_mgr()->Connect(peer->identifier(), success_cb)); |
| EXPECT_TRUE(conn_mgr()->Connect(peer->identifier(), success_cb)); |
| |
| RunLoopUntilIdle(); |
| |
| ASSERT_EQ(2u, conn_refs.size()); |
| |
| EXPECT_TRUE(conn_mgr()->Disconnect(peer->identifier())); |
| |
| RunLoopUntilIdle(); |
| |
| EXPECT_EQ(2, closed_count); |
| EXPECT_TRUE(connected_peers().empty()); |
| EXPECT_TRUE(canceled_peers().empty()); |
| } |
| |
| TEST_F(GAP_LowEnergyConnectionManagerTest, IntentionalDisconnectDisablesAutoConnectBehavior) { |
| auto* peer = peer_cache()->NewPeer(kAddress0, true); |
| test_device()->AddPeer(std::make_unique<FakePeer>(kAddress0)); |
| |
| std::vector<LowEnergyConnectionRefPtr> conn_refs; |
| auto success_cb = [&conn_refs](auto status, auto conn_ref) { |
| conn_refs.push_back(std::move(conn_ref)); |
| }; |
| |
| sm::PairingData data; |
| data.ltk = sm::LTK(); |
| data.irk = sm::Key(sm::SecurityProperties(), Random<UInt128>()); |
| EXPECT_TRUE(peer_cache()->StoreLowEnergyBond(peer->identifier(), data)); |
| |
| // Issue connection ref. |
| EXPECT_TRUE(conn_mgr()->Connect(peer->identifier(), success_cb)); |
| RunLoopUntilIdle(); |
| |
| // 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())); |
| RunLoopUntilIdle(); |
| EXPECT_FALSE(peer->le()->should_auto_connect()); |
| |
| // Intentional re-connection should re-enable the auto-connection property. |
| EXPECT_TRUE(conn_mgr()->Connect(peer->identifier(), success_cb)); |
| RunLoopUntilIdle(); |
| EXPECT_TRUE(peer->le()->should_auto_connect()); |
| } |
| |
| TEST_F(GAP_LowEnergyConnectionManagerTest, IncidentalDisconnectDoesNotAffectAutoConnectBehavior) { |
| auto* peer = peer_cache()->NewPeer(kAddress0, true); |
| test_device()->AddPeer(std::make_unique<FakePeer>(kAddress0)); |
| |
| std::vector<LowEnergyConnectionRefPtr> conn_refs; |
| auto success_cb = [&conn_refs](auto status, auto conn_ref) { |
| conn_refs.push_back(std::move(conn_ref)); |
| }; |
| |
| sm::PairingData data; |
| data.ltk = sm::LTK(); |
| data.irk = sm::Key(sm::SecurityProperties(), Random<UInt128>()); |
| EXPECT_TRUE(peer_cache()->StoreLowEnergyBond(peer->identifier(), data)); |
| |
| // Issue connection ref. |
| EXPECT_TRUE(conn_mgr()->Connect(peer->identifier(), success_cb)); |
| RunLoopUntilIdle(); |
| |
| // 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_refs.size()); |
| conn_refs[0] = nullptr; |
| RunLoopUntilIdle(); |
| EXPECT_TRUE(peer->le()->should_auto_connect()); |
| } |
| |
| TEST_F(GAP_LowEnergyConnectionManagerTest, DisconnectThrice) { |
| auto* peer = peer_cache()->NewPeer(kAddress0, true); |
| test_device()->AddPeer(std::make_unique<FakePeer>(kAddress0)); |
| |
| int closed_count = 0; |
| auto closed_cb = [&closed_count] { closed_count++; }; |
| |
| LowEnergyConnectionRefPtr conn_ref; |
| auto success_cb = [&closed_cb, &conn_ref](auto status, auto cb_conn_ref) { |
| EXPECT_TRUE(status); |
| conn_ref = std::move(cb_conn_ref); |
| ASSERT_TRUE(conn_ref); |
| conn_ref->set_closed_callback(closed_cb); |
| }; |
| |
| EXPECT_TRUE(conn_mgr()->Connect(peer->identifier(), success_cb)); |
| |
| RunLoopUntilIdle(); |
| |
| 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())); |
| |
| RunLoopUntilIdle(); |
| |
| // 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(GAP_LowEnergyConnectionManagerTest, DisconnectEvent) { |
| auto* peer = peer_cache()->NewPeer(kAddress0, true); |
| |
| test_device()->AddPeer(std::make_unique<FakePeer>(kAddress0)); |
| |
| int closed_count = 0; |
| auto closed_cb = [&closed_count] { closed_count++; }; |
| |
| std::vector<LowEnergyConnectionRefPtr> conn_refs; |
| auto success_cb = [&conn_refs, &closed_cb](auto status, auto conn_ref) { |
| EXPECT_TRUE(status); |
| ASSERT_TRUE(conn_ref); |
| conn_ref->set_closed_callback(closed_cb); |
| conn_refs.push_back(std::move(conn_ref)); |
| }; |
| |
| // Issue two connection refs. |
| EXPECT_TRUE(conn_mgr()->Connect(peer->identifier(), success_cb)); |
| EXPECT_TRUE(conn_mgr()->Connect(peer->identifier(), success_cb)); |
| |
| RunLoopUntilIdle(); |
| |
| ASSERT_EQ(2u, conn_refs.size()); |
| |
| // This makes FakeController send us HCI Disconnection Complete events. |
| test_device()->Disconnect(kAddress0); |
| |
| RunLoopUntilIdle(); |
| |
| EXPECT_EQ(2, closed_count); |
| } |
| |
| TEST_F(GAP_LowEnergyConnectionManagerTest, DisconnectAfterRefsReleased) { |
| auto* peer = peer_cache()->NewPeer(kAddress0, true); |
| test_device()->AddPeer(std::make_unique<FakePeer>(kAddress0)); |
| |
| LowEnergyConnectionRefPtr conn_ref; |
| auto success_cb = [&conn_ref](auto status, auto cb_conn_ref) { |
| EXPECT_TRUE(status); |
| conn_ref = std::move(cb_conn_ref); |
| }; |
| |
| EXPECT_TRUE(conn_mgr()->Connect(peer->identifier(), success_cb)); |
| |
| RunLoopUntilIdle(); |
| |
| ASSERT_TRUE(conn_ref); |
| conn_ref.reset(); |
| |
| // Try to disconnect while the zero-refs connection is being disconnected. |
| EXPECT_TRUE(conn_mgr()->Disconnect(peer->identifier())); |
| |
| RunLoopUntilIdle(); |
| |
| EXPECT_TRUE(connected_peers().empty()); |
| EXPECT_TRUE(canceled_peers().empty()); |
| } |
| |
| TEST_F(GAP_LowEnergyConnectionManagerTest, DisconnectWhileRefPending) { |
| auto* peer = peer_cache()->NewPeer(kAddress0, true); |
| test_device()->AddPeer(std::make_unique<FakePeer>(kAddress0)); |
| |
| LowEnergyConnectionRefPtr conn_ref; |
| auto success_cb = [&conn_ref](auto status, auto cb_conn_ref) { |
| EXPECT_TRUE(status); |
| ASSERT_TRUE(cb_conn_ref); |
| EXPECT_TRUE(cb_conn_ref->active()); |
| |
| conn_ref = std::move(cb_conn_ref); |
| }; |
| |
| EXPECT_TRUE(conn_mgr()->Connect(peer->identifier(), success_cb)); |
| RunLoopUntilIdle(); |
| ASSERT_TRUE(conn_ref); |
| |
| auto ref_cb = [](auto status, auto conn_ref) { |
| EXPECT_FALSE(conn_ref); |
| EXPECT_FALSE(status); |
| EXPECT_EQ(HostError::kFailed, status.error()); |
| }; |
| |
| EXPECT_TRUE(conn_mgr()->Connect(peer->identifier(), ref_cb)); |
| |
| // This should invalidate the ref that was bound to |ref_cb|. |
| EXPECT_TRUE(conn_mgr()->Disconnect(peer->identifier())); |
| |
| RunLoopUntilIdle(); |
| } |
| |
| // This tests that a connection reference callback returns nullptr if a HCI |
| // Disconnection Complete event is received for the corresponding ACL link |
| // BEFORE the callback gets run. |
| TEST_F(GAP_LowEnergyConnectionManagerTest, DisconnectCompleteEventWhileRefPending) { |
| auto* peer = peer_cache()->NewPeer(kAddress0, true); |
| test_device()->AddPeer(std::make_unique<FakePeer>(kAddress0)); |
| |
| LowEnergyConnectionRefPtr conn_ref; |
| auto success_cb = [&conn_ref](auto status, auto cb_conn_ref) { |
| ASSERT_TRUE(cb_conn_ref); |
| ASSERT_TRUE(status); |
| EXPECT_TRUE(cb_conn_ref->active()); |
| |
| conn_ref = std::move(cb_conn_ref); |
| }; |
| |
| EXPECT_TRUE(conn_mgr()->Connect(peer->identifier(), success_cb)); |
| RunLoopUntilIdle(); |
| ASSERT_TRUE(conn_ref); |
| |
| // 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 status, auto conn_ref) { |
| ref_cb_count++; |
| EXPECT_FALSE(conn_ref); |
| EXPECT_FALSE(status); |
| EXPECT_EQ(HostError::kFailed, status.error()); |
| }; |
| |
| size_t disconn_cb_count = 0; |
| auto disconn_cb = [this, ref_cb, peer, &disconn_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 be |
| // invalidated before |ref_cb| gets called. |
| EXPECT_TRUE(conn_mgr()->Connect(peer->identifier(), ref_cb)); |
| }; |
| conn_mgr()->SetDisconnectCallbackForTesting(disconn_cb); |
| |
| test_device()->SendDisconnectionCompleteEvent(conn_ref->handle()); |
| |
| RunLoopUntilIdle(); |
| |
| EXPECT_EQ(1u, ref_cb_count); |
| EXPECT_EQ(1u, disconn_cb_count); |
| } |
| |
| TEST_F(GAP_LowEnergyConnectionManagerTest, RemovePeerFromPeerCacheDuringDisconnection) { |
| auto* peer = peer_cache()->NewPeer(kAddress0, true); |
| test_device()->AddPeer(std::make_unique<FakePeer>(kAddress0)); |
| |
| LowEnergyConnectionRefPtr conn_ref; |
| auto success_cb = [&conn_ref](auto status, auto cb_conn_ref) { |
| EXPECT_TRUE(status); |
| ASSERT_TRUE(cb_conn_ref); |
| EXPECT_TRUE(cb_conn_ref->active()); |
| |
| conn_ref = std::move(cb_conn_ref); |
| }; |
| |
| EXPECT_TRUE(conn_mgr()->Connect(peer->identifier(), success_cb)); |
| RunLoopUntilIdle(); |
| ASSERT_TRUE(conn_ref); |
| |
| // 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_ref->active()); |
| |
| EXPECT_TRUE(peer_cache()->RemoveDisconnectedPeer(id)); |
| |
| RunLoopUntilIdle(); |
| |
| EXPECT_FALSE(peer_cache()->FindById(id)); |
| EXPECT_FALSE(peer_cache()->FindByAddress(kAddress0)); |
| } |
| |
| // Listener receives remote initiated connection ref. |
| TEST_F(GAP_LowEnergyConnectionManagerTest, RegisterRemoteInitiatedLink) { |
| test_device()->AddPeer(std::make_unique<FakePeer>(kAddress0)); |
| |
| // First create a fake incoming connection. |
| test_device()->ConnectLowEnergy(kAddress0); |
| |
| RunLoopUntilIdle(); |
| |
| auto link = MoveLastRemoteInitiated(); |
| ASSERT_TRUE(link); |
| |
| LowEnergyConnectionRefPtr conn_ref; |
| std::optional<hci::Status> status; |
| conn_mgr()->RegisterRemoteInitiatedLink( |
| std::move(link), BondableMode::Bondable, |
| [&](hci::Status cb_status, LowEnergyConnectionRefPtr conn) { |
| status = cb_status; |
| conn_ref = std::move(conn); |
| }); |
| RunLoopUntilIdle(); |
| |
| EXPECT_TRUE(status.has_value()); |
| EXPECT_TRUE(status->is_success()); |
| ASSERT_TRUE(conn_ref); |
| EXPECT_TRUE(conn_ref->active()); |
| // A Peer should now exist in the cache. |
| auto* peer = peer_cache()->FindByAddress(kAddress0); |
| ASSERT_TRUE(peer); |
| EXPECT_EQ(peer->identifier(), conn_ref->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_ref = nullptr; |
| |
| RunLoopUntilIdle(); |
| EXPECT_TRUE(connected_peers().empty()); |
| } |
| |
| // Listener receives remote initiated connection ref for a known peer with the |
| // same BR/EDR address. |
| TEST_F(GAP_LowEnergyConnectionManagerTest, IncomingConnectionUpgradesKnownBrEdrPeerToDualMode) { |
| Peer* peer = peer_cache()->NewPeer(kAddrAlias0, 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)); |
| |
| // First create a fake incoming connection. |
| test_device()->ConnectLowEnergy(kAddress0); |
| |
| RunLoopUntilIdle(); |
| |
| auto link = MoveLastRemoteInitiated(); |
| ASSERT_TRUE(link); |
| |
| LowEnergyConnectionRefPtr conn_ref; |
| conn_mgr()->RegisterRemoteInitiatedLink( |
| std::move(link), BondableMode::Bondable, |
| [&conn_ref](hci::Status status, LowEnergyConnectionRefPtr conn) { |
| conn_ref = std::move(conn); |
| }); |
| RunLoopUntilIdle(); |
| ASSERT_TRUE(conn_ref); |
| |
| EXPECT_EQ(peer->identifier(), conn_ref->peer_identifier()); |
| EXPECT_EQ(TechnologyType::kDualMode, peer->technology()); |
| } |
| |
| // Successful connection to a peer whose address type is kBREDR. |
| // TODO(2761): 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(GAP_LowEnergyConnectionManagerTest, ConnectAndDisconnectDualModeDeviceWithBrEdrAddress) { |
| Peer* peer = peer_cache()->NewPeer(kAddrAlias0, 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)); |
| |
| // Initialize as error to verify that |callback| assigns success. |
| hci::Status status(HostError::kFailed); |
| LowEnergyConnectionRefPtr conn_ref; |
| auto callback = [&status, &conn_ref](auto cb_status, auto cb_conn_ref) { |
| ASSERT_TRUE(cb_conn_ref); |
| status = cb_status; |
| conn_ref = std::move(cb_conn_ref); |
| EXPECT_TRUE(conn_ref->active()); |
| }; |
| |
| EXPECT_TRUE(connected_peers().empty()); |
| EXPECT_TRUE(conn_mgr()->Connect(peer->identifier(), callback)); |
| EXPECT_EQ(Peer::ConnectionState::kInitializing, peer->le()->connection_state()); |
| |
| RunLoopUntilIdle(); |
| |
| EXPECT_TRUE(status); |
| EXPECT_EQ(1u, connected_peers().size()); |
| EXPECT_EQ(1u, connected_peers().count(kAddress0)); |
| |
| ASSERT_TRUE(conn_ref); |
| EXPECT_TRUE(conn_ref->active()); |
| EXPECT_EQ(peer->identifier(), conn_ref->peer_identifier()); |
| EXPECT_FALSE(peer->temporary()); |
| EXPECT_EQ(Peer::ConnectionState::kConnected, peer->le()->connection_state()); |
| |
| conn_ref = nullptr; |
| RunLoopUntilIdle(); |
| EXPECT_EQ(0u, connected_peers().size()); |
| } |
| |
| // Tests that the master accepts the connection parameters that are sent from |
| // a fake slave and eventually applies them to the link. |
| TEST_F(GAP_LowEnergyConnectionManagerTest, L2CAPLEConnectionParameterUpdate) { |
| // Set up a fake peer and a connection over which to process the L2CAP |
| // request. |
| test_device()->AddPeer(std::make_unique<FakePeer>(kAddress0)); |
| auto* peer = peer_cache()->NewPeer(kAddress0, true); |
| ASSERT_TRUE(peer); |
| |
| LowEnergyConnectionRefPtr conn_ref; |
| auto conn_cb = [&conn_ref](const auto& peer_id, auto cr) { conn_ref = std::move(cr); }; |
| ASSERT_TRUE(conn_mgr()->Connect(peer->identifier(), conn_cb)); |
| |
| RunLoopUntilIdle(); |
| ASSERT_TRUE(conn_ref); |
| |
| hci::LEPreferredConnectionParameters preferred( |
| hci::kLEConnectionIntervalMin, hci::kLEConnectionIntervalMax, hci::kLEConnectionLatencyMax, |
| hci::kLEConnectionSupervisionTimeoutMax); |
| |
| hci::LEConnectionParameters actual; |
| bool fake_peer_cb_called = false; |
| bool conn_params_cb_called = false; |
| |
| auto fake_peer_cb = [&actual, &fake_peer_cb_called](const auto& addr, const auto& params) { |
| fake_peer_cb_called = true; |
| actual = params; |
| }; |
| test_device()->set_le_connection_parameters_callback(fake_peer_cb); |
| |
| auto conn_params_cb = [&conn_params_cb_called, &conn_ref](const auto& peer) { |
| EXPECT_EQ(conn_ref->peer_identifier(), peer.identifier()); |
| conn_params_cb_called = true; |
| }; |
| conn_mgr()->SetConnectionParametersCallbackForTesting(conn_params_cb); |
| |
| fake_l2cap()->TriggerLEConnectionParameterUpdate(conn_ref->handle(), preferred); |
| |
| RunLoopUntilIdle(); |
| |
| EXPECT_TRUE(fake_peer_cb_called); |
| ASSERT_TRUE(conn_params_cb_called); |
| |
| ASSERT_TRUE(peer->le()); |
| EXPECT_EQ(preferred, *peer->le()->preferred_connection_parameters()); |
| EXPECT_EQ(actual, *peer->le()->connection_parameters()); |
| } |
| |
| TEST_F(GAP_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)); |
| auto* peer = peer_cache()->NewPeer(kAddress0, true); |
| ASSERT_TRUE(peer); |
| |
| fbl::RefPtr<l2cap::Channel> att_chan; |
| auto l2cap_chan_cb = [&att_chan](auto chan) { att_chan = chan; }; |
| fake_l2cap()->set_channel_callback(l2cap_chan_cb); |
| |
| LowEnergyConnectionRefPtr conn_ref; |
| auto conn_cb = [&conn_ref](const auto& peer_id, auto cr) { conn_ref = std::move(cr); }; |
| ASSERT_TRUE(conn_mgr()->Connect(peer->identifier(), conn_cb)); |
| |
| RunLoopUntilIdle(); |
| ASSERT_TRUE(conn_ref); |
| ASSERT_TRUE(att_chan); |
| ASSERT_EQ(1u, connected_peers().size()); |
| |
| // Signaling a link error through the channel should disconnect the link. |
| att_chan->SignalLinkError(); |
| |
| RunLoopUntilIdle(); |
| EXPECT_TRUE(connected_peers().empty()); |
| } |
| |
| TEST_F(GAP_LowEnergyConnectionManagerTest, PairUnconnectedPeer) { |
| auto* peer = peer_cache()->NewPeer(kAddress0, true); |
| EXPECT_TRUE(peer->temporary()); |
| ASSERT_EQ(peer_cache()->count(), 1u); |
| uint count_cb_called = 0; |
| auto cb = [&count_cb_called](sm::Status status) { |
| ASSERT_EQ(status.error(), bt::HostError::kNotFound); |
| count_cb_called++; |
| }; |
| conn_mgr()->Pair(peer->identifier(), sm::SecurityLevel::kEncrypted, sm::BondableMode::Bondable, |
| cb); |
| ASSERT_EQ(count_cb_called, 1u); |
| } |
| |
| TEST_F(GAP_LowEnergyConnectionManagerTest, PairBondable) { |
| // clang-format off |
| const auto kExpected = CreateStaticByteBuffer( |
| 0x01, // code: "Pairing Request" |
| 0x03, // IO cap.: NoInputNoOutput |
| 0x00, // OOB: not present |
| 0x01, // AuthReq: bonding, no MITM |
| 0x10, // encr. key size: 16 (default max) |
| 0x00, // initiator keys: none |
| 0x03 // responder keys: enc key and identity info |
| ); |
| |
| // clang-format on |
| auto* peer = peer_cache()->NewPeer(kAddress0, true); |
| EXPECT_TRUE(peer->temporary()); |
| // This is to capture the channel created during the Connection process |
| FakeChannel* fake_chan = nullptr; |
| fake_l2cap()->set_channel_callback( |
| [&fake_chan](fbl::RefPtr<FakeChannel> new_fake_chan) { fake_chan = new_fake_chan.get(); }); |
| |
| auto fake_peer = std::make_unique<FakePeer>(kAddress0); |
| test_device()->AddPeer(std::move(fake_peer)); |
| // Initialize as error to verify that |callback| assigns success. |
| hci::Status status(HostError::kFailed); |
| LowEnergyConnectionRefPtr conn_ref; |
| auto callback = [&status, &conn_ref](auto cb_status, auto cb_conn_ref) { |
| EXPECT_TRUE(cb_conn_ref); |
| status = cb_status; |
| conn_ref = std::move(cb_conn_ref); |
| EXPECT_TRUE(conn_ref->active()); |
| }; |
| |
| ASSERT_TRUE(conn_mgr()->Connect(peer->identifier(), callback)); |
| ASSERT_TRUE(peer->le()); |
| |
| RunLoopUntilIdle(); |
| |
| ASSERT_TRUE(status); |
| ASSERT_EQ(Peer::ConnectionState::kConnected, peer->le()->connection_state()); |
| |
| ASSERT_TRUE(fake_chan); |
| |
| bool cb_called = false; |
| // This test only checks that PairingState kicks off a pairing feature exchange correctly, as |
| // LowEnergyConnectionManager is only responsible for starting pairing, not for completing it. |
| auto expect_default_bytebuffer = [&cb_called, kExpected](ByteBufferPtr sent) { |
| ASSERT_TRUE(sent); |
| ASSERT_EQ(*sent, kExpected); |
| cb_called = true; |
| }; |
| fake_chan->SetSendCallback(expect_default_bytebuffer, dispatcher()); |
| |
| conn_mgr()->Pair(peer->identifier(), sm::SecurityLevel::kEncrypted, sm::BondableMode::Bondable, |
| [](sm::Status cb_status) {}); |
| RunLoopUntilIdle(); |
| ASSERT_TRUE(cb_called); |
| } |
| |
| TEST_F(GAP_LowEnergyConnectionManagerTest, PairNonBondable) { |
| // clang-format off |
| const auto kExpected = CreateStaticByteBuffer( |
| 0x01, // code: "Pairing Request" |
| 0x03, // IO cap.: NoInputNoOutput |
| 0x00, // OOB: not present |
| 0x00, // AuthReq: bonding, no MITM |
| 0x10, // encr. key size: 16 (default max) |
| 0x00, // initiator keys: none |
| 0x00 // responder keys: enc key and identity info |
| ); |
| |
| // clang-format on |
| auto* peer = peer_cache()->NewPeer(kAddress0, true); |
| EXPECT_TRUE(peer->temporary()); |
| // This is to capture the channel created during the Connection process |
| FakeChannel* fake_chan = nullptr; |
| fake_l2cap()->set_channel_callback( |
| [&fake_chan](fbl::RefPtr<FakeChannel> new_fake_chan) { fake_chan = new_fake_chan.get(); }); |
| |
| auto fake_peer = std::make_unique<FakePeer>(kAddress0); |
| test_device()->AddPeer(std::move(fake_peer)); |
| // Initialize as error to verify that |callback| assigns success. |
| hci::Status status(HostError::kFailed); |
| LowEnergyConnectionRefPtr conn_ref; |
| auto callback = [&status, &conn_ref](auto cb_status, auto cb_conn_ref) { |
| EXPECT_TRUE(cb_conn_ref); |
| status = cb_status; |
| conn_ref = std::move(cb_conn_ref); |
| EXPECT_TRUE(conn_ref->active()); |
| }; |
| |
| ASSERT_TRUE(conn_mgr()->Connect(peer->identifier(), callback)); |
| ASSERT_TRUE(peer->le()); |
| |
| RunLoopUntilIdle(); |
| |
| ASSERT_TRUE(status); |
| ASSERT_EQ(Peer::ConnectionState::kConnected, peer->le()->connection_state()); |
| |
| ASSERT_TRUE(fake_chan); |
| |
| bool cb_called = false; |
| // This test only checks that PairingState kicks off a pairing feature exchange correctly, as |
| // LowEnergyConnectionManager is only responsible for starting pairing, not for completing it. |
| auto expect_default_bytebuffer = [&cb_called, kExpected](ByteBufferPtr sent) { |
| ASSERT_TRUE(sent); |
| ASSERT_EQ(*sent, kExpected); |
| cb_called = true; |
| }; |
| fake_chan->SetSendCallback(expect_default_bytebuffer, dispatcher()); |
| |
| conn_mgr()->Pair(peer->identifier(), sm::SecurityLevel::kEncrypted, sm::BondableMode::NonBondable, |
| [](sm::Status cb_status) {}); |
| RunLoopUntilIdle(); |
| ASSERT_TRUE(cb_called); |
| } |
| |
| // Listener receives remote initiated connection ref. |
| TEST_F(GAP_LowEnergyConnectionManagerTest, PassBondableThroughRemoteInitiatedLink) { |
| test_device()->AddPeer(std::make_unique<FakePeer>(kAddress0)); |
| |
| // First create a fake incoming connection. |
| test_device()->ConnectLowEnergy(kAddress0); |
| |
| RunLoopUntilIdle(); |
| |
| auto link = MoveLastRemoteInitiated(); |
| ASSERT_TRUE(link); |
| |
| LowEnergyConnectionRefPtr conn_ref; |
| conn_mgr()->RegisterRemoteInitiatedLink( |
| std::move(link), BondableMode::Bondable, |
| [&conn_ref](hci::Status status, LowEnergyConnectionRefPtr conn) { |
| conn_ref = std::move(conn); |
| }); |
| RunLoopUntilIdle(); |
| |
| ASSERT_TRUE(conn_ref); |
| EXPECT_TRUE(conn_ref->active()); |
| EXPECT_EQ(conn_ref->bondable_mode(), BondableMode::Bondable); |
| } |
| |
| TEST_F(GAP_LowEnergyConnectionManagerTest, PassNonBondableThroughRemoteInitiatedLink) { |
| test_device()->AddPeer(std::make_unique<FakePeer>(kAddress0)); |
| |
| // First create a fake incoming connection. |
| test_device()->ConnectLowEnergy(kAddress0); |
| |
| RunLoopUntilIdle(); |
| |
| auto link = MoveLastRemoteInitiated(); |
| ASSERT_TRUE(link); |
| |
| LowEnergyConnectionRefPtr conn_ref; |
| conn_mgr()->RegisterRemoteInitiatedLink( |
| std::move(link), BondableMode::NonBondable, |
| [&conn_ref](hci::Status status, LowEnergyConnectionRefPtr conn) { |
| conn_ref = std::move(conn); |
| }); |
| RunLoopUntilIdle(); |
| |
| ASSERT_TRUE(conn_ref); |
| EXPECT_TRUE(conn_ref->active()); |
| EXPECT_EQ(conn_ref->bondable_mode(), BondableMode::NonBondable); |
| } |
| |
| // Successful connection to single peer |
| TEST_F(GAP_LowEnergyConnectionManagerTest, PassBondableThroughConnect) { |
| auto* peer = peer_cache()->NewPeer(kAddress0, true); |
| EXPECT_TRUE(peer->temporary()); |
| |
| auto fake_peer = std::make_unique<FakePeer>(kAddress0); |
| test_device()->AddPeer(std::move(fake_peer)); |
| |
| // Initialize as error to verify that |callback| assigns success. |
| hci::Status status(HostError::kFailed); |
| LowEnergyConnectionRefPtr conn_ref; |
| auto callback = [&status, &conn_ref](auto cb_status, auto cb_conn_ref) { |
| EXPECT_TRUE(cb_conn_ref); |
| status = cb_status; |
| conn_ref = std::move(cb_conn_ref); |
| EXPECT_TRUE(conn_ref->active()); |
| }; |
| |
| EXPECT_TRUE(connected_peers().empty()); |
| ASSERT_TRUE(conn_mgr()->Connect(peer->identifier(), callback, BondableMode::Bondable)); |
| |
| RunLoopUntilIdle(); |
| |
| EXPECT_TRUE(status); |
| ASSERT_TRUE(conn_ref); |
| EXPECT_EQ(conn_ref->bondable_mode(), BondableMode::Bondable); |
| } |
| |
| // Successful connection to single peer |
| TEST_F(GAP_LowEnergyConnectionManagerTest, PassNonBondableThroughConnect) { |
| auto* peer = peer_cache()->NewPeer(kAddress0, true); |
| EXPECT_TRUE(peer->temporary()); |
| |
| auto fake_peer = std::make_unique<FakePeer>(kAddress0); |
| test_device()->AddPeer(std::move(fake_peer)); |
| |
| // Initialize as error to verify that |callback| assigns success. |
| hci::Status status(HostError::kFailed); |
| LowEnergyConnectionRefPtr conn_ref; |
| auto callback = [&status, &conn_ref](auto cb_status, auto cb_conn_ref) { |
| EXPECT_TRUE(cb_conn_ref); |
| status = cb_status; |
| conn_ref = std::move(cb_conn_ref); |
| EXPECT_TRUE(conn_ref->active()); |
| }; |
| |
| EXPECT_TRUE(connected_peers().empty()); |
| ASSERT_TRUE(conn_mgr()->Connect(peer->identifier(), callback, BondableMode::NonBondable)); |
| |
| RunLoopUntilIdle(); |
| |
| EXPECT_TRUE(status); |
| ASSERT_TRUE(conn_ref); |
| EXPECT_EQ(conn_ref->bondable_mode(), BondableMode::NonBondable); |
| } |
| |
| // Tests that the connection manager cleans up its connection map correctly following a |
| // disconnection due to encryption failure. |
| TEST_F(GAP_LowEnergyConnectionManagerTest, ConnectionCleanUpFollowingEncryptionFailure) { |
| // Set up a connection. |
| auto* peer = peer_cache()->NewPeer(kAddress0, true); |
| EXPECT_TRUE(peer->temporary()); |
| |
| auto fake_peer = std::make_unique<FakePeer>(kAddress0); |
| test_device()->AddPeer(std::move(fake_peer)); |
| |
| LowEnergyConnectionRefPtr conn; |
| conn_mgr()->Connect( |
| peer->identifier(), [&](auto, auto c) { conn = std::move(c); }, BondableMode::Bondable); |
| RunLoopUntilIdle(); |
| ASSERT_TRUE(conn); |
| |
| hci::ConnectionHandle handle = conn->handle(); |
| bool ref_cleaned_up = false; |
| bool disconnected = false; |
| conn->set_closed_callback([&] { ref_cleaned_up = true; }); |
| conn_mgr()->SetDisconnectCallbackForTesting([&](hci::ConnectionHandle cb_handle) { |
| EXPECT_EQ(handle, cb_handle); |
| disconnected = true; |
| }); |
| |
| test_device()->SendEncryptionChangeEvent(handle, hci::StatusCode::kConnectionTerminatedMICFailure, |
| hci::EncryptionStatus::kOff); |
| test_device()->SendDisconnectionCompleteEvent(handle); |
| RunLoopUntilIdle(); |
| |
| EXPECT_TRUE(ref_cleaned_up); |
| EXPECT_TRUE(disconnected); |
| } |
| |
| TEST_F(GAP_LowEnergyConnectionManagerTest, SuccessfulInterrogationSetsPeerVersionAndFeatures) { |
| constexpr hci::LESupportedFeatures kLEFeatures{ |
| static_cast<uint64_t>(hci::LESupportedFeature::kConnectionParametersRequestProcedure)}; |
| |
| // Set up a connection. |
| auto* peer = peer_cache()->NewPeer(kAddress0, true); |
| ASSERT_TRUE(peer->le()); |
| |
| auto fake_peer = std::make_unique<FakePeer>(kAddress0); |
| fake_peer->set_le_features(kLEFeatures); |
| test_device()->AddPeer(std::move(fake_peer)); |
| |
| LowEnergyConnectionRefPtr conn; |
| conn_mgr()->Connect( |
| peer->identifier(), [&](auto, auto c) { conn = std::move(c); }, BondableMode::Bondable); |
| |
| EXPECT_FALSE(peer->version().has_value()); |
| EXPECT_FALSE(peer->le()->features().has_value()); |
| RunLoopUntilIdle(); |
| 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(GAP_LowEnergyConnectionManagerTest, ConnectInterrogationFailure) { |
| // Set up a connection. |
| auto* peer = peer_cache()->NewPeer(kAddress0, true); |
| ASSERT_TRUE(peer->le()); |
| |
| auto fake_peer = std::make_unique<FakePeer>(kAddress0); |
| test_device()->AddPeer(std::move(fake_peer)); |
| |
| LowEnergyConnectionRefPtr conn; |
| std::optional<hci::Status> status; |
| conn_mgr()->Connect( |
| peer->identifier(), |
| [&](auto cb_status, auto c) { |
| conn = std::move(c); |
| status = cb_status; |
| }, |
| BondableMode::Bondable); |
| 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); }); |
| |
| RunLoopUntilIdle(); |
| ASSERT_TRUE(status.has_value()); |
| EXPECT_FALSE(status->is_success()); |
| EXPECT_FALSE(conn); |
| EXPECT_FALSE(peer->connected()); |
| EXPECT_FALSE(peer->le()->connected()); |
| EXPECT_FALSE(peer->temporary()); |
| } |
| |
| TEST_F(GAP_LowEnergyConnectionManagerTest, RemoteInitiatedLinkInterrogationFailure) { |
| test_device()->AddPeer(std::make_unique<FakePeer>(kAddress0)); |
| |
| // First create a fake incoming connection. |
| test_device()->ConnectLowEnergy(kAddress0); |
| |
| RunLoopUntilIdle(); |
| |
| auto link = MoveLastRemoteInitiated(); |
| ASSERT_TRUE(link); |
| |
| LowEnergyConnectionRefPtr conn_ref; |
| std::optional<hci::Status> status; |
| conn_mgr()->RegisterRemoteInitiatedLink( |
| std::move(link), BondableMode::Bondable, |
| [&](hci::Status cb_status, LowEnergyConnectionRefPtr conn) { |
| status = cb_status; |
| conn_ref = std::move(conn); |
| }); |
| |
| // 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); }); |
| |
| RunLoopUntilIdle(); |
| |
| ASSERT_TRUE(status.has_value()); |
| EXPECT_FALSE(status->is_success()); |
| EXPECT_FALSE(conn_ref); |
| // 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_TRUE(peer->temporary()); |
| } |
| |
| TEST_F(GAP_LowEnergyConnectionManagerTest, L2capRequestConnParamUpdateAfterInterrogation) { |
| const hci::LEPreferredConnectionParameters kConnParams( |
| hci::defaults::kLEConnectionIntervalMin, hci::defaults::kLEConnectionIntervalMax, |
| /*max_latency=*/0, hci::defaults::kLESupervisionTimeout); |
| |
| // Connection Parameter Update procedure NOT supported. |
| constexpr hci::LESupportedFeatures kLEFeatures{0}; |
| auto peer = std::make_unique<FakePeer>(kAddress0); |
| peer->set_le_features(kLEFeatures); |
| test_device()->AddPeer(std::move(peer)); |
| |
| // First create a fake incoming connection as peripheral. |
| test_device()->ConnectLowEnergy(kAddress0, hci::ConnectionRole::kSlave); |
| |
| RunLoopUntilIdle(); |
| |
| auto link = MoveLastRemoteInitiated(); |
| ASSERT_TRUE(link); |
| |
| LowEnergyConnectionRefPtr conn_ref; |
| std::optional<hci::Status> status; |
| conn_mgr()->RegisterRemoteInitiatedLink( |
| std::move(link), BondableMode::Bondable, |
| [&](hci::Status cb_status, LowEnergyConnectionRefPtr conn) { |
| status = cb_status; |
| conn_ref = std::move(conn); |
| }); |
| |
| 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++; }); |
| |
| RunLoopUntilIdle(); |
| |
| ASSERT_TRUE(status.has_value()); |
| EXPECT_TRUE(status->is_success()); |
| ASSERT_TRUE(conn_ref); |
| EXPECT_TRUE(conn_ref->active()); |
| EXPECT_EQ(0u, l2cap_conn_param_update_count); |
| EXPECT_EQ(0u, hci_update_conn_param_count); |
| |
| RunLoopFor(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(GAP_LowEnergyConnectionManagerTest, PeripheralsRetryLLConnectionUpdateWithL2capRequest) { |
| auto peer0 = std::make_unique<FakePeer>(kAddress0); |
| auto peer1 = std::make_unique<FakePeer>(kAddress1); |
| |
| // Connection Parameter Update procedure supported by controller. |
| constexpr hci::LESupportedFeatures kLEFeatures{ |
| static_cast<uint64_t>(hci::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, hci::ConnectionRole::kSlave); |
| RunLoopUntilIdle(); |
| auto link0 = MoveLastRemoteInitiated(); |
| ASSERT_TRUE(link0); |
| |
| LowEnergyConnectionRefPtr conn_ref0; |
| std::optional<hci::Status> status0; |
| conn_mgr()->RegisterRemoteInitiatedLink( |
| std::move(link0), BondableMode::Bondable, |
| [&](hci::Status cb_status, LowEnergyConnectionRefPtr conn) { |
| status0 = cb_status; |
| conn_ref0 = std::move(conn); |
| }); |
| |
| test_device()->ConnectLowEnergy(kAddress1, hci::ConnectionRole::kSlave); |
| RunLoopUntilIdle(); |
| auto link1 = MoveLastRemoteInitiated(); |
| ASSERT_TRUE(link1); |
| |
| LowEnergyConnectionRefPtr conn_ref1; |
| std::optional<hci::Status> status1; |
| conn_mgr()->RegisterRemoteInitiatedLink( |
| std::move(link1), BondableMode::Bondable, |
| [&](hci::Status cb_status, LowEnergyConnectionRefPtr conn) { |
| status1 = cb_status; |
| conn_ref1 = std::move(conn); |
| }); |
| |
| 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) { |
| switch (l2cap_conn_param_update_count) { |
| case 0: |
| EXPECT_EQ(handle, conn_ref0->handle()); |
| break; |
| case 1: |
| EXPECT_EQ(handle, conn_ref1->handle()); |
| break; |
| default: |
| ADD_FAILURE(); |
| } |
| |
| // connection update commands should be sent before l2cap requests |
| EXPECT_EQ(2u, hci_update_conn_param_count); |
| |
| l2cap_conn_param_update_count++; |
| return true; |
| }); |
| |
| test_device()->set_le_connection_parameters_callback([&](auto address, auto params) { |
| switch (hci_update_conn_param_count) { |
| case 0: |
| EXPECT_EQ(address, kAddress0); |
| break; |
| case 1: |
| EXPECT_EQ(address, kAddress1); |
| break; |
| default: |
| ADD_FAILURE(); |
| } |
| |
| // l2cap requests should not be sent until after failed HCI connection update commands |
| EXPECT_EQ(0u, l2cap_conn_param_update_count); |
| |
| hci_update_conn_param_count++; |
| }); |
| |
| RunLoopFor(kLEConnectionPausePeripheral); |
| ASSERT_TRUE(status0.has_value()); |
| EXPECT_TRUE(status0->is_success()); |
| ASSERT_TRUE(conn_ref0); |
| EXPECT_TRUE(conn_ref0->active()); |
| |
| ASSERT_TRUE(status1.has_value()); |
| EXPECT_TRUE(status1->is_success()); |
| ASSERT_TRUE(conn_ref1); |
| EXPECT_TRUE(conn_ref1->active()); |
| |
| EXPECT_EQ(2u, hci_update_conn_param_count); |
| EXPECT_EQ(2u, l2cap_conn_param_update_count); |
| |
| // l2cap requests should not be sent on subsequent events |
| test_device()->SendLEConnectionUpdateCompleteSubevent(conn_ref1->handle(), |
| hci::LEConnectionParameters(), |
| hci::StatusCode::kUnsupportedRemoteFeature); |
| RunLoopUntilIdle(); |
| EXPECT_EQ(2u, l2cap_conn_param_update_count); |
| } |
| |
| // 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(GAP_LowEnergyConnectionManagerTest, |
| PeripheralSendsL2capConnParamReqAfterConnUpdateCommandStatusUnsupportedRemoteFeature) { |
| auto peer = std::make_unique<FakePeer>(kAddress0); |
| |
| // Connection Parameter Update procedure supported by controller. |
| constexpr hci::LESupportedFeatures kLEFeatures{ |
| static_cast<uint64_t>(hci::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, hci::ConnectionRole::kSlave); |
| RunLoopUntilIdle(); |
| |
| auto link = MoveLastRemoteInitiated(); |
| ASSERT_TRUE(link); |
| |
| LowEnergyConnectionRefPtr conn_ref; |
| std::optional<hci::Status> status; |
| conn_mgr()->RegisterRemoteInitiatedLink( |
| std::move(link), BondableMode::Bondable, |
| [&](hci::Status cb_status, LowEnergyConnectionRefPtr conn) { |
| status = cb_status; |
| conn_ref = std::move(conn); |
| }); |
| |
| 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::kLEConnectionUpdate, |
| hci::StatusCode::kUnsupportedRemoteFeature); |
| |
| RunLoopFor(kLEConnectionPausePeripheral); |
| ASSERT_TRUE(status.has_value()); |
| EXPECT_TRUE(status->is_success()); |
| ASSERT_TRUE(conn_ref); |
| EXPECT_TRUE(conn_ref->active()); |
| EXPECT_EQ(0u, hci_update_conn_param_count); |
| EXPECT_EQ(1u, l2cap_conn_param_update_count); |
| |
| test_device()->ClearDefaultCommandStatus(hci::kLEConnectionUpdate); |
| |
| // l2cap request should not be called on subsequent events |
| test_device()->SendLEConnectionUpdateCompleteSubevent(conn_ref->handle(), |
| hci::LEConnectionParameters(), |
| hci::StatusCode::kUnsupportedRemoteFeature); |
| |
| RunLoopUntilIdle(); |
| 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(GAP_LowEnergyConnectionManagerTest, |
| PeripheralDoesNotSendL2capConnParamReqAfterConnUpdateCommandStatusError) { |
| auto peer = std::make_unique<FakePeer>(kAddress0); |
| |
| // Connection Parameter Update procedure supported by controller. |
| constexpr hci::LESupportedFeatures kLEFeatures{ |
| static_cast<uint64_t>(hci::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, hci::ConnectionRole::kSlave); |
| RunLoopUntilIdle(); |
| |
| auto link = MoveLastRemoteInitiated(); |
| ASSERT_TRUE(link); |
| |
| LowEnergyConnectionRefPtr conn_ref; |
| std::optional<hci::Status> status; |
| conn_mgr()->RegisterRemoteInitiatedLink( |
| std::move(link), BondableMode::Bondable, |
| [&](hci::Status cb_status, LowEnergyConnectionRefPtr conn) { |
| status = cb_status; |
| conn_ref = std::move(conn); |
| }); |
| |
| 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::kLEConnectionUpdate, |
| hci::StatusCode::kUnspecifiedError); |
| |
| RunLoopFor(kLEConnectionPausePeripheral); |
| ASSERT_TRUE(status.has_value()); |
| EXPECT_TRUE(status->is_success()); |
| ASSERT_TRUE(conn_ref); |
| EXPECT_TRUE(conn_ref->active()); |
| EXPECT_EQ(0u, hci_update_conn_param_count); |
| EXPECT_EQ(0u, l2cap_conn_param_update_count); |
| |
| test_device()->ClearDefaultCommandStatus(hci::kLEConnectionUpdate); |
| |
| // l2cap request should not be called on subsequent events |
| test_device()->SendLEConnectionUpdateCompleteSubevent(conn_ref->handle(), |
| hci::LEConnectionParameters(), |
| hci::StatusCode::kUnsupportedRemoteFeature); |
| |
| RunLoopUntilIdle(); |
| EXPECT_EQ(0u, l2cap_conn_param_update_count); |
| } |
| |
| TEST_F(GAP_LowEnergyConnectionManagerTest, HciUpdateConnParamsAfterInterrogation) { |
| constexpr hci::LESupportedFeatures kLEFeatures{ |
| static_cast<uint64_t>(hci::LESupportedFeature::kConnectionParametersRequestProcedure)}; |
| |
| auto peer = std::make_unique<FakePeer>(kAddress0); |
| peer->set_le_features(kLEFeatures); |
| test_device()->AddPeer(std::move(peer)); |
| |
| // First create a fake incoming connection. |
| test_device()->ConnectLowEnergy(kAddress0, hci::ConnectionRole::kSlave); |
| |
| RunLoopUntilIdle(); |
| |
| auto link = MoveLastRemoteInitiated(); |
| ASSERT_TRUE(link); |
| |
| LowEnergyConnectionRefPtr conn_ref; |
| std::optional<hci::Status> status; |
| conn_mgr()->RegisterRemoteInitiatedLink( |
| std::move(link), BondableMode::Bondable, |
| [&](hci::Status cb_status, LowEnergyConnectionRefPtr conn) { |
| status = cb_status; |
| conn_ref = std::move(conn); |
| }); |
| |
| 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::LEConnectionParameters& params) { |
| // FakeController will pick an interval between min and max interval. |
| EXPECT_TRUE(params.interval() >= hci::defaults::kLEConnectionIntervalMin && |
| params.interval() <= hci::defaults::kLEConnectionIntervalMax); |
| EXPECT_EQ(0u, params.latency()); |
| EXPECT_EQ(hci::defaults::kLESupervisionTimeout, params.supervision_timeout()); |
| hci_update_conn_param_count++; |
| }); |
| |
| RunLoopUntilIdle(); |
| |
| ASSERT_TRUE(status.has_value()); |
| EXPECT_TRUE(status->is_success()); |
| ASSERT_TRUE(conn_ref); |
| EXPECT_TRUE(conn_ref->active()); |
| EXPECT_EQ(0u, l2cap_conn_param_update_count); |
| EXPECT_EQ(0u, hci_update_conn_param_count); |
| |
| RunLoopFor(kLEConnectionPausePeripheral); |
| EXPECT_EQ(0u, l2cap_conn_param_update_count); |
| EXPECT_EQ(1u, hci_update_conn_param_count); |
| } |
| |
| TEST_F(GAP_LowEnergyConnectionManagerTest, CentralUpdatesConnectionParametersAfterInitialization) { |
| // Set up a connection. |
| auto* peer = peer_cache()->NewPeer(kAddress0, true); |
| ASSERT_TRUE(peer->le()); |
| |
| test_device()->AddPeer(std::make_unique<FakePeer>(kAddress0)); |
| |
| size_t hci_update_conn_param_count = 0; |
| test_device()->set_le_connection_parameters_callback( |
| [&](auto address, const hci::LEConnectionParameters& params) { |
| // FakeController will pick an interval between min and max interval. |
| EXPECT_TRUE(params.interval() >= hci::defaults::kLEConnectionIntervalMin && |
| params.interval() <= hci::defaults::kLEConnectionIntervalMax); |
| EXPECT_EQ(0u, params.latency()); |
| EXPECT_EQ(hci::defaults::kLESupervisionTimeout, params.supervision_timeout()); |
| hci_update_conn_param_count++; |
| }); |
| |
| LowEnergyConnectionRefPtr conn; |
| conn_mgr()->Connect( |
| peer->identifier(), [&](auto, auto c) { conn = std::move(c); }, BondableMode::Bondable); |
| |
| RunLoopUntilIdle(); |
| EXPECT_EQ(0u, hci_update_conn_param_count); |
| |
| RunLoopFor(kLEConnectionPauseCentral); |
| EXPECT_EQ(1u, hci_update_conn_param_count); |
| EXPECT_TRUE(conn); |
| } |
| |
| // Tests for assertions that enforce invariants. |
| class GAP_LowEnergyConnectionManagerDeathTest : public LowEnergyConnectionManagerTest {}; |
| |
| // Tests that a disconnection event that occurs after a peer gets removed is handled gracefully. |
| TEST_F(GAP_LowEnergyConnectionManagerDeathTest, DisconnectAfterPeerRemovalAsserts) { |
| // Set up a connection. |
| auto* peer = peer_cache()->NewPeer(kAddress0, true); |
| EXPECT_TRUE(peer->temporary()); |
| |
| auto fake_peer = std::make_unique<FakePeer>(kAddress0); |
| test_device()->AddPeer(std::move(fake_peer)); |
| |
| LowEnergyConnectionRefPtr conn; |
| conn_mgr()->Connect( |
| peer->identifier(), [&](auto, auto c) { conn = std::move(c); }, BondableMode::Bondable); |
| RunLoopUntilIdle(); |
| ASSERT_TRUE(conn); |
| |
| hci::ConnectionHandle handle = conn->handle(); |
| |
| EXPECT_DEATH_IF_SUPPORTED( |
| { |
| // Remove the peer without removing it from the cache. Normally this is not recommended as |
| // implied by the name of the function but it is possible for this invariant to be broken |
| // due to programmer error. The connection manager should assert this invariant. |
| peer->MutLe().SetConnectionState(Peer::ConnectionState::kNotConnected); |
| __UNUSED auto _ = peer_cache()->RemoveDisconnectedPeer(peer->identifier()); |
| |
| test_device()->SendDisconnectionCompleteEvent(handle); |
| RunLoopUntilIdle(); |
| }, |
| ".*"); |
| } |
| |
| // 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_ref0(). |
| 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, true); |
| EXPECT_TRUE(peer0_->temporary()); |
| test_device()->AddPeer(std::make_unique<FakePeer>(kPeerAddr0)); |
| |
| peer1_ = peer_cache()->NewPeer(kPeerAddr1, true); |
| EXPECT_TRUE(peer1_->temporary()); |
| test_device()->AddPeer(std::make_unique<FakePeer>(kPeerAddr1)); |
| |
| // Connect |peer0| |
| hci::Status status0(HostError::kFailed); |
| conn_ref0_.reset(); |
| auto callback0 = [this, &status0](auto cb_status, auto cb_conn_ref) { |
| EXPECT_TRUE(cb_conn_ref); |
| status0 = cb_status; |
| conn_ref0_ = std::move(cb_conn_ref); |
| EXPECT_TRUE(conn_ref0_->active()); |
| }; |
| EXPECT_TRUE(conn_mgr()->Connect(peer0_->identifier(), callback0)); |
| RunLoopUntilIdle(); |
| |
| // Connect |peer1| |
| hci::Status status1(HostError::kFailed); |
| conn_ref1_.reset(); |
| auto callback1 = [this, &status1](auto cb_status, auto cb_conn_ref) { |
| EXPECT_TRUE(cb_conn_ref); |
| status1 = cb_status; |
| conn_ref1_ = std::move(cb_conn_ref); |
| EXPECT_TRUE(conn_ref1_->active()); |
| }; |
| EXPECT_TRUE(conn_mgr()->Connect(peer1_->identifier(), callback1)); |
| RunLoopUntilIdle(); |
| |
| 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); |
| |
| // Fill controller buffer by sending |kMaxNumPackets| packets to peer0. |
| for (size_t i = 0; i < kLEMaxNumPackets; i++) { |
| ASSERT_TRUE(acl_data_channel()->SendPacket( |
| hci::ACLDataPacket::New(conn_ref0_->handle(), |
| hci::ACLPacketBoundaryFlag::kFirstNonFlushable, |
| hci::ACLBroadcastFlag::kPointToPoint, 1), |
| l2cap::kInvalidChannelId)); |
| } |
| |
| // Queue packet for |peer1|. |
| ASSERT_TRUE(acl_data_channel()->SendPacket( |
| hci::ACLDataPacket::New(conn_ref1_->handle(), |
| hci::ACLPacketBoundaryFlag::kFirstNonFlushable, |
| hci::ACLBroadcastFlag::kPointToPoint, 1), |
| l2cap::kInvalidChannelId)); |
| |
| RunLoopUntilIdle(); |
| |
| // Packet for |peer1| should not have been sent because controller buffer is full. |
| EXPECT_EQ(kLEMaxNumPackets, packet_count_); |
| |
| handle0_ = conn_ref0_->handle(); |
| } |
| |
| void TearDown() override { |
| RunLoopUntilIdle(); |
| |
| // Packet for |peer1| should not have been sent before Disconnection Complete event. |
| EXPECT_EQ(kLEMaxNumPackets, packet_count_); |
| |
| // This makes FakeController send us the HCI Disconnection Complete event. |
| test_device()->SendDisconnectionCompleteEvent(handle0_); |
| RunLoopUntilIdle(); |
| |
| // |peer0|'s link should have been unregistered. |
| ASSERT_FALSE(acl_data_channel()->SendPacket( |
| hci::ACLDataPacket::New(handle0_, hci::ACLPacketBoundaryFlag::kFirstNonFlushable, |
| hci::ACLBroadcastFlag::kPointToPoint, 1), |
| l2cap::kInvalidChannelId)); |
| |
| // Packet for |peer1| should have been sent. |
| EXPECT_EQ(kLEMaxNumPackets + 1, packet_count_); |
| |
| peer0_ = nullptr; |
| peer1_ = nullptr; |
| conn_ref0_.reset(); |
| conn_ref1_.reset(); |
| |
| LowEnergyConnectionManagerTest::TearDown(); |
| } |
| |
| Peer* peer0() { return peer0_; } |
| LowEnergyConnectionRefPtr& conn_ref0() { return conn_ref0_; } |
| |
| private: |
| size_t packet_count_; |
| Peer* peer0_; |
| Peer* peer1_; |
| hci::ConnectionHandle handle0_; |
| LowEnergyConnectionRefPtr conn_ref0_; |
| LowEnergyConnectionRefPtr conn_ref1_; |
| }; |
| |
| using GAP_LowEnergyConnectionManagerPendingPacketsTest = PendingPacketsTest; |
| |
| TEST_F(GAP_LowEnergyConnectionManagerPendingPacketsTest, Disconnect) { |
| // Send HCI Disconnect to controller. |
| EXPECT_TRUE(conn_mgr()->Disconnect(peer0()->identifier())); |
| } |
| |
| TEST_F(GAP_LowEnergyConnectionManagerPendingPacketsTest, ReleaseRef) { |
| // Releasing ref should send HCI Disconnect to controller. |
| conn_ref0().reset(); |
| } |
| |
| } // namespace |
| } // namespace gap |
| } // namespace bt |