blob: a6dc703bbe49383afed353033b5f349b1100a846 [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/low_energy_scanner.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/hci/util.h"
namespace bt::hci {
static std::string ScanStateToString(LowEnergyScanner::State state) {
switch (state) {
case LowEnergyScanner::State::kIdle:
return "(idle)";
case LowEnergyScanner::State::kStopping:
return "(stopping)";
case LowEnergyScanner::State::kInitiating:
return "(initiating)";
case LowEnergyScanner::State::kActiveScanning:
return "(active scanning)";
case LowEnergyScanner::State::kPassiveScanning:
return "(passive scanning)";
default:
break;
}
BT_PANIC("invalid scanner state: %u", static_cast<unsigned int>(state));
return "(unknown)";
}
void LowEnergyScanResult::AppendData(const ByteBuffer& data) {
size_t bytes_needed = data_size_ + data.size();
if (buffer_.size() < bytes_needed) {
buffer_.expand(bytes_needed);
}
buffer_.Write(data, data_size_);
data_size_ += data.size();
}
LowEnergyScanResult& LowEnergyScanResult::operator=(
const LowEnergyScanResult& other) {
address_ = other.address_;
resolved_ = other.resolved_;
connectable_ = other.connectable_;
rssi_ = other.rssi_;
data_size_ = other.data_size_;
if (buffer_.size() < other.buffer_.size()) {
buffer_.expand(other.buffer_.size());
}
other.buffer_.Copy(&buffer_);
return *this;
}
LowEnergyScanner::PendingScanResult::PendingScanResult(
LowEnergyScanResult&& result,
pw::async::Dispatcher& dispatcher,
pw::chrono::SystemClock::duration timeout,
fit::closure timeout_handler)
: result_(std::move(result)), timeout_(timeout), timeout_task_(dispatcher) {
timeout_task_.set_function(
[timeout_handler = std::move(timeout_handler)](pw::async::Context /*ctx*/,
pw::Status status) {
if (status.ok()) {
timeout_handler();
}
});
StartTimer();
}
LowEnergyScanner::LowEnergyScanner(LocalAddressDelegate* local_addr_delegate,
hci::Transport::WeakPtr hci,
pw::async::Dispatcher& pw_dispatcher)
: pw_dispatcher_(pw_dispatcher),
scan_timeout_task_(pw_dispatcher_),
local_addr_delegate_(local_addr_delegate),
hci_(std::move(hci)) {
BT_DEBUG_ASSERT(local_addr_delegate_);
BT_DEBUG_ASSERT(hci_.is_alive());
hci_cmd_runner_ = std::make_unique<SequentialCommandRunner>(
hci_->command_channel()->AsWeakPtr());
scan_timeout_task_.set_function(
[this](pw::async::Context /*ctx*/, pw::Status status) {
if (status.ok() && IsScanning()) {
StopScanInternal(false);
}
});
}
void LowEnergyScanner::AddPendingResult(LowEnergyScanResult&& scan_result) {
auto pending = std::make_unique<PendingScanResult>(
std::move(scan_result),
pw_dispatcher_,
scan_response_timeout_,
[this, address = scan_result.address()] {
std::unique_ptr<PendingScanResult> result =
RemovePendingResult(address);
delegate()->OnPeerFound(result->result());
});
pending_results_.emplace(scan_result.address(), std::move(pending));
}
std::unique_ptr<LowEnergyScanner::PendingScanResult>
LowEnergyScanner::RemovePendingResult(const DeviceAddress& address) {
auto node = pending_results_.extract(address);
if (node.empty()) {
return nullptr;
}
node.mapped()->CancelTimeout();
return std::move(node.mapped());
}
bool LowEnergyScanner::StartScan(const ScanOptions& options,
ScanStatusCallback callback) {
BT_ASSERT(callback);
BT_ASSERT(options.window < options.interval);
if (state_ != State::kIdle) {
bt_log(ERROR,
"hci-le",
"cannot start scan while in state: %s",
ScanStateToString(state_).c_str());
return false;
}
state_ = State::kInitiating;
scan_response_timeout_ = options.scan_response_timeout;
scan_cb_ = std::move(callback);
// Obtain the local address type.
local_addr_delegate_->EnsureLocalAddress(
[this, options, callback = std::move(callback)](
const auto& address) mutable {
StartScanInternal(address, options, std::move(callback));
});
return true;
}
void LowEnergyScanner::StartScanInternal(const DeviceAddress& local_address,
const ScanOptions& options,
ScanStatusCallback callback) {
// Check if the scan request was canceled by StopScan() while we were waiting
// for the local address.
if (state_ != State::kInitiating) {
bt_log(DEBUG,
"hci-le",
"scan request was canceled while obtaining local address");
return;
}
bt_log(DEBUG,
"hci-le",
"requesting scan (%s, address: %s, interval: %#.4x, window: %#.4x)",
(options.active ? "active" : "passive"),
local_address.ToString().c_str(),
options.interval,
options.window);
EmbossCommandPacket scan_params_command =
BuildSetScanParametersPacket(local_address, options);
EmbossCommandPacket scan_enable_command = BuildEnablePacket(
options, pw::bluetooth::emboss::GenericEnableParam::ENABLE);
hci_cmd_runner_->QueueCommand(std::move(scan_params_command));
hci_cmd_runner_->QueueCommand(std::move(scan_enable_command));
hci_cmd_runner_->RunCommands([this,
active = options.active,
period = options.period](Result<> status) {
BT_DEBUG_ASSERT(scan_cb_);
BT_DEBUG_ASSERT(state_ == State::kInitiating);
if (status.is_error()) {
if (status == ToResult(HostError::kCanceled)) {
bt_log(DEBUG, "hci-le", "scan canceled");
return;
}
bt_log(ERROR, "hci-le", "failed to start scan: %s", bt_str(status));
state_ = State::kIdle;
scan_cb_(ScanStatus::kFailed);
return;
}
// Schedule the timeout.
if (period != kPeriodInfinite) {
scan_timeout_task_.PostAfter(period);
}
if (active) {
state_ = State::kActiveScanning;
scan_cb_(ScanStatus::kActive);
} else {
state_ = State::kPassiveScanning;
scan_cb_(ScanStatus::kPassive);
}
});
}
bool LowEnergyScanner::StopScan() {
if (state_ == State::kStopping || state_ == State::kIdle) {
bt_log(DEBUG,
"hci-le",
"cannot stop scan while in state: %s",
ScanStateToString(state_).c_str());
return false;
}
// Scan is either being initiated or already running. Cancel any in-flight HCI
// command sequence.
if (!hci_cmd_runner_->IsReady()) {
hci_cmd_runner_->Cancel();
}
// We'll tell the controller to stop scanning even if it is not (this is OK
// because the command will have no effect; see Core Spec v5.0, Vol 2, Part E,
// Section 7.8.11, paragraph 4).
StopScanInternal(true);
return true;
}
void LowEnergyScanner::StopScanInternal(bool stopped_by_user) {
BT_DEBUG_ASSERT(scan_cb_);
scan_timeout_task_.Cancel();
state_ = State::kStopping;
// Notify any pending scan results unless the scan was terminated by the user.
if (!stopped_by_user) {
for (auto& result : pending_results_) {
const std::unique_ptr<PendingScanResult>& pending = result.second;
delegate_->OnPeerFound(pending->result());
}
}
// Either way clear all results from the previous scan period.
pending_results_.clear();
BT_DEBUG_ASSERT(hci_cmd_runner_->IsReady());
// Tell the controller to stop scanning.
ScanOptions options;
EmbossCommandPacket command = BuildEnablePacket(
options, pw::bluetooth::emboss::GenericEnableParam::DISABLE);
hci_cmd_runner_->QueueCommand(std::move(command));
hci_cmd_runner_->RunCommands([this, stopped_by_user](Result<> status) {
BT_DEBUG_ASSERT(scan_cb_);
BT_DEBUG_ASSERT(state_ == State::kStopping);
state_ = State::kIdle;
// Something went wrong but there isn't really a meaningful way to recover,
// so we just fall through and notify the caller with ScanStatus::kFailed
// instead.
bt_is_error(
status, WARN, "hci-le", "failed to stop scan: %s", bt_str(status));
ScanStatus scan_status = ScanStatus::kFailed;
if (status.is_error()) {
scan_status = ScanStatus::kFailed;
} else if (stopped_by_user) {
scan_status = ScanStatus::kStopped;
} else {
scan_status = ScanStatus::kComplete;
}
scan_cb_(scan_status);
});
}
} // namespace bt::hci