blob: e1069e99536d969ac58c4c84b04099fc783e2439 [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/common/byte_buffer.h"
#include "src/connectivity/bluetooth/core/bt-host/common/test_helpers.h"
#include "src/connectivity/bluetooth/core/bt-host/gap/low_energy_advertising_manager.h"
#include "src/connectivity/bluetooth/core/bt-host/gap/low_energy_connection_manager.h"
#include "src/connectivity/bluetooth/core/bt-host/testing/fake_peer.h"
namespace bthost {
namespace {
namespace fble = fuchsia::bluetooth::le;
const bt::DeviceAddress kTestAddr(bt::DeviceAddress::Type::kLEPublic, {0x01, 0, 0, 0, 0, 0});
using bt::testing::FakePeer;
using FidlAdvHandle = fidl::InterfaceHandle<fble::AdvertisingHandle>;
class FIDL_LowEnergyPeripheralServerTest : public bthost::testing::AdapterTestFixture {
public:
FIDL_LowEnergyPeripheralServerTest() = default;
~FIDL_LowEnergyPeripheralServerTest() override = default;
void SetUp() override {
AdapterTestFixture::SetUp();
// Create a LowEnergyPeripheralServer and bind it to a local client.
fidl::InterfaceHandle<fble::Peripheral> handle;
server_ = std::make_unique<LowEnergyPeripheralServer>(adapter(), 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_;
DISALLOW_COPY_AND_ASSIGN_ALLOW_MOVE(FIDL_LowEnergyPeripheralServerTest);
};
// Tests that aborting a StartAdvertising command sequence does not cause a crash in successive
// requests.
TEST_F(FIDL_LowEnergyPeripheralServerTest, StartAdvertisingWhilePendingDoesNotCrash) {
fble::AdvertisingParameters params1, params2, params3;
FidlAdvHandle token1, token2, token3;
std::optional<fit::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(FIDL_LowEnergyPeripheralServerTest,
StartAdvertisingWhilePendingDoesNotCrashWithControllerError) {
test_device()->SetDefaultResponseStatus(bt::hci::kLESetAdvertisingEnable,
bt::hci::StatusCode::kCommandDisallowed);
fble::AdvertisingParameters params1, params2, params3, params4;
FidlAdvHandle token1, token2, token3, token4;
std::optional<fit::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::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(FIDL_LowEnergyPeripheralServerTest, AdvertiseNoConnectionRelatedParamsNoConnection) {
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<fit::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));
test_device()->ConnectLowEnergy(kTestAddr);
RunLoopUntilIdle();
ASSERT_FALSE(peer.has_id());
ASSERT_FALSE(conn.is_valid());
}
TEST_F(FIDL_LowEnergyPeripheralServerTest, AdvertiseConnectableParameterTrueConnectsBondable) {
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<fit::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));
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(FIDL_LowEnergyPeripheralServerTest, AdvertiseEmptyConnectionOptionsConnectsBondable) {
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<fit::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));
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(FIDL_LowEnergyPeripheralServerTest, AdvertiseBondableConnectsBondable) {
fble::Peer peer;
// This is needed to hold onto the connection sent by the OnPeerConnected event. Otherwise the
// resulting connection is dropped immediately after the call to test_device()->ConnectLowEnergy
// completes and we cannot check whether it is bondable at the end of the test.
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(true);
params.set_connection_options(std::move(conn_opts));
FidlAdvHandle token;
std::optional<fit::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));
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(FIDL_LowEnergyPeripheralServerTest, AdvertiseNonBondableConnectsNonBondable) {
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(false);
params.set_connection_options(std::move(conn_opts));
FidlAdvHandle token;
std::optional<fit::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));
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::NonBondable);
}
TEST_F(FIDL_LowEnergyPeripheralServerTest, RestartAdvDuringInboundConnKeepsNewAdvAlive) {
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<fit::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::kReadRemoteVersionInfo,
[&](fit::closure trigger) { complete_interrogation = std::move(trigger); });
test_device()->AddPeer(std::make_unique<FakePeer>(kTestAddr));
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::kLESetAdvertisingParameters,
[&](fit::closure trigger) { complete_start_advertising = std::move(trigger); });
// Restart advertising during inbound connection, simulating the race seen in fxbug.dev/72825.
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(bt::IsChannelPeerClosed(first_token.channel()));
// Allow interrogation to complete, enabling the connection process to proceed.
complete_interrogation();
RunLoopUntilIdle();
// Now peer should be connected
EXPECT_TRUE(peer.has_id());
EXPECT_TRUE(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(bt::IsChannelPeerClosed(second_token.channel()));
}
} // namespace
} // namespace bthost