blob: b9d4e8a34f7e3e67ab2a6024b8c79c11779eac45 [file] [log] [blame]
// Copyright 2018 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 "host_server.h"
#include <lib/fit/result.h>
#include <zircon/assert.h>
#include "fuchsia/bluetooth/control/cpp/fidl.h"
#include "helpers.h"
#include "low_energy_central_server.h"
#include "low_energy_peripheral_server.h"
#include "profile_server.h"
#include "src/connectivity/bluetooth/core/bt-host/common/identifier.h"
#include "src/connectivity/bluetooth/core/bt-host/common/log.h"
#include "src/connectivity/bluetooth/core/bt-host/gap/adapter.h"
#include "src/connectivity/bluetooth/core/bt-host/gap/bonding_data.h"
#include "src/connectivity/bluetooth/core/bt-host/gap/bredr_connection_manager.h"
#include "src/connectivity/bluetooth/core/bt-host/gap/bredr_discovery_manager.h"
#include "src/connectivity/bluetooth/core/bt-host/gap/gap.h"
#include "src/connectivity/bluetooth/core/bt-host/gap/low_energy_address_manager.h"
#include "src/connectivity/bluetooth/core/bt-host/gap/low_energy_discovery_manager.h"
#include "src/connectivity/bluetooth/core/bt-host/gatt_host.h"
#include "src/connectivity/bluetooth/core/bt-host/sm/types.h"
#include "src/connectivity/bluetooth/core/bt-host/sm/util.h"
#include "src/lib/fxl/logging.h"
#include "src/lib/fxl/strings/join_strings.h"
#include "src/lib/fxl/strings/string_number_conversions.h"
#include "src/lib/fxl/strings/string_printf.h"
namespace bthost {
namespace fbt = fuchsia::bluetooth;
namespace fsys = fuchsia::bluetooth::sys;
using bt::PeerId;
using bt::sm::IOCapability;
using fidl_helpers::AddressBytesFromString;
using fidl_helpers::HostErrorToFidl;
using fidl_helpers::NewFidlError;
using fidl_helpers::PeerIdFromString;
using fidl_helpers::SecurityLevelFromFidl;
using fidl_helpers::StatusToFidl;
using fidl_helpers::StatusToFidlDeprecated;
using fuchsia::bluetooth::Bool;
using fuchsia::bluetooth::ErrorCode;
using fuchsia::bluetooth::Status;
using fuchsia::bluetooth::control::AdapterState;
using fuchsia::bluetooth::control::BondingData;
using fuchsia::bluetooth::control::PairingOptions;
using fuchsia::bluetooth::control::RemoteDevice;
using fuchsia::bluetooth::control::TechnologyType;
std::pair<PeerTracker::Updated, PeerTracker::Removed> PeerTracker::ToFidl(
const bt::gap::PeerCache* peer_cache) {
PeerTracker::Updated updated_fidl;
for (auto& id : updated_) {
auto* peer = peer_cache->FindById(id);
// All ids in |updated_| are assumed to be valid as they would otherwise be in |removed_|.
ZX_ASSERT(peer);
updated_fidl.push_back(fidl_helpers::PeerToFidl(*peer));
}
PeerTracker::Removed removed_fidl;
for (auto& id : removed_) {
removed_fidl.push_back(fbt::PeerId{id.value()});
}
return std::make_pair(std::move(updated_fidl), std::move(removed_fidl));
}
void PeerTracker::Update(bt::PeerId id) {
updated_.insert(id);
removed_.erase(id);
}
void PeerTracker::Remove(bt::PeerId id) {
updated_.erase(id);
removed_.insert(id);
}
WatchPeersGetter::WatchPeersGetter(bt::gap::PeerCache* peer_cache) : peer_cache_(peer_cache) {
ZX_DEBUG_ASSERT(peer_cache_);
}
void WatchPeersGetter::Notify(std::queue<Callback> callbacks, PeerTracker peers) {
auto [updated, removed] = peers.ToFidl(peer_cache_);
while (!callbacks.empty()) {
auto f = std::move(callbacks.front());
callbacks.pop();
f(fidl::Clone(updated), fidl::Clone(removed));
}
}
HostServer::HostServer(zx::channel channel, fxl::WeakPtr<bt::gap::Adapter> adapter,
fbl::RefPtr<GattHost> gatt_host)
: AdapterServerBase(adapter, this, std::move(channel)),
pairing_delegate_(nullptr),
gatt_host_(gatt_host),
requesting_discovery_(false),
requesting_discoverable_(false),
io_capability_(IOCapability::kNoInputNoOutput),
watch_peers_getter_(adapter->peer_cache()),
weak_ptr_factory_(this) {
ZX_DEBUG_ASSERT(gatt_host_);
auto self = weak_ptr_factory_.GetWeakPtr();
adapter->peer_cache()->set_peer_updated_callback([self](const auto& peer) {
if (self) {
self->OnPeerUpdated(peer);
}
});
adapter->peer_cache()->set_peer_removed_callback([self](const auto& identifier) {
if (self) {
self->OnPeerRemoved(identifier);
}
});
adapter->peer_cache()->set_peer_bonded_callback([self](const auto& peer) {
if (self) {
self->OnPeerBonded(peer);
}
});
adapter->set_auto_connect_callback([self](auto conn_ref) {
if (self) {
self->RegisterLowEnergyConnection(std::move(conn_ref), true);
}
});
// Initialize the HostInfo getter with the initial state.
NotifyInfoChange();
// Initialize the peer watcher with all known connectable peers that are in the cache.
adapter->peer_cache()->ForEach([this](const bt::gap::Peer& peer) { OnPeerUpdated(peer); });
}
HostServer::~HostServer() { Close(); }
void HostServer::WatchState(WatchStateCallback callback) {
info_getter_.Watch(std::move(callback));
}
void HostServer::SetLocalData(::fuchsia::bluetooth::control::HostData host_data) {
if (host_data.irk) {
bt_log(TRACE, "bt-host", "assign IRK");
auto addr_mgr = adapter()->le_address_manager();
if (addr_mgr) {
addr_mgr->set_irk(fidl_helpers::LocalKeyFromFidl(*host_data.irk));
}
}
}
void HostServer::WatchPeers(WatchPeersCallback callback) {
watch_peers_getter_.Watch(std::move(callback));
}
// TODO(35008): Add a unit test for this method.
void HostServer::SetLocalName(::std::string local_name, SetLocalNameCallback callback) {
ZX_DEBUG_ASSERT(!local_name.empty());
// Make a copy of |local_name| to move separately into the lambda.
std::string name_copy(local_name);
adapter()->SetLocalName(std::move(local_name),
[self = weak_ptr_factory_.GetWeakPtr(), local_name = std::move(name_copy),
callback = std::move(callback)](auto status) {
// Send adapter state update on success and if the connection is still
// open.
if (status && self) {
self->NotifyInfoChange();
}
callback(StatusToFidl(status));
});
}
// TODO(35008): Add a unit test for this method.
void HostServer::SetDeviceClass(fbt::DeviceClass device_class, SetDeviceClassCallback callback) {
// Device Class values must only contain data in the lower 3 bytes.
if (device_class.value >= 1 << 24) {
callback(fit::error(fsys::Error::INVALID_ARGUMENTS));
return;
}
bt::DeviceClass dev_class(device_class.value);
adapter()->SetDeviceClass(
dev_class, [callback = std::move(callback)](auto status) { callback(StatusToFidl(status)); });
}
void HostServer::StartLEDiscovery(StartDiscoveryCallback callback) {
auto le_manager = adapter()->le_discovery_manager();
if (!le_manager) {
callback(fit::error(fsys::Error::FAILED));
return;
}
le_manager->StartDiscovery(
[self = weak_ptr_factory_.GetWeakPtr(), callback = std::move(callback)](auto session) {
// End the new session if this AdapterServer got destroyed in the
// mean time (e.g. because the client disconnected).
if (!self) {
callback(fit::error(fsys::Error::FAILED));
return;
}
if (!self->requesting_discovery_) {
callback(fit::error(fsys::Error::CANCELED));
return;
}
if (!session) {
bt_log(TRACE, "bt-host", "failed to start LE discovery session");
callback(fit::error(fsys::Error::FAILED));
self->bredr_discovery_session_ = nullptr;
self->requesting_discovery_ = false;
return;
}
// Set up a general-discovery filter for connectable devices.
// NOTE(armansito): This currently has no effect since peer updates
// are driven by PeerCache events. |session|'s "result callback" is unused.
session->filter()->set_connectable(true);
session->filter()->SetGeneralDiscoveryFlags();
self->le_discovery_session_ = std::move(session);
self->requesting_discovery_ = false;
// Send the adapter state update.
self->NotifyInfoChange();
callback(fit::ok());
});
}
void HostServer::StartDiscovery(StartDiscoveryCallback callback) {
bt_log(TRACE, "bt-host", "StartDiscovery()");
ZX_DEBUG_ASSERT(adapter());
if (le_discovery_session_ || requesting_discovery_) {
bt_log(TRACE, "bt-host", "discovery already in progress");
callback(fit::error(fsys::Error::IN_PROGRESS));
return;
}
requesting_discovery_ = true;
auto bredr_manager = adapter()->bredr_discovery_manager();
if (!bredr_manager) {
StartLEDiscovery(std::move(callback));
return;
}
// TODO(jamuraa): start these in parallel instead of sequence
bredr_manager->RequestDiscovery(
[self = weak_ptr_factory_.GetWeakPtr(), callback = std::move(callback)](
bt::hci::Status status, auto session) mutable {
if (!self) {
callback(fit::error(fsys::Error::FAILED));
return;
}
if (!self->requesting_discovery_) {
callback(fit::error(fsys::Error::CANCELED));
return;
}
if (!status || !session) {
bt_log(TRACE, "bt-host", "failed to start BR/EDR discovery session");
fit::result<void, fsys::Error> result;
if (!status) {
result = StatusToFidl(status);
} else {
result = fit::error(fsys::Error::FAILED);
}
self->requesting_discovery_ = false;
callback(std::move(result));
return;
}
self->bredr_discovery_session_ = std::move(session);
self->StartLEDiscovery(std::move(callback));
});
}
void HostServer::StopDiscovery() {
bt_log(TRACE, "bt-host", "StopDiscovery()");
bool discovering = le_discovery_session_ || bredr_discovery_session_;
bredr_discovery_session_ = nullptr;
le_discovery_session_ = nullptr;
if (discovering) {
NotifyInfoChange();
} else {
bt_log(TRACE, "bt-host", "no active discovery session");
}
}
void HostServer::SetConnectable(bool connectable, SetConnectableCallback callback) {
bt_log(TRACE, "bt-host", "SetConnectable(%s)", connectable ? "true" : "false");
auto bredr_conn_manager = adapter()->bredr_connection_manager();
if (!bredr_conn_manager) {
callback(fit::error(fsys::Error::NOT_SUPPORTED));
return;
}
bredr_conn_manager->SetConnectable(
connectable,
[callback = std::move(callback)](const auto& status) { callback(StatusToFidl(status)); });
}
void HostServer::AddBondedDevices(::std::vector<BondingData> bonds,
AddBondedDevicesCallback callback) {
bt_log(TRACE, "bt-host", "AddBondedDevices");
if (bonds.empty()) {
callback(NewFidlError(ErrorCode::NOT_SUPPORTED, "No bonds were added"));
return;
}
std::vector<std::string> failed_ids;
for (auto& bond : bonds) {
auto peer_id = PeerIdFromString(bond.identifier);
if (!peer_id) {
failed_ids.push_back(bond.identifier);
continue;
}
std::optional<std::string> peer_name;
if (bond.name) {
peer_name = std::move(bond.name);
}
bt::DeviceAddress address;
bt::sm::PairingData le_bond_data;
if (bond.le) {
if (bond.bredr && bond.le->address != bond.bredr->address) {
bt_log(ERROR, "bt-host", "Dual-mode bonding data mismatched (id: %s)",
bond.identifier.c_str());
failed_ids.push_back(bond.identifier);
continue;
}
le_bond_data = fidl_helpers::PairingDataFromFidl(*bond.le);
// The |identity_address| field in bt::sm::PairingData is optional
// however it is not nullable in the FIDL struct. Hence it must be
// present.
ZX_DEBUG_ASSERT(le_bond_data.identity_address);
address = *le_bond_data.identity_address;
}
std::optional<bt::sm::LTK> bredr_link_key;
if (bond.bredr) {
// Dual-mode peers will have a BR/EDR-typed address.
auto addr = AddressBytesFromString(bond.bredr->address);
ZX_DEBUG_ASSERT(addr);
address = bt::DeviceAddress(bt::DeviceAddress::Type::kBREDR, *addr);
bredr_link_key = fidl_helpers::BrEdrKeyFromFidl(*bond.bredr);
}
if (!bond.le && !bond.bredr) {
bt_log(ERROR, "bt-host", "Required bonding data missing (id: %s)", bond.identifier.c_str());
failed_ids.push_back(bond.identifier);
continue;
}
// TODO(armansito): BondingData should contain the identity address for both
// transports instead of storing them separately. For now use the one we
// obtained from |bond.le|.
if (!adapter()->AddBondedPeer(
bt::gap::BondingData{*peer_id, address, peer_name, le_bond_data, bredr_link_key})) {
failed_ids.push_back(bond.identifier);
continue;
}
}
if (!failed_ids.empty()) {
callback(fidl_helpers::NewFidlError(
ErrorCode::FAILED, fxl::StringPrintf("Some peers failed to load (ids: %s)",
fxl::JoinStrings(failed_ids, ", ").c_str())));
} else {
callback(Status());
}
}
void HostServer::OnPeerBonded(const bt::gap::Peer& peer) {
bt_log(TRACE, "bt-host", "OnPeerBonded()");
binding()->events().OnNewBondingData(fidl_helpers::NewBondingData(*adapter(), peer));
}
void HostServer::RegisterLowEnergyConnection(bt::gap::LowEnergyConnectionRefPtr conn_ref,
bool auto_connect) {
ZX_DEBUG_ASSERT(conn_ref);
bt::PeerId id = conn_ref->peer_identifier();
auto iter = le_connections_.find(id);
if (iter != le_connections_.end()) {
bt_log(WARN, "bt-host", "peer already connected; reference dropped");
return;
}
bt_log(TRACE, "bt-host", "LE peer connected (%s): %s ", (auto_connect ? "auto" : "direct"),
bt_str(id));
conn_ref->set_closed_callback([self = weak_ptr_factory_.GetWeakPtr(), id] {
if (self)
self->le_connections_.erase(id);
});
le_connections_[id] = std::move(conn_ref);
}
void HostServer::SetDiscoverable(bool discoverable, SetDiscoverableCallback callback) {
bt_log(TRACE, "bt-host", "SetDiscoverable(%s)", discoverable ? "true" : "false");
// TODO(NET-830): advertise LE here
if (!discoverable) {
bredr_discoverable_session_ = nullptr;
NotifyInfoChange();
callback(fit::ok());
return;
}
if (discoverable && requesting_discoverable_) {
bt_log(TRACE, "bt-host", "SetDiscoverable already in progress");
callback(fit::error(fsys::Error::IN_PROGRESS));
return;
}
requesting_discoverable_ = true;
auto bredr_manager = adapter()->bredr_discovery_manager();
if (!bredr_manager) {
callback(fit::error(fsys::Error::FAILED));
return;
}
bredr_manager->RequestDiscoverable(
[self = weak_ptr_factory_.GetWeakPtr(), callback = std::move(callback)](
bt::hci::Status status, auto session) {
if (!self) {
callback(fit::error(fsys::Error::FAILED));
return;
}
if (!self->requesting_discoverable_) {
callback(fit::error(fsys::Error::CANCELED));
return;
}
if (!status || !session) {
bt_log(TRACE, "bt-host", "failed to set discoverable");
fit::result<void, fsys::Error> result;
if (!status) {
result = StatusToFidl(status);
} else {
result = fit::error(fsys::Error::FAILED);
}
self->requesting_discoverable_ = false;
callback(std::move(result));
return;
}
self->bredr_discoverable_session_ = std::move(session);
self->requesting_discoverable_ = false;
self->NotifyInfoChange();
callback(fit::ok());
});
}
void HostServer::EnableBackgroundScan(bool enabled) {
bt_log(TRACE, "bt-host", "%s background scan", (enabled ? "enable" : "disable"));
auto le_manager = adapter()->le_discovery_manager();
if (le_manager) {
le_manager->EnableBackgroundScan(enabled);
}
}
void HostServer::EnablePrivacy(bool enabled) {
bt_log(TRACE, "bt-host", "%s LE privacy", (enabled ? "enable" : "disable"));
auto addr_mgr = adapter()->le_address_manager();
if (addr_mgr) {
addr_mgr->EnablePrivacy(enabled);
}
}
void HostServer::SetPairingDelegate(
::fuchsia::bluetooth::control::InputCapabilityType input,
::fuchsia::bluetooth::control::OutputCapabilityType output,
::fidl::InterfaceHandle<::fuchsia::bluetooth::control::PairingDelegate> delegate) {
bool cleared = !delegate;
pairing_delegate_.Bind(std::move(delegate));
if (cleared) {
bt_log(TRACE, "bt-host", "PairingDelegate cleared");
ResetPairingDelegate();
return;
}
io_capability_ = fidl_helpers::IoCapabilityFromFidl(input, output);
bt_log(TRACE, "bt-host", "PairingDelegate assigned (I/O capability: %s)",
bt::sm::util::IOCapabilityToString(io_capability_).c_str());
auto self = weak_ptr_factory_.GetWeakPtr();
adapter()->SetPairingDelegate(self);
pairing_delegate_.set_error_handler([self](zx_status_t status) {
bt_log(TRACE, "bt-host", "PairingDelegate disconnected");
if (self) {
self->ResetPairingDelegate();
}
});
}
// Attempt to connect to peer identified by |peer_id|. The peer must be
// in our peer cache. We will attempt to connect technologies (LowEnergy,
// Classic or Dual-Mode) as the peer claims to support when discovered
void HostServer::Connect(fbt::PeerId peer_id, ConnectCallback callback) {
bt::PeerId id{peer_id.value};
auto peer = adapter()->peer_cache()->FindById(id);
if (!peer) {
// We don't support connecting to peers that are not in our cache
callback(fit::error(fsys::Error::PEER_NOT_FOUND));
return;
}
// TODO(BT-649): Dual-mode currently not supported; if the peer supports
// LowEnergy we assume LE. If a dual-mode peer, we should attempt to connect
// both protocols.
if (!peer->le()) {
ConnectBrEdr(id, std::move(callback));
return;
}
ConnectLowEnergy(id, std::move(callback));
}
// Attempt to disconnect the peer identified by |peer_id| from all transports.
// If the peer is already not connected, return success. If the peer is
// disconnected succesfully, return success.
void HostServer::Disconnect(fbt::PeerId peer_id, DisconnectCallback callback) {
bt::PeerId id{peer_id.value};
auto le_disc = adapter()->le_connection_manager()->Disconnect(id);
auto bredr_disc = adapter()->bredr_connection_manager()->Disconnect(id);
if (le_disc && bredr_disc) {
callback(fit::ok());
} else {
callback(fit::error(fsys::Error::FAILED));
}
}
void HostServer::ConnectLowEnergy(PeerId peer_id, ConnectCallback callback) {
auto self = weak_ptr_factory_.GetWeakPtr();
auto on_complete = [self, callback = std::move(callback), peer_id](auto status, auto connection) {
if (!status) {
ZX_ASSERT(!connection);
bt_log(TRACE, "bt-host", "failed to connect LE transport to peer (id %s)", bt_str(peer_id));
callback(fit::error(HostErrorToFidl(status.error())));
return;
}
// We must be connected and to the right peer
ZX_ASSERT(connection);
ZX_ASSERT(peer_id == connection->peer_identifier());
callback(fit::ok());
if (self)
self->RegisterLowEnergyConnection(std::move(connection), false);
};
if (!adapter()->le_connection_manager()->Connect(peer_id, std::move(on_complete))) {
callback(fit::error(fsys::Error::FAILED));
}
}
// Initiate an outgoing Br/Edr connection, unless already connected
// Br/Edr connections are host-wide, and stored in BrEdrConnectionManager
void HostServer::ConnectBrEdr(PeerId peer_id, ConnectCallback callback) {
auto on_complete = [callback = std::move(callback), peer_id](auto status, auto connection) {
if (!status) {
ZX_ASSERT(!connection);
bt_log(TRACE, "bt-host", "failed to connect BR/EDR transport to peer (id %s)",
bt_str(peer_id));
callback(fit::error(HostErrorToFidl(status.error())));
return;
}
// We must be connected and to the right peer
ZX_ASSERT(connection);
ZX_ASSERT(peer_id == connection->peer_id());
callback(fit::ok());
};
if (!adapter()->bredr_connection_manager()->Connect(peer_id, std::move(on_complete))) {
callback(fit::error(fsys::Error::FAILED));
}
}
void HostServer::Forget(fbt::PeerId peer_id, ForgetCallback callback) {
bt::PeerId id{peer_id.value};
auto peer = adapter()->peer_cache()->FindById(id);
if (!peer) {
bt_log(TRACE, "bt-host", "peer %s to forget wasn't found", bt_str(id));
callback(fit::ok());
return;
}
const bool le_disconnected = adapter()->le_connection_manager()->Disconnect(id);
const bool bredr_disconnected = adapter()->bredr_connection_manager()->Disconnect(id);
const bool peer_removed = adapter()->peer_cache()->RemoveDisconnectedPeer(id);
if (!le_disconnected || !bredr_disconnected) {
const auto message =
fxl::StringPrintf("link(s) failed to close:%s%s", le_disconnected ? "" : " LE",
bredr_disconnected ? "" : " BR/EDR");
callback(fit::error(fsys::Error::FAILED));
} else {
ZX_ASSERT(peer_removed);
callback(fit::ok());
}
}
void HostServer::Pair(fuchsia::bluetooth::PeerId id,
fuchsia::bluetooth::control::PairingOptions options, PairCallback callback) {
auto peer_id = bt::PeerId(id.value);
auto peer = adapter()->peer_cache()->FindById(peer_id);
if (!peer) {
// We don't support pairing to peers that are not in our cache
callback(fit::error(fsys::Error::PEER_NOT_FOUND));
return;
}
// If options specifies a transport preference for LE or BR/EDR, we use that. Otherwise, we use
// whatever transport exists, defaulting to LE for dual-mode connections.
bool pair_bredr = !peer->le();
if (options.has_transport() && options.transport() != TechnologyType::DUAL_MODE) {
pair_bredr = (options.transport() == TechnologyType::CLASSIC);
}
if (pair_bredr) {
PairBrEdr(peer_id, std::move(callback));
return;
}
PairLowEnergy(peer_id, std::move(options), std::move(callback));
}
void HostServer::PairLowEnergy(PeerId peer_id, PairingOptions options, PairCallback callback) {
std::optional<bt::sm::SecurityLevel> security_level;
if (options.has_le_security_level()) {
security_level = SecurityLevelFromFidl(options.le_security_level());
if (!security_level.has_value()) {
callback(fit::error(fsys::Error::INVALID_ARGUMENTS));
return;
}
} else {
security_level = bt::sm::SecurityLevel::kAuthenticated;
}
bt::sm::BondableMode bondable_mode = bt::sm::BondableMode::Bondable;
if (options.has_non_bondable() && options.non_bondable()) {
bondable_mode = bt::sm::BondableMode::NonBondable;
}
auto on_complete = [peer_id, callback = std::move(callback)](bt::sm::Status status) {
if (!status) {
bt_log(WARN, "bt-host", "failed to pair to peer (id %s)", bt_str(peer_id));
callback(fit::error(HostErrorToFidl(status.error())));
} else {
callback(fit::ok());
}
};
adapter()->le_connection_manager()->Pair(peer_id, *security_level, bondable_mode,
std::move(on_complete));
}
void HostServer::PairBrEdr(PeerId peer_id, PairCallback callback) {
auto on_complete = [peer_id, callback = std::move(callback)](bt::hci::Status status) {
if (!status) {
bt_log(WARN, "bt-host", "failed to pair to peer (id %s)", bt_str(peer_id));
callback(fit::error(HostErrorToFidl(status.error())));
} else {
callback(fit::ok());
}
};
adapter()->bredr_connection_manager()->Pair(peer_id, std::move(on_complete));
}
void HostServer::RequestLowEnergyCentral(
fidl::InterfaceRequest<fuchsia::bluetooth::le::Central> request) {
BindServer<LowEnergyCentralServer>(std::move(request), gatt_host_);
}
void HostServer::RequestLowEnergyPeripheral(
fidl::InterfaceRequest<fuchsia::bluetooth::le::Peripheral> request) {
BindServer<LowEnergyPeripheralServer>(std::move(request));
}
void HostServer::RequestGattServer(
fidl::InterfaceRequest<fuchsia::bluetooth::gatt::Server> request) {
// GATT FIDL requests are handled by GattHost.
gatt_host_->BindGattServer(std::move(request));
}
void HostServer::RequestProfile(
fidl::InterfaceRequest<fuchsia::bluetooth::bredr::Profile> request) {
BindServer<ProfileServer>(std::move(request));
}
void HostServer::Close() {
bt_log(TRACE, "bt-host", "closing FIDL handles");
// Invalidate all weak pointers. This will guarantee that all pending tasks
// that reference this HostServer will return early if they run in the future.
weak_ptr_factory_.InvalidateWeakPtrs();
// Destroy all FIDL bindings.
servers_.clear();
gatt_host_->CloseServers();
// Cancel pending requests.
requesting_discovery_ = false;
requesting_discoverable_ = false;
le_discovery_session_ = nullptr;
bredr_discovery_session_ = nullptr;
bredr_discoverable_session_ = nullptr;
// Drop all connections that are attached to this HostServer.
le_connections_.clear();
// Stop background scan if enabled.
auto le_manager = adapter()->le_discovery_manager();
if (le_manager) {
le_manager->EnableBackgroundScan(false);
}
auto addr_mgr = adapter()->le_address_manager();
if (addr_mgr) {
addr_mgr->EnablePrivacy(false);
addr_mgr->set_irk(std::nullopt);
}
// Disallow future pairing.
pairing_delegate_ = nullptr;
ResetPairingDelegate();
// Send adapter state change.
if (binding()->is_bound()) {
NotifyInfoChange();
}
}
bt::sm::IOCapability HostServer::io_capability() const {
bt_log(TRACE, "bt-host", "I/O capability: %s",
bt::sm::util::IOCapabilityToString(io_capability_).c_str());
return io_capability_;
}
void HostServer::CompletePairing(PeerId id, bt::sm::Status status) {
bt_log(TRACE, "bt-host", "pairing complete for peer: %s, status: %s", bt_str(id), bt_str(status));
ZX_DEBUG_ASSERT(pairing_delegate_);
pairing_delegate_->OnPairingComplete(id.ToString(), StatusToFidlDeprecated(status));
}
void HostServer::ConfirmPairing(PeerId id, ConfirmCallback confirm) {
bt_log(TRACE, "bt-host", "pairing confirmation request for peer: %s", bt_str(id));
DisplayPairingRequest(id, std::nullopt, fuchsia::bluetooth::control::PairingMethod::CONSENT,
std::move(confirm));
}
void HostServer::DisplayPasskey(PeerId id, uint32_t passkey, DisplayMethod method,
ConfirmCallback confirm) {
auto fidl_method = fuchsia::bluetooth::control::PairingMethod::PASSKEY_DISPLAY;
if (method == DisplayMethod::kComparison) {
bt_log(TRACE, "bt-host", "compare passkey %06u on peer: %s", passkey, bt_str(id));
fidl_method = fuchsia::bluetooth::control::PairingMethod::PASSKEY_COMPARISON;
} else {
bt_log(TRACE, "bt-host", "enter passkey %06u on peer: %s", passkey, bt_str(id));
}
DisplayPairingRequest(id, passkey, fidl_method, std::move(confirm));
}
void HostServer::RequestPasskey(PeerId id, PasskeyResponseCallback respond) {
bt_log(TRACE, "bt-host", "passkey request for peer: %s", bt_str(id));
auto found_peer = adapter()->peer_cache()->FindById(id);
ZX_ASSERT(found_peer);
auto device = fidl_helpers::NewRemoteDevicePtr(*found_peer);
ZX_ASSERT(device);
ZX_ASSERT(pairing_delegate_);
pairing_delegate_->OnPairingRequest(
std::move(*device), fuchsia::bluetooth::control::PairingMethod::PASSKEY_ENTRY, nullptr,
[respond = std::move(respond)](const bool success, fidl::StringPtr passkey) {
bt_log(TRACE, "bt-host", "got peer response: %s, \"%s\"", success ? "success" : "failure",
passkey.has_value() ? passkey.value().c_str() : "(null)");
if (!success) {
respond(-1);
return;
}
if (!passkey.has_value()) {
bt_log(WARN, "bt-host", "Passkey not supplied");
respond(-1);
return;
}
uint32_t response;
if (!fxl::StringToNumberWithError<uint32_t>(passkey.value(), &response)) {
bt_log(WARN, "bt-host", "Unrecognized integer in string: \"%s\"",
passkey.value().c_str());
respond(-1);
return;
}
bt_log(SPEW, "bt-host", "got peer passkey: \"%u\"", response);
respond(response);
});
}
void HostServer::DisplayPairingRequest(bt::PeerId id, std::optional<uint32_t> passkey,
fuchsia::bluetooth::control::PairingMethod method,
ConfirmCallback confirm) {
auto found_peer = adapter()->peer_cache()->FindById(id);
ZX_ASSERT(found_peer);
auto device = fidl_helpers::NewRemoteDevicePtr(*found_peer);
ZX_ASSERT(device);
ZX_ASSERT(pairing_delegate_);
fidl::StringPtr displayed_passkey = nullptr;
if (passkey) {
displayed_passkey = fxl::StringPrintf("%06u", *passkey);
}
pairing_delegate_->OnPairingRequest(
std::move(*device), method, displayed_passkey,
[confirm = std::move(confirm)](const bool success, fidl::StringPtr passkey) {
bt_log(TRACE, "bt-host", "got peer response: %s, \"%s\"", success ? "success" : "failure",
passkey.has_value() ? passkey.value().c_str() : "(null)");
confirm(success);
});
}
void HostServer::OnConnectionError(Server* server) {
ZX_DEBUG_ASSERT(server);
servers_.erase(server);
}
void HostServer::OnPeerUpdated(const bt::gap::Peer& peer) {
if (!peer.connectable()) {
return;
}
watch_peers_getter_.Transform([id = peer.identifier()](auto tracker) {
tracker.Update(id);
return tracker;
});
}
void HostServer::OnPeerRemoved(bt::PeerId id) {
// TODO(armansito): Notify only if the peer is connectable for symmetry with
// OnPeerUpdated?
watch_peers_getter_.Transform([id](auto tracker) {
tracker.Remove(id);
return tracker;
});
}
void HostServer::ResetPairingDelegate() {
io_capability_ = IOCapability::kNoInputNoOutput;
adapter()->SetPairingDelegate(fxl::WeakPtr<HostServer>());
}
void HostServer::NotifyInfoChange() { info_getter_.Set(fidl_helpers::HostInfoToFidl(*adapter())); }
} // namespace bthost