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