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