// 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 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(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::Advertise( const AdvertisingParameters& parameters) { pw::expected data_result = AdvertisingDataFrom(parameters.data); if (!data_result.has_value()) { return async2::OnceReceiver( 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 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 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(¶meters.procedure)) { if (legacy->scan_response.has_value()) { pw::expected scan_response_result = AdvertisingDataFrom(legacy->scan_response.value()); if (!scan_response_result.has_value()) { return async2::OnceReceiver( 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(¶meters.procedure)) { extended_pdu = true; if (std::get_if(&extended->configuration)) { anonymous = true; } else if (const ScanResponse* scan_response_param = std::get_if(&extended->configuration)) { pw::expected scan_response_result = AdvertisingDataFrom(*scan_response_param); if (!scan_response_result.has_value()) { return async2::OnceReceiver( pw::unexpected(scan_response_result.error())); } scan_response = std::move(scan_response_result.value()); } else if (const ConnectionOptions* connection_options = std::get_if(&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( pw::unexpected(AdvertiseError::kNotSupported)); } auto [result_sender, result_receiver] = async2::MakeOnceSenderAndReceiver(); 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 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 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