blob: 23cae07ec0667e6bea2af2977fca93c31bbaf14a [file] [log] [blame]
// Copyright 2023 The Pigweed Authors
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
// use this file except in compliance with the License. You may obtain a copy of
// the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations under
// the License.
#include "pw_bluetooth_sapphire/internal/host/hci/low_energy_advertiser.h"
#include "pw_bluetooth_sapphire/internal/host/hci/sequential_command_runner.h"
namespace bt::hci {
LowEnergyAdvertiser::LowEnergyAdvertiser(hci::Transport::WeakPtr hci)
: hci_(std::move(hci)),
hci_cmd_runner_(std::make_unique<SequentialCommandRunner>(
hci_->command_channel()->AsWeakPtr())) {}
fit::result<HostError> LowEnergyAdvertiser::CanStartAdvertising(
const DeviceAddress& address,
const AdvertisingData& data,
const AdvertisingData& scan_rsp,
const AdvertisingOptions& options) const {
BT_ASSERT(address.type() != DeviceAddress::Type::kBREDR);
if (options.anonymous) {
bt_log(WARN, "hci-le", "anonymous advertising not supported");
return fit::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 fit::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 fit::error(HostError::kScanResponseTooLong);
}
return fit::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
EmbossCommandPacket packet = BuildEnablePacket(
address, pw::bluetooth::emboss::GenericEnableParam::DISABLE);
hci_cmd_runner_->QueueCommand(packet);
}
// Set advertising parameters
pw::bluetooth::emboss::LEAdvertisingType type =
pw::bluetooth::emboss::LEAdvertisingType::NOT_CONNECTABLE_UNDIRECTED;
if (connect_callback) {
type = pw::bluetooth::emboss::LEAdvertisingType::
CONNECTABLE_AND_SCANNABLE_UNDIRECTED;
} else if (scan_rsp.CalculateBlockSize() > 0) {
type = pw::bluetooth::emboss::LEAdvertisingType::SCANNABLE_UNDIRECTED;
}
pw::bluetooth::emboss::LEOwnAddressType own_addr_type;
if (address.type() == DeviceAddress::Type::kLEPublic) {
own_addr_type = pw::bluetooth::emboss::LEOwnAddressType::PUBLIC;
} else {
own_addr_type = pw::bluetooth::emboss::LEOwnAddressType::RANDOM;
}
data.Copy(&staged_parameters_.data);
scan_rsp.Copy(&staged_parameters_.scan_rsp);
using PacketPtr = std::unique_ptr<CommandPacket>;
CommandChannel::CommandPacketVariant set_adv_params_packet =
BuildSetAdvertisingParams(address, type, own_addr_type, interval);
if (std::holds_alternative<PacketPtr>(set_adv_params_packet) &&
!std::get<PacketPtr>(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>;
CommandChannel::CommandPacketVariant set_adv_data_packet =
BuildSetAdvertisingData(address, staged_parameters_.data, flags);
if (std::holds_alternative<PacketPtr>(set_adv_data_packet) &&
!std::get<PacketPtr>(set_adv_data_packet)) {
bt_log(WARN,
"hci-le",
"cannot build HCI set advertising data packet for %s",
bt_str(address));
return false;
}
CommandChannel::CommandPacketVariant set_scan_rsp_packet =
BuildSetScanResponse(address, staged_parameters_.scan_rsp);
if (std::holds_alternative<PacketPtr>(set_scan_rsp_packet) &&
!std::get<PacketPtr>(set_scan_rsp_packet)) {
bt_log(WARN,
"hci-le",
"cannot build HCI set scan response data packet for %s",
bt_str(address));
return false;
}
EmbossCommandPacket enable_packet = BuildEnablePacket(
address, pw::bluetooth::emboss::GenericEnableParam::ENABLE);
hci_cmd_runner_->QueueCommand(std::move(set_adv_data_packet));
hci_cmd_runner_->QueueCommand(std::move(set_scan_rsp_packet));
hci_cmd_runner_->QueueCommand(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) {
EmbossCommandPacket disable_packet = BuildEnablePacket(
address, pw::bluetooth::emboss::GenericEnableParam::DISABLE);
using PacketPtr = std::unique_ptr<hci::CommandPacket>;
hci::CommandChannel::CommandPacketVariant unset_scan_rsp_packet =
BuildUnsetScanResponse(address);
if (std::holds_alternative<PacketPtr>(unset_scan_rsp_packet) &&
!std::get<PacketPtr>(unset_scan_rsp_packet)) {
bt_log(WARN,
"hci-le",
"cannot build HCI unset scan rsp packet for %s",
bt_str(address));
return false;
}
hci::CommandChannel::CommandPacketVariant unset_adv_data_packet =
BuildUnsetAdvertisingData(address);
if (std::holds_alternative<PacketPtr>(unset_adv_data_packet) &&
!std::get<PacketPtr>(unset_adv_data_packet)) {
bt_log(WARN,
"hci-le",
"cannot build HCI unset advertising data packet for %s",
bt_str(address));
return false;
}
EmbossCommandPacket remove_packet = BuildRemoveAdvertisingSet(address);
hci_cmd_runner_->QueueCommand(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(remove_packet);
return true;
}
void LowEnergyAdvertiser::CompleteIncomingConnection(
hci_spec::ConnectionHandle handle,
pw::bluetooth::emboss::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