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   LowEnergyScanner(LocalAddressDelegate* local_addr_delegate,
221                    Transport::WeakPtr hci,
222                    pw::async::Dispatcher& pw_dispatcher);
223   ~LowEnergyScanner() override = default;
224 
225   // Returns the current Scan state.
state()226   State state() const { return state_; }
227 
IsActiveScanning()228   bool IsActiveScanning() const { return state() == State::kActiveScanning; }
IsPassiveScanning()229   bool IsPassiveScanning() const { return state() == State::kPassiveScanning; }
IsScanning()230   bool IsScanning() const { return IsActiveScanning() || IsPassiveScanning(); }
IsInitiating()231   bool IsInitiating() const { return state() == State::kInitiating; }
232 
233   // LocalAddressClient override:
AllowsRandomAddressChange()234   bool AllowsRandomAddressChange() const override {
235     return !IsScanning() && hci_cmd_runner_->IsReady();
236   }
237 
238   // True if no scan procedure is currently enabled.
IsIdle()239   bool IsIdle() const { return state() == State::kIdle; }
240 
241   // Initiates a scan. This is an asynchronous operation that abides by the
242   // following rules:
243   //
244   //   - This method synchronously returns false if the procedure could not be
245   //   started, e.g. because discovery is already in progress, or it is in the
246   //   process of being stopped, or the controller does not support discovery,
247   //   etc.
248   //
249   //   - Synchronously returns true if the procedure was initiated but the it is
250   //   unknown whether or not the procedure has succeeded.
251   //
252   //   - |callback| is invoked asynchronously to report the status of the
253   //   procedure. In the case of failure, |callback| will be invoked once to
254   //   report the end of the procedure. In the case of success, |callback| will
255   //   be invoked twice: the first time to report that the procedure has
256   //   started, and a second time to report when the procedure ends, either due
257   //   to a timeout or cancellation.
258   //
259   //   - |period| specifies (in milliseconds) the duration of the scan. If the
260   //   special value of kPeriodInfinite is passed then scanning will continue
261   //   indefinitely and must be explicitly stopped by calling StopScan().
262   //   Otherwise, the value must be non-zero.
263   //
264   // Once started, a scan can be terminated at any time by calling the
265   // StopScan() method. Otherwise, an ongoing scan will terminate at the end of
266   // the scan period if a finite value for |period| was provided.
267   //
268   // During an active scan, scannable advertisements are reported alongside
269   // their corresponding scan response. Every scannable advertisement is stored
270   // and not reported until either
271   //
272   //   a) a scan response is received
273   //   b) an implementation determined timeout period expires
274   //   c) for periodic scans, when the scan period expires
275   //
276   // Since a passive scan involves no scan request/response, all advertisements
277   // are reported immediately without waiting for a scan response.
278   //
279   // (For more information about passive and active scanning, see Core Spec.
280   // v5.2, Vol 6, Part B, 4.4.3.1 and 4.4.3.2).
281   using ScanStatusCallback = fit::function<void(ScanStatus)>;
282   virtual bool StartScan(const ScanOptions& options,
283                          ScanStatusCallback callback);
284 
285   // Stops a previously started scan. Returns false if a scan is not in
286   // progress. Otherwise, cancels any in progress scan procedure and returns
287   // true.
288   virtual bool StopScan();
289 
290   // Assigns the delegate for scan events.
set_delegate(Delegate * delegate)291   void set_delegate(Delegate* delegate) { delegate_ = delegate; }
292 
293  protected:
294   // Build the HCI command packet to set the scan parameters for the flavor of
295   // low energy scanning being implemented.
296   virtual CommandPacket BuildSetScanParametersPacket(
297       const DeviceAddress& local_address, const ScanOptions& options) = 0;
298 
299   // Build the HCI command packet to enable scanning for the flavor of low
300   // energy scanning being implemented.
301   virtual CommandPacket BuildEnablePacket(
302       const ScanOptions& options,
303       pw::bluetooth::emboss::GenericEnableParam enable) = 0;
304 
305   void AddPendingResult(LowEnergyScanResult&& scan_result);
306 
HasPendingResult(const DeviceAddress & address)307   bool HasPendingResult(const DeviceAddress& address) {
308     return pending_results_.count(address) != 0;
309   }
310 
311   std::unique_ptr<PendingScanResult> RemovePendingResult(
312       const DeviceAddress& address);
313 
hci()314   Transport::WeakPtr hci() const { return hci_; }
delegate()315   Delegate* delegate() const { return delegate_; }
316 
317  private:
318   // Called by StartScan() after the local peer address has been obtained.
319   void StartScanInternal(const DeviceAddress& local_address,
320                          const ScanOptions& options,
321                          ScanStatusCallback callback);
322 
323   // Called by StopScan() and by the scan timeout handler set up by StartScan().
324   void StopScanInternal(bool stopped);
325 
326   State state_ = State::kIdle;
327   pw::async::Dispatcher& pw_dispatcher_;
328   Delegate* delegate_ = nullptr;  // weak
329 
330   // Callback passed in to the most recently accepted call to StartScan();
331   ScanStatusCallback scan_cb_;
332 
333   // The scan period timeout handler for the currently active scan session.
334   SmartTask scan_timeout_task_;
335 
336   // Maximum time duration for which a scannable advertisement will be stored
337   // and not reported to clients until a corresponding scan response is
338   // received.
339   pw::chrono::SystemClock::duration scan_response_timeout_;
340 
341   // Scannable advertising events for which a Scan Response PDU has not been
342   // received. This is accumulated during a discovery procedure and always
343   // cleared at the end of the scan period.
344   std::unordered_map<DeviceAddress, std::unique_ptr<PendingScanResult>>
345       pending_results_;
346 
347   // Used to obtain the local peer address type to use during scanning.
348   LocalAddressDelegate* local_addr_delegate_;  // weak
349 
350   // The HCI transport.
351   Transport::WeakPtr hci_;
352 
353   // Command runner for all HCI commands sent out by implementations.
354   std::unique_ptr<SequentialCommandRunner> hci_cmd_runner_;
355 
356   BT_DISALLOW_COPY_AND_ASSIGN_ALLOW_MOVE(LowEnergyScanner);
357 };
358 
359 }  // namespace bt::hci
360