blob: 5c583601a336fc7fd803b4da161b12f363024dc3 [file] [log] [blame]
// Copyright 2017 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/hci/extended_low_energy_scanner.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/hci/fake_local_address_delegate.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/hci/legacy_low_energy_scanner.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/testing/controller_test.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/testing/fake_controller.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/testing/fake_peer.h"
// LowEnergyScanner has many potential subclasses (e.g. LegacyLowEnergyScanner,
// ExtendedLowEnergyScanner, etc). The unique features of these subclasses are
// tested individually in their own unittest files. However, there are some
// common features that all LowEnergyScanners should follow. This test file
// implements a type parameterized test to exercise those common features.
//
// If you add a new subclass of LowEnergyScanner in the future, make sure to add
// its type to the list of types below (in the TYPED_TEST_SUITE) so that its
// common features are exercised as well.
namespace bt::hci {
using bt::testing::FakeController;
using bt::testing::FakePeer;
using TestingBase = bt::testing::FakeDispatcherControllerTest<FakeController>;
constexpr pw::chrono::SystemClock::duration kScanPeriod =
std::chrono::seconds(10);
constexpr pw::chrono::SystemClock::duration kPwScanPeriod =
std::chrono::seconds(10);
constexpr pw::chrono::SystemClock::duration kScanResponseTimeout =
std::chrono::seconds(2);
constexpr pw::chrono::SystemClock::duration kPwScanResponseTimeout =
std::chrono::seconds(2);
// The unit tests below assume that the scan period is longer than the scan
// response timeout when exercising timeout expiration.
static_assert(kScanResponseTimeout < kScanPeriod,
"expected a smaller scan response timeout for testing");
const StaticByteBuffer kPlainAdvDataBytes('T', 'e', 's', 't');
const StaticByteBuffer kPlainScanRspBytes('D', 'a', 't', 'a');
constexpr char kPlainAdvData[] = "Test";
constexpr char kPlainScanRsp[] = "Data";
constexpr char kAdvDataAndScanRsp[] = "TestData";
const DeviceAddress kPublicAddress1(DeviceAddress::Type::kLEPublic, {1});
const DeviceAddress kPublicAddress2(DeviceAddress::Type::kLEPublic, {2});
const DeviceAddress kRandomAddress1(DeviceAddress::Type::kLERandom, {3});
const DeviceAddress kRandomAddress2(DeviceAddress::Type::kLERandom, {4});
const DeviceAddress kRandomAddress3(DeviceAddress::Type::kLERandom, {5});
const DeviceAddress kRandomAddress4(DeviceAddress::Type::kLERandom, {6});
template <typename T>
class LowEnergyScannerTest : public TestingBase,
public LowEnergyScanner::Delegate {
public:
LowEnergyScannerTest() = default;
~LowEnergyScannerTest() override = default;
protected:
void SetUp() override {
TestingBase::SetUp();
FakeController::Settings settings;
settings.ApplyLegacyLEConfig();
this->test_device()->set_settings(settings);
scanner_ = std::unique_ptr<T>(CreateScannerInternal());
scanner_->set_delegate(this);
}
void TearDown() override {
scanner_ = nullptr;
this->test_device()->Stop();
TestingBase::TearDown();
}
template <bool same = std::is_same_v<T, ExtendedLowEnergyScanner>>
std::enable_if_t<same, ExtendedLowEnergyScanner>* CreateScannerInternal() {
return new ExtendedLowEnergyScanner(
fake_address_delegate(), transport()->GetWeakPtr(), dispatcher());
}
template <bool same = std::is_same_v<T, LegacyLowEnergyScanner>>
std::enable_if_t<same, LegacyLowEnergyScanner>* CreateScannerInternal() {
return new LegacyLowEnergyScanner(
fake_address_delegate(), transport()->GetWeakPtr(), dispatcher());
}
using PeerFoundCallback = fit::function<void(const LowEnergyScanResult&)>;
void set_peer_found_callback(PeerFoundCallback cb) {
peer_found_cb_ = std::move(cb);
}
using DirectedAdvCallback = fit::function<void(const LowEnergyScanResult&)>;
void set_directed_adv_callback(DirectedAdvCallback cb) {
directed_adv_cb_ = std::move(cb);
}
bool StartScan(bool active,
pw::chrono::SystemClock::duration period =
LowEnergyScanner::kPeriodInfinite) {
LowEnergyScanner::ScanOptions options{
.active = active,
.filter_duplicates = true,
.period = period,
.scan_response_timeout = kPwScanResponseTimeout};
return scanner()->StartScan(
options, [this](auto status) { last_scan_status_ = status; });
}
// LowEnergyScanner::Delegate override:
void OnPeerFound(const LowEnergyScanResult& result) override {
if (peer_found_cb_) {
peer_found_cb_(result);
}
}
// LowEnergyScanner::Observer override:
void OnDirectedAdvertisement(const LowEnergyScanResult& result) override {
if (directed_adv_cb_) {
directed_adv_cb_(result);
}
}
// Adds 6 fake peers using kAddress[0-5] above.
void AddFakePeers() {
// Generates ADV_IND
auto fake_peer =
std::make_unique<FakePeer>(kPublicAddress1, dispatcher(), true, true);
fake_peer->set_advertising_data(kPlainAdvDataBytes);
fake_peer->set_scan_response(kPlainScanRspBytes);
test_device()->AddPeer(std::move(fake_peer));
// Generates ADV_SCAN_IND
fake_peer =
std::make_unique<FakePeer>(kRandomAddress1, dispatcher(), false, true);
fake_peer->set_advertising_data(kPlainAdvDataBytes);
fake_peer->set_scan_response(kPlainScanRspBytes);
test_device()->AddPeer(std::move(fake_peer));
// Generates ADV_IND
fake_peer =
std::make_unique<FakePeer>(kPublicAddress2, dispatcher(), true, true);
fake_peer->set_advertising_data(kPlainAdvDataBytes);
fake_peer->set_scan_response(DynamicByteBuffer());
test_device()->AddPeer(std::move(fake_peer));
// Generates ADV_IND
fake_peer =
std::make_unique<FakePeer>(kRandomAddress2, dispatcher(), true, true);
fake_peer->set_scan_response(kPlainScanRspBytes);
test_device()->AddPeer(std::move(fake_peer));
// Generates ADV_IND, a scan response is never sent even though ADV_IND is
// scannable.
fake_peer =
std::make_unique<FakePeer>(kRandomAddress3, dispatcher(), true, false);
fake_peer->set_advertising_data(kPlainAdvDataBytes);
test_device()->AddPeer(std::move(fake_peer));
// Generates ADV_NONCONN_IND
fake_peer =
std::make_unique<FakePeer>(kRandomAddress4, dispatcher(), false, false);
fake_peer->set_advertising_data(kPlainAdvDataBytes);
test_device()->AddPeer(std::move(fake_peer));
}
LowEnergyScanner* scanner() const { return scanner_.get(); }
FakeLocalAddressDelegate* fake_address_delegate() {
return &fake_address_delegate_;
}
LowEnergyScanner::ScanStatus last_scan_status() const {
return last_scan_status_;
}
private:
PeerFoundCallback peer_found_cb_;
DirectedAdvCallback directed_adv_cb_;
FakeLocalAddressDelegate fake_address_delegate_{dispatcher()};
std::unique_ptr<LowEnergyScanner> scanner_;
LowEnergyScanner::ScanStatus last_scan_status_;
BT_DISALLOW_COPY_AND_ASSIGN_ALLOW_MOVE(LowEnergyScannerTest);
};
using Implementations =
::testing::Types<LegacyLowEnergyScanner, ExtendedLowEnergyScanner>;
TYPED_TEST_SUITE(LowEnergyScannerTest, Implementations);
TYPED_TEST(LowEnergyScannerTest, StartScanHCIErrors) {
EXPECT_TRUE(this->scanner()->IsIdle());
EXPECT_FALSE(this->scanner()->IsScanning());
EXPECT_FALSE(this->test_device()->le_scan_state().enabled);
// Set Scan Parameters will fail.
this->test_device()->SetDefaultResponseStatus(
hci_spec::kLESetScanParameters,
pw::bluetooth::emboss::StatusCode::HARDWARE_FAILURE);
this->test_device()->SetDefaultResponseStatus(
hci_spec::kLESetExtendedScanParameters,
pw::bluetooth::emboss::StatusCode::HARDWARE_FAILURE);
EXPECT_EQ(0, this->test_device()->le_scan_state().scan_interval);
EXPECT_TRUE(this->StartScan(false));
EXPECT_EQ(LowEnergyScanner::State::kInitiating, this->scanner()->state());
// Calling StartScan() should fail as the state is not kIdle.
EXPECT_FALSE(this->StartScan(false));
this->RunUntilIdle();
// Status should be failure and the scan parameters shouldn't have applied.
EXPECT_EQ(LowEnergyScanner::ScanStatus::kFailed, this->last_scan_status());
EXPECT_EQ(0, this->test_device()->le_scan_state().scan_interval);
EXPECT_FALSE(this->test_device()->le_scan_state().enabled);
EXPECT_TRUE(this->scanner()->IsIdle());
EXPECT_FALSE(this->scanner()->IsScanning());
// Set Scan Parameters will succeed but Set Scan Enable will fail.
this->test_device()->ClearDefaultResponseStatus(
hci_spec::kLESetScanParameters);
this->test_device()->ClearDefaultResponseStatus(
hci_spec::kLESetExtendedScanParameters);
this->test_device()->SetDefaultResponseStatus(
hci_spec::kLESetScanEnable,
pw::bluetooth::emboss::StatusCode::HARDWARE_FAILURE);
this->test_device()->SetDefaultResponseStatus(
hci_spec::kLESetExtendedScanEnable,
pw::bluetooth::emboss::StatusCode::HARDWARE_FAILURE);
EXPECT_TRUE(this->StartScan(false));
EXPECT_EQ(LowEnergyScanner::State::kInitiating, this->scanner()->state());
this->RunUntilIdle();
// Status should be failure but the scan parameters should have applied.
EXPECT_EQ(LowEnergyScanner::ScanStatus::kFailed, this->last_scan_status());
EXPECT_EQ(hci_spec::defaults::kLEScanInterval,
this->test_device()->le_scan_state().scan_interval);
EXPECT_EQ(hci_spec::defaults::kLEScanWindow,
this->test_device()->le_scan_state().scan_window);
EXPECT_EQ(pw::bluetooth::emboss::LEScanFilterPolicy::BASIC_UNFILTERED,
this->test_device()->le_scan_state().filter_policy);
EXPECT_FALSE(this->test_device()->le_scan_state().enabled);
EXPECT_TRUE(this->scanner()->IsIdle());
EXPECT_FALSE(this->scanner()->IsScanning());
}
TYPED_TEST(LowEnergyScannerTest, StartScan) {
EXPECT_TRUE(this->scanner()->IsIdle());
EXPECT_FALSE(this->scanner()->IsScanning());
EXPECT_FALSE(this->test_device()->le_scan_state().enabled);
EXPECT_TRUE(this->StartScan(true, kPwScanPeriod));
EXPECT_EQ(LowEnergyScanner::State::kInitiating, this->scanner()->state());
this->RunUntilIdle();
// Scan should have started.
EXPECT_EQ(LowEnergyScanner::ScanStatus::kActive, this->last_scan_status());
EXPECT_EQ(hci_spec::defaults::kLEScanInterval,
this->test_device()->le_scan_state().scan_interval);
EXPECT_EQ(hci_spec::defaults::kLEScanWindow,
this->test_device()->le_scan_state().scan_window);
EXPECT_EQ(pw::bluetooth::emboss::LEScanFilterPolicy::BASIC_UNFILTERED,
this->test_device()->le_scan_state().filter_policy);
EXPECT_EQ(pw::bluetooth::emboss::LEScanType::ACTIVE,
this->test_device()->le_scan_state().scan_type);
EXPECT_TRUE(this->test_device()->le_scan_state().filter_duplicates);
EXPECT_TRUE(this->test_device()->le_scan_state().enabled);
EXPECT_EQ(LowEnergyScanner::State::kActiveScanning, this->scanner()->state());
EXPECT_TRUE(this->scanner()->IsScanning());
// Calling StartScan should fail as a scan is already in progress.
EXPECT_FALSE(this->StartScan(true));
// After 10 s (kScanPeriod) the scan should stop by itself.
this->RunFor(kScanPeriod);
EXPECT_EQ(LowEnergyScanner::ScanStatus::kComplete, this->last_scan_status());
EXPECT_FALSE(this->test_device()->le_scan_state().enabled);
EXPECT_TRUE(this->scanner()->IsIdle());
EXPECT_FALSE(this->scanner()->IsScanning());
}
TYPED_TEST(LowEnergyScannerTest, StopScan) {
EXPECT_TRUE(this->scanner()->IsIdle());
EXPECT_FALSE(this->scanner()->IsScanning());
EXPECT_FALSE(this->test_device()->le_scan_state().enabled);
// Calling StopScan should fail while a scan is not in progress.
EXPECT_FALSE(this->scanner()->StopScan());
// Pass a long scan period value. This should not matter as we will terminate
// the scan directly.
EXPECT_TRUE(this->StartScan(true, kPwScanPeriod * 10u));
EXPECT_EQ(LowEnergyScanner::State::kInitiating, this->scanner()->state());
this->RunUntilIdle();
// Scan should have started.
EXPECT_EQ(LowEnergyScanner::ScanStatus::kActive, this->last_scan_status());
EXPECT_TRUE(this->test_device()->le_scan_state().enabled);
EXPECT_EQ(LowEnergyScanner::State::kActiveScanning, this->scanner()->state());
EXPECT_TRUE(this->scanner()->IsScanning());
// StopScan() should terminate the scan session and the status should be
// kStopped.
EXPECT_TRUE(this->scanner()->StopScan());
this->RunUntilIdle();
EXPECT_EQ(LowEnergyScanner::ScanStatus::kStopped, this->last_scan_status());
EXPECT_FALSE(this->test_device()->le_scan_state().enabled);
EXPECT_TRUE(this->scanner()->IsIdle());
EXPECT_FALSE(this->scanner()->IsScanning());
}
TYPED_TEST(LowEnergyScannerTest, StopScanWhileInitiating) {
EXPECT_TRUE(this->scanner()->IsIdle());
EXPECT_FALSE(this->scanner()->IsScanning());
EXPECT_FALSE(this->test_device()->le_scan_state().enabled);
EXPECT_TRUE(this->StartScan(true));
EXPECT_EQ(LowEnergyScanner::State::kInitiating, this->scanner()->state());
// Call StopScan(). This should cancel the HCI command sequence set up by
// StartScan() so that the it never completes. The HCI_LE_Set_Scan_Parameters
// command *may* get sent but the scan should never get enabled.
EXPECT_TRUE(this->scanner()->StopScan());
this->RunUntilIdle();
EXPECT_EQ(LowEnergyScanner::ScanStatus::kStopped, this->last_scan_status());
EXPECT_FALSE(this->test_device()->le_scan_state().enabled);
EXPECT_TRUE(this->scanner()->IsIdle());
EXPECT_FALSE(this->scanner()->IsScanning());
}
TYPED_TEST(LowEnergyScannerTest, ScanResponseTimeout) {
constexpr pw::chrono::SystemClock::duration kHalfTimeout =
kScanResponseTimeout / 2;
std::unordered_set<DeviceAddress> results;
this->set_peer_found_callback([&](const LowEnergyScanResult& result) {
results.insert(result.address());
});
// Add a peer that sends a scan response and one that doesn't.
auto fake_peer = std::make_unique<FakePeer>(
kRandomAddress1, this->dispatcher(), false, true);
fake_peer->set_advertising_data(kPlainAdvDataBytes);
fake_peer->set_scan_response(kPlainScanRspBytes);
this->test_device()->AddPeer(std::move(fake_peer));
fake_peer = std::make_unique<FakePeer>(
kRandomAddress2, this->dispatcher(), true, false);
fake_peer->set_advertising_data(kPlainAdvDataBytes);
this->test_device()->AddPeer(std::move(fake_peer));
EXPECT_TRUE(this->StartScan(true));
this->RunUntilIdle();
ASSERT_EQ(1u, results.size());
EXPECT_EQ(1u, results.count(kRandomAddress1));
// Advance the time but do not expire the timeout.
this->RunFor(kHalfTimeout);
ASSERT_EQ(1u, results.size());
// Add another peer that doesn't send a scan response after the kHalfTimeout
// delay. This is to test that a separate timeout is kept for every peer.
fake_peer = std::make_unique<FakePeer>(
kRandomAddress3, this->dispatcher(), true, false);
fake_peer->set_advertising_data(kPlainAdvDataBytes);
this->test_device()->AddPeer(std::move(fake_peer));
// Expire the first timeout.
this->RunFor(kHalfTimeout);
ASSERT_EQ(2u, results.size());
EXPECT_EQ(1u, results.count(kRandomAddress1));
EXPECT_EQ(1u, results.count(kRandomAddress2));
// Expire the second timeout.
this->RunFor(kHalfTimeout);
ASSERT_EQ(3u, results.size());
EXPECT_EQ(1u, results.count(kRandomAddress1));
EXPECT_EQ(1u, results.count(kRandomAddress2));
EXPECT_EQ(1u, results.count(kRandomAddress3));
}
TYPED_TEST(LowEnergyScannerTest, ScanResponseAfterTimeout) {
{
auto peer = std::make_unique<FakePeer>(
kPublicAddress1, this->dispatcher(), true, true, false);
peer->set_advertising_data(kPlainAdvDataBytes);
peer->set_scan_response(kPlainScanRspBytes);
this->test_device()->AddPeer(std::move(peer));
}
auto peer = this->test_device()->FindPeer(kPublicAddress1);
// The callback should get called on timeout waiting for a scan response
bool peer_found_callback_called = false;
std::unordered_map<DeviceAddress, std::unique_ptr<DynamicByteBuffer>> map;
this->set_peer_found_callback([&](const LowEnergyScanResult& result) {
peer_found_callback_called = true;
map[result.address()] = std::make_unique<DynamicByteBuffer>(result.data());
});
EXPECT_TRUE(this->StartScan(true));
this->RunUntilIdle();
this->test_device()->SendAdvertisingReport(*peer);
this->RunFor(kPwScanResponseTimeout);
ASSERT_TRUE(peer_found_callback_called);
ASSERT_EQ(1u, map.count(peer->address()));
EXPECT_EQ(kPlainAdvDataBytes.ToString(), map[peer->address()]->ToString());
peer_found_callback_called = false;
this->test_device()->SendScanResponseReport(*peer);
this->RunUntilIdle();
ASSERT_FALSE(peer_found_callback_called);
}
TYPED_TEST(LowEnergyScannerTest, ActiveScanResults) {
// One of the 6 fake peers is scannable but never sends scan response
// packets. That peer doesn't get reported until the end of the scan period.
constexpr size_t kExpectedResultCount = 5u;
this->AddFakePeers();
std::map<DeviceAddress, LowEnergyScanResult> results;
this->set_peer_found_callback([&](const LowEnergyScanResult& result) {
results[result.address()] = result;
});
// Perform an active scan.
EXPECT_TRUE(this->StartScan(true, kPwScanPeriod));
EXPECT_EQ(LowEnergyScanner::State::kInitiating, this->scanner()->state());
this->RunUntilIdle();
ASSERT_EQ(kExpectedResultCount, results.size());
// Ending the scan period should notify Fake Peer #4.
this->RunFor(kScanPeriod);
EXPECT_EQ(LowEnergyScanner::ScanStatus::kComplete, this->last_scan_status());
ASSERT_EQ(kExpectedResultCount + 1, results.size());
// Verify the 6 results against the fake peers that were set up by
// this->AddFakePeers(). Since the scan period ended naturally,
// LowEnergyScanner should generate a peer found event for all pending reports
// even if a scan response was not received for a scannable peer (see Fake
// Peer 4, i.e. kRandomAddress3).
// Result 0 (ADV_IND)
{
const auto& iter = results.find(kPublicAddress1);
ASSERT_NE(iter, results.end());
EXPECT_EQ(kAdvDataAndScanRsp, iter->second.data().ToString());
EXPECT_EQ(kPublicAddress1, iter->second.address());
EXPECT_TRUE(iter->second.connectable());
results.erase(iter);
}
// Result 1 (ADV_SCAN_IND)
{
const auto& iter = results.find(kRandomAddress1);
ASSERT_NE(iter, results.end());
EXPECT_EQ(kAdvDataAndScanRsp, iter->second.data().ToString());
EXPECT_EQ(kRandomAddress1, iter->second.address());
EXPECT_FALSE(iter->second.connectable());
results.erase(iter);
}
// Result 2 (ADV_IND), empty scan response
{
const auto& iter = results.find(kPublicAddress2);
ASSERT_NE(iter, results.end());
EXPECT_EQ(kPlainAdvData, iter->second.data().ToString());
EXPECT_EQ(kPublicAddress2, iter->second.address());
EXPECT_TRUE(iter->second.connectable());
results.erase(iter);
}
// Result 3 (ADV_IND), empty advertising data w/ scan response
{
const auto& iter = results.find(kRandomAddress2);
ASSERT_NE(iter, results.end());
EXPECT_EQ(kPlainScanRsp, iter->second.data().ToString());
EXPECT_EQ(kRandomAddress2, iter->second.address());
EXPECT_TRUE(iter->second.connectable());
results.erase(iter);
}
// Result 4 (ADV_IND), no scan response
{
const auto& iter = results.find(kRandomAddress3);
ASSERT_NE(iter, results.end());
EXPECT_EQ(kPlainAdvData, iter->second.data().ToString());
EXPECT_EQ(kRandomAddress3, iter->second.address());
EXPECT_TRUE(iter->second.connectable());
results.erase(iter);
}
// Result 5 (ADV_NONCONN_IND)
{
const auto& iter = results.find(kRandomAddress4);
ASSERT_NE(iter, results.end());
EXPECT_EQ(kPlainAdvData, iter->second.data().ToString());
EXPECT_EQ(kRandomAddress4, iter->second.address());
EXPECT_FALSE(iter->second.connectable());
results.erase(iter);
}
// No other reports are expected
EXPECT_TRUE(results.empty());
}
TYPED_TEST(LowEnergyScannerTest, StopDuringActiveScan) {
this->AddFakePeers();
std::map<DeviceAddress, LowEnergyScanResult> results;
this->set_peer_found_callback([&results](const LowEnergyScanResult& result) {
results[result.address()] = result;
});
// Perform an active scan indefinitely. This means that the scan period will
// never complete by itself.
EXPECT_TRUE(this->StartScan(true));
EXPECT_EQ(LowEnergyScanner::State::kInitiating, this->scanner()->state());
this->RunUntilIdle();
EXPECT_EQ(LowEnergyScanner::State::kActiveScanning, this->scanner()->state());
// Run the loop until we've seen an event for the last peer that we
// added. Fake Peer kRandomAddress3 is scannable but it never sends a scan
// response so we expect that to remain in the scanner's pending reports list.
this->RunUntilIdle();
EXPECT_EQ(5u, results.size());
EXPECT_EQ(results.find(kRandomAddress3), results.end());
// Stop the scan. Since we are terminating the scan period early,
// LowEnergyScanner should not send a report for the pending peer.
EXPECT_TRUE(this->scanner()->StopScan());
this->RunUntilIdle();
EXPECT_TRUE(this->scanner()->IsIdle());
EXPECT_EQ(5u, results.size());
EXPECT_EQ(results.find(kRandomAddress3), results.end());
}
TYPED_TEST(LowEnergyScannerTest, PassiveScanResults) {
constexpr size_t kExpectedResultCount = 6u;
this->AddFakePeers();
std::map<DeviceAddress, LowEnergyScanResult> results;
this->set_peer_found_callback([&](const LowEnergyScanResult& result) {
results[result.address()] = result;
});
// Perform a passive scan.
EXPECT_TRUE(this->StartScan(false));
EXPECT_EQ(LowEnergyScanner::State::kInitiating, this->scanner()->state());
this->RunUntilIdle();
EXPECT_EQ(LowEnergyScanner::State::kPassiveScanning,
this->scanner()->state());
EXPECT_EQ(LowEnergyScanner::ScanStatus::kPassive, this->last_scan_status());
ASSERT_EQ(kExpectedResultCount, results.size());
// Verify the 6 results against the fake peers that were set up by
// this->AddFakePeers(). All Scan Response PDUs should have been ignored.
// Result 0
{
const auto& iter = results.find(kPublicAddress1);
ASSERT_NE(iter, results.end());
EXPECT_EQ(kPlainAdvData, iter->second.data().ToString());
EXPECT_EQ(kPublicAddress1, iter->second.address());
EXPECT_TRUE(iter->second.connectable());
results.erase(iter);
}
// Result 1
{
const auto& iter = results.find(kRandomAddress1);
ASSERT_NE(iter, results.end());
EXPECT_EQ(kPlainAdvData, iter->second.data().ToString());
EXPECT_EQ(kRandomAddress1, iter->second.address());
EXPECT_FALSE(iter->second.connectable());
results.erase(iter);
}
// Result 2
{
const auto& iter = results.find(kPublicAddress2);
ASSERT_NE(iter, results.end());
EXPECT_EQ(kPlainAdvData, iter->second.data().ToString());
EXPECT_EQ(kPublicAddress2, iter->second.address());
EXPECT_TRUE(iter->second.connectable());
results.erase(iter);
}
// Result 3
{
const auto& iter = results.find(kRandomAddress2);
ASSERT_NE(iter, results.end());
EXPECT_EQ("", iter->second.data().ToString());
EXPECT_EQ(kRandomAddress2, iter->second.address());
EXPECT_TRUE(iter->second.connectable());
results.erase(iter);
}
// Result 4
{
const auto& iter = results.find(kRandomAddress3);
ASSERT_NE(iter, results.end());
EXPECT_EQ(kPlainAdvData, iter->second.data().ToString());
EXPECT_EQ(kRandomAddress3, iter->second.address());
EXPECT_TRUE(iter->second.connectable());
results.erase(iter);
}
// Result 5
{
const auto& iter = results.find(kRandomAddress4);
ASSERT_NE(iter, results.end());
EXPECT_EQ(kPlainAdvData, iter->second.data().ToString());
EXPECT_EQ(kRandomAddress4, iter->second.address());
EXPECT_FALSE(iter->second.connectable());
results.erase(iter);
}
EXPECT_TRUE(results.empty());
}
TYPED_TEST(LowEnergyScannerTest, DirectedReport) {
const auto& kPublicUnresolved = kPublicAddress1;
const auto& kPublicResolved = kPublicAddress2;
const auto& kRandomUnresolved = kRandomAddress1;
const auto& kRandomResolved = kRandomAddress2;
constexpr size_t kExpectedResultCount = 4u;
// Unresolved public.
auto fake_peer = std::make_unique<FakePeer>(
kPublicUnresolved, this->dispatcher(), true, false);
fake_peer->set_directed_advertising_enabled(true);
this->test_device()->AddPeer(std::move(fake_peer));
// Unresolved random.
fake_peer = std::make_unique<FakePeer>(
kRandomUnresolved, this->dispatcher(), true, false);
fake_peer->set_directed_advertising_enabled(true);
this->test_device()->AddPeer(std::move(fake_peer));
// Resolved public.
fake_peer = std::make_unique<FakePeer>(
kPublicResolved, this->dispatcher(), true, false);
fake_peer->set_address_resolved(true);
fake_peer->set_directed_advertising_enabled(true);
this->test_device()->AddPeer(std::move(fake_peer));
// Resolved random.
fake_peer = std::make_unique<FakePeer>(
kRandomResolved, this->dispatcher(), true, false);
fake_peer->set_address_resolved(true);
fake_peer->set_directed_advertising_enabled(true);
this->test_device()->AddPeer(std::move(fake_peer));
std::unordered_map<DeviceAddress, LowEnergyScanResult> results;
this->set_directed_adv_callback([&](const LowEnergyScanResult& result) {
results[result.address()] = result;
});
EXPECT_TRUE(this->StartScan(true));
EXPECT_EQ(LowEnergyScanner::State::kInitiating, this->scanner()->state());
this->RunUntilIdle();
ASSERT_EQ(LowEnergyScanner::ScanStatus::kActive, this->last_scan_status());
ASSERT_EQ(kExpectedResultCount, results.size());
ASSERT_TRUE(results.count(kPublicUnresolved));
EXPECT_FALSE(results[kPublicUnresolved].resolved());
ASSERT_TRUE(results.count(kRandomUnresolved));
EXPECT_FALSE(results[kRandomUnresolved].resolved());
ASSERT_TRUE(results.count(kPublicResolved));
EXPECT_TRUE(results[kPublicResolved].resolved());
ASSERT_TRUE(results.count(kRandomResolved));
EXPECT_TRUE(results[kRandomResolved].resolved());
}
TYPED_TEST(LowEnergyScannerTest, AllowsRandomAddressChange) {
EXPECT_TRUE(this->scanner()->AllowsRandomAddressChange());
EXPECT_TRUE(this->StartScan(false));
// Address change should not be allowed while the procedure is pending.
EXPECT_TRUE(this->scanner()->IsInitiating());
EXPECT_FALSE(this->scanner()->AllowsRandomAddressChange());
this->RunUntilIdle();
EXPECT_TRUE(this->scanner()->IsPassiveScanning());
EXPECT_FALSE(this->scanner()->AllowsRandomAddressChange());
}
TYPED_TEST(LowEnergyScannerTest,
AllowsRandomAddressChangeWhileRequestingLocalAddress) {
// Make the local address delegate report its result asynchronously.
this->fake_address_delegate()->set_async(true);
EXPECT_TRUE(this->StartScan(false));
// The scanner should be in the initiating state without initiating controller
// procedures that would prevent a local address change.
EXPECT_TRUE(this->scanner()->IsInitiating());
EXPECT_TRUE(this->scanner()->AllowsRandomAddressChange());
this->RunUntilIdle();
EXPECT_TRUE(this->scanner()->IsPassiveScanning());
EXPECT_FALSE(this->scanner()->AllowsRandomAddressChange());
}
TYPED_TEST(LowEnergyScannerTest, ScanUsingPublicAddress) {
this->fake_address_delegate()->set_local_address(kPublicAddress1);
EXPECT_TRUE(this->StartScan(false));
this->RunUntilIdle();
EXPECT_TRUE(this->scanner()->IsPassiveScanning());
EXPECT_EQ(pw::bluetooth::emboss::LEOwnAddressType::PUBLIC,
this->test_device()->le_scan_state().own_address_type);
}
TYPED_TEST(LowEnergyScannerTest, ScanUsingRandomAddress) {
this->fake_address_delegate()->set_local_address(kRandomAddress1);
EXPECT_TRUE(this->StartScan(false));
this->RunUntilIdle();
EXPECT_TRUE(this->scanner()->IsPassiveScanning());
EXPECT_EQ(pw::bluetooth::emboss::LEOwnAddressType::RANDOM,
this->test_device()->le_scan_state().own_address_type);
}
TYPED_TEST(LowEnergyScannerTest, StopScanWhileWaitingForLocalAddress) {
this->fake_address_delegate()->set_async(true);
EXPECT_TRUE(this->StartScan(false));
// Should be waiting for the random address.
EXPECT_TRUE(this->scanner()->IsInitiating());
EXPECT_TRUE(this->scanner()->AllowsRandomAddressChange());
EXPECT_TRUE(this->scanner()->StopScan());
this->RunUntilIdle();
// Should end up not scanning.
EXPECT_TRUE(this->scanner()->IsIdle());
EXPECT_FALSE(this->test_device()->le_scan_state().enabled);
}
TYPED_TEST(LowEnergyScannerTest, CallbackStopsScanning) {
auto fake_peer = std::make_unique<FakePeer>(
kRandomAddress1, this->dispatcher(), true, false);
fake_peer->set_advertising_data(kPlainAdvDataBytes);
this->test_device()->AddPeer(std::move(fake_peer));
fake_peer = std::make_unique<FakePeer>(
kRandomAddress2, this->dispatcher(), true, false);
fake_peer->set_advertising_data(kPlainAdvDataBytes);
this->test_device()->AddPeer(std::move(fake_peer));
// We should be able to stop scanning in the callback and not crash. Note: if
// crashing, it will likely be due to a use-after-free type bug. Such a bug
// may or may not manifest itself in a non-asan build.
this->set_peer_found_callback(
[&](const LowEnergyScanResult& result) { this->scanner()->StopScan(); });
EXPECT_TRUE(this->StartScan(true, kPwScanPeriod));
this->RunFor(kScanPeriod);
}
} // namespace bt::hci