xref: /aosp_15_r20/external/pigweed/pw_bluetooth_sapphire/peripheral.cc (revision 61c4878ac05f98d0ceed94b57d316916de578985)
1 // Copyright 2024 The Pigweed Authors
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License"); you may not
4 // use this file except in compliance with the License. You may obtain a copy of
5 // the License at
6 //
7 //     https://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12 // License for the specific language governing permissions and limitations under
13 // the License.
14 
15 #include "pw_bluetooth_sapphire/peripheral.h"
16 
17 namespace pw::bluetooth_sapphire {
18 
19 static pw::sync::Mutex g_peripheral_lock;
20 
21 using AdvertiseError = pw::bluetooth::low_energy::Peripheral2::AdvertiseError;
22 using LegacyAdvertising =
23     pw::bluetooth::low_energy::Peripheral2::LegacyAdvertising;
24 using ExtendedAdvertising =
25     pw::bluetooth::low_energy::Peripheral2::ExtendedAdvertising;
26 using ScanResponse = pw::bluetooth::low_energy::Peripheral2::ScanResponse;
27 using Anonymous =
28     pw::bluetooth::low_energy::Peripheral2::ExtendedAdvertising::Anonymous;
29 using AdvertisedPeripheral2 = pw::bluetooth::low_energy::AdvertisedPeripheral2;
30 
31 namespace {
UuidFrom(const pw::bluetooth::Uuid & uuid)32 bt::UUID UuidFrom(const pw::bluetooth::Uuid& uuid) {
33   return bt::UUID(bt::BufferView(pw::as_bytes(uuid.As128BitSpan())));
34 }
35 
AdvertisingDataFrom(const pw::bluetooth::low_energy::AdvertisingData & data_in)36 pw::expected<bt::AdvertisingData, AdvertiseError> AdvertisingDataFrom(
37     const pw::bluetooth::low_energy::AdvertisingData& data_in) {
38   bt::AdvertisingData data_out;
39 
40   if (!data_out.SetLocalName(std::string(data_in.name))) {
41     return pw::unexpected(AdvertiseError::kAdvertisingDataTooLong);
42   }
43 
44   data_out.SetAppearance(static_cast<uint16_t>(data_in.appearance));
45 
46   for (const bluetooth::Uuid& service_uuid : data_in.service_uuids) {
47     if (!data_out.AddServiceUuid(UuidFrom(service_uuid))) {
48       return pw::unexpected(AdvertiseError::kAdvertisingDataTooLong);
49     }
50   }
51 
52   for (const bluetooth::low_energy::ServiceData& service_data :
53        data_in.service_data) {
54     if (!data_out.SetServiceData(UuidFrom(service_data.uuid),
55                                  bt::BufferView(service_data.data))) {
56       return pw::unexpected(AdvertiseError::kAdvertisingDataTooLong);
57     }
58   }
59 
60   for (const bluetooth::low_energy::ManufacturerData& manufacturer_data :
61        data_in.manufacturer_data) {
62     if (!data_out.SetManufacturerData(manufacturer_data.company_id,
63                                       bt::BufferView(manufacturer_data.data))) {
64       return pw::unexpected(AdvertiseError::kAdvertisingDataTooLong);
65     }
66   }
67 
68   for (const std::string_view uri : data_in.uris) {
69     if (!data_out.AddUri(std::string(uri))) {
70       return pw::unexpected(AdvertiseError::kAdvertisingDataTooLong);
71     }
72   }
73   return data_out;
74 }
75 
DeviceAddressTypeFrom(pw::bluetooth::Address::Type address_type)76 bt::DeviceAddress::Type DeviceAddressTypeFrom(
77     pw::bluetooth::Address::Type address_type) {
78   // TODO: https://pwbug.dev/377301546 - Support all types of random addresses
79   // in DeviceAddress::Type.
80   switch (address_type) {
81     case bluetooth::Address::Type::kPublic:
82       return bt::DeviceAddress::Type::kLEPublic;
83     case bluetooth::Address::Type::kRandomStatic:
84       return bt::DeviceAddress::Type::kLERandom;
85     case bluetooth::Address::Type::kRandomResolvablePrivate:
86       return bt::DeviceAddress::Type::kLERandom;
87     case bluetooth::Address::Type::kRandomNonResolvablePrivate:
88       return bt::DeviceAddress::Type::kLERandom;
89   }
90 }
91 
BondableModeFrom(bool bondable)92 bt::sm::BondableMode BondableModeFrom(bool bondable) {
93   return bondable ? bt::sm::BondableMode::Bondable
94                   : bt::sm::BondableMode::NonBondable;
95 }
96 
AdvertiseErrorFrom(bt::hci::Result<> result)97 AdvertiseError AdvertiseErrorFrom(bt::hci::Result<> result) {
98   if (result.error_value().is(bt::HostError::kNotSupported)) {
99     return AdvertiseError::kNotSupported;
100   } else if (result.error_value().is(bt::HostError::kInvalidParameters)) {
101     return AdvertiseError::kInvalidParameters;
102   } else if (result.error_value().is(bt::HostError::kAdvertisingDataTooLong)) {
103     return AdvertiseError::kAdvertisingDataTooLong;
104   } else if (result.error_value().is(bt::HostError::kScanResponseTooLong)) {
105     return AdvertiseError::kScanResponseDataTooLong;
106   }
107   return AdvertiseError::kFailed;
108 }
109 
110 }  // namespace
111 
Peripheral(bt::gap::Adapter::WeakPtr adapter,pw::async::Dispatcher & dispatcher)112 Peripheral::Peripheral(bt::gap::Adapter::WeakPtr adapter,
113                        pw::async::Dispatcher& dispatcher)
114     : dispatcher_(dispatcher), adapter_(std::move(adapter)) {}
115 
~Peripheral()116 Peripheral::~Peripheral() {
117   weak_factory_.InvalidatePtrs();
118   {
119     std::lock_guard guard(lock());
120     for (auto& [_, adv] : advertisements_) {
121       adv.OnStopLocked(pw::Status::Cancelled());
122     }
123     advertisements_.clear();
124   }
125 }
126 
Advertise(const AdvertisingParameters & parameters)127 async2::OnceReceiver<Peripheral::AdvertiseResult> Peripheral::Advertise(
128     const AdvertisingParameters& parameters) {
129   pw::expected<bt::AdvertisingData, AdvertiseError> data_result =
130       AdvertisingDataFrom(parameters.data);
131   if (!data_result.has_value()) {
132     return async2::OnceReceiver<Peripheral::AdvertiseResult>(
133         pw::unexpected(data_result.error()));
134   }
135   bt::AdvertisingData data = std::move(data_result.value());
136   bt::AdvertisingData scan_response;
137   bool include_tx_power_level = parameters.data.include_tx_power_level;
138 
139   // TODO: https://pwbug.dev/377301546 - Use parameters.interval_range & update
140   // internal API to accept a range instead of AdvertisingInterval.
141   bt::gap::AdvertisingInterval interval = bt::gap::AdvertisingInterval::SLOW;
142 
143   std::optional<bt::gap::Adapter::LowEnergy::ConnectableAdvertisingParameters>
144       connectable;
145 
146   auto connection_cb =
147       [self = self_](bt::gap::AdvertisementId advertisement_id,
148                      bt::gap::Adapter::LowEnergy::ConnectionResult result) {
149         if (self.is_alive()) {
150           self->OnConnection(advertisement_id, std::move(result));
151         }
152       };
153 
154   std::optional<bt::DeviceAddress::Type> address_type;
155   if (parameters.address_type.has_value()) {
156     address_type = DeviceAddressTypeFrom(parameters.address_type.value());
157   }
158 
159   bool anonymous = false;
160   bool extended_pdu = false;
161 
162   if (const LegacyAdvertising* legacy =
163           std::get_if<LegacyAdvertising>(&parameters.procedure)) {
164     if (legacy->scan_response.has_value()) {
165       pw::expected<bt::AdvertisingData, AdvertiseError> scan_response_result =
166           AdvertisingDataFrom(legacy->scan_response.value());
167       if (!scan_response_result.has_value()) {
168         return async2::OnceReceiver<Peripheral::AdvertiseResult>(
169             pw::unexpected(scan_response_result.error()));
170       }
171       scan_response = std::move(scan_response_result.value());
172     }
173 
174     if (legacy->connection_options.has_value()) {
175       connectable.emplace();
176       connectable->connection_cb = std::move(connection_cb);
177       connectable->bondable_mode =
178           BondableModeFrom(legacy->connection_options->bondable_mode);
179 
180       // TODO: https://pwbug.dev/377301546 - Use remaining connection options.
181       // Requires modifying Adapter::LowEnergy::StartAdvertising.
182     }
183   } else if (const ExtendedAdvertising* extended =
184                  std::get_if<ExtendedAdvertising>(&parameters.procedure)) {
185     extended_pdu = true;
186 
187     if (std::get_if<Anonymous>(&extended->configuration)) {
188       anonymous = true;
189     } else if (const ScanResponse* scan_response_param =
190                    std::get_if<ScanResponse>(&extended->configuration)) {
191       pw::expected<bt::AdvertisingData, AdvertiseError> scan_response_result =
192           AdvertisingDataFrom(*scan_response_param);
193       if (!scan_response_result.has_value()) {
194         return async2::OnceReceiver<Peripheral::AdvertiseResult>(
195             pw::unexpected(scan_response_result.error()));
196       }
197       scan_response = std::move(scan_response_result.value());
198     } else if (const ConnectionOptions* connection_options =
199                    std::get_if<ConnectionOptions>(&extended->configuration)) {
200       connectable.emplace();
201       connectable->connection_cb = std::move(connection_cb);
202       connectable->bondable_mode =
203           BondableModeFrom(connection_options->bondable_mode);
204 
205       // TODO: https://pwbug.dev/377301546 - Use remaining connection options.
206       // Requires modifying Adapter::LowEnergy::StartAdvertising.
207     }
208   } else {
209     // This should never happen unless additional procedures are added in the
210     // future.
211     bt_log(WARN, "api", "Advertising procedure not supported");
212     return async2::OnceReceiver<Peripheral::AdvertiseResult>(
213         pw::unexpected(AdvertiseError::kNotSupported));
214   }
215 
216   auto [result_sender, result_receiver] =
217       async2::MakeOnceSenderAndReceiver<Peripheral::AdvertiseResult>();
218   auto callback = [self = self_, sender = std::move(result_sender)](
219                       bt::gap::AdvertisementInstance instance,
220                       bt::hci::Result<> status) mutable {
221     if (self.is_alive()) {
222       self->OnAdvertiseResult(std::move(instance), status, std::move(sender));
223     }
224   };
225 
226   // Post to BT dispatcher for thread safety.
227   pw::Status post_status =
228       dispatcher_.Post([adapter = adapter_,
229                         adv_data = std::move(data),
230                         scan_rsp = std::move(scan_response),
231                         interval,
232                         extended_pdu,
233                         anonymous,
234                         include_tx_power_level,
235                         connection_options = std::move(connectable),
236                         address_type,
237                         cb = std::move(callback)](pw::async::Context&,
238                                                   pw::Status status) mutable {
239         if (!status.ok()) {
240           // Dispatcher is shutting down.
241           return;
242         }
243         adapter->le()->StartAdvertising(std::move(adv_data),
244                                         std::move(scan_rsp),
245                                         interval,
246                                         extended_pdu,
247                                         anonymous,
248                                         include_tx_power_level,
249                                         std::move(connection_options),
250                                         address_type,
251                                         std::move(cb));
252       });
253   PW_CHECK_OK(post_status);
254 
255   return std::move(result_receiver);
256 }
257 
lock()258 pw::sync::Mutex& Peripheral::lock() { return g_peripheral_lock; }
259 
StopAdvertising()260 void Peripheral::AdvertisedPeripheralImpl::StopAdvertising() {
261   std::lock_guard guard(Peripheral::lock());
262   if (!peripheral_) {
263     return;
264   }
265 
266   peripheral_->StopAdvertising(id_);
267 }
268 
PendStop(async2::Context & cx)269 async2::Poll<pw::Status> Peripheral::AdvertisedPeripheralImpl::PendStop(
270     async2::Context& cx) {
271   std::lock_guard guard(Peripheral::lock());
272   if (stop_status_.has_value()) {
273     return async2::Ready(stop_status_.value());
274   }
275   PW_ASYNC_STORE_WAKER(cx, waker_, "AdvPeripheralPendStop");
276   return async2::Pending();
277 }
278 
~Advertisement()279 Peripheral::Advertisement::~Advertisement() { OnStopLocked(OkStatus()); }
280 
OnStopLocked(pw::Status status)281 void Peripheral::Advertisement::OnStopLocked(pw::Status status) {
282   if (!advertised_peripheral_) {
283     return;
284   }
285 
286   advertised_peripheral_->stop_status_ = status;
287   advertised_peripheral_->peripheral_ = nullptr;
288   std::move(advertised_peripheral_->waker_).Wake();
289   advertised_peripheral_ = nullptr;
290 }
291 
OnAdvertisedPeripheralDestroyedLocked(bt::gap::AdvertisementId advertisement_id)292 void Peripheral::OnAdvertisedPeripheralDestroyedLocked(
293     bt::gap::AdvertisementId advertisement_id) {
294   auto iter = advertisements_.find(advertisement_id);
295   if (iter == advertisements_.end()) {
296     return;
297   }
298   iter->second.OnAdvertisedPeripheralDestroyedLocked();
299   StopAdvertising(advertisement_id);
300 }
301 
StopAdvertising(bt::gap::AdvertisementId advertisement_id)302 void Peripheral::StopAdvertising(bt::gap::AdvertisementId advertisement_id) {
303   // Post to BT dispatcher for thread safety.
304   pw::Status post_status =
305       dispatcher_.Post([self = self_, id = advertisement_id](
306                            pw::async::Context&, pw::Status status) {
307         if (!self.is_alive() || !status.ok()) {
308           return;
309         }
310         std::lock_guard guard(Peripheral::lock());
311         // TODO: https://pwbug.dev/377301546 - Implement a callback for when
312         // advertising is actually stopped. This just destroys the
313         // AdvertisementInstance and does not wait for advertising to actually
314         // stop, so it does not properly implement
315         // AdvertisedPeripheral2::StopAdvertising().
316         self->advertisements_.erase(id);
317       });
318   PW_CHECK_OK(post_status);
319 }
320 
OnAdvertiseResult(bt::gap::AdvertisementInstance instance,bt::hci::Result<> result,async2::OnceSender<AdvertiseResult> result_sender)321 void Peripheral::OnAdvertiseResult(
322     bt::gap::AdvertisementInstance instance,
323     bt::hci::Result<> result,
324     async2::OnceSender<AdvertiseResult> result_sender) {
325   if (result.is_error()) {
326     result_sender.emplace(pw::unexpected(AdvertiseErrorFrom(result)));
327     return;
328   }
329 
330   bt::gap::AdvertisementId id = instance.id();
331 
332   AdvertisedPeripheralImpl* impl = new AdvertisedPeripheralImpl(id, this);
333   AdvertisedPeripheral2::Ptr advertised_peripheral(impl);
334 
335   std::lock_guard guard(lock());
336   auto [iter, inserted] =
337       advertisements_.try_emplace(id, std::move(instance), impl);
338   PW_CHECK(inserted);
339 
340   result_sender.emplace(std::move(advertised_peripheral));
341 }
342 
OnConnection(bt::gap::AdvertisementId,bt::gap::Adapter::LowEnergy::ConnectionResult)343 void Peripheral::OnConnection(
344     bt::gap::AdvertisementId /*advertisement_id*/,
345     bt::gap::Adapter::LowEnergy::ConnectionResult /*result*/) {
346   // TODO: https://pwbug.dev/377301546 - Implement connection handling.
347 }
348 
349 }  // namespace pw::bluetooth_sapphire
350