| // Copyright 2017 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "remote_device.h" |
| |
| #include "garnet/drivers/bluetooth/lib/gap/advertising_data.h" |
| #include "garnet/drivers/bluetooth/lib/hci/low_energy_scanner.h" |
| #include "lib/fxl/logging.h" |
| #include "lib/fxl/strings/string_printf.h" |
| |
| #include "advertising_data.h" |
| |
| namespace btlib { |
| namespace gap { |
| namespace { |
| |
| std::string ConnectionStateToString(RemoteDevice::ConnectionState state) { |
| switch (state) { |
| case RemoteDevice::ConnectionState::kNotConnected: |
| return "not connected"; |
| case RemoteDevice::ConnectionState::kInitializing: |
| return "initializing"; |
| case RemoteDevice::ConnectionState::kConnected: |
| return "initialized"; |
| case RemoteDevice::ConnectionState::kBonding: |
| return "bonding"; |
| case RemoteDevice::ConnectionState::kBonded: |
| return "bonded"; |
| } |
| |
| FXL_NOTREACHED(); |
| return "(unknown)"; |
| } |
| |
| constexpr uint16_t kClockOffsetValidBitMask = 0x8000; |
| |
| } // namespace |
| |
| RemoteDevice::RemoteDevice(DeviceCallback notify_listeners_callback, |
| DeviceCallback update_expiry_callback, |
| const std::string& identifier, |
| const common::DeviceAddress& address, |
| bool connectable) |
| : notify_listeners_callback_(std::move(notify_listeners_callback)), |
| update_expiry_callback_(std::move(update_expiry_callback)), |
| identifier_(identifier), |
| address_(address), |
| technology_((address.type() == common::DeviceAddress::Type::kBREDR) |
| ? TechnologyType::kClassic |
| : TechnologyType::kLowEnergy), |
| le_connection_state_(ConnectionState::kNotConnected), |
| bredr_connection_state_(ConnectionState::kNotConnected), |
| connectable_(connectable), |
| temporary_(true), |
| rssi_(hci::kRSSIInvalid), |
| advertising_data_length_(0u) { |
| FXL_DCHECK(notify_listeners_callback_); |
| FXL_DCHECK(update_expiry_callback_); |
| FXL_DCHECK(!identifier_.empty()); |
| // TODO(armansito): Add a mechanism for assigning "dual-mode" for technology. |
| } |
| |
| void RemoteDevice::SetLEConnectionState(ConnectionState state) { |
| FXL_DCHECK(connectable() || state == ConnectionState::kNotConnected); |
| FXL_VLOG(1) << "gap: RemoteDevice le_connection_state changed from \"" |
| << ConnectionStateToString(le_connection_state_) << "\" to \"" |
| << ConnectionStateToString(state) << "\""; |
| |
| le_connection_state_ = state; |
| update_expiry_callback_(*this); |
| notify_listeners_callback_(*this); |
| } |
| |
| void RemoteDevice::SetBREDRConnectionState(ConnectionState state) { |
| FXL_DCHECK(connectable() || state == ConnectionState::kNotConnected); |
| FXL_VLOG(1) << "gap: RemoteDevice bredr_connection_state changed from \"" |
| << ConnectionStateToString(bredr_connection_state_) << "\" to \"" |
| << ConnectionStateToString(state) << "\""; |
| |
| bredr_connection_state_ = state; |
| update_expiry_callback_(*this); |
| notify_listeners_callback_(*this); |
| } |
| |
| void RemoteDevice::SetLEAdvertisingData( |
| int8_t rssi, const common::ByteBuffer& advertising_data) { |
| FXL_DCHECK(technology() == TechnologyType::kLowEnergy); |
| FXL_DCHECK(address_.type() != common::DeviceAddress::Type::kBREDR); |
| update_expiry_callback_(*this); |
| |
| // Reallocate the advertising data buffer only if we need more space. |
| // TODO(armansito): Revisit this strategy while addressing NET-209 |
| if (advertising_data_buffer_.size() < advertising_data.size()) { |
| advertising_data_buffer_ = |
| common::DynamicByteBuffer(advertising_data.size()); |
| } |
| |
| AdvertisingData old_parsed_ad; |
| if (!AdvertisingData::FromBytes(advertising_data_buffer_, &old_parsed_ad)) { |
| old_parsed_ad = AdvertisingData(); |
| } |
| |
| AdvertisingData new_parsed_ad; |
| if (!AdvertisingData::FromBytes(advertising_data, &new_parsed_ad)) { |
| new_parsed_ad = AdvertisingData(); |
| } |
| |
| rssi_ = rssi; |
| advertising_data_length_ = advertising_data.size(); |
| advertising_data.Copy(&advertising_data_buffer_); |
| |
| if (old_parsed_ad.local_name() != new_parsed_ad.local_name()) { |
| notify_listeners_callback_(*this); |
| } |
| } |
| |
| template <typename T> |
| typename std::enable_if< |
| std::is_same<T, hci::InquiryResult>::value || |
| std::is_same<T, hci::InquiryResultRSSI>::value || |
| std::is_same<T, hci::ExtendedInquiryResultEventParams>::value>::type |
| RemoteDevice::SetInquiryData(const T& inquiry_result) { |
| FXL_DCHECK(address_.value() == inquiry_result.bd_addr); |
| update_expiry_callback_(*this); |
| |
| bool significant_change_common = |
| !device_class_ || (device_class_->major_class() != |
| inquiry_result.class_of_device.major_class()); |
| clock_offset_ = |
| le16toh(kClockOffsetValidBitMask | inquiry_result.clock_offset); |
| page_scan_repetition_mode_ = inquiry_result.page_scan_repetition_mode; |
| device_class_ = inquiry_result.class_of_device; |
| |
| bool significant_change_specific = SetSpecificInquiryData(inquiry_result); |
| if (significant_change_common || significant_change_specific) { |
| notify_listeners_callback_(*this); |
| } |
| } |
| |
| template void RemoteDevice::SetInquiryData<>(const hci::InquiryResult&); |
| template void RemoteDevice::SetInquiryData<>(const hci::InquiryResultRSSI&); |
| template void RemoteDevice::SetInquiryData<>( |
| const hci::ExtendedInquiryResultEventParams&); |
| |
| void RemoteDevice::SetName(const std::string& name) { |
| update_expiry_callback_(*this); |
| if (!name_ || *name_ != name) { |
| name_ = name; |
| notify_listeners_callback_(*this); |
| } |
| } |
| |
| bool RemoteDevice::TryMakeNonTemporary() { |
| // TODO(armansito): Since we don't currently support address resolution, |
| // random addresses should never be persisted. |
| if (!connectable() || |
| address().type() == common::DeviceAddress::Type::kLERandom || |
| address().type() == common::DeviceAddress::Type::kLEAnonymous) { |
| FXL_VLOG(1) << "gap: remains temporary: " << ToString(); |
| return false; |
| } |
| |
| if (temporary_) { |
| temporary_ = false; |
| update_expiry_callback_(*this); |
| notify_listeners_callback_(*this); |
| } |
| |
| return true; |
| } |
| |
| std::string RemoteDevice::ToString() const { |
| return fxl::StringPrintf("{remote-device id: %s, address: %s}", |
| identifier_.c_str(), address_.ToString().c_str()); |
| } |
| |
| // Private methods below. |
| |
| bool RemoteDevice::SetExtendedInquiryResponse(const common::ByteBuffer& bytes) { |
| FXL_DCHECK(bytes.size() <= hci::kExtendedInquiryResponseBytes); |
| update_expiry_callback_(*this); |
| |
| if (extended_inquiry_response_.size() < bytes.size()) { |
| extended_inquiry_response_ = common::DynamicByteBuffer(bytes.size()); |
| } |
| bytes.Copy(&extended_inquiry_response_); |
| |
| // TODO(jamuraa): maybe rename this class? |
| AdvertisingDataReader reader(extended_inquiry_response_); |
| |
| gap::DataType type; |
| common::BufferView data; |
| bool significant_change = false; |
| while (reader.GetNextField(&type, &data)) { |
| if (type == gap::DataType::kCompleteLocalName) { |
| auto new_name = data.ToString(); |
| if (!name_ || *name_ != new_name) { |
| name_ = new_name; |
| significant_change = true; |
| } |
| } |
| } |
| |
| return significant_change; |
| } |
| |
| bool RemoteDevice::SetSpecificInquiryData(const hci::InquiryResult& result) { |
| // All InquiryResult data is handled in the common case. Nothing left to do. |
| return false; |
| } |
| |
| bool RemoteDevice::SetSpecificInquiryData( |
| const hci::InquiryResultRSSI& result) { |
| rssi_ = result.rssi; |
| return false; |
| } |
| |
| bool RemoteDevice::SetSpecificInquiryData( |
| const hci::ExtendedInquiryResultEventParams& result) { |
| rssi_ = result.rssi; |
| return SetExtendedInquiryResponse(common::BufferView( |
| result.extended_inquiry_response, hci::kExtendedInquiryResponseBytes)); |
| } |
| |
| } // namespace gap |
| } // namespace btlib |