blob: 19069c85a5df9a541504927353e26077bd71a853 [file] [log] [blame]
// Copyright 2024 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/legacy_low_energy_scanner.h"
#include "pw_bluetooth/hci_events.emb.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/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"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/testing/test_helpers.h"
namespace bt::hci {
using bt::testing::FakeController;
using bt::testing::FakePeer;
using TestingBase = bt::testing::FakeDispatcherControllerTest<FakeController>;
constexpr pw::chrono::SystemClock::duration kPwScanResponseTimeout =
std::chrono::seconds(2);
const StaticByteBuffer kPlainAdvDataBytes('T', 'e', 's', 't');
const StaticByteBuffer kPlainScanRspBytes('D', 'a', 't', 'a');
const DeviceAddress kPublicAddr(DeviceAddress::Type::kLEPublic, {1});
class LegacyLowEnergyScannerTest : public TestingBase,
public LowEnergyScanner::Delegate {
public:
LegacyLowEnergyScannerTest() = default;
~LegacyLowEnergyScannerTest() override = default;
protected:
void SetUp() override {
TestingBase::SetUp();
FakeController::Settings settings;
settings.ApplyLegacyLEConfig();
test_device()->set_settings(settings);
scanner_ = std::make_unique<LegacyLowEnergyScanner>(
fake_address_delegate(), transport()->GetWeakPtr(), dispatcher());
scanner_->set_delegate(this);
auto p = std::make_unique<FakePeer>(kPublicAddr,
dispatcher(),
/*scannable=*/true,
/*send_advertising_report=*/true);
p->set_use_extended_advertising_pdus(true);
p->set_advertising_data(kPlainAdvDataBytes);
p->set_scan_response(kPlainScanRspBytes);
peers_.push_back(std::move(p));
}
void TearDown() override {
scanner_ = nullptr;
test_device()->Stop();
TestingBase::TearDown();
}
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, [](auto status) {});
}
using PeerFoundCallback = fit::function<void(const LowEnergyScanResult&)>;
void set_peer_found_callback(PeerFoundCallback cb) {
peer_found_cb_ = std::move(cb);
}
// LowEnergyScanner::Delegate override:
void OnPeerFound(const LowEnergyScanResult& result) override {
if (peer_found_cb_) {
peer_found_cb_(result);
}
}
LowEnergyScanner* scanner() const { return scanner_.get(); }
FakeLocalAddressDelegate* fake_address_delegate() {
return &fake_address_delegate_;
}
static constexpr size_t event_prefix_size =
pw::bluetooth::emboss::LEAdvertisingReportSubevent::MinSizeInBytes();
static constexpr size_t report_prefix_size =
pw::bluetooth::emboss::LEAdvertisingReportData::MinSizeInBytes();
private:
std::unique_ptr<LowEnergyScanner> scanner_;
PeerFoundCallback peer_found_cb_;
std::vector<std::unique_ptr<FakePeer>> peers_;
FakeLocalAddressDelegate fake_address_delegate_{dispatcher()};
};
// Ensure we can parse a single advertising report correctly
TEST_F(LegacyLowEnergyScannerTest, ParseAdvertisingReportsSingleReport) {
{
auto peer = std::make_unique<FakePeer>(kPublicAddr,
dispatcher(),
/*connectable=*/false,
/*scannable=*/false,
/*send_advertising_report=*/false);
peer->set_advertising_data(kPlainAdvDataBytes);
test_device()->AddPeer(std::move(peer));
}
bool peer_found_callback_called = false;
std::unordered_map<DeviceAddress, std::unique_ptr<DynamicByteBuffer>> map;
set_peer_found_callback([&](const LowEnergyScanResult& result) {
peer_found_callback_called = true;
map[result.address()] =
std::make_unique<DynamicByteBuffer>(result.data().size());
result.data().Copy(&*map[result.address()]);
});
ASSERT_TRUE(StartScan(true));
RunUntilIdle();
auto peer = test_device()->FindPeer(kPublicAddr);
DynamicByteBuffer buffer = peer->BuildLegacyAdvertisingReportEvent(false);
test_device()->SendCommandChannelPacket(buffer);
RunUntilIdle();
ASSERT_TRUE(peer_found_callback_called);
ASSERT_EQ(1u, map.count(peer->address()));
ASSERT_TRUE(ContainersEqual(kPlainAdvDataBytes, *map[peer->address()]));
}
// Ensure we can parse multiple extended advertising reports correctly
TEST_F(LegacyLowEnergyScannerTest, ParseAdvertisingReportsMultipleReports) {
{
auto peer = std::make_unique<FakePeer>(kPublicAddr,
dispatcher(),
/*connectable=*/true,
/*scannable=*/true,
/*send_advertising_report=*/false);
peer->set_advertising_data(kPlainAdvDataBytes);
peer->set_scan_response(kPlainScanRspBytes);
test_device()->AddPeer(std::move(peer));
}
bool peer_found_callback_called = false;
std::unordered_map<DeviceAddress, std::unique_ptr<DynamicByteBuffer>> map;
set_peer_found_callback([&](const LowEnergyScanResult& result) {
peer_found_callback_called = true;
map[result.address()] =
std::make_unique<DynamicByteBuffer>(result.data().size());
result.data().Copy(&*map[result.address()]);
});
ASSERT_TRUE(StartScan(true));
RunUntilIdle();
auto peer = test_device()->FindPeer(kPublicAddr);
DynamicByteBuffer buffer = peer->BuildLegacyAdvertisingReportEvent(true);
test_device()->SendCommandChannelPacket(buffer);
RunUntilIdle();
ASSERT_TRUE(peer_found_callback_called);
ASSERT_EQ(1u, map.count(peer->address()));
ASSERT_EQ(kPlainAdvDataBytes.ToString() + kPlainScanRspBytes.ToString(),
map[peer->address()]->ToString());
}
// Test that we check for enough data being present before constructing a view
// on top of it. This case hopefully should never happen since the
// Controller should always send back valid data but it's better to be
// careful and avoid a crash.
TEST_F(LegacyLowEnergyScannerTest, ParseAdvertisingReportsNotEnoughData) {
{
auto peer = std::make_unique<FakePeer>(kPublicAddr,
dispatcher(),
/*connectable=*/true,
/*scannable=*/true,
/*send_advertising_report=*/false);
peer->set_advertising_data(kPlainAdvDataBytes);
test_device()->AddPeer(std::move(peer));
}
ASSERT_TRUE(StartScan(true));
RunUntilIdle();
auto peer = test_device()->FindPeer(kPublicAddr);
DynamicByteBuffer buffer = peer->BuildLegacyAdvertisingReportEvent(false);
auto params = pw::bluetooth::emboss::LEAdvertisingReportSubeventWriter(
buffer.mutable_data(), buffer.size());
auto report = pw::bluetooth::emboss::LEAdvertisingReportDataWriter(
params.BackingStorage().data(), params.BackingStorage().SizeInBytes());
report.data_length().Write(report.data_length().Read() + 1);
test_device()->SendCommandChannelPacket(buffer);
// there wasn't enough data available so we shouldn't have parsed out any
// advertising reports
set_peer_found_callback([&](const LowEnergyScanResult& result) { FAIL(); });
RunUntilIdle();
}
} // namespace bt::hci