blob: 02f7c178a760cf47f7e92d70128bbb133c34f55d [file] [log] [blame]
// Copyright 2017 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef SRC_CONNECTIVITY_BLUETOOTH_CORE_BT_HOST_HCI_LOW_ENERGY_SCANNER_H_
#define SRC_CONNECTIVITY_BLUETOOTH_CORE_BT_HOST_HCI_LOW_ENERGY_SCANNER_H_
#include <lib/async/dispatcher.h>
#include <set>
#include <fbl/macros.h>
#include "src/connectivity/bluetooth/core/bt-host/common/byte_buffer.h"
#include "src/connectivity/bluetooth/core/bt-host/common/device_address.h"
#include "src/connectivity/bluetooth/core/bt-host/hci-spec/constants.h"
#include "src/connectivity/bluetooth/core/bt-host/hci-spec/defaults.h"
#include "src/connectivity/bluetooth/core/bt-host/hci/local_address_delegate.h"
#include "src/connectivity/bluetooth/core/bt-host/hci/sequential_command_runner.h"
#include "src/lib/fxl/memory/ref_ptr.h"
namespace bt::hci {
class Transport;
// Represents a discovered Bluetooth Low Energy peer.
struct LowEnergyScanResult {
// The device address of the remote peer.
DeviceAddress address;
// True if |address| is a static or random identity address resolved by the
// controller.
bool resolved = false;
// True if this peer accepts connections. This is the case if this peer
// sent a connectable advertising PDU.
bool connectable = false;
// The received signal strength of the advertisement packet corresponding to
// this peer.
int8_t rssi = kRSSIInvalid;
};
// LowEnergyScanner manages Low Energy scan procedures that are used
// during general and limited discovery and connection establishment
// procedures. This is an abstract class that provides a common interface
// over 5.0 Extended Advertising and Legacy Advertising features.
//
// Instances of this class are expected to each as a singleton on a
// per-transport basis as multiple instances cannot accurately reflect the state
// of the controller while allowing simultaneous scan operations.
class LowEnergyScanner : public LocalAddressClient {
public:
// Value that can be passed to StartScan() to scan indefinitely.
static constexpr zx::duration kPeriodInfinite = zx::duration::infinite();
enum class State {
// No scan is currently being performed.
kIdle,
// A previously running scan is being stopped.
kStopping,
// A scan is being initiated.
kInitiating,
// An active scan is currently being performed.
kActiveScanning,
// A passive scan is currently being performed.
kPassiveScanning,
};
// Interface for receiving events related to Low Energy scan.
class Delegate {
public:
virtual ~Delegate() = default;
// Called when a peer is found. During a passive scan |data| contains the advertising data.
// During an active scan |data| contains the combined advertising and scan response data (if the
// peer is scannable).
virtual void OnPeerFound(const LowEnergyScanResult& result, const ByteBuffer& data);
// Called when a directed advertising report is received from the peer
// with the given address.
virtual void OnDirectedAdvertisement(const LowEnergyScanResult& result);
};
LowEnergyScanner(fxl::WeakPtr<Transport> hci, async_dispatcher_t* dispatcher);
virtual ~LowEnergyScanner() = default;
// Returns the current Scan state.
State state() const { return state_; }
bool IsActiveScanning() const { return state() == State::kActiveScanning; }
bool IsPassiveScanning() const { return state() == State::kPassiveScanning; }
bool IsScanning() const { return IsActiveScanning() || IsPassiveScanning(); }
bool IsInitiating() const { return state() == State::kInitiating; }
// LocalAddressClient override:
bool AllowsRandomAddressChange() const override {
return !IsScanning() && hci_cmd_runner()->IsReady();
}
// True if no scan procedure is currently enabled.
bool IsIdle() const { return state() == State::kIdle; }
// Initiates a scan. This is an asynchronous operation that abides by
// the following rules:
//
// - This method synchronously returns false if the procedure could not be
// started, e.g. because discovery is already in progress, or it is in the
// process of being stopped, or the controller does not support discovery,
// etc.
//
// - Synchronously returns true if the procedure was initiated but the it is
// unknown whether or not the procedure has succeeded.
//
// - |callback| is invoked asynchronously to report the status of the
// procedure. In the case of failure, |callback| will be invoked once to
// report the end of the procedure. In the case of success, |callback|
// will be invoked twice: the first time to report that the procedure has
// started, and a second time to report when the procedure ends, either
// due to a timeout or cancellation.
//
// - |period| specifies (in milliseconds) the duration of the scan. If the
// special value of kPeriodInfinite is passed then scanning will continue
// indefinitely and must be explicitly stopped by calling StopScan().
// Otherwise, the value must be non-zero.
//
// Once started, a scan can be terminated at any time by calling the
// StopScan() method. Otherwise, an ongoing scan will terminate at the end of
// the scan period if a finite value for |period| was provided.
//
// During an active scan, scannable advertisements are reported alongside their
// corresponding scan response. Every scannable advertisement is stored and not
// reported until either
// a) a scan response is received
// b) an implementation determined timeout period expires
// c) for periodic scans, when the scan period expires
//
// Since a passive scan involves no scan request/response, all advertisements are
// reported immediately without waiting for a scan response.
//
// (For more information about passive and active scanning, see Core Spec. v5.2,
// Vol 6, Part B, 4.4.3.1 and 4.4.3.2).
enum class ScanStatus {
// Reported when the scan could not be started.
kFailed,
// Reported when an active scan was started and is currently in progress.
kActive,
// Reported when a passive scan was started and is currently in progress.
kPassive,
// Called when the scan was terminated naturally at the end of the scan
// period.
kComplete,
// Called when the scan was terminated due to a call to StopScan().
kStopped,
};
struct ScanOptions {
// Perform an active scan if true. During an active scan, scannable advertisements
// are reported alongside their corresponding scan response.
bool active = false;
// When enabled, the controller will filter out duplicate advertising reports. This means that
// Delegate::OnPeerFound will be called only once per device address during the scan period.
//
// When disabled, Delegate::OnPeerFound will get called once for every observed advertisement
// (depending on |filter_policy|).
bool filter_duplicates = false;
// Determines the type of filtering the controller should perform to limit the number of
// advertising reports.
LEScanFilterPolicy filter_policy = LEScanFilterPolicy::kNoWhiteList;
// Determines the length of the software defined scan period. If the value is kPeriodInfinite,
// then the scan will remain enabled until StopScan() gets called. For all other values, the
// scan will be disabled after the duration expires.
zx::duration period;
// Maximum time duration during an active scan for which a scannable advertisement will be
// stored and not reported to clients until a corresponding scan response is received.
zx::duration scan_response_timeout;
// Scan parameters.
uint16_t interval = defaults::kLEScanInterval;
uint16_t window = defaults::kLEScanWindow;
};
using ScanStatusCallback = fit::function<void(ScanStatus)>;
virtual bool StartScan(const ScanOptions& options, ScanStatusCallback callback) = 0;
// Stops a previously started scan. Returns false if a scan is not in
// progress. Otherwise, cancels any in progress scan procedure and returns
// true.
virtual bool StopScan() = 0;
// Assigns the delegate for scan events.
void set_delegate(Delegate* delegate) { delegate_ = delegate; }
protected:
async_dispatcher_t* dispatcher() const { return dispatcher_; }
fxl::WeakPtr<Transport> transport() const { return transport_; }
SequentialCommandRunner* hci_cmd_runner() const { return hci_cmd_runner_.get(); }
Delegate* delegate() const {
ZX_ASSERT(delegate_);
return delegate_;
}
void set_state(State state) { state_ = state; }
// Returns true if an active scan was most recently requested. This applies to
// the on-going scan only if IsScanning() returns true.
bool active_scan_requested() const { return active_scan_requested_; }
void set_active_scan_requested(bool value) { active_scan_requested_ = value; }
private:
State state_;
bool active_scan_requested_;
Delegate* delegate_; // weak
// Task runner for all asynchronous tasks.
async_dispatcher_t* dispatcher_;
// The HCI transport.
fxl::WeakPtr<Transport> transport_;
// Command runner for all HCI commands sent out by implementations.
std::unique_ptr<SequentialCommandRunner> hci_cmd_runner_;
DISALLOW_COPY_AND_ASSIGN_ALLOW_MOVE(LowEnergyScanner);
};
} // namespace bt::hci
#endif // SRC_CONNECTIVITY_BLUETOOTH_CORE_BT_HOST_HCI_LOW_ENERGY_SCANNER_H_