blob: a28faa4913b7158888e3c6f65d3a66769773b282 [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/legacy_low_energy_scanner.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/hci/advertising_report_parser.h"
#pragma clang diagnostic ignored "-Wswitch-enum"
namespace bt::hci {
namespace pwemb = pw::bluetooth::emboss;
LegacyLowEnergyScanner::LegacyLowEnergyScanner(
LocalAddressDelegate* local_addr_delegate,
Transport::WeakPtr transport,
pw::async::Dispatcher& pw_dispatcher)
: LowEnergyScanner(
local_addr_delegate, std::move(transport), pw_dispatcher),
weak_self_(this) {
auto self = weak_self_.GetWeakPtr();
event_handler_id_ = hci()->command_channel()->AddLEMetaEventHandler(
hci_spec::kLEAdvertisingReportSubeventCode,
[self](const EmbossEventPacket& event) {
if (!self.is_alive()) {
return hci::CommandChannel::EventCallbackResult::kRemove;
}
self->OnAdvertisingReportEvent(event);
return hci::CommandChannel::EventCallbackResult::kContinue;
});
}
LegacyLowEnergyScanner::~LegacyLowEnergyScanner() {
// This object is probably being destroyed because the stack is shutting down,
// in which case the HCI layer may have already been destroyed.
if (!hci().is_alive() || !hci()->command_channel()) {
return;
}
hci()->command_channel()->RemoveEventHandler(event_handler_id_);
StopScan();
}
bool LegacyLowEnergyScanner::StartScan(const ScanOptions& options,
ScanStatusCallback callback) {
BT_ASSERT(options.interval >= hci_spec::kLEScanIntervalMin);
BT_ASSERT(options.interval <= hci_spec::kLEScanIntervalMax);
BT_ASSERT(options.window >= hci_spec::kLEScanIntervalMin);
BT_ASSERT(options.window <= hci_spec::kLEScanIntervalMax);
return LowEnergyScanner::StartScan(options, std::move(callback));
}
EmbossCommandPacket LegacyLowEnergyScanner::BuildSetScanParametersPacket(
const DeviceAddress& local_address, const ScanOptions& options) {
auto packet = hci::EmbossCommandPacket::New<
pw::bluetooth::emboss::LESetScanParametersCommandWriter>(
hci_spec::kLESetScanParameters);
auto params = packet.view_t();
params.le_scan_type().Write(pw::bluetooth::emboss::LEScanType::PASSIVE);
if (options.active) {
params.le_scan_type().Write(pw::bluetooth::emboss::LEScanType::ACTIVE);
}
params.le_scan_interval().Write(options.interval);
params.le_scan_window().Write(options.window);
params.scanning_filter_policy().Write(options.filter_policy);
if (local_address.type() == DeviceAddress::Type::kLERandom) {
params.own_address_type().Write(
pw::bluetooth::emboss::LEOwnAddressType::RANDOM);
} else {
params.own_address_type().Write(
pw::bluetooth::emboss::LEOwnAddressType::PUBLIC);
}
return packet;
}
EmbossCommandPacket LegacyLowEnergyScanner::BuildEnablePacket(
const ScanOptions& options,
pw::bluetooth::emboss::GenericEnableParam enable) {
auto packet = EmbossCommandPacket::New<
pw::bluetooth::emboss::LESetScanEnableCommandWriter>(
hci_spec::kLESetScanEnable);
auto params = packet.view_t();
params.le_scan_enable().Write(enable);
params.filter_duplicates().Write(
pw::bluetooth::emboss::GenericEnableParam::DISABLE);
if (options.filter_duplicates) {
params.filter_duplicates().Write(
pw::bluetooth::emboss::GenericEnableParam::ENABLE);
}
return packet;
}
void LegacyLowEnergyScanner::HandleScanResponse(const DeviceAddress& address,
bool resolved,
int8_t rssi,
const ByteBuffer& data) {
std::unique_ptr<PendingScanResult> pending = RemovePendingResult(address);
if (!pending) {
bt_log(DEBUG, "hci-le", "dropping unmatched scan response");
return;
}
BT_DEBUG_ASSERT(address == pending->result().address());
pending->result().AppendData(data);
pending->result().set_resolved(resolved);
pending->result().set_rssi(rssi);
delegate()->OnPeerFound(pending->result());
// The callback handler may stop the scan, destroying objects within the
// LowEnergyScanner. Avoid doing anything more to prevent use after free
// bugs.
}
// Extract all advertising reports from a given HCI LE Advertising Report event
std::vector<pw::bluetooth::emboss::LEAdvertisingReportDataView>
LegacyLowEnergyScanner::ParseAdvertisingReports(
const EmbossEventPacket& event) {
BT_DEBUG_ASSERT(event.event_code() == hci_spec::kLEMetaEventCode);
BT_DEBUG_ASSERT(event.view<pw::bluetooth::emboss::LEMetaEventView>()
.subevent_code()
.Read() == hci_spec::kLEAdvertisingReportSubeventCode);
auto params =
event.view<pw::bluetooth::emboss::LEAdvertisingReportSubeventView>();
uint8_t num_reports = params.num_reports().Read();
std::vector<pw::bluetooth::emboss::LEAdvertisingReportDataView> reports;
reports.reserve(num_reports);
size_t bytes_read = 0;
while (bytes_read < params.reports().BackingStorage().SizeInBytes()) {
size_t min_size =
pw::bluetooth::emboss::LEAdvertisingReportData::MinSizeInBytes();
auto report_prefix = pw::bluetooth::emboss::MakeLEAdvertisingReportDataView(
params.reports().BackingStorage().begin() + bytes_read, min_size);
uint8_t data_length = report_prefix.data_length().Read();
size_t actual_size = min_size + data_length;
size_t bytes_left =
params.reports().BackingStorage().SizeInBytes() - bytes_read;
if (actual_size > bytes_left) {
bt_log(WARN,
"hci-le",
"parsing advertising reports, next report size %zu bytes, but "
"only %zu bytes left",
actual_size,
bytes_left);
break;
}
auto report = pw::bluetooth::emboss::MakeLEAdvertisingReportDataView(
params.reports().BackingStorage().begin() + bytes_read, actual_size);
reports.push_back(report);
bytes_read += actual_size;
}
return reports;
}
// Returns a DeviceAddress and whether or not that DeviceAddress has been
// resolved
static std::tuple<DeviceAddress, bool> BuildDeviceAddress(
pw::bluetooth::emboss::LEAddressType report_type,
pw::bluetooth::emboss::BdAddrView address_view) {
std::optional<DeviceAddress::Type> address_type =
DeviceAddress::LeAddrToDeviceAddr(report_type);
BT_DEBUG_ASSERT(address_type);
bool resolved = false;
switch (report_type) {
case pw::bluetooth::emboss::LEAddressType::PUBLIC_IDENTITY:
case pw::bluetooth::emboss::LEAddressType::RANDOM_IDENTITY:
resolved = true;
break;
case pw::bluetooth::emboss::LEAddressType::PUBLIC:
case pw::bluetooth::emboss::LEAddressType::RANDOM:
resolved = false;
break;
}
DeviceAddress address =
DeviceAddress(*address_type, DeviceAddressBytes(address_view));
return std::make_tuple(address, resolved);
}
void LegacyLowEnergyScanner::OnAdvertisingReportEvent(
const EmbossEventPacket& event) {
if (!IsScanning()) {
return;
}
std::vector<pw::bluetooth::emboss::LEAdvertisingReportDataView> reports =
ParseAdvertisingReports(event);
for (pw::bluetooth::emboss::LEAdvertisingReportDataView report : reports) {
if (report.data_length().Read() > hci_spec::kMaxLEAdvertisingDataLength) {
bt_log(WARN, "hci-le", "advertising data too long! Ignoring");
continue;
}
const auto& [address, resolved] =
BuildDeviceAddress(report.address_type().Read(), report.address());
bool needs_scan_rsp = false;
bool connectable = false;
bool directed = false;
switch (report.event_type().Read()) {
case pwemb::LEAdvertisingEventType::CONNECTABLE_DIRECTED: {
directed = true;
break;
}
case pwemb::LEAdvertisingEventType::
CONNECTABLE_AND_SCANNABLE_UNDIRECTED: {
connectable = true;
[[fallthrough]];
}
case pwemb::LEAdvertisingEventType::SCANNABLE_UNDIRECTED: {
if (IsActiveScanning()) {
needs_scan_rsp = true;
}
break;
}
case pwemb::LEAdvertisingEventType::SCAN_RESPONSE: {
if (IsActiveScanning()) {
BufferView data = BufferView(report.data().BackingStorage().data(),
report.data_length().Read());
HandleScanResponse(address, resolved, report.rssi().Read(), data);
}
continue;
}
default: {
break;
}
}
LowEnergyScanResult result(address, resolved, connectable);
result.AppendData(BufferView(report.data().BackingStorage().data(),
report.data_length().Read()));
result.set_rssi(report.rssi().Read());
if (directed) {
delegate()->OnDirectedAdvertisement(result);
continue;
}
if (!needs_scan_rsp) {
delegate()->OnPeerFound(result);
continue;
}
AddPendingResult(std::move(result));
}
}
} // namespace bt::hci