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