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 #include <lib/fit/function.h> 17 18 #include <unordered_map> 19 20 #include "pw_bluetooth_sapphire/internal/host/common/device_address.h" 21 #include "pw_bluetooth_sapphire/internal/host/common/inspect.h" 22 #include "pw_bluetooth_sapphire/internal/host/common/macros.h" 23 #include "pw_bluetooth_sapphire/internal/host/common/smart_task.h" 24 #include "pw_bluetooth_sapphire/internal/host/gap/bonding_data.h" 25 #include "pw_bluetooth_sapphire/internal/host/gap/identity_resolving_list.h" 26 #include "pw_bluetooth_sapphire/internal/host/gap/peer.h" 27 #include "pw_bluetooth_sapphire/internal/host/gap/peer_metrics.h" 28 #include "pw_bluetooth_sapphire/internal/host/hci/connection.h" 29 #include "pw_bluetooth_sapphire/internal/host/sm/types.h" 30 31 namespace bt { 32 33 class ByteBuffer; 34 35 namespace hci { 36 class LowEnergyScanResult; 37 } // namespace hci 38 39 namespace gap { 40 41 // A PeerCache provides access to remote Bluetooth devices that are 42 // known to the system. 43 class PeerCache final { 44 public: 45 using CallbackId = uint64_t; 46 using PeerCallback = fit::function<void(const Peer& peer)>; 47 using PeerIdCallback = fit::function<void(PeerId identifier)>; 48 PeerCache(pw::async::Dispatcher & dispatcher)49 PeerCache(pw::async::Dispatcher& dispatcher) : dispatcher_(dispatcher) {} 50 51 // Creates a new peer entry using the given parameters, and returns a 52 // (non-owning) pointer to that peer. The caller must not retain the pointer 53 // beyond the current dispatcher task, as the underlying Peer is owned 54 // by |this| PeerCache, and may be invalidated spontaneously. 55 // 56 // Returns nullptr if an entry matching |address| already exists in the cache, 57 // including as a public identity of a peer with a different technology. 58 Peer* NewPeer(const DeviceAddress& address, bool connectable); 59 60 // Iterates over all current peers in the map, running |f| on each entry 61 // synchronously. This is intended for IPC methods that request a list of 62 // peers. 63 // 64 // Clients should use the FindBy*() methods below to interact with 65 // Peer objects. 66 void ForEach(PeerCallback f); 67 68 // Creates a new non-temporary peer entry using the given |bd.identifier| and 69 // identity |bd.address|. This is intended to initialize this PeerCache 70 // with previously bonded peers while bootstrapping a bt-host peer. The 71 // "peer bonded" callback will not be invoked. 72 // 73 // This method is not intended for updating the bonding data of a peer that 74 // already exists the cache and returns false if a mapping for 75 // |bd. identifier| or |bd.address| is already present. Use Store*Bond() 76 // methods to update pairing information of an existing peer. 77 // 78 // If a peer already exists that has the same public identity address with a 79 // different technology, this method will return false. The existing peer 80 // should be instead updated with new bond information to create a dual-mode 81 // peer. 82 // 83 bool AddBondedPeer(BondingData bd); 84 85 // Update the peer with the given identifier with new LE bonding 86 // information. The peer will be considered "bonded" and the bonded callback 87 // will be notified. If the peer is already bonded then bonding data will be 88 // updated. 89 // 90 // If |bond_data| contains an |identity_address|, the peer cache will be 91 // updated with a new mapping from that address to this peer identifier. If 92 // the identity address already maps to an existing peer, this method will 93 // return false. TODO(armansito): Merge the peers instead of failing? What 94 // happens if we obtain a LE identity address from a dual-mode peer that 95 // matches the BD_ADDR previously obtained from it over BR/EDR? 96 bool StoreLowEnergyBond(PeerId identifier, const sm::PairingData& bond_data); 97 98 // Update a peer identified by BD_ADDR |address| with a new BR/EDR link key. 99 // The peer will be considered "bonded" and the bonded callback notified. If 100 // the peer is already bonded then the link key will be updated. Returns 101 // false if the address does not match that of a known peer. 102 bool StoreBrEdrBond(const DeviceAddress& address, const sm::LTK& link_key); 103 104 // Update a peer's auto-connect behavior appropriately for an intentional 105 // (eg. manual) disconnect. Returns false if the address does not match that 106 // of a known peer. 107 bool SetAutoConnectBehaviorForIntentionalDisconnect(PeerId peer_id); 108 109 // Update a peer's auto-connect behavior appropriately after a successful 110 // connection. Returns false if the address does not match that of a known 111 // peer. 112 bool SetAutoConnectBehaviorForSuccessfulConnection(PeerId peer_id); 113 114 // If a peer identified by |peer_id| exists and is not connected on either 115 // transport, remove it from the cache immediately. Returns true after no peer 116 // with |peer_id| exists in the cache, false otherwise. 117 [[nodiscard]] bool RemoveDisconnectedPeer(PeerId peer_id); 118 119 // Returns the remote peer with identifier |peer_id|. Returns nullptr if 120 // |peer_id| is not recognized. 121 Peer* FindById(PeerId peer_id) const; 122 123 // Finds and returns a Peer with address |address| if it exists, 124 // returns nullptr otherwise. Tries to resolve |address| if it is resolvable. 125 // If |address| is of type kBREDR or kLEPublic, then this searches for peers 126 // that have either type of address. 127 Peer* FindByAddress(const DeviceAddress& address) const; 128 129 // Attach peer cache inspect node as a child node of |parent|. 130 static constexpr const char* kInspectNodeName = "peer_cache"; 131 void AttachInspect(inspect::Node& parent, 132 std::string name = kInspectNodeName); 133 134 // Register a |callback| to be invoked whenever a peer is added or updated. 135 CallbackId add_peer_updated_callback(PeerCallback callback); 136 137 // Unregister the callback indicated by |id|. Returns true if the callback was 138 // removed successfully, or false otherwise (e.g. the callback was previously 139 // removed). 140 bool remove_peer_updated_callback(CallbackId id); 141 142 // When set, |callback| will be invoked whenever a peer is removed. set_peer_removed_callback(PeerIdCallback callback)143 void set_peer_removed_callback(PeerIdCallback callback) { 144 peer_removed_callback_ = std::move(callback); 145 } 146 147 // When this callback is set, |callback| will be invoked whenever the bonding 148 // data of a peer is updated and should be persisted. The caller must ensure 149 // that |callback| outlives |this|. set_peer_bonded_callback(PeerCallback callback)150 void set_peer_bonded_callback(PeerCallback callback) { 151 peer_bonded_callback_ = std::move(callback); 152 } 153 154 // Returns the number of peers that are currently in the peer cache. count()155 size_t count() const { return peers_.size(); } 156 157 // Used by connection managers to increment peer bonding metrics. LogBrEdrBondingEvent(bool success)158 void LogBrEdrBondingEvent(bool success) { 159 if (success) { 160 peer_metrics_.LogBrEdrBondSuccessEvent(); 161 } else { 162 peer_metrics_.LogBrEdrBondFailureEvent(); 163 } 164 } 165 166 private: 167 class PeerRecord final { 168 public: PeerRecord(std::unique_ptr<Peer> peer,fit::closure remove_peer_callback,pw::async::Dispatcher & dispatcher)169 PeerRecord(std::unique_ptr<Peer> peer, 170 fit::closure remove_peer_callback, 171 pw::async::Dispatcher& dispatcher) 172 : peer_(std::move(peer)), 173 remove_peer_callback_(std::move(remove_peer_callback)), 174 removal_task_(dispatcher) { 175 removal_task_.set_function( 176 [this](pw::async::Context /*ctx*/, pw::Status status) { 177 if (status.ok() && remove_peer_callback_) { 178 remove_peer_callback_(); 179 } 180 }); 181 } 182 183 // The copy and move ctors cannot be implicitly defined, since 184 // async::TaskClosure does not support those operations. Nor is any 185 // meaningful explicit definition possible. 186 PeerRecord(const PeerRecord&) = delete; 187 PeerRecord(PeerRecord&&) = delete; 188 peer()189 Peer* peer() const { return peer_.get(); } 190 191 // Returns a pointer to removal_task_, which can be used to (re-)schedule or 192 // cancel |remove_peer_callback|. removal_task()193 SmartTask* removal_task() { return &removal_task_; } 194 195 private: 196 std::unique_ptr<Peer> peer_; 197 fit::closure remove_peer_callback_; 198 SmartTask removal_task_; 199 }; 200 201 // Create and track a record of a remote peer with a given |identifier|, 202 // |address|, and connectability (|connectable|). Returns a pointer to the 203 // inserted peer or nullptr if |identifier| or |address| already exists in 204 // the cache. 205 Peer* InsertPeerRecord(PeerId identifier, 206 const DeviceAddress& address, 207 bool connectable); 208 209 // Notifies interested parties that |peer| has bonded 210 // |peer| must already exist in the cache. 211 void NotifyPeerBonded(const Peer& peer); 212 213 // Notifies interested parties that |peer| has seen a significant change. 214 // |peer| must already exist in the cache. 215 void NotifyPeerUpdated(const Peer& peer, Peer::NotifyListenersChange change); 216 217 // Updates the expiration time for |peer|, if a temporary. Cancels expiry, 218 // if a non-temporary. Pre-conditions: 219 // - |peer| must already exist in the cache 220 // - can only be called from the thread that created |peer| 221 void UpdateExpiry(const Peer& peer); 222 223 // Updates the cache when an existing peer is found to be dual-mode. Also 224 // notifies listeners of the "peer updated" callback. 225 // |peer| must already exist in the cache. 226 void MakeDualMode(const Peer& peer); 227 228 // Removes |peer| from this cache, and notifies listeners of the removal. 229 void RemovePeer(Peer* peer); 230 231 // Search for an unique peer ID by its peer address |address|, by both 232 // technologies if it is a public address. |address| should be already 233 // resolved, if it is resolvable. If found, returns a valid peer ID; 234 // otherwise returns std::nullopt. 235 std::optional<PeerId> FindIdByAddress(const DeviceAddress& address) const; 236 237 pw::async::Dispatcher& dispatcher_; 238 239 // Mapping from unique peer IDs to PeerRecords. 240 // Owns the corresponding Peers. 241 std::unordered_map<PeerId, PeerRecord> peers_; 242 243 // Mapping from peer addresses to unique peer identifiers for all known 244 // peers. This is used to look-up and update existing cached data for a 245 // particular scan result so as to avoid creating duplicate entries for the 246 // same peer. 247 // 248 // Dual-mode peers shall have identity addresses of both technologies 249 // mapped to the same ID, if the addresses have the same value. 250 std::unordered_map<DeviceAddress, PeerId> address_map_; 251 252 // The LE identity resolving list used to resolve RPAs. 253 IdentityResolvingList le_resolving_list_; 254 255 CallbackId next_callback_id_ = 0u; 256 std::unordered_map<CallbackId, PeerCallback> peer_updated_callbacks_; 257 PeerIdCallback peer_removed_callback_; 258 PeerCallback peer_bonded_callback_; 259 260 inspect::Node node_; 261 262 PeerMetrics peer_metrics_; 263 264 BT_DISALLOW_COPY_AND_ASSIGN_ALLOW_MOVE(PeerCache); 265 }; 266 267 } // namespace gap 268 } // namespace bt 269