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.h"
16
17 #include <cpp-string/string_printf.h>
18 #include <pw_assert/check.h>
19 #include <pw_bytes/endian.h>
20 #include <pw_string/utf_codecs.h>
21
22 #include <cinttypes>
23
24 #include "pw_bluetooth_sapphire/internal/host/common/advertising_data.h"
25 #include "pw_bluetooth_sapphire/internal/host/common/manufacturer_names.h"
26 #include "pw_bluetooth_sapphire/internal/host/common/uuid.h"
27 #include "pw_bluetooth_sapphire/internal/host/gap/gap.h"
28 #include "pw_bluetooth_sapphire/internal/host/hci-spec/util.h"
29 #include "pw_bluetooth_sapphire/internal/host/sm/types.h"
30
31 namespace bt::gap {
32 namespace {
33 // To prevent log spam, we only log every Nth failure to parse AdvertisingData
34 // from each peer. This value controls N.
35 const int64_t kAdvDataParseFailureWarnLogInterval = 25;
36 } // namespace
37
ConnectionStateToString(Peer::ConnectionState state)38 std::string Peer::ConnectionStateToString(Peer::ConnectionState state) {
39 switch (state) {
40 case Peer::ConnectionState::kNotConnected:
41 return "not connected";
42 case Peer::ConnectionState::kInitializing:
43 return "connecting";
44 case Peer::ConnectionState::kConnected:
45 return "connected";
46 }
47
48 PW_CRASH("invalid connection state %u", static_cast<unsigned int>(state));
49 return "(unknown)";
50 }
51
NameSourceToString(Peer::NameSource name_source)52 std::string Peer::NameSourceToString(Peer::NameSource name_source) {
53 switch (name_source) {
54 case Peer::NameSource::kNameDiscoveryProcedure:
55 return "Name Discovery Procedure";
56 case Peer::NameSource::kAdvertisingDataComplete:
57 return "Advertising data (complete)";
58 case Peer::NameSource::kAdvertisingDataShortened:
59 return "Advertising data (shortened)";
60 case Peer::NameSource::kInquiryResultComplete:
61 return "Inquiry result (complete)";
62 case Peer::NameSource::kInquiryResultShortened:
63 return "Inquiry result (shortened)";
64 case Peer::NameSource::kGenericAccessService:
65 return "Generic Access Service";
66 case Peer::NameSource::kUnknown:
67 return "Unknown source";
68 }
69
70 PW_CRASH("invalid peer name source %u",
71 static_cast<unsigned int>(name_source));
72 return "(unknown)";
73 }
74
LowEnergyData(Peer * owner)75 Peer::LowEnergyData::LowEnergyData(Peer* owner)
76 : peer_(owner),
77 bond_data_(std::nullopt,
78 [](const std::optional<sm::PairingData>& p) {
79 return p.has_value();
80 }),
81 auto_conn_behavior_(AutoConnectBehavior::kAlways),
82 features_(std::nullopt,
__anonab3fe1f50302(const std::optional<hci_spec::LESupportedFeatures> f) 83 [](const std::optional<hci_spec::LESupportedFeatures> f) {
84 return f ? bt_lib_cpp_string::StringPrintf("%#.16" PRIx64, *f)
85 : "";
86 }),
87 service_changed_gatt_data_({.notify = false, .indicate = false}) {
88 PW_DCHECK(peer_);
89 }
90
AttachInspect(inspect::Node & parent,std::string name)91 void Peer::LowEnergyData::AttachInspect(inspect::Node& parent,
92 std::string name) {
93 node_ = parent.CreateChild(name);
94 inspect_properties_.connection_state =
95 node_.CreateString(LowEnergyData::kInspectConnectionStateName,
96 Peer::ConnectionStateToString(connection_state()));
97 inspect_properties_.last_adv_data_parse_failure = node_.CreateString(
98 LowEnergyData::kInspectLastAdvertisingDataParseFailureName, "");
99 adv_data_parse_failure_count_.AttachInspect(
100 node_, LowEnergyData::kInspectAdvertisingDataParseFailureCountName);
101 bond_data_.AttachInspect(node_, LowEnergyData::kInspectBondDataName);
102 features_.AttachInspect(node_, LowEnergyData::kInspectFeaturesName);
103 }
104
SetAdvertisingData(int8_t rssi,const ByteBuffer & data,pw::chrono::SystemClock::time_point timestamp)105 void Peer::LowEnergyData::SetAdvertisingData(
106 int8_t rssi,
107 const ByteBuffer& data,
108 pw::chrono::SystemClock::time_point timestamp) {
109 // Prolong this peer's expiration in case it is temporary.
110 peer_->UpdateExpiry();
111
112 peer_->SetRssiInternal(rssi);
113
114 AdvertisingData::ParseResult res = AdvertisingData::FromBytes(data);
115 if (!res.is_ok()) {
116 int64_t current_failure_count = *adv_data_parse_failure_count_;
117 adv_data_parse_failure_count_.Set(current_failure_count + 1);
118 inspect_properties_.last_adv_data_parse_failure.Set(
119 AdvertisingData::ParseErrorToString(res.error_value()));
120 std::string message = bt_lib_cpp_string::StringPrintf(
121 "failed to parse advertising data: %s (peer: %s)",
122 bt::AdvertisingData::ParseErrorToString(res.error_value()).c_str(),
123 bt_str(peer_->identifier()));
124 // To prevent log spam, we only log the first, and then every Nth failure to
125 // parse AdvertisingData from each peer at WARN level. Other failures are
126 // logged at DEBUG level.
127 if (*adv_data_parse_failure_count_ % kAdvDataParseFailureWarnLogInterval ==
128 1) {
129 bt_log(WARN, "gap-le", "%s", message.c_str());
130 } else {
131 bt_log(DEBUG, "gap-le", "%s", message.c_str());
132 }
133 // Update the error if we don't have a successful parse already
134 if (parsed_adv_data_.is_error()) {
135 parsed_adv_data_ = std::move(res);
136 }
137 } else {
138 // Update the advertising data
139 adv_data_buffer_ = DynamicByteBuffer(data.size());
140 data.Copy(&adv_data_buffer_);
141 parsed_adv_data_ = std::move(res);
142 // Only update the parsed_adv_timestamp if the AdvertisingData parsed
143 // successfully
144 parsed_adv_timestamp_ = timestamp;
145
146 // Do not update the name of bonded peers because advertisements are
147 // unauthenticated.
148 // TODO(fxbug.dev/42166256): Populate more Peer fields with relevant fields
149 // from parsed_adv_data_.
150 if (!peer_->bonded() && parsed_adv_data_->local_name().has_value()) {
151 peer_->RegisterNameInternal(
152 parsed_adv_data_->local_name()->name,
153 parsed_adv_data_->local_name()->is_complete
154 ? Peer::NameSource::kAdvertisingDataComplete
155 : Peer::NameSource::kAdvertisingDataShortened);
156 }
157 }
158
159 peer_->UpdatePeerAndNotifyListeners(NotifyListenersChange::kBondNotUpdated);
160 }
161
162 Peer::InitializingConnectionToken
RegisterInitializingConnection()163 Peer::LowEnergyData::RegisterInitializingConnection() {
164 ConnectionState prev_state = connection_state();
165 initializing_tokens_count_++;
166 OnConnectionStateMaybeChanged(prev_state);
167
168 auto unregister_cb = [self = peer_->GetWeakPtr(), this] {
169 if (!self.is_alive()) {
170 return;
171 }
172
173 ConnectionState conn_prev_state = connection_state();
174 initializing_tokens_count_--;
175 OnConnectionStateMaybeChanged(conn_prev_state);
176 };
177
178 return InitializingConnectionToken(std::move(unregister_cb));
179 }
180
RegisterConnection()181 Peer::ConnectionToken Peer::LowEnergyData::RegisterConnection() {
182 // The high-level connection state is the same whether one or many registered
183 // connections exist, but we track each connection in metrics to support
184 // multiple connections to the same peer.
185 peer_->peer_metrics_->LogLeConnection();
186
187 ConnectionState prev_state = connection_state();
188 connection_tokens_count_++;
189 OnConnectionStateMaybeChanged(prev_state);
190
191 auto unregister_cb = [self = peer_->GetWeakPtr(), this] {
192 if (!self.is_alive()) {
193 return;
194 }
195
196 connection_tokens_count_--;
197 peer_->peer_metrics_->LogLeDisconnection();
198 OnConnectionStateMaybeChanged(/*previous=*/ConnectionState::kConnected);
199 };
200
201 return ConnectionToken(std::move(unregister_cb));
202 }
203
RegisterPairing()204 Peer::PairingToken Peer::LowEnergyData::RegisterPairing() {
205 pairing_tokens_count_++;
206 auto unregister_cb = [self = peer_->GetWeakPtr(), this] {
207 if (!self.is_alive()) {
208 return;
209 }
210 pairing_tokens_count_--;
211 OnPairingMaybeComplete();
212 };
213 return PairingToken(std::move(unregister_cb));
214 }
215
is_pairing() const216 bool Peer::LowEnergyData::is_pairing() const {
217 return pairing_tokens_count_ > 0;
218 }
219
add_pairing_completion_callback(fit::callback<void ()> && callback)220 void Peer::LowEnergyData::add_pairing_completion_callback(
221 fit::callback<void()>&& callback) {
222 pairing_complete_callbacks_.emplace_back(std::move(callback));
223 OnPairingMaybeComplete();
224 }
225
OnPairingMaybeComplete()226 void Peer::LowEnergyData::OnPairingMaybeComplete() {
227 if (pairing_tokens_count_ > 0 || pairing_complete_callbacks_.empty()) {
228 return;
229 }
230 std::vector<fit::callback<void()>> callbacks;
231 std::swap(callbacks, pairing_complete_callbacks_);
232 for (auto& cb : callbacks) {
233 cb();
234 }
235 }
236
SetConnectionParameters(const hci_spec::LEConnectionParameters & params)237 void Peer::LowEnergyData::SetConnectionParameters(
238 const hci_spec::LEConnectionParameters& params) {
239 PW_DCHECK(peer_->connectable());
240 conn_params_ = params;
241 }
242
SetPreferredConnectionParameters(const hci_spec::LEPreferredConnectionParameters & params)243 void Peer::LowEnergyData::SetPreferredConnectionParameters(
244 const hci_spec::LEPreferredConnectionParameters& params) {
245 PW_DCHECK(peer_->connectable());
246 preferred_conn_params_ = params;
247 }
248
StoreBond(const sm::PairingData & bond_data)249 bool Peer::LowEnergyData::StoreBond(const sm::PairingData& bond_data) {
250 return peer_->store_le_bond_callback_(bond_data);
251 }
252
SetBondData(const sm::PairingData & bond_data)253 void Peer::LowEnergyData::SetBondData(const sm::PairingData& bond_data) {
254 PW_DCHECK(peer_->connectable());
255 PW_DCHECK(peer_->address().type() != DeviceAddress::Type::kLEAnonymous);
256
257 // TODO(fxbug.dev/42072204): Do not overwrite an existing key that has
258 // greater strength or authentication.
259
260 // Make sure the peer is non-temporary.
261 peer_->TryMakeNonTemporary();
262
263 // This will mark the peer as bonded
264 bond_data_.Set(bond_data);
265
266 // Update to the new identity address if the current address is random.
267 if (peer_->address().type() == DeviceAddress::Type::kLERandom &&
268 bond_data.identity_address) {
269 peer_->set_identity_known(true);
270 peer_->set_address(*bond_data.identity_address);
271 }
272
273 // PeerCache notifies listeners of new bonds, so no need to request that here.
274 peer_->UpdatePeerAndNotifyListeners(NotifyListenersChange::kBondNotUpdated);
275 }
276
ClearBondData()277 void Peer::LowEnergyData::ClearBondData() {
278 PW_CHECK(bond_data_->has_value());
279 if (bond_data_->value().irk) {
280 peer_->set_identity_known(false);
281 }
282 bond_data_.Set(std::nullopt);
283 }
284
OnConnectionStateMaybeChanged(ConnectionState previous)285 void Peer::LowEnergyData::OnConnectionStateMaybeChanged(
286 ConnectionState previous) {
287 if (connection_state() == previous) {
288 return;
289 }
290
291 bt_log(DEBUG,
292 "gap-le",
293 "peer (%s) LE connection state changed from %s to %s (initializing "
294 "count: %hu, "
295 "connection count: %hu)",
296 bt_str(peer_->identifier()),
297 ConnectionStateToString(previous).c_str(),
298 ConnectionStateToString(connection_state()).c_str(),
299 initializing_tokens_count_,
300 connection_tokens_count_);
301
302 inspect_properties_.connection_state.Set(
303 ConnectionStateToString(connection_state()));
304
305 if (previous == ConnectionState::kNotConnected) {
306 peer_->TryMakeNonTemporary();
307 } else if (connection_state() == ConnectionState::kNotConnected) {
308 peer_->TryMakeTemporary();
309 }
310
311 peer_->UpdateExpiry();
312 peer_->UpdatePeerAndNotifyListeners(NotifyListenersChange::kBondNotUpdated);
313 }
314
BrEdrData(Peer * owner)315 Peer::BrEdrData::BrEdrData(Peer* owner)
316 : peer_(owner), services_({}, MakeContainerOfToStringConvertFunction()) {
317 PW_DCHECK(peer_);
318 PW_DCHECK(peer_->identity_known());
319
320 // Devices that are capable of BR/EDR and use a LE random device address will
321 // end up with separate entries for the BR/EDR and LE addresses.
322 PW_DCHECK(peer_->address().type() != DeviceAddress::Type::kLERandom &&
323 peer_->address().type() != DeviceAddress::Type::kLEAnonymous);
324 address_ = {DeviceAddress::Type::kBREDR, peer_->address().value()};
325 }
326
AttachInspect(inspect::Node & parent,std::string name)327 void Peer::BrEdrData::AttachInspect(inspect::Node& parent, std::string name) {
328 node_ = parent.CreateChild(name);
329 inspect_properties_.connection_state =
330 node_.CreateString(BrEdrData::kInspectConnectionStateName,
331 ConnectionStateToString(connection_state()));
332
333 if (bonded()) {
334 link_key_.value().AttachInspect(node_, BrEdrData::kInspectLinkKeyName);
335 }
336 services_.AttachInspect(node_, BrEdrData::kInspectServicesName);
337 }
338
SetInquiryData(const pw::bluetooth::emboss::InquiryResultView & view)339 void Peer::BrEdrData::SetInquiryData(
340 const pw::bluetooth::emboss::InquiryResultView& view) {
341 PW_DCHECK(peer_->address().value() == DeviceAddressBytes{view.bd_addr()});
342 SetInquiryData(
343 DeviceClass(view.class_of_device().BackingStorage().ReadUInt()),
344 view.clock_offset().BackingStorage().ReadUInt(),
345 view.page_scan_repetition_mode().Read());
346 }
347
SetInquiryData(const pw::bluetooth::emboss::InquiryResultWithRssiView & view)348 void Peer::BrEdrData::SetInquiryData(
349 const pw::bluetooth::emboss::InquiryResultWithRssiView& view) {
350 PW_DCHECK(peer_->address().value() == DeviceAddressBytes{view.bd_addr()});
351 SetInquiryData(
352 DeviceClass(view.class_of_device().BackingStorage().ReadUInt()),
353 view.clock_offset().BackingStorage().ReadUInt(),
354 view.page_scan_repetition_mode().Read(),
355 view.rssi().Read());
356 }
357
SetInquiryData(const pw::bluetooth::emboss::ExtendedInquiryResultEventView & view)358 void Peer::BrEdrData::SetInquiryData(
359 const pw::bluetooth::emboss::ExtendedInquiryResultEventView& view) {
360 PW_DCHECK(peer_->address().value() == DeviceAddressBytes(view.bd_addr()));
361 const BufferView response_view(
362 view.extended_inquiry_response().BackingStorage().data(),
363 view.extended_inquiry_response().SizeInBytes());
364 SetInquiryData(
365 DeviceClass(view.class_of_device().BackingStorage().ReadUInt()),
366 view.clock_offset().BackingStorage().ReadUInt(),
367 view.page_scan_repetition_mode().Read(),
368 view.rssi().Read(),
369 response_view);
370 }
371
SetIncomingRequest(const pw::bluetooth::emboss::ConnectionRequestEventView & view)372 void Peer::BrEdrData::SetIncomingRequest(
373 const pw::bluetooth::emboss::ConnectionRequestEventView& view) {
374 device_class_ =
375 DeviceClass(view.class_of_device().BackingStorage().ReadUInt());
376 }
377
378 Peer::InitializingConnectionToken
RegisterInitializingConnection()379 Peer::BrEdrData::RegisterInitializingConnection() {
380 PW_CHECK(!connected());
381
382 ConnectionState prev_state = connection_state();
383 initializing_tokens_count_++;
384 OnConnectionStateMaybeChanged(prev_state);
385
386 return InitializingConnectionToken([self = peer_->GetWeakPtr(), this] {
387 if (!self.is_alive()) {
388 return;
389 }
390
391 ConnectionState conn_prev_state = connection_state();
392 initializing_tokens_count_--;
393 OnConnectionStateMaybeChanged(conn_prev_state);
394 });
395 }
396
RegisterConnection()397 Peer::ConnectionToken Peer::BrEdrData::RegisterConnection() {
398 PW_CHECK(!connected(),
399 "attempt to register BR/EDR connection when a connection is "
400 "already registered (peer: %s)",
401 bt_str(peer_->identifier()));
402
403 ConnectionState prev_state = connection_state();
404 connection_tokens_count_++;
405 OnConnectionStateMaybeChanged(prev_state);
406
407 return ConnectionToken([self = peer_->GetWeakPtr(), this] {
408 if (!self.is_alive()) {
409 return;
410 }
411
412 ConnectionState conn_prev_state = connection_state();
413 connection_tokens_count_--;
414 OnConnectionStateMaybeChanged(conn_prev_state);
415 });
416 }
417
RegisterPairing()418 Peer::PairingToken Peer::BrEdrData::RegisterPairing() {
419 pairing_tokens_count_++;
420 auto unregister_cb = [self = peer_->GetWeakPtr(), this] {
421 if (!self.is_alive()) {
422 return;
423 }
424 pairing_tokens_count_--;
425 OnPairingMaybeComplete();
426 };
427 return PairingToken(std::move(unregister_cb));
428 }
429
is_pairing() const430 bool Peer::BrEdrData::is_pairing() const { return pairing_tokens_count_ > 0; }
431
add_pairing_completion_callback(fit::callback<void ()> && callback)432 void Peer::BrEdrData::add_pairing_completion_callback(
433 fit::callback<void()>&& callback) {
434 pairing_complete_callbacks_.emplace_back(std::move(callback));
435 OnPairingMaybeComplete();
436 }
437
OnPairingMaybeComplete()438 void Peer::BrEdrData::OnPairingMaybeComplete() {
439 if (pairing_tokens_count_ > 0 || pairing_complete_callbacks_.empty()) {
440 return;
441 }
442 std::vector<fit::callback<void()>> callbacks;
443 std::swap(callbacks, pairing_complete_callbacks_);
444 for (auto& cb : callbacks) {
445 cb();
446 }
447 }
448
OnConnectionStateMaybeChanged(ConnectionState previous)449 void Peer::BrEdrData::OnConnectionStateMaybeChanged(ConnectionState previous) {
450 if (previous == connection_state()) {
451 return;
452 }
453
454 bt_log(DEBUG,
455 "gap-bredr",
456 "peer (%s) BR/EDR connection state changed from \"%s\" to \"%s\"",
457 bt_str(peer_->identifier()),
458 ConnectionStateToString(previous).c_str(),
459 ConnectionStateToString(connection_state()).c_str());
460 inspect_properties_.connection_state.Set(
461 ConnectionStateToString(connection_state()));
462
463 if (connection_state() == ConnectionState::kConnected) {
464 peer_->peer_metrics_->LogBrEdrConnection();
465 } else if (previous == ConnectionState::kConnected) {
466 peer_->peer_metrics_->LogBrEdrDisconnection();
467 }
468
469 peer_->UpdateExpiry();
470
471 // Transition to or from kConnected state is a notifyable change.
472 if (previous == ConnectionState::kConnected ||
473 connection_state() == ConnectionState::kConnected) {
474 peer_->UpdatePeerAndNotifyListeners(NotifyListenersChange::kBondNotUpdated);
475 }
476
477 // Become non-temporary if we successfully connect or are initializing. BR/EDR
478 // device remain non-temporary afterwards if bonded, and temporary again if
479 // disconnect without bonding.
480 if (connection_state() == ConnectionState::kNotConnected) {
481 peer_->TryMakeTemporary();
482 } else {
483 peer_->TryMakeNonTemporary();
484 }
485 }
486
SetInquiryData(DeviceClass device_class,uint16_t clock_offset,pw::bluetooth::emboss::PageScanRepetitionMode page_scan_rep_mode,int8_t rssi,const BufferView & eir_data)487 void Peer::BrEdrData::SetInquiryData(
488 DeviceClass device_class,
489 uint16_t clock_offset,
490 pw::bluetooth::emboss::PageScanRepetitionMode page_scan_rep_mode,
491 int8_t rssi,
492 const BufferView& eir_data) {
493 peer_->UpdateExpiry();
494
495 bool notify_listeners = false;
496
497 // TODO(armansito): Consider sending notifications for RSSI updates perhaps
498 // with throttling to avoid spamming.
499 peer_->SetRssiInternal(rssi);
500
501 page_scan_rep_mode_ = page_scan_rep_mode;
502 clock_offset_ =
503 pw::bytes::ConvertOrderFrom(cpp20::endian::little, clock_offset) &
504 hci_spec::kClockOffsetMask;
505
506 if (!device_class_ || *device_class_ != device_class) {
507 device_class_ = device_class;
508 notify_listeners = true;
509 }
510
511 if (eir_data.size() && SetEirData(eir_data)) {
512 notify_listeners = true;
513 }
514
515 peer_->OnPeerUpdate();
516
517 if (notify_listeners) {
518 peer_->NotifyListeners(NotifyListenersChange::kBondNotUpdated);
519 }
520 }
521
SetEirData(const ByteBuffer & eir)522 bool Peer::BrEdrData::SetEirData(const ByteBuffer& eir) {
523 PW_DCHECK(eir.size());
524
525 // TODO(armansito): Validate that the EIR data is not malformed?
526 SupplementDataReader reader(eir);
527 DataType type;
528 BufferView data;
529 bool changed = false;
530 while (reader.GetNextField(&type, &data)) {
531 if (type == DataType::kCompleteLocalName) {
532 // TODO(armansito): Parse more fields.
533 // Do not update the name of bonded peers because inquiry results are
534 // unauthenticated.
535 if (!peer_->bonded()) {
536 changed = peer_->RegisterNameInternal(
537 data.ToString(), Peer::NameSource::kInquiryResultComplete);
538 }
539 } else if (type == DataType::kIncomplete16BitServiceUuids ||
540 type == DataType::kComplete16BitServiceUuids) {
541 // TODO(fxbug.dev/42082102): Consider adding 32-bit and 128-bit UUIDs to
542 // the list
543 ParseUuids(
544 data, UUIDElemSize::k16Bit, [this, &changed](const UUID& uuid) {
545 auto [_, inserted] = services_.Mutable()->insert(uuid);
546 if (inserted) {
547 changed = true;
548 }
549 return true;
550 });
551 }
552 }
553 return changed;
554 }
555
SetBondData(const sm::LTK & link_key)556 bool Peer::BrEdrData::SetBondData(const sm::LTK& link_key) {
557 PW_DCHECK(peer_->connectable());
558
559 // Do not overwrite an existing key that has greater strength or
560 // authentication.
561 if (link_key_.has_value() &&
562 !link_key.security().IsAsSecureAs(link_key_->security())) {
563 return false;
564 }
565
566 // Make sure the peer is non-temporary.
567 peer_->TryMakeNonTemporary();
568
569 // Storing the key establishes the bond.
570 link_key_ = link_key;
571 link_key_.value().AttachInspect(node_, BrEdrData::kInspectLinkKeyName);
572
573 // PeerCache notifies listeners of new bonds, so no need to request that here.
574 peer_->UpdatePeerAndNotifyListeners(NotifyListenersChange::kBondNotUpdated);
575 return true;
576 }
577
ClearBondData()578 void Peer::BrEdrData::ClearBondData() {
579 PW_CHECK(link_key_.has_value());
580 link_key_ = std::nullopt;
581 }
582
AddService(UUID uuid)583 void Peer::BrEdrData::AddService(UUID uuid) {
584 auto [_, inserted] = services_.Mutable()->insert(uuid);
585 if (inserted) {
586 auto update_bond = bonded() ? NotifyListenersChange::kBondUpdated
587 : NotifyListenersChange::kBondNotUpdated;
588 peer_->UpdatePeerAndNotifyListeners(update_bond);
589 }
590 }
591
Peer(NotifyListenersCallback notify_listeners_callback,PeerCallback update_expiry_callback,PeerCallback dual_mode_callback,StoreLowEnergyBondCallback store_le_bond_callback,PeerId identifier,const DeviceAddress & address,bool connectable,PeerMetrics * peer_metrics,pw::async::Dispatcher & dispatcher)592 Peer::Peer(NotifyListenersCallback notify_listeners_callback,
593 PeerCallback update_expiry_callback,
594 PeerCallback dual_mode_callback,
595 StoreLowEnergyBondCallback store_le_bond_callback,
596 PeerId identifier,
597 const DeviceAddress& address,
598 bool connectable,
599 PeerMetrics* peer_metrics,
600 pw::async::Dispatcher& dispatcher)
601 : notify_listeners_callback_(std::move(notify_listeners_callback)),
602 update_expiry_callback_(std::move(update_expiry_callback)),
603 dual_mode_callback_(std::move(dual_mode_callback)),
604 store_le_bond_callback_(std::move(store_le_bond_callback)),
605 identifier_(identifier, MakeToStringInspectConvertFunction()),
606 technology_((address.type() == DeviceAddress::Type::kBREDR)
607 ? TechnologyType::kClassic
608 : TechnologyType::kLowEnergy,
609 [](TechnologyType t) { return TechnologyTypeToString(t); }),
610 address_(address, MakeToStringInspectConvertFunction()),
611 identity_known_(false),
612 name_(std::nullopt,
__anonab3fe1f50c02(const std::optional<PeerName>& v) 613 [](const std::optional<PeerName>& v) {
614 return v ? v->name +
615 " [source: " + NameSourceToString(v->source) + "]"
616 : "";
617 }),
618 lmp_version_(std::nullopt,
619 [](const std::optional<
__anonab3fe1f50d02(const std::optional< pw::bluetooth::emboss::CoreSpecificationVersion>& v) 620 pw::bluetooth::emboss::CoreSpecificationVersion>& v) {
621 return v ? hci_spec::HCIVersionToString(*v) : "";
622 }),
623 lmp_manufacturer_(std::nullopt,
__anonab3fe1f50e02(const std::optional<uint16_t>& m) 624 [](const std::optional<uint16_t>& m) {
625 return m ? GetManufacturerName(*m) : "";
626 }),
627 lmp_features_(hci_spec::LMPFeatureSet(),
628 MakeToStringInspectConvertFunction()),
629 connectable_(connectable),
630 temporary_(true),
631 rssi_(hci_spec::kRSSIInvalid),
632 peer_metrics_(peer_metrics),
633 last_updated_(dispatcher.now()),
634 dispatcher_(dispatcher),
635 weak_self_(this) {
636 PW_DCHECK(notify_listeners_callback_);
637 PW_DCHECK(update_expiry_callback_);
638 PW_DCHECK(dual_mode_callback_);
639 PW_DCHECK(identifier.IsValid());
640
641 if (address.type() == DeviceAddress::Type::kBREDR ||
642 address.type() == DeviceAddress::Type::kLEPublic) {
643 identity_known_ = true;
644 }
645
646 // Initialize transport-specific state.
647 if (*technology_ == TechnologyType::kClassic) {
648 bredr_data_ = BrEdrData(this);
649 } else {
650 le_data_ = LowEnergyData(this);
651 }
652 }
653
AttachInspect(inspect::Node & parent,std::string node_name)654 void Peer::AttachInspect(inspect::Node& parent, std::string node_name) {
655 node_ = parent.CreateChild(node_name);
656 identifier_.AttachInspect(node_, kInspectPeerIdName);
657 technology_.AttachInspect(node_, kInspectTechnologyName);
658 address_.AttachInspect(node_, kInspectAddressName);
659 name_.AttachInspect(node_, kInspectPeerNameName);
660 lmp_version_.AttachInspect(node_, kInspectVersionName);
661 lmp_manufacturer_.AttachInspect(node_, kInspectManufacturerName);
662 lmp_features_.AttachInspect(node_, kInspectFeaturesName);
663 connectable_.AttachInspect(node_, kInspectConnectableName);
664 temporary_.AttachInspect(node_, kInspectTemporaryName);
665
666 if (bredr_data_) {
667 bredr_data_->AttachInspect(node_, Peer::BrEdrData::kInspectNodeName);
668 }
669 if (le_data_) {
670 le_data_->AttachInspect(node_, Peer::LowEnergyData::kInspectNodeName);
671 }
672 }
673
MutLe()674 Peer::LowEnergyData& Peer::MutLe() {
675 if (le_data_) {
676 return *le_data_;
677 }
678
679 le_data_ = LowEnergyData(this);
680 le_data_->AttachInspect(node_);
681
682 // Make dual-mode if both transport states have been initialized.
683 if (bredr_data_) {
684 MakeDualMode();
685 }
686 return *le_data_;
687 }
688
MutBrEdr()689 Peer::BrEdrData& Peer::MutBrEdr() {
690 if (bredr_data_) {
691 return *bredr_data_;
692 }
693
694 bredr_data_ = BrEdrData(this);
695 bredr_data_->AttachInspect(node_);
696
697 // Make dual-mode if both transport states have been initialized.
698 if (le_data_) {
699 MakeDualMode();
700 }
701 return *bredr_data_;
702 }
703
ToString() const704 std::string Peer::ToString() const {
705 return bt_lib_cpp_string::StringPrintf(
706 "{peer id: %s, address: %s}", bt_str(*identifier_), bt_str(*address_));
707 }
708
RegisterName(const std::string & name,Peer::NameSource source)709 bool Peer::RegisterName(const std::string& name, Peer::NameSource source) {
710 if (RegisterNameInternal(name, source)) {
711 UpdateExpiry();
712 // TODO(fxbug.dev/42140058): Update the bond when this happens
713 UpdatePeerAndNotifyListeners(NotifyListenersChange::kBondNotUpdated);
714 return true;
715 }
716 return false;
717 }
718
719 // Private methods below:
720
SetRssiInternal(int8_t rssi)721 bool Peer::SetRssiInternal(int8_t rssi) {
722 if (rssi != hci_spec::kRSSIInvalid && rssi_ != rssi) {
723 rssi_ = rssi;
724 return true;
725 }
726 return false;
727 }
728
RegisterNameInternal(const std::string & name,Peer::NameSource source)729 bool Peer::RegisterNameInternal(const std::string& name,
730 Peer::NameSource source) {
731 if (!pw::utf8::IsStringValid(name)) {
732 bt_log(WARN,
733 "gap",
734 "%s: not setting name to string that is not valid UTF-8",
735 bt_str(*this));
736 return false;
737 }
738 if (!name_->has_value() || source < (*name_)->source ||
739 (source == (*name_)->source && name != (*name_)->name)) {
740 name_.Set(Peer::PeerName{name, source});
741 return true;
742 }
743 return false;
744 }
745
TryMakeNonTemporary()746 bool Peer::TryMakeNonTemporary() {
747 // TODO(armansito): Since we don't currently support address resolution,
748 // random addresses should never be persisted.
749 if (!connectable()) {
750 bt_log(DEBUG, "gap", "remains temporary: %s", bt_str(*this));
751 return false;
752 }
753
754 bt_log(DEBUG, "gap", "became non-temporary: %s:", bt_str(*this));
755
756 if (*temporary_) {
757 temporary_.Set(false);
758 UpdateExpiry();
759 UpdatePeerAndNotifyListeners(NotifyListenersChange::kBondNotUpdated);
760 }
761
762 return true;
763 }
764
TryMakeTemporary()765 bool Peer::TryMakeTemporary() {
766 if (le() && le()->connection_state() == ConnectionState::kNotConnected &&
767 !identity_known()) {
768 bt_log(DEBUG, "gap", "LE became temporary: %s:", bt_str(*this));
769 temporary_.Set(true);
770 return true;
771 }
772 if (bredr() && !bredr()->bonded()) {
773 bt_log(DEBUG, "gap", "BR/EDR became temporary: %s:", bt_str(*this));
774 temporary_.Set(true);
775 return true;
776 }
777 return false;
778 }
779
UpdateExpiry()780 void Peer::UpdateExpiry() {
781 PW_DCHECK(update_expiry_callback_);
782 update_expiry_callback_(*this);
783 }
784
NotifyListeners(NotifyListenersChange change)785 void Peer::NotifyListeners(NotifyListenersChange change) {
786 PW_DCHECK(notify_listeners_callback_);
787 notify_listeners_callback_(*this, change);
788 }
789
MakeDualMode()790 void Peer::MakeDualMode() {
791 technology_.Set(TechnologyType::kDualMode);
792 PW_DCHECK(dual_mode_callback_);
793 dual_mode_callback_(*this);
794 }
795
OnPeerUpdate()796 void Peer::OnPeerUpdate() { last_updated_ = dispatcher_.now(); }
797
UpdatePeerAndNotifyListeners(NotifyListenersChange change)798 void Peer::UpdatePeerAndNotifyListeners(NotifyListenersChange change) {
799 OnPeerUpdate();
800 NotifyListeners(change);
801 }
802
803 } // namespace bt::gap
804