blob: abb56b5cadb792df24dd3c1e00681763a81b855a [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 "src/connectivity/bluetooth/core/bt-host/hci/extended_low_energy_advertiser.h"
#include "src/connectivity/bluetooth/core/bt-host/hci-spec/util.h"
#include "src/connectivity/bluetooth/core/bt-host/transport/transport.h"
namespace bt::hci {
ExtendedLowEnergyAdvertiser::ExtendedLowEnergyAdvertiser(fxl::WeakPtr<Transport> hci_ptr)
: LowEnergyAdvertiser(std::move(hci_ptr)), weak_ptr_factory_(this) {
auto self = weak_ptr_factory_.GetWeakPtr();
set_terminated_event_handler_id_ = hci()->command_channel()->AddLEMetaEventHandler(
hci_spec::kLEAdvertisingSetTerminatedSubeventCode, [self](const EventPacket& event_packet) {
if (self) {
return self->OnAdvertisingSetTerminatedEvent(event_packet);
}
return CommandChannel::EventCallbackResult::kRemove;
});
}
ExtendedLowEnergyAdvertiser::~ExtendedLowEnergyAdvertiser() {
hci()->command_channel()->RemoveEventHandler(set_terminated_event_handler_id_);
StopAdvertising();
}
std::unique_ptr<CommandPacket> ExtendedLowEnergyAdvertiser::BuildEnablePacket(
const DeviceAddress& address, hci_spec::GenericEnableParam enable) {
// We only enable or disable a single address at a time. The multiply by 1 is set explicitly to
// show that data[] within LESetExtendedAdvertisingEnableData is of size 1.
constexpr size_t kPayloadSize = sizeof(hci_spec::LESetExtendedAdvertisingEnableCommandParams) +
1 * sizeof(hci_spec::LESetExtendedAdvertisingEnableData);
std::unique_ptr<CommandPacket> packet =
CommandPacket::New(hci_spec::kLESetExtendedAdvertisingEnable, kPayloadSize);
packet->mutable_view()->mutable_payload_data().SetToZeros();
auto payload = packet->mutable_payload<hci_spec::LESetExtendedAdvertisingEnableCommandParams>();
payload->enable = enable;
payload->number_of_sets = 1;
std::optional<hci_spec::AdvertisingHandle> handle = advertising_handle_map_.GetHandle(address);
ZX_ASSERT(handle);
// TODO(fxbug.dev/77614): advertising currently continues until disabled. We should provide
// options to the user to advertise only a set amount of times. Duration can already be controlled
// via the client closing the FIDL protocol after a set amount of time. We don't want to
// support a second way to enable duration.
hci_spec::LESetExtendedAdvertisingEnableData enable_data;
enable_data.adv_handle = handle.value();
enable_data.max_extended_adv_events = hci_spec::kNoMaxExtendedAdvertisingEvents;
enable_data.duration = hci_spec::kNoAdvertisingDuration;
auto buffer = packet->mutable_view()->mutable_payload_data().mutable_view();
buffer.WriteObj(enable_data, sizeof(hci_spec::LESetExtendedAdvertisingEnableCommandParams));
return packet;
}
std::unique_ptr<CommandPacket> ExtendedLowEnergyAdvertiser::BuildSetAdvertisingParams(
const DeviceAddress& address, hci_spec::LEAdvertisingType type,
hci_spec::LEOwnAddressType own_address_type, AdvertisingIntervalRange interval) {
constexpr size_t kPayloadSize = sizeof(hci_spec::LESetExtendedAdvertisingParametersCommandParams);
std::unique_ptr<CommandPacket> packet =
CommandPacket::New(hci_spec::kLESetExtendedAdvertisingParameters, kPayloadSize);
packet->mutable_view()->mutable_payload_data().SetToZeros();
auto payload =
packet->mutable_payload<hci_spec::LESetExtendedAdvertisingParametersCommandParams>();
// advertising handle
std::optional<hci_spec::AdvertisingHandle> handle = advertising_handle_map_.MapHandle(address);
if (!handle) {
bt_log(WARN, "hci-le",
"could not allocate a new advertising handle for address: %s (all in use)",
bt_str(address));
return nullptr;
}
payload->adv_handle = handle.value();
// advertising event properties
std::optional<hci_spec::AdvertisingEventBits> bits = AdvertisingTypeToEventBits(type);
if (!bits) {
bt_log(WARN, "hci-le", "could not generate event bits for type: %hhu", type);
return nullptr;
}
payload->adv_event_properties = bits.value();
// advertising interval, NOTE: LE advertising parameters allow for up to 3 octets (10 ms to
// 10428 s) to configure an advertising interval. However, we expose only the recommended
// advertising interval configurations to users, as specified in the Bluetooth Spec Volume 3, Part
// C, Appendix A. These values are expressed as uint16_t so we simply copy them (taking care of
// endianness) into the 3 octets as is.
hci_spec::EncodeLegacyAdvertisingInterval(interval.min(), payload->primary_adv_interval_min);
hci_spec::EncodeLegacyAdvertisingInterval(interval.max(), payload->primary_adv_interval_max);
// other settings
payload->primary_adv_channel_map = hci_spec::kLEAdvertisingChannelAll;
payload->own_address_type = own_address_type;
payload->adv_filter_policy = hci_spec::LEAdvFilterPolicy::kAllowAll;
payload->adv_tx_power = hci_spec::kLEExtendedAdvertisingTxPowerNoPreference;
payload->scan_request_notification_enable = hci_spec::GenericEnableParam::kDisable;
// TODO(fxbug.dev/81470): using legacy PDUs requires advertisements on the LE 1M PHY.
payload->primary_adv_phy = hci_spec::LEPHY::kLE1M;
payload->secondary_adv_phy = hci_spec::LEPHY::kLE1M;
// Payload values were initialized to zero above. By not setting the values for the following
// fields, we are purposely ignoring them:
//
// advertising_sid: We use only legacy PDUs, the controller ignores this field in that case
// peer_address: We don't support directed advertising yet
// peer_address_type: We don't support directed advertising yet
// secondary_adv_max_skip: We use only legacy PDUs, the controller ignores this field in that case
return packet;
}
std::unique_ptr<CommandPacket> ExtendedLowEnergyAdvertiser::BuildSetAdvertisingData(
const DeviceAddress& address, const AdvertisingData& data, AdvFlags flags) {
AdvertisingData adv_data;
data.Copy(&adv_data);
if (staged_advertising_parameters_.include_tx_power_level) {
adv_data.SetTxPower(staged_advertising_parameters_.selected_tx_power_level);
}
size_t block_size = adv_data.CalculateBlockSize(/*include_flags=*/true);
size_t kPayloadSize = sizeof(hci_spec::LESetExtendedAdvertisingDataCommandParams) + block_size;
std::unique_ptr<CommandPacket> packet =
CommandPacket::New(hci_spec::kLESetExtendedAdvertisingData, kPayloadSize);
packet->mutable_view()->mutable_payload_data().SetToZeros();
auto payload = packet->mutable_payload<hci_spec::LESetExtendedAdvertisingDataCommandParams>();
// advertising handle
std::optional<hci_spec::AdvertisingHandle> handle = advertising_handle_map_.GetHandle(address);
ZX_ASSERT(handle);
payload->adv_handle = handle.value();
// TODO(fxbug.dev/81470): We support only legacy PDUs and do not support fragmented extended
// advertising data at this time.
payload->operation = hci_spec::LESetExtendedAdvDataOp::kComplete;
payload->fragment_preference = hci_spec::LEExtendedAdvFragmentPreference::kShouldNotFragment;
// advertising data
payload->adv_data_length = block_size;
MutableBufferView data_view(payload->adv_data, payload->adv_data_length);
adv_data.WriteBlock(&data_view, flags);
return packet;
}
std::unique_ptr<CommandPacket> ExtendedLowEnergyAdvertiser::BuildUnsetAdvertisingData(
const DeviceAddress& address) {
std::unique_ptr<CommandPacket> packet =
CommandPacket::New(hci_spec::kLESetExtendedAdvertisingData,
sizeof(hci_spec::LESetExtendedAdvertisingDataCommandParams));
packet->mutable_view()->mutable_payload_data().SetToZeros();
auto payload = packet->mutable_payload<hci_spec::LESetExtendedAdvertisingDataCommandParams>();
// advertising handle
std::optional<hci_spec::AdvertisingHandle> handle = advertising_handle_map_.GetHandle(address);
ZX_ASSERT(handle);
payload->adv_handle = handle.value();
// TODO(fxbug.dev/81470): We support only legacy PDUs and do not support fragmented extended
// advertising data at this time.
payload->operation = hci_spec::LESetExtendedAdvDataOp::kComplete;
payload->fragment_preference = hci_spec::LEExtendedAdvFragmentPreference::kShouldNotFragment;
payload->adv_data_length = 0;
return packet;
}
std::unique_ptr<CommandPacket> ExtendedLowEnergyAdvertiser::BuildSetScanResponse(
const DeviceAddress& address, const AdvertisingData& data) {
AdvertisingData scan_rsp;
data.Copy(&scan_rsp);
if (staged_advertising_parameters_.include_tx_power_level) {
scan_rsp.SetTxPower(staged_advertising_parameters_.selected_tx_power_level);
}
size_t block_size = scan_rsp.CalculateBlockSize();
size_t kPayloadSize = sizeof(hci_spec::LESetExtendedScanResponseDataCommandParams) + block_size;
std::unique_ptr<CommandPacket> packet =
CommandPacket::New(hci_spec::kLESetExtendedScanResponseData, kPayloadSize);
packet->mutable_view()->mutable_payload_data().SetToZeros();
auto payload = packet->mutable_payload<hci_spec::LESetExtendedScanResponseDataCommandParams>();
// advertising handle
std::optional<hci_spec::AdvertisingHandle> handle = advertising_handle_map_.GetHandle(address);
ZX_ASSERT(handle);
payload->adv_handle = handle.value();
// TODO(fxbug.dev/81470): We support only legacy PDUs and do not support fragmented extended
// advertising data at this time.
payload->operation = hci_spec::LESetExtendedAdvDataOp::kComplete;
payload->fragment_preference = hci_spec::LEExtendedAdvFragmentPreference::kShouldNotFragment;
// scan response data
payload->scan_rsp_data_length = block_size;
MutableBufferView scan_rsp_view(payload->scan_rsp_data, payload->scan_rsp_data_length);
scan_rsp.WriteBlock(&scan_rsp_view, std::nullopt);
return packet;
}
std::unique_ptr<CommandPacket> ExtendedLowEnergyAdvertiser::BuildUnsetScanResponse(
const DeviceAddress& address) {
std::unique_ptr<CommandPacket> packet =
CommandPacket::New(hci_spec::kLESetExtendedScanResponseData,
sizeof(hci_spec::LESetExtendedScanResponseDataCommandParams));
packet->mutable_view()->mutable_payload_data().SetToZeros();
auto payload = packet->mutable_payload<hci_spec::LESetExtendedScanResponseDataCommandParams>();
// advertising handle
std::optional<hci_spec::AdvertisingHandle> handle = advertising_handle_map_.GetHandle(address);
ZX_ASSERT(handle);
payload->adv_handle = handle.value();
// TODO(fxbug.dev/81470): We support only legacy PDUs and do not support fragmented extended
// advertising data at this time.
payload->operation = hci_spec::LESetExtendedAdvDataOp::kComplete;
payload->fragment_preference = hci_spec::LEExtendedAdvFragmentPreference::kShouldNotFragment;
payload->scan_rsp_data_length = 0;
return packet;
}
std::unique_ptr<CommandPacket> ExtendedLowEnergyAdvertiser::BuildRemoveAdvertisingSet(
const DeviceAddress& address) {
auto packet = CommandPacket::New(hci_spec::kLERemoveAdvertisingSet,
sizeof(hci_spec::LERemoveAdvertisingSetCommandParams));
auto payload = packet->mutable_payload<hci_spec::LERemoveAdvertisingSetCommandParams>();
std::optional<hci_spec::AdvertisingHandle> handle = advertising_handle_map_.GetHandle(address);
ZX_ASSERT(handle);
payload->adv_handle = handle.value();
return packet;
}
void ExtendedLowEnergyAdvertiser::OnSetAdvertisingParamsComplete(const EventPacket& event) {
ZX_ASSERT(event.event_code() == hci_spec::kCommandCompleteEventCode);
ZX_ASSERT(event.params<hci_spec::CommandCompleteEventParams>().command_opcode ==
hci_spec::kLESetExtendedAdvertisingParameters);
Result<> result = event.ToResult();
if (bt_is_error(result, WARN, "hci-le", "set advertising parameters, error received: %s",
bt_str(result))) {
return; // full error handling done in super class, can just return here
}
auto params = event.return_params<hci_spec::LESetExtendedAdvertisingParametersReturnParams>();
ZX_ASSERT(params);
if (staged_advertising_parameters_.include_tx_power_level) {
staged_advertising_parameters_.selected_tx_power_level = params->selected_tx_power;
}
}
void ExtendedLowEnergyAdvertiser::StartAdvertising(const DeviceAddress& address,
const AdvertisingData& data,
const AdvertisingData& scan_rsp,
AdvertisingOptions options,
ConnectionCallback connect_callback,
ResultFunction<> result_callback) {
// if there is an operation currently in progress, enqueue this operation and we will get to it
// the next time we have a chance
if (!hci_cmd_runner().IsReady()) {
bt_log(INFO, "hci-le", "hci cmd runner not ready, queuing advertisement commands for now");
AdvertisingData copied_data;
data.Copy(&copied_data);
AdvertisingData copied_scan_rsp;
scan_rsp.Copy(&copied_scan_rsp);
op_queue_.push([this, address, data = std::move(copied_data),
scan_rsp = std::move(copied_scan_rsp), options,
conn_cb = std::move(connect_callback),
result_cb = std::move(result_callback)]() mutable {
StartAdvertising(address, data, scan_rsp, options, std::move(conn_cb), std::move(result_cb));
});
return;
}
fitx::result<HostError> result = CanStartAdvertising(address, data, scan_rsp, options);
if (result.is_error()) {
result_callback(ToResult(result.error_value()));
return;
}
if (IsAdvertising(address)) {
bt_log(DEBUG, "hci-le", "updating existing advertisement for %s", bt_str(address));
}
std::memset(&staged_advertising_parameters_, 0, sizeof(staged_advertising_parameters_));
staged_advertising_parameters_.include_tx_power_level = options.include_tx_power_level;
// Core Spec, Volume 4, Part E, Section 7.8.58: "the number of advertising sets that can be
// supported is not fixed and the Controller can change it at any time. The memory used to store
// advertising sets can also be used for other purposes."
//
// Depending on the memory profile of the controller, a new advertising set may or may not be
// accepted. We could use HCI_LE_Read_Number_of_Supported_Advertising_Sets to check if the
// controller has space for another advertising set. However, the value may change after the read
// and before the addition of the advertising set. Furthermore, sending an extra HCI command
// increases the latency of our stack. Instead, we simply attempt to add. If the controller is
// unable to support another advertising set, it will respond with a memory capacity exceeded
// error.
StartAdvertisingInternal(address, data, scan_rsp, options.interval, options.flags,
std::move(connect_callback), std::move(result_callback));
}
void ExtendedLowEnergyAdvertiser::StopAdvertising() {
LowEnergyAdvertiser::StopAdvertising();
advertising_handle_map_.Clear();
// std::queue doesn't have a clear method so we have to resort to this tomfoolery :(
decltype(op_queue_) empty;
std::swap(op_queue_, empty);
}
void ExtendedLowEnergyAdvertiser::StopAdvertising(const DeviceAddress& address) {
// if there is an operation currently in progress, enqueue this operation and we will get to it
// the next time we have a chance
if (!hci_cmd_runner().IsReady()) {
bt_log(INFO, "hci-le", "hci cmd runner not ready, queueing stop advertising command for now");
op_queue_.push([this, address]() { StopAdvertising(address); });
return;
}
LowEnergyAdvertiser::StopAdvertisingInternal(address);
advertising_handle_map_.RemoveAddress(address);
}
void ExtendedLowEnergyAdvertiser::OnIncomingConnection(
hci_spec::ConnectionHandle handle, hci_spec::ConnectionRole role,
const DeviceAddress& peer_address, const hci_spec::LEConnectionParameters& conn_params) {
// Core Spec Volume 4, Part E, Section 7.8.56: Incoming connections to LE Extended Advertising
// occur through two events: HCI_LE_Connection_Complete and HCI_LE_Advertising_Set_Terminated.
// This method is called as a result of the HCI_LE_Connection_Complete event. At this point, we
// only have a connection handle but don't know the locally advertised address that the
// connection is for. Until we receive the HCI_LE_Advertising_Set_Terminated event, we stage
// these parameters.
staged_connections_map_[handle] = {role, peer_address, conn_params};
}
// The HCI_LE_Advertising_Set_Terminated event contains the mapping between connection handle and
// advertising handle. After the HCI_LE_Advertising_Set_Terminated event, we have all the
// information necessary to create a connection object within the Host layer.
CommandChannel::EventCallbackResult ExtendedLowEnergyAdvertiser::OnAdvertisingSetTerminatedEvent(
const EventPacket& event) {
ZX_ASSERT(event.event_code() == hci_spec::kLEMetaEventCode);
ZX_ASSERT(event.params<hci_spec::LEMetaEventParams>().subevent_code ==
hci_spec::kLEAdvertisingSetTerminatedSubeventCode);
Result<> result = event.ToResult();
if (bt_is_error(result, ERROR, "hci-le", "advertising set terminated event, error received %s",
bt_str(result))) {
return CommandChannel::EventCallbackResult::kContinue;
}
auto params = event.subevent_params<hci_spec::LEAdvertisingSetTerminatedSubeventParams>();
ZX_ASSERT(params);
hci_spec::ConnectionHandle connection_handle = params->connection_handle;
auto staged_parameters_node = staged_connections_map_.extract(connection_handle);
if (staged_parameters_node.empty()) {
bt_log(ERROR, "hci-le",
"advertising set terminated event, staged params not available "
"(handle: %d)",
params->adv_handle);
return CommandChannel::EventCallbackResult::kContinue;
}
hci_spec::AdvertisingHandle adv_handle = params->adv_handle;
std::optional<DeviceAddress> opt_local_address = advertising_handle_map_.GetAddress(adv_handle);
// We use the identity address as the local address if we aren't advertising or otherwise don't
// know about this advertising set. This is obviously wrong. However, the link will be
// disconnected in that case before it can propagate to higher layers.
static DeviceAddress identity_address = DeviceAddress(DeviceAddress::Type::kLEPublic, {0});
DeviceAddress local_address = identity_address;
if (opt_local_address) {
local_address = opt_local_address.value();
}
StagedConnectionParameters staged = staged_parameters_node.mapped();
CompleteIncomingConnection(connection_handle, staged.role, local_address, staged.peer_address,
staged.conn_params);
std::memset(&staged_advertising_parameters_, 0, sizeof(staged_advertising_parameters_));
return CommandChannel::EventCallbackResult::kContinue;
}
void ExtendedLowEnergyAdvertiser::OnCurrentOperationComplete() {
if (op_queue_.empty()) {
return; // no more queued operations so nothing to do
}
fit::closure closure = std::move(op_queue_.front());
op_queue_.pop();
closure();
}
} // namespace bt::hci