• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 #include <pw_assert/check.h>
19 
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     PW_CHECK(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   log_bond_failure.cancel();
201   peer_metrics_.LogLeBondSuccessEvent();
202 
203   if (bond_data.cross_transport_key.has_value()) {
204     if (peer->identity_known()) {
205       if (!peer->MutBrEdr().SetBondData(
206               bond_data.cross_transport_key.value())) {
207         bt_log(WARN,
208                "gap",
209                "failed to store BR/EDR cross transport key"
210                "(peer: %s)",
211                bt_str(peer->identifier()));
212       }
213     } else {
214       bt_log(WARN,
215              "gap",
216              "cannot use BR/EDR cross transport key without identity address "
217              "(peer: %s)",
218              bt_str(peer->identifier()));
219     }
220   }
221 
222   // Report the bond for persisting only if the identity of the peer is known.
223   if (peer->identity_known()) {
224     NotifyPeerBonded(*peer);
225   }
226 
227   return true;
228 }
229 
StoreBrEdrBond(const DeviceAddress & address,const sm::LTK & link_key)230 bool PeerCache::StoreBrEdrBond(const DeviceAddress& address,
231                                const sm::LTK& link_key) {
232   PW_DCHECK(address.IsPublic());
233   auto* peer = FindByAddress(address);
234   if (!peer) {
235     bt_log(WARN,
236            "gap-bredr",
237            "failed to store bond for unknown peer (address: %s)",
238            bt_str(address));
239     return false;
240   }
241 
242   if (!peer->MutBrEdr().SetBondData(link_key)) {
243     return false;
244   }
245   PW_DCHECK(!peer->temporary());
246   PW_DCHECK(peer->bredr()->bonded());
247 
248   NotifyPeerBonded(*peer);
249   return true;
250 }
251 
SetAutoConnectBehaviorForIntentionalDisconnect(PeerId peer_id)252 bool PeerCache::SetAutoConnectBehaviorForIntentionalDisconnect(PeerId peer_id) {
253   Peer* const peer = FindById(peer_id);
254   if (!peer) {
255     bt_log(WARN,
256            "gap-le",
257            "failed to update auto-connect behavior to kSkipUntilNextConnection "
258            "for "
259            "unknown peer: %s",
260            bt_str(peer_id));
261     return false;
262   }
263 
264   bt_log(DEBUG,
265          "gap-le",
266          "updated auto-connect behavior to kSkipUntilNextConnection (peer: %s)",
267          bt_str(peer_id));
268 
269   peer->MutLe().set_auto_connect_behavior(
270       Peer::AutoConnectBehavior::kSkipUntilNextConnection);
271 
272   // TODO(fxbug.dev/42113239): When implementing auto-connect behavior tracking
273   // for classic bluetooth, consider tracking this policy for the peer as a
274   // whole unless we think this policy should be applied separately for each
275   // transport (per armansito@).
276 
277   return true;
278 }
279 
SetAutoConnectBehaviorForSuccessfulConnection(PeerId peer_id)280 bool PeerCache::SetAutoConnectBehaviorForSuccessfulConnection(PeerId peer_id) {
281   Peer* const peer = FindById(peer_id);
282   if (!peer) {
283     bt_log(WARN,
284            "gap-le",
285            "failed to update auto-connect behavior to kAlways for unknown "
286            "peer: %s",
287            bt_str(peer_id));
288     return false;
289   }
290 
291   bt_log(DEBUG,
292          "gap-le",
293          "updated auto-connect behavior to kAlways (peer: %s)",
294          bt_str(peer_id));
295 
296   peer->MutLe().set_auto_connect_behavior(Peer::AutoConnectBehavior::kAlways);
297 
298   // TODO(fxbug.dev/42113239): Implement auto-connect behavior tracking for
299   // classic bluetooth.
300 
301   return true;
302 }
303 
RemoveDisconnectedPeer(PeerId peer_id)304 bool PeerCache::RemoveDisconnectedPeer(PeerId peer_id) {
305   Peer* const peer = FindById(peer_id);
306   if (!peer) {
307     return true;
308   }
309 
310   if (peer->connected()) {
311     return false;
312   }
313 
314   RemovePeer(peer);
315   return true;
316 }
317 
FindById(PeerId peer_id) const318 Peer* PeerCache::FindById(PeerId peer_id) const {
319   auto iter = peers_.find(peer_id);
320   return iter != peers_.end() ? iter->second.peer() : nullptr;
321 }
322 
FindByAddress(const DeviceAddress & in_address) const323 Peer* PeerCache::FindByAddress(const DeviceAddress& in_address) const {
324   std::optional<DeviceAddress> address;
325   if (in_address.IsResolvablePrivate()) {
326     address = le_resolving_list_.Resolve(in_address);
327   }
328 
329   // Fall back to the input if an identity wasn't resolved.
330   if (!address) {
331     address = in_address;
332   }
333 
334   PW_DCHECK(address);
335   auto identifier = FindIdByAddress(*address);
336   if (!identifier) {
337     return nullptr;
338   }
339 
340   auto* p = FindById(*identifier);
341   PW_DCHECK(p);
342   return p;
343 }
344 
AttachInspect(inspect::Node & parent,std::string name)345 void PeerCache::AttachInspect(inspect::Node& parent, std::string name) {
346   node_ = parent.CreateChild(name);
347 
348   if (!node_) {
349     return;
350   }
351 
352   peer_metrics_.AttachInspect(node_);
353 
354   for (auto& [_, record] : peers_) {
355     record.peer()->AttachInspect(node_, node_.UniqueName("peer_"));
356   }
357 }
358 
add_peer_updated_callback(PeerCallback callback)359 PeerCache::CallbackId PeerCache::add_peer_updated_callback(
360     PeerCallback callback) {
361   auto [iter, success] =
362       peer_updated_callbacks_.emplace(next_callback_id_++, std::move(callback));
363   PW_CHECK(success);
364   return iter->first;
365 }
366 
remove_peer_updated_callback(CallbackId id)367 bool PeerCache::remove_peer_updated_callback(CallbackId id) {
368   return peer_updated_callbacks_.erase(id);
369 }
370 
371 // Private methods below.
372 
InsertPeerRecord(PeerId identifier,const DeviceAddress & address,bool connectable)373 Peer* PeerCache::InsertPeerRecord(PeerId identifier,
374                                   const DeviceAddress& address,
375                                   bool connectable) {
376   if (FindIdByAddress(address)) {
377     bt_log(WARN,
378            "gap",
379            "tried to insert peer with existing address: %s",
380            address.ToString().c_str());
381     return nullptr;
382   }
383 
384   auto store_le_bond_cb = [this, identifier](const sm::PairingData& data) {
385     return StoreLowEnergyBond(identifier, data);
386   };
387 
388   std::unique_ptr<Peer> peer(
389       new Peer(fit::bind_member<&PeerCache::NotifyPeerUpdated>(this),
390                fit::bind_member<&PeerCache::UpdateExpiry>(this),
391                fit::bind_member<&PeerCache::MakeDualMode>(this),
392                std::move(store_le_bond_cb),
393                identifier,
394                address,
395                connectable,
396                &peer_metrics_,
397                dispatcher_));
398   if (node_) {
399     peer->AttachInspect(node_, node_.UniqueName("peer_"));
400   }
401 
402   // Note: we must construct the PeerRecord in-place, because it doesn't
403   // support copy or move.
404   auto [iter, inserted] = peers_.try_emplace(
405       peer->identifier(),
406       std::move(peer),
407       [this, p = peer.get()] { RemovePeer(p); },
408       dispatcher_);
409   if (!inserted) {
410     bt_log(WARN,
411            "gap",
412            "tried to insert peer with existing ID: %s",
413            bt_str(identifier));
414     return nullptr;
415   }
416 
417   address_map_[address] = identifier;
418   return iter->second.peer();
419 }
420 
NotifyPeerBonded(const Peer & peer)421 void PeerCache::NotifyPeerBonded(const Peer& peer) {
422   PW_DCHECK(peers_.find(peer.identifier()) != peers_.end());
423   PW_DCHECK(peers_.at(peer.identifier()).peer() == &peer);
424   PW_DCHECK(peer.identity_known(),
425             "peers not allowed to bond with unknown identity!");
426 
427   bt_log(INFO, "gap", "successfully bonded (peer: %s)", bt_str(peer));
428   if (peer_bonded_callback_) {
429     peer_bonded_callback_(peer);
430   }
431 }
432 
NotifyPeerUpdated(const Peer & peer,Peer::NotifyListenersChange change)433 void PeerCache::NotifyPeerUpdated(const Peer& peer,
434                                   Peer::NotifyListenersChange change) {
435   PW_DCHECK(peers_.find(peer.identifier()) != peers_.end());
436   PW_DCHECK(peers_.at(peer.identifier()).peer() == &peer);
437 
438   for (auto& [_, peer_updated_callback] : peer_updated_callbacks_) {
439     peer_updated_callback(peer);
440   }
441 
442   if (change == Peer::NotifyListenersChange::kBondUpdated) {
443     PW_CHECK(peer.bonded());
444     bt_log(INFO, "gap", "peer bond updated %s", bt_str(peer));
445     if (peer_bonded_callback_) {
446       peer_bonded_callback_(peer);
447     }
448   }
449 }
450 
UpdateExpiry(const Peer & peer)451 void PeerCache::UpdateExpiry(const Peer& peer) {
452   auto peer_record_iter = peers_.find(peer.identifier());
453   PW_DCHECK(peer_record_iter != peers_.end());
454 
455   auto& peer_record = peer_record_iter->second;
456   PW_DCHECK(peer_record.peer() == &peer);
457 
458   peer_record.removal_task()->Cancel();
459 
460   // Previous expiry task has been canceled. Re-schedule only if the peer is
461   // temporary.
462   if (peer.temporary()) {
463     peer_record.removal_task()->PostAfter(kCacheTimeout);
464   }
465 }
466 
MakeDualMode(const Peer & peer)467 void PeerCache::MakeDualMode(const Peer& peer) {
468   PW_CHECK(address_map_.at(peer.address()) == peer.identifier());
469   const auto address_alias = GetAliasAddress(peer.address());
470   auto [iter, inserted] =
471       address_map_.try_emplace(address_alias, peer.identifier());
472   PW_CHECK(inserted || iter->second == peer.identifier(),
473            "%s can't become dual-mode because %s maps to %s",
474            bt_str(peer.identifier()),
475            bt_str(address_alias),
476            bt_str(iter->second));
477   bt_log(INFO,
478          "gap",
479          "peer became dual mode (peer: %s, address: %s, alias: %s)",
480          bt_str(peer.identifier()),
481          bt_str(peer.address()),
482          bt_str(address_alias));
483 
484   // The peer became dual mode in lieu of adding a new peer but is as
485   // significant, so notify listeners of the change.
486   NotifyPeerUpdated(peer, Peer::NotifyListenersChange::kBondNotUpdated);
487 }
488 
RemovePeer(Peer * peer)489 void PeerCache::RemovePeer(Peer* peer) {
490   PW_DCHECK(peer);
491 
492   auto peer_record_it = peers_.find(peer->identifier());
493   PW_DCHECK(peer_record_it != peers_.end());
494   PW_DCHECK(peer_record_it->second.peer() == peer);
495 
496   PeerId id = peer->identifier();
497   bt_log(DEBUG, "gap", "removing peer %s", bt_str(id));
498   for (auto iter = address_map_.begin(); iter != address_map_.end();) {
499     if (iter->second == id) {
500       iter = address_map_.erase(iter);
501     } else {
502       iter++;
503     }
504   }
505 
506   if (peer->le() && peer->le()->bonded()) {
507     if (auto& address = peer->le()->bond_data()->identity_address) {
508       le_resolving_list_.Remove(*address);
509     }
510   }
511 
512   peers_.erase(peer_record_it);  // Destroys |peer|.
513   if (peer_removed_callback_) {
514     peer_removed_callback_(id);
515   }
516 }
517 
FindIdByAddress(const DeviceAddress & address) const518 std::optional<PeerId> PeerCache::FindIdByAddress(
519     const DeviceAddress& address) const {
520   auto iter = address_map_.find(address);
521   if (iter == address_map_.end()) {
522     // Search again using the other technology's address. This is necessary when
523     // a dual-mode peer is known by only one technology and is then discovered
524     // or connected on its other technology.
525     iter = address_map_.find(GetAliasAddress(address));
526   }
527 
528   if (iter == address_map_.end()) {
529     return {};
530   }
531   return {iter->second};
532 }
533 
534 }  // namespace bt::gap
535