| // 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>(¶meters.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>(¶meters.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 |