blob: 81e256015c3c9232d01e3d90206c03b74138bf71 [file] [log] [blame]
// 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