blob: 833a9dd0e06dfc80c997c17ddbacfe2146e512e8 [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_cache.h"
#include <fbl/function.h>
#include <zircon/assert.h>
#include "garnet/drivers/bluetooth/lib/gap/remote_device.h"
#include "garnet/drivers/bluetooth/lib/hci/connection.h"
#include "garnet/drivers/bluetooth/lib/hci/low_energy_scanner.h"
#include "lib/async/default.h"
#include "lib/fxl/random/uuid.h"
namespace btlib {
namespace gap {
using common::DeviceAddress;
namespace {
// Return an address with the same value as given, but with type kBREDR for
// kLEPublic addresses and vice versa.
DeviceAddress GetAliasAddress(const DeviceAddress& address) {
if (address.type() == DeviceAddress::Type::kBREDR) {
return {DeviceAddress::Type::kLEPublic, address.value()};
} else if (address.type() == DeviceAddress::Type::kLEPublic) {
return {DeviceAddress::Type::kBREDR, address.value()};
}
return address;
}
} // namespace
RemoteDevice* RemoteDeviceCache::NewDevice(const DeviceAddress& address,
bool connectable) {
ZX_DEBUG_ASSERT(thread_checker_.IsCreationThreadCurrent());
auto* const device =
InsertDeviceRecord(fxl::GenerateUUID(), address, connectable);
if (device) {
UpdateExpiry(*device);
NotifyDeviceUpdated(*device);
}
return device;
}
void RemoteDeviceCache::ForEach(DeviceCallback f) {
ZX_DEBUG_ASSERT(thread_checker_.IsCreationThreadCurrent());
ZX_DEBUG_ASSERT(f);
for (const auto& iter : devices_) {
f(*iter.second.device());
}
}
bool RemoteDeviceCache::AddBondedDevice(const std::string& identifier,
const DeviceAddress& address,
const sm::PairingData& bond_data) {
ZX_DEBUG_ASSERT(thread_checker_.IsCreationThreadCurrent());
ZX_DEBUG_ASSERT(!bond_data.identity_address ||
address == *bond_data.identity_address);
ZX_DEBUG_ASSERT(address.type() != DeviceAddress::Type::kLEAnonymous);
// |bond_data| must contain either a LTK or CSRK for LE Security Mode 1 or 2.
// TODO(armansito): Accept empty |bond_data| if a BR/EDR link key is provided.
if (!bond_data.ltk && !bond_data.csrk) {
bt_log(ERROR, "gap-le", "mandatory keys missing: no IRK or CSRK (id: %s)",
identifier.c_str());
return false;
}
auto* device = InsertDeviceRecord(identifier, address, true);
if (!device) {
return false;
}
// A bonded device must have its identity known.
device->set_identity_known(true);
device->MutLe().SetBondData(bond_data);
ZX_DEBUG_ASSERT(!device->temporary());
ZX_DEBUG_ASSERT(device->le()->bonded());
// Add the device to the resolving list if it has an IRK.
if (bond_data.irk) {
le_resolving_list_.Add(device->address(), bond_data.irk->value());
}
bt_log(SPEW, "gap", "restored bonded device: %s, id: %s",
address.ToString().c_str(), identifier.c_str());
// Don't call UpdateExpiry(). Since a bonded device starts out as
// non-temporary it is not necessary to ever set up the expiration callback.
NotifyDeviceUpdated(*device);
return true;
}
bool RemoteDeviceCache::StoreLowEnergyBond(const std::string& identifier,
const sm::PairingData& bond_data) {
ZX_DEBUG_ASSERT(thread_checker_.IsCreationThreadCurrent());
auto* device = FindDeviceById(identifier);
if (!device) {
bt_log(TRACE, "gap-le", "failed to store bond for unknown device: %s",
identifier.c_str());
return false;
}
// Either a LTK or CSRK is mandatory for bonding (the former is needed for LE
// Security Mode 1 and the latter is needed for Mode 2).
if (!bond_data.ltk && !bond_data.csrk) {
bt_log(TRACE, "gap-le", "mandatory keys missing: no IRK or CSRK (id: %s)",
identifier.c_str());
return false;
}
if (bond_data.identity_address) {
auto existing_id = FindIdByAddress(*bond_data.identity_address);
if (!existing_id) {
// Map the new address to |device|. We leave old addresses that map to
// this device in the cache in case there are any pending controller
// procedures that expect them.
// TODO(armansito): Maybe expire the old address after a while?
address_map_[*bond_data.identity_address] = identifier;
} else if (existing_id->get() != identifier) {
bt_log(TRACE, "gap-le",
"identity address %s for device %s belongs to another device %s!",
bond_data.identity_address->ToString().c_str(), identifier.c_str(),
existing_id->get().c_str());
return false;
}
// We have either created a new mapping or the identity address already
// maps to this device.
}
// TODO(BT-619): Check that we're not downgrading the security level before
// overwriting the bond.
device->MutLe().SetBondData(bond_data);
ZX_DEBUG_ASSERT(!device->temporary());
ZX_DEBUG_ASSERT(device->le()->bonded());
// Add the device to the resolving list if it has an IRK.
if (device->identity_known() && bond_data.irk) {
le_resolving_list_.Add(device->address(), bond_data.irk->value());
}
// Report the bond for persisting only if the identity of the device is known.
if (device->identity_known()) {
NotifyDeviceBonded(*device);
}
return true;
}
bool RemoteDeviceCache::StoreBrEdrBond(const common::DeviceAddress& address,
const sm::LTK& link_key) {
ZX_DEBUG_ASSERT(thread_checker_.IsCreationThreadCurrent());
ZX_DEBUG_ASSERT(address.type() == common::DeviceAddress::Type::kBREDR);
auto* device = FindDeviceByAddress(address);
if (!device) {
bt_log(TRACE, "gap-bredr", "failed to store bond for unknown device: %s",
address.ToString().c_str());
return false;
}
// TODO(BT-619): Check that we're not downgrading the security level before
// overwriting the bond.
device->MutBrEdr().SetLinkKey(link_key);
ZX_DEBUG_ASSERT(!device->temporary());
ZX_DEBUG_ASSERT(device->bredr()->bonded());
NotifyDeviceBonded(*device);
return true;
}
RemoteDevice* RemoteDeviceCache::FindDeviceById(
const std::string& identifier) const {
ZX_DEBUG_ASSERT(thread_checker_.IsCreationThreadCurrent());
auto iter = devices_.find(identifier);
return iter != devices_.end() ? iter->second.device() : nullptr;
}
RemoteDevice* RemoteDeviceCache::FindDeviceByAddress(
const DeviceAddress& in_address) const {
ZX_DEBUG_ASSERT(thread_checker_.IsCreationThreadCurrent());
std::optional<DeviceAddress> address;
if (in_address.IsResolvablePrivate()) {
address = le_resolving_list_.Resolve(in_address);
}
// Fall back to the input if an identity wasn't resolved.
if (!address) {
address = in_address;
}
ZX_DEBUG_ASSERT(address);
auto identifier = FindIdByAddress(*address);
if (!identifier) {
return nullptr;
}
auto* dev = FindDeviceById(identifier->get());
ZX_DEBUG_ASSERT(dev);
return dev;
}
// Private methods below.
RemoteDevice* RemoteDeviceCache::InsertDeviceRecord(
const std::string& identifier, const common::DeviceAddress& address,
bool connectable) {
if (FindIdByAddress(address)) {
bt_log(WARN, "gap", "tried to insert device with existing address: %s",
address.ToString().c_str());
return nullptr;
}
std::unique_ptr<RemoteDevice> device(new RemoteDevice(
fit::bind_member(this, &RemoteDeviceCache::NotifyDeviceUpdated),
fit::bind_member(this, &RemoteDeviceCache::UpdateExpiry),
fit::bind_member(this, &RemoteDeviceCache::MakeDualMode), identifier,
address, connectable));
// Note: we must construct the RemoteDeviceRecord in-place, because it doesn't
// support copy or move.
auto [iter, inserted] =
devices_.try_emplace(device->identifier(), std::move(device),
[this, d = device.get()] { RemoveDevice(d); });
if (!inserted) {
bt_log(WARN, "gap", "tried to insert device with existing ID: %s",
identifier.c_str());
return nullptr;
}
address_map_[address] = identifier;
return iter->second.device();
}
void RemoteDeviceCache::NotifyDeviceBonded(const RemoteDevice& device) {
ZX_DEBUG_ASSERT(devices_.find(device.identifier()) != devices_.end());
ZX_DEBUG_ASSERT(devices_.at(device.identifier()).device() == &device);
ZX_DEBUG_ASSERT_MSG(device.identity_known(),
"devices not allowed to bond with unknown identity!");
bt_log(INFO, "gap", "peer bonded %s", device.ToString().c_str());
if (device_bonded_callback_) {
device_bonded_callback_(device);
}
}
void RemoteDeviceCache::NotifyDeviceUpdated(const RemoteDevice& device) {
ZX_DEBUG_ASSERT(devices_.find(device.identifier()) != devices_.end());
ZX_DEBUG_ASSERT(devices_.at(device.identifier()).device() == &device);
if (device_updated_callback_)
device_updated_callback_(device);
}
void RemoteDeviceCache::UpdateExpiry(const RemoteDevice& device) {
auto device_record_iter = devices_.find(device.identifier());
ZX_DEBUG_ASSERT(device_record_iter != devices_.end());
auto& device_record = device_record_iter->second;
ZX_DEBUG_ASSERT(device_record.device() == &device);
const auto cancel_res = device_record.removal_task()->Cancel();
ZX_DEBUG_ASSERT(cancel_res == ZX_OK || cancel_res == ZX_ERR_NOT_FOUND);
// Previous expiry task has been canceled. Re-schedule only if the device is
// temporary.
if (device.temporary()) {
const auto schedule_res = device_record.removal_task()->PostDelayed(
async_get_default_dispatcher(), kCacheTimeout);
ZX_DEBUG_ASSERT(schedule_res == ZX_OK || schedule_res == ZX_ERR_BAD_STATE);
}
}
void RemoteDeviceCache::MakeDualMode(const RemoteDevice& device) {
ZX_DEBUG_ASSERT(address_map_.at(device.address()) == device.identifier());
const auto address_alias = GetAliasAddress(device.address());
auto [iter, inserted] =
address_map_.try_emplace(address_alias, device.identifier());
ZX_DEBUG_ASSERT_MSG(inserted || iter->second == device.identifier(),
"%s can't become dual-mode because %s maps to %s",
device.identifier().c_str(),
address_alias.ToString().c_str(), iter->second.c_str());
// The device became dual mode in lieu of adding a new device but is as
// significant, so notify listeners of the change.
NotifyDeviceUpdated(device);
}
void RemoteDeviceCache::RemoveDevice(RemoteDevice* device) {
ZX_DEBUG_ASSERT(device);
auto device_record_it = devices_.find(device->identifier());
ZX_DEBUG_ASSERT(device_record_it != devices_.end());
ZX_DEBUG_ASSERT(device_record_it->second.device() == device);
const std::string identifier_copy = device->identifier();
for (auto iter = address_map_.begin(); iter != address_map_.end();) {
if (iter->second == device->identifier()) {
iter = address_map_.erase(iter);
} else {
iter++;
}
}
devices_.erase(device_record_it); // Destroys |device|.
if (device_removed_callback_) {
device_removed_callback_(identifier_copy);
}
}
// This returns string cref instead of string_view because the caller is likely
// to use the ID to index into |devices_|.
// TODO(BT-305): This would be less messy using an integer ID type.
std::optional<std::reference_wrapper<const std::string>>
RemoteDeviceCache::FindIdByAddress(const common::DeviceAddress& address) const {
auto iter = address_map_.find(address);
if (iter == address_map_.end()) {
// Search again using the other technology's address. This is necessary when
// a dual-mode device is known by only one technology and is then discovered
// or connected on its other technology.
iter = address_map_.find(GetAliasAddress(address));
}
if (iter == address_map_.end()) {
return {};
}
return {iter->second};
}
} // namespace gap
} // namespace btlib