xref: /aosp_15_r20/external/pigweed/pw_bluetooth_sapphire/host/gap/peer_cache.cc (revision 61c4878ac05f98d0ceed94b57d316916de578985)
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 #include "pw_bluetooth_sapphire/internal/host/gap/peer_cache.h"
16 
17 #include <lib/fit/function.h>
18 
19 #include "pw_bluetooth_sapphire/internal/host/common/assert.h"
20 #include "pw_bluetooth_sapphire/internal/host/common/random.h"
21 #include "pw_bluetooth_sapphire/internal/host/gap/peer.h"
22 #include "pw_bluetooth_sapphire/internal/host/hci/connection.h"
23 #include "pw_bluetooth_sapphire/internal/host/hci/low_energy_scanner.h"
24 #include "pw_bluetooth_sapphire/internal/host/sm/types.h"
25 
26 namespace bt::gap {
27 
28 namespace {
29 
30 // Return an address with the same value as given, but with type kBREDR for
31 // kLEPublic addresses and vice versa.
GetAliasAddress(const DeviceAddress & address)32 DeviceAddress GetAliasAddress(const DeviceAddress& address) {
33   if (address.type() == DeviceAddress::Type::kBREDR) {
34     return {DeviceAddress::Type::kLEPublic, address.value()};
35   } else if (address.type() == DeviceAddress::Type::kLEPublic) {
36     return {DeviceAddress::Type::kBREDR, address.value()};
37   }
38   return address;
39 }
40 
41 }  // namespace
42 
NewPeer(const DeviceAddress & address,bool connectable)43 Peer* PeerCache::NewPeer(const DeviceAddress& address, bool connectable) {
44   auto* const peer = InsertPeerRecord(RandomPeerId(), address, connectable);
45   if (peer) {
46     UpdateExpiry(*peer);
47     NotifyPeerUpdated(*peer, Peer::NotifyListenersChange::kBondNotUpdated);
48   }
49   return peer;
50 }
51 
ForEach(PeerCallback f)52 void PeerCache::ForEach(PeerCallback f) {
53   PW_DCHECK(f);
54   for (const auto& iter : peers_) {
55     f(*iter.second.peer());
56   }
57 }
58 
AddBondedPeer(BondingData bd)59 bool PeerCache::AddBondedPeer(BondingData bd) {
60   PW_DCHECK(bd.address.type() != DeviceAddress::Type::kLEAnonymous);
61 
62   const bool bond_le = bd.le_pairing_data.peer_ltk ||
63                        bd.le_pairing_data.local_ltk || bd.le_pairing_data.csrk;
64   const bool bond_bredr = bd.bredr_link_key.has_value();
65 
66   // |bd.le_pairing_data| must contain either a LTK or CSRK for LE Security Mode
67   // 1 or 2.
68   //
69   // TODO(fxbug.dev/42102158): the address type checks here don't add much value
70   // because the address type is derived from the presence of FIDL bredr_bond
71   // and le_bond fields, so the check really should be whether at least one of
72   // the mandatory bond secrets is present.
73   if (bd.address.IsLowEnergy() && !bond_le) {
74     bt_log(ERROR,
75            "gap-le",
76            "mandatory keys missing: no LTK or CSRK (id: %s)",
77            bt_str(bd.identifier));
78     return false;
79   }
80 
81   if (bd.address.IsBrEdr() && !bond_bredr) {
82     bt_log(ERROR,
83            "gap-bredr",
84            "mandatory link key missing (id: %s)",
85            bt_str(bd.identifier));
86     return false;
87   }
88 
89   auto* peer =
90       InsertPeerRecord(bd.identifier, bd.address, /*connectable=*/true);
91   if (!peer) {
92     return false;
93   }
94 
95   // A bonded peer must have its identity known.
96   peer->set_identity_known(true);
97 
98   if (bd.name.has_value()) {
99     peer->RegisterName(bd.name.value(), Peer::NameSource::kUnknown);
100   }
101 
102   if (bond_le) {
103     PW_CHECK(bd.le_pairing_data.irk.has_value() ==
104              bd.le_pairing_data.identity_address.has_value());
105     peer->MutLe().SetBondData(bd.le_pairing_data);
106     PW_CHECK(peer->le()->bonded());
107 
108     // Add the peer to the resolving list if it has an IRK.
109     if (bd.le_pairing_data.irk) {
110       le_resolving_list_.Add(bd.le_pairing_data.identity_address.value(),
111                              bd.le_pairing_data.irk.value().value());
112     }
113   }
114 
115   if (bond_bredr) {
116     for (auto& service : bd.bredr_services) {
117       peer->MutBrEdr().AddService(std::move(service));
118     }
119 
120     peer->MutBrEdr().SetBondData(*bd.bredr_link_key);
121     PW_DCHECK(peer->bredr()->bonded());
122   }
123 
124   if (peer->technology() == TechnologyType::kDualMode) {
125     address_map_[GetAliasAddress(bd.address)] = bd.identifier;
126   }
127 
128   PW_DCHECK(!peer->temporary());
129   PW_DCHECK(peer->bonded());
130   bt_log(TRACE,
131          "gap",
132          "restored bonded peer: %s, id: %s",
133          bt_str(bd.address),
134          bt_str(bd.identifier));
135 
136   // Don't call UpdateExpiry(). Since a bonded peer starts out as
137   // non-temporary it is not necessary to ever set up the expiration callback.
138   NotifyPeerUpdated(*peer, Peer::NotifyListenersChange::kBondNotUpdated);
139   return true;
140 }
141 
StoreLowEnergyBond(PeerId identifier,const sm::PairingData & bond_data)142 bool PeerCache::StoreLowEnergyBond(PeerId identifier,
143                                    const sm::PairingData& bond_data) {
144   PW_CHECK(bond_data.irk.has_value() == bond_data.identity_address.has_value());
145 
146   auto log_bond_failure =
147       fit::defer([this] { peer_metrics_.LogLeBondFailureEvent(); });
148 
149   auto* peer = FindById(identifier);
150   if (!peer) {
151     bt_log(WARN,
152            "gap-le",
153            "failed to store bond for unknown peer (peer: %s)",
154            bt_str(identifier));
155     return false;
156   }
157 
158   // Either a LTK or CSRK is mandatory for bonding (the former is needed for LE
159   // Security Mode 1 and the latter is needed for Mode 2).
160   if (!bond_data.peer_ltk && !bond_data.local_ltk && !bond_data.csrk) {
161     bt_log(WARN,
162            "gap-le",
163            "mandatory keys missing: no LTK or CSRK (peer: %s)",
164            bt_str(identifier));
165     return false;
166   }
167 
168   if (bond_data.identity_address) {
169     auto existing_id = FindIdByAddress(*bond_data.identity_address);
170     if (!existing_id) {
171       // Map the new address to |peer|. We leave old addresses that map to
172       // this peer in the cache in case there are any pending controller
173       // procedures that expect them.
174       // TODO(armansito): Maybe expire the old address after a while?
175       address_map_[*bond_data.identity_address] = identifier;
176     } else if (*existing_id != identifier) {
177       bt_log(WARN,
178              "gap-le",
179              "identity address %s for peer %s belongs to another peer %s!",
180              bt_str(*bond_data.identity_address),
181              bt_str(identifier),
182              bt_str(*existing_id));
183       return false;
184     }
185     // We have either created a new mapping or the identity address already
186     // maps to this peer.
187   }
188 
189   // TODO(fxbug.dev/42072204): Check that we're not downgrading the security
190   // level before overwriting the bond.
191   peer->MutLe().SetBondData(bond_data);
192   PW_DCHECK(!peer->temporary());
193   PW_DCHECK(peer->le()->bonded());
194 
195   // Add the peer to the resolving list if it has an IRK.
196   if (peer->identity_known() && bond_data.irk) {
197     le_resolving_list_.Add(*bond_data.identity_address, bond_data.irk->value());
198   }
199 
200   if (bond_data.cross_transport_key) {
201     peer->StoreBrEdrCrossTransportKey(*bond_data.cross_transport_key);
202   }
203 
204   // Report the bond for persisting only if the identity of the peer is known.
205   if (peer->identity_known()) {
206     NotifyPeerBonded(*peer);
207   }
208 
209   log_bond_failure.cancel();
210   peer_metrics_.LogLeBondSuccessEvent();
211   return true;
212 }
213 
StoreBrEdrBond(const DeviceAddress & address,const sm::LTK & link_key)214 bool PeerCache::StoreBrEdrBond(const DeviceAddress& address,
215                                const sm::LTK& link_key) {
216   PW_DCHECK(address.type() == DeviceAddress::Type::kBREDR);
217   auto* peer = FindByAddress(address);
218   if (!peer) {
219     bt_log(WARN,
220            "gap-bredr",
221            "failed to store bond for unknown peer (address: %s)",
222            bt_str(address));
223     return false;
224   }
225 
226   // TODO(fxbug.dev/42072204): Check that we're not downgrading the security
227   // level before overwriting the bond.
228   peer->MutBrEdr().SetBondData(link_key);
229   PW_DCHECK(!peer->temporary());
230   PW_DCHECK(peer->bredr()->bonded());
231 
232   NotifyPeerBonded(*peer);
233   return true;
234 }
235 
SetAutoConnectBehaviorForIntentionalDisconnect(PeerId peer_id)236 bool PeerCache::SetAutoConnectBehaviorForIntentionalDisconnect(PeerId peer_id) {
237   Peer* const peer = FindById(peer_id);
238   if (!peer) {
239     bt_log(WARN,
240            "gap-le",
241            "failed to update auto-connect behavior to kSkipUntilNextConnection "
242            "for "
243            "unknown peer: %s",
244            bt_str(peer_id));
245     return false;
246   }
247 
248   bt_log(DEBUG,
249          "gap-le",
250          "updated auto-connect behavior to kSkipUntilNextConnection (peer: %s)",
251          bt_str(peer_id));
252 
253   peer->MutLe().set_auto_connect_behavior(
254       Peer::AutoConnectBehavior::kSkipUntilNextConnection);
255 
256   // TODO(fxbug.dev/42113239): When implementing auto-connect behavior tracking
257   // for classic bluetooth, consider tracking this policy for the peer as a
258   // whole unless we think this policy should be applied separately for each
259   // transport (per armansito@).
260 
261   return true;
262 }
263 
SetAutoConnectBehaviorForSuccessfulConnection(PeerId peer_id)264 bool PeerCache::SetAutoConnectBehaviorForSuccessfulConnection(PeerId peer_id) {
265   Peer* const peer = FindById(peer_id);
266   if (!peer) {
267     bt_log(WARN,
268            "gap-le",
269            "failed to update auto-connect behavior to kAlways for unknown "
270            "peer: %s",
271            bt_str(peer_id));
272     return false;
273   }
274 
275   bt_log(DEBUG,
276          "gap-le",
277          "updated auto-connect behavior to kAlways (peer: %s)",
278          bt_str(peer_id));
279 
280   peer->MutLe().set_auto_connect_behavior(Peer::AutoConnectBehavior::kAlways);
281 
282   // TODO(fxbug.dev/42113239): Implement auto-connect behavior tracking for
283   // classic bluetooth.
284 
285   return true;
286 }
287 
RemoveDisconnectedPeer(PeerId peer_id)288 bool PeerCache::RemoveDisconnectedPeer(PeerId peer_id) {
289   Peer* const peer = FindById(peer_id);
290   if (!peer) {
291     return true;
292   }
293 
294   if (peer->connected()) {
295     return false;
296   }
297 
298   RemovePeer(peer);
299   return true;
300 }
301 
FindById(PeerId peer_id) const302 Peer* PeerCache::FindById(PeerId peer_id) const {
303   auto iter = peers_.find(peer_id);
304   return iter != peers_.end() ? iter->second.peer() : nullptr;
305 }
306 
FindByAddress(const DeviceAddress & in_address) const307 Peer* PeerCache::FindByAddress(const DeviceAddress& in_address) const {
308   std::optional<DeviceAddress> address;
309   if (in_address.IsResolvablePrivate()) {
310     address = le_resolving_list_.Resolve(in_address);
311   }
312 
313   // Fall back to the input if an identity wasn't resolved.
314   if (!address) {
315     address = in_address;
316   }
317 
318   PW_DCHECK(address);
319   auto identifier = FindIdByAddress(*address);
320   if (!identifier) {
321     return nullptr;
322   }
323 
324   auto* p = FindById(*identifier);
325   PW_DCHECK(p);
326   return p;
327 }
328 
AttachInspect(inspect::Node & parent,std::string name)329 void PeerCache::AttachInspect(inspect::Node& parent, std::string name) {
330   node_ = parent.CreateChild(name);
331 
332   if (!node_) {
333     return;
334   }
335 
336   peer_metrics_.AttachInspect(node_);
337 
338   for (auto& [_, record] : peers_) {
339     record.peer()->AttachInspect(node_, node_.UniqueName("peer_"));
340   }
341 }
342 
add_peer_updated_callback(PeerCallback callback)343 PeerCache::CallbackId PeerCache::add_peer_updated_callback(
344     PeerCallback callback) {
345   auto [iter, success] =
346       peer_updated_callbacks_.emplace(next_callback_id_++, std::move(callback));
347   PW_CHECK(success);
348   return iter->first;
349 }
350 
remove_peer_updated_callback(CallbackId id)351 bool PeerCache::remove_peer_updated_callback(CallbackId id) {
352   return peer_updated_callbacks_.erase(id);
353 }
354 
355 // Private methods below.
356 
InsertPeerRecord(PeerId identifier,const DeviceAddress & address,bool connectable)357 Peer* PeerCache::InsertPeerRecord(PeerId identifier,
358                                   const DeviceAddress& address,
359                                   bool connectable) {
360   if (FindIdByAddress(address)) {
361     bt_log(WARN,
362            "gap",
363            "tried to insert peer with existing address: %s",
364            address.ToString().c_str());
365     return nullptr;
366   }
367 
368   auto store_le_bond_cb = [this, identifier](const sm::PairingData& data) {
369     return StoreLowEnergyBond(identifier, data);
370   };
371 
372   std::unique_ptr<Peer> peer(
373       new Peer(fit::bind_member<&PeerCache::NotifyPeerUpdated>(this),
374                fit::bind_member<&PeerCache::UpdateExpiry>(this),
375                fit::bind_member<&PeerCache::MakeDualMode>(this),
376                std::move(store_le_bond_cb),
377                identifier,
378                address,
379                connectable,
380                &peer_metrics_,
381                dispatcher_));
382   if (node_) {
383     peer->AttachInspect(node_, node_.UniqueName("peer_"));
384   }
385 
386   // Note: we must construct the PeerRecord in-place, because it doesn't
387   // support copy or move.
388   auto [iter, inserted] = peers_.try_emplace(
389       peer->identifier(),
390       std::move(peer),
391       [this, p = peer.get()] { RemovePeer(p); },
392       dispatcher_);
393   if (!inserted) {
394     bt_log(WARN,
395            "gap",
396            "tried to insert peer with existing ID: %s",
397            bt_str(identifier));
398     return nullptr;
399   }
400 
401   address_map_[address] = identifier;
402   return iter->second.peer();
403 }
404 
NotifyPeerBonded(const Peer & peer)405 void PeerCache::NotifyPeerBonded(const Peer& peer) {
406   PW_DCHECK(peers_.find(peer.identifier()) != peers_.end());
407   PW_DCHECK(peers_.at(peer.identifier()).peer() == &peer);
408   PW_DCHECK(peer.identity_known(),
409             "peers not allowed to bond with unknown identity!");
410 
411   bt_log(INFO, "gap", "successfully bonded (peer: %s)", bt_str(peer));
412   if (peer_bonded_callback_) {
413     peer_bonded_callback_(peer);
414   }
415 }
416 
NotifyPeerUpdated(const Peer & peer,Peer::NotifyListenersChange change)417 void PeerCache::NotifyPeerUpdated(const Peer& peer,
418                                   Peer::NotifyListenersChange change) {
419   PW_DCHECK(peers_.find(peer.identifier()) != peers_.end());
420   PW_DCHECK(peers_.at(peer.identifier()).peer() == &peer);
421 
422   for (auto& [_, peer_updated_callback] : peer_updated_callbacks_) {
423     peer_updated_callback(peer);
424   }
425 
426   if (change == Peer::NotifyListenersChange::kBondUpdated) {
427     PW_CHECK(peer.bonded());
428     bt_log(INFO, "gap", "peer bond updated %s", bt_str(peer));
429     if (peer_bonded_callback_) {
430       peer_bonded_callback_(peer);
431     }
432   }
433 }
434 
UpdateExpiry(const Peer & peer)435 void PeerCache::UpdateExpiry(const Peer& peer) {
436   auto peer_record_iter = peers_.find(peer.identifier());
437   PW_DCHECK(peer_record_iter != peers_.end());
438 
439   auto& peer_record = peer_record_iter->second;
440   PW_DCHECK(peer_record.peer() == &peer);
441 
442   peer_record.removal_task()->Cancel();
443 
444   // Previous expiry task has been canceled. Re-schedule only if the peer is
445   // temporary.
446   if (peer.temporary()) {
447     peer_record.removal_task()->PostAfter(kCacheTimeout);
448   }
449 }
450 
MakeDualMode(const Peer & peer)451 void PeerCache::MakeDualMode(const Peer& peer) {
452   PW_CHECK(address_map_.at(peer.address()) == peer.identifier());
453   const auto address_alias = GetAliasAddress(peer.address());
454   auto [iter, inserted] =
455       address_map_.try_emplace(address_alias, peer.identifier());
456   PW_CHECK(inserted || iter->second == peer.identifier(),
457            "%s can't become dual-mode because %s maps to %s",
458            bt_str(peer.identifier()),
459            bt_str(address_alias),
460            bt_str(iter->second));
461   bt_log(INFO,
462          "gap",
463          "peer became dual mode (peer: %s, address: %s, alias: %s)",
464          bt_str(peer.identifier()),
465          bt_str(peer.address()),
466          bt_str(address_alias));
467 
468   // The peer became dual mode in lieu of adding a new peer but is as
469   // significant, so notify listeners of the change.
470   NotifyPeerUpdated(peer, Peer::NotifyListenersChange::kBondNotUpdated);
471 }
472 
RemovePeer(Peer * peer)473 void PeerCache::RemovePeer(Peer* peer) {
474   PW_DCHECK(peer);
475 
476   auto peer_record_it = peers_.find(peer->identifier());
477   PW_DCHECK(peer_record_it != peers_.end());
478   PW_DCHECK(peer_record_it->second.peer() == peer);
479 
480   PeerId id = peer->identifier();
481   bt_log(DEBUG, "gap", "removing peer %s", bt_str(id));
482   for (auto iter = address_map_.begin(); iter != address_map_.end();) {
483     if (iter->second == id) {
484       iter = address_map_.erase(iter);
485     } else {
486       iter++;
487     }
488   }
489 
490   if (peer->le() && peer->le()->bonded()) {
491     if (auto& address = peer->le()->bond_data()->identity_address) {
492       le_resolving_list_.Remove(*address);
493     }
494   }
495 
496   peers_.erase(peer_record_it);  // Destroys |peer|.
497   if (peer_removed_callback_) {
498     peer_removed_callback_(id);
499   }
500 }
501 
FindIdByAddress(const DeviceAddress & address) const502 std::optional<PeerId> PeerCache::FindIdByAddress(
503     const DeviceAddress& address) const {
504   auto iter = address_map_.find(address);
505   if (iter == address_map_.end()) {
506     // Search again using the other technology's address. This is necessary when
507     // a dual-mode peer is known by only one technology and is then discovered
508     // or connected on its other technology.
509     iter = address_map_.find(GetAliasAddress(address));
510   }
511 
512   if (iter == address_map_.end()) {
513     return {};
514   }
515   return {iter->second};
516 }
517 
518 }  // namespace bt::gap
519