blob: d206433e6386933e116605a8ee2e4aef763ed63e [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 "garnet/drivers/bluetooth/lib/gap/low_energy_connection_manager.h"
#include <memory>
#include <vector>
#include <zircon/assert.h>
#include "garnet/drivers/bluetooth/lib/data/fake_domain.h"
#include "garnet/drivers/bluetooth/lib/gap/remote_device.h"
#include "garnet/drivers/bluetooth/lib/gap/remote_device_cache.h"
#include "garnet/drivers/bluetooth/lib/gatt/fake_layer.h"
#include "garnet/drivers/bluetooth/lib/hci/hci_constants.h"
#include "garnet/drivers/bluetooth/lib/hci/low_energy_connector.h"
#include "garnet/drivers/bluetooth/lib/l2cap/fake_channel.h"
#include "garnet/drivers/bluetooth/lib/testing/fake_controller.h"
#include "garnet/drivers/bluetooth/lib/testing/fake_controller_test.h"
#include "garnet/drivers/bluetooth/lib/testing/fake_device.h"
#include "lib/fxl/macros.h"
namespace btlib {
namespace gap {
namespace {
using ::btlib::testing::FakeController;
using ::btlib::testing::FakeDevice;
using common::DeviceAddress;
using TestingBase = ::btlib::testing::FakeControllerTest<FakeController>;
const DeviceAddress kAddress0(DeviceAddress::Type::kLEPublic,
"00:00:00:00:00:01");
const DeviceAddress kAddrAlias0(DeviceAddress::Type::kBREDR, kAddress0.value());
const DeviceAddress kAddress1(DeviceAddress::Type::kLEPublic,
"00:00:00:00:00:02");
const DeviceAddress kAddress2(DeviceAddress::Type::kBREDR, "00:00:00:00:00:03");
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(),
hci::DataBufferInfo(hci::kMaxACLPayloadSize, 10));
FakeController::Settings settings;
settings.ApplyLegacyLEConfig();
test_device()->set_settings(settings);
dev_cache_ = std::make_unique<RemoteDeviceCache>();
l2cap_ = data::testing::FakeDomain::Create();
l2cap_->Initialize();
// TODO(armansito): Pass a fake connector here.
connector_ = std::make_unique<hci::LowEnergyConnector>(
transport(), kAddress0, dispatcher(),
fit::bind_member(
this, &LowEnergyConnectionManagerTest::OnIncomingConnection));
conn_mgr_ = std::make_unique<LowEnergyConnectionManager>(
transport(), connector_.get(), dev_cache_.get(), l2cap_,
gatt::testing::FakeLayer::Create());
test_device()->SetConnectionStateCallback(
fit::bind_member(
this, &LowEnergyConnectionManagerTest::OnConnectionStateChanged),
dispatcher());
StartTestDevice();
}
void TearDown() override {
if (conn_mgr_)
conn_mgr_ = nullptr;
dev_cache_ = nullptr;
l2cap_->ShutDown();
l2cap_ = nullptr;
TestingBase::TearDown();
}
// Deletes |conn_mgr_|.
void DeleteConnMgr() { conn_mgr_ = nullptr; }
RemoteDeviceCache* dev_cache() const { return dev_cache_.get(); }
LowEnergyConnectionManager* conn_mgr() const { return conn_mgr_.get(); }
data::testing::FakeDomain* fake_l2cap() const { return l2cap_.get(); }
// Addresses of currently connected fake devices.
using DeviceList = std::unordered_set<common::DeviceAddress>;
const DeviceList& connected_devices() const { return connected_devices_; }
// Addresses of devices with a canceled connection attempt.
const DeviceList& canceled_devices() const { return canceled_devices_; }
hci::ConnectionPtr MoveLastRemoteInitiated() {
return std::move(last_remote_initiated_);
}
private:
// Called by |connector_| when a new remote initiated connection is received.
void OnIncomingConnection(hci::ConnectionHandle handle,
hci::Connection::Role role,
const common::DeviceAddress& peer_address,
const hci::LEConnectionParameters& conn_params) {
common::DeviceAddress local_address(common::DeviceAddress::Type::kLEPublic,
"03:02:01:01:02:03");
// Create a production connection object that can interact with the fake
// controller.
last_remote_initiated_ = hci::Connection::CreateLE(
handle, role, local_address, peer_address, conn_params, transport());
}
// Called by FakeController on connection events.
void OnConnectionStateChanged(const common::DeviceAddress& address,
bool connected,
bool canceled) {
bt_log(SPEW, "gap-test",
"OnConnectionStateChanged: %s connected: %s, canceled %s",
address.ToString().c_str(), connected ? "true" : "false",
canceled ? "true" : "false");
if (canceled) {
canceled_devices_.insert(address);
} else if (connected) {
ZX_DEBUG_ASSERT(connected_devices_.find(address) ==
connected_devices_.end());
connected_devices_.insert(address);
} else {
ZX_DEBUG_ASSERT(connected_devices_.find(address) !=
connected_devices_.end());
connected_devices_.erase(address);
}
}
fbl::RefPtr<data::testing::FakeDomain> l2cap_;
std::unique_ptr<RemoteDeviceCache> dev_cache_;
std::unique_ptr<hci::LowEnergyConnector> connector_;
std::unique_ptr<LowEnergyConnectionManager> conn_mgr_;
// The most recent remote-initiated connection reported by |connector_|.
hci::ConnectionPtr last_remote_initiated_;
DeviceList connected_devices_;
DeviceList canceled_devices_;
FXL_DISALLOW_COPY_AND_ASSIGN(LowEnergyConnectionManagerTest);
};
using GAP_LowEnergyConnectionManagerTest = LowEnergyConnectionManagerTest;
TEST_F(GAP_LowEnergyConnectionManagerTest, ConnectUnknownDevice) {
EXPECT_FALSE(conn_mgr()->Connect("nope", {}));
}
TEST_F(GAP_LowEnergyConnectionManagerTest, ConnectClassicDevice) {
auto* dev = dev_cache()->NewDevice(kAddress2, true);
EXPECT_FALSE(conn_mgr()->Connect(dev->identifier(), {}));
}
TEST_F(GAP_LowEnergyConnectionManagerTest, ConnectNonConnectableDevice) {
auto* dev = dev_cache()->NewDevice(kAddress0, false);
EXPECT_FALSE(conn_mgr()->Connect(dev->identifier(), {}));
}
// An error is received via the HCI Command cb_status event
TEST_F(GAP_LowEnergyConnectionManagerTest, ConnectSingleDeviceErrorStatus) {
auto* dev = dev_cache()->NewDevice(kAddress0, true);
auto fake_dev = std::make_unique<FakeDevice>(kAddress0);
fake_dev->set_connect_status(
hci::StatusCode::kConnectionFailedToBeEstablished);
test_device()->AddDevice(std::move(fake_dev));
ASSERT_TRUE(dev->le());
EXPECT_EQ(RemoteDevice::ConnectionState::kNotConnected,
dev->le()->connection_state());
hci::Status status;
auto callback = [&status](auto cb_status, auto conn_ref) {
EXPECT_FALSE(conn_ref);
status = cb_status;
};
EXPECT_TRUE(conn_mgr()->Connect(dev->identifier(), callback));
EXPECT_EQ(RemoteDevice::ConnectionState::kInitializing,
dev->le()->connection_state());
RunLoopUntilIdle();
EXPECT_TRUE(status.is_protocol_error());
EXPECT_EQ(hci::StatusCode::kConnectionFailedToBeEstablished,
status.protocol_error());
EXPECT_EQ(RemoteDevice::ConnectionState::kNotConnected,
dev->le()->connection_state());
}
// LE Connection Complete event reports error
TEST_F(GAP_LowEnergyConnectionManagerTest, ConnectSingleDeviceFailure) {
auto* dev = dev_cache()->NewDevice(kAddress0, true);
auto fake_dev = std::make_unique<FakeDevice>(kAddress0);
fake_dev->set_connect_response(
hci::StatusCode::kConnectionFailedToBeEstablished);
test_device()->AddDevice(std::move(fake_dev));
hci::Status status;
auto callback = [&status](auto cb_status, auto conn_ref) {
EXPECT_FALSE(conn_ref);
status = cb_status;
};
EXPECT_TRUE(conn_mgr()->Connect(dev->identifier(), callback));
ASSERT_TRUE(dev->le());
EXPECT_EQ(RemoteDevice::ConnectionState::kInitializing,
dev->le()->connection_state());
RunLoopUntilIdle();
EXPECT_TRUE(status.is_protocol_error());
EXPECT_EQ(hci::StatusCode::kConnectionFailedToBeEstablished,
status.protocol_error());
EXPECT_EQ(RemoteDevice::ConnectionState::kNotConnected,
dev->le()->connection_state());
}
TEST_F(GAP_LowEnergyConnectionManagerTest, ConnectSingleDeviceTimeout) {
constexpr int64_t kTestRequestTimeoutMs = 20000;
auto* dev = dev_cache()->NewDevice(kAddress0, true);
// We add no fake devices to cause the request to time out.
hci::Status status;
auto callback = [&status](auto cb_status, auto conn_ref) {
EXPECT_FALSE(conn_ref);
status = cb_status;
};
conn_mgr()->set_request_timeout_for_testing(kTestRequestTimeoutMs);
EXPECT_TRUE(conn_mgr()->Connect(dev->identifier(), callback));
ASSERT_TRUE(dev->le());
EXPECT_EQ(RemoteDevice::ConnectionState::kInitializing,
dev->le()->connection_state());
RunLoopFor(zx::msec(kTestRequestTimeoutMs));
EXPECT_FALSE(status);
EXPECT_EQ(common::HostError::kTimedOut, status.error()) << status.ToString();
EXPECT_EQ(RemoteDevice::ConnectionState::kNotConnected,
dev->le()->connection_state());
}
// Successful connection to single device
TEST_F(GAP_LowEnergyConnectionManagerTest, ConnectSingleDevice) {
auto* dev = dev_cache()->NewDevice(kAddress0, true);
EXPECT_TRUE(dev->temporary());
auto fake_dev = std::make_unique<FakeDevice>(kAddress0);
test_device()->AddDevice(std::move(fake_dev));
// Initialize as error to verify that |callback| assigns success.
hci::Status status(common::HostError::kFailed);
LowEnergyConnectionRefPtr conn_ref;
auto callback = [&status, &conn_ref](auto cb_status, auto cb_conn_ref) {
EXPECT_TRUE(cb_conn_ref);
status = cb_status;
conn_ref = std::move(cb_conn_ref);
EXPECT_TRUE(conn_ref->active());
};
EXPECT_TRUE(connected_devices().empty());
EXPECT_TRUE(conn_mgr()->Connect(dev->identifier(), callback));
ASSERT_TRUE(dev->le());
EXPECT_EQ(RemoteDevice::ConnectionState::kInitializing,
dev->le()->connection_state());
RunLoopUntilIdle();
EXPECT_TRUE(status);
EXPECT_EQ(1u, connected_devices().size());
EXPECT_EQ(1u, connected_devices().count(kAddress0));
ASSERT_TRUE(conn_ref);
EXPECT_TRUE(conn_ref->active());
EXPECT_EQ(dev->identifier(), conn_ref->device_identifier());
EXPECT_FALSE(dev->temporary());
EXPECT_EQ(RemoteDevice::ConnectionState::kConnected,
dev->le()->connection_state());
}
struct TestObject final : fbl::RefCounted<TestObject> {
explicit TestObject(bool* d) : deleted(d) {
ZX_DEBUG_ASSERT(deleted);
*deleted = false;
}
~TestObject() { *deleted = true; }
bool* deleted;
};
TEST_F(GAP_LowEnergyConnectionManagerTest, DeleteRefInClosedCallback) {
auto* dev = dev_cache()->NewDevice(kAddress0, true);
test_device()->AddDevice(std::make_unique<FakeDevice>(kAddress0));
bool deleted = false;
auto obj = fbl::AdoptRef(new TestObject(&deleted));
LowEnergyConnectionRefPtr conn_ref;
int closed_count = 0;
auto closed_cb = [&, obj = std::move(obj)] {
closed_count++;
conn_ref = nullptr;
// The object should remain alive for the duration of this callback.
EXPECT_FALSE(deleted);
};
auto success_cb = [&conn_ref, &closed_cb, this](auto status,
auto cb_conn_ref) {
EXPECT_TRUE(status);
ASSERT_TRUE(cb_conn_ref);
conn_ref = std::move(cb_conn_ref);
conn_ref->set_closed_callback(std::move(closed_cb));
};
ASSERT_TRUE(conn_mgr()->Connect(dev->identifier(), success_cb));
RunLoopUntilIdle();
ASSERT_TRUE(conn_ref);
ASSERT_TRUE(conn_ref->active());
// This will trigger the closed callback.
EXPECT_TRUE(conn_mgr()->Disconnect(dev->identifier()));
RunLoopUntilIdle();
EXPECT_EQ(1, closed_count);
EXPECT_TRUE(connected_devices().empty());
EXPECT_FALSE(conn_ref);
// The object should be deleted.
EXPECT_TRUE(deleted);
}
TEST_F(GAP_LowEnergyConnectionManagerTest, ReleaseRef) {
auto* dev = dev_cache()->NewDevice(kAddress0, true);
auto fake_dev = std::make_unique<FakeDevice>(kAddress0);
test_device()->AddDevice(std::move(fake_dev));
// Initialize as error to verify that |callback| assigns success.
hci::Status status(common::HostError::kFailed);
LowEnergyConnectionRefPtr conn_ref;
auto callback = [&status, &conn_ref](auto cb_status, auto cb_conn_ref) {
EXPECT_TRUE(cb_conn_ref);
status = cb_status;
conn_ref = std::move(cb_conn_ref);
EXPECT_TRUE(conn_ref->active());
};
EXPECT_TRUE(connected_devices().empty());
EXPECT_TRUE(conn_mgr()->Connect(dev->identifier(), callback));
RunLoopUntilIdle();
EXPECT_TRUE(status);
EXPECT_EQ(1u, connected_devices().size());
ASSERT_TRUE(dev->le());
EXPECT_EQ(RemoteDevice::ConnectionState::kConnected,
dev->le()->connection_state());
ASSERT_TRUE(conn_ref);
conn_ref = nullptr;
RunLoopUntilIdle();
EXPECT_TRUE(connected_devices().empty());
EXPECT_EQ(RemoteDevice::ConnectionState::kNotConnected,
dev->le()->connection_state());
}
TEST_F(GAP_LowEnergyConnectionManagerTest,
OneDeviceTwoPendingRequestsBothFail) {
constexpr int kRequestCount = 2;
auto* dev = dev_cache()->NewDevice(kAddress0, true);
auto fake_dev = std::make_unique<FakeDevice>(kAddress0);
fake_dev->set_connect_response(
hci::StatusCode::kConnectionFailedToBeEstablished);
test_device()->AddDevice(std::move(fake_dev));
hci::Status statuses[kRequestCount];
int cb_count = 0;
auto callback = [&statuses, &cb_count](auto cb_status, auto conn_ref) {
EXPECT_FALSE(conn_ref);
statuses[cb_count++] = cb_status;
};
for (int i = 0; i < kRequestCount; ++i) {
EXPECT_TRUE(conn_mgr()->Connect(dev->identifier(), callback))
<< "request count: " << i + 1;
}
RunLoopUntilIdle();
ASSERT_EQ(kRequestCount, cb_count);
for (int i = 0; i < kRequestCount; ++i) {
EXPECT_TRUE(statuses[i].is_protocol_error());
EXPECT_EQ(hci::StatusCode::kConnectionFailedToBeEstablished,
statuses[i].protocol_error())
<< "request count: " << i + 1;
}
}
TEST_F(GAP_LowEnergyConnectionManagerTest, OneDeviceManyPendingRequests) {
constexpr size_t kRequestCount = 50;
auto* dev = dev_cache()->NewDevice(kAddress0, true);
auto fake_dev = std::make_unique<FakeDevice>(kAddress0);
test_device()->AddDevice(std::move(fake_dev));
std::vector<LowEnergyConnectionRefPtr> conn_refs;
auto callback = [&conn_refs](auto cb_status, auto conn_ref) {
EXPECT_TRUE(conn_ref);
EXPECT_TRUE(cb_status);
conn_refs.emplace_back(std::move(conn_ref));
};
for (size_t i = 0; i < kRequestCount; ++i) {
EXPECT_TRUE(conn_mgr()->Connect(dev->identifier(), callback))
<< "request count: " << i + 1;
}
RunLoopUntilIdle();
EXPECT_EQ(1u, connected_devices().size());
EXPECT_EQ(1u, connected_devices().count(kAddress0));
EXPECT_EQ(kRequestCount, conn_refs.size());
for (size_t i = 0; i < kRequestCount; ++i) {
ASSERT_TRUE(conn_refs[i]);
EXPECT_TRUE(conn_refs[i]->active());
EXPECT_EQ(dev->identifier(), conn_refs[i]->device_identifier());
}
// Release one reference. The rest should be active.
conn_refs[0] = nullptr;
for (size_t i = 1; i < kRequestCount; ++i)
EXPECT_TRUE(conn_refs[i]->active());
// Release all but one reference.
for (size_t i = 1; i < kRequestCount - 1; ++i)
conn_refs[i] = nullptr;
EXPECT_TRUE(conn_refs[kRequestCount - 1]->active());
// Drop the last reference.
conn_refs[kRequestCount - 1] = nullptr;
RunLoopUntilIdle();
EXPECT_TRUE(connected_devices().empty());
}
TEST_F(GAP_LowEnergyConnectionManagerTest, AddRefAfterConnection) {
constexpr size_t kRefCount = 50;
auto* dev = dev_cache()->NewDevice(kAddress0, true);
auto fake_dev = std::make_unique<FakeDevice>(kAddress0);
test_device()->AddDevice(std::move(fake_dev));
std::vector<LowEnergyConnectionRefPtr> conn_refs;
auto callback = [&conn_refs](auto cb_status, auto conn_ref) {
EXPECT_TRUE(conn_ref);
EXPECT_TRUE(cb_status);
conn_refs.emplace_back(std::move(conn_ref));
};
EXPECT_TRUE(conn_mgr()->Connect(dev->identifier(), callback));
RunLoopUntilIdle();
EXPECT_EQ(1u, connected_devices().size());
EXPECT_EQ(1u, connected_devices().count(kAddress0));
EXPECT_EQ(1u, conn_refs.size());
// Add new references.
for (size_t i = 1; i < kRefCount; ++i) {
EXPECT_TRUE(conn_mgr()->Connect(dev->identifier(), callback))
<< "request count: " << i + 1;
RunLoopUntilIdle();
}
EXPECT_EQ(1u, connected_devices().size());
EXPECT_EQ(1u, connected_devices().count(kAddress0));
EXPECT_EQ(kRefCount, conn_refs.size());
// Disconnect.
conn_refs.clear();
RunLoopUntilIdle();
EXPECT_TRUE(connected_devices().empty());
}
TEST_F(GAP_LowEnergyConnectionManagerTest, PendingRequestsOnTwoDevices) {
auto* dev0 = dev_cache()->NewDevice(kAddress0, true);
auto* dev1 = dev_cache()->NewDevice(kAddress1, true);
test_device()->AddDevice(std::make_unique<FakeDevice>(kAddress0));
test_device()->AddDevice(std::make_unique<FakeDevice>(kAddress1));
std::vector<LowEnergyConnectionRefPtr> conn_refs;
auto callback = [&conn_refs](auto cb_status, auto conn_ref) {
EXPECT_TRUE(conn_ref);
EXPECT_TRUE(cb_status);
conn_refs.emplace_back(std::move(conn_ref));
};
EXPECT_TRUE(conn_mgr()->Connect(dev0->identifier(), callback));
EXPECT_TRUE(conn_mgr()->Connect(dev1->identifier(), callback));
RunLoopUntilIdle();
EXPECT_EQ(2u, connected_devices().size());
EXPECT_EQ(1u, connected_devices().count(kAddress0));
EXPECT_EQ(1u, connected_devices().count(kAddress1));
ASSERT_EQ(2u, conn_refs.size());
ASSERT_TRUE(conn_refs[0]);
ASSERT_TRUE(conn_refs[1]);
EXPECT_EQ(dev0->identifier(), conn_refs[0]->device_identifier());
EXPECT_EQ(dev1->identifier(), conn_refs[1]->device_identifier());
// |dev1| should disconnect first.
conn_refs[1] = nullptr;
RunLoopUntilIdle();
EXPECT_EQ(1u, connected_devices().size());
EXPECT_EQ(1u, connected_devices().count(kAddress0));
conn_refs.clear();
RunLoopUntilIdle();
EXPECT_TRUE(connected_devices().empty());
}
TEST_F(GAP_LowEnergyConnectionManagerTest,
PendingRequestsOnTwoDevicesOneFails) {
auto* dev0 = dev_cache()->NewDevice(kAddress0, true);
auto* dev1 = dev_cache()->NewDevice(kAddress1, true);
auto fake_dev0 = std::make_unique<FakeDevice>(kAddress0);
fake_dev0->set_connect_response(
hci::StatusCode::kConnectionFailedToBeEstablished);
test_device()->AddDevice(std::move(fake_dev0));
test_device()->AddDevice(std::make_unique<FakeDevice>(kAddress1));
std::vector<LowEnergyConnectionRefPtr> conn_refs;
auto callback = [&conn_refs](auto, auto conn_ref) {
conn_refs.emplace_back(std::move(conn_ref));
};
EXPECT_TRUE(conn_mgr()->Connect(dev0->identifier(), callback));
EXPECT_TRUE(conn_mgr()->Connect(dev1->identifier(), callback));
RunLoopUntilIdle();
EXPECT_EQ(1u, connected_devices().size());
EXPECT_EQ(1u, connected_devices().count(kAddress1));
ASSERT_EQ(2u, conn_refs.size());
EXPECT_FALSE(conn_refs[0]);
ASSERT_TRUE(conn_refs[1]);
EXPECT_EQ(dev1->identifier(), conn_refs[1]->device_identifier());
// Both connections should disconnect.
conn_refs.clear();
RunLoopUntilIdle();
EXPECT_TRUE(connected_devices().empty());
}
TEST_F(GAP_LowEnergyConnectionManagerTest, Destructor) {
auto* dev0 = dev_cache()->NewDevice(kAddress0, true);
auto* dev1 = dev_cache()->NewDevice(kAddress1, true);
// Connecting to this device will succeed.
test_device()->AddDevice(std::make_unique<FakeDevice>(kAddress0));
// Connecting to this device will remain pending.
auto pending_dev = std::make_unique<FakeDevice>(kAddress1);
pending_dev->set_force_pending_connect(true);
test_device()->AddDevice(std::move(pending_dev));
// Below we create one connection and one pending request to have at the time
// of destruction.
LowEnergyConnectionRefPtr conn_ref;
auto success_cb = [&conn_ref, this](auto status, auto cb_conn_ref) {
EXPECT_TRUE(cb_conn_ref);
EXPECT_TRUE(status);
conn_ref = std::move(cb_conn_ref);
};
EXPECT_TRUE(conn_mgr()->Connect(dev0->identifier(), success_cb));
RunLoopUntilIdle();
ASSERT_TRUE(conn_ref);
bool conn_closed = false;
conn_ref->set_closed_callback([&conn_closed] { conn_closed = true; });
bool error_cb_called = false;
auto error_cb = [&error_cb_called](auto status, auto conn_ref) {
EXPECT_FALSE(conn_ref);
EXPECT_EQ(common::HostError::kFailed, status.error());
error_cb_called = true;
};
// This will send an HCI command to the fake controller. We delete the
// connection manager before a connection event gets received which should
// cancel the connection.
EXPECT_TRUE(conn_mgr()->Connect(dev1->identifier(), error_cb));
DeleteConnMgr();
RunLoopUntilIdle();
EXPECT_TRUE(error_cb_called);
EXPECT_TRUE(conn_closed);
EXPECT_EQ(1u, canceled_devices().size());
EXPECT_EQ(1u, canceled_devices().count(kAddress1));
}
TEST_F(GAP_LowEnergyConnectionManagerTest, DisconnectError) {
auto* dev = dev_cache()->NewDevice(kAddress0, true);
test_device()->AddDevice(std::make_unique<FakeDevice>(kAddress0));
// This should fail as |dev0| is not connected.
EXPECT_FALSE(conn_mgr()->Disconnect(dev->identifier()));
}
TEST_F(GAP_LowEnergyConnectionManagerTest, Disconnect) {
auto* dev = dev_cache()->NewDevice(kAddress0, true);
test_device()->AddDevice(std::make_unique<FakeDevice>(kAddress0));
int closed_count = 0;
auto closed_cb = [&closed_count] { closed_count++; };
std::vector<LowEnergyConnectionRefPtr> conn_refs;
auto success_cb = [&conn_refs, &closed_cb, this](auto status, auto conn_ref) {
EXPECT_TRUE(status);
ASSERT_TRUE(conn_ref);
conn_ref->set_closed_callback(closed_cb);
conn_refs.push_back(std::move(conn_ref));
};
// Issue two connection refs.
EXPECT_TRUE(conn_mgr()->Connect(dev->identifier(), success_cb));
EXPECT_TRUE(conn_mgr()->Connect(dev->identifier(), success_cb));
RunLoopUntilIdle();
ASSERT_EQ(2u, conn_refs.size());
EXPECT_TRUE(conn_mgr()->Disconnect(dev->identifier()));
RunLoopUntilIdle();
EXPECT_EQ(2, closed_count);
EXPECT_TRUE(connected_devices().empty());
EXPECT_TRUE(canceled_devices().empty());
}
// Tests when a link is lost without explicitly disconnecting
TEST_F(GAP_LowEnergyConnectionManagerTest, DisconnectEvent) {
auto* dev = dev_cache()->NewDevice(kAddress0, true);
test_device()->AddDevice(std::make_unique<FakeDevice>(kAddress0));
int closed_count = 0;
auto closed_cb = [&closed_count, this] {
closed_count++;
};
std::vector<LowEnergyConnectionRefPtr> conn_refs;
auto success_cb = [&conn_refs, &closed_cb, this](auto status, auto conn_ref) {
EXPECT_TRUE(status);
ASSERT_TRUE(conn_ref);
conn_ref->set_closed_callback(closed_cb);
conn_refs.push_back(std::move(conn_ref));
};
// Issue two connection refs.
EXPECT_TRUE(conn_mgr()->Connect(dev->identifier(), success_cb));
EXPECT_TRUE(conn_mgr()->Connect(dev->identifier(), success_cb));
RunLoopUntilIdle();
ASSERT_EQ(2u, conn_refs.size());
// This makes FakeController send us HCI Disconnection Complete events.
test_device()->Disconnect(kAddress0);
RunLoopUntilIdle();
EXPECT_EQ(2, closed_count);
}
TEST_F(GAP_LowEnergyConnectionManagerTest, DisconnectWhileRefPending) {
auto* dev = dev_cache()->NewDevice(kAddress0, true);
test_device()->AddDevice(std::make_unique<FakeDevice>(kAddress0));
LowEnergyConnectionRefPtr conn_ref;
auto success_cb = [&conn_ref, this](auto status, auto cb_conn_ref) {
EXPECT_TRUE(status);
ASSERT_TRUE(cb_conn_ref);
EXPECT_TRUE(cb_conn_ref->active());
conn_ref = std::move(cb_conn_ref);
};
EXPECT_TRUE(conn_mgr()->Connect(dev->identifier(), success_cb));
RunLoopUntilIdle();
ASSERT_TRUE(conn_ref);
auto ref_cb = [](auto status, auto conn_ref) {
EXPECT_FALSE(conn_ref);
EXPECT_FALSE(status);
EXPECT_EQ(common::HostError::kFailed, status.error());
};
EXPECT_TRUE(conn_mgr()->Connect(dev->identifier(), ref_cb));
// This should invalidate the ref that was bound to |ref_cb|.
EXPECT_TRUE(conn_mgr()->Disconnect(dev->identifier()));
RunLoopUntilIdle();
}
// This tests that a connection reference callback returns nullptr if a HCI
// Disconnection Complete event is received for the corresponding ACL link
// BEFORE the callback gets run.
TEST_F(GAP_LowEnergyConnectionManagerTest, DisconnectEventWhileRefPending) {
auto* dev = dev_cache()->NewDevice(kAddress0, true);
test_device()->AddDevice(std::make_unique<FakeDevice>(kAddress0));
LowEnergyConnectionRefPtr conn_ref;
auto success_cb = [&conn_ref, this](auto status, auto cb_conn_ref) {
ASSERT_TRUE(cb_conn_ref);
ASSERT_TRUE(status);
EXPECT_TRUE(cb_conn_ref->active());
conn_ref = std::move(cb_conn_ref);
};
EXPECT_TRUE(conn_mgr()->Connect(dev->identifier(), success_cb));
RunLoopUntilIdle();
ASSERT_TRUE(conn_ref);
// Request a new reference. Disconnect the link before the reference is
// received.
auto ref_cb = [](auto status, auto conn_ref) {
EXPECT_FALSE(conn_ref);
EXPECT_FALSE(status);
EXPECT_EQ(common::HostError::kFailed, status.error());
};
auto disconn_cb = [this, ref_cb, dev](auto) {
// The link is gone but conn_mgr() hasn't updated the connection state yet.
// The request to connect will attempt to add a new reference which will be
// invalidated before |ref_cb| gets called.
EXPECT_TRUE(conn_mgr()->Connect(dev->identifier(), ref_cb));
};
conn_mgr()->SetDisconnectCallbackForTesting(disconn_cb);
test_device()->Disconnect(kAddress0);
RunLoopUntilIdle();
}
// Listener receives remote initiated connection ref.
TEST_F(GAP_LowEnergyConnectionManagerTest, RegisterRemoteInitiatedLink) {
test_device()->AddDevice(std::make_unique<FakeDevice>(kAddress0));
// First create a fake incoming connection.
test_device()->ConnectLowEnergy(kAddress0);
RunLoopUntilIdle();
auto link = MoveLastRemoteInitiated();
ASSERT_TRUE(link);
LowEnergyConnectionRefPtr conn_ref =
conn_mgr()->RegisterRemoteInitiatedLink(std::move(link));
ASSERT_TRUE(conn_ref);
EXPECT_TRUE(conn_ref->active());
// A RemoteDevice should now exist in the cache.
auto* dev = dev_cache()->FindDeviceByAddress(kAddress0);
ASSERT_TRUE(dev);
EXPECT_EQ(dev->identifier(), conn_ref->device_identifier());
EXPECT_TRUE(dev->connected());
EXPECT_TRUE(dev->le()->connected());
conn_ref = nullptr;
RunLoopUntilIdle();
EXPECT_TRUE(connected_devices().empty());
}
// Listener receives remote initiated connection ref for a known device with the
// same BR/EDR address.
TEST_F(GAP_LowEnergyConnectionManagerTest,
IncomingConnectionUpgradesKnownBrEdrDeviceToDualMode) {
RemoteDevice* device = dev_cache()->NewDevice(kAddrAlias0, true);
ASSERT_TRUE(device);
ASSERT_EQ(device, dev_cache()->FindDeviceByAddress(kAddress0));
ASSERT_EQ(TechnologyType::kClassic, device->technology());
test_device()->AddDevice(std::make_unique<FakeDevice>(kAddress0));
// First create a fake incoming connection.
test_device()->ConnectLowEnergy(kAddress0);
RunLoopUntilIdle();
auto link = MoveLastRemoteInitiated();
ASSERT_TRUE(link);
LowEnergyConnectionRefPtr conn_ref =
conn_mgr()->RegisterRemoteInitiatedLink(std::move(link));
ASSERT_TRUE(conn_ref);
EXPECT_EQ(device->identifier(), conn_ref->device_identifier());
EXPECT_EQ(TechnologyType::kDualMode, device->technology());
}
// Tests that the master accepts the connection parameters that are sent from
// a fake slave and eventually applies them to the link.
TEST_F(GAP_LowEnergyConnectionManagerTest, L2CAPLEConnectionParameterUpdate) {
// Set up a fake device and a connection over which to process the L2CAP
// request.
test_device()->AddDevice(std::make_unique<FakeDevice>(kAddress0));
auto* dev = dev_cache()->NewDevice(kAddress0, true);
ASSERT_TRUE(dev);
LowEnergyConnectionRefPtr conn_ref;
auto conn_cb = [&conn_ref](const auto& dev_id, auto cr) {
conn_ref = std::move(cr);
};
ASSERT_TRUE(conn_mgr()->Connect(dev->identifier(), conn_cb));
RunLoopUntilIdle();
ASSERT_TRUE(conn_ref);
hci::LEPreferredConnectionParameters preferred(
hci::kLEConnectionIntervalMin, hci::kLEConnectionIntervalMax,
hci::kLEConnectionLatencyMax, hci::kLEConnectionSupervisionTimeoutMax);
hci::LEConnectionParameters actual;
bool fake_dev_cb_called = false;
bool conn_params_cb_called = false;
auto fake_dev_cb = [&actual, &fake_dev_cb_called](const auto& addr,
const auto& params) {
fake_dev_cb_called = true;
actual = params;
};
test_device()->SetLEConnectionParametersCallback(fake_dev_cb, dispatcher());
auto conn_params_cb = [&conn_params_cb_called, &conn_ref](const auto& dev) {
EXPECT_EQ(conn_ref->device_identifier(), dev.identifier());
conn_params_cb_called = true;
};
conn_mgr()->SetConnectionParametersCallbackForTesting(conn_params_cb);
fake_l2cap()->TriggerLEConnectionParameterUpdate(conn_ref->handle(),
preferred);
RunLoopUntilIdle();
EXPECT_TRUE(fake_dev_cb_called);
ASSERT_TRUE(conn_params_cb_called);
ASSERT_TRUE(dev->le());
EXPECT_EQ(preferred, *dev->le()->preferred_connection_parameters());
EXPECT_EQ(actual, *dev->le()->connection_parameters());
}
TEST_F(GAP_LowEnergyConnectionManagerTest, L2CAPSignalLinkError) {
// Set up a fake device and a connection over which to process the L2CAP
// request.
test_device()->AddDevice(std::make_unique<FakeDevice>(kAddress0));
auto* dev = dev_cache()->NewDevice(kAddress0, true);
ASSERT_TRUE(dev);
fbl::RefPtr<l2cap::Channel> att_chan;
auto l2cap_chan_cb = [&att_chan](auto chan) { att_chan = chan; };
fake_l2cap()->set_channel_callback(l2cap_chan_cb);
LowEnergyConnectionRefPtr conn_ref;
auto conn_cb = [&conn_ref](const auto& dev_id, auto cr) {
conn_ref = std::move(cr);
};
ASSERT_TRUE(conn_mgr()->Connect(dev->identifier(), conn_cb));
RunLoopUntilIdle();
ASSERT_TRUE(conn_ref);
ASSERT_TRUE(att_chan);
ASSERT_EQ(1u, connected_devices().size());
// Signaling a link error through the channel should disconnect the link.
att_chan->SignalLinkError();
RunLoopUntilIdle();
EXPECT_TRUE(connected_devices().empty());
}
} // namespace
} // namespace gap
} // namespace btlib