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