| // Copyright 2023 The Pigweed Authors |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); you may not |
| // use this file except in compliance with the License. You may obtain a copy of |
| // the License at |
| // |
| // https://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
| // License for the specific language governing permissions and limitations under |
| // the License. |
| |
| #include "pw_bluetooth_sapphire/internal/host/gap/peer.h" |
| |
| #include <cpp-string/string_printf.h> |
| #include <cpp-string/utf_codecs.h> |
| |
| #include "pw_bluetooth_sapphire/internal/host/common/advertising_data.h" |
| #include "pw_bluetooth_sapphire/internal/host/common/assert.h" |
| #include "pw_bluetooth_sapphire/internal/host/common/manufacturer_names.h" |
| #include "pw_bluetooth_sapphire/internal/host/common/uuid.h" |
| #include "pw_bluetooth_sapphire/internal/host/gap/gap.h" |
| #include "pw_bluetooth_sapphire/internal/host/hci-spec/util.h" |
| #include "pw_bluetooth_sapphire/internal/host/hci/low_energy_scanner.h" |
| #include "pw_bluetooth_sapphire/internal/host/sm/types.h" |
| |
| namespace bt::gap { |
| namespace { |
| // To prevent log spam, we only log every Nth failure to parse AdvertisingData |
| // from each peer. This value controls N. |
| const int64_t kAdvDataParseFailureWarnLogInterval = 25; |
| } // namespace |
| |
| std::string Peer::ConnectionStateToString(Peer::ConnectionState state) { |
| switch (state) { |
| case Peer::ConnectionState::kNotConnected: |
| return "not connected"; |
| case Peer::ConnectionState::kInitializing: |
| return "connecting"; |
| case Peer::ConnectionState::kConnected: |
| return "connected"; |
| } |
| |
| BT_PANIC("invalid connection state %u", static_cast<unsigned int>(state)); |
| return "(unknown)"; |
| } |
| |
| std::string Peer::NameSourceToString(Peer::NameSource name_source) { |
| switch (name_source) { |
| case Peer::NameSource::kNameDiscoveryProcedure: |
| return "Name Discovery Procedure"; |
| case Peer::NameSource::kAdvertisingDataComplete: |
| return "Advertising data (complete)"; |
| case Peer::NameSource::kAdvertisingDataShortened: |
| return "Advertising data (shortened)"; |
| case Peer::NameSource::kInquiryResultComplete: |
| return "Inquiry result (complete)"; |
| case Peer::NameSource::kInquiryResultShortened: |
| return "Inquiry result (shortened)"; |
| case Peer::NameSource::kGenericAccessService: |
| return "Generic Access Service"; |
| case Peer::NameSource::kUnknown: |
| return "Unknown source"; |
| } |
| |
| BT_PANIC("invalid peer name source %u", |
| static_cast<unsigned int>(name_source)); |
| return "(unknown)"; |
| } |
| |
| Peer::LowEnergyData::LowEnergyData(Peer* owner) |
| : peer_(owner), |
| bond_data_(std::nullopt, |
| [](const std::optional<sm::PairingData>& p) { |
| return p.has_value(); |
| }), |
| auto_conn_behavior_(AutoConnectBehavior::kAlways), |
| features_(std::nullopt, |
| [](const std::optional<hci_spec::LESupportedFeatures> f) { |
| return f ? bt_lib_cpp_string::StringPrintf("%#.16lx", |
| f->le_features) |
| : ""; |
| }), |
| service_changed_gatt_data_({.notify = false, .indicate = false}) { |
| BT_DEBUG_ASSERT(peer_); |
| } |
| |
| void Peer::LowEnergyData::AttachInspect(inspect::Node& parent, |
| std::string name) { |
| node_ = parent.CreateChild(name); |
| inspect_properties_.connection_state = |
| node_.CreateString(LowEnergyData::kInspectConnectionStateName, |
| Peer::ConnectionStateToString(connection_state())); |
| inspect_properties_.last_adv_data_parse_failure = node_.CreateString( |
| LowEnergyData::kInspectLastAdvertisingDataParseFailureName, ""); |
| adv_data_parse_failure_count_.AttachInspect( |
| node_, LowEnergyData::kInspectAdvertisingDataParseFailureCountName); |
| bond_data_.AttachInspect(node_, LowEnergyData::kInspectBondDataName); |
| features_.AttachInspect(node_, LowEnergyData::kInspectFeaturesName); |
| } |
| |
| void Peer::LowEnergyData::SetAdvertisingData( |
| int8_t rssi, |
| const ByteBuffer& data, |
| pw::chrono::SystemClock::time_point timestamp) { |
| // Prolong this peer's expiration in case it is temporary. |
| peer_->UpdateExpiry(); |
| |
| peer_->SetRssiInternal(rssi); |
| |
| // Update the advertising data |
| adv_data_buffer_ = DynamicByteBuffer(data.size()); |
| data.Copy(&adv_data_buffer_); |
| AdvertisingData::ParseResult res = |
| AdvertisingData::FromBytes(adv_data_buffer_); |
| if (!res.is_ok()) { |
| int64_t current_failure_count = *adv_data_parse_failure_count_; |
| adv_data_parse_failure_count_.Set(current_failure_count + 1); |
| inspect_properties_.last_adv_data_parse_failure.Set( |
| AdvertisingData::ParseErrorToString(res.error_value())); |
| std::string message = bt_lib_cpp_string::StringPrintf( |
| "failed to parse advertising data: %s (peer: %s)", |
| bt::AdvertisingData::ParseErrorToString(res.error_value()).c_str(), |
| bt_str(peer_->identifier())); |
| // To prevent log spam, we only log the first, and then every Nth failure to |
| // parse AdvertisingData from each peer at WARN level. Other failures are |
| // logged at DEBUG level. |
| if (*adv_data_parse_failure_count_ % kAdvDataParseFailureWarnLogInterval == |
| 1) { |
| bt_log(WARN, "gap-le", "%s", message.c_str()); |
| } else { |
| bt_log(DEBUG, "gap-le", "%s", message.c_str()); |
| } |
| // Update the error if we don't have a successful parse already |
| if (parsed_adv_data_.is_error()) { |
| parsed_adv_data_ = std::move(res); |
| } |
| } else { |
| // Only update the adv_timestamp if the AdvertisingData parsed successfully |
| adv_timestamp_ = timestamp; |
| parsed_adv_data_ = std::move(res); |
| |
| // Do not update the name of bonded peers because advertisements are |
| // unauthenticated. |
| // TODO(fxbug.dev/42166256): Populate more Peer fields with relevant fields |
| // from parsed_adv_data_. |
| if (!peer_->bonded() && parsed_adv_data_->local_name().has_value()) { |
| peer_->RegisterNameInternal( |
| parsed_adv_data_->local_name()->name, |
| parsed_adv_data_->local_name()->is_complete |
| ? Peer::NameSource::kAdvertisingDataComplete |
| : Peer::NameSource::kAdvertisingDataShortened); |
| } |
| } |
| |
| peer_->UpdatePeerAndNotifyListeners(NotifyListenersChange::kBondNotUpdated); |
| } |
| |
| Peer::InitializingConnectionToken |
| Peer::LowEnergyData::RegisterInitializingConnection() { |
| ConnectionState prev_state = connection_state(); |
| initializing_tokens_count_++; |
| OnConnectionStateMaybeChanged(prev_state); |
| |
| auto unregister_cb = [self = peer_->GetWeakPtr(), this] { |
| if (!self.is_alive()) { |
| return; |
| } |
| |
| ConnectionState prev_state = connection_state(); |
| initializing_tokens_count_--; |
| OnConnectionStateMaybeChanged(prev_state); |
| }; |
| |
| return InitializingConnectionToken(std::move(unregister_cb)); |
| } |
| |
| Peer::ConnectionToken Peer::LowEnergyData::RegisterConnection() { |
| // The high-level connection state is the same whether one or many registered |
| // connections exist, but we track each connection in metrics to support |
| // multiple connections to the same peer. |
| peer_->peer_metrics_->LogLeConnection(); |
| |
| ConnectionState prev_state = connection_state(); |
| connection_tokens_count_++; |
| OnConnectionStateMaybeChanged(prev_state); |
| |
| auto unregister_cb = [self = peer_->GetWeakPtr(), this] { |
| if (!self.is_alive()) { |
| return; |
| } |
| |
| connection_tokens_count_--; |
| peer_->peer_metrics_->LogLeDisconnection(); |
| OnConnectionStateMaybeChanged(/*previous=*/ConnectionState::kConnected); |
| }; |
| |
| return ConnectionToken(std::move(unregister_cb)); |
| } |
| |
| void Peer::LowEnergyData::SetConnectionParameters( |
| const hci_spec::LEConnectionParameters& params) { |
| BT_DEBUG_ASSERT(peer_->connectable()); |
| conn_params_ = params; |
| } |
| |
| void Peer::LowEnergyData::SetPreferredConnectionParameters( |
| const hci_spec::LEPreferredConnectionParameters& params) { |
| BT_DEBUG_ASSERT(peer_->connectable()); |
| preferred_conn_params_ = params; |
| } |
| |
| bool Peer::LowEnergyData::StoreBond(const sm::PairingData& bond_data) { |
| return peer_->store_le_bond_callback_(bond_data); |
| } |
| |
| void Peer::LowEnergyData::SetBondData(const sm::PairingData& bond_data) { |
| BT_DEBUG_ASSERT(peer_->connectable()); |
| BT_DEBUG_ASSERT(peer_->address().type() != DeviceAddress::Type::kLEAnonymous); |
| |
| // Make sure the peer is non-temporary. |
| peer_->TryMakeNonTemporary(); |
| |
| // This will mark the peer as bonded |
| bond_data_.Set(bond_data); |
| |
| // Update to the new identity address if the current address is random. |
| if (peer_->address().type() == DeviceAddress::Type::kLERandom && |
| bond_data.identity_address) { |
| peer_->set_identity_known(true); |
| peer_->set_address(*bond_data.identity_address); |
| } |
| |
| // PeerCache notifies listeners of new bonds, so no need to request that here. |
| peer_->UpdatePeerAndNotifyListeners(NotifyListenersChange::kBondNotUpdated); |
| } |
| |
| void Peer::LowEnergyData::ClearBondData() { |
| BT_ASSERT(bond_data_->has_value()); |
| if (bond_data_->value().irk) { |
| peer_->set_identity_known(false); |
| } |
| bond_data_.Set(std::nullopt); |
| } |
| |
| void Peer::LowEnergyData::OnConnectionStateMaybeChanged( |
| ConnectionState previous) { |
| if (connection_state() == previous) { |
| return; |
| } |
| |
| bt_log(DEBUG, |
| "gap-le", |
| "peer (%s) LE connection state changed from %s to %s (initializing " |
| "count: %hu, " |
| "connection count: %hu)", |
| bt_str(peer_->identifier()), |
| ConnectionStateToString(previous).c_str(), |
| ConnectionStateToString(connection_state()).c_str(), |
| initializing_tokens_count_, |
| connection_tokens_count_); |
| |
| inspect_properties_.connection_state.Set( |
| ConnectionStateToString(connection_state())); |
| |
| if (previous == ConnectionState::kNotConnected) { |
| peer_->TryMakeNonTemporary(); |
| } else if (connection_state() == ConnectionState::kNotConnected) { |
| peer_->TryMakeTemporary(); |
| } |
| |
| peer_->UpdateExpiry(); |
| peer_->UpdatePeerAndNotifyListeners(NotifyListenersChange::kBondNotUpdated); |
| } |
| |
| Peer::BrEdrData::BrEdrData(Peer* owner) |
| : peer_(owner), services_({}, MakeContainerOfToStringConvertFunction()) { |
| BT_DEBUG_ASSERT(peer_); |
| BT_DEBUG_ASSERT(peer_->identity_known()); |
| |
| // Devices that are capable of BR/EDR and use a LE random device address will |
| // end up with separate entries for the BR/EDR and LE addresses. |
| BT_DEBUG_ASSERT(peer_->address().type() != DeviceAddress::Type::kLERandom && |
| peer_->address().type() != DeviceAddress::Type::kLEAnonymous); |
| address_ = {DeviceAddress::Type::kBREDR, peer_->address().value()}; |
| } |
| |
| void Peer::BrEdrData::AttachInspect(inspect::Node& parent, std::string name) { |
| node_ = parent.CreateChild(name); |
| inspect_properties_.connection_state = |
| node_.CreateString(BrEdrData::kInspectConnectionStateName, |
| ConnectionStateToString(connection_state())); |
| |
| if (bonded()) { |
| link_key_.value().AttachInspect(node_, BrEdrData::kInspectLinkKeyName); |
| } |
| services_.AttachInspect(node_, BrEdrData::kInspectServicesName); |
| } |
| |
| void Peer::BrEdrData::SetInquiryData( |
| const pw::bluetooth::emboss::InquiryResultView& view) { |
| BT_DEBUG_ASSERT(peer_->address().value() == |
| DeviceAddressBytes{view.bd_addr()}); |
| SetInquiryData( |
| DeviceClass(view.class_of_device().BackingStorage().ReadUInt()), |
| view.clock_offset().BackingStorage().ReadUInt(), |
| view.page_scan_repetition_mode().Read()); |
| } |
| |
| void Peer::BrEdrData::SetInquiryData( |
| const pw::bluetooth::emboss::InquiryResultWithRssiView& view) { |
| BT_DEBUG_ASSERT(peer_->address().value() == |
| DeviceAddressBytes{view.bd_addr()}); |
| SetInquiryData( |
| DeviceClass(view.class_of_device().BackingStorage().ReadUInt()), |
| view.clock_offset().BackingStorage().ReadUInt(), |
| view.page_scan_repetition_mode().Read(), |
| view.rssi().Read()); |
| } |
| |
| void Peer::BrEdrData::SetInquiryData( |
| const pw::bluetooth::emboss::ExtendedInquiryResultEventView& view) { |
| BT_DEBUG_ASSERT(peer_->address().value() == |
| DeviceAddressBytes(view.bd_addr())); |
| const BufferView response_view( |
| view.extended_inquiry_response().BackingStorage().data(), |
| view.extended_inquiry_response().SizeInBytes()); |
| SetInquiryData( |
| DeviceClass(view.class_of_device().BackingStorage().ReadUInt()), |
| view.clock_offset().BackingStorage().ReadUInt(), |
| view.page_scan_repetition_mode().Read(), |
| view.rssi().Read(), |
| response_view); |
| } |
| |
| Peer::InitializingConnectionToken |
| Peer::BrEdrData::RegisterInitializingConnection() { |
| BT_ASSERT(!connected()); |
| |
| ConnectionState prev_state = connection_state(); |
| initializing_tokens_count_++; |
| OnConnectionStateMaybeChanged(prev_state); |
| |
| return InitializingConnectionToken([self = peer_->GetWeakPtr(), this] { |
| if (!self.is_alive()) { |
| return; |
| } |
| |
| ConnectionState prev_state = connection_state(); |
| initializing_tokens_count_--; |
| OnConnectionStateMaybeChanged(prev_state); |
| }); |
| } |
| |
| Peer::ConnectionToken Peer::BrEdrData::RegisterConnection() { |
| BT_ASSERT_MSG(!connected(), |
| "attempt to register BR/EDR connection when a connection is " |
| "already registered (peer: %s)", |
| bt_str(peer_->identifier())); |
| |
| ConnectionState prev_state = connection_state(); |
| connection_tokens_count_++; |
| OnConnectionStateMaybeChanged(prev_state); |
| |
| return ConnectionToken([self = peer_->GetWeakPtr(), this] { |
| if (!self.is_alive()) { |
| return; |
| } |
| |
| ConnectionState prev_state = connection_state(); |
| connection_tokens_count_--; |
| OnConnectionStateMaybeChanged(prev_state); |
| }); |
| } |
| |
| void Peer::BrEdrData::OnConnectionStateMaybeChanged(ConnectionState previous) { |
| if (previous == connection_state()) { |
| return; |
| } |
| |
| bt_log(DEBUG, |
| "gap-bredr", |
| "peer (%s) BR/EDR connection state changed from \"%s\" to \"%s\"", |
| bt_str(peer_->identifier()), |
| ConnectionStateToString(previous).c_str(), |
| ConnectionStateToString(connection_state()).c_str()); |
| inspect_properties_.connection_state.Set( |
| ConnectionStateToString(connection_state())); |
| |
| if (connection_state() == ConnectionState::kConnected) { |
| peer_->peer_metrics_->LogBrEdrConnection(); |
| } else if (previous == ConnectionState::kConnected) { |
| peer_->peer_metrics_->LogBrEdrDisconnection(); |
| } |
| |
| peer_->UpdateExpiry(); |
| |
| // Transition to or from kConnected state is a notifyable change. |
| if (previous == ConnectionState::kConnected || |
| connection_state() == ConnectionState::kConnected) { |
| peer_->UpdatePeerAndNotifyListeners(NotifyListenersChange::kBondNotUpdated); |
| } |
| |
| // Become non-temporary if we successfully connect or are initializing. BR/EDR |
| // device remain non-temporary afterwards if bonded, and temporary again if |
| // disconnect without bonding. |
| if (connection_state() == ConnectionState::kNotConnected) { |
| peer_->TryMakeTemporary(); |
| } else { |
| peer_->TryMakeNonTemporary(); |
| } |
| } |
| |
| void Peer::BrEdrData::SetInquiryData( |
| DeviceClass device_class, |
| uint16_t clock_offset, |
| pw::bluetooth::emboss::PageScanRepetitionMode page_scan_rep_mode, |
| int8_t rssi, |
| const BufferView& eir_data) { |
| peer_->UpdateExpiry(); |
| |
| bool notify_listeners = false; |
| |
| // TODO(armansito): Consider sending notifications for RSSI updates perhaps |
| // with throttling to avoid spamming. |
| peer_->SetRssiInternal(rssi); |
| |
| page_scan_rep_mode_ = page_scan_rep_mode; |
| clock_offset_ = le16toh(clock_offset) & hci_spec::kClockOffsetMask; |
| |
| if (!device_class_ || *device_class_ != device_class) { |
| device_class_ = device_class; |
| notify_listeners = true; |
| } |
| |
| if (eir_data.size() && SetEirData(eir_data)) { |
| notify_listeners = true; |
| } |
| |
| peer_->OnPeerUpdate(); |
| |
| if (notify_listeners) { |
| peer_->NotifyListeners(NotifyListenersChange::kBondNotUpdated); |
| } |
| } |
| |
| bool Peer::BrEdrData::SetEirData(const ByteBuffer& eir) { |
| BT_DEBUG_ASSERT(eir.size()); |
| |
| // TODO(armansito): Validate that the EIR data is not malformed? |
| SupplementDataReader reader(eir); |
| DataType type; |
| BufferView data; |
| bool changed = false; |
| while (reader.GetNextField(&type, &data)) { |
| if (type == DataType::kCompleteLocalName) { |
| // TODO(armansito): Parse more fields. |
| // Do not update the name of bonded peers because inquiry results are |
| // unauthenticated. |
| if (!peer_->bonded()) { |
| changed = peer_->RegisterNameInternal( |
| data.ToString(), Peer::NameSource::kInquiryResultComplete); |
| } |
| } else if (type == DataType::kIncomplete16BitServiceUuids || |
| type == DataType::kComplete16BitServiceUuids) { |
| // TODO(fxbug.dev/42082102): Consider adding 32-bit and 128-bit UUIDs to |
| // the list |
| ParseUuids( |
| data, UUIDElemSize::k16Bit, [this, &changed](const UUID& uuid) { |
| auto [_, inserted] = services_.Mutable()->insert(uuid); |
| if (inserted) { |
| changed = true; |
| } |
| return true; |
| }); |
| } |
| } |
| return changed; |
| } |
| |
| void Peer::BrEdrData::SetBondData(const sm::LTK& link_key) { |
| BT_DEBUG_ASSERT(peer_->connectable()); |
| |
| // Make sure the peer is non-temporary. |
| peer_->TryMakeNonTemporary(); |
| |
| // Storing the key establishes the bond. |
| link_key_ = link_key; |
| link_key_.value().AttachInspect(node_, BrEdrData::kInspectLinkKeyName); |
| |
| // PeerCache notifies listeners of new bonds, so no need to request that here. |
| peer_->UpdatePeerAndNotifyListeners(NotifyListenersChange::kBondNotUpdated); |
| } |
| |
| void Peer::BrEdrData::ClearBondData() { |
| BT_ASSERT(link_key_.has_value()); |
| link_key_ = std::nullopt; |
| } |
| |
| void Peer::BrEdrData::AddService(UUID uuid) { |
| auto [_, inserted] = services_.Mutable()->insert(uuid); |
| if (inserted) { |
| auto update_bond = bonded() ? NotifyListenersChange::kBondUpdated |
| : NotifyListenersChange::kBondNotUpdated; |
| peer_->UpdatePeerAndNotifyListeners(update_bond); |
| } |
| } |
| |
| Peer::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) |
| : notify_listeners_callback_(std::move(notify_listeners_callback)), |
| update_expiry_callback_(std::move(update_expiry_callback)), |
| dual_mode_callback_(std::move(dual_mode_callback)), |
| store_le_bond_callback_(std::move(store_le_bond_callback)), |
| identifier_(identifier, MakeToStringInspectConvertFunction()), |
| technology_((address.type() == DeviceAddress::Type::kBREDR) |
| ? TechnologyType::kClassic |
| : TechnologyType::kLowEnergy, |
| [](TechnologyType t) { return TechnologyTypeToString(t); }), |
| address_(address, MakeToStringInspectConvertFunction()), |
| identity_known_(false), |
| name_(std::nullopt, |
| [](const std::optional<PeerName>& v) { |
| return v ? v->name + |
| " [source: " + NameSourceToString(v->source) + "]" |
| : ""; |
| }), |
| lmp_version_(std::nullopt, |
| [](const std::optional< |
| pw::bluetooth::emboss::CoreSpecificationVersion>& v) { |
| return v ? hci_spec::HCIVersionToString(*v) : ""; |
| }), |
| lmp_manufacturer_(std::nullopt, |
| [](const std::optional<uint16_t>& m) { |
| return m ? GetManufacturerName(*m) : ""; |
| }), |
| lmp_features_(hci_spec::LMPFeatureSet(), |
| MakeToStringInspectConvertFunction()), |
| connectable_(connectable), |
| temporary_(true), |
| rssi_(hci_spec::kRSSIInvalid), |
| peer_metrics_(peer_metrics), |
| last_updated_(dispatcher.now()), |
| dispatcher_(dispatcher), |
| weak_self_(this) { |
| BT_DEBUG_ASSERT(notify_listeners_callback_); |
| BT_DEBUG_ASSERT(update_expiry_callback_); |
| BT_DEBUG_ASSERT(dual_mode_callback_); |
| BT_DEBUG_ASSERT(identifier.IsValid()); |
| |
| if (address.type() == DeviceAddress::Type::kBREDR || |
| address.type() == DeviceAddress::Type::kLEPublic) { |
| identity_known_ = true; |
| } |
| |
| // Initialize transport-specific state. |
| if (*technology_ == TechnologyType::kClassic) { |
| bredr_data_ = BrEdrData(this); |
| } else { |
| le_data_ = LowEnergyData(this); |
| } |
| } |
| |
| void Peer::AttachInspect(inspect::Node& parent, std::string node_name) { |
| node_ = parent.CreateChild(node_name); |
| identifier_.AttachInspect(node_, kInspectPeerIdName); |
| technology_.AttachInspect(node_, kInspectTechnologyName); |
| address_.AttachInspect(node_, kInspectAddressName); |
| name_.AttachInspect(node_, kInspectPeerNameName); |
| lmp_version_.AttachInspect(node_, kInspectVersionName); |
| lmp_manufacturer_.AttachInspect(node_, kInspectManufacturerName); |
| lmp_features_.AttachInspect(node_, kInspectFeaturesName); |
| connectable_.AttachInspect(node_, kInspectConnectableName); |
| temporary_.AttachInspect(node_, kInspectTemporaryName); |
| |
| if (bredr_data_) { |
| bredr_data_->AttachInspect(node_, Peer::BrEdrData::kInspectNodeName); |
| } |
| if (le_data_) { |
| le_data_->AttachInspect(node_, Peer::LowEnergyData::kInspectNodeName); |
| } |
| } |
| |
| Peer::LowEnergyData& Peer::MutLe() { |
| if (le_data_) { |
| return *le_data_; |
| } |
| |
| le_data_ = LowEnergyData(this); |
| le_data_->AttachInspect(node_); |
| |
| // Make dual-mode if both transport states have been initialized. |
| if (bredr_data_) { |
| MakeDualMode(); |
| } |
| return *le_data_; |
| } |
| |
| Peer::BrEdrData& Peer::MutBrEdr() { |
| if (bredr_data_) { |
| return *bredr_data_; |
| } |
| |
| bredr_data_ = BrEdrData(this); |
| bredr_data_->AttachInspect(node_); |
| |
| // Make dual-mode if both transport states have been initialized. |
| if (le_data_) { |
| MakeDualMode(); |
| } |
| return *bredr_data_; |
| } |
| |
| std::string Peer::ToString() const { |
| return bt_lib_cpp_string::StringPrintf( |
| "{peer id: %s, address: %s}", bt_str(*identifier_), bt_str(*address_)); |
| } |
| |
| bool Peer::RegisterName(const std::string& name, Peer::NameSource source) { |
| if (RegisterNameInternal(name, source)) { |
| UpdateExpiry(); |
| // TODO(fxbug.dev/42140058): Update the bond when this happens |
| UpdatePeerAndNotifyListeners(NotifyListenersChange::kBondNotUpdated); |
| return true; |
| } |
| return false; |
| } |
| |
| void Peer::StoreBrEdrCrossTransportKey(sm::LTK ct_key) { |
| if (!bredr_data_.has_value()) { |
| // If the peer is LE-only, store the CT key separately until the peer is |
| // otherwise marked as dual-mode. |
| bredr_cross_transport_key_ = ct_key; |
| } else if (!bredr_data_->link_key().has_value() || |
| ct_key.security().IsAsSecureAs( |
| bredr_data_->link_key()->security())) { |
| // "The devices shall not overwrite that existing key with a key that is |
| // inclusive-language: ignore |
| // weaker in either strength or MITM protection." (v5.2 Vol. 3 Part C 14.1). |
| bredr_data_->SetBondData(ct_key); |
| } |
| } |
| |
| // Private methods below: |
| |
| bool Peer::SetRssiInternal(int8_t rssi) { |
| if (rssi != hci_spec::kRSSIInvalid && rssi_ != rssi) { |
| rssi_ = rssi; |
| return true; |
| } |
| return false; |
| } |
| |
| bool Peer::RegisterNameInternal(const std::string& name, |
| Peer::NameSource source) { |
| if (!bt_lib_cpp_string::IsStringUTF8(name)) { |
| bt_log(WARN, |
| "gap", |
| "%s: not setting name to string that is not valid UTF-8", |
| bt_str(*this)); |
| return false; |
| } |
| if (!name_->has_value() || source < (*name_)->source || |
| (source == (*name_)->source && name != (*name_)->name)) { |
| name_.Set(Peer::PeerName{name, source}); |
| return true; |
| } |
| return false; |
| } |
| |
| bool Peer::TryMakeNonTemporary() { |
| // TODO(armansito): Since we don't currently support address resolution, |
| // random addresses should never be persisted. |
| if (!connectable()) { |
| bt_log(DEBUG, "gap", "remains temporary: %s", bt_str(*this)); |
| return false; |
| } |
| |
| bt_log(DEBUG, "gap", "became non-temporary: %s:", bt_str(*this)); |
| |
| if (*temporary_) { |
| temporary_.Set(false); |
| UpdateExpiry(); |
| UpdatePeerAndNotifyListeners(NotifyListenersChange::kBondNotUpdated); |
| } |
| |
| return true; |
| } |
| |
| bool Peer::TryMakeTemporary() { |
| if (le() && le()->connection_state() == ConnectionState::kNotConnected && |
| !identity_known()) { |
| bt_log(DEBUG, "gap", "LE became temporary: %s:", bt_str(*this)); |
| temporary_.Set(true); |
| return true; |
| } |
| if (bredr() && !bredr()->bonded()) { |
| bt_log(DEBUG, "gap", "BR/EDR became temporary: %s:", bt_str(*this)); |
| temporary_.Set(true); |
| return true; |
| } |
| return false; |
| } |
| |
| void Peer::UpdateExpiry() { |
| BT_DEBUG_ASSERT(update_expiry_callback_); |
| update_expiry_callback_(*this); |
| } |
| |
| void Peer::NotifyListeners(NotifyListenersChange change) { |
| BT_DEBUG_ASSERT(notify_listeners_callback_); |
| notify_listeners_callback_(*this, change); |
| } |
| |
| void Peer::MakeDualMode() { |
| technology_.Set(TechnologyType::kDualMode); |
| if (bredr_cross_transport_key_) { |
| BT_ASSERT( |
| bredr_data_); // Should only be hit after BR/EDR is already created. |
| bredr_data_->SetBondData(*bredr_cross_transport_key_); |
| bt_log(DEBUG, |
| "gap-bredr", |
| "restored cross-transport-generated br/edr link key"); |
| bredr_cross_transport_key_ = std::nullopt; |
| } |
| BT_DEBUG_ASSERT(dual_mode_callback_); |
| dual_mode_callback_(*this); |
| } |
| |
| void Peer::OnPeerUpdate() { last_updated_ = dispatcher_.now(); } |
| |
| void Peer::UpdatePeerAndNotifyListeners(NotifyListenersChange change) { |
| OnPeerUpdate(); |
| NotifyListeners(change); |
| } |
| |
| } // namespace bt::gap |