• 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 #pragma once
16 #include <pw_assert/assert.h>
17 
18 #include <memory>
19 
20 #include "pw_bluetooth_sapphire/internal/host/hci-spec/constants.h"
21 #include "pw_bluetooth_sapphire/internal/host/hci/local_address_delegate.h"
22 #include "pw_bluetooth_sapphire/internal/host/hci/low_energy_connection.h"
23 #include "pw_bluetooth_sapphire/internal/host/hci/sequential_command_runner.h"
24 #include "pw_bluetooth_sapphire/internal/host/transport/error.h"
25 
26 namespace bt {
27 class AdvertisingData;
28 
29 namespace hci {
30 class Transport;
31 
32 class AdvertisingIntervalRange final {
33  public:
34   // Constructs an advertising interval range, capping the values based on the
35   // allowed range (Vol 2, Part E, 7.8.5).
AdvertisingIntervalRange(uint16_t min,uint16_t max)36   constexpr AdvertisingIntervalRange(uint16_t min, uint16_t max)
37       : min_(std::max(min, hci_spec::kLEAdvertisingIntervalMin)),
38         max_(std::min(max, hci_spec::kLEAdvertisingIntervalMax)) {
39     PW_ASSERT(min < max);
40   }
41 
min()42   uint16_t min() const { return min_; }
max()43   uint16_t max() const { return max_; }
44 
45  private:
46   uint16_t min_;
47   uint16_t max_;
48 };
49 
50 class LowEnergyAdvertiser : public LocalAddressClient {
51  public:
52   explicit LowEnergyAdvertiser(hci::Transport::WeakPtr hci,
53                                uint16_t max_advertising_data_length);
54   ~LowEnergyAdvertiser() override = default;
55 
56   using ConnectionCallback =
57       fit::function<void(std::unique_ptr<hci::LowEnergyConnection> link)>;
58 
59   // TODO(armansito): The |address| parameter of this function doesn't always
60   // correspond to the advertised device address as the local address for an
61   // advertisement cannot always be configured by the advertiser. This is the
62   // case especially in the following conditions:
63   //
64   //   1. The type of |address| is "LE Public". The advertised address always
65   //   corresponds to the
66   //      controller's BD_ADDR. This is the case in both legacy and extended
67   //      advertising.
68   //
69   //   2. The type of |address| is "LE Random" and the advertiser implements
70   //   legacy advertising.
71   //      Since the controller local address is shared between scan, initiation,
72   //      and advertising procedures, the advertiser cannot configure this
73   //      address without interfering with the state of other ongoing
74   //      procedures.
75   //
76   // We should either revisit this interface or update the documentation to
77   // reflect the fact that the |address| is sometimes a hint and may or may not
78   // end up being advertised. Currently the GAP layer decides which address to
79   // pass to this call but the layering should be revisited when we add support
80   // for extended advertising.
81   //
82   // -----
83   //
84   // Attempt to start advertising |data| with |options.flags| and scan response
85   // |scan_rsp| using advertising address |address|. If |options.anonymous| is
86   // set, |address| is ignored.
87   //
88   // If |address| is currently advertised, the advertisement is updated.
89   //
90   // If |connect_callback| is provided, the advertisement will be connectable,
91   // and the provided |status_callback| will be called with a connection
92   // reference when this advertisement is connected to and the advertisement has
93   // been stopped.
94   //
95   // |options.interval| must be a value in "controller timeslices". See
96   // hci-spec/hci_constants.h for the valid range.
97   //
98   // Provides results in |status_callback|. If advertising is setup, the final
99   // interval of advertising is provided in |interval| and |status| is kSuccess.
100   // Otherwise, |status| indicates the type of error and |interval| has no
101   // meaning.
102   //
103   // |status_callback| may be called before this function returns, but will be
104   // called before any calls to |connect_callback|.
105   //
106   // The maximum advertising and scan response data sizes are determined by the
107   // Bluetooth controller (4.x supports up to 31 bytes while 5.x is extended up
108   // to 251). If |data| and |scan_rsp| exceed this internal limit, a
109   // HostError::kAdvertisingDataTooLong or HostError::kScanResponseTooLong error
110   // will be generated.
111   struct AdvertisingOptions {
AdvertisingOptionsAdvertisingOptions112     AdvertisingOptions(AdvertisingIntervalRange init_interval,
113                        AdvFlags init_flags,
114                        bool init_extended_pdu,
115                        bool init_anonymous,
116                        bool init_include_tx_power_level)
117         : interval(init_interval),
118           flags(init_flags),
119           extended_pdu(init_extended_pdu),
120           include_tx_power_level(init_include_tx_power_level),
121           anonymous(init_anonymous) {}
122 
123     AdvertisingIntervalRange interval;
124     AdvFlags flags;
125     bool extended_pdu;
126     bool include_tx_power_level;
127 
128     // TODO(b/42157563): anonymous advertising is currently not // supported
129     bool anonymous;
130   };
131 
132   // Core Spec Version 5.4, Volume 4, Part E, Section 7.8.53: These fields are
133   // the same as those defined in advertising event properties.
134   //
135   // TODO(fxbug.dev/333129711): LEAdvertisingEventProperties is
136   // currently defined in Emboss as a bits field. Unfortunately, this means that
137   // we cannot use it as storage within our own code. Instead, we have to
138   // redefine a struct with the same fields in it if we want to use it as
139   // storage.
140   struct AdvertisingEventProperties {
141     bool connectable = false;
142     bool scannable = false;
143     bool directed = false;
144     bool high_duty_cycle_directed_connectable = false;
145     bool use_legacy_pdus = false;
146     bool anonymous_advertising = false;
147     bool include_tx_power = false;
148 
IsDirectedAdvertisingEventProperties149     bool IsDirected() const {
150       return directed || high_duty_cycle_directed_connectable;
151     }
152   };
153 
154   // Determine the properties of an advertisement based on the parameters the
155   // client has passed in. For example, if the client has included a scan
156   // response, the advertisement should be scannable.
157   static AdvertisingEventProperties GetAdvertisingEventProperties(
158       const AdvertisingData& data,
159       const AdvertisingData& scan_rsp,
160       const AdvertisingOptions& options,
161       const ConnectionCallback& connect_callback);
162 
163   // Convert individual advertisement properties (e.g. connecatble, scannable,
164   // directed, etc) to a legacy LEAdvertisingType
165   static pw::bluetooth::emboss::LEAdvertisingType
166   AdvertisingEventPropertiesToLEAdvertisingType(
167       const AdvertisingEventProperties& p);
168 
169   virtual void StartAdvertising(const DeviceAddress& address,
170                                 const AdvertisingData& data,
171                                 const AdvertisingData& scan_rsp,
172                                 const AdvertisingOptions& options,
173                                 ConnectionCallback connect_callback,
174                                 ResultFunction<> result_callback) = 0;
175 
176   // Stops advertisement on all currently advertising addresses. Idempotent and
177   // asynchronous.
178   virtual void StopAdvertising();
179 
180   // Stops any advertisement currently active on |address|. Idempotent and
181   // asynchronous.
182   virtual void StopAdvertising(const DeviceAddress& address,
183                                bool extended_pdu) = 0;
184 
185   // Callback for an incoming LE connection. This function should be called in
186   // reaction to any connection that was not initiated locally. This object will
187   // determine if it was a result of an active advertisement and route the
188   // connection accordingly.
189   virtual void OnIncomingConnection(
190       hci_spec::ConnectionHandle handle,
191       pw::bluetooth::emboss::ConnectionRole role,
192       const DeviceAddress& peer_address,
193       const hci_spec::LEConnectionParameters& conn_params) = 0;
194 
195   // Returns true if currently advertising at all
IsAdvertising()196   bool IsAdvertising() const { return !connection_callbacks_.empty(); }
197 
198   // Returns true if currently advertising for the given address
IsAdvertising(const DeviceAddress & address,bool extended_pdu)199   bool IsAdvertising(const DeviceAddress& address, bool extended_pdu) const {
200     return connection_callbacks_.count({address, extended_pdu}) != 0;
201   }
202 
203   // Returns the number of advertisements currently registered
NumAdvertisements()204   size_t NumAdvertisements() const { return connection_callbacks_.size(); }
205 
206   // Returns the maximum number of advertisements that can be supported
207   virtual size_t MaxAdvertisements() const = 0;
208 
209  protected:
210   // Build the HCI command packet to enable advertising for the flavor of low
211   // energy advertising being implemented.
212   virtual CommandPacket BuildEnablePacket(
213       const DeviceAddress& address,
214       pw::bluetooth::emboss::GenericEnableParam enable,
215       bool extended_pdu) = 0;
216 
217   // Build the HCI command packet to set the advertising parameters for the
218   // flavor of low energy advertising being implemented.
219   virtual std::optional<CommandPacket> BuildSetAdvertisingParams(
220       const DeviceAddress& address,
221       const AdvertisingEventProperties& properties,
222       pw::bluetooth::emboss::LEOwnAddressType own_address_type,
223       const AdvertisingIntervalRange& interval,
224       bool extended_pdu) = 0;
225 
226   // Build the HCI command packet to set the advertising data for the flavor of
227   // low energy advertising being implemented.
228   virtual std::vector<CommandPacket> BuildSetAdvertisingData(
229       const DeviceAddress& address,
230       const AdvertisingData& data,
231       AdvFlags flags,
232       bool extended_pdu) = 0;
233 
234   // Build the HCI command packet to delete the advertising parameters from the
235   // controller for the flavor of low energy advertising being implemented. This
236   // method is used when stopping an advertisement.
237   virtual CommandPacket BuildUnsetAdvertisingData(const DeviceAddress& address,
238                                                   bool extended_pdu) = 0;
239 
240   // Build the HCI command packet to set the data sent in a scan response (if
241   // requested) for the flavor of low energy advertising being implemented.
242   virtual std::vector<CommandPacket> BuildSetScanResponse(
243       const DeviceAddress& address,
244       const AdvertisingData& scan_rsp,
245       bool extended_pdu) = 0;
246 
247   // Build the HCI command packet to delete the advertising parameters from the
248   // controller for the flavor of low energy advertising being implemented.
249   virtual CommandPacket BuildUnsetScanResponse(const DeviceAddress& address,
250                                                bool extended_pdu) = 0;
251 
252   // Build the HCI command packet to remove the advertising set entirely from
253   // the controller's memory for the flavor of low energy advertising being
254   // implemented.
255   virtual CommandPacket BuildRemoveAdvertisingSet(const DeviceAddress& address,
256                                                   bool extended_pdu) = 0;
257 
258   // Called when the command packet created with BuildSetAdvertisingParams
259   // returns with a result
OnSetAdvertisingParamsComplete(const EventPacket &)260   virtual void OnSetAdvertisingParamsComplete(const EventPacket&) {}
261 
262   // Called when a sequence of HCI commands that form a single operation (e.g.
263   // start advertising, stop advertising) completes in its entirety. Subclasses
264   // can override this method to be notified when the HCI command runner is
265   // available once again.
OnCurrentOperationComplete()266   virtual void OnCurrentOperationComplete() {}
267 
268   // Get the current limit in bytes of the advertisement data supported.
269   size_t GetSizeLimit(const AdvertisingEventProperties& properties,
270                       const AdvertisingOptions& options) const;
271 
272   // Check whether we can actually start advertising given the combination of
273   // input parameters (e.g. check that the requested advertising data and scan
274   // response will actually fit within the size limitations of the advertising
275   // PDUs)
276   fit::result<HostError> CanStartAdvertising(
277       const DeviceAddress& address,
278       const AdvertisingData& data,
279       const AdvertisingData& scan_rsp,
280       const AdvertisingOptions& options,
281       const ConnectionCallback& connect_callback) const;
282 
283   // Unconditionally start advertising (all checks must be performed in the
284   // methods that call this one).
285   void StartAdvertisingInternal(const DeviceAddress& address,
286                                 const AdvertisingData& data,
287                                 const AdvertisingData& scan_rsp,
288                                 const AdvertisingOptions& options,
289                                 ConnectionCallback connect_callback,
290                                 hci::ResultFunction<> callback);
291 
292   // Unconditionally stop advertising (all checks muts be performed in the
293   // methods that call this one).
294   void StopAdvertisingInternal(const DeviceAddress& address, bool extended_pdu);
295 
296   // Handle shared housekeeping tasks when an incoming connection is completed
297   // (e.g. clean up internal state, call callbacks, etc)
298   void CompleteIncomingConnection(
299       hci_spec::ConnectionHandle handle,
300       pw::bluetooth::emboss::ConnectionRole role,
301       const DeviceAddress& local_address,
302       const DeviceAddress& peer_address,
303       const hci_spec::LEConnectionParameters& conn_params,
304       bool extended_pdu);
305 
hci_cmd_runner()306   SequentialCommandRunner& hci_cmd_runner() const { return *hci_cmd_runner_; }
hci()307   hci::Transport::WeakPtr hci() const { return hci_; }
308 
309  private:
310   struct StagedParameters {
311     AdvertisingData data;
312     AdvertisingData scan_rsp;
313 
resetStagedParameters314     void reset() {
315       AdvertisingData blank;
316       blank.Copy(&data);
317       blank.Copy(&scan_rsp);
318     }
319   };
320 
321   // Continuation function for starting advertising, called automatically via
322   // callbacks in StartAdvertisingInternal. Developers should not call this
323   // function directly.
324   bool StartAdvertisingInternalStep2(const DeviceAddress& address,
325                                      const AdvertisingOptions& options,
326                                      ConnectionCallback connect_callback,
327                                      hci::ResultFunction<> result_callback);
328 
329   // Enqueue onto the HCI command runner the HCI commands necessary to stop
330   // advertising and completely remove a given address from the controller's
331   // memory. If even one of the HCI commands cannot be generated for some
332   // reason, no HCI commands are enqueued.
333   bool EnqueueStopAdvertisingCommands(const DeviceAddress& address,
334                                       bool extended_pdu);
335 
336   hci::Transport::WeakPtr hci_;
337   std::unique_ptr<SequentialCommandRunner> hci_cmd_runner_;
338   StagedParameters staged_parameters_;
339 
340   struct TupleKeyHasher {
operatorTupleKeyHasher341     size_t operator()(const std::tuple<DeviceAddress, bool>& t) const {
342       std::hash<DeviceAddress> device_address_hasher;
343       std::hash<bool> bool_hasher;
344       const auto& [address, extended_pdu] = t;
345       return device_address_hasher(address) ^ bool_hasher(extended_pdu);
346     }
347   };
348   std::unordered_map<std::tuple<DeviceAddress, bool>,
349                      ConnectionCallback,
350                      TupleKeyHasher>
351       connection_callbacks_;
352 
353   uint16_t max_advertising_data_length_ = hci_spec::kMaxLEAdvertisingDataLength;
354 
355   BT_DISALLOW_COPY_AND_ASSIGN_ALLOW_MOVE(LowEnergyAdvertiser);
356 };
357 
358 }  // namespace hci
359 }  // namespace bt
360