• 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 
17 #include "pw_bluetooth_sapphire/internal/host/hci-spec/constants.h"
18 #include "pw_bluetooth_sapphire/internal/host/hci-spec/defaults.h"
19 #include "pw_bluetooth_sapphire/internal/host/hci/local_address_delegate.h"
20 #include "pw_bluetooth_sapphire/internal/host/hci/sequential_command_runner.h"
21 #include "pw_bluetooth_sapphire/internal/host/transport/transport.h"
22 
23 namespace bt::hci {
24 
25 class SequentialCommandRunner;
26 
27 // Represents a discovered Bluetooth Low Energy peer.
28 class LowEnergyScanResult {
29  public:
30   LowEnergyScanResult() = default;
LowEnergyScanResult(const DeviceAddress & address,bool resolved,bool connectable)31   LowEnergyScanResult(const DeviceAddress& address,
32                       bool resolved,
33                       bool connectable)
34       : address_(address), resolved_(resolved), connectable_(connectable) {}
35 
36   LowEnergyScanResult& operator=(const LowEnergyScanResult& other);
LowEnergyScanResult(const LowEnergyScanResult & other)37   LowEnergyScanResult(const LowEnergyScanResult& other) { *this = other; }
38 
address()39   const DeviceAddress& address() const { return address_; }
connectable()40   bool connectable() const { return connectable_; }
41 
resolved()42   bool resolved() const { return resolved_; }
set_resolved(bool value)43   void set_resolved(bool value) { resolved_ = value; }
44 
rssi()45   int8_t rssi() const { return rssi_; }
set_rssi(int8_t value)46   void set_rssi(int8_t value) { rssi_ = value; }
47 
tx_power()48   int8_t tx_power() const { return tx_power_; }
set_tx_power(int8_t value)49   void set_tx_power(int8_t value) { tx_power_ = value; }
50 
advertising_sid()51   uint8_t advertising_sid() const { return advertising_sid_; }
set_advertising_sid(uint8_t value)52   void set_advertising_sid(uint8_t value) { advertising_sid_ = value; }
53 
data()54   BufferView data() const { return buffer_.view(0, data_size_); }
55   void AppendData(const ByteBuffer& data);
56 
57  private:
58   // The device address of the remote peer.
59   DeviceAddress address_;
60 
61   // True if |address| is a static or random identity address resolved by the
62   // controller.
63   bool resolved_ = false;
64 
65   // True if this peer accepts connections. This is the case if this peer sent a
66   // connectable advertising PDU.
67   bool connectable_ = false;
68 
69   // The received signal strength of the advertisement packet corresponding to
70   // this peer.
71   int8_t rssi_ = hci_spec::kRSSIInvalid;
72 
73   // The transmitted signal strength of this packet, according to the advertiser
74   int8_t tx_power_ = hci_spec::kTxPowerInvalid;
75 
76   // Matches the advertising SID subfield in the ADI field of the received
77   // advertisement, used to synchronize against a periodic advertising train
78   uint8_t advertising_sid_ = hci_spec::kAdvertisingSidInvalid;
79 
80   // The size of the data so far accumulated in |buffer_|.
81   size_t data_size_ = 0u;
82 
83   // Stores both advertising and scan response payloads.
84   DynamicByteBuffer buffer_;
85 };
86 
87 // LowEnergyScanner manages Low Energy scan procedures that are used during
88 // general and limited discovery and connection establishment procedures. This
89 // is an abstract class that provides a common interface over 5.0 Extended
90 // Advertising and Legacy Advertising features.
91 //
92 // Instances of this class are expected to each as a singleton on a
93 // per-transport basis as multiple instances cannot accurately reflect the state
94 // of the controller while allowing simultaneous scan operations.
95 class LowEnergyScanner : public LocalAddressClient {
96  public:
97   // Value that can be passed to StartScan() to scan indefinitely.
98   static constexpr pw::chrono::SystemClock::duration kPeriodInfinite =
99       pw::chrono::SystemClock::duration::zero();
100 
101   enum class State {
102     // No scan is currently being performed.
103     kIdle,
104 
105     // A previously running scan is being stopped.
106     kStopping,
107 
108     // A scan is being initiated.
109     kInitiating,
110 
111     // An active scan is currently being performed.
112     kActiveScanning,
113 
114     // A passive scan is currently being performed.
115     kPassiveScanning,
116   };
117 
118   enum class ScanStatus {
119     // Reported when the scan could not be started.
120     kFailed,
121 
122     // Reported when an active scan was started and is currently in progress.
123     kActive,
124 
125     // Reported when a passive scan was started and is currently in progress.
126     kPassive,
127 
128     // Called when the scan was terminated naturally at the end of the scan
129     // period.
130     kComplete,
131 
132     // Called when the scan was terminated due to a call to StopScan().
133     kStopped,
134   };
135 
136   struct ScanOptions {
137     // Perform an active scan if true. During an active scan, scannable
138     // advertisements are reported alongside their corresponding scan response.
139     bool active = false;
140 
141     // When enabled, the controller will filter out duplicate advertising
142     // reports. This means that Delegate::OnPeerFound will be called only once
143     // per device address during the scan period.
144     //
145     // When disabled, Delegate::OnPeerFound will get called once for every
146     // observed advertisement (depending on |filter_policy|).
147     bool filter_duplicates = false;
148 
149     // Determines the type of filtering the controller should perform to limit
150     // the number of advertising reports.
151     pw::bluetooth::emboss::LEScanFilterPolicy filter_policy =
152         pw::bluetooth::emboss::LEScanFilterPolicy::BASIC_UNFILTERED;
153 
154     // Determines the length of the software defined scan period. If the value
155     // is kPeriodInfinite, then the scan will remain enabled until StopScan()
156     // gets called. For all other values, the scan will be disabled after the
157     // duration expires.
158     pw::chrono::SystemClock::duration period = kPeriodInfinite;
159 
160     // Maximum time duration during an active scan for which a scannable
161     // advertisement will be stored and not reported to clients until a
162     // corresponding scan response is received.
163     pw::chrono::SystemClock::duration scan_response_timeout =
164         std::chrono::seconds(2);
165 
166     // Scan parameters.
167     uint16_t interval = hci_spec::defaults::kLEScanInterval;
168     uint16_t window = hci_spec::defaults::kLEScanWindow;
169   };
170 
171   // This represents the data obtained for a scannable advertisement for which a
172   // scan response has not yet been received. Clients are notified for scannable
173   // advertisement either when the corresponding scan response is received or,
174   // otherwise, a timeout expires.
175   class PendingScanResult {
176    public:
177     PendingScanResult(LowEnergyScanResult&& result,
178                       pw::async::Dispatcher& dispatcher,
179                       pw::chrono::SystemClock::duration timeout,
180                       fit::closure timeout_handler);
~PendingScanResult()181     ~PendingScanResult() { CancelTimeout(); }
182 
result()183     LowEnergyScanResult& result() { return result_; }
184 
StartTimer()185     void StartTimer() {
186       CancelTimeout();
187       timeout_task_.PostAfter(timeout_);
188     }
189 
CancelTimeout()190     bool CancelTimeout() { return timeout_task_.Cancel(); }
191 
192    private:
193     LowEnergyScanResult result_;
194 
195     // The duration which we will wait for a pending scan result to receive more
196     // data before reporting the pending result to the delegate.
197     pw::chrono::SystemClock::duration timeout_;
198 
199     // Since not all scannable advertisements are always followed by a scan
200     // response, we report a pending result if a scan response is not received
201     // within a timeout.
202     SmartTask timeout_task_;
203   };
204 
205   // Interface for receiving events related to Low Energy scan.
206   class Delegate {
207    public:
208     virtual ~Delegate() = default;
209 
210     // Called when a peer is found. During a passive scan |data| contains the
211     // advertising data. During an active scan |data| contains the combined
212     // advertising and scan response data (if the peer is scannable).
OnPeerFound(const LowEnergyScanResult &)213     virtual void OnPeerFound(const LowEnergyScanResult&) {}
214 
215     // Called when a directed advertising report is received from the peer with
216     // the given address.
OnDirectedAdvertisement(const LowEnergyScanResult &)217     virtual void OnDirectedAdvertisement(const LowEnergyScanResult&) {}
218   };
219 
220   // Some Controllers support offloaded packet filtering (e.g. in Android vendor
221   // extensions). PacketFilterConfig allows us to switch on whether this feature
222   // is enabled for the current Controller or not.
223   class PacketFilterConfig {
224    public:
PacketFilterConfig(bool offloading_enabled,uint8_t max_filters)225     PacketFilterConfig(bool offloading_enabled, uint8_t max_filters)
226         : offloading_enabled_(offloading_enabled), max_filters_(max_filters) {}
227 
offloading_enabled()228     bool offloading_enabled() const { return offloading_enabled_; }
max_filters()229     uint8_t max_filters() const { return max_filters_; }
230 
231    private:
232     bool offloading_enabled_ = false;
233     uint8_t max_filters_ = 0;
234   };
235 
236   LowEnergyScanner(LocalAddressDelegate* local_addr_delegate,
237                    const PacketFilterConfig& packet_filter_config,
238                    Transport::WeakPtr hci,
239                    pw::async::Dispatcher& pw_dispatcher);
240   ~LowEnergyScanner() override = default;
241 
242   // Returns the current Scan state.
state()243   State state() const { return state_; }
244 
IsActiveScanning()245   bool IsActiveScanning() const { return state() == State::kActiveScanning; }
IsPassiveScanning()246   bool IsPassiveScanning() const { return state() == State::kPassiveScanning; }
IsScanning()247   bool IsScanning() const { return IsActiveScanning() || IsPassiveScanning(); }
IsInitiating()248   bool IsInitiating() const { return state() == State::kInitiating; }
249 
250   // LocalAddressClient override:
AllowsRandomAddressChange()251   bool AllowsRandomAddressChange() const override {
252     return !IsScanning() && hci_cmd_runner_->IsReady();
253   }
254 
255   // True if no scan procedure is currently enabled.
IsIdle()256   bool IsIdle() const { return state() == State::kIdle; }
257 
258   // Initiates a scan. This is an asynchronous operation that abides by the
259   // following rules:
260   //
261   //   - This method synchronously returns false if the procedure could not be
262   //   started, e.g. because discovery is already in progress, or it is in the
263   //   process of being stopped, or the controller does not support discovery,
264   //   etc.
265   //
266   //   - Synchronously returns true if the procedure was initiated but the it is
267   //   unknown whether or not the procedure has succeeded.
268   //
269   //   - |callback| is invoked asynchronously to report the status of the
270   //   procedure. In the case of failure, |callback| will be invoked once to
271   //   report the end of the procedure. In the case of success, |callback| will
272   //   be invoked twice: the first time to report that the procedure has
273   //   started, and a second time to report when the procedure ends, either due
274   //   to a timeout or cancellation.
275   //
276   //   - |period| specifies (in milliseconds) the duration of the scan. If the
277   //   special value of kPeriodInfinite is passed then scanning will continue
278   //   indefinitely and must be explicitly stopped by calling StopScan().
279   //   Otherwise, the value must be non-zero.
280   //
281   // Once started, a scan can be terminated at any time by calling the
282   // StopScan() method. Otherwise, an ongoing scan will terminate at the end of
283   // the scan period if a finite value for |period| was provided.
284   //
285   // During an active scan, scannable advertisements are reported alongside
286   // their corresponding scan response. Every scannable advertisement is stored
287   // and not reported until either
288   //
289   //   a) a scan response is received
290   //   b) an implementation determined timeout period expires
291   //   c) for periodic scans, when the scan period expires
292   //
293   // Since a passive scan involves no scan request/response, all advertisements
294   // are reported immediately without waiting for a scan response.
295   //
296   // (For more information about passive and active scanning, see Core Spec.
297   // v5.2, Vol 6, Part B, 4.4.3.1 and 4.4.3.2).
298   using ScanStatusCallback = fit::function<void(ScanStatus)>;
299   virtual bool StartScan(const ScanOptions& options,
300                          ScanStatusCallback callback);
301 
302   // Stops a previously started scan. Returns false if a scan is not in
303   // progress. Otherwise, cancels any in progress scan procedure and returns
304   // true.
305   virtual bool StopScan();
306 
307   // Assigns the delegate for scan events.
set_delegate(Delegate * delegate)308   void set_delegate(Delegate* delegate) { delegate_ = delegate; }
309 
310  protected:
311   // Build the HCI command packet to set the scan parameters for the flavor of
312   // low energy scanning being implemented.
313   virtual CommandPacket BuildSetScanParametersPacket(
314       const DeviceAddress& local_address, const ScanOptions& options) = 0;
315 
316   // Build the HCI command packet to enable scanning for the flavor of low
317   // energy scanning being implemented.
318   virtual CommandPacket BuildEnablePacket(
319       const ScanOptions& options,
320       pw::bluetooth::emboss::GenericEnableParam enable) = 0;
321 
322   void AddPendingResult(LowEnergyScanResult&& scan_result);
323 
HasPendingResult(const DeviceAddress & address)324   bool HasPendingResult(const DeviceAddress& address) {
325     return pending_results_.count(address) != 0;
326   }
327 
328   std::unique_ptr<PendingScanResult> RemovePendingResult(
329       const DeviceAddress& address);
330 
packet_filter_config()331   const PacketFilterConfig& packet_filter_config() const {
332     return packet_filter_config_;
333   }
334 
hci()335   Transport::WeakPtr hci() const { return hci_; }
delegate()336   Delegate* delegate() const { return delegate_; }
337 
338  private:
339   // Called by StartScan() after the local peer address has been obtained.
340   void StartScanInternal(const DeviceAddress& local_address,
341                          const ScanOptions& options,
342                          ScanStatusCallback callback);
343 
344   // Called by StopScan() and by the scan timeout handler set up by StartScan().
345   void StopScanInternal(bool stopped);
346 
347   State state_ = State::kIdle;
348   pw::async::Dispatcher& pw_dispatcher_;
349   Delegate* delegate_ = nullptr;  // weak
350 
351   // Callback passed in to the most recently accepted call to StartScan();
352   ScanStatusCallback scan_cb_;
353 
354   // The scan period timeout handler for the currently active scan session.
355   SmartTask scan_timeout_task_;
356 
357   // Maximum time duration for which a scannable advertisement will be stored
358   // and not reported to clients until a corresponding scan response is
359   // received.
360   pw::chrono::SystemClock::duration scan_response_timeout_;
361 
362   // Scannable advertising events for which a Scan Response PDU has not been
363   // received. This is accumulated during a discovery procedure and always
364   // cleared at the end of the scan period.
365   std::unordered_map<DeviceAddress, std::unique_ptr<PendingScanResult>>
366       pending_results_;
367 
368   PacketFilterConfig packet_filter_config_;
369 
370   // Used to obtain the local peer address type to use during scanning.
371   LocalAddressDelegate* local_addr_delegate_;  // weak
372 
373   // The HCI transport.
374   Transport::WeakPtr hci_;
375 
376   // Command runner for all HCI commands sent out by implementations.
377   std::unique_ptr<SequentialCommandRunner> hci_cmd_runner_;
378 
379   BT_DISALLOW_COPY_AND_ASSIGN_ALLOW_MOVE(LowEnergyScanner);
380 };
381 
382 }  // namespace bt::hci
383