blob: 9dae547d63424c2c11a3047d97faac81b8b86f6e [file] [log] [blame]
// 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