xref: /aosp_15_r20/external/pigweed/pw_bluetooth_sapphire/host/hci/low_energy_advertiser.cc (revision 61c4878ac05f98d0ceed94b57d316916de578985)
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/low_energy_advertiser.h"
16 
17 #include "pw_bluetooth_sapphire/internal/host/hci/sequential_command_runner.h"
18 
19 namespace bt::hci {
20 namespace pwemb = pw::bluetooth::emboss;
21 
LowEnergyAdvertiser(hci::Transport::WeakPtr hci,uint16_t max_advertising_data_length)22 LowEnergyAdvertiser::LowEnergyAdvertiser(hci::Transport::WeakPtr hci,
23                                          uint16_t max_advertising_data_length)
24     : hci_(std::move(hci)),
25       hci_cmd_runner_(std::make_unique<SequentialCommandRunner>(
26           hci_->command_channel()->AsWeakPtr())),
27       max_advertising_data_length_(max_advertising_data_length) {}
28 
GetSizeLimit(const AdvertisingEventProperties & properties,const AdvertisingOptions & options) const29 size_t LowEnergyAdvertiser::GetSizeLimit(
30     const AdvertisingEventProperties& properties,
31     const AdvertisingOptions& options) const {
32   if (!properties.use_legacy_pdus) {
33     return max_advertising_data_length_;
34   }
35 
36   // Core Spec Version 5.4, Volume 6, Part B, Section 2.3.1.2: legacy
37   // advertising PDUs that use directed advertising (ADV_DIRECT_IND) don't
38   // have an advertising data field in their payloads.
39   if (properties.IsDirected()) {
40     return 0;
41   }
42 
43   uint16_t size_limit = hci_spec::kMaxLEAdvertisingDataLength;
44 
45   // Core Spec Version 5.4, Volume 6, Part B, Section 2.3, Figure 2.5: Legacy
46   // advertising PDUs headers don't have a predesignated field for tx power.
47   // Instead, we include it in the Host advertising data itself. Subtract the
48   // size it will take up from the allowable remaining data size.
49   if (options.include_tx_power_level) {
50     size_limit -= kTLVTxPowerLevelSize;
51   }
52 
53   return size_limit;
54 }
55 
CanStartAdvertising(const DeviceAddress & address,const AdvertisingData & data,const AdvertisingData & scan_rsp,const AdvertisingOptions & options,const ConnectionCallback & connect_callback) const56 fit::result<HostError> LowEnergyAdvertiser::CanStartAdvertising(
57     const DeviceAddress& address,
58     const AdvertisingData& data,
59     const AdvertisingData& scan_rsp,
60     const AdvertisingOptions& options,
61     const ConnectionCallback& connect_callback) const {
62   PW_CHECK(address.type() != DeviceAddress::Type::kBREDR);
63 
64   if (options.anonymous) {
65     bt_log(WARN, "hci-le", "anonymous advertising not supported");
66     return fit::error(HostError::kNotSupported);
67   }
68 
69   AdvertisingEventProperties properties =
70       GetAdvertisingEventProperties(data, scan_rsp, options, connect_callback);
71 
72   // Core Spec Version 5.4, Volume 5, Part E, Section 7.8.53: If extended
73   // advertising PDU types are being used then the advertisement shall not be
74   // both connectable and scannable.
75   if (!properties.use_legacy_pdus && properties.connectable &&
76       properties.scannable) {
77     bt_log(
78         WARN,
79         "hci-le",
80         "extended advertising pdus cannot be both connectable and scannable");
81     return fit::error(HostError::kNotSupported);
82   }
83 
84   size_t size_limit = GetSizeLimit(properties, options);
85   if (size_t size = data.CalculateBlockSize(/*include_flags=*/true);
86       size > size_limit) {
87     bt_log(WARN,
88            "hci-le",
89            "advertising data too large (actual: %zu, max: %zu)",
90            size,
91            size_limit);
92     return fit::error(HostError::kAdvertisingDataTooLong);
93   }
94 
95   if (size_t size = scan_rsp.CalculateBlockSize(/*include_flags=*/false);
96       size > size_limit) {
97     bt_log(WARN,
98            "hci-le",
99            "scan response too large (actual: %zu, max: %zu)",
100            size,
101            size_limit);
102     return fit::error(HostError::kScanResponseTooLong);
103   }
104 
105   return fit::ok();
106 }
107 
108 static LowEnergyAdvertiser::AdvertisingEventProperties
GetExtendedAdvertisingEventProperties(const AdvertisingData &,const AdvertisingData & scan_rsp,const LowEnergyAdvertiser::AdvertisingOptions & options,const LowEnergyAdvertiser::ConnectionCallback & connect_callback)109 GetExtendedAdvertisingEventProperties(
110     const AdvertisingData&,
111     const AdvertisingData& scan_rsp,
112     const LowEnergyAdvertiser::AdvertisingOptions& options,
113     const LowEnergyAdvertiser::ConnectionCallback& connect_callback) {
114   LowEnergyAdvertiser::AdvertisingEventProperties properties;
115 
116   if (connect_callback) {
117     properties.connectable = true;
118   }
119 
120   if (scan_rsp.CalculateBlockSize() > 0) {
121     properties.scannable = true;
122   }
123 
124   // don't set the following fields because we don't currently support sending
125   // out directed advertisements:
126   //   - directed
127   //   - high_duty_cycle_directed_connectable
128 
129   if (!options.extended_pdu) {
130     properties.use_legacy_pdus = true;
131   }
132 
133   if (options.anonymous) {
134     properties.anonymous_advertising = true;
135   }
136 
137   if (options.include_tx_power_level) {
138     properties.include_tx_power = true;
139   }
140 
141   return properties;
142 }
143 
144 static LowEnergyAdvertiser::AdvertisingEventProperties
GetLegacyAdvertisingEventProperties(const AdvertisingData &,const AdvertisingData & scan_rsp,const LowEnergyAdvertiser::AdvertisingOptions &,const LowEnergyAdvertiser::ConnectionCallback & connect_callback)145 GetLegacyAdvertisingEventProperties(
146     const AdvertisingData&,
147     const AdvertisingData& scan_rsp,
148     const LowEnergyAdvertiser::AdvertisingOptions&,
149     const LowEnergyAdvertiser::ConnectionCallback& connect_callback) {
150   LowEnergyAdvertiser::AdvertisingEventProperties properties;
151   properties.use_legacy_pdus = true;
152 
153   // ADV_IND
154   if (connect_callback) {
155     properties.connectable = true;
156     properties.scannable = true;
157     return properties;
158   }
159 
160   // ADV_SCAN_IND
161   if (scan_rsp.CalculateBlockSize() > 0) {
162     properties.scannable = true;
163     return properties;
164   }
165 
166   // ADV_NONCONN_IND
167   return properties;
168 }
169 
170 LowEnergyAdvertiser::AdvertisingEventProperties
GetAdvertisingEventProperties(const AdvertisingData & data,const AdvertisingData & scan_rsp,const AdvertisingOptions & options,const ConnectionCallback & connect_callback)171 LowEnergyAdvertiser::GetAdvertisingEventProperties(
172     const AdvertisingData& data,
173     const AdvertisingData& scan_rsp,
174     const AdvertisingOptions& options,
175     const ConnectionCallback& connect_callback) {
176   if (options.extended_pdu) {
177     return GetExtendedAdvertisingEventProperties(
178         data, scan_rsp, options, connect_callback);
179   }
180 
181   return GetLegacyAdvertisingEventProperties(
182       data, scan_rsp, options, connect_callback);
183 }
184 
185 pwemb::LEAdvertisingType
AdvertisingEventPropertiesToLEAdvertisingType(const AdvertisingEventProperties & p)186 LowEnergyAdvertiser::AdvertisingEventPropertiesToLEAdvertisingType(
187     const AdvertisingEventProperties& p) {
188   // ADV_IND
189   if (!p.high_duty_cycle_directed_connectable && !p.directed && p.scannable &&
190       p.connectable) {
191     return pwemb::LEAdvertisingType::CONNECTABLE_AND_SCANNABLE_UNDIRECTED;
192   }
193 
194   // ADV_DIRECT_IND
195   if (!p.high_duty_cycle_directed_connectable && p.directed && !p.scannable &&
196       p.connectable) {
197     return pwemb::LEAdvertisingType::CONNECTABLE_LOW_DUTY_CYCLE_DIRECTED;
198   }
199 
200   // ADV_DIRECT_IND
201   if (p.high_duty_cycle_directed_connectable && p.directed && !p.scannable &&
202       p.connectable) {
203     return pwemb::LEAdvertisingType::CONNECTABLE_HIGH_DUTY_CYCLE_DIRECTED;
204   }
205 
206   // ADV_SCAN_IND
207   if (!p.high_duty_cycle_directed_connectable && !p.directed && p.scannable &&
208       !p.connectable) {
209     return pwemb::LEAdvertisingType::SCANNABLE_UNDIRECTED;
210   }
211 
212   // ADV_NONCONN_IND
213   return pwemb::LEAdvertisingType::NOT_CONNECTABLE_UNDIRECTED;
214 }
215 
StartAdvertisingInternal(const DeviceAddress & address,const AdvertisingData & data,const AdvertisingData & scan_rsp,const AdvertisingOptions & options,ConnectionCallback connect_callback,hci::ResultFunction<> result_callback)216 void LowEnergyAdvertiser::StartAdvertisingInternal(
217     const DeviceAddress& address,
218     const AdvertisingData& data,
219     const AdvertisingData& scan_rsp,
220     const AdvertisingOptions& options,
221     ConnectionCallback connect_callback,
222     hci::ResultFunction<> result_callback) {
223   if (IsAdvertising(address, options.extended_pdu)) {
224     // Temporarily disable advertising so we can tweak the parameters
225     CommandPacket packet = BuildEnablePacket(
226         address, pwemb::GenericEnableParam::DISABLE, options.extended_pdu);
227     hci_cmd_runner_->QueueCommand(packet);
228   }
229 
230   data.Copy(&staged_parameters_.data);
231   scan_rsp.Copy(&staged_parameters_.scan_rsp);
232 
233   pwemb::LEOwnAddressType own_addr_type;
234   if (address.type() == DeviceAddress::Type::kLEPublic) {
235     own_addr_type = pwemb::LEOwnAddressType::PUBLIC;
236   } else {
237     own_addr_type = pwemb::LEOwnAddressType::RANDOM;
238   }
239 
240   AdvertisingEventProperties properties =
241       GetAdvertisingEventProperties(data, scan_rsp, options, connect_callback);
242   std::optional<CommandPacket> set_adv_params_packet =
243       BuildSetAdvertisingParams(address,
244                                 properties,
245                                 own_addr_type,
246                                 options.interval,
247                                 options.extended_pdu);
248   if (!set_adv_params_packet) {
249     bt_log(
250         WARN, "hci-le", "failed to start advertising for %s", bt_str(address));
251     return;
252   }
253 
254   hci_cmd_runner_->QueueCommand(
255       *set_adv_params_packet,
256       fit::bind_member<&LowEnergyAdvertiser::OnSetAdvertisingParamsComplete>(
257           this));
258 
259   // In order to support use cases where advertisers use the return parameters
260   // of the SetAdvertisingParams HCI command, we place the remaining advertising
261   // setup HCI commands in the result callback here. SequentialCommandRunner
262   // doesn't allow enqueuing commands within a callback (during a run).
263   hci_cmd_runner_->RunCommands(
264       [this,
265        address,
266        options,
267        result_cb = std::move(result_callback),
268        connect_cb = std::move(connect_callback)](hci::Result<> result) mutable {
269         if (bt_is_error(result,
270                         WARN,
271                         "hci-le",
272                         "failed to start advertising for %s",
273                         bt_str(address))) {
274           result_cb(result);
275           return;
276         }
277 
278         bool success = StartAdvertisingInternalStep2(
279             address, options, std::move(connect_cb), std::move(result_cb));
280         if (!success) {
281           result_cb(ToResult(HostError::kCanceled));
282         }
283       });
284 }
285 
StartAdvertisingInternalStep2(const DeviceAddress & address,const AdvertisingOptions & options,ConnectionCallback connect_callback,hci::ResultFunction<> result_callback)286 bool LowEnergyAdvertiser::StartAdvertisingInternalStep2(
287     const DeviceAddress& address,
288     const AdvertisingOptions& options,
289     ConnectionCallback connect_callback,
290     hci::ResultFunction<> result_callback) {
291   std::vector<CommandPacket> set_adv_data_packets = BuildSetAdvertisingData(
292       address, staged_parameters_.data, options.flags, options.extended_pdu);
293   for (auto& packet : set_adv_data_packets) {
294     hci_cmd_runner_->QueueCommand(std::move(packet));
295   }
296 
297   std::vector<CommandPacket> set_scan_rsp_packets = BuildSetScanResponse(
298       address, staged_parameters_.scan_rsp, options.extended_pdu);
299   for (auto& packet : set_scan_rsp_packets) {
300     hci_cmd_runner_->QueueCommand(std::move(packet));
301   }
302 
303   CommandPacket enable_packet = BuildEnablePacket(
304       address, pwemb::GenericEnableParam::ENABLE, options.extended_pdu);
305   hci_cmd_runner_->QueueCommand(enable_packet);
306 
307   staged_parameters_.reset();
308   hci_cmd_runner_->RunCommands([this,
309                                 address,
310                                 extended_pdu = options.extended_pdu,
311                                 result_cb = std::move(result_callback),
312                                 connect_cb = std::move(connect_callback)](
313                                    Result<> result) mutable {
314     if (!bt_is_error(result,
315                      WARN,
316                      "hci-le",
317                      "failed to start advertising for %s",
318                      bt_str(address))) {
319       bt_log(INFO, "hci-le", "advertising enabled for %s", bt_str(address));
320       connection_callbacks_[{address, extended_pdu}] = std::move(connect_cb);
321     }
322 
323     result_cb(result);
324     OnCurrentOperationComplete();
325   });
326 
327   return true;
328 }
329 
330 // We have StopAdvertising(address) so one would naturally think to implement
331 // StopAdvertising() by iterating through all addresses and calling
332 // StopAdvertising(address) on each iteration. However, such an implementation
333 // won't work. Each call to StopAdvertising(address) checks if the command
334 // runner is running, cancels any pending commands if it is, and then issues new
335 // ones. Called in quick succession, StopAdvertising(address) won't have a
336 // chance to finish its previous HCI commands before being cancelled. Instead,
337 // we must enqueue them all at once and then run them together.
StopAdvertising()338 void LowEnergyAdvertiser::StopAdvertising() {
339   if (!hci_cmd_runner_->IsReady()) {
340     hci_cmd_runner_->Cancel();
341   }
342 
343   for (auto itr = connection_callbacks_.begin();
344        itr != connection_callbacks_.end();) {
345     const auto& [address, extended_pdu] = itr->first;
346 
347     bool success = EnqueueStopAdvertisingCommands(address, extended_pdu);
348     if (success) {
349       itr = connection_callbacks_.erase(itr);
350     } else {
351       bt_log(WARN, "hci-le", "cannot stop advertising for %s", bt_str(address));
352       itr++;
353     }
354   }
355 
356   if (hci_cmd_runner_->HasQueuedCommands()) {
357     hci_cmd_runner_->RunCommands([this](hci::Result<> result) {
358       bt_log(INFO, "hci-le", "advertising stopped: %s", bt_str(result));
359       OnCurrentOperationComplete();
360     });
361   }
362 }
363 
StopAdvertisingInternal(const DeviceAddress & address,bool extended_pdu)364 void LowEnergyAdvertiser::StopAdvertisingInternal(const DeviceAddress& address,
365                                                   bool extended_pdu) {
366   if (!IsAdvertising(address, extended_pdu)) {
367     return;
368   }
369 
370   bool success = EnqueueStopAdvertisingCommands(address, extended_pdu);
371   if (!success) {
372     bt_log(WARN, "hci-le", "cannot stop advertising for %s", bt_str(address));
373     return;
374   }
375 
376   hci_cmd_runner_->RunCommands([this, address](Result<> result) {
377     bt_log(INFO,
378            "hci-le",
379            "advertising stopped for %s: %s",
380            bt_str(address),
381            bt_str(result));
382     OnCurrentOperationComplete();
383   });
384 
385   connection_callbacks_.erase({address, extended_pdu});
386 }
387 
EnqueueStopAdvertisingCommands(const DeviceAddress & address,bool extended_pdu)388 bool LowEnergyAdvertiser::EnqueueStopAdvertisingCommands(
389     const DeviceAddress& address, bool extended_pdu) {
390   CommandPacket disable_packet = BuildEnablePacket(
391       address, pwemb::GenericEnableParam::DISABLE, extended_pdu);
392   CommandPacket unset_scan_rsp_packet =
393       BuildUnsetScanResponse(address, extended_pdu);
394   CommandPacket unset_adv_data_packet =
395       BuildUnsetAdvertisingData(address, extended_pdu);
396   CommandPacket remove_packet =
397       BuildRemoveAdvertisingSet(address, extended_pdu);
398 
399   hci_cmd_runner_->QueueCommand(disable_packet);
400   hci_cmd_runner_->QueueCommand(unset_scan_rsp_packet);
401   hci_cmd_runner_->QueueCommand(unset_adv_data_packet);
402   hci_cmd_runner_->QueueCommand(remove_packet);
403 
404   return true;
405 }
406 
CompleteIncomingConnection(hci_spec::ConnectionHandle handle,pwemb::ConnectionRole role,const DeviceAddress & local_address,const DeviceAddress & peer_address,const hci_spec::LEConnectionParameters & conn_params,bool extended_pdu)407 void LowEnergyAdvertiser::CompleteIncomingConnection(
408     hci_spec::ConnectionHandle handle,
409     pwemb::ConnectionRole role,
410     const DeviceAddress& local_address,
411     const DeviceAddress& peer_address,
412     const hci_spec::LEConnectionParameters& conn_params,
413     bool extended_pdu) {
414   // Immediately construct a Connection object. If this object goes out of scope
415   // following the error checks below, it will send the a command to disconnect
416   // the link.
417   std::unique_ptr<LowEnergyConnection> link =
418       std::make_unique<LowEnergyConnection>(
419           handle, local_address, peer_address, conn_params, role, hci());
420 
421   if (!IsAdvertising(local_address, extended_pdu)) {
422     bt_log(DEBUG,
423            "hci-le",
424            "connection received without advertising address (role: %d, local "
425            "address: %s, peer "
426            "address: %s, connection parameters: %s)",
427            static_cast<uint8_t>(role),
428            bt_str(local_address),
429            bt_str(peer_address),
430            bt_str(conn_params));
431     return;
432   }
433 
434   ConnectionCallback connect_callback =
435       std::move(connection_callbacks_[{local_address, extended_pdu}]);
436   if (!connect_callback) {
437     bt_log(DEBUG,
438            "hci-le",
439            "connection received when not connectable (role: %d, "
440            "local address: %s, "
441            "peer address: %s, "
442            "connection parameters: %s)",
443            static_cast<uint8_t>(role),
444            bt_str(local_address),
445            bt_str(peer_address),
446            bt_str(conn_params));
447     return;
448   }
449 
450   StopAdvertising(local_address, extended_pdu);
451   connect_callback(std::move(link));
452   connection_callbacks_.erase({local_address, extended_pdu});
453 }
454 
455 }  // namespace bt::hci
456