blob: bf201f8c5b18e67130e31333f97b3cb71e5d446a [file] [log] [blame]
// Copyright 2022 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/android_extended_low_energy_advertiser.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/hci-spec/vendor_protocol.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/transport/transport.h"
#include <pw_bluetooth/hci_common.emb.h>
#include <pw_bluetooth/hci_android.emb.h>
namespace bt::hci {
namespace pwemb = pw::bluetooth::emboss;
// Android range -70 to +20, select the middle for now
constexpr int8_t kTransmitPower = -25;
// AndroidExtendedLowEnergyAdvertiser doesn't support extended advertising PDUs
constexpr bool kUseExtendedPdu = false;
namespace android_hci = hci_spec::vendor::android;
namespace android_emb = pw::bluetooth::vendor::android_hci;
AndroidExtendedLowEnergyAdvertiser::AndroidExtendedLowEnergyAdvertiser(
hci::Transport::WeakPtr hci_ptr, uint8_t max_advertisements)
: LowEnergyAdvertiser(std::move(hci_ptr),
hci_spec::kMaxLEAdvertisingDataLength),
advertising_handle_map_(max_advertisements) {
state_changed_event_handler_id_ =
hci()->command_channel()->AddVendorEventHandler(
android_hci::kLEMultiAdvtStateChangeSubeventCode,
[this](const EmbossEventPacket& event_packet) {
return OnAdvertisingStateChangedSubevent(event_packet);
});
}
AndroidExtendedLowEnergyAdvertiser::~AndroidExtendedLowEnergyAdvertiser() {
// 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(state_changed_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 AndroidExtendedLowEnergyAdvertiser::BuildEnablePacket(
const DeviceAddress& address,
pwemb::GenericEnableParam enable,
bool extended_pdu) {
std::optional<hci_spec::AdvertisingHandle> handle =
advertising_handle_map_.GetHandle(address, extended_pdu);
BT_ASSERT(handle);
auto packet = hci::EmbossCommandPacket::New<
android_emb::LEMultiAdvtEnableCommandWriter>(android_hci::kLEMultiAdvt);
auto packet_view = packet.view_t();
packet_view.vendor_command().sub_opcode().Write(
android_hci::kLEMultiAdvtEnableSubopcode);
packet_view.enable().Write(enable);
packet_view.advertising_handle().Write(handle.value());
return packet;
}
std::optional<EmbossCommandPacket>
AndroidExtendedLowEnergyAdvertiser::BuildSetAdvertisingParams(
const DeviceAddress& address,
const AdvertisingEventProperties& properties,
pwemb::LEOwnAddressType own_address_type,
const AdvertisingIntervalRange& interval,
bool extended_pdu) {
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;
}
auto packet = hci::EmbossCommandPacket::New<
android_emb::LEMultiAdvtSetAdvtParamCommandWriter>(
android_hci::kLEMultiAdvt);
auto view = packet.view_t();
view.vendor_command().sub_opcode().Write(
android_hci::kLEMultiAdvtSetAdvtParamSubopcode);
view.adv_interval_min().Write(interval.min());
view.adv_interval_max().Write(interval.max());
view.adv_type().Write(
AdvertisingEventPropertiesToLEAdvertisingType(properties));
view.own_addr_type().Write(own_address_type);
view.adv_channel_map().channel_37().Write(true);
view.adv_channel_map().channel_38().Write(true);
view.adv_channel_map().channel_39().Write(true);
view.adv_filter_policy().Write(pwemb::LEAdvertisingFilterPolicy::ALLOW_ALL);
view.adv_handle().Write(handle.value());
view.adv_tx_power().Write(hci_spec::kLEAdvertisingTxPowerMax);
// We don't support directed advertising yet, so leave peer_address and
// peer_address_type as 0x00
// (|packet| parameters are initialized to zero above).
return packet;
}
std::vector<EmbossCommandPacket>
AndroidExtendedLowEnergyAdvertiser::BuildSetAdvertisingData(
const DeviceAddress& address,
const AdvertisingData& data,
AdvFlags flags,
bool extended_pdu) {
if (data.CalculateBlockSize() == 0) {
std::vector<EmbossCommandPacket> packets;
return packets;
}
std::optional<hci_spec::AdvertisingHandle> handle =
advertising_handle_map_.GetHandle(address, extended_pdu);
BT_ASSERT(handle);
uint8_t adv_data_length =
static_cast<uint8_t>(data.CalculateBlockSize(/*include_flags=*/true));
size_t packet_size =
android_emb::LEMultiAdvtSetAdvtDataCommandWriter::MinSizeInBytes()
.Read() +
adv_data_length;
auto packet = hci::EmbossCommandPacket::New<
android_emb::LEMultiAdvtSetAdvtDataCommandWriter>(
android_hci::kLEMultiAdvt, packet_size);
auto view = packet.view_t();
view.vendor_command().sub_opcode().Write(
android_hci::kLEMultiAdvtSetAdvtDataSubopcode);
view.adv_data_length().Write(adv_data_length);
view.adv_handle().Write(handle.value());
MutableBufferView data_view(view.adv_data().BackingStorage().data(),
adv_data_length);
data.WriteBlock(&data_view, flags);
std::vector<EmbossCommandPacket> packets;
packets.reserve(1);
packets.emplace_back(std::move(packet));
return packets;
}
EmbossCommandPacket
AndroidExtendedLowEnergyAdvertiser::BuildUnsetAdvertisingData(
const DeviceAddress& address, bool extended_pdu) {
std::optional<hci_spec::AdvertisingHandle> handle =
advertising_handle_map_.GetHandle(address, extended_pdu);
BT_ASSERT(handle);
size_t packet_size =
android_emb::LEMultiAdvtSetAdvtDataCommandWriter::MinSizeInBytes().Read();
auto packet = hci::EmbossCommandPacket::New<
android_emb::LEMultiAdvtSetAdvtDataCommandWriter>(
android_hci::kLEMultiAdvt, packet_size);
auto view = packet.view_t();
view.vendor_command().sub_opcode().Write(
android_hci::kLEMultiAdvtSetAdvtDataSubopcode);
view.adv_data_length().Write(0);
view.adv_handle().Write(handle.value());
return packet;
}
std::vector<EmbossCommandPacket>
AndroidExtendedLowEnergyAdvertiser::BuildSetScanResponse(
const DeviceAddress& address,
const AdvertisingData& data,
bool extended_pdu) {
if (data.CalculateBlockSize() == 0) {
std::vector<EmbossCommandPacket> packets;
return packets;
}
std::optional<hci_spec::AdvertisingHandle> handle =
advertising_handle_map_.GetHandle(address, extended_pdu);
BT_ASSERT(handle);
uint8_t scan_rsp_length = static_cast<uint8_t>(data.CalculateBlockSize());
size_t packet_size =
android_emb::LEMultiAdvtSetScanRespDataCommandWriter::MinSizeInBytes()
.Read() +
scan_rsp_length;
auto packet = hci::EmbossCommandPacket::New<
android_emb::LEMultiAdvtSetScanRespDataCommandWriter>(
android_hci::kLEMultiAdvt, packet_size);
auto view = packet.view_t();
view.vendor_command().sub_opcode().Write(
android_hci::kLEMultiAdvtSetScanRespSubopcode);
view.scan_resp_length().Write(scan_rsp_length);
view.adv_handle().Write(handle.value());
MutableBufferView data_view(view.adv_data().BackingStorage().data(),
scan_rsp_length);
data.WriteBlock(&data_view, std::nullopt);
std::vector<EmbossCommandPacket> packets;
packets.reserve(1);
packets.emplace_back(std::move(packet));
return packets;
}
EmbossCommandPacket AndroidExtendedLowEnergyAdvertiser::BuildUnsetScanResponse(
const DeviceAddress& address, bool extended_pdu) {
std::optional<hci_spec::AdvertisingHandle> handle =
advertising_handle_map_.GetHandle(address, extended_pdu);
BT_ASSERT(handle);
size_t packet_size =
android_emb::LEMultiAdvtSetScanRespDataCommandWriter::MinSizeInBytes()
.Read();
auto packet = hci::EmbossCommandPacket::New<
android_emb::LEMultiAdvtSetScanRespDataCommandWriter>(
android_hci::kLEMultiAdvt, packet_size);
auto view = packet.view_t();
view.vendor_command().sub_opcode().Write(
android_hci::kLEMultiAdvtSetScanRespSubopcode);
view.scan_resp_length().Write(0);
view.adv_handle().Write(handle.value());
return packet;
}
EmbossCommandPacket
AndroidExtendedLowEnergyAdvertiser::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<
android_emb::LEMultiAdvtEnableCommandWriter>(android_hci::kLEMultiAdvt);
auto packet_view = packet.view_t();
packet_view.vendor_command().sub_opcode().Write(
android_hci::kLEMultiAdvtEnableSubopcode);
packet_view.enable().Write(pwemb::GenericEnableParam::DISABLE);
packet_view.advertising_handle().Write(handle.value());
return packet;
}
void AndroidExtendedLowEnergyAdvertiser::StartAdvertising(
const DeviceAddress& address,
const AdvertisingData& data,
const AdvertisingData& scan_rsp,
const AdvertisingOptions& options,
ConnectionCallback connect_callback,
ResultFunction<> result_callback) {
if (options.extended_pdu) {
bt_log(WARN,
"hci-le",
"android vendor extensions cannot use extended advertising PDUs");
result_callback(ToResult(HostError::kNotSupported));
return;
}
fit::result<HostError> result =
CanStartAdvertising(address, data, scan_rsp, options, connect_callback);
if (result.is_error()) {
result_callback(ToResult(result.error_value()));
return;
}
AdvertisingData copied_data;
data.Copy(&copied_data);
AdvertisingData copied_scan_rsp;
scan_rsp.Copy(&copied_scan_rsp);
// 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");
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;
}
if (IsAdvertising(address, options.extended_pdu)) {
bt_log(DEBUG,
"hci-le",
"updating existing advertisement for %s",
bt_str(address));
}
if (options.include_tx_power_level) {
copied_data.SetTxPower(kTransmitPower);
copied_scan_rsp.SetTxPower(kTransmitPower);
}
StartAdvertisingInternal(address,
copied_data,
copied_scan_rsp,
options,
std::move(connect_callback),
std::move(result_callback));
}
void AndroidExtendedLowEnergyAdvertiser::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 AndroidExtendedLowEnergyAdvertiser::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, kUseExtendedPdu);
advertising_handle_map_.RemoveAddress(address, kUseExtendedPdu);
}
void AndroidExtendedLowEnergyAdvertiser::OnIncomingConnection(
hci_spec::ConnectionHandle handle,
pwemb::ConnectionRole role,
const DeviceAddress& peer_address,
const hci_spec::LEConnectionParameters& conn_params) {
staged_connections_map_[handle] = {role, peer_address, conn_params};
}
// The LE multi-advertising state change subevent contains the mapping between
// connection handle and advertising handle. After the LE multi-advertising
// state change subevent, we have all the information necessary to create a
// connection object within the Host layer.
CommandChannel::EventCallbackResult
AndroidExtendedLowEnergyAdvertiser::OnAdvertisingStateChangedSubevent(
const EmbossEventPacket& event) {
BT_ASSERT(event.event_code() == hci_spec::kVendorDebugEventCode);
BT_ASSERT(event.view<pwemb::VendorDebugEventView>().subevent_code().Read() ==
android_hci::kLEMultiAdvtStateChangeSubeventCode);
Result<> result = event.ToResult();
if (bt_is_error(result,
ERROR,
"hci-le",
"advertising state change event, error received %s",
bt_str(result))) {
return CommandChannel::EventCallbackResult::kContinue;
}
auto view = event.view<android_emb::LEMultiAdvtStateChangeSubeventView>();
hci_spec::AdvertisingHandle adv_handle = view.advertising_handle().Read();
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();
}
hci_spec::ConnectionHandle connection_handle =
view.connection_handle().Read();
auto staged_node = staged_connections_map_.extract(connection_handle);
if (staged_node.empty()) {
bt_log(ERROR,
"hci-le",
"advertising state change event, staged params not available "
"(handle: %d)",
view.advertising_handle().Read());
return CommandChannel::EventCallbackResult::kContinue;
}
StagedConnectionParameters staged = staged_node.mapped();
CompleteIncomingConnection(connection_handle,
staged.role,
local_address,
staged.peer_address,
staged.conn_params,
kUseExtendedPdu);
return CommandChannel::EventCallbackResult::kContinue;
}
void AndroidExtendedLowEnergyAdvertiser::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