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