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