| // Copyright 2019 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/fidl/low_energy_peripheral_server.h" |
| |
| #include "adapter_test_fixture.h" |
| #include "fuchsia/bluetooth/cpp/fidl.h" |
| #include "fuchsia/bluetooth/le/cpp/fidl.h" |
| #include "src/connectivity/bluetooth/core/bt-host/fidl/fake_adapter_test_fixture.h" |
| #include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/common/advertising_data.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/gap/low_energy_advertising_manager.h" |
| #include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/gap/low_energy_connection_manager.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/test_helpers.h" |
| |
| namespace bthost { |
| namespace { |
| |
| bool IsChannelPeerClosed(const zx::channel& channel) { |
| zx_signals_t ignored; |
| return ZX_OK == channel.wait_one(/*signals=*/ZX_CHANNEL_PEER_CLOSED, |
| /*deadline=*/zx::time(ZX_TIME_INFINITE_PAST), &ignored); |
| } |
| |
| namespace fble = fuchsia::bluetooth::le; |
| const bt::DeviceAddress kTestAddr(bt::DeviceAddress::Type::kLEPublic, {0x01, 0, 0, 0, 0, 0}); |
| const bt::DeviceAddress kTestAddr2(bt::DeviceAddress::Type::kLEPublic, {0x02, 0, 0, 0, 0, 0}); |
| |
| using bt::testing::FakePeer; |
| using FidlAdvHandle = fidl::InterfaceHandle<fble::AdvertisingHandle>; |
| |
| class LowEnergyPeripheralServerTestFakeAdapter : public bt::fidl::testing::FakeAdapterTestFixture { |
| public: |
| LowEnergyPeripheralServerTestFakeAdapter() = default; |
| ~LowEnergyPeripheralServerTestFakeAdapter() override = default; |
| |
| void SetUp() override { |
| bt::fidl::testing::FakeAdapterTestFixture::SetUp(); |
| |
| fake_gatt_ = std::make_unique<bt::gatt::testing::FakeLayer>(pw_dispatcher()); |
| |
| // Create a LowEnergyPeripheralServer and bind it to a local client. |
| fidl::InterfaceHandle<fble::Peripheral> handle; |
| server_ = std::make_unique<LowEnergyPeripheralServer>( |
| adapter()->AsWeakPtr(), fake_gatt_->GetWeakPtr(), handle.NewRequest()); |
| peripheral_client_.Bind(std::move(handle)); |
| } |
| |
| void TearDown() override { |
| RunLoopUntilIdle(); |
| |
| peripheral_client_ = nullptr; |
| server_ = nullptr; |
| bt::fidl::testing::FakeAdapterTestFixture::TearDown(); |
| } |
| |
| LowEnergyPeripheralServer* server() const { return server_.get(); } |
| |
| void SetOnPeerConnectedCallback(fble::Peripheral::OnPeerConnectedCallback cb) { |
| peripheral_client_.events().OnPeerConnected = std::move(cb); |
| } |
| |
| private: |
| std::unique_ptr<LowEnergyPeripheralServer> server_; |
| fble::PeripheralPtr peripheral_client_; |
| std::unique_ptr<bt::gatt::testing::FakeLayer> fake_gatt_; |
| BT_DISALLOW_COPY_AND_ASSIGN_ALLOW_MOVE(LowEnergyPeripheralServerTestFakeAdapter); |
| }; |
| |
| class LowEnergyPeripheralServerTest : public bthost::testing::AdapterTestFixture { |
| public: |
| LowEnergyPeripheralServerTest() = default; |
| ~LowEnergyPeripheralServerTest() override = default; |
| |
| void SetUp() override { |
| AdapterTestFixture::SetUp(); |
| |
| fake_gatt_ = std::make_unique<bt::gatt::testing::FakeLayer>(pw_dispatcher()); |
| |
| // Create a LowEnergyPeripheralServer and bind it to a local client. |
| fidl::InterfaceHandle<fble::Peripheral> handle; |
| server_ = std::make_unique<LowEnergyPeripheralServer>(adapter(), fake_gatt_->GetWeakPtr(), |
| handle.NewRequest()); |
| peripheral_client_.Bind(std::move(handle)); |
| } |
| |
| void TearDown() override { |
| RunLoopUntilIdle(); |
| |
| peripheral_client_ = nullptr; |
| server_ = nullptr; |
| AdapterTestFixture::TearDown(); |
| } |
| |
| LowEnergyPeripheralServer* server() const { return server_.get(); } |
| |
| void SetOnPeerConnectedCallback(fble::Peripheral::OnPeerConnectedCallback cb) { |
| peripheral_client_.events().OnPeerConnected = std::move(cb); |
| } |
| |
| private: |
| std::unique_ptr<LowEnergyPeripheralServer> server_; |
| fble::PeripheralPtr peripheral_client_; |
| std::unique_ptr<bt::gatt::testing::FakeLayer> fake_gatt_; |
| BT_DISALLOW_COPY_AND_ASSIGN_ALLOW_MOVE(LowEnergyPeripheralServerTest); |
| }; |
| |
| class BoolParam : public LowEnergyPeripheralServerTest, |
| public ::testing::WithParamInterface<bool> {}; |
| |
| class FakeAdvertisedPeripheral : public ServerBase<fble::AdvertisedPeripheral> { |
| public: |
| struct Connection { |
| fble::Peer peer; |
| fble::ConnectionHandle connection; |
| OnConnectedCallback callback; |
| }; |
| |
| FakeAdvertisedPeripheral(fidl::InterfaceRequest<fble::AdvertisedPeripheral> request) |
| : ServerBase(this, std::move(request)) {} |
| |
| void Unbind() { binding()->Unbind(); } |
| |
| void OnConnected(fble::Peer peer, fidl::InterfaceHandle<fble::Connection> connection, |
| OnConnectedCallback callback) override { |
| connections_.push_back({std::move(peer), std::move(connection), std::move(callback)}); |
| } |
| |
| std::optional<bt::PeerId> last_connected_peer() const { |
| if (connections_.empty()) { |
| return std::nullopt; |
| } |
| return bt::PeerId(connections_.back().peer.id().value); |
| } |
| |
| std::vector<Connection>& connections() { return connections_; } |
| |
| private: |
| std::vector<Connection> connections_; |
| }; |
| |
| // Tests that aborting a StartAdvertising command sequence does not cause a crash in successive |
| // requests. |
| TEST_F(LowEnergyPeripheralServerTest, StartAdvertisingWhilePendingDoesNotCrash) { |
| fble::AdvertisingParameters params1, params2, params3; |
| FidlAdvHandle token1, token2, token3; |
| |
| std::optional<fpromise::result<void, fble::PeripheralError>> result1, result2, result3; |
| server()->StartAdvertising(std::move(params1), token1.NewRequest(), |
| [&](auto result) { result1 = std::move(result); }); |
| server()->StartAdvertising(std::move(params2), token2.NewRequest(), |
| [&](auto result) { result2 = std::move(result); }); |
| server()->StartAdvertising(std::move(params3), token3.NewRequest(), |
| [&](auto result) { result3 = std::move(result); }); |
| RunLoopUntilIdle(); |
| |
| ASSERT_TRUE(result1); |
| ASSERT_TRUE(result2); |
| ASSERT_TRUE(result3); |
| EXPECT_TRUE(result1->is_error()); |
| EXPECT_EQ(fble::PeripheralError::ABORTED, result1->error()); |
| EXPECT_TRUE(result2->is_error()); |
| EXPECT_EQ(fble::PeripheralError::ABORTED, result2->error()); |
| EXPECT_TRUE(result3->is_ok()); |
| } |
| |
| // Same as the test above but tests that an error status leaves the server in the expected state. |
| TEST_F(LowEnergyPeripheralServerTest, StartAdvertisingWhilePendingDoesNotCrashWithControllerError) { |
| test_device()->SetDefaultResponseStatus(bt::hci_spec::kLESetAdvertisingEnable, |
| pw::bluetooth::emboss::StatusCode::COMMAND_DISALLOWED); |
| fble::AdvertisingParameters params1, params2, params3, params4; |
| FidlAdvHandle token1, token2, token3, token4; |
| |
| std::optional<fpromise::result<void, fble::PeripheralError>> result1, result2, result3, result4; |
| server()->StartAdvertising(std::move(params1), token1.NewRequest(), |
| [&](auto result) { result1 = std::move(result); }); |
| server()->StartAdvertising(std::move(params2), token2.NewRequest(), |
| [&](auto result) { result2 = std::move(result); }); |
| server()->StartAdvertising(std::move(params3), token3.NewRequest(), |
| [&](auto result) { result3 = std::move(result); }); |
| RunLoopUntilIdle(); |
| |
| ASSERT_TRUE(result1); |
| ASSERT_TRUE(result2); |
| ASSERT_TRUE(result3); |
| EXPECT_TRUE(result1->is_error()); |
| EXPECT_EQ(fble::PeripheralError::ABORTED, result1->error()); |
| EXPECT_TRUE(result2->is_error()); |
| EXPECT_EQ(fble::PeripheralError::ABORTED, result2->error()); |
| EXPECT_TRUE(result3->is_error()); |
| EXPECT_EQ(fble::PeripheralError::FAILED, result3->error()); |
| |
| // The next request should succeed as normal. |
| test_device()->ClearDefaultResponseStatus(bt::hci_spec::kLESetAdvertisingEnable); |
| server()->StartAdvertising(std::move(params4), token4.NewRequest(), |
| [&](auto result) { result4 = std::move(result); }); |
| RunLoopUntilIdle(); |
| |
| ASSERT_TRUE(result4); |
| EXPECT_TRUE(result4->is_ok()); |
| } |
| |
| TEST_F(LowEnergyPeripheralServerTest, AdvertiseWhilePendingDoesNotCrashWithControllerError) { |
| test_device()->SetDefaultResponseStatus(bt::hci_spec::kLESetAdvertisingEnable, |
| pw::bluetooth::emboss::StatusCode::COMMAND_DISALLOWED); |
| fble::AdvertisingParameters params1, params2, params3, params4; |
| |
| fble::AdvertisedPeripheralHandle adv_peripheral_handle_1; |
| FakeAdvertisedPeripheral adv_peripheral_server_1(adv_peripheral_handle_1.NewRequest()); |
| fble::AdvertisedPeripheralHandle adv_peripheral_handle_2; |
| FakeAdvertisedPeripheral adv_peripheral_server_2(adv_peripheral_handle_2.NewRequest()); |
| fble::AdvertisedPeripheralHandle adv_peripheral_handle_3; |
| FakeAdvertisedPeripheral adv_peripheral_server_3(adv_peripheral_handle_3.NewRequest()); |
| |
| std::optional<fpromise::result<void, fble::PeripheralError>> result1, result2, result3, result4; |
| server()->Advertise(std::move(params1), std::move(adv_peripheral_handle_1), |
| [&](auto result) { result1 = std::move(result); }); |
| server()->Advertise(std::move(params2), std::move(adv_peripheral_handle_2), |
| [&](auto result) { result2 = std::move(result); }); |
| server()->Advertise(std::move(params3), std::move(adv_peripheral_handle_3), |
| [&](auto result) { result3 = std::move(result); }); |
| RunLoopUntilIdle(); |
| ASSERT_TRUE(result1); |
| ASSERT_TRUE(result2); |
| ASSERT_TRUE(result3); |
| EXPECT_TRUE(result1->is_error()); |
| EXPECT_EQ(fble::PeripheralError::FAILED, result1->error()); |
| EXPECT_TRUE(result2->is_error()); |
| EXPECT_EQ(fble::PeripheralError::FAILED, result2->error()); |
| EXPECT_TRUE(result3->is_error()); |
| EXPECT_EQ(fble::PeripheralError::FAILED, result3->error()); |
| |
| // The next request should succeed as normal. |
| test_device()->ClearDefaultResponseStatus(bt::hci_spec::kLESetAdvertisingEnable); |
| |
| fble::AdvertisedPeripheralHandle adv_peripheral_handle_4; |
| FakeAdvertisedPeripheral adv_peripheral_server_4(adv_peripheral_handle_4.NewRequest()); |
| server()->Advertise(std::move(params4), std::move(adv_peripheral_handle_4), |
| [&](auto result) { result4 = std::move(result); }); |
| RunLoopUntilIdle(); |
| EXPECT_FALSE(result4); |
| adv_peripheral_server_4.Unbind(); |
| RunLoopUntilIdle(); |
| EXPECT_TRUE(result4); |
| } |
| |
| TEST_F(LowEnergyPeripheralServerTest, StartAdvertisingNoConnectionRelatedParamsNoConnection) { |
| fble::Peer peer; |
| // `conn` is stored so the bondable mode of the connection resulting from `OnPeerConnected` can |
| // be checked. The connection would otherwise be dropped immediately after `ConnectLowEnergy`. |
| fidl::InterfaceHandle<fble::Connection> conn; |
| auto peer_connected_cb = [&](auto cb_peer, auto cb_conn) { |
| peer = std::move(cb_peer); |
| conn = std::move(cb_conn); |
| }; |
| SetOnPeerConnectedCallback(peer_connected_cb); |
| |
| fble::AdvertisingParameters params; |
| |
| FidlAdvHandle token; |
| |
| std::optional<fpromise::result<void, fble::PeripheralError>> result; |
| server()->StartAdvertising(std::move(params), token.NewRequest(), |
| [&](auto cb_result) { result = std::move(cb_result); }); |
| RunLoopUntilIdle(); |
| ASSERT_TRUE(result.has_value()); |
| ASSERT_FALSE(result->is_error()); |
| |
| test_device()->AddPeer(std::make_unique<FakePeer>(kTestAddr, pw_dispatcher())); |
| test_device()->ConnectLowEnergy(kTestAddr); |
| RunLoopUntilIdle(); |
| |
| ASSERT_FALSE(peer.has_id()); |
| ASSERT_FALSE(conn.is_valid()); |
| } |
| |
| TEST_F(LowEnergyPeripheralServerTest, AdvertiseNoConnectionRelatedParamsNoConnection) { |
| fble::AdvertisedPeripheralHandle adv_peripheral_handle; |
| FakeAdvertisedPeripheral adv_peripheral_server(adv_peripheral_handle.NewRequest()); |
| fble::AdvertisingParameters params; |
| std::optional<fpromise::result<void, fble::PeripheralError>> result; |
| server()->Advertise(std::move(params), std::move(adv_peripheral_handle), |
| [&](auto cb_result) { result = std::move(cb_result); }); |
| RunLoopUntilIdle(); |
| EXPECT_FALSE(result.has_value()); |
| |
| test_device()->AddPeer(std::make_unique<FakePeer>(kTestAddr, pw_dispatcher())); |
| test_device()->ConnectLowEnergy(kTestAddr); |
| RunLoopUntilIdle(); |
| EXPECT_FALSE(adv_peripheral_server.last_connected_peer()); |
| adv_peripheral_server.Unbind(); |
| RunLoopUntilIdle(); |
| EXPECT_TRUE(result); |
| } |
| |
| TEST_F(LowEnergyPeripheralServerTest, StartAdvertisingConnectableParameterTrueConnectsBondable) { |
| fble::Peer peer; |
| // `conn` is stored so the bondable mode of the connection resulting from `OnPeerConnected` can |
| // be checked. The connection would otherwise be dropped immediately after `ConnectLowEnergy`. |
| fidl::InterfaceHandle<fble::Connection> conn; |
| auto peer_connected_cb = [&](auto cb_peer, auto cb_conn) { |
| peer = std::move(cb_peer); |
| conn = std::move(cb_conn); |
| }; |
| SetOnPeerConnectedCallback(peer_connected_cb); |
| |
| fble::AdvertisingParameters params; |
| params.set_connectable(true); |
| |
| FidlAdvHandle token; |
| |
| std::optional<fpromise::result<void, fble::PeripheralError>> result; |
| server()->StartAdvertising(std::move(params), token.NewRequest(), |
| [&](auto cb_result) { result = std::move(cb_result); }); |
| RunLoopUntilIdle(); |
| ASSERT_TRUE(result.has_value()); |
| ASSERT_FALSE(result->is_error()); |
| |
| test_device()->AddPeer(std::make_unique<FakePeer>(kTestAddr, pw_dispatcher())); |
| test_device()->ConnectLowEnergy(kTestAddr); |
| RunLoopUntilIdle(); |
| |
| ASSERT_TRUE(peer.has_id()); |
| ASSERT_TRUE(conn.is_valid()); |
| |
| auto connected_id = bt::PeerId(peer.id().value); |
| const bt::gap::LowEnergyConnectionHandle* conn_handle = |
| server()->FindConnectionForTesting(connected_id); |
| |
| ASSERT_TRUE(conn_handle); |
| ASSERT_EQ(conn_handle->bondable_mode(), bt::sm::BondableMode::Bondable); |
| } |
| |
| TEST_F(LowEnergyPeripheralServerTest, StartAdvertisingEmptyConnectionOptionsConnectsBondable) { |
| fble::Peer peer; |
| // `conn` is stored so the bondable mode of the connection resulting from `OnPeerConnected` can |
| // be checked. The connection would otherwise be dropped immediately after `ConnectLowEnergy`. |
| fidl::InterfaceHandle<fble::Connection> conn; |
| auto peer_connected_cb = [&](auto cb_peer, auto cb_conn) { |
| peer = std::move(cb_peer); |
| conn = std::move(cb_conn); |
| }; |
| SetOnPeerConnectedCallback(peer_connected_cb); |
| |
| fble::AdvertisingParameters params; |
| fble::ConnectionOptions conn_opts; |
| params.set_connection_options(std::move(conn_opts)); |
| |
| FidlAdvHandle token; |
| |
| std::optional<fpromise::result<void, fble::PeripheralError>> result; |
| server()->StartAdvertising(std::move(params), token.NewRequest(), |
| [&](auto cb_result) { result = std::move(cb_result); }); |
| RunLoopUntilIdle(); |
| ASSERT_TRUE(result.has_value()); |
| ASSERT_FALSE(result->is_error()); |
| |
| test_device()->AddPeer(std::make_unique<FakePeer>(kTestAddr, pw_dispatcher())); |
| test_device()->ConnectLowEnergy(kTestAddr); |
| RunLoopUntilIdle(); |
| |
| ASSERT_TRUE(peer.has_id()); |
| ASSERT_TRUE(conn.is_valid()); |
| |
| auto connected_id = bt::PeerId(peer.id().value); |
| const bt::gap::LowEnergyConnectionHandle* conn_handle = |
| server()->FindConnectionForTesting(connected_id); |
| |
| ASSERT_TRUE(conn_handle); |
| ASSERT_EQ(conn_handle->bondable_mode(), bt::sm::BondableMode::Bondable); |
| } |
| |
| TEST_F(LowEnergyPeripheralServerTest, AdvertiseEmptyConnectionOptionsConnectsBondable) { |
| fble::AdvertisedPeripheralHandle adv_peripheral_handle; |
| FakeAdvertisedPeripheral adv_peripheral_server(adv_peripheral_handle.NewRequest()); |
| |
| fble::AdvertisingParameters params; |
| fble::ConnectionOptions conn_opts; |
| params.set_connection_options(std::move(conn_opts)); |
| |
| std::optional<fpromise::result<void, fble::PeripheralError>> result; |
| server()->Advertise(std::move(params), std::move(adv_peripheral_handle), |
| [&](auto cb_result) { result = std::move(cb_result); }); |
| RunLoopUntilIdle(); |
| EXPECT_FALSE(result.has_value()); |
| |
| test_device()->AddPeer(std::make_unique<FakePeer>(kTestAddr, pw_dispatcher())); |
| test_device()->ConnectLowEnergy(kTestAddr); |
| RunLoopUntilIdle(); |
| auto connected_id = adv_peripheral_server.last_connected_peer(); |
| ASSERT_TRUE(connected_id); |
| |
| const bt::gap::LowEnergyConnectionHandle* conn_handle = |
| server()->FindConnectionForTesting(*connected_id); |
| ASSERT_TRUE(conn_handle); |
| ASSERT_EQ(conn_handle->bondable_mode(), bt::sm::BondableMode::Bondable); |
| |
| adv_peripheral_server.Unbind(); |
| RunLoopUntilIdle(); |
| EXPECT_TRUE(result.has_value()); |
| } |
| |
| TEST_P(BoolParam, AdvertiseBondableOrNonBondableConnectsBondableOrNonBondable) { |
| const bool bondable = GetParam(); |
| |
| fble::AdvertisedPeripheralHandle adv_peripheral_handle; |
| FakeAdvertisedPeripheral adv_peripheral_server(adv_peripheral_handle.NewRequest()); |
| |
| fble::AdvertisingParameters params; |
| fble::ConnectionOptions conn_opts; |
| conn_opts.set_bondable_mode(bondable); |
| params.set_connection_options(std::move(conn_opts)); |
| |
| std::optional<fpromise::result<void, fble::PeripheralError>> result; |
| server()->Advertise(std::move(params), std::move(adv_peripheral_handle), |
| [&](auto cb_result) { result = std::move(cb_result); }); |
| RunLoopUntilIdle(); |
| EXPECT_FALSE(result.has_value()); |
| |
| test_device()->AddPeer(std::make_unique<FakePeer>(kTestAddr, pw_dispatcher())); |
| test_device()->ConnectLowEnergy(kTestAddr); |
| RunLoopUntilIdle(); |
| auto connected_id = adv_peripheral_server.last_connected_peer(); |
| ASSERT_TRUE(connected_id); |
| |
| const bt::gap::LowEnergyConnectionHandle* conn_handle = |
| server()->FindConnectionForTesting(*connected_id); |
| ASSERT_TRUE(conn_handle); |
| EXPECT_EQ(conn_handle->bondable_mode(), |
| bondable ? bt::sm::BondableMode::Bondable : bt::sm::BondableMode::NonBondable); |
| |
| adv_peripheral_server.Unbind(); |
| RunLoopUntilIdle(); |
| } |
| |
| TEST_P(BoolParam, StartAdvertisingBondableOrNonBondableConnectsBondableOrNonBondable) { |
| const bool bondable = GetParam(); |
| |
| fble::Peer peer; |
| // `conn` is stored so the bondable mode of the connection resulting from `OnPeerConnected` can |
| // be checked. The connection would otherwise be dropped immediately after `ConnectLowEnergy`. |
| fidl::InterfaceHandle<fble::Connection> conn; |
| auto peer_connected_cb = [&](auto cb_peer, auto cb_conn) { |
| peer = std::move(cb_peer); |
| conn = std::move(cb_conn); |
| }; |
| SetOnPeerConnectedCallback(peer_connected_cb); |
| |
| fble::AdvertisingParameters params; |
| fble::ConnectionOptions conn_opts; |
| conn_opts.set_bondable_mode(bondable); |
| params.set_connection_options(std::move(conn_opts)); |
| |
| FidlAdvHandle token; |
| |
| std::optional<fpromise::result<void, fble::PeripheralError>> result; |
| server()->StartAdvertising(std::move(params), token.NewRequest(), |
| [&](auto cb_result) { result = std::move(cb_result); }); |
| RunLoopUntilIdle(); |
| ASSERT_TRUE(result); |
| ASSERT_FALSE(result->is_error()); |
| |
| test_device()->AddPeer(std::make_unique<FakePeer>(kTestAddr, pw_dispatcher())); |
| test_device()->ConnectLowEnergy(kTestAddr); |
| RunLoopUntilIdle(); |
| |
| ASSERT_TRUE(peer.has_id()); |
| ASSERT_TRUE(conn.is_valid()); |
| |
| auto connected_id = bt::PeerId(peer.id().value); |
| const bt::gap::LowEnergyConnectionHandle* conn_handle = |
| server()->FindConnectionForTesting(connected_id); |
| |
| ASSERT_TRUE(conn_handle); |
| EXPECT_EQ(conn_handle->bondable_mode(), |
| bondable ? bt::sm::BondableMode::Bondable : bt::sm::BondableMode::NonBondable); |
| } |
| |
| TEST_F(LowEnergyPeripheralServerTest, RestartStartAdvertisingDuringInboundConnKeepsNewAdvAlive) { |
| fble::Peer peer; |
| // `conn` is stored so that the connection is not dropped immediately after connection. |
| fidl::InterfaceHandle<fble::Connection> conn; |
| auto peer_connected_cb = [&](auto cb_peer, auto cb_conn) { |
| peer = std::move(cb_peer); |
| conn = std::move(cb_conn); |
| }; |
| SetOnPeerConnectedCallback(peer_connected_cb); |
| |
| FidlAdvHandle first_token, second_token; |
| |
| fble::AdvertisingParameters params; |
| params.set_connectable(true); |
| std::optional<fpromise::result<void, fble::PeripheralError>> result; |
| server()->StartAdvertising(std::move(params), first_token.NewRequest(), |
| [&](auto cb_result) { result = std::move(cb_result); }); |
| RunLoopUntilIdle(); |
| ASSERT_TRUE(result.has_value()); |
| EXPECT_TRUE(result->is_ok()); |
| |
| fit::closure complete_interrogation; |
| // Hang interrogation so we can control when the inbound connection procedure completes. |
| test_device()->pause_responses_for_opcode( |
| bt::hci_spec::kReadRemoteVersionInfo, |
| [&](fit::closure trigger) { complete_interrogation = std::move(trigger); }); |
| |
| test_device()->AddPeer(std::make_unique<FakePeer>(kTestAddr, pw_dispatcher())); |
| test_device()->ConnectLowEnergy(kTestAddr); |
| RunLoopUntilIdle(); |
| |
| EXPECT_FALSE(peer.has_id()); |
| EXPECT_FALSE(conn.is_valid()); |
| // test_device()->ConnectLowEnergy caused interrogation as part of the inbound GAP connection |
| // process, so this closure should be filled in. |
| ASSERT_TRUE(complete_interrogation); |
| |
| // Hang the SetAdvertisingParameters HCI command so we can invoke the advertising status callback |
| // after connection completion. |
| fit::closure complete_start_advertising; |
| test_device()->pause_responses_for_opcode( |
| bt::hci_spec::kLESetAdvertisingParameters, |
| [&](fit::closure trigger) { complete_start_advertising = std::move(trigger); }); |
| |
| // Restart advertising during inbound connection, simulating the race seen in |
| // https://fxbug.dev/42152329. |
| result = std::nullopt; |
| server()->StartAdvertising(fble::AdvertisingParameters{}, second_token.NewRequest(), |
| [&](auto cb_result) { result = std::move(cb_result); }); |
| RunLoopUntilIdle(); |
| ASSERT_TRUE(complete_start_advertising); |
| // Advertising shouldn't complete until we trigger the above closure |
| EXPECT_FALSE(result.has_value()); |
| // The first AdvertisingHandle should be closed, as we have started a second advertisement. |
| EXPECT_TRUE(IsChannelPeerClosed(first_token.channel())); |
| |
| // Allow interrogation to complete, enabling the connection process to proceed. |
| complete_interrogation(); |
| RunLoopUntilIdle(); |
| // Connection should have been dropped after completing because first advertisement was canceled. |
| EXPECT_FALSE(peer.has_id()); |
| EXPECT_FALSE(conn.is_valid()); |
| |
| // Allow second StartAdvertising to complete. |
| complete_start_advertising(); |
| RunLoopUntilIdle(); |
| ASSERT_TRUE(result.has_value()); |
| EXPECT_TRUE(result->is_ok()); |
| // The second advertising handle should still be active. |
| EXPECT_FALSE(IsChannelPeerClosed(second_token.channel())); |
| } |
| |
| // Ensures that a connection to a canceled advertisement received after the advertisement is |
| // canceled doesn't end or get sent to a new AdvertisedPeripheral. |
| TEST_F(LowEnergyPeripheralServerTest, RestartAdvertiseDuringInboundConnKeepsNewAdvAlive) { |
| fble::AdvertisedPeripheralHandle adv_peripheral_handle_0; |
| FakeAdvertisedPeripheral adv_peripheral_server_0(adv_peripheral_handle_0.NewRequest()); |
| |
| fble::AdvertisingParameters params; |
| params.set_connectable(true); |
| std::optional<fpromise::result<void, fble::PeripheralError>> result; |
| server()->Advertise(std::move(params), std::move(adv_peripheral_handle_0), |
| [&](auto cb_result) { result = std::move(cb_result); }); |
| RunLoopUntilIdle(); |
| EXPECT_FALSE(result.has_value()); |
| |
| fit::closure complete_interrogation; |
| // Hang interrogation so we can control when the inbound connection procedure completes. |
| test_device()->pause_responses_for_opcode( |
| bt::hci_spec::kReadRemoteVersionInfo, |
| [&](fit::closure trigger) { complete_interrogation = std::move(trigger); }); |
| |
| test_device()->AddPeer(std::make_unique<FakePeer>(kTestAddr, pw_dispatcher())); |
| test_device()->ConnectLowEnergy(kTestAddr); |
| RunLoopUntilIdle(); |
| EXPECT_FALSE(adv_peripheral_server_0.last_connected_peer()); |
| // test_device()->ConnectLowEnergy caused interrogation as part of the inbound GAP connection |
| // process, so this closure should be filled in. |
| ASSERT_TRUE(complete_interrogation); |
| |
| // Cancel the first advertisement. |
| adv_peripheral_server_0.Unbind(); |
| RunLoopUntilIdle(); |
| ASSERT_TRUE(result.has_value()); |
| EXPECT_TRUE(result->is_ok()); |
| |
| // Hang the SetAdvertisingParameters HCI command so we can invoke the advertising status callback |
| // of the second advertising request after connection completion. |
| fit::closure complete_start_advertising; |
| test_device()->pause_responses_for_opcode( |
| bt::hci_spec::kLESetAdvertisingParameters, |
| [&](fit::closure trigger) { complete_start_advertising = std::move(trigger); }); |
| |
| // Restart advertising during inbound connection, simulating the race seen in |
| // https://fxbug.dev/42152329. |
| fble::AdvertisedPeripheralHandle adv_peripheral_handle_1; |
| FakeAdvertisedPeripheral adv_peripheral_server_1(adv_peripheral_handle_1.NewRequest()); |
| bool server_1_closed = false; |
| adv_peripheral_server_1.set_error_handler([&](auto) { server_1_closed = true; }); |
| result = std::nullopt; |
| server()->Advertise(fble::AdvertisingParameters{}, std::move(adv_peripheral_handle_1), |
| [&](auto cb_result) { result = std::move(cb_result); }); |
| RunLoopUntilIdle(); |
| ASSERT_TRUE(complete_start_advertising); |
| EXPECT_FALSE(result.has_value()); |
| |
| // Allow interrogation to complete, enabling the connection process to proceed. |
| complete_interrogation(); |
| RunLoopUntilIdle(); |
| // The connection should have been dropped. |
| EXPECT_FALSE(adv_peripheral_server_1.last_connected_peer()); |
| EXPECT_FALSE(adv_peripheral_server_0.last_connected_peer()); |
| |
| // Allow second Advertise to complete. |
| complete_start_advertising(); |
| RunLoopUntilIdle(); |
| EXPECT_FALSE(result.has_value()); |
| EXPECT_FALSE(server_1_closed); |
| EXPECT_FALSE(adv_peripheral_server_1.last_connected_peer()); |
| |
| adv_peripheral_server_1.Unbind(); |
| RunLoopUntilIdle(); |
| EXPECT_TRUE(result.has_value()); |
| } |
| |
| TEST_F(LowEnergyPeripheralServerTestFakeAdapter, StartAdvertisingWithIncludeTxPowerSetToTrue) { |
| fble::AdvertisingParameters params; |
| fble::AdvertisingData adv_data; |
| adv_data.set_include_tx_power_level(true); |
| params.set_data(std::move(adv_data)); |
| |
| FidlAdvHandle token; |
| |
| server()->StartAdvertising(std::move(params), token.NewRequest(), [&](auto) {}); |
| RunLoopUntilIdle(); |
| ASSERT_EQ(adapter()->fake_le()->registered_advertisements().size(), 1u); |
| EXPECT_TRUE( |
| adapter()->fake_le()->registered_advertisements().begin()->second.include_tx_power_level); |
| } |
| |
| TEST_F(LowEnergyPeripheralServerTestFakeAdapter, AdvertiseWithIncludeTxPowerSetToTrue) { |
| fble::AdvertisingParameters params; |
| fble::AdvertisingData adv_data; |
| adv_data.set_include_tx_power_level(true); |
| params.set_data(std::move(adv_data)); |
| |
| fble::AdvertisedPeripheralHandle adv_peripheral_handle; |
| FakeAdvertisedPeripheral adv_peripheral_server(adv_peripheral_handle.NewRequest()); |
| |
| std::optional<fpromise::result<void, fble::PeripheralError>> result; |
| server()->Advertise(std::move(params), std::move(adv_peripheral_handle), |
| [&](auto cb_result) { result = std::move(cb_result); }); |
| RunLoopUntilIdle(); |
| ASSERT_EQ(adapter()->fake_le()->registered_advertisements().size(), 1u); |
| EXPECT_TRUE( |
| adapter()->fake_le()->registered_advertisements().begin()->second.include_tx_power_level); |
| |
| adv_peripheral_server.Unbind(); |
| RunLoopUntilIdle(); |
| } |
| |
| TEST_F(LowEnergyPeripheralServerTestFakeAdapter, AdvertiseInvalidAdvData) { |
| fble::AdvertisingData adv_data; |
| adv_data.set_name(std::string(bt::kMaxNameLength + 1, '*')); |
| fble::AdvertisingParameters params; |
| params.set_data(std::move(adv_data)); |
| |
| fidl::InterfaceHandle<fble::AdvertisedPeripheral> advertised_peripheral_client; |
| fidl::InterfaceRequest<fble::AdvertisedPeripheral> advertised_peripheral_server = |
| advertised_peripheral_client.NewRequest(); |
| |
| std::optional<fpromise::result<void, fble::PeripheralError>> adv_result; |
| server()->Advertise(std::move(params), std::move(advertised_peripheral_client), |
| [&](auto result) { adv_result = std::move(result); }); |
| RunLoopUntilIdle(); |
| EXPECT_EQ(adapter()->fake_le()->registered_advertisements().size(), 0u); |
| ASSERT_TRUE(adv_result); |
| EXPECT_TRUE(adv_result.value().is_error()); |
| EXPECT_EQ(adv_result->error(), fble::PeripheralError::INVALID_PARAMETERS); |
| } |
| |
| TEST_F(LowEnergyPeripheralServerTestFakeAdapter, AdvertiseInvalidScanResponseData) { |
| fble::AdvertisingData adv_data; |
| adv_data.set_name(std::string(bt::kMaxNameLength + 1, '*')); |
| fble::AdvertisingParameters params; |
| params.set_scan_response(std::move(adv_data)); |
| |
| fidl::InterfaceHandle<fble::AdvertisedPeripheral> advertised_peripheral_client; |
| fidl::InterfaceRequest<fble::AdvertisedPeripheral> advertised_peripheral_server = |
| advertised_peripheral_client.NewRequest(); |
| |
| std::optional<fpromise::result<void, fble::PeripheralError>> adv_result; |
| server()->Advertise(std::move(params), std::move(advertised_peripheral_client), |
| [&](auto result) { adv_result = std::move(result); }); |
| RunLoopUntilIdle(); |
| EXPECT_EQ(adapter()->fake_le()->registered_advertisements().size(), 0u); |
| ASSERT_TRUE(adv_result); |
| EXPECT_TRUE(adv_result.value().is_error()); |
| EXPECT_EQ(adv_result->error(), fble::PeripheralError::INVALID_PARAMETERS); |
| } |
| |
| TEST_F(LowEnergyPeripheralServerTest, AdvertiseAndReceiveTwoConnections) { |
| fble::AdvertisedPeripheralHandle adv_peripheral_handle; |
| FakeAdvertisedPeripheral adv_peripheral_server(adv_peripheral_handle.NewRequest()); |
| |
| fble::AdvertisingParameters params; |
| fble::ConnectionOptions conn_opts; |
| params.set_connection_options(std::move(conn_opts)); |
| |
| std::optional<fpromise::result<void, fble::PeripheralError>> adv_result; |
| server()->Advertise(std::move(params), std::move(adv_peripheral_handle), |
| [&](auto cb_result) { adv_result = std::move(cb_result); }); |
| RunLoopUntilIdle(); |
| EXPECT_FALSE(adv_result.has_value()); |
| |
| test_device()->AddPeer(std::make_unique<FakePeer>(kTestAddr, pw_dispatcher())); |
| test_device()->ConnectLowEnergy(kTestAddr); |
| RunLoopUntilIdle(); |
| ASSERT_TRUE(adv_peripheral_server.last_connected_peer()); |
| |
| // Sending response to first connection should restart advertising. |
| adv_peripheral_server.connections()[0].callback(); |
| RunLoopUntilIdle(); |
| |
| test_device()->AddPeer(std::make_unique<FakePeer>(kTestAddr2, pw_dispatcher())); |
| test_device()->ConnectLowEnergy(kTestAddr2); |
| RunLoopUntilIdle(); |
| ASSERT_EQ(adv_peripheral_server.connections().size(), 2u); |
| |
| adv_peripheral_server.Unbind(); |
| RunLoopUntilIdle(); |
| ASSERT_TRUE(adv_result.has_value()); |
| EXPECT_TRUE(adv_result->is_ok()); |
| } |
| |
| TEST_F(LowEnergyPeripheralServerTest, AdvertiseCanceledBeforeAdvertisingStarts) { |
| fit::closure send_adv_enable_response; |
| test_device()->pause_responses_for_opcode( |
| bt::hci_spec::kLESetAdvertisingEnable, |
| [&](fit::closure send_rsp) { send_adv_enable_response = std::move(send_rsp); }); |
| |
| fble::AdvertisedPeripheralHandle adv_peripheral_handle; |
| FakeAdvertisedPeripheral adv_peripheral_server(adv_peripheral_handle.NewRequest()); |
| |
| fble::AdvertisingParameters params; |
| std::optional<fpromise::result<void, fble::PeripheralError>> adv_result; |
| server()->Advertise(std::move(params), std::move(adv_peripheral_handle), |
| [&](auto cb_result) { adv_result = std::move(cb_result); }); |
| RunLoopUntilIdle(); |
| ASSERT_TRUE(send_adv_enable_response); |
| |
| adv_peripheral_server.Unbind(); |
| RunLoopUntilIdle(); |
| send_adv_enable_response(); |
| RunLoopUntilIdle(); |
| ASSERT_TRUE(adv_result.has_value()); |
| EXPECT_TRUE(adv_result->is_ok()); |
| } |
| |
| TEST_P(BoolParam, AdvertiseTwiceCausesSecondToFail) { |
| fble::AdvertisedPeripheralHandle adv_peripheral_handle_0; |
| FakeAdvertisedPeripheral adv_peripheral_server_0(adv_peripheral_handle_0.NewRequest()); |
| bool adv_peripheral_server_0_closed = false; |
| adv_peripheral_server_0.set_error_handler([&](auto) { adv_peripheral_server_0_closed = true; }); |
| |
| fble::AdvertisingParameters params_0; |
| fble::ConnectionOptions conn_opts; |
| params_0.set_connection_options(std::move(conn_opts)); |
| |
| std::optional<fpromise::result<void, fble::PeripheralError>> adv_result_0; |
| server()->Advertise(std::move(params_0), std::move(adv_peripheral_handle_0), |
| [&](auto cb_result) { adv_result_0 = std::move(cb_result); }); |
| |
| // Test both with and without running the loop between Advertise requests. |
| if (GetParam()) { |
| RunLoopUntilIdle(); |
| EXPECT_FALSE(adv_result_0.has_value()); |
| EXPECT_FALSE(adv_peripheral_server_0_closed); |
| } |
| |
| fble::AdvertisedPeripheralHandle adv_peripheral_handle_1; |
| FakeAdvertisedPeripheral adv_peripheral_server_1(adv_peripheral_handle_1.NewRequest()); |
| bool adv_peripheral_server_1_closed = false; |
| adv_peripheral_server_1.set_error_handler([&](auto) { adv_peripheral_server_1_closed = true; }); |
| fble::AdvertisingParameters params_1; |
| std::optional<fpromise::result<void, fble::PeripheralError>> adv_result_1; |
| server()->Advertise(std::move(params_1), std::move(adv_peripheral_handle_1), |
| [&](auto cb_result) { adv_result_1 = std::move(cb_result); }); |
| RunLoopUntilIdle(); |
| EXPECT_FALSE(adv_result_0.has_value()); |
| EXPECT_FALSE(adv_peripheral_server_0_closed); |
| ASSERT_TRUE(adv_result_1.has_value()); |
| ASSERT_TRUE(adv_result_1->is_error()); |
| EXPECT_EQ(adv_result_1->error(), fble::PeripheralError::FAILED); |
| EXPECT_TRUE(adv_peripheral_server_1_closed); |
| |
| // Server 0 should still receive connections. |
| test_device()->AddPeer(std::make_unique<FakePeer>(kTestAddr, pw_dispatcher())); |
| test_device()->ConnectLowEnergy(kTestAddr); |
| RunLoopUntilIdle(); |
| EXPECT_TRUE(adv_peripheral_server_0.last_connected_peer()); |
| |
| adv_peripheral_server_0.Unbind(); |
| RunLoopUntilIdle(); |
| ASSERT_TRUE(adv_result_0.has_value()); |
| EXPECT_TRUE(adv_result_0->is_ok()); |
| } |
| |
| TEST_F(LowEnergyPeripheralServerTest, CallAdvertiseTwiceSequentiallyBothSucceed) { |
| fble::AdvertisedPeripheralHandle adv_peripheral_handle_0; |
| FakeAdvertisedPeripheral adv_peripheral_server_0(adv_peripheral_handle_0.NewRequest()); |
| fble::AdvertisingParameters params_0; |
| std::optional<fpromise::result<void, fble::PeripheralError>> adv_result_0; |
| server()->Advertise(std::move(params_0), std::move(adv_peripheral_handle_0), |
| [&](auto cb_result) { adv_result_0 = std::move(cb_result); }); |
| RunLoopUntilIdle(); |
| EXPECT_FALSE(adv_result_0.has_value()); |
| |
| adv_peripheral_server_0.Unbind(); |
| RunLoopUntilIdle(); |
| ASSERT_TRUE(adv_result_0.has_value()); |
| EXPECT_TRUE(adv_result_0->is_ok()); |
| |
| fble::AdvertisedPeripheralHandle adv_peripheral_handle_1; |
| FakeAdvertisedPeripheral adv_peripheral_server_1(adv_peripheral_handle_1.NewRequest()); |
| |
| fble::AdvertisingParameters params_1; |
| fble::ConnectionOptions conn_opts; |
| params_1.set_connection_options(std::move(conn_opts)); |
| |
| std::optional<fpromise::result<void, fble::PeripheralError>> adv_result_1; |
| server()->Advertise(std::move(params_1), std::move(adv_peripheral_handle_1), |
| [&](auto cb_result) { adv_result_1 = std::move(cb_result); }); |
| RunLoopUntilIdle(); |
| EXPECT_FALSE(adv_result_1.has_value()); |
| |
| // Server 1 should receive connections. |
| test_device()->AddPeer(std::make_unique<FakePeer>(kTestAddr, pw_dispatcher())); |
| test_device()->ConnectLowEnergy(kTestAddr); |
| RunLoopUntilIdle(); |
| EXPECT_TRUE(adv_peripheral_server_1.last_connected_peer()); |
| |
| adv_peripheral_server_1.Unbind(); |
| RunLoopUntilIdle(); |
| ASSERT_TRUE(adv_result_1.has_value()); |
| EXPECT_TRUE(adv_result_1->is_ok()); |
| } |
| |
| TEST_F(LowEnergyPeripheralServerTest, PeerDisconnectClosesConnection) { |
| fble::AdvertisedPeripheralHandle adv_peripheral_handle; |
| FakeAdvertisedPeripheral adv_peripheral_server(adv_peripheral_handle.NewRequest()); |
| |
| fble::AdvertisingParameters params; |
| fble::ConnectionOptions conn_opts; |
| params.set_connection_options(std::move(conn_opts)); |
| |
| std::optional<fpromise::result<void, fble::PeripheralError>> adv_result; |
| server()->Advertise(std::move(params), std::move(adv_peripheral_handle), |
| [&](auto cb_result) { adv_result = std::move(cb_result); }); |
| RunLoopUntilIdle(); |
| EXPECT_FALSE(adv_result.has_value()); |
| |
| test_device()->AddPeer(std::make_unique<FakePeer>(kTestAddr, pw_dispatcher())); |
| test_device()->ConnectLowEnergy(kTestAddr); |
| RunLoopUntilIdle(); |
| EXPECT_TRUE(adv_peripheral_server.last_connected_peer()); |
| fidl::InterfacePtr<fble::Connection> connection = |
| adv_peripheral_server.connections()[0].connection.Bind(); |
| bool connection_closed = false; |
| connection.set_error_handler([&](auto) { connection_closed = true; }); |
| EXPECT_FALSE(connection_closed); |
| RunLoopUntilIdle(); |
| |
| adv_peripheral_server.Unbind(); |
| RunLoopUntilIdle(); |
| ASSERT_TRUE(adv_result.has_value()); |
| EXPECT_TRUE(adv_result->is_ok()); |
| EXPECT_FALSE(connection_closed); |
| |
| test_device()->Disconnect(kTestAddr); |
| RunLoopUntilIdle(); |
| EXPECT_TRUE(connection_closed); |
| } |
| |
| TEST_F(LowEnergyPeripheralServerTest, IncomingConnectionFailureContinuesAdvertising) { |
| fble::AdvertisedPeripheralHandle adv_peripheral_handle; |
| FakeAdvertisedPeripheral adv_peripheral_server(adv_peripheral_handle.NewRequest()); |
| |
| fble::AdvertisingParameters params; |
| fble::ConnectionOptions conn_opts; |
| params.set_connection_options(std::move(conn_opts)); |
| |
| std::optional<fpromise::result<void, fble::PeripheralError>> adv_result; |
| server()->Advertise(std::move(params), std::move(adv_peripheral_handle), |
| [&](auto cb_result) { adv_result = std::move(cb_result); }); |
| RunLoopUntilIdle(); |
| EXPECT_FALSE(adv_result.has_value()); |
| |
| // Cause peer interrogation to fail. This will result in a connection error status to be |
| // received. Advertising should be immediately resumed, allowing future connections. |
| test_device()->SetDefaultCommandStatus( |
| bt::hci_spec::kReadRemoteVersionInfo, |
| pw::bluetooth::emboss::StatusCode::UNSUPPORTED_REMOTE_FEATURE); |
| |
| test_device()->AddPeer(std::make_unique<FakePeer>(kTestAddr, pw_dispatcher())); |
| test_device()->ConnectLowEnergy(kTestAddr); |
| RunLoopUntilIdle(); |
| EXPECT_FALSE(adv_peripheral_server.last_connected_peer()); |
| EXPECT_FALSE(adv_result.has_value()); |
| |
| // Allow next interrogation to succeed. |
| test_device()->ClearDefaultCommandStatus(bt::hci_spec::kReadRemoteVersionInfo); |
| |
| test_device()->AddPeer(std::make_unique<FakePeer>(kTestAddr, pw_dispatcher())); |
| test_device()->ConnectLowEnergy(kTestAddr); |
| RunLoopUntilIdle(); |
| EXPECT_TRUE(adv_peripheral_server.last_connected_peer()); |
| EXPECT_FALSE(adv_result.has_value()); |
| |
| adv_peripheral_server.Unbind(); |
| RunLoopUntilIdle(); |
| EXPECT_TRUE(adv_result.has_value()); |
| } |
| |
| INSTANTIATE_TEST_SUITE_P(LowEnergyPeripheralServerTest, BoolParam, ::testing::Bool()); |
| |
| } // namespace |
| } // namespace bthost |