blob: ffbeab84edfca93dc4f3d6155b1f28762f8be7cc [file] [log] [blame]
// Copyright 2024 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/peripheral.h"
namespace pw::bluetooth_sapphire {
static pw::sync::Mutex g_peripheral_lock;
using AdvertiseError = pw::bluetooth::low_energy::Peripheral2::AdvertiseError;
using LegacyAdvertising =
pw::bluetooth::low_energy::Peripheral2::LegacyAdvertising;
using ExtendedAdvertising =
pw::bluetooth::low_energy::Peripheral2::ExtendedAdvertising;
using ScanResponse = pw::bluetooth::low_energy::Peripheral2::ScanResponse;
using Anonymous =
pw::bluetooth::low_energy::Peripheral2::ExtendedAdvertising::Anonymous;
using AdvertisedPeripheral2 = pw::bluetooth::low_energy::AdvertisedPeripheral2;
namespace {
bt::UUID UuidFrom(const pw::bluetooth::Uuid& uuid) {
return bt::UUID(bt::BufferView(pw::as_bytes(uuid.As128BitSpan())));
}
pw::expected<bt::AdvertisingData, AdvertiseError> AdvertisingDataFrom(
const pw::bluetooth::low_energy::AdvertisingData& data_in) {
bt::AdvertisingData data_out;
if (!data_out.SetLocalName(std::string(data_in.name))) {
return pw::unexpected(AdvertiseError::kAdvertisingDataTooLong);
}
data_out.SetAppearance(static_cast<uint16_t>(data_in.appearance));
for (const bluetooth::Uuid& service_uuid : data_in.service_uuids) {
if (!data_out.AddServiceUuid(UuidFrom(service_uuid))) {
return pw::unexpected(AdvertiseError::kAdvertisingDataTooLong);
}
}
for (const bluetooth::low_energy::ServiceData& service_data :
data_in.service_data) {
if (!data_out.SetServiceData(UuidFrom(service_data.uuid),
bt::BufferView(service_data.data))) {
return pw::unexpected(AdvertiseError::kAdvertisingDataTooLong);
}
}
for (const bluetooth::low_energy::ManufacturerData& manufacturer_data :
data_in.manufacturer_data) {
if (!data_out.SetManufacturerData(manufacturer_data.company_id,
bt::BufferView(manufacturer_data.data))) {
return pw::unexpected(AdvertiseError::kAdvertisingDataTooLong);
}
}
for (const std::string_view uri : data_in.uris) {
if (!data_out.AddUri(std::string(uri))) {
return pw::unexpected(AdvertiseError::kAdvertisingDataTooLong);
}
}
return data_out;
}
bt::DeviceAddress::Type DeviceAddressTypeFrom(
pw::bluetooth::Address::Type address_type) {
// TODO: https://pwbug.dev/377301546 - Support all types of random addresses
// in DeviceAddress::Type.
switch (address_type) {
case bluetooth::Address::Type::kPublic:
return bt::DeviceAddress::Type::kLEPublic;
case bluetooth::Address::Type::kRandomStatic:
return bt::DeviceAddress::Type::kLERandom;
case bluetooth::Address::Type::kRandomResolvablePrivate:
return bt::DeviceAddress::Type::kLERandom;
case bluetooth::Address::Type::kRandomNonResolvablePrivate:
return bt::DeviceAddress::Type::kLERandom;
}
}
bt::sm::BondableMode BondableModeFrom(bool bondable) {
return bondable ? bt::sm::BondableMode::Bondable
: bt::sm::BondableMode::NonBondable;
}
AdvertiseError AdvertiseErrorFrom(bt::hci::Result<> result) {
if (result.error_value().is(bt::HostError::kNotSupported)) {
return AdvertiseError::kNotSupported;
} else if (result.error_value().is(bt::HostError::kInvalidParameters)) {
return AdvertiseError::kInvalidParameters;
} else if (result.error_value().is(bt::HostError::kAdvertisingDataTooLong)) {
return AdvertiseError::kAdvertisingDataTooLong;
} else if (result.error_value().is(bt::HostError::kScanResponseTooLong)) {
return AdvertiseError::kScanResponseDataTooLong;
}
return AdvertiseError::kFailed;
}
} // namespace
Peripheral::Peripheral(bt::gap::Adapter::WeakPtr adapter,
pw::async::Dispatcher& dispatcher)
: dispatcher_(dispatcher), adapter_(std::move(adapter)) {}
Peripheral::~Peripheral() {
weak_factory_.InvalidatePtrs();
{
std::lock_guard guard(lock());
for (auto& [_, adv] : advertisements_) {
adv.OnStopLocked(pw::Status::Cancelled());
}
advertisements_.clear();
}
}
async2::OnceReceiver<Peripheral::AdvertiseResult> Peripheral::Advertise(
const AdvertisingParameters& parameters) {
pw::expected<bt::AdvertisingData, AdvertiseError> data_result =
AdvertisingDataFrom(parameters.data);
if (!data_result.has_value()) {
return async2::OnceReceiver<Peripheral::AdvertiseResult>(
pw::unexpected(data_result.error()));
}
bt::AdvertisingData data = std::move(data_result.value());
bt::AdvertisingData scan_response;
bool include_tx_power_level = parameters.data.include_tx_power_level;
// TODO: https://pwbug.dev/377301546 - Use parameters.interval_range & update
// internal API to accept a range instead of AdvertisingInterval.
bt::gap::AdvertisingInterval interval = bt::gap::AdvertisingInterval::SLOW;
std::optional<bt::gap::Adapter::LowEnergy::ConnectableAdvertisingParameters>
connectable;
auto connection_cb =
[self = self_](bt::gap::AdvertisementId advertisement_id,
bt::gap::Adapter::LowEnergy::ConnectionResult result) {
if (self.is_alive()) {
self->OnConnection(advertisement_id, std::move(result));
}
};
std::optional<bt::DeviceAddress::Type> address_type;
if (parameters.address_type.has_value()) {
address_type = DeviceAddressTypeFrom(parameters.address_type.value());
}
bool anonymous = false;
bool extended_pdu = false;
if (const LegacyAdvertising* legacy =
std::get_if<LegacyAdvertising>(&parameters.procedure)) {
if (legacy->scan_response.has_value()) {
pw::expected<bt::AdvertisingData, AdvertiseError> scan_response_result =
AdvertisingDataFrom(legacy->scan_response.value());
if (!scan_response_result.has_value()) {
return async2::OnceReceiver<Peripheral::AdvertiseResult>(
pw::unexpected(scan_response_result.error()));
}
scan_response = std::move(scan_response_result.value());
}
if (legacy->connection_options.has_value()) {
connectable.emplace();
connectable->connection_cb = std::move(connection_cb);
connectable->bondable_mode =
BondableModeFrom(legacy->connection_options->bondable_mode);
// TODO: https://pwbug.dev/377301546 - Use remaining connection options.
// Requires modifying Adapter::LowEnergy::StartAdvertising.
}
} else if (const ExtendedAdvertising* extended =
std::get_if<ExtendedAdvertising>(&parameters.procedure)) {
extended_pdu = true;
if (std::get_if<Anonymous>(&extended->configuration)) {
anonymous = true;
} else if (const ScanResponse* scan_response_param =
std::get_if<ScanResponse>(&extended->configuration)) {
pw::expected<bt::AdvertisingData, AdvertiseError> scan_response_result =
AdvertisingDataFrom(*scan_response_param);
if (!scan_response_result.has_value()) {
return async2::OnceReceiver<Peripheral::AdvertiseResult>(
pw::unexpected(scan_response_result.error()));
}
scan_response = std::move(scan_response_result.value());
} else if (const ConnectionOptions* connection_options =
std::get_if<ConnectionOptions>(&extended->configuration)) {
connectable.emplace();
connectable->connection_cb = std::move(connection_cb);
connectable->bondable_mode =
BondableModeFrom(connection_options->bondable_mode);
// TODO: https://pwbug.dev/377301546 - Use remaining connection options.
// Requires modifying Adapter::LowEnergy::StartAdvertising.
}
} else {
// This should never happen unless additional procedures are added in the
// future.
bt_log(WARN, "api", "Advertising procedure not supported");
return async2::OnceReceiver<Peripheral::AdvertiseResult>(
pw::unexpected(AdvertiseError::kNotSupported));
}
auto [result_sender, result_receiver] =
async2::MakeOnceSenderAndReceiver<Peripheral::AdvertiseResult>();
auto callback = [self = self_, sender = std::move(result_sender)](
bt::gap::AdvertisementInstance instance,
bt::hci::Result<> status) mutable {
if (self.is_alive()) {
self->OnAdvertiseResult(std::move(instance), status, std::move(sender));
}
};
// Post to BT dispatcher for thread safety.
pw::Status post_status =
dispatcher_.Post([adapter = adapter_,
adv_data = std::move(data),
scan_rsp = std::move(scan_response),
interval,
extended_pdu,
anonymous,
include_tx_power_level,
connection_options = std::move(connectable),
address_type,
cb = std::move(callback)](pw::async::Context&,
pw::Status status) mutable {
if (!status.ok()) {
// Dispatcher is shutting down.
return;
}
adapter->le()->StartAdvertising(std::move(adv_data),
std::move(scan_rsp),
interval,
extended_pdu,
anonymous,
include_tx_power_level,
std::move(connection_options),
address_type,
std::move(cb));
});
PW_CHECK_OK(post_status);
return std::move(result_receiver);
}
pw::sync::Mutex& Peripheral::lock() { return g_peripheral_lock; }
void Peripheral::AdvertisedPeripheralImpl::StopAdvertising() {
std::lock_guard guard(Peripheral::lock());
if (!peripheral_) {
return;
}
peripheral_->StopAdvertising(id_);
}
async2::Poll<pw::Status> Peripheral::AdvertisedPeripheralImpl::PendStop(
async2::Context& cx) {
std::lock_guard guard(Peripheral::lock());
if (stop_status_.has_value()) {
return async2::Ready(stop_status_.value());
}
PW_ASYNC_STORE_WAKER(cx, waker_, "AdvPeripheralPendStop");
return async2::Pending();
}
Peripheral::Advertisement::~Advertisement() { OnStopLocked(OkStatus()); }
void Peripheral::Advertisement::OnStopLocked(pw::Status status) {
if (!advertised_peripheral_) {
return;
}
advertised_peripheral_->stop_status_ = status;
advertised_peripheral_->peripheral_ = nullptr;
std::move(advertised_peripheral_->waker_).Wake();
advertised_peripheral_ = nullptr;
}
void Peripheral::OnAdvertisedPeripheralDestroyedLocked(
bt::gap::AdvertisementId advertisement_id) {
auto iter = advertisements_.find(advertisement_id);
if (iter == advertisements_.end()) {
return;
}
iter->second.OnAdvertisedPeripheralDestroyedLocked();
StopAdvertising(advertisement_id);
}
void Peripheral::StopAdvertising(bt::gap::AdvertisementId advertisement_id) {
// Post to BT dispatcher for thread safety.
pw::Status post_status =
dispatcher_.Post([self = self_, id = advertisement_id](
pw::async::Context&, pw::Status status) {
if (!self.is_alive() || !status.ok()) {
return;
}
std::lock_guard guard(Peripheral::lock());
// TODO: https://pwbug.dev/377301546 - Implement a callback for when
// advertising is actually stopped. This just destroys the
// AdvertisementInstance and does not wait for advertising to actually
// stop, so it does not properly implement
// AdvertisedPeripheral2::StopAdvertising().
self->advertisements_.erase(id);
});
PW_CHECK_OK(post_status);
}
void Peripheral::OnAdvertiseResult(
bt::gap::AdvertisementInstance instance,
bt::hci::Result<> result,
async2::OnceSender<AdvertiseResult> result_sender) {
if (result.is_error()) {
result_sender.emplace(pw::unexpected(AdvertiseErrorFrom(result)));
return;
}
bt::gap::AdvertisementId id = instance.id();
AdvertisedPeripheralImpl* impl = new AdvertisedPeripheralImpl(id, this);
AdvertisedPeripheral2::Ptr advertised_peripheral(impl);
std::lock_guard guard(lock());
auto [iter, inserted] =
advertisements_.try_emplace(id, std::move(instance), impl);
PW_CHECK(inserted);
result_sender.emplace(std::move(advertised_peripheral));
}
void Peripheral::OnConnection(
bt::gap::AdvertisementId /*advertisement_id*/,
bt::gap::Adapter::LowEnergy::ConnectionResult /*result*/) {
// TODO: https://pwbug.dev/377301546 - Implement connection handling.
}
} // namespace pw::bluetooth_sapphire