blob: 1fc7110b90811bb5dda1ecf469e2e9c1585d4b76 [file] [log] [blame]
// Copyright 2017 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 "legacy_low_energy_advertiser.h"
#include <endian.h>
#include <lib/async/default.h>
#include <zircon/assert.h>
#include "src/connectivity/bluetooth/core/bt-host/common/byte_buffer.h"
#include "src/connectivity/bluetooth/core/bt-host/common/log.h"
#include "src/connectivity/bluetooth/core/bt-host/hci-spec/util.h"
#include "src/connectivity/bluetooth/core/bt-host/hci/transport.h"
namespace bt::hci {
namespace {
// The size, in bytes, of the serialized TX Power Level.
constexpr size_t kTxPowerLevelTLVSize = 3;
// Helpers for building HCI command packets:
std::unique_ptr<CommandPacket> BuildEnablePacket(GenericEnableParam enable) {
constexpr size_t kPayloadSize = sizeof(LESetAdvertisingEnableCommandParams);
auto packet = CommandPacket::New(kLESetAdvertisingEnable, kPayloadSize);
packet->mutable_payload<LESetAdvertisingEnableCommandParams>()->advertising_enable = enable;
ZX_ASSERT(packet);
return packet;
}
std::unique_ptr<CommandPacket> BuildSetAdvertisingData(const AdvertisingData& data,
AdvFlags flags) {
auto packet =
CommandPacket::New(kLESetAdvertisingData, sizeof(LESetAdvertisingDataCommandParams));
packet->mutable_view()->mutable_payload_data().SetToZeros();
auto params = packet->mutable_payload<LESetAdvertisingDataCommandParams>();
params->adv_data_length = data.CalculateBlockSize(/*include_flags=*/true);
MutableBufferView adv_view(params->adv_data, params->adv_data_length);
data.WriteBlock(&adv_view, flags);
return packet;
}
std::unique_ptr<CommandPacket> BuildSetScanResponse(const AdvertisingData& scan_rsp) {
auto packet =
CommandPacket::New(kLESetScanResponseData, sizeof(LESetScanResponseDataCommandParams));
packet->mutable_view()->mutable_payload_data().SetToZeros();
auto params = packet->mutable_payload<LESetScanResponseDataCommandParams>();
params->scan_rsp_data_length = scan_rsp.CalculateBlockSize();
MutableBufferView scan_data_view(params->scan_rsp_data, sizeof(params->scan_rsp_data));
scan_rsp.WriteBlock(&scan_data_view, std::nullopt);
return packet;
}
std::unique_ptr<CommandPacket> BuildSetAdvertisingParams(LEAdvertisingType type,
LEOwnAddressType own_address_type,
AdvertisingIntervalRange interval) {
auto packet = CommandPacket::New(kLESetAdvertisingParameters,
sizeof(LESetAdvertisingParametersCommandParams));
packet->mutable_view()->mutable_payload_data().SetToZeros();
auto params = packet->mutable_payload<LESetAdvertisingParametersCommandParams>();
params->adv_interval_min = htole16(interval.min());
params->adv_interval_max = htole16(interval.max());
params->adv_type = type;
params->own_address_type = own_address_type;
params->adv_channel_map = kLEAdvertisingChannelAll;
params->adv_filter_policy = LEAdvFilterPolicy::kAllowAll;
// We don't support directed advertising yet, so leave peer_address as 0x00
// (|packet| parameters are initialized to zero above).
return packet;
}
std::unique_ptr<CommandPacket> BuildReadAdvertisingTxPower() {
auto packet = CommandPacket::New(kLEReadAdvertisingChannelTxPower);
ZX_ASSERT(packet);
return packet;
}
} // namespace
LegacyLowEnergyAdvertiser::LegacyLowEnergyAdvertiser(fxl::WeakPtr<Transport> hci)
: hci_(std::move(hci)), starting_(false), connect_callback_(nullptr) {
hci_cmd_runner_ = std::make_unique<SequentialCommandRunner>(async_get_default_dispatcher(), hci_);
}
LegacyLowEnergyAdvertiser::~LegacyLowEnergyAdvertiser() { StopAdvertisingInternal(); }
size_t LegacyLowEnergyAdvertiser::GetSizeLimit() { return kMaxLEAdvertisingDataLength; }
bool LegacyLowEnergyAdvertiser::AllowsRandomAddressChange() const {
return !starting_ && !advertising();
}
void LegacyLowEnergyAdvertiser::StartAdvertisingInternal(
const DeviceAddress& address, const AdvertisingData& data, const AdvertisingData& scan_rsp,
AdvertisingIntervalRange interval, AdvFlags flags, ConnectionCallback connect_callback,
StatusCallback callback) {
if (advertising()) {
// Temporarily disable advertising so we can tweak the parameters.
hci_cmd_runner_->QueueCommand(BuildEnablePacket(GenericEnableParam::kDisable));
}
// Set advertising and scan response data. If either data is empty then it
// will be cleared accordingly.
hci_cmd_runner_->QueueCommand(BuildSetAdvertisingData(data, flags));
hci_cmd_runner_->QueueCommand(BuildSetScanResponse(scan_rsp));
// Set advertising parameters
LEAdvertisingType type = LEAdvertisingType::kAdvNonConnInd;
if (connect_callback) {
type = LEAdvertisingType::kAdvInd;
} else if (scan_rsp.CalculateBlockSize() > 0) {
type = LEAdvertisingType::kAdvScanInd;
}
LEOwnAddressType own_addr_type;
if (address.type() == DeviceAddress::Type::kLEPublic) {
own_addr_type = LEOwnAddressType::kPublic;
} else {
own_addr_type = LEOwnAddressType::kRandom;
}
hci_cmd_runner_->QueueCommand(BuildSetAdvertisingParams(type, own_addr_type, interval));
// Enable advertising.
hci_cmd_runner_->QueueCommand(BuildEnablePacket(GenericEnableParam::kEnable));
hci_cmd_runner_->RunCommands(
[this, address, callback = std::move(callback),
connect_callback = std::move(connect_callback)](Status status) mutable {
ZX_DEBUG_ASSERT(starting_);
starting_ = false;
if (bt_is_error(status, ERROR, "hci-le", "failed to start advertising")) {
// Clear out the advertising data if it partially succeeded.
StopAdvertisingInternal();
} else {
bt_log(INFO, "hci-le", "advertising enabled");
advertised_ = address;
connect_callback_ = std::move(connect_callback);
}
callback(status);
});
}
void LegacyLowEnergyAdvertiser::StartAdvertising(
const DeviceAddress& address, const AdvertisingData& data, const AdvertisingData& scan_rsp,
AdvertisingOptions adv_options, ConnectionCallback connect_callback, StatusCallback callback) {
ZX_DEBUG_ASSERT(callback);
ZX_DEBUG_ASSERT(address.type() != DeviceAddress::Type::kBREDR);
if (adv_options.anonymous) {
bt_log(DEBUG, "hci-le", "anonymous advertising not supported");
callback(Status(HostError::kNotSupported));
return;
}
if (advertising()) {
if (address != advertised_) {
bt_log(DEBUG, "hci-le", "already advertising");
callback(Status(HostError::kNotSupported));
return;
}
bt_log(DEBUG, "hci-le", "updating existing advertisement");
}
// If the TX Power Level is requested, ensure both buffers have enough space.
size_t size_limit = GetSizeLimit();
if (adv_options.include_tx_power_level)
size_limit -= kTxPowerLevelTLVSize;
if (data.CalculateBlockSize(/*include_flags=*/true) > size_limit) {
bt_log(DEBUG, "hci-le", "advertising data too large");
callback(Status(HostError::kAdvertisingDataTooLong));
return;
}
if (scan_rsp.CalculateBlockSize() > size_limit) {
bt_log(DEBUG, "hci-le", "scan response too large");
callback(Status(HostError::kScanResponseTooLong));
return;
}
// Midst of a TX power level read - send a cancel over the previous status callback.
if (staged_params_.has_value()) {
auto status_cb = std::move(staged_params_.value().callback);
status_cb(Status(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 (adv_options.include_tx_power_level) {
AdvertisingData data_copy, scan_rsp_copy;
data.Copy(&data_copy);
scan_rsp.Copy(&scan_rsp_copy);
staged_params_ = StagedParams{address,
adv_options.interval,
adv_options.flags,
std::move(data_copy),
std::move(scan_rsp_copy),
std::move(connect_callback),
std::move(callback)};
if (starting_ && hci_cmd_runner_->IsReady()) {
return;
}
}
if (!hci_cmd_runner_->IsReady()) {
// 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 |callback| was moved), return early.
if (adv_options.include_tx_power_level) {
auto power_cb = [this](auto, const hci::EventPacket& event) mutable {
ZX_ASSERT(staged_params_.has_value());
if ((!starting_) || (!staged_params_.value().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().callback(event.ToStatus());
staged_params_ = {};
starting_ = false;
return;
}
const auto& params = event.return_params<hci::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),
std::move(staged_params.callback));
};
hci_->command_channel()->SendCommand(BuildReadAdvertisingTxPower(), std::move(power_cb));
return;
}
StartAdvertisingInternal(address, data, scan_rsp, adv_options.interval, adv_options.flags,
std::move(connect_callback), std::move(callback));
}
// TODO(fxbug.dev/50542): StopAdvertising() should cancel outstanding calls to StartAdvertising()
// and clean up state.
bool LegacyLowEnergyAdvertiser::StopAdvertising(const DeviceAddress& address) {
if (advertised_ != address) {
// not advertising, or not on this address.
return false;
}
StopAdvertisingInternal();
return true;
}
void LegacyLowEnergyAdvertiser::StopAdvertisingInternal() {
connect_callback_ = nullptr;
if (!hci_cmd_runner_->IsReady()) {
if (!starting_) {
bt_log(DEBUG, "hci-le", "already stopping");
// The advertised address must have been cleared in this state.
ZX_DEBUG_ASSERT(!advertising());
return;
}
// Cancel the pending start
ZX_DEBUG_ASSERT(starting_);
hci_cmd_runner_->Cancel();
starting_ = false;
}
// Even on failure, we want to consider us not advertising. Clear the
// advertised address here so that new advertisements can be requested right
// away.
advertised_ = {};
// Disable advertising
hci_cmd_runner_->QueueCommand(BuildEnablePacket(GenericEnableParam::kDisable));
// Unset advertising data
auto data_packet =
CommandPacket::New(kLESetAdvertisingData, sizeof(LESetAdvertisingDataCommandParams));
data_packet->mutable_view()->mutable_payload_data().SetToZeros();
hci_cmd_runner_->QueueCommand(std::move(data_packet));
// Set scan response data
auto scan_rsp_packet =
CommandPacket::New(kLESetScanResponseData, sizeof(LESetScanResponseDataCommandParams));
scan_rsp_packet->mutable_view()->mutable_payload_data().SetToZeros();
hci_cmd_runner_->QueueCommand(std::move(scan_rsp_packet));
hci_cmd_runner_->RunCommands(
[](Status status) { bt_log(INFO, "hci-le", "advertising stopped: %s", bt_str(status)); });
}
void LegacyLowEnergyAdvertiser::OnIncomingConnection(ConnectionHandle handle, Connection::Role role,
const DeviceAddress& peer_address,
const 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. We assign |advertised_| as the
// local address however this address may be invalid if we're not advertising. This is OK as the
// link will be disconnected in that case before it can propagate to higher layers.
//
// TODO(fxbug.dev/2761): We can't assign the default address since an LE connection cannot have a
// BR/EDR type. This temporary default won't be necessary was we remove transport from the address
// type.
auto local_address =
advertising() ? advertised_ : DeviceAddress(DeviceAddress::Type::kLEPublic, {0});
auto link = Connection::CreateLE(handle, role, local_address, peer_address, conn_params, hci_);
if (!advertising()) {
bt_log(DEBUG, "hci-le", "connection received without advertising!");
return;
}
if (!connect_callback_) {
bt_log(DEBUG, "hci-le", "connection received when not connectable!");
return;
}
// Assign the currently advertised address as the local address of the
// connection.
auto callback = std::move(connect_callback_);
StopAdvertisingInternal();
// Pass on the ownership.
callback(std::move(link));
}
} // namespace bt::hci