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