| // 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/hci/low_energy_connector.h" |
| |
| #include <fbl/macros.h> |
| #include <lib/async/cpp/task.h> |
| |
| #include <vector> |
| |
| #include "src/connectivity/bluetooth/core/bt-host/hci/defaults.h" |
| #include "src/connectivity/bluetooth/core/bt-host/hci/fake_connection.h" |
| #include "src/connectivity/bluetooth/core/bt-host/hci/fake_local_address_delegate.h" |
| #include "src/connectivity/bluetooth/core/bt-host/testing/fake_controller.h" |
| #include "src/connectivity/bluetooth/core/bt-host/testing/fake_controller_test.h" |
| #include "src/connectivity/bluetooth/core/bt-host/testing/fake_peer.h" |
| |
| namespace bt { |
| namespace hci { |
| namespace { |
| |
| using bt::testing::FakeController; |
| using bt::testing::FakePeer; |
| using TestingBase = bt::testing::FakeControllerTest<FakeController>; |
| |
| const DeviceAddress kLocalAddress(DeviceAddress::Type::kLEPublic, |
| "00:00:00:00:00:FF"); |
| const DeviceAddress kRandomAddress(DeviceAddress::Type::kLERandom, |
| "00:00:00:00:00:FE"); |
| const DeviceAddress kTestAddress(DeviceAddress::Type::kLEPublic, |
| "00:00:00:00:00:01"); |
| const LEPreferredConnectionParameters kTestParams(1, 1, 1, 1); |
| constexpr zx::duration kConnectTimeout = zx::sec(10); |
| |
| class LowEnergyConnectorTest : public TestingBase { |
| public: |
| LowEnergyConnectorTest() = default; |
| ~LowEnergyConnectorTest() override = default; |
| |
| protected: |
| // TestingBase overrides: |
| void SetUp() override { |
| TestingBase::SetUp(); |
| InitializeACLDataChannel(); |
| |
| FakeController::Settings settings; |
| settings.ApplyLegacyLEConfig(); |
| test_device()->set_settings(settings); |
| |
| fake_address_delegate_.set_local_address(kLocalAddress); |
| connector_ = std::make_unique<LowEnergyConnector>( |
| transport(), &fake_address_delegate_, dispatcher(), |
| fit::bind_member(this, |
| &LowEnergyConnectorTest::OnIncomingConnectionCreated)); |
| |
| test_device()->SetConnectionStateCallback( |
| fit::bind_member(this, |
| &LowEnergyConnectorTest::OnConnectionStateChanged), |
| dispatcher()); |
| StartTestDevice(); |
| } |
| |
| void TearDown() override { |
| connector_ = nullptr; |
| test_device()->Stop(); |
| TestingBase::TearDown(); |
| } |
| |
| void DeleteConnector() { connector_ = nullptr; } |
| |
| bool request_canceled = false; |
| |
| const std::vector<std::unique_ptr<Connection>>& in_connections() const { |
| return in_connections_; |
| } |
| LowEnergyConnector* connector() const { return connector_.get(); } |
| FakeLocalAddressDelegate* fake_address_delegate() { |
| return &fake_address_delegate_; |
| } |
| |
| private: |
| void OnIncomingConnectionCreated(ConnectionHandle handle, |
| Connection::Role role, |
| const DeviceAddress& peer_address, |
| const LEConnectionParameters& conn_params) { |
| in_connections_.push_back(std::make_unique<testing::FakeConnection>( |
| handle, hci::Connection::LinkType::kLE, role, kLocalAddress, |
| peer_address)); |
| } |
| |
| void OnConnectionStateChanged(const DeviceAddress& address, bool connected, |
| bool canceled) { |
| request_canceled = canceled; |
| } |
| |
| FakeLocalAddressDelegate fake_address_delegate_; |
| std::unique_ptr<LowEnergyConnector> connector_; |
| |
| // Incoming connections. |
| std::vector<std::unique_ptr<Connection>> in_connections_; |
| |
| DISALLOW_COPY_AND_ASSIGN_ALLOW_MOVE(LowEnergyConnectorTest); |
| }; |
| |
| using HCI_LowEnergyConnectorTest = LowEnergyConnectorTest; |
| |
| TEST_F(HCI_LowEnergyConnectorTest, CreateConnection) { |
| auto fake_peer = std::make_unique<FakePeer>(kTestAddress, true, true); |
| test_device()->AddPeer(std::move(fake_peer)); |
| |
| EXPECT_FALSE(connector()->request_pending()); |
| |
| hci::Status status; |
| ConnectionPtr conn; |
| bool callback_called = false; |
| |
| auto callback = [&](auto cb_status, auto cb_conn) { |
| status = cb_status; |
| conn = std::move(cb_conn); |
| callback_called = true; |
| }; |
| |
| bool ret = connector()->CreateConnection( |
| false, kTestAddress, defaults::kLEScanInterval, defaults::kLEScanWindow, |
| kTestParams, callback, kConnectTimeout); |
| EXPECT_TRUE(ret); |
| EXPECT_TRUE(connector()->request_pending()); |
| |
| ret = connector()->CreateConnection( |
| false, kTestAddress, defaults::kLEScanInterval, defaults::kLEScanWindow, |
| kTestParams, callback, kConnectTimeout); |
| EXPECT_FALSE(ret); |
| |
| RunLoopUntilIdle(); |
| |
| EXPECT_FALSE(connector()->request_pending()); |
| EXPECT_TRUE(callback_called); |
| EXPECT_TRUE(status); |
| EXPECT_TRUE(in_connections().empty()); |
| |
| ASSERT_TRUE(conn); |
| EXPECT_EQ(1u, conn->handle()); |
| EXPECT_EQ(kLocalAddress, conn->local_address()); |
| EXPECT_EQ(kTestAddress, conn->peer_address()); |
| EXPECT_TRUE(conn->is_open()); |
| conn->set_closed(); |
| } |
| |
| // Controller reports error from HCI Command Status event. |
| TEST_F(HCI_LowEnergyConnectorTest, CreateConnectionStatusError) { |
| auto fake_peer = std::make_unique<FakePeer>(kTestAddress, true, true); |
| fake_peer->set_connect_status(StatusCode::kCommandDisallowed); |
| test_device()->AddPeer(std::move(fake_peer)); |
| |
| EXPECT_FALSE(connector()->request_pending()); |
| |
| hci::Status status; |
| ConnectionPtr conn; |
| bool callback_called = false; |
| |
| auto callback = [&](auto cb_status, auto cb_conn) { |
| status = cb_status; |
| conn = std::move(cb_conn); |
| callback_called = true; |
| }; |
| |
| bool ret = connector()->CreateConnection( |
| false, kTestAddress, defaults::kLEScanInterval, defaults::kLEScanWindow, |
| kTestParams, callback, kConnectTimeout); |
| EXPECT_TRUE(ret); |
| EXPECT_TRUE(connector()->request_pending()); |
| |
| RunLoopUntilIdle(); |
| |
| EXPECT_FALSE(connector()->request_pending()); |
| EXPECT_TRUE(callback_called); |
| EXPECT_FALSE(status); |
| EXPECT_TRUE(status.is_protocol_error()); |
| EXPECT_EQ(StatusCode::kCommandDisallowed, status.protocol_error()); |
| EXPECT_FALSE(conn); |
| EXPECT_TRUE(in_connections().empty()); |
| } |
| |
| // Controller reports error from HCI LE Connection Complete event |
| TEST_F(HCI_LowEnergyConnectorTest, CreateConnectionEventError) { |
| auto fake_peer = std::make_unique<FakePeer>(kTestAddress, true, true); |
| fake_peer->set_connect_response(StatusCode::kConnectionRejectedSecurity); |
| test_device()->AddPeer(std::move(fake_peer)); |
| |
| EXPECT_FALSE(connector()->request_pending()); |
| |
| hci::Status status; |
| ConnectionPtr conn; |
| bool callback_called = false; |
| |
| auto callback = [&](auto cb_status, auto cb_conn) { |
| status = cb_status; |
| callback_called = true; |
| conn = std::move(cb_conn); |
| }; |
| |
| bool ret = connector()->CreateConnection( |
| false, kTestAddress, defaults::kLEScanInterval, defaults::kLEScanWindow, |
| kTestParams, callback, kConnectTimeout); |
| EXPECT_TRUE(ret); |
| EXPECT_TRUE(connector()->request_pending()); |
| |
| RunLoopUntilIdle(); |
| |
| EXPECT_FALSE(connector()->request_pending()); |
| EXPECT_TRUE(callback_called); |
| EXPECT_FALSE(status); |
| EXPECT_TRUE(status.is_protocol_error()); |
| EXPECT_EQ(StatusCode::kConnectionRejectedSecurity, status.protocol_error()); |
| EXPECT_TRUE(in_connections().empty()); |
| EXPECT_FALSE(conn); |
| } |
| |
| // Controller reports error from HCI LE Connection Complete event |
| TEST_F(HCI_LowEnergyConnectorTest, Cancel) { |
| auto fake_peer = std::make_unique<FakePeer>(kTestAddress, true, true); |
| |
| // Make sure the pending connect remains pending. |
| fake_peer->set_force_pending_connect(true); |
| test_device()->AddPeer(std::move(fake_peer)); |
| |
| hci::Status status; |
| ConnectionPtr conn; |
| bool callback_called = false; |
| |
| auto callback = [&](auto cb_status, auto cb_conn) { |
| status = cb_status; |
| callback_called = true; |
| conn = std::move(cb_conn); |
| }; |
| |
| bool ret = connector()->CreateConnection( |
| false, kTestAddress, defaults::kLEScanInterval, defaults::kLEScanWindow, |
| kTestParams, callback, kConnectTimeout); |
| EXPECT_TRUE(ret); |
| EXPECT_TRUE(connector()->request_pending()); |
| |
| ASSERT_FALSE(request_canceled); |
| |
| connector()->Cancel(); |
| EXPECT_TRUE(connector()->request_pending()); |
| |
| // The request timeout should be canceled regardless of whether it was posted |
| // before. |
| EXPECT_FALSE(connector()->timeout_posted()); |
| |
| RunLoopUntilIdle(); |
| |
| EXPECT_FALSE(connector()->timeout_posted()); |
| EXPECT_FALSE(connector()->request_pending()); |
| EXPECT_TRUE(callback_called); |
| EXPECT_TRUE(request_canceled); |
| EXPECT_EQ(HostError::kCanceled, status.error()); |
| EXPECT_TRUE(in_connections().empty()); |
| EXPECT_FALSE(conn); |
| } |
| |
| TEST_F(HCI_LowEnergyConnectorTest, IncomingConnect) { |
| EXPECT_TRUE(in_connections().empty()); |
| EXPECT_FALSE(connector()->request_pending()); |
| |
| LEConnectionCompleteSubeventParams event; |
| std::memset(&event, 0, sizeof(event)); |
| |
| event.status = StatusCode::kSuccess; |
| event.peer_address = kTestAddress.value(); |
| event.peer_address_type = LEPeerAddressType::kPublic; |
| event.conn_interval = defaults::kLEConnectionIntervalMin; |
| event.connection_handle = 1; |
| |
| test_device()->SendLEMetaEvent(kLEConnectionCompleteSubeventCode, |
| BufferView(&event, sizeof(event))); |
| |
| RunLoopUntilIdle(); |
| |
| ASSERT_EQ(1u, in_connections().size()); |
| |
| auto conn = in_connections()[0].get(); |
| EXPECT_EQ(1u, conn->handle()); |
| EXPECT_EQ(kLocalAddress, conn->local_address()); |
| EXPECT_EQ(kTestAddress, conn->peer_address()); |
| EXPECT_TRUE(conn->is_open()); |
| conn->set_closed(); |
| } |
| |
| TEST_F(HCI_LowEnergyConnectorTest, IncomingConnectDuringConnectionRequest) { |
| const DeviceAddress kIncomingAddress(DeviceAddress::Type::kLEPublic, |
| "00:00:00:00:00:02"); |
| |
| EXPECT_TRUE(in_connections().empty()); |
| EXPECT_FALSE(connector()->request_pending()); |
| |
| auto fake_peer = std::make_unique<FakePeer>(kTestAddress, true, true); |
| test_device()->AddPeer(std::move(fake_peer)); |
| |
| hci::Status status; |
| ConnectionPtr conn; |
| unsigned int callback_count = 0; |
| |
| auto callback = [&](auto cb_status, auto cb_conn) { |
| status = cb_status; |
| callback_count++; |
| conn = std::move(cb_conn); |
| }; |
| |
| connector()->CreateConnection(false, kTestAddress, defaults::kLEScanInterval, |
| defaults::kLEScanWindow, kTestParams, callback, |
| kConnectTimeout); |
| |
| async::PostTask(dispatcher(), [kIncomingAddress, this] { |
| LEConnectionCompleteSubeventParams event; |
| std::memset(&event, 0, sizeof(event)); |
| |
| event.status = StatusCode::kSuccess; |
| event.peer_address = kIncomingAddress.value(); |
| event.peer_address_type = LEPeerAddressType::kPublic; |
| event.conn_interval = defaults::kLEConnectionIntervalMin; |
| event.connection_handle = 2; |
| |
| test_device()->SendLEMetaEvent(kLEConnectionCompleteSubeventCode, |
| BufferView(&event, sizeof(event))); |
| }); |
| |
| RunLoopUntilIdle(); |
| |
| EXPECT_TRUE(status); |
| EXPECT_EQ(1u, callback_count); |
| ASSERT_EQ(1u, in_connections().size()); |
| |
| const auto& in_conn = in_connections().front(); |
| |
| EXPECT_EQ(1u, conn->handle()); |
| EXPECT_EQ(2u, in_conn->handle()); |
| EXPECT_EQ(kTestAddress, conn->peer_address()); |
| EXPECT_EQ(kIncomingAddress, in_conn->peer_address()); |
| |
| EXPECT_TRUE(conn->is_open()); |
| EXPECT_TRUE(in_conn->is_open()); |
| conn->set_closed(); |
| in_conn->set_closed(); |
| } |
| |
| TEST_F(HCI_LowEnergyConnectorTest, CreateConnectionTimeout) { |
| // We do not set up any fake devices. This will cause the request to time out. |
| EXPECT_FALSE(connector()->request_pending()); |
| |
| hci::Status status; |
| ConnectionPtr conn; |
| bool callback_called = false; |
| |
| auto callback = [&](auto cb_status, auto cb_conn) { |
| status = cb_status; |
| callback_called = true; |
| conn = std::move(cb_conn); |
| }; |
| |
| connector()->CreateConnection(false, kTestAddress, defaults::kLEScanInterval, |
| defaults::kLEScanWindow, kTestParams, callback, |
| kConnectTimeout); |
| EXPECT_TRUE(connector()->request_pending()); |
| |
| EXPECT_FALSE(request_canceled); |
| |
| // Make the connection attempt time out. |
| RunLoopFor(kConnectTimeout); |
| |
| EXPECT_FALSE(connector()->request_pending()); |
| EXPECT_TRUE(callback_called); |
| EXPECT_TRUE(request_canceled); |
| EXPECT_EQ(HostError::kTimedOut, status.error()) << status.ToString(); |
| EXPECT_TRUE(in_connections().empty()); |
| EXPECT_FALSE(conn); |
| } |
| |
| TEST_F(HCI_LowEnergyConnectorTest, SendRequestAndDelete) { |
| auto fake_peer = std::make_unique<FakePeer>(kTestAddress, true, true); |
| |
| // Make sure the pending connect remains pending. |
| fake_peer->set_force_pending_connect(true); |
| test_device()->AddPeer(std::move(fake_peer)); |
| |
| bool ret = connector()->CreateConnection( |
| false, kTestAddress, defaults::kLEScanInterval, defaults::kLEScanWindow, |
| kTestParams, [](auto, auto) {}, kConnectTimeout); |
| EXPECT_TRUE(ret); |
| EXPECT_TRUE(connector()->request_pending()); |
| |
| DeleteConnector(); |
| RunLoopUntilIdle(); |
| |
| EXPECT_TRUE(request_canceled); |
| EXPECT_TRUE(in_connections().empty()); |
| } |
| |
| TEST_F(HCI_LowEnergyConnectorTest, AllowsRandomAddressChange) { |
| EXPECT_TRUE(connector()->AllowsRandomAddressChange()); |
| |
| auto fake_device = std::make_unique<FakePeer>(kTestAddress, true, true); |
| test_device()->AddPeer(std::move(fake_device)); |
| |
| // Address change should not be allowed while the procedure is pending. |
| connector()->CreateConnection( |
| false, kTestAddress, defaults::kLEScanInterval, defaults::kLEScanWindow, |
| kTestParams, [](auto, auto) {}, kConnectTimeout); |
| EXPECT_TRUE(connector()->request_pending()); |
| EXPECT_FALSE(connector()->AllowsRandomAddressChange()); |
| |
| RunLoopUntilIdle(); |
| EXPECT_TRUE(connector()->AllowsRandomAddressChange()); |
| } |
| |
| TEST_F(HCI_LowEnergyConnectorTest, |
| AllowsRandomAddressChangeWhileRequestingLocalAddress) { |
| // Make the local address delegate report its result asynchronously. |
| fake_address_delegate()->set_async(true); |
| |
| // The connector should be in the "request pending" state without initiating |
| // controller procedures that would prevent a local address change. |
| connector()->CreateConnection( |
| false, kTestAddress, defaults::kLEScanInterval, defaults::kLEScanWindow, |
| kTestParams, [](auto, auto) {}, kConnectTimeout); |
| EXPECT_TRUE(connector()->request_pending()); |
| EXPECT_TRUE(connector()->AllowsRandomAddressChange()); |
| |
| // Initiating a new connection should fail in this state. |
| bool result = connector()->CreateConnection( |
| false, kTestAddress, defaults::kLEScanInterval, defaults::kLEScanWindow, |
| kTestParams, [](auto, auto) {}, kConnectTimeout); |
| EXPECT_FALSE(result); |
| |
| // After the loop runs the request should remain pending (since we added no |
| // fake device, the request would eventually timeout) but address change |
| // should no longer be allowed. |
| RunLoopUntilIdle(); |
| EXPECT_TRUE(connector()->request_pending()); |
| EXPECT_FALSE(connector()->AllowsRandomAddressChange()); |
| } |
| |
| TEST_F(HCI_LowEnergyConnectorTest, ConnectUsingPublicAddress) { |
| auto fake_device = std::make_unique<FakePeer>(kTestAddress, true, true); |
| test_device()->AddPeer(std::move(fake_device)); |
| connector()->CreateConnection( |
| false, kTestAddress, defaults::kLEScanInterval, defaults::kLEScanWindow, |
| kTestParams, [](auto, auto) {}, kConnectTimeout); |
| RunLoopUntilIdle(); |
| ASSERT_TRUE(test_device()->le_connect_params()); |
| EXPECT_EQ(LEOwnAddressType::kPublic, |
| test_device()->le_connect_params()->own_address_type); |
| } |
| |
| TEST_F(HCI_LowEnergyConnectorTest, ConnectUsingRandomAddress) { |
| fake_address_delegate()->set_local_address(kRandomAddress); |
| |
| auto fake_device = std::make_unique<FakePeer>(kTestAddress, true, true); |
| test_device()->AddPeer(std::move(fake_device)); |
| connector()->CreateConnection( |
| false, kTestAddress, defaults::kLEScanInterval, defaults::kLEScanWindow, |
| kTestParams, [](auto, auto) {}, kConnectTimeout); |
| RunLoopUntilIdle(); |
| ASSERT_TRUE(test_device()->le_connect_params()); |
| EXPECT_EQ(LEOwnAddressType::kRandom, |
| test_device()->le_connect_params()->own_address_type); |
| } |
| |
| TEST_F(HCI_LowEnergyConnectorTest, CancelConnectWhileWaitingForLocalAddress) { |
| Status status; |
| ConnectionPtr conn; |
| auto callback = [&](auto s, auto c) { |
| status = s; |
| conn = std::move(c); |
| }; |
| fake_address_delegate()->set_async(true); |
| connector()->CreateConnection(false, kTestAddress, defaults::kLEScanInterval, |
| defaults::kLEScanWindow, kTestParams, |
| std::move(callback), kConnectTimeout); |
| |
| // Should be waiting for the address. |
| EXPECT_TRUE(connector()->request_pending()); |
| EXPECT_TRUE(connector()->AllowsRandomAddressChange()); |
| |
| connector()->Cancel(); |
| RunLoopUntilIdle(); |
| EXPECT_FALSE(connector()->request_pending()); |
| EXPECT_TRUE(connector()->AllowsRandomAddressChange()); |
| |
| // The controller should have received no command from us. |
| EXPECT_FALSE(test_device()->le_connect_params()); |
| EXPECT_FALSE(request_canceled); |
| |
| // Our request should have resulted in an error. |
| EXPECT_EQ(HostError::kCanceled, status.error()); |
| EXPECT_FALSE(conn); |
| } |
| |
| } // namespace |
| } // namespace hci |
| } // namespace bt |