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