1 // Copyright 2023 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/internal/host/hci/extended_low_energy_advertiser.h"
16
17 #include "pw_bluetooth_sapphire/internal/host/hci-spec/util.h"
18 #include "pw_bluetooth_sapphire/internal/host/transport/transport.h"
19
20 namespace bt::hci {
21
ExtendedLowEnergyAdvertiser(hci::Transport::WeakPtr hci_ptr)22 ExtendedLowEnergyAdvertiser::ExtendedLowEnergyAdvertiser(
23 hci::Transport::WeakPtr hci_ptr)
24 : LowEnergyAdvertiser(std::move(hci_ptr)), weak_self_(this) {
25 auto self = weak_self_.GetWeakPtr();
26 set_terminated_event_handler_id_ =
27 hci()->command_channel()->AddLEMetaEventHandler(
28 hci_spec::kLEAdvertisingSetTerminatedSubeventCode,
29 [self](const EventPacket& event_packet) {
30 if (self.is_alive()) {
31 return self->OnAdvertisingSetTerminatedEvent(event_packet);
32 }
33
34 return CommandChannel::EventCallbackResult::kRemove;
35 });
36 }
37
~ExtendedLowEnergyAdvertiser()38 ExtendedLowEnergyAdvertiser::~ExtendedLowEnergyAdvertiser() {
39 // This object is probably being destroyed because the stack is shutting down,
40 // in which case the HCI layer may have already been destroyed.
41 if (!hci().is_alive() || !hci()->command_channel()) {
42 return;
43 }
44 hci()->command_channel()->RemoveEventHandler(
45 set_terminated_event_handler_id_);
46 // TODO(fxbug.dev/42063496): This will only cancel one advertisement, after
47 // which the SequentialCommandRunner will have been destroyed and no further
48 // commands will be sent.
49 StopAdvertising();
50 }
51
BuildEnablePacket(const DeviceAddress & address,pw::bluetooth::emboss::GenericEnableParam enable)52 EmbossCommandPacket ExtendedLowEnergyAdvertiser::BuildEnablePacket(
53 const DeviceAddress& address,
54 pw::bluetooth::emboss::GenericEnableParam enable) {
55 // We only enable or disable a single address at a time. The multiply by 1 is
56 // set explicitly to show that data[] within
57 // LESetExtendedAdvertisingEnableData is of size 1.
58 constexpr size_t kPacketSize =
59 pw::bluetooth::emboss::LESetExtendedAdvertisingEnableCommand::
60 MinSizeInBytes() +
61 (1 * pw::bluetooth::emboss::LESetExtendedAdvertisingEnableData::
62 IntrinsicSizeInBytes());
63 auto packet = hci::EmbossCommandPacket::New<
64 pw::bluetooth::emboss::LESetExtendedAdvertisingEnableCommandWriter>(
65 hci_spec::kLESetExtendedAdvertisingEnable, kPacketSize);
66 auto packet_view = packet.view_t();
67 packet_view.enable().Write(enable);
68 packet_view.num_sets().Write(1);
69
70 std::optional<hci_spec::AdvertisingHandle> handle =
71 advertising_handle_map_.GetHandle(address);
72 BT_ASSERT(handle);
73
74 packet_view.data()[0].advertising_handle().Write(handle.value());
75 packet_view.data()[0].duration().Write(hci_spec::kNoAdvertisingDuration);
76 packet_view.data()[0].max_extended_advertising_events().Write(
77 hci_spec::kNoMaxExtendedAdvertisingEvents);
78
79 return packet;
80 }
81
82 CommandChannel::CommandPacketVariant
BuildSetAdvertisingParams(const DeviceAddress & address,pw::bluetooth::emboss::LEAdvertisingType type,pw::bluetooth::emboss::LEOwnAddressType own_address_type,AdvertisingIntervalRange interval)83 ExtendedLowEnergyAdvertiser::BuildSetAdvertisingParams(
84 const DeviceAddress& address,
85 pw::bluetooth::emboss::LEAdvertisingType type,
86 pw::bluetooth::emboss::LEOwnAddressType own_address_type,
87 AdvertisingIntervalRange interval) {
88 constexpr size_t kPacketSize = pw::bluetooth::emboss::
89 LESetExtendedAdvertisingParametersV1CommandView::SizeInBytes();
90 auto packet = hci::EmbossCommandPacket::New<
91 pw::bluetooth::emboss::LESetExtendedAdvertisingParametersV1CommandWriter>(
92 hci_spec::kLESetExtendedAdvertisingParameters, kPacketSize);
93 auto packet_view = packet.view_t();
94
95 // advertising handle
96 std::optional<hci_spec::AdvertisingHandle> handle =
97 advertising_handle_map_.MapHandle(address);
98 if (!handle) {
99 bt_log(WARN,
100 "hci-le",
101 "could not allocate a new advertising handle for address: %s (all "
102 "in use)",
103 bt_str(address));
104 return std::unique_ptr<CommandPacket>();
105 }
106 packet_view.advertising_handle().Write(handle.value());
107
108 // advertising event properties
109 std::optional<hci_spec::AdvertisingEventBits> bits =
110 hci_spec::AdvertisingTypeToEventBits(type);
111 if (!bits) {
112 bt_log(WARN,
113 "hci-le",
114 "could not generate event bits for type: %hhu",
115 static_cast<unsigned char>(type));
116 return std::unique_ptr<CommandPacket>();
117 }
118 uint16_t properties = bits.value();
119 packet_view.advertising_event_properties().BackingStorage().WriteUInt(
120 properties);
121
122 // advertising interval, NOTE: LE advertising parameters allow for up to 3
123 // octets (10 ms to 10428 s) to configure an advertising interval. However, we
124 // expose only the recommended advertising interval configurations to users,
125 // as specified in the Bluetooth Spec Volume 3, Part C, Appendix A. These
126 // values are expressed as uint16_t so we simply copy them (taking care of
127 // endianness) into the 3 octets as is.
128 packet_view.primary_advertising_interval_min().Write(interval.min());
129 packet_view.primary_advertising_interval_max().Write(interval.max());
130
131 // advertise on all channels
132 packet_view.primary_advertising_channel_map().channel_37().Write(true);
133 packet_view.primary_advertising_channel_map().channel_38().Write(true);
134 packet_view.primary_advertising_channel_map().channel_39().Write(true);
135
136 packet_view.own_address_type().Write(own_address_type);
137 packet_view.advertising_filter_policy().Write(
138 pw::bluetooth::emboss::LEAdvertisingFilterPolicy::ALLOW_ALL);
139 packet_view.advertising_tx_power().Write(
140 hci_spec::kLEExtendedAdvertisingTxPowerNoPreference);
141 packet_view.scan_request_notification_enable().Write(
142 pw::bluetooth::emboss::GenericEnableParam::DISABLE);
143
144 // TODO(fxbug.dev/42161929): using legacy PDUs requires advertisements on the
145 // LE 1M PHY.
146 packet_view.primary_advertising_phy().Write(
147 pw::bluetooth::emboss::LEPrimaryAdvertisingPHY::LE_1M);
148 packet_view.secondary_advertising_phy().Write(
149 pw::bluetooth::emboss::LESecondaryAdvertisingPHY::LE_1M);
150
151 // Payload values were initialized to zero above. By not setting the values
152 // for the following fields, we are purposely ignoring them:
153 //
154 // advertising_sid: We use only legacy PDUs, the controller ignores this field
155 // in that case peer_address: We don't support directed advertising yet
156 // peer_address_type: We don't support directed advertising yet
157 // secondary_adv_max_skip: We use only legacy PDUs, the controller ignores
158 // this field in that case
159
160 return packet;
161 }
162
163 CommandChannel::CommandPacketVariant
BuildSetAdvertisingData(const DeviceAddress & address,const AdvertisingData & data,AdvFlags flags)164 ExtendedLowEnergyAdvertiser::BuildSetAdvertisingData(
165 const DeviceAddress& address, const AdvertisingData& data, AdvFlags flags) {
166 AdvertisingData adv_data;
167 data.Copy(&adv_data);
168 if (staged_advertising_parameters_.include_tx_power_level) {
169 adv_data.SetTxPower(staged_advertising_parameters_.selected_tx_power_level);
170 }
171 size_t block_size = adv_data.CalculateBlockSize(/*include_flags=*/true);
172
173 size_t kPayloadSize =
174 pw::bluetooth::emboss::LESetExtendedAdvertisingDataCommandView::
175 MinSizeInBytes()
176 .Read() +
177 block_size;
178 auto packet = EmbossCommandPacket::New<
179 pw::bluetooth::emboss::LESetExtendedAdvertisingDataCommandWriter>(
180 hci_spec::kLESetExtendedAdvertisingData, kPayloadSize);
181 auto params = packet.view_t();
182
183 // advertising handle
184 std::optional<hci_spec::AdvertisingHandle> handle =
185 advertising_handle_map_.GetHandle(address);
186 BT_ASSERT(handle);
187 params.advertising_handle().Write(handle.value());
188
189 // TODO(fxbug.dev/42161929): We support only legacy PDUs and do not support
190 // fragmented extended advertising data at this time.
191 params.operation().Write(
192 pw::bluetooth::emboss::LESetExtendedAdvDataOp::COMPLETE);
193 params.fragment_preference().Write(
194 pw::bluetooth::emboss::LEExtendedAdvFragmentPreference::
195 SHOULD_NOT_FRAGMENT);
196
197 // advertising data
198 params.advertising_data_length().Write(static_cast<uint8_t>(block_size));
199 MutableBufferView data_view(params.advertising_data().BackingStorage().data(),
200 params.advertising_data_length().Read());
201 adv_data.WriteBlock(&data_view, flags);
202
203 return packet;
204 }
205
206 CommandChannel::CommandPacketVariant
BuildUnsetAdvertisingData(const DeviceAddress & address)207 ExtendedLowEnergyAdvertiser::BuildUnsetAdvertisingData(
208 const DeviceAddress& address) {
209 constexpr size_t kPacketSize =
210 pw::bluetooth::emboss::LESetExtendedAdvertisingDataCommandView::
211 MinSizeInBytes()
212 .Read();
213 auto packet = EmbossCommandPacket::New<
214 pw::bluetooth::emboss::LESetExtendedAdvertisingDataCommandWriter>(
215 hci_spec::kLESetExtendedAdvertisingData, kPacketSize);
216 auto payload = packet.view_t();
217
218 // advertising handle
219 std::optional<hci_spec::AdvertisingHandle> handle =
220 advertising_handle_map_.GetHandle(address);
221 BT_ASSERT(handle);
222 payload.advertising_handle().Write(handle.value());
223
224 // TODO(fxbug.dev/42161929): We support only legacy PDUs and do not support
225 // fragmented extended advertising data at this time.
226 payload.operation().Write(
227 pw::bluetooth::emboss::LESetExtendedAdvDataOp::COMPLETE);
228 payload.fragment_preference().Write(
229 pw::bluetooth::emboss::LEExtendedAdvFragmentPreference::
230 SHOULD_NOT_FRAGMENT);
231 payload.advertising_data_length().Write(0);
232
233 return packet;
234 }
235
236 CommandChannel::CommandPacketVariant
BuildSetScanResponse(const DeviceAddress & address,const AdvertisingData & data)237 ExtendedLowEnergyAdvertiser::BuildSetScanResponse(const DeviceAddress& address,
238 const AdvertisingData& data) {
239 AdvertisingData scan_rsp;
240 data.Copy(&scan_rsp);
241 if (staged_advertising_parameters_.include_tx_power_level) {
242 scan_rsp.SetTxPower(staged_advertising_parameters_.selected_tx_power_level);
243 }
244 size_t block_size = scan_rsp.CalculateBlockSize();
245
246 size_t kPayloadSize =
247 pw::bluetooth::emboss::LESetExtendedScanResponseDataCommandView::
248 MinSizeInBytes()
249 .Read() +
250 block_size;
251 auto packet = EmbossCommandPacket::New<
252 pw::bluetooth::emboss::LESetExtendedScanResponseDataCommandWriter>(
253 hci_spec::kLESetExtendedScanResponseData, kPayloadSize);
254 auto params = packet.view_t();
255
256 // advertising handle
257 std::optional<hci_spec::AdvertisingHandle> handle =
258 advertising_handle_map_.GetHandle(address);
259 BT_ASSERT(handle);
260 params.advertising_handle().Write(handle.value());
261
262 // TODO(fxbug.dev/42161929): We support only legacy PDUs and do not support
263 // fragmented extended advertising data at this time.
264 params.operation().Write(
265 pw::bluetooth::emboss::LESetExtendedAdvDataOp::COMPLETE);
266 params.fragment_preference().Write(
267 pw::bluetooth::emboss::LEExtendedAdvFragmentPreference::
268 SHOULD_NOT_FRAGMENT);
269
270 // scan response data
271 params.scan_response_data_length().Write(static_cast<uint8_t>(block_size));
272 MutableBufferView scan_rsp_view(
273 params.scan_response_data().BackingStorage().data(),
274 params.scan_response_data_length().Read());
275 scan_rsp.WriteBlock(&scan_rsp_view, std::nullopt);
276
277 return packet;
278 }
279
280 CommandChannel::CommandPacketVariant
BuildUnsetScanResponse(const DeviceAddress & address)281 ExtendedLowEnergyAdvertiser::BuildUnsetScanResponse(
282 const DeviceAddress& address) {
283 constexpr size_t kPacketSize =
284 pw::bluetooth::emboss::LESetExtendedScanResponseDataCommandView::
285 MinSizeInBytes()
286 .Read();
287 auto packet = EmbossCommandPacket::New<
288 pw::bluetooth::emboss::LESetExtendedScanResponseDataCommandWriter>(
289 hci_spec::kLESetExtendedScanResponseData, kPacketSize);
290 auto payload = packet.view_t();
291
292 // advertising handle
293 std::optional<hci_spec::AdvertisingHandle> handle =
294 advertising_handle_map_.GetHandle(address);
295 BT_ASSERT(handle);
296 payload.advertising_handle().Write(handle.value());
297
298 // TODO(fxbug.dev/42161929): We support only legacy PDUs and do not support
299 // fragmented extended advertising data at this time.
300 payload.operation().Write(
301 pw::bluetooth::emboss::LESetExtendedAdvDataOp::COMPLETE);
302 payload.fragment_preference().Write(
303 pw::bluetooth::emboss::LEExtendedAdvFragmentPreference::
304 SHOULD_NOT_FRAGMENT);
305 payload.scan_response_data_length().Write(0);
306
307 return packet;
308 }
309
BuildRemoveAdvertisingSet(const DeviceAddress & address)310 EmbossCommandPacket ExtendedLowEnergyAdvertiser::BuildRemoveAdvertisingSet(
311 const DeviceAddress& address) {
312 std::optional<hci_spec::AdvertisingHandle> handle =
313 advertising_handle_map_.GetHandle(address);
314 BT_ASSERT(handle);
315 auto packet = hci::EmbossCommandPacket::New<
316 pw::bluetooth::emboss::LERemoveAdvertisingSetCommandWriter>(
317 hci_spec::kLERemoveAdvertisingSet);
318 auto packet_view = packet.view_t();
319 packet_view.advertising_handle().Write(handle.value());
320
321 return packet;
322 }
323
OnSetAdvertisingParamsComplete(const EventPacket & event)324 void ExtendedLowEnergyAdvertiser::OnSetAdvertisingParamsComplete(
325 const EventPacket& event) {
326 BT_ASSERT(event.event_code() == hci_spec::kCommandCompleteEventCode);
327 BT_ASSERT(
328 event.params<hci_spec::CommandCompleteEventParams>().command_opcode ==
329 hci_spec::kLESetExtendedAdvertisingParameters);
330
331 Result<> result = event.ToResult();
332 if (bt_is_error(result,
333 WARN,
334 "hci-le",
335 "set advertising parameters, error received: %s",
336 bt_str(result))) {
337 return; // full error handling done in super class, can just return here
338 }
339
340 auto params = event.return_params<
341 hci_spec::LESetExtendedAdvertisingParametersReturnParams>();
342 BT_ASSERT(params);
343
344 if (staged_advertising_parameters_.include_tx_power_level) {
345 staged_advertising_parameters_.selected_tx_power_level =
346 params->selected_tx_power;
347 }
348 }
349
StartAdvertising(const DeviceAddress & address,const AdvertisingData & data,const AdvertisingData & scan_rsp,AdvertisingOptions options,ConnectionCallback connect_callback,ResultFunction<> result_callback)350 void ExtendedLowEnergyAdvertiser::StartAdvertising(
351 const DeviceAddress& address,
352 const AdvertisingData& data,
353 const AdvertisingData& scan_rsp,
354 AdvertisingOptions options,
355 ConnectionCallback connect_callback,
356 ResultFunction<> result_callback) {
357 // if there is an operation currently in progress, enqueue this operation and
358 // we will get to it the next time we have a chance
359 if (!hci_cmd_runner().IsReady()) {
360 bt_log(INFO,
361 "hci-le",
362 "hci cmd runner not ready, queuing advertisement commands for now");
363
364 AdvertisingData copied_data;
365 data.Copy(&copied_data);
366
367 AdvertisingData copied_scan_rsp;
368 scan_rsp.Copy(&copied_scan_rsp);
369
370 op_queue_.push([this,
371 address,
372 data = std::move(copied_data),
373 scan_rsp = std::move(copied_scan_rsp),
374 options,
375 conn_cb = std::move(connect_callback),
376 result_cb = std::move(result_callback)]() mutable {
377 StartAdvertising(address,
378 data,
379 scan_rsp,
380 options,
381 std::move(conn_cb),
382 std::move(result_cb));
383 });
384
385 return;
386 }
387
388 fit::result<HostError> result =
389 CanStartAdvertising(address, data, scan_rsp, options);
390 if (result.is_error()) {
391 result_callback(ToResult(result.error_value()));
392 return;
393 }
394
395 if (IsAdvertising(address)) {
396 bt_log(DEBUG,
397 "hci-le",
398 "updating existing advertisement for %s",
399 bt_str(address));
400 }
401
402 std::memset(&staged_advertising_parameters_,
403 0,
404 sizeof(staged_advertising_parameters_));
405 staged_advertising_parameters_.include_tx_power_level =
406 options.include_tx_power_level;
407
408 // Core Spec, Volume 4, Part E, Section 7.8.58: "the number of advertising
409 // sets that can be supported is not fixed and the Controller can change it at
410 // any time. The memory used to store advertising sets can also be used for
411 // other purposes."
412 //
413 // Depending on the memory profile of the controller, a new advertising set
414 // may or may not be accepted. We could use
415 // HCI_LE_Read_Number_of_Supported_Advertising_Sets to check if the controller
416 // has space for another advertising set. However, the value may change after
417 // the read and before the addition of the advertising set. Furthermore,
418 // sending an extra HCI command increases the latency of our stack. Instead,
419 // we simply attempt to add. If the controller is unable to support another
420 // advertising set, it will respond with a memory capacity exceeded error.
421 StartAdvertisingInternal(address,
422 data,
423 scan_rsp,
424 options.interval,
425 options.flags,
426 std::move(connect_callback),
427 std::move(result_callback));
428 }
429
StopAdvertising()430 void ExtendedLowEnergyAdvertiser::StopAdvertising() {
431 LowEnergyAdvertiser::StopAdvertising();
432 advertising_handle_map_.Clear();
433
434 // std::queue doesn't have a clear method so we have to resort to this
435 // tomfoolery :(
436 decltype(op_queue_) empty;
437 std::swap(op_queue_, empty);
438 }
439
StopAdvertising(const DeviceAddress & address)440 void ExtendedLowEnergyAdvertiser::StopAdvertising(
441 const DeviceAddress& address) {
442 // if there is an operation currently in progress, enqueue this operation and
443 // we will get to it the next time we have a chance
444 if (!hci_cmd_runner().IsReady()) {
445 bt_log(
446 INFO,
447 "hci-le",
448 "hci cmd runner not ready, queueing stop advertising command for now");
449 op_queue_.push([this, address]() { StopAdvertising(address); });
450 return;
451 }
452
453 LowEnergyAdvertiser::StopAdvertisingInternal(address);
454 advertising_handle_map_.RemoveAddress(address);
455 }
456
OnIncomingConnection(hci_spec::ConnectionHandle handle,pw::bluetooth::emboss::ConnectionRole role,const DeviceAddress & peer_address,const hci_spec::LEConnectionParameters & conn_params)457 void ExtendedLowEnergyAdvertiser::OnIncomingConnection(
458 hci_spec::ConnectionHandle handle,
459 pw::bluetooth::emboss::ConnectionRole role,
460 const DeviceAddress& peer_address,
461 const hci_spec::LEConnectionParameters& conn_params) {
462 // Core Spec Volume 4, Part E, Section 7.8.56: Incoming connections to LE
463 // Extended Advertising occur through two events: HCI_LE_Connection_Complete
464 // and HCI_LE_Advertising_Set_Terminated. This method is called as a result of
465 // the HCI_LE_Connection_Complete event. At this point, we only have a
466 // connection handle but don't know the locally advertised address that the
467 // connection is for. Until we receive the HCI_LE_Advertising_Set_Terminated
468 // event, we stage these parameters.
469 staged_connections_map_[handle] = {role, peer_address, conn_params};
470 }
471
472 // The HCI_LE_Advertising_Set_Terminated event contains the mapping between
473 // connection handle and advertising handle. After the
474 // HCI_LE_Advertising_Set_Terminated event, we have all the information
475 // necessary to create a connection object within the Host layer.
476 CommandChannel::EventCallbackResult
OnAdvertisingSetTerminatedEvent(const EventPacket & event)477 ExtendedLowEnergyAdvertiser::OnAdvertisingSetTerminatedEvent(
478 const EventPacket& event) {
479 BT_ASSERT(event.event_code() == hci_spec::kLEMetaEventCode);
480 BT_ASSERT(event.params<hci_spec::LEMetaEventParams>().subevent_code ==
481 hci_spec::kLEAdvertisingSetTerminatedSubeventCode);
482
483 Result<> result = event.ToResult();
484 if (bt_is_error(result,
485 ERROR,
486 "hci-le",
487 "advertising set terminated event, error received %s",
488 bt_str(result))) {
489 return CommandChannel::EventCallbackResult::kContinue;
490 }
491
492 auto params = event.subevent_params<
493 hci_spec::LEAdvertisingSetTerminatedSubeventParams>();
494 BT_ASSERT(params);
495
496 hci_spec::ConnectionHandle connection_handle = params->connection_handle;
497 auto staged_parameters_node =
498 staged_connections_map_.extract(connection_handle);
499
500 if (staged_parameters_node.empty()) {
501 bt_log(ERROR,
502 "hci-le",
503 "advertising set terminated event, staged params not available "
504 "(handle: %d)",
505 params->adv_handle);
506 return CommandChannel::EventCallbackResult::kContinue;
507 }
508
509 hci_spec::AdvertisingHandle adv_handle = params->adv_handle;
510 std::optional<DeviceAddress> opt_local_address =
511 advertising_handle_map_.GetAddress(adv_handle);
512
513 // We use the identity address as the local address if we aren't advertising
514 // or otherwise don't know about this advertising set. This is obviously
515 // wrong. However, the link will be disconnected in that case before it can
516 // propagate to higher layers.
517 static DeviceAddress identity_address =
518 DeviceAddress(DeviceAddress::Type::kLEPublic, {0});
519 DeviceAddress local_address = identity_address;
520 if (opt_local_address) {
521 local_address = opt_local_address.value();
522 }
523
524 StagedConnectionParameters staged = staged_parameters_node.mapped();
525
526 CompleteIncomingConnection(connection_handle,
527 staged.role,
528 local_address,
529 staged.peer_address,
530 staged.conn_params);
531
532 std::memset(&staged_advertising_parameters_,
533 0,
534 sizeof(staged_advertising_parameters_));
535 return CommandChannel::EventCallbackResult::kContinue;
536 }
537
OnCurrentOperationComplete()538 void ExtendedLowEnergyAdvertiser::OnCurrentOperationComplete() {
539 if (op_queue_.empty()) {
540 return; // no more queued operations so nothing to do
541 }
542
543 fit::closure closure = std::move(op_queue_.front());
544 op_queue_.pop();
545 closure();
546 }
547
548 } // namespace bt::hci
549