blob: ce3b36ac81a49b760fcc169824368f64c9343cf3 [file] [log] [blame]
// Copyright 2021 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 "low_energy_advertiser.h"
#include "lib/async/default.h"
#include "src/connectivity/bluetooth/core/bt-host/hci/sequential_command_runner.h"
namespace bt::hci {
LowEnergyAdvertiser::LowEnergyAdvertiser(fxl::WeakPtr<Transport> hci)
: hci_(std::move(hci)),
hci_cmd_runner_(
std::make_unique<SequentialCommandRunner>(async_get_default_dispatcher(), hci_)) {}
fitx::result<HostError> LowEnergyAdvertiser::CanStartAdvertising(
const DeviceAddress& address, const AdvertisingData& data, const AdvertisingData& scan_rsp,
const AdvertisingOptions& options) const {
ZX_ASSERT(address.type() != DeviceAddress::Type::kBREDR);
if (options.anonymous) {
bt_log(WARN, "hci-le", "anonymous advertising not supported");
return fitx::error(HostError::kNotSupported);
}
// If the TX Power Level is requested, ensure both buffers have enough space.
size_t size_limit = GetSizeLimit();
if (options.include_tx_power_level) {
size_limit -= kTLVTxPowerLevelSize;
}
if (size_t size = data.CalculateBlockSize(/*include_flags=*/true); size > size_limit) {
bt_log(WARN, "hci-le", "advertising data too large (actual: %zu, max: %zu)", size, size_limit);
return fitx::error(HostError::kAdvertisingDataTooLong);
}
if (size_t size = scan_rsp.CalculateBlockSize(/*include_flags=*/false); size > size_limit) {
bt_log(WARN, "hci-le", "scan response too large (actual: %zu, max: %zu)", size, size_limit);
return fitx::error(HostError::kScanResponseTooLong);
}
return fitx::ok();
}
void LowEnergyAdvertiser::StartAdvertisingInternal(
const DeviceAddress& address, const AdvertisingData& data, const AdvertisingData& scan_rsp,
AdvertisingIntervalRange interval, AdvFlags flags, ConnectionCallback connect_callback,
hci::ResultFunction<> result_callback) {
if (IsAdvertising(address)) {
// Temporarily disable advertising so we can tweak the parameters
std::unique_ptr<CommandPacket> packet =
BuildEnablePacket(address, hci_spec::GenericEnableParam::kDisable);
if (!packet) {
bt_log(WARN, "hci-le", "cannot build HCI disable packet for %s", bt_str(address));
result_callback(ToResult(HostError::kCanceled));
return;
}
hci_cmd_runner_->QueueCommand(std::move(packet));
}
// Set advertising parameters
hci_spec::LEAdvertisingType type = hci_spec::LEAdvertisingType::kAdvNonConnInd;
if (connect_callback) {
type = hci_spec::LEAdvertisingType::kAdvInd;
} else if (scan_rsp.CalculateBlockSize() > 0) {
type = hci_spec::LEAdvertisingType::kAdvScanInd;
}
hci_spec::LEOwnAddressType own_addr_type;
if (address.type() == DeviceAddress::Type::kLEPublic) {
own_addr_type = hci_spec::LEOwnAddressType::kPublic;
} else {
own_addr_type = hci_spec::LEOwnAddressType::kRandom;
}
data.Copy(&staged_parameters_.data);
scan_rsp.Copy(&staged_parameters_.scan_rsp);
std::unique_ptr<CommandPacket> set_adv_params_packet =
BuildSetAdvertisingParams(address, type, own_addr_type, interval);
if (!set_adv_params_packet) {
bt_log(WARN, "hci-le", "cannot build HCI set params packet for %s", bt_str(address));
result_callback(ToResult(HostError::kCanceled));
return;
}
hci_cmd_runner_->QueueCommand(
std::move(set_adv_params_packet),
fit::bind_member<&LowEnergyAdvertiser::OnSetAdvertisingParamsComplete>(this));
// In order to support use cases where advertisers use the return parameters of the
// SetAdvertisingParams HCI command, we place the remaining advertising setup HCI commands in the
// result callback here. SequentialCommandRunner doesn't allow enqueuing commands within a
// callback (during a run).
hci_cmd_runner_->RunCommands(
[this, address, flags, result_callback = std::move(result_callback),
connect_callback = std::move(connect_callback)](hci::Result<> result) mutable {
if (bt_is_error(result, WARN, "hci-le", "failed to start advertising for %s",
bt_str(address))) {
result_callback(result);
return;
}
bool success = StartAdvertisingInternalStep2(address, flags, std::move(connect_callback),
std::move(result_callback));
if (!success) {
result_callback(ToResult(HostError::kCanceled));
}
});
}
bool LowEnergyAdvertiser::StartAdvertisingInternalStep2(const DeviceAddress& address,
AdvFlags flags,
ConnectionCallback connect_callback,
hci::ResultFunction<> result_callback) {
using PacketPtr = std::unique_ptr<CommandPacket>;
PacketPtr set_adv_data_packet = BuildSetAdvertisingData(address, staged_parameters_.data, flags);
if (!set_adv_data_packet) {
bt_log(WARN, "hci-le", "cannot build HCI set advertising data packet for %s", bt_str(address));
return false;
}
PacketPtr set_scan_rsp_packet = BuildSetScanResponse(address, staged_parameters_.scan_rsp);
if (!set_scan_rsp_packet) {
bt_log(WARN, "hci-le", "cannot build HCI set scan response data packet for %s",
bt_str(address));
return false;
}
PacketPtr enable_packet = BuildEnablePacket(address, hci_spec::GenericEnableParam::kEnable);
if (!enable_packet) {
bt_log(WARN, "hci-le", "cannot build HCI enable packet for %s", bt_str(address));
return false;
}
hci_cmd_runner_->QueueCommand(std::move(set_adv_data_packet));
hci_cmd_runner_->QueueCommand(std::move(set_scan_rsp_packet));
hci_cmd_runner_->QueueCommand(std::move(enable_packet));
staged_parameters_.reset();
hci_cmd_runner_->RunCommands(
[this, address, result_callback = std::move(result_callback),
connect_callback = std::move(connect_callback)](Result<> result) mutable {
if (bt_is_error(result, WARN, "hci-le", "failed to start advertising for %s",
bt_str(address))) {
} else {
bt_log(INFO, "hci-le", "advertising enabled for %s", bt_str(address));
connection_callbacks_.emplace(address, std::move(connect_callback));
}
result_callback(result);
OnCurrentOperationComplete();
});
return true;
}
// We have StopAdvertising(address) so one would naturally think to implement StopAdvertising() by
// iterating through all addresses and calling StopAdvertising(address) on each iteration. However,
// such an implementation won't work. Each call to StopAdvertising(address) checks if the command
// runner is running, cancels any pending commands if it is, and then issues new ones. Called in
// quick succession, StopAdvertising(address) won't have a chance to finish its previous HCI
// commands before being cancelled. Instead, we must enqueue them all at once and then run them
// together.
void LowEnergyAdvertiser::StopAdvertising() {
if (!hci_cmd_runner_->IsReady()) {
hci_cmd_runner_->Cancel();
}
for (auto itr = connection_callbacks_.begin(); itr != connection_callbacks_.end();) {
const DeviceAddress& address = itr->first;
bool success = EnqueueStopAdvertisingCommands(address);
if (success) {
itr = connection_callbacks_.erase(itr);
} else {
bt_log(WARN, "hci-le", "cannot stop advertising for %s", bt_str(address));
itr++;
}
}
if (hci_cmd_runner_->HasQueuedCommands()) {
hci_cmd_runner_->RunCommands([this](hci::Result<> result) {
bt_log(INFO, "hci-le", "advertising stopped: %s", bt_str(result));
OnCurrentOperationComplete();
});
}
}
void LowEnergyAdvertiser::StopAdvertisingInternal(const DeviceAddress& address) {
if (!IsAdvertising(address)) {
return;
}
bool success = EnqueueStopAdvertisingCommands(address);
if (!success) {
bt_log(WARN, "hci-le", "cannot stop advertising for %s", bt_str(address));
return;
}
hci_cmd_runner_->RunCommands([this, address](Result<> result) {
bt_log(INFO, "hci-le", "advertising stopped for %s: %s", bt_str(address), bt_str(result));
OnCurrentOperationComplete();
});
connection_callbacks_.erase(address);
}
bool LowEnergyAdvertiser::EnqueueStopAdvertisingCommands(const DeviceAddress& address) {
std::unique_ptr<CommandPacket> disable_packet =
BuildEnablePacket(address, hci_spec::GenericEnableParam::kDisable);
if (!disable_packet) {
bt_log(WARN, "hci-le", "cannot build HCI disable packet for %s", bt_str(address));
return false;
}
std::unique_ptr<CommandPacket> unset_scan_rsp_packet = BuildUnsetScanResponse(address);
if (!unset_scan_rsp_packet) {
bt_log(WARN, "hci-le", "cannot build HCI unset scan rsp packet for %s", bt_str(address));
return false;
}
std::unique_ptr<CommandPacket> unset_adv_data_packet = BuildUnsetAdvertisingData(address);
if (!unset_adv_data_packet) {
bt_log(WARN, "hci-le", "cannot build HCI unset advertising data packet for %s",
bt_str(address));
return false;
}
std::unique_ptr<CommandPacket> remove_packet = BuildRemoveAdvertisingSet(address);
if (!remove_packet) {
bt_log(WARN, "hci-le", "cannot build HCI remove packet for %s", bt_str(address));
return false;
}
hci_cmd_runner_->QueueCommand(std::move(disable_packet));
hci_cmd_runner_->QueueCommand(std::move(unset_scan_rsp_packet));
hci_cmd_runner_->QueueCommand(std::move(unset_adv_data_packet));
hci_cmd_runner_->QueueCommand(std::move(remove_packet));
return true;
}
void LowEnergyAdvertiser::CompleteIncomingConnection(
hci_spec::ConnectionHandle handle, hci_spec::ConnectionRole role,
const DeviceAddress& local_address, const DeviceAddress& peer_address,
const hci_spec::LEConnectionParameters& conn_params) {
// Immediately construct a Connection object. If this object goes out of scope following the error
// checks below, it will send the a command to disconnect the link.
std::unique_ptr<LowEnergyConnection> link = std::make_unique<LowEnergyConnection>(
handle, local_address, peer_address, conn_params, role, hci());
if (!IsAdvertising(local_address)) {
bt_log(DEBUG, "hci-le",
"connection received without advertising address (role: %d, local address: %s, peer "
"address: %s, connection parameters: %s)",
static_cast<uint8_t>(role), bt_str(local_address), bt_str(peer_address),
bt_str(conn_params));
return;
}
if (!connection_callbacks_[local_address]) {
bt_log(WARN, "hci-le",
"connection received when not connectable (role: %d, local address: %s, peer "
"address: %s, connection parameters: %s)",
static_cast<uint8_t>(role), bt_str(local_address), bt_str(peer_address),
bt_str(conn_params));
return;
}
ConnectionCallback connect_callback = std::move(connection_callbacks_[local_address]);
StopAdvertising(local_address);
connect_callback(std::move(link));
}
} // namespace bt::hci