blob: cb96ae23e00f2ff725d215da2ac47fed993b31b7 [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/public/pw_bluetooth_sapphire/internal/host/hci/extended_low_energy_advertiser.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/transport/transport.h"
namespace bt::hci {
namespace pwemb = pw::bluetooth::emboss;
ExtendedLowEnergyAdvertiser::ExtendedLowEnergyAdvertiser(
hci::Transport::WeakPtr hci_ptr, uint16_t max_advertising_data_length)
: LowEnergyAdvertiser(std::move(hci_ptr), max_advertising_data_length) {
event_handler_id_ = hci()->command_channel()->AddLEMetaEventHandler(
hci_spec::kLEAdvertisingSetTerminatedSubeventCode,
[this](const EventPacket& event) {
OnAdvertisingSetTerminatedEvent(event);
return CommandChannel::EventCallbackResult::kContinue;
});
}
ExtendedLowEnergyAdvertiser::~ExtendedLowEnergyAdvertiser() {
// 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_);
// TODO(https://fxbug.dev/42063496): This will only cancel one advertisement,
// after which the SequentialCommandRunner will have been destroyed and no
// further commands will be sent.
StopAdvertising();
}
EmbossCommandPacket ExtendedLowEnergyAdvertiser::BuildEnablePacket(
const DeviceAddress& address,
pwemb::GenericEnableParam enable,
bool extended_pdu) {
// 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 kPacketSize =
pwemb::LESetExtendedAdvertisingEnableCommand::MinSizeInBytes() +
(1 * pwemb::LESetExtendedAdvertisingEnableData::IntrinsicSizeInBytes());
auto packet = hci::EmbossCommandPacket::New<
pwemb::LESetExtendedAdvertisingEnableCommandWriter>(
hci_spec::kLESetExtendedAdvertisingEnable, kPacketSize);
auto view = packet.view_t();
view.enable().Write(enable);
view.num_sets().Write(1);
std::optional<hci_spec::AdvertisingHandle> handle =
advertising_handle_map_.GetHandle(address, extended_pdu);
BT_ASSERT(handle);
view.data()[0].advertising_handle().Write(handle.value());
view.data()[0].duration().Write(hci_spec::kNoAdvertisingDuration);
view.data()[0].max_extended_advertising_events().Write(
hci_spec::kNoMaxExtendedAdvertisingEvents);
return packet;
}
static void WriteAdvertisingEventProperties(
const ExtendedLowEnergyAdvertiser::AdvertisingEventProperties& properties,
pwemb::LESetExtendedAdvertisingParametersV1CommandWriter& view) {
view.advertising_event_properties().connectable().Write(
properties.connectable);
view.advertising_event_properties().scannable().Write(properties.scannable);
view.advertising_event_properties().directed().Write(properties.directed);
view.advertising_event_properties()
.high_duty_cycle_directed_connectable()
.Write(properties.high_duty_cycle_directed_connectable);
view.advertising_event_properties().use_legacy_pdus().Write(
properties.use_legacy_pdus);
view.advertising_event_properties().anonymous_advertising().Write(
properties.anonymous_advertising);
view.advertising_event_properties().include_tx_power().Write(
properties.include_tx_power);
}
std::optional<EmbossCommandPacket>
ExtendedLowEnergyAdvertiser::BuildSetAdvertisingParams(
const DeviceAddress& address,
const AdvertisingEventProperties& properties,
pwemb::LEOwnAddressType own_address_type,
const AdvertisingIntervalRange& interval,
bool extended_pdu) {
auto packet = hci::EmbossCommandPacket::New<
pwemb::LESetExtendedAdvertisingParametersV1CommandWriter>(
hci_spec::kLESetExtendedAdvertisingParameters);
auto view = packet.view_t();
// advertising handle
std::optional<hci_spec::AdvertisingHandle> handle =
advertising_handle_map_.MapHandle(address, extended_pdu);
if (!handle) {
bt_log(WARN,
"hci-le",
"could not allocate advertising handle for address: %s",
bt_str(address));
return std::nullopt;
}
view.advertising_handle().Write(handle.value());
WriteAdvertisingEventProperties(properties, view);
// 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.
view.primary_advertising_interval_min().Write(interval.min());
view.primary_advertising_interval_max().Write(interval.max());
// advertise on all channels
view.primary_advertising_channel_map().channel_37().Write(true);
view.primary_advertising_channel_map().channel_38().Write(true);
view.primary_advertising_channel_map().channel_39().Write(true);
view.own_address_type().Write(own_address_type);
view.advertising_filter_policy().Write(
pwemb::LEAdvertisingFilterPolicy::ALLOW_ALL);
view.advertising_tx_power().Write(
hci_spec::kLEExtendedAdvertisingTxPowerNoPreference);
view.scan_request_notification_enable().Write(
pwemb::GenericEnableParam::DISABLE);
// TODO(https://fxbug.dev/42161929): using legacy PDUs requires advertisements
// on the LE 1M PHY.
view.primary_advertising_phy().Write(pwemb::LEPrimaryAdvertisingPHY::LE_1M);
view.secondary_advertising_phy().Write(
pwemb::LESecondaryAdvertisingPHY::LE_1M);
// 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;
}
// TODO(https://fxbug.dev/330935479): we can reduce code duplication by
// templatizing this method. However, we first have to rename
// advertising_data_length and advertising_data in
// LESetExtendedAdvertisingDataCommand to just data_length and data,
// respectively. We would also have to do the same in
// LESetExtendedScanResponseDataCommand. It's not worth it to do this rename
// right now since Sapphire lives in the Fuchsia repository and we would need to
// do an Emboss naming transition. Once Sapphire is back in the Pigweed
// repository, we can do this in one fell swoop rather than going through the
// Emboss naming transition.
EmbossCommandPacket
ExtendedLowEnergyAdvertiser::BuildAdvertisingDataFragmentPacket(
hci_spec::AdvertisingHandle handle,
const BufferView& data,
pwemb::LESetExtendedAdvDataOp operation,
pwemb::LEExtendedAdvFragmentPreference fragment_preference) {
size_t kPayloadSize =
pwemb::LESetExtendedAdvertisingDataCommandView::MinSizeInBytes().Read() +
data.size();
auto packet = EmbossCommandPacket::New<
pwemb::LESetExtendedAdvertisingDataCommandWriter>(
hci_spec::kLESetExtendedAdvertisingData, kPayloadSize);
auto params = packet.view_t();
params.advertising_handle().Write(handle);
params.operation().Write(operation);
params.fragment_preference().Write(fragment_preference);
params.advertising_data_length().Write(static_cast<uint8_t>(data.size()));
MutableBufferView data_view(params.advertising_data().BackingStorage().data(),
data.size());
data.Copy(&data_view);
return packet;
}
EmbossCommandPacket
ExtendedLowEnergyAdvertiser::BuildScanResponseDataFragmentPacket(
hci_spec::AdvertisingHandle handle,
const BufferView& data,
pwemb::LESetExtendedAdvDataOp operation,
pwemb::LEExtendedAdvFragmentPreference fragment_preference) {
size_t kPayloadSize =
pwemb::LESetExtendedScanResponseDataCommandView::MinSizeInBytes().Read() +
data.size();
auto packet = EmbossCommandPacket::New<
pwemb::LESetExtendedScanResponseDataCommandWriter>(
hci_spec::kLESetExtendedScanResponseData, kPayloadSize);
auto params = packet.view_t();
params.advertising_handle().Write(handle);
params.operation().Write(operation);
params.fragment_preference().Write(fragment_preference);
params.scan_response_data_length().Write(static_cast<uint8_t>(data.size()));
MutableBufferView data_view(
params.scan_response_data().BackingStorage().data(), data.size());
data.Copy(&data_view);
return packet;
}
std::vector<EmbossCommandPacket>
ExtendedLowEnergyAdvertiser::BuildSetAdvertisingData(
const DeviceAddress& address,
const AdvertisingData& data,
AdvFlags flags,
bool extended_pdu) {
if (data.CalculateBlockSize() == 0) {
std::vector<EmbossCommandPacket> packets;
return packets;
}
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);
}
std::optional<hci_spec::AdvertisingHandle> handle =
advertising_handle_map_.GetHandle(address, extended_pdu);
BT_ASSERT(handle);
size_t block_size = adv_data.CalculateBlockSize(/*include_flags=*/true);
DynamicByteBuffer buffer(block_size);
adv_data.WriteBlock(&buffer, flags);
size_t max_length =
pwemb::LESetExtendedAdvertisingDataCommand::advertising_data_length_max();
// If all data fits into a single HCI packet, we don't need to do any
// fragmentation ourselves. The Controller may still perform fragmentation
// over the air but we don't have to when sending the data to the Controller.
if (block_size <= max_length) {
EmbossCommandPacket packet = BuildAdvertisingDataFragmentPacket(
handle.value(),
buffer.view(),
pwemb::LESetExtendedAdvDataOp::COMPLETE,
pwemb::LEExtendedAdvFragmentPreference::SHOULD_NOT_FRAGMENT);
std::vector<EmbossCommandPacket> packets;
packets.reserve(1);
packets.emplace_back(std::move(packet));
return packets;
}
// We have more data than will fit in a single HCI packet. Calculate the
// amount of packets we need to send, perform the fragmentation, and queue up
// the multiple LE Set Extended Advertising Data packets to the Controller.
size_t num_packets = block_size / max_length;
if (block_size % max_length != 0) {
num_packets++;
}
std::vector<EmbossCommandPacket> packets;
packets.reserve(num_packets);
for (size_t i = 0; i < num_packets; i++) {
size_t packet_size = max_length;
pwemb::LESetExtendedAdvDataOp operation =
pwemb::LESetExtendedAdvDataOp::INTERMEDIATE_FRAGMENT;
if (i == 0) {
operation = pwemb::LESetExtendedAdvDataOp::FIRST_FRAGMENT;
} else if (i == num_packets - 1) {
operation = pwemb::LESetExtendedAdvDataOp::LAST_FRAGMENT;
if (block_size % max_length != 0) {
packet_size = block_size % max_length;
}
}
size_t offset = i * max_length;
BufferView buffer_view(buffer.data() + offset, packet_size);
EmbossCommandPacket packet = BuildAdvertisingDataFragmentPacket(
handle.value(),
buffer_view,
operation,
pwemb::LEExtendedAdvFragmentPreference::SHOULD_NOT_FRAGMENT);
packets.push_back(packet);
}
return packets;
}
EmbossCommandPacket ExtendedLowEnergyAdvertiser::BuildUnsetAdvertisingData(
const DeviceAddress& address, bool extended_pdu) {
constexpr size_t kPacketSize =
pwemb::LESetExtendedAdvertisingDataCommandView::MinSizeInBytes().Read();
auto packet = EmbossCommandPacket::New<
pwemb::LESetExtendedAdvertisingDataCommandWriter>(
hci_spec::kLESetExtendedAdvertisingData, kPacketSize);
auto payload = packet.view_t();
// advertising handle
std::optional<hci_spec::AdvertisingHandle> handle =
advertising_handle_map_.GetHandle(address, extended_pdu);
BT_ASSERT(handle);
payload.advertising_handle().Write(handle.value());
payload.operation().Write(pwemb::LESetExtendedAdvDataOp::COMPLETE);
payload.fragment_preference().Write(
pwemb::LEExtendedAdvFragmentPreference::SHOULD_NOT_FRAGMENT);
payload.advertising_data_length().Write(0);
return packet;
}
std::vector<EmbossCommandPacket>
ExtendedLowEnergyAdvertiser::BuildSetScanResponse(const DeviceAddress& address,
const AdvertisingData& data,
bool extended_pdu) {
if (data.CalculateBlockSize() == 0) {
std::vector<EmbossCommandPacket> packets;
return packets;
}
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);
}
std::optional<hci_spec::AdvertisingHandle> handle =
advertising_handle_map_.GetHandle(address, extended_pdu);
BT_ASSERT(handle);
size_t block_size = scan_rsp.CalculateBlockSize(/*include_flags=*/false);
DynamicByteBuffer buffer(block_size);
scan_rsp.WriteBlock(&buffer, std::nullopt);
size_t max_length = pwemb::LESetExtendedScanResponseDataCommand::
scan_response_data_length_max();
// If all data fits into a single HCI packet, we don't need to do any
// fragmentation ourselves. The Controller may still perform fragmentation
// over the air but we don't have to when sending the data to the Controller.
if (block_size <= max_length) {
EmbossCommandPacket packet = BuildScanResponseDataFragmentPacket(
handle.value(),
buffer.view(),
pwemb::LESetExtendedAdvDataOp::COMPLETE,
pwemb::LEExtendedAdvFragmentPreference::SHOULD_NOT_FRAGMENT);
std::vector<EmbossCommandPacket> packets;
packets.reserve(1);
packets.emplace_back(std::move(packet));
return packets;
}
// We have more data than will fit in a single HCI packet. Calculate the
// amount of packets we need to send, perform the fragmentation, and queue up
// the multiple LE Set Extended Advertising Data packets to the Controller.
size_t num_packets = block_size / max_length;
if (block_size % max_length != 0) {
num_packets++;
}
std::vector<EmbossCommandPacket> packets;
packets.reserve(num_packets);
for (size_t i = 0; i < num_packets; i++) {
size_t packet_size = max_length;
pwemb::LESetExtendedAdvDataOp operation =
pwemb::LESetExtendedAdvDataOp::INTERMEDIATE_FRAGMENT;
if (i == 0) {
operation = pwemb::LESetExtendedAdvDataOp::FIRST_FRAGMENT;
} else if (i == num_packets - 1) {
operation = pwemb::LESetExtendedAdvDataOp::LAST_FRAGMENT;
if (block_size % max_length != 0) {
packet_size = block_size % max_length;
}
}
size_t offset = i * max_length;
BufferView buffer_view(buffer.data() + offset, packet_size);
EmbossCommandPacket packet = BuildScanResponseDataFragmentPacket(
handle.value(),
buffer_view,
operation,
pwemb::LEExtendedAdvFragmentPreference::SHOULD_NOT_FRAGMENT);
packets.push_back(packet);
}
return packets;
}
EmbossCommandPacket ExtendedLowEnergyAdvertiser::BuildUnsetScanResponse(
const DeviceAddress& address, bool extended_pdu) {
constexpr size_t kPacketSize =
pwemb::LESetExtendedScanResponseDataCommandView::MinSizeInBytes().Read();
auto packet = EmbossCommandPacket::New<
pwemb::LESetExtendedScanResponseDataCommandWriter>(
hci_spec::kLESetExtendedScanResponseData, kPacketSize);
auto payload = packet.view_t();
// advertising handle
std::optional<hci_spec::AdvertisingHandle> handle =
advertising_handle_map_.GetHandle(address, extended_pdu);
BT_ASSERT(handle);
payload.advertising_handle().Write(handle.value());
payload.operation().Write(pwemb::LESetExtendedAdvDataOp::COMPLETE);
payload.fragment_preference().Write(
pwemb::LEExtendedAdvFragmentPreference::SHOULD_NOT_FRAGMENT);
payload.scan_response_data_length().Write(0);
return packet;
}
EmbossCommandPacket ExtendedLowEnergyAdvertiser::BuildRemoveAdvertisingSet(
const DeviceAddress& address, bool extended_pdu) {
std::optional<hci_spec::AdvertisingHandle> handle =
advertising_handle_map_.GetHandle(address, extended_pdu);
BT_ASSERT(handle);
auto packet =
hci::EmbossCommandPacket::New<pwemb::LERemoveAdvertisingSetCommandWriter>(
hci_spec::kLERemoveAdvertisingSet);
auto view = packet.view_t();
view.advertising_handle().Write(handle.value());
return packet;
}
void ExtendedLowEnergyAdvertiser::OnSetAdvertisingParamsComplete(
const EventPacket& event) {
BT_ASSERT(event.event_code() == hci_spec::kCommandCompleteEventCode);
BT_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>();
BT_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,
const 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;
}
fit::result<HostError> result =
CanStartAdvertising(address, data, scan_rsp, options, connect_callback);
if (result.is_error()) {
result_callback(ToResult(result.error_value()));
return;
}
if (IsAdvertising(address, options.extended_pdu)) {
bt_log(DEBUG,
"hci-le",
"updating existing advertisement for %s",
bt_str(address));
}
staged_advertising_parameters_.clear();
staged_advertising_parameters_.include_tx_power_level =
options.include_tx_power_level;
staged_advertising_parameters_.extended_pdu = options.extended_pdu;
// 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,
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,
bool extended_pdu) {
// 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, extended_pdu]() {
StopAdvertising(address, extended_pdu);
});
return;
}
LowEnergyAdvertiser::StopAdvertisingInternal(address, extended_pdu);
advertising_handle_map_.RemoveAddress(address, extended_pdu);
}
void ExtendedLowEnergyAdvertiser::OnIncomingConnection(
hci_spec::ConnectionHandle handle,
pwemb::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_[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.
void ExtendedLowEnergyAdvertiser::OnAdvertisingSetTerminatedEvent(
const EventPacket& event) {
BT_ASSERT(event.event_code() == hci_spec::kLEMetaEventCode);
BT_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;
}
auto params = event.subevent_params<
hci_spec::LEAdvertisingSetTerminatedSubeventParams>();
BT_ASSERT(params);
hci_spec::ConnectionHandle connection_handle = params->connection_handle;
auto staged_parameters_node = staged_connections_.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;
}
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,
staged_advertising_parameters_.extended_pdu);
staged_advertising_parameters_.clear();
}
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