blob: 6f9b01eab62991ad21ef53b701b260f78c62d39f [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 "low_energy_advertising_manager.h"
#include <zircon/assert.h>
#include "low_energy_address_manager.h"
#include "peer.h"
#include "src/connectivity/bluetooth/core/bt-host/common/log.h"
#include "src/connectivity/bluetooth/core/bt-host/common/random.h"
#include "src/connectivity/bluetooth/core/bt-host/common/slab_allocator.h"
#include "src/connectivity/bluetooth/core/bt-host/hci-spec/util.h"
#include "src/lib/fxl/strings/string_printf.h"
namespace bt::gap {
namespace {
// Returns the matching minimum and maximum advertising interval values in controller timeslices.
hci::AdvertisingIntervalRange GetIntervalRange(AdvertisingInterval interval) {
switch (interval) {
case AdvertisingInterval::FAST1:
return {kLEAdvertisingFastIntervalMin1, kLEAdvertisingFastIntervalMax1};
case AdvertisingInterval::FAST2:
return {kLEAdvertisingFastIntervalMin2, kLEAdvertisingFastIntervalMax2};
case AdvertisingInterval::SLOW:
return {kLEAdvertisingSlowIntervalMin, kLEAdvertisingSlowIntervalMax};
}
ZX_PANIC("unexpected advertising interval value");
return {kLEAdvertisingSlowIntervalMin, kLEAdvertisingSlowIntervalMax};
}
} // namespace
AdvertisementInstance::AdvertisementInstance() : id_(kInvalidAdvertisementId) {}
AdvertisementInstance::AdvertisementInstance(AdvertisementId id,
fxl::WeakPtr<LowEnergyAdvertisingManager> owner)
: id_(id), owner_(owner) {
ZX_DEBUG_ASSERT(id_ != kInvalidAdvertisementId);
ZX_DEBUG_ASSERT(owner_);
}
AdvertisementInstance::~AdvertisementInstance() { Reset(); }
void AdvertisementInstance::Move(AdvertisementInstance* other) {
// Destroy the old advertisement instance if active and clear the contents.
Reset();
// Transfer the old data over and clear |other| so that it no longer owns its advertisement.
owner_ = std::move(other->owner_);
id_ = other->id_;
other->id_ = kInvalidAdvertisementId;
}
void AdvertisementInstance::Reset() {
if (owner_ && id_ != kInvalidAdvertisementId) {
owner_->StopAdvertising(id_);
}
owner_.reset();
id_ = kInvalidAdvertisementId;
}
class LowEnergyAdvertisingManager::ActiveAdvertisement final {
public:
// TODO(fxbug.dev/863): Don't randomly generate the ID of an advertisement.
// Instead use a counter like other internal IDs once this ID is not visible
// outside of bt-host.
explicit ActiveAdvertisement(const DeviceAddress& address)
: address_(address), id_(RandomPeerId().value()) {}
~ActiveAdvertisement() = default;
const DeviceAddress& address() const { return address_; }
AdvertisementId id() const { return id_; }
private:
DeviceAddress address_;
AdvertisementId id_;
DISALLOW_COPY_AND_ASSIGN_ALLOW_MOVE(ActiveAdvertisement);
};
LowEnergyAdvertisingManager::LowEnergyAdvertisingManager(
hci::LowEnergyAdvertiser* advertiser, hci::LocalAddressDelegate* local_addr_delegate)
: advertiser_(advertiser), local_addr_delegate_(local_addr_delegate), weak_ptr_factory_(this) {
ZX_DEBUG_ASSERT(advertiser_);
ZX_DEBUG_ASSERT(local_addr_delegate_);
}
LowEnergyAdvertisingManager::~LowEnergyAdvertisingManager() {
// Turn off all the advertisements!
for (const auto& ad : advertisements_) {
advertiser_->StopAdvertising(ad.second->address());
}
}
void LowEnergyAdvertisingManager::StartAdvertising(AdvertisingData data, AdvertisingData scan_rsp,
ConnectionCallback connect_callback,
AdvertisingInterval interval, bool anonymous,
bool include_tx_power_level,
AdvertisingStatusCallback status_callback) {
// Can't be anonymous and connectable
if (anonymous && connect_callback) {
bt_log(DEBUG, "gap-le", "can't advertise anonymously and connectable!");
status_callback(AdvertisementInstance(), hci::Status(HostError::kInvalidParameters));
return;
}
// v5.1, Vol 3, Part C, Appendix A recommends the FAST1 parameters for connectable advertising and
// FAST2 parameters for non-connectable advertising. Some Bluetooth controllers reject the FAST1
// parameters for non-connectable advertising, hence we fall back to FAST2 in that case.
if (interval == AdvertisingInterval::FAST1 && !connect_callback) {
interval = AdvertisingInterval::FAST2;
}
hci::LowEnergyAdvertiser::AdvertisingOptions options(GetIntervalRange(interval), anonymous,
AdvFlag::kLEGeneralDiscoverableMode,
include_tx_power_level);
auto self = weak_ptr_factory_.GetWeakPtr();
// TODO(fxbug.dev/1335): The address used for legacy advertising must be
// coordinated via |local_addr_delegate_| however a unique address can be
// generated and assigned to each advertising set when the controller
// supports 5.0 extended advertising. hci::LowEnergyAdvertiser needs to be
// revised to not use device addresses to distinguish between advertising
// instances especially since |local_addr_delegate_| is likely to return the
// same address if called frequently.
//
// Revisit this logic when multi-advertising is supported.
local_addr_delegate_->EnsureLocalAddress(
[self, data = std::move(data), scan_rsp = std::move(scan_rsp), options,
connect_cb = std::move(connect_callback),
status_cb = std::move(status_callback)](const auto& address) mutable {
if (!self)
return;
auto ad_ptr = std::make_unique<ActiveAdvertisement>(address);
hci::LowEnergyAdvertiser::ConnectionCallback adv_conn_cb;
if (connect_cb) {
adv_conn_cb = [self, id = ad_ptr->id(), connect_cb = std::move(connect_cb)](auto link) {
bt_log(DEBUG, "gap-le", "received new connection");
if (!self)
return;
// remove the advertiser because advertising has stopped
self->advertisements_.erase(id);
connect_cb(id, std::move(link));
};
}
auto status_cb_wrapper = [self, ad_ptr = std::move(ad_ptr),
status_cb = std::move(status_cb)](hci::Status status) mutable {
if (!self)
return;
if (!status) {
status_cb(AdvertisementInstance(), status);
return;
}
auto id = ad_ptr->id();
self->advertisements_.emplace(id, std::move(ad_ptr));
status_cb(AdvertisementInstance(id, self), status);
};
// Call StartAdvertising, with the callback
self->advertiser_->StartAdvertising(address, data, scan_rsp, options,
std::move(adv_conn_cb), std::move(status_cb_wrapper));
});
}
bool LowEnergyAdvertisingManager::StopAdvertising(AdvertisementId advertisement_id) {
auto it = advertisements_.find(advertisement_id);
if (it == advertisements_.end())
return false;
advertiser_->StopAdvertising(it->second->address());
advertisements_.erase(it);
return true;
}
} // namespace bt::gap