• 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 
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   BT_DEBUG_ASSERT(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   BT_DEBUG_ASSERT(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     BT_ASSERT(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     BT_ASSERT(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     BT_DEBUG_ASSERT(peer->bredr()->bonded());
122   }
123 
124   if (peer->technology() == TechnologyType::kDualMode) {
125     address_map_[GetAliasAddress(bd.address)] = bd.identifier;
126   }
127 
128   BT_DEBUG_ASSERT(!peer->temporary());
129   BT_DEBUG_ASSERT(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   BT_ASSERT(bond_data.irk.has_value() ==
145             bond_data.identity_address.has_value());
146 
147   auto log_bond_failure =
148       fit::defer([this] { peer_metrics_.LogLeBondFailureEvent(); });
149 
150   auto* peer = FindById(identifier);
151   if (!peer) {
152     bt_log(WARN,
153            "gap-le",
154            "failed to store bond for unknown peer (peer: %s)",
155            bt_str(identifier));
156     return false;
157   }
158 
159   // Either a LTK or CSRK is mandatory for bonding (the former is needed for LE
160   // Security Mode 1 and the latter is needed for Mode 2).
161   if (!bond_data.peer_ltk && !bond_data.local_ltk && !bond_data.csrk) {
162     bt_log(WARN,
163            "gap-le",
164            "mandatory keys missing: no LTK or CSRK (peer: %s)",
165            bt_str(identifier));
166     return false;
167   }
168 
169   if (bond_data.identity_address) {
170     auto existing_id = FindIdByAddress(*bond_data.identity_address);
171     if (!existing_id) {
172       // Map the new address to |peer|. We leave old addresses that map to
173       // this peer in the cache in case there are any pending controller
174       // procedures that expect them.
175       // TODO(armansito): Maybe expire the old address after a while?
176       address_map_[*bond_data.identity_address] = identifier;
177     } else if (*existing_id != identifier) {
178       bt_log(WARN,
179              "gap-le",
180              "identity address %s for peer %s belongs to another peer %s!",
181              bt_str(*bond_data.identity_address),
182              bt_str(identifier),
183              bt_str(*existing_id));
184       return false;
185     }
186     // We have either created a new mapping or the identity address already
187     // maps to this peer.
188   }
189 
190   // TODO(fxbug.dev/42072204): Check that we're not downgrading the security
191   // level before overwriting the bond.
192   peer->MutLe().SetBondData(bond_data);
193   BT_DEBUG_ASSERT(!peer->temporary());
194   BT_DEBUG_ASSERT(peer->le()->bonded());
195 
196   // Add the peer to the resolving list if it has an IRK.
197   if (peer->identity_known() && bond_data.irk) {
198     le_resolving_list_.Add(*bond_data.identity_address, bond_data.irk->value());
199   }
200 
201   if (bond_data.cross_transport_key) {
202     peer->StoreBrEdrCrossTransportKey(*bond_data.cross_transport_key);
203   }
204 
205   // Report the bond for persisting only if the identity of the peer is known.
206   if (peer->identity_known()) {
207     NotifyPeerBonded(*peer);
208   }
209 
210   log_bond_failure.cancel();
211   peer_metrics_.LogLeBondSuccessEvent();
212   return true;
213 }
214 
StoreBrEdrBond(const DeviceAddress & address,const sm::LTK & link_key)215 bool PeerCache::StoreBrEdrBond(const DeviceAddress& address,
216                                const sm::LTK& link_key) {
217   BT_DEBUG_ASSERT(address.type() == DeviceAddress::Type::kBREDR);
218   auto* peer = FindByAddress(address);
219   if (!peer) {
220     bt_log(WARN,
221            "gap-bredr",
222            "failed to store bond for unknown peer (address: %s)",
223            bt_str(address));
224     return false;
225   }
226 
227   // TODO(fxbug.dev/42072204): Check that we're not downgrading the security
228   // level before overwriting the bond.
229   peer->MutBrEdr().SetBondData(link_key);
230   BT_DEBUG_ASSERT(!peer->temporary());
231   BT_DEBUG_ASSERT(peer->bredr()->bonded());
232 
233   NotifyPeerBonded(*peer);
234   return true;
235 }
236 
SetAutoConnectBehaviorForIntentionalDisconnect(PeerId peer_id)237 bool PeerCache::SetAutoConnectBehaviorForIntentionalDisconnect(PeerId peer_id) {
238   Peer* const peer = FindById(peer_id);
239   if (!peer) {
240     bt_log(WARN,
241            "gap-le",
242            "failed to update auto-connect behavior to kSkipUntilNextConnection "
243            "for "
244            "unknown peer: %s",
245            bt_str(peer_id));
246     return false;
247   }
248 
249   bt_log(DEBUG,
250          "gap-le",
251          "updated auto-connect behavior to kSkipUntilNextConnection (peer: %s)",
252          bt_str(peer_id));
253 
254   peer->MutLe().set_auto_connect_behavior(
255       Peer::AutoConnectBehavior::kSkipUntilNextConnection);
256 
257   // TODO(fxbug.dev/42113239): When implementing auto-connect behavior tracking
258   // for classic bluetooth, consider tracking this policy for the peer as a
259   // whole unless we think this policy should be applied separately for each
260   // transport (per armansito@).
261 
262   return true;
263 }
264 
SetAutoConnectBehaviorForSuccessfulConnection(PeerId peer_id)265 bool PeerCache::SetAutoConnectBehaviorForSuccessfulConnection(PeerId peer_id) {
266   Peer* const peer = FindById(peer_id);
267   if (!peer) {
268     bt_log(WARN,
269            "gap-le",
270            "failed to update auto-connect behavior to kAlways for unknown "
271            "peer: %s",
272            bt_str(peer_id));
273     return false;
274   }
275 
276   bt_log(DEBUG,
277          "gap-le",
278          "updated auto-connect behavior to kAlways (peer: %s)",
279          bt_str(peer_id));
280 
281   peer->MutLe().set_auto_connect_behavior(Peer::AutoConnectBehavior::kAlways);
282 
283   // TODO(fxbug.dev/42113239): Implement auto-connect behavior tracking for
284   // classic bluetooth.
285 
286   return true;
287 }
288 
RemoveDisconnectedPeer(PeerId peer_id)289 bool PeerCache::RemoveDisconnectedPeer(PeerId peer_id) {
290   Peer* const peer = FindById(peer_id);
291   if (!peer) {
292     return true;
293   }
294 
295   if (peer->connected()) {
296     return false;
297   }
298 
299   RemovePeer(peer);
300   return true;
301 }
302 
FindById(PeerId peer_id) const303 Peer* PeerCache::FindById(PeerId peer_id) const {
304   auto iter = peers_.find(peer_id);
305   return iter != peers_.end() ? iter->second.peer() : nullptr;
306 }
307 
FindByAddress(const DeviceAddress & in_address) const308 Peer* PeerCache::FindByAddress(const DeviceAddress& in_address) const {
309   std::optional<DeviceAddress> address;
310   if (in_address.IsResolvablePrivate()) {
311     address = le_resolving_list_.Resolve(in_address);
312   }
313 
314   // Fall back to the input if an identity wasn't resolved.
315   if (!address) {
316     address = in_address;
317   }
318 
319   BT_DEBUG_ASSERT(address);
320   auto identifier = FindIdByAddress(*address);
321   if (!identifier) {
322     return nullptr;
323   }
324 
325   auto* p = FindById(*identifier);
326   BT_DEBUG_ASSERT(p);
327   return p;
328 }
329 
AttachInspect(inspect::Node & parent,std::string name)330 void PeerCache::AttachInspect(inspect::Node& parent, std::string name) {
331   node_ = parent.CreateChild(name);
332 
333   if (!node_) {
334     return;
335   }
336 
337   peer_metrics_.AttachInspect(node_);
338 
339   for (auto& [_, record] : peers_) {
340     record.peer()->AttachInspect(node_, node_.UniqueName("peer_"));
341   }
342 }
343 
add_peer_updated_callback(PeerCallback callback)344 PeerCache::CallbackId PeerCache::add_peer_updated_callback(
345     PeerCallback callback) {
346   auto [iter, success] =
347       peer_updated_callbacks_.emplace(next_callback_id_++, std::move(callback));
348   BT_ASSERT(success);
349   return iter->first;
350 }
351 
remove_peer_updated_callback(CallbackId id)352 bool PeerCache::remove_peer_updated_callback(CallbackId id) {
353   return peer_updated_callbacks_.erase(id);
354 }
355 
356 // Private methods below.
357 
InsertPeerRecord(PeerId identifier,const DeviceAddress & address,bool connectable)358 Peer* PeerCache::InsertPeerRecord(PeerId identifier,
359                                   const DeviceAddress& address,
360                                   bool connectable) {
361   if (FindIdByAddress(address)) {
362     bt_log(WARN,
363            "gap",
364            "tried to insert peer with existing address: %s",
365            address.ToString().c_str());
366     return nullptr;
367   }
368 
369   auto store_le_bond_cb = [this, identifier](const sm::PairingData& data) {
370     return StoreLowEnergyBond(identifier, data);
371   };
372 
373   std::unique_ptr<Peer> peer(
374       new Peer(fit::bind_member<&PeerCache::NotifyPeerUpdated>(this),
375                fit::bind_member<&PeerCache::UpdateExpiry>(this),
376                fit::bind_member<&PeerCache::MakeDualMode>(this),
377                std::move(store_le_bond_cb),
378                identifier,
379                address,
380                connectable,
381                &peer_metrics_,
382                dispatcher_));
383   if (node_) {
384     peer->AttachInspect(node_, node_.UniqueName("peer_"));
385   }
386 
387   // Note: we must construct the PeerRecord in-place, because it doesn't
388   // support copy or move.
389   auto [iter, inserted] = peers_.try_emplace(
390       peer->identifier(),
391       std::move(peer),
392       [this, p = peer.get()] { RemovePeer(p); },
393       dispatcher_);
394   if (!inserted) {
395     bt_log(WARN,
396            "gap",
397            "tried to insert peer with existing ID: %s",
398            bt_str(identifier));
399     return nullptr;
400   }
401 
402   address_map_[address] = identifier;
403   return iter->second.peer();
404 }
405 
NotifyPeerBonded(const Peer & peer)406 void PeerCache::NotifyPeerBonded(const Peer& peer) {
407   BT_DEBUG_ASSERT(peers_.find(peer.identifier()) != peers_.end());
408   BT_DEBUG_ASSERT(peers_.at(peer.identifier()).peer() == &peer);
409   BT_DEBUG_ASSERT_MSG(peer.identity_known(),
410                       "peers not allowed to bond with unknown identity!");
411 
412   bt_log(INFO, "gap", "successfully bonded (peer: %s)", bt_str(peer));
413   if (peer_bonded_callback_) {
414     peer_bonded_callback_(peer);
415   }
416 }
417 
NotifyPeerUpdated(const Peer & peer,Peer::NotifyListenersChange change)418 void PeerCache::NotifyPeerUpdated(const Peer& peer,
419                                   Peer::NotifyListenersChange change) {
420   BT_DEBUG_ASSERT(peers_.find(peer.identifier()) != peers_.end());
421   BT_DEBUG_ASSERT(peers_.at(peer.identifier()).peer() == &peer);
422 
423   for (auto& [_, peer_updated_callback] : peer_updated_callbacks_) {
424     peer_updated_callback(peer);
425   }
426 
427   if (change == Peer::NotifyListenersChange::kBondUpdated) {
428     BT_ASSERT(peer.bonded());
429     bt_log(INFO, "gap", "peer bond updated %s", bt_str(peer));
430     if (peer_bonded_callback_) {
431       peer_bonded_callback_(peer);
432     }
433   }
434 }
435 
UpdateExpiry(const Peer & peer)436 void PeerCache::UpdateExpiry(const Peer& peer) {
437   auto peer_record_iter = peers_.find(peer.identifier());
438   BT_DEBUG_ASSERT(peer_record_iter != peers_.end());
439 
440   auto& peer_record = peer_record_iter->second;
441   BT_DEBUG_ASSERT(peer_record.peer() == &peer);
442 
443   peer_record.removal_task()->Cancel();
444 
445   // Previous expiry task has been canceled. Re-schedule only if the peer is
446   // temporary.
447   if (peer.temporary()) {
448     peer_record.removal_task()->PostAfter(kCacheTimeout);
449   }
450 }
451 
MakeDualMode(const Peer & peer)452 void PeerCache::MakeDualMode(const Peer& peer) {
453   BT_ASSERT(address_map_.at(peer.address()) == peer.identifier());
454   const auto address_alias = GetAliasAddress(peer.address());
455   auto [iter, inserted] =
456       address_map_.try_emplace(address_alias, peer.identifier());
457   BT_ASSERT_MSG(inserted || iter->second == peer.identifier(),
458                 "%s can't become dual-mode because %s maps to %s",
459                 bt_str(peer.identifier()),
460                 bt_str(address_alias),
461                 bt_str(iter->second));
462   bt_log(INFO,
463          "gap",
464          "peer became dual mode (peer: %s, address: %s, alias: %s)",
465          bt_str(peer.identifier()),
466          bt_str(peer.address()),
467          bt_str(address_alias));
468 
469   // The peer became dual mode in lieu of adding a new peer but is as
470   // significant, so notify listeners of the change.
471   NotifyPeerUpdated(peer, Peer::NotifyListenersChange::kBondNotUpdated);
472 }
473 
RemovePeer(Peer * peer)474 void PeerCache::RemovePeer(Peer* peer) {
475   BT_DEBUG_ASSERT(peer);
476 
477   auto peer_record_it = peers_.find(peer->identifier());
478   BT_DEBUG_ASSERT(peer_record_it != peers_.end());
479   BT_DEBUG_ASSERT(peer_record_it->second.peer() == peer);
480 
481   PeerId id = peer->identifier();
482   bt_log(DEBUG, "gap", "removing peer %s", bt_str(id));
483   for (auto iter = address_map_.begin(); iter != address_map_.end();) {
484     if (iter->second == id) {
485       iter = address_map_.erase(iter);
486     } else {
487       iter++;
488     }
489   }
490 
491   if (peer->le() && peer->le()->bonded()) {
492     if (auto& address = peer->le()->bond_data()->identity_address) {
493       le_resolving_list_.Remove(*address);
494     }
495   }
496 
497   peers_.erase(peer_record_it);  // Destroys |peer|.
498   if (peer_removed_callback_) {
499     peer_removed_callback_(id);
500   }
501 }
502 
FindIdByAddress(const DeviceAddress & address) const503 std::optional<PeerId> PeerCache::FindIdByAddress(
504     const DeviceAddress& address) const {
505   auto iter = address_map_.find(address);
506   if (iter == address_map_.end()) {
507     // Search again using the other technology's address. This is necessary when
508     // a dual-mode peer is known by only one technology and is then discovered
509     // or connected on its other technology.
510     iter = address_map_.find(GetAliasAddress(address));
511   }
512 
513   if (iter == address_map_.end()) {
514     return {};
515   }
516   return {iter->second};
517 }
518 
519 }  // namespace bt::gap
520