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