blob: f85cd47e2248140c76a5a1f09798c818bcf0c6bb [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_GAP_PEER_H_
#define SRC_CONNECTIVITY_BLUETOOTH_CORE_BT_HOST_GAP_PEER_H_
#include <lib/sys/inspect/cpp/component.h>
#include <string>
#include <type_traits>
#include <unordered_map>
#include <unordered_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/common/inspectable.h"
#include "src/connectivity/bluetooth/core/bt-host/common/metrics.h"
#include "src/connectivity/bluetooth/core/bt-host/common/uuid.h"
#include "src/connectivity/bluetooth/core/bt-host/gap/gap.h"
#include "src/connectivity/bluetooth/core/bt-host/gap/peer_metrics.h"
#include "src/connectivity/bluetooth/core/bt-host/gatt/persisted_data.h"
#include "src/connectivity/bluetooth/core/bt-host/hci-spec/constants.h"
#include "src/connectivity/bluetooth/core/bt-host/hci-spec/lmp_feature_set.h"
#include "src/connectivity/bluetooth/core/bt-host/hci/connection.h"
#include "src/connectivity/bluetooth/core/bt-host/sm/security_manager.h"
#include "src/connectivity/bluetooth/core/bt-host/sm/types.h"
namespace bt::gap {
class PeerCache;
// Represents a remote Bluetooth device that is known to the current system due
// to discovery and/or connection and bonding procedures. These devices can be
// LE-only, Classic-only, or dual-mode.
//
// Instances should not be created directly and must be obtained via a
// PeerCache.
class Peer final {
public:
using PeerCallback = fit::function<void(const Peer&)>;
// Describes the change(s) that caused the peer to notify listeners.
enum class NotifyListenersChange {
kBondNotUpdated, // No persistent data has changed
kBondUpdated, // Persistent data has changed
};
using NotifyListenersCallback = fit::function<void(const Peer&, NotifyListenersChange)>;
// Caller must ensure that callbacks are non-empty.
// Note that the ctor is only intended for use by PeerCache.
// Expanding access would a) violate the constraint that all Peers
// are created through a PeerCache, and b) introduce lifetime issues
// (do the callbacks outlive |this|?).
Peer(NotifyListenersCallback notify_listeners_callback, PeerCallback update_expiry_callback,
PeerCallback dual_mode_callback, PeerId identifier, const DeviceAddress& address,
bool connectable, PeerMetrics* peer_metrics);
// Connection state as considered by the GAP layer. This may not correspond
// exactly with the presence or absence of a link at the link layer. For
// example, GAP may consider a peer disconnected whilst the link disconnection
// procedure is still continuing.
enum class ConnectionState {
// No link exists between the local adapter and peer or link is being torn
// down (disconnection command has been sent).
kNotConnected,
// Currently establishing a link, performing service discovery, or
// setting up encryption. In this state, a link may have been
// established but it is not ready to use yet.
kInitializing,
// Link setup, service discovery, and any encryption setup has completed
kConnected
};
static std::string ConnectionStateToString(Peer::ConnectionState);
// Description of auto-connect behaviors.
//
// By default, the stack will auto-connect to any bonded devices as soon as
// soon as they become available.
enum class AutoConnectBehavior {
// Always auto-connect device when possible.
kAlways,
// Ignore auto-connection possibilities, but reset to kAlways after the next
// manual connection.
kSkipUntilNextConnection,
};
static constexpr const char* kInspectPeerIdName = "peer_id";
static constexpr const char* kInspectTechnologyName = "technology";
static constexpr const char* kInspectAddressName = "address";
static constexpr const char* kInspectConnectableName = "connectable";
static constexpr const char* kInspectTemporaryName = "temporary";
static constexpr const char* kInspectFeaturesName = "features";
static constexpr const char* kInspectVersionName = "hci_version";
static constexpr const char* kInspectManufacturerName = "manufacturer";
// Attach peer as child node of |parent| with specified |name|.
void AttachInspect(inspect::Node& parent, std::string name = "peer");
// Contains Peer data that apply only to the LE transport.
class LowEnergyData final {
public:
static constexpr const char* kInspectNodeName = "le_data";
static constexpr const char* kInspectConnectionStateName = "connection_state";
static constexpr const char* kInspectBondDataName = "bonded";
static constexpr const char* kInspectFeaturesName = "features";
LowEnergyData(Peer* owner);
void AttachInspect(inspect::Node& parent, std::string name = kInspectNodeName);
// Current connection state.
ConnectionState connection_state() const { return *conn_state_; }
bool connected() const { return connection_state() == ConnectionState::kConnected; }
bool bonded() const { return bond_data_->has_value(); }
bool should_auto_connect() const {
return bonded() && auto_conn_behavior_ == AutoConnectBehavior::kAlways;
}
// Advertising (and optionally scan response) data obtained during
// discovery.
const ByteBuffer& advertising_data() const { return adv_data_buffer_; }
// Most recently used LE connection parameters. Has no value if the peer
// has never been connected.
const std::optional<hci::LEConnectionParameters>& connection_parameters() const {
return conn_params_;
}
// Preferred LE connection parameters as reported by the peer.
const std::optional<hci::LEPreferredConnectionParameters>& preferred_connection_parameters()
const {
return preferred_conn_params_;
}
// This peer's LE bond data, if bonded.
const std::optional<sm::PairingData>& bond_data() const { return *bond_data_; }
// Bit mask of LE features (Core Spec v5.2, Vol 6, Part B, Section 4.6).
std::optional<hci::LESupportedFeatures> features() const { return *features_; }
// Setters:
// Overwrites the stored advertising and scan response data with the contents of |data|
// and updates the known RSSI with the given value.
void SetAdvertisingData(int8_t rssi, const ByteBuffer& data);
// Updates the connection state and notifies listeners if necessary.
void SetConnectionState(ConnectionState state);
// Modify the current or preferred connection parameters.
// The device must be connectable.
void SetConnectionParameters(const hci::LEConnectionParameters& value);
void SetPreferredConnectionParameters(const hci::LEPreferredConnectionParameters& value);
// Stores LE bonding data and makes this "bonded."
// Marks as non-temporary if necessary.
void SetBondData(const sm::PairingData& bond_data);
// Removes any stored keys. Does not make the peer temporary, even if it
// is disconnected. Does not notify listeners.
void ClearBondData();
void SetFeatures(hci::LESupportedFeatures features) { features_.Set(features); }
// Get pieces of the GATT database that must be persisted for bonded peers.
const gatt::ServiceChangedCCCPersistedData& get_service_changed_gatt_data() const {
return service_changed_gatt_data_;
}
// Set pieces of the GATT database that must be persisted for bonded peers.
void set_service_changed_gatt_data(const gatt::ServiceChangedCCCPersistedData& gatt_data) {
service_changed_gatt_data_ = gatt_data;
}
void set_auto_connect_behavior(AutoConnectBehavior behavior) { auto_conn_behavior_ = behavior; }
// TODO(armansito): Store most recently seen random address and identity
// address separately, once PeerCache can index peers by multiple
// addresses.
private:
Peer* peer_; // weak
inspect::Node node_;
StringInspectable<ConnectionState> conn_state_;
std::optional<hci::LEConnectionParameters> conn_params_;
std::optional<hci::LEPreferredConnectionParameters> preferred_conn_params_;
// Buffer containing advertising and scan response data appended to each other.
// NOTE: Repeated fields in advertising and scan response data are not deduplicated, so
// duplicate entries are possible. It is OK to assume that fields repeated in scan response
// data supercede those in the original advertising data when processing fields in order.
DynamicByteBuffer adv_data_buffer_;
BoolInspectable<std::optional<sm::PairingData>> bond_data_;
AutoConnectBehavior auto_conn_behavior_ = AutoConnectBehavior::kAlways;
StringInspectable<std::optional<hci::LESupportedFeatures>> features_;
// TODO(armansito): Store GATT service UUIDs.
// Data persisted from GATT database for bonded peers.
gatt::ServiceChangedCCCPersistedData service_changed_gatt_data_;
};
// Contains Peer data that apply only to the BR/EDR transport.
class BrEdrData final {
public:
static constexpr const char* kInspectNodeName = "bredr_data";
static constexpr const char* kInspectConnectionStateName = "connection_state";
static constexpr const char* kInspectLinkKeyName = "bonded";
static constexpr const char* kInspectServicesName = "services";
BrEdrData(Peer* owner);
// Attach peer inspect node as a child node of |parent|.
void AttachInspect(inspect::Node& parent, std::string name = kInspectNodeName);
// Current connection state.
ConnectionState connection_state() const { return *conn_state_; }
bool connected() const { return connection_state() == ConnectionState::kConnected; }
bool bonded() const { return link_key_->has_value(); }
// Returns the peer's BD_ADDR.
const DeviceAddress& address() const { return address_; }
// Returns the device class reported by the peer, if it is known.
const std::optional<DeviceClass>& device_class() const { return device_class_; }
// Returns the page scan repetition mode of the peer, if known.
const std::optional<hci::PageScanRepetitionMode>& page_scan_repetition_mode() const {
return page_scan_rep_mode_;
}
// Returns the clock offset reported by the peer, if known and valid. The
// clock offset will have the highest-order bit set and the rest represent
// bits 16-2 of CLKNslave-CLK (see hci::kClockOffsetFlagBit in
// hci/hci_constants.h).
const std::optional<uint16_t>& clock_offset() const { return clock_offset_; }
const BufferView extended_inquiry_response() const { return eir_buffer_.view(0, eir_len_); }
const std::optional<sm::LTK>& link_key() const { return *link_key_; }
const std::unordered_set<UUID>& services() const { return *services_; }
// Setters:
// Updates the inquiry data and notifies listeners. These
// methods expect HCI inquiry result structures as they are obtained from
// the Bluetooth controller. Each field should be encoded in little-endian
// byte order.
void SetInquiryData(const hci::InquiryResult& value);
void SetInquiryData(const hci::InquiryResultRSSI& value);
void SetInquiryData(const hci::ExtendedInquiryResultEventParams& value);
// Updates the connection state and notifies listeners if necessary.
void SetConnectionState(ConnectionState state);
// Stores a link key resulting from Secure Simple Pairing and makes this
// peer "bonded." Marks the peer as non-temporary if necessary. All
// BR/EDR link keys are "long term" (reusable across sessions).
void SetBondData(const sm::LTK& link_key);
// Removes any stored link key. Does not make the device temporary, even if
// it is disconnected. Does not notify listeners.
void ClearBondData();
// Adds a service discovered on the peer, identified by |uuid|, then notifies listeners. No-op
// if already present.
void AddService(UUID uuid);
// TODO(armansito): Store BD_ADDR here, once PeerCache can index
// devices by multiple addresses.
private:
// All multi-byte fields must be in little-endian byte order as they were
// received from the controller.
void SetInquiryData(DeviceClass device_class, uint16_t clock_offset,
hci::PageScanRepetitionMode page_scan_rep_mode,
int8_t rssi = hci::kRSSIInvalid, const BufferView& eir_data = BufferView());
// Updates the EIR data field and returns true if any properties changed.
bool SetEirData(const ByteBuffer& data);
Peer* peer_; // weak
inspect::Node node_;
StringInspectable<ConnectionState> conn_state_;
DeviceAddress address_;
std::optional<DeviceClass> device_class_;
std::optional<hci::PageScanRepetitionMode> page_scan_rep_mode_;
std::optional<uint16_t> clock_offset_;
// TODO(jamuraa): Parse more of the Extended Inquiry Response fields
size_t eir_len_;
DynamicByteBuffer eir_buffer_;
BoolInspectable<std::optional<sm::LTK>> link_key_;
StringInspectable<std::unordered_set<UUID>> services_;
};
// Number that uniquely identifies this device with respect to the bt-host
// that generated it.
// TODO(armansito): Come up with a scheme that guarnatees the uniqueness of
// this ID across all bt-hosts. Today this is guaranteed since we don't allow
// clients to interact with multiple controllers simultaneously though this
// could possibly lead to collisions if the active adapter gets changed
// without clearing the previous adapter's cache.
PeerId identifier() const { return *identifier_; }
// The Bluetooth technologies that are supported by this device.
TechnologyType technology() const { return *technology_; }
// The known device address of this device. Depending on the technologies
// supported by this device this has the following meaning:
//
// * For BR/EDR devices this is the BD_ADDR.
//
// * For LE devices this is identity address IF identity_known() returns
// true. This is always the case if the address type is LE Public.
//
// For LE devices that use privacy, identity_known() will be set to false
// upon discovery. The address will be updated only once the identity
// address has been obtained during the pairing procedure.
//
// * For BR/EDR/LE devices this is the BD_ADDR and the LE identity address.
// If a BR/EDR/LE device uses an identity address that is different from
// its BD_ADDR, then there will be two separate Peer entries for
// it.
const DeviceAddress& address() const { return *address_; }
bool identity_known() const { return identity_known_; }
// The LMP version of this device obtained doing discovery.
const std::optional<hci::HCIVersion>& version() const { return *lmp_version_; }
// Returns true if this is a connectable device.
bool connectable() const { return *connectable_; }
// Returns true if this device is connected over BR/EDR or LE transports.
bool connected() const {
return (le() && le()->connected()) || (bredr() && bredr()->connected());
}
// Returns true if this device has been bonded over BR/EDR or LE transports.
bool bonded() const { return (le() && le()->bonded()) || (bredr() && bredr()->bonded()); }
// Returns the most recently observed RSSI for this peer. Returns
// hci::kRSSIInvalid if the value is unknown.
int8_t rssi() const { return rssi_; }
// Gets the user-friendly name of the device, if it's known. This can be
// assigned based on LE advertising data, BR/EDR inquiry data, or by directly
// calling the SetName() method.
const std::optional<std::string>& name() const { return name_; }
// Returns the set of features of this device.
const hci::LMPFeatureSet& features() const { return *lmp_features_; }
// A temporary device gets removed from the PeerCache after a period
// of inactivity (see the |update_expiry_callback| argument to the
// constructor). The following rules determine the temporary state of a
// device:
// a. A device is temporary by default.
// b. A device becomes non-temporary when it gets connected.
// c. A device becomes temporary again when disconnected only if its
// identity is not known (i.e. identity_known() returns false). This only
// applies to LE devices that use the privacy feature.
//
// Temporary devices are never bonded.
bool temporary() const { return *temporary_; }
// Returns the LE transport specific data of this device, if any. This will be
// present if information about this device is obtained using the LE discovery
// and connection procedures.
const std::optional<LowEnergyData>& le() const { return le_data_; }
// Returns the BR/EDR transport specific data of this device, if any. This
// will be present if information about this device is obtained using the
// BR/EDR discovery and connection procedures.
const std::optional<BrEdrData>& bredr() const { return bredr_data_; }
// Returns a mutable reference to each transport-specific data structure,
// initializing the structure if it is unitialized. Use these to mutate
// members of the transport-specific structs. The caller must make sure to
// invoke these only if the device is known to support said technology.
LowEnergyData& MutLe();
BrEdrData& MutBrEdr();
// Returns a string representation of this device.
std::string ToString() const;
// The following methods mutate Peer properties:
// Updates the name of this device. This will override the existing name (if
// present) and notify listeners of the change.
void SetName(const std::string& name);
// Sets the value of the LMP |features| for the given |page| number.
void SetFeaturePage(size_t page, uint64_t features) {
lmp_features_.Mutable()->SetPage(page, features);
}
// Sets the last available LMP feature |page| number for this device.
void set_last_page_number(uint8_t page) { lmp_features_.Mutable()->set_last_page_number(page); }
void set_version(hci::HCIVersion version, uint16_t manufacturer, uint16_t subversion) {
lmp_version_.Set(version);
lmp_manufacturer_.Set(manufacturer);
lmp_subversion_ = subversion;
}
// Marks this device's identity as known. Called by PeerCache when
// initializing a bonded device and by LowEnergyData when setting bond data
// with an identity address.
void set_identity_known(bool value) { identity_known_ = value; }
// Stores the BR/EDR cross-transport key generated through LE pairing. This method will not mark
// the peer as dual-mode if it is not already dual-mode. It will also not overwrite existing
// BR/EDR link keys of stronger security than `ct_key`.
void StoreBrEdrCrossTransportKey(sm::LTK ct_key);
// Update the connectable status of this peer. This is useful if the peer sends both
// non-connectable and connectable advertisements (e.g. when it is a beacon).
void set_connectable(bool connectable) { connectable_.Set(connectable); }
fxl::WeakPtr<Peer> GetWeakPtr() { return weak_ptr_factory_.GetWeakPtr(); }
private:
// Assigns a new value for the address of this device. Called by LowEnergyData
// when a new identity address is assigned.
void set_address(const DeviceAddress& address) { address_.Set(address); }
// Updates the RSSI and returns true if it changed.
bool SetRssiInternal(int8_t rssi);
// Updates the name and returns true if there was a change without notifying
// listeners.
// TODO(armansito): Add similarly styled internal setters so that we can batch
// more updates.
bool SetNameInternal(const std::string& name);
// Marks this device as non-temporary. This operation may fail due to one of
// the conditions described above the |temporary()| method.
//
// TODO(armansito): Replace this with something more sophisticated when we
// implement bonding procedures. This method is here to remind us that these
// conditions are subtle and not fully supported yet.
bool TryMakeNonTemporary();
// Tells the owning PeerCache to update the expiry state of this
// device.
void UpdateExpiry();
// Signal to the cache to notify listeners.
void NotifyListeners(NotifyListenersChange change);
// Mark this device as dual mode and signal the cache.
void MakeDualMode();
// Set static and lazy inspect properties.
void InitializeInspect();
inspect::Node node_;
// Callbacks used to notify state changes.
NotifyListenersCallback notify_listeners_callback_;
PeerCallback update_expiry_callback_;
PeerCallback dual_mode_callback_;
StringInspectable<PeerId> identifier_;
StringInspectable<TechnologyType> technology_;
StringInspectable<DeviceAddress> address_;
bool identity_known_;
std::optional<std::string> name_;
StringInspectable<std::optional<hci::HCIVersion>> lmp_version_;
StringInspectable<std::optional<uint16_t>> lmp_manufacturer_;
std::optional<uint16_t> lmp_subversion_;
StringInspectable<hci::LMPFeatureSet> lmp_features_;
BoolInspectable<bool> connectable_;
BoolInspectable<bool> temporary_;
int8_t rssi_;
// The spec does not explicitly prohibit LE->BREDR cross-transport key generation for LE-only
// peers. This is used to store a CT-generated BR/EDR key for LE-only peers to avoid incorrectly
// marking a peer as dual-mode.
std::optional<sm::LTK> bredr_cross_transport_key_;
// Data that only applies to the LE transport. This is present if this device
// is known to support LE.
std::optional<LowEnergyData> le_data_;
// Data that only applies to the BR/EDR transport. This is present if this
// device is known to support BR/EDR.
std::optional<BrEdrData> bredr_data_;
// Metrics counters used across all peer objects. Weak reference.
PeerMetrics* peer_metrics_;
fxl::WeakPtrFactory<Peer> weak_ptr_factory_;
DISALLOW_COPY_AND_ASSIGN_ALLOW_MOVE(Peer);
};
} // namespace bt::gap
#endif // SRC_CONNECTIVITY_BLUETOOTH_CORE_BT_HOST_GAP_PEER_H_