blob: 0658ec41ac7ec0ad721af5abb8c34671ea87e65c [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 GARNET_DRIVERS_BLUETOOTH_LIB_GAP_REMOTE_DEVICE_CACHE_H_
#define GARNET_DRIVERS_BLUETOOTH_LIB_GAP_REMOTE_DEVICE_CACHE_H_
#include <unordered_map>
#include <lib/async/cpp/task.h>
#include "garnet/drivers/bluetooth/lib/common/device_address.h"
#include "garnet/drivers/bluetooth/lib/gap/identity_resolving_list.h"
#include "garnet/drivers/bluetooth/lib/gap/remote_device.h"
#include "garnet/drivers/bluetooth/lib/hci/connection.h"
#include "garnet/drivers/bluetooth/lib/sm/types.h"
#include "lib/fxl/macros.h"
#include "lib/fxl/synchronization/thread_checker.h"
namespace btlib {
namespace common {
class ByteBuffer;
} // namespace common
namespace hci {
struct LowEnergyScanResult;
} // namespace hci
namespace gap {
// A RemoteDeviceCache provides access to remote Bluetooth devices that are
// known to the system.
class RemoteDeviceCache final {
public:
using DeviceCallback = fit::function<void(const RemoteDevice& device)>;
using DeviceIdCallback = fit::function<void(const std::string& identifier)>;
RemoteDeviceCache() = default;
// Creates a new device entry using the given parameters, and returns a
// (non-owning) pointer to that device. The caller must not retain the pointer
// beyond the current dispatcher task, as the underlying RemoteDevice is owned
// by |this| RemoteDeviceCache, and may be invalidated spontaneously.
//
// Returns nullptr if an entry matching |address| already exists in the cache,
// including as a public identity of a device with a different technology.
RemoteDevice* NewDevice(const common::DeviceAddress& address,
bool connectable);
// Iterates over all current devices in the map, running |f| on each entry
// synchronously. This is intended for IPC methods that request a list of
// devices.
//
// Clients should use the FindDeviceBy*() methods below to interact with
// RemoteDevice objects.
void ForEach(DeviceCallback f);
// Creates a new non-temporary device entry using the given |identifier| and
// identity |address|. This is intended to initialize this RemoteDeviceCache
// with previously bonded devices while bootstrapping a bt-host device.
//
// This method is not intended for updating the bonding data of a device that
// already exists the cache and returns false if a mapping for |identifier| or
// |address| is already present. Use Store*Bond() methods to update pairing
// information of an existing device.
//
// If a device already exists that has the same public identity address with a
// different technology, this method will return false. The existing device
// should be instead updated with new bond information to create a dual-mode
// device.
//
// TODO(armansito): Pass in BR/EDR link key here as well, if present.
bool AddBondedDevice(const std::string& identifier,
const common::DeviceAddress& address,
const sm::PairingData& bond_data);
// Update the device with the given identifier with new LE bonding
// information. The device will be considered "bonded" and the bonded callback
// will be notified. If the device is already bonded then bonding data will be
// updated.
//
// If |bond_data| contains an |identity_address|, the device cache will be
// updated with a new mapping from that address to this device identifier. If
// the identity address already maps to an existing device, this method will
// return false. TODO(armansito): Merge the devices instead of failing? What
// happens if we obtain a LE identity address from a dual-mode device that
// matches the BD_ADDR previously obtained from it over BR/EDR?
bool StoreLowEnergyBond(const std::string& identifier,
const sm::PairingData& bond_data);
// Update a device identified by BD_ADDR |address| with a new BR/EDR link key.
// The device will be considered "bonded" and the bonded callback notified. If
// the device is already bonded then the link key will be updated. Returns
// false if the address does not match that of a known device.
bool StoreBrEdrBond(const common::DeviceAddress& address,
const sm::LTK& link_key);
// Returns the remote device with identifier |identifier|. Returns nullptr if
// |identifier| is not recognized.
RemoteDevice* FindDeviceById(const std::string& identifier) const;
// Finds and returns a RemoteDevice with address |address| if it exists,
// returns nullptr otherwise. Tries to resolve |address| if it is resolvable.
// If |address| is of type kBREDR or kLEPublic, then this searches for devices
// that have either type of address.
RemoteDevice* FindDeviceByAddress(const common::DeviceAddress& address) const;
// When set, |callback| will be invoked whenever a device is added
// or updated.
void set_device_updated_callback(DeviceCallback callback) {
ZX_DEBUG_ASSERT(thread_checker_.IsCreationThreadCurrent());
device_updated_callback_ = std::move(callback);
}
// When set, |callback| will be invoked whenever a device is
// removed.
void set_device_removed_callback(DeviceIdCallback callback) {
ZX_DEBUG_ASSERT(thread_checker_.IsCreationThreadCurrent());
device_removed_callback_ = std::move(callback);
}
// When this callback is set, |callback| will be invoked whenever the bonding
// data of a device is updated and should be persisted. The caller must ensure
// that |callback| outlives |this|.
void set_device_bonded_callback(DeviceCallback callback) {
ZX_DEBUG_ASSERT(thread_checker_.IsCreationThreadCurrent());
device_bonded_callback_ = std::move(callback);
}
// Returns the number of devices that are currently in the device cache.
size_t count() const { return devices_.size(); }
private:
// Maps unique device IDs to the corresponding RemoteDevice entry.
using RemoteDeviceMap =
std::unordered_map<std::string, std::unique_ptr<RemoteDevice>>;
class RemoteDeviceRecord final {
public:
RemoteDeviceRecord(std::unique_ptr<RemoteDevice> device,
fbl::Closure remove_device_callback)
: device_(std::move(device)),
removal_task_(std::move(remove_device_callback)) {}
// The copy and move ctors cannot be implicitly defined, since
// async::TaskClosure does not support those operations. Nor is any
// meaningful explicit definition possible.
RemoteDeviceRecord(const RemoteDeviceRecord&) = delete;
RemoteDeviceRecord(RemoteDeviceRecord&&) = delete;
RemoteDevice* device() const { return device_.get(); }
// Returns a pointer to removal_task_, which can be used to (re-)schedule or
// cancel |remove_device_callback|.
async::TaskClosure* removal_task() { return &removal_task_; }
private:
std::unique_ptr<RemoteDevice> device_;
async::TaskClosure removal_task_;
};
// Create and track a record of a remote device with a given |identifier|,
// |address|, and connectability (|connectable|). Returns a pointer to the
// inserted device or nullptr if |identifier| or |address| already exists in
// the cache.
RemoteDevice* InsertDeviceRecord(const std::string& identifier,
const common::DeviceAddress& address,
bool connectable);
// Notifies interested parties that |device| has bonded
// |device| must already exist in the cache.
void NotifyDeviceBonded(const RemoteDevice& device);
// Notifies interested parties that |device| has seen a significant change.
// |device| must already exist in the cache.
void NotifyDeviceUpdated(const RemoteDevice& device);
// Updates the expiration time for |device|, if a temporary. Cancels expiry,
// if a non-temporary. Pre-conditions:
// - |device| must already exist in the cache
// - can only be called from the thread that created |device|
void UpdateExpiry(const RemoteDevice& device);
// Updates the cache when an existing device is found to be dual-mode. Also
// notifies listeners of the "device updated" callback.
// |device| must already exist in the cache.
void MakeDualMode(const RemoteDevice& device);
// Removes |device| from this cache, and notifies listeners of the
// removal.
void RemoveDevice(RemoteDevice* device);
// Search for an unique device ID by its device address |address|, by both
// technologies if it is a public address. |address| should be already
// resolved, if it is resolvable. Returns a reference to a value of
// |address_map_| if found, empty otherwise.
std::optional<std::reference_wrapper<const std::string>> FindIdByAddress(
const common::DeviceAddress& address) const;
// Mapping from unique device IDs to RemoteDeviceRecords.
// Owns the corresponding RemoteDevices.
std::unordered_map<std::string, RemoteDeviceRecord> devices_;
// Mapping from device addresses to unique device identifiers for all known
// devices. This is used to look-up and update existing cached data for a
// particular scan result so as to avoid creating duplicate entries for the
// same device.
//
// Dual-mode devices shall have identity addresses of both technologies
// mapped to the same ID, if the addresses have the same value.
//
// TODO(armansito): Replace this with an implementation that can resolve
// device identity, to handle bonded LE devices that use privacy.
std::unordered_map<common::DeviceAddress, std::string> address_map_;
// The LE identity resolving list used to resolve RPAs.
IdentityResolvingList le_resolving_list_;
DeviceCallback device_updated_callback_;
DeviceIdCallback device_removed_callback_;
DeviceCallback device_bonded_callback_;
fxl::ThreadChecker thread_checker_;
FXL_DISALLOW_COPY_AND_ASSIGN(RemoteDeviceCache);
};
} // namespace gap
} // namespace btlib
#endif // GARNET_DRIVERS_BLUETOOTH_LIB_GAP_REMOTE_DEVICE_CACHE_H_