blob: 007f429b055a7a53aa7d3a528315da53fb3b9031 [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/legacy_low_energy_advertiser.h"
#include <endian.h>
#include "pw_bluetooth_sapphire/internal/host/common/advertising_data.h"
#include "pw_bluetooth_sapphire/internal/host/common/assert.h"
#include "pw_bluetooth_sapphire/internal/host/common/byte_buffer.h"
#include "pw_bluetooth_sapphire/internal/host/common/log.h"
#include "pw_bluetooth_sapphire/internal/host/hci-spec/util.h"
#include "pw_bluetooth_sapphire/internal/host/hci/sequential_command_runner.h"
#include "pw_bluetooth_sapphire/internal/host/transport/transport.h"
namespace bt::hci {
LegacyLowEnergyAdvertiser::~LegacyLowEnergyAdvertiser() {
// 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;
}
StopAdvertising();
}
EmbossCommandPacket LegacyLowEnergyAdvertiser::BuildEnablePacket(
const DeviceAddress& address,
pw::bluetooth::emboss::GenericEnableParam enable) {
auto packet = hci::EmbossCommandPacket::New<
pw::bluetooth::emboss::LESetAdvertisingEnableCommandWriter>(
hci_spec::kLESetAdvertisingEnable);
auto packet_view = packet.view_t();
packet_view.advertising_enable().Write(enable);
return packet;
}
CommandChannel::CommandPacketVariant
LegacyLowEnergyAdvertiser::BuildSetAdvertisingData(const DeviceAddress& address,
const AdvertisingData& data,
AdvFlags flags) {
auto packet = EmbossCommandPacket::New<
pw::bluetooth::emboss::LESetAdvertisingDataCommandWriter>(
hci_spec::kLESetAdvertisingData);
auto params = packet.view_t();
const uint8_t data_length =
static_cast<uint8_t>(data.CalculateBlockSize(/*include_flags=*/true));
params.advertising_data_length().Write(data_length);
MutableBufferView adv_view(params.advertising_data().BackingStorage().data(),
data_length);
data.WriteBlock(&adv_view, flags);
return packet;
}
CommandChannel::CommandPacketVariant
LegacyLowEnergyAdvertiser::BuildSetScanResponse(
const DeviceAddress& address, const AdvertisingData& scan_rsp) {
auto packet = EmbossCommandPacket::New<
pw::bluetooth::emboss::LESetScanResponseDataCommandWriter>(
hci_spec::kLESetScanResponseData);
auto params = packet.view_t();
const uint8_t data_length =
static_cast<uint8_t>(scan_rsp.CalculateBlockSize());
params.scan_response_data_length().Write(data_length);
MutableBufferView scan_data_view(
params.scan_response_data().BackingStorage().data(), data_length);
scan_rsp.WriteBlock(&scan_data_view, /*flags=*/std::nullopt);
return packet;
}
CommandChannel::CommandPacketVariant
LegacyLowEnergyAdvertiser::BuildSetAdvertisingParams(
const DeviceAddress& address,
pw::bluetooth::emboss::LEAdvertisingType type,
pw::bluetooth::emboss::LEOwnAddressType own_address_type,
AdvertisingIntervalRange interval) {
auto packet = EmbossCommandPacket::New<
pw::bluetooth::emboss::LESetAdvertisingParametersCommandWriter>(
hci_spec::kLESetAdvertisingParameters);
auto params = packet.view_t();
params.advertising_interval_min().UncheckedWrite(interval.min());
params.advertising_interval_max().UncheckedWrite(interval.max());
params.adv_type().Write(type);
params.own_address_type().Write(own_address_type);
params.advertising_channel_map().BackingStorage().WriteUInt(
hci_spec::kLEAdvertisingChannelAll);
params.advertising_filter_policy().Write(
pw::bluetooth::emboss::LEAdvertisingFilterPolicy::ALLOW_ALL);
// 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;
}
CommandChannel::CommandPacketVariant
LegacyLowEnergyAdvertiser::BuildUnsetAdvertisingData(
const DeviceAddress& address) {
return EmbossCommandPacket::New<
pw::bluetooth::emboss::LESetAdvertisingDataCommandWriter>(
hci_spec::kLESetAdvertisingData);
}
CommandChannel::CommandPacketVariant
LegacyLowEnergyAdvertiser::BuildUnsetScanResponse(
const DeviceAddress& address) {
auto packet = EmbossCommandPacket::New<
pw::bluetooth::emboss::LESetScanResponseDataCommandWriter>(
hci_spec::kLESetScanResponseData);
return packet;
}
EmbossCommandPacket LegacyLowEnergyAdvertiser::BuildRemoveAdvertisingSet(
const DeviceAddress& address) {
auto packet = hci::EmbossCommandPacket::New<
pw::bluetooth::emboss::LESetAdvertisingEnableCommandWriter>(
hci_spec::kLESetAdvertisingEnable);
auto packet_view = packet.view_t();
packet_view.advertising_enable().Write(
pw::bluetooth::emboss::GenericEnableParam::DISABLE);
return packet;
}
static EmbossCommandPacket BuildReadAdvertisingTxPower() {
return EmbossCommandPacket::New<
pw::bluetooth::emboss::LEReadAdvertisingChannelTxPowerCommandView>(
hci_spec::kLEReadAdvertisingChannelTxPower);
}
void LegacyLowEnergyAdvertiser::StartAdvertising(
const DeviceAddress& address,
const AdvertisingData& data,
const AdvertisingData& scan_rsp,
AdvertisingOptions options,
ConnectionCallback connect_callback,
ResultFunction<> result_callback) {
fit::result<HostError> result =
CanStartAdvertising(address, data, scan_rsp, options);
if (result.is_error()) {
result_callback(ToResult(result.error_value()));
return;
}
if (IsAdvertising() && !IsAdvertising(address)) {
bt_log(INFO,
"hci-le",
"already advertising (only one advertisement supported at a time)");
result_callback(ToResult(HostError::kNotSupported));
return;
}
if (IsAdvertising()) {
bt_log(DEBUG, "hci-le", "updating existing advertisement");
}
// Midst of a TX power level read - send a cancel over the previous status
// callback.
if (staged_params_.has_value()) {
auto result_cb = std::move(staged_params_.value().result_callback);
result_cb(ToResult(HostError::kCanceled));
}
// If the TX Power level is requested, then stage the parameters for the read
// operation. If there already is an outstanding TX Power Level read request,
// return early. Advertising on the outstanding call will now use the updated
// |staged_params_|.
if (options.include_tx_power_level) {
AdvertisingData data_copy;
data.Copy(&data_copy);
AdvertisingData scan_rsp_copy;
scan_rsp.Copy(&scan_rsp_copy);
staged_params_ = StagedParams{address,
options.interval,
options.flags,
std::move(data_copy),
std::move(scan_rsp_copy),
std::move(connect_callback),
std::move(result_callback)};
if (starting_ && hci_cmd_runner().IsReady()) {
return;
}
}
if (!hci_cmd_runner().IsReady()) {
bt_log(DEBUG,
"hci-le",
"canceling advertising start/stop sequence due to new advertising "
"request");
// Abort any remaining commands from the current stop sequence. If we got
// here then the controller MUST receive our request to disable advertising,
// so the commands that we send next will overwrite the current advertising
// settings and re-enable it.
hci_cmd_runner().Cancel();
}
starting_ = true;
// If the TX Power Level is requested, read it from the controller, update the
// data buf, and proceed with starting advertising.
//
// If advertising was canceled during the TX power level read (either
// |starting_| was reset or the |result_callback| was moved), return early.
if (options.include_tx_power_level) {
auto power_cb = [this](auto, const hci::EventPacket& event) mutable {
BT_ASSERT(staged_params_.has_value());
if (!starting_ || !staged_params_.value().result_callback) {
bt_log(
INFO, "hci-le", "Advertising canceled during TX Power Level read.");
return;
}
if (hci_is_error(event, WARN, "hci-le", "read TX power level failed")) {
staged_params_.value().result_callback(event.ToResult());
staged_params_ = {};
starting_ = false;
return;
}
const auto& params = event.return_params<
hci_spec::LEReadAdvertisingChannelTxPowerReturnParams>();
// Update the advertising and scan response data with the TX power level.
auto staged_params = std::move(staged_params_.value());
staged_params.data.SetTxPower(params->tx_power);
if (staged_params.scan_rsp.CalculateBlockSize()) {
staged_params.scan_rsp.SetTxPower(params->tx_power);
}
// Reset the |staged_params_| as it is no longer in use.
staged_params_ = {};
StartAdvertisingInternal(
staged_params.address,
staged_params.data,
staged_params.scan_rsp,
staged_params.interval,
staged_params.flags,
std::move(staged_params.connect_callback),
[this, result_callback = std::move(staged_params.result_callback)](
const Result<>& result) {
starting_ = false;
result_callback(result);
});
};
hci()->command_channel()->SendCommand(BuildReadAdvertisingTxPower(),
std::move(power_cb));
return;
}
StartAdvertisingInternal(address,
data,
scan_rsp,
options.interval,
options.flags,
std::move(connect_callback),
[this, result_callback = std::move(result_callback)](
const Result<>& result) {
starting_ = false;
result_callback(result);
});
}
void LegacyLowEnergyAdvertiser::StopAdvertising() {
LowEnergyAdvertiser::StopAdvertising();
starting_ = false;
}
void LegacyLowEnergyAdvertiser::StopAdvertising(const DeviceAddress& address) {
if (!hci_cmd_runner().IsReady()) {
hci_cmd_runner().Cancel();
}
LowEnergyAdvertiser::StopAdvertisingInternal(address);
starting_ = false;
}
void LegacyLowEnergyAdvertiser::OnIncomingConnection(
hci_spec::ConnectionHandle handle,
pw::bluetooth::emboss::ConnectionRole role,
const DeviceAddress& peer_address,
const hci_spec::LEConnectionParameters& conn_params) {
static DeviceAddress identity_address =
DeviceAddress(DeviceAddress::Type::kLEPublic, {0});
// We use the identity address as the local address if we aren't advertising.
// If we aren't advertising, this is obviously wrong. However, the link will
// be disconnected in that case before it can propagate to higher layers.
DeviceAddress local_address = identity_address;
if (IsAdvertising()) {
local_address = connection_callbacks().begin()->first;
}
CompleteIncomingConnection(
handle, role, local_address, peer_address, conn_params);
}
} // namespace bt::hci