| // 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 <fuchsia/mem/cpp/fidl.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()) { |
| // A request to restore an empty list of bonds succeeds immediately, as there is nothing to do |
| callback(Status()); |
| 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(SPEW, "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(fsys::InputCapability input, fsys::OutputCapability output, |
| ::fidl::InterfaceHandle<fsys::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(); |
| } |
| } |
| |
| void HostServer::GetInspectVmo(GetInspectVmoCallback callback) { |
| bt_log(TRACE, "bt-host", "Get Inspect Vmo"); |
| auto vmo = adapter()->InspectVmo(); |
| size_t vmo_size = 0; |
| auto status = vmo.get_size(&vmo_size); |
| ZX_ASSERT_MSG(status == ZX_OK, "Reading VMO size failed"); |
| fuchsia::mem::Buffer buffer = {.vmo = std::move(vmo), .size = vmo_size}; |
| callback(std::move(buffer)); |
| } |
| |
| 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(fbt::PeerId{id.value()}, status.is_success()); |
| } |
| |
| 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, fsys::PairingMethod::CONSENT, std::move(confirm)); |
| } |
| |
| void HostServer::DisplayPasskey(PeerId id, uint32_t passkey, DisplayMethod method, |
| ConfirmCallback confirm) { |
| auto fidl_method = fsys::PairingMethod::PASSKEY_DISPLAY; |
| if (method == DisplayMethod::kComparison) { |
| bt_log(TRACE, "bt-host", "compare passkey %06u on peer: %s", passkey, bt_str(id)); |
| fidl_method = fsys::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 peer = fidl_helpers::PeerToFidl(*found_peer); |
| |
| ZX_ASSERT(pairing_delegate_); |
| pairing_delegate_->OnPairingRequest( |
| std::move(peer), fsys::PairingMethod::PASSKEY_ENTRY, 0u, |
| [respond = std::move(respond)](const bool accept, uint32_t entered_passkey) { |
| bt_log(TRACE, "bt-host", "got peer response: %s, \"%u\"", accept ? "accept" : "reject", |
| entered_passkey); |
| if (!accept) { |
| respond(-1); |
| } else { |
| bt_log(SPEW, "bt-host", "got peer passkey: \"%u\"", entered_passkey); |
| respond(entered_passkey); |
| } |
| }); |
| } |
| |
| void HostServer::DisplayPairingRequest(bt::PeerId id, std::optional<uint32_t> passkey, |
| fsys::PairingMethod method, ConfirmCallback confirm) { |
| auto found_peer = adapter()->peer_cache()->FindById(id); |
| ZX_ASSERT(found_peer); |
| auto peer = fidl_helpers::PeerToFidl(*found_peer); |
| |
| ZX_ASSERT(pairing_delegate_); |
| uint32_t displayed_passkey = passkey ? *passkey : 0u; |
| pairing_delegate_->OnPairingRequest( |
| std::move(peer), method, displayed_passkey, |
| [confirm = std::move(confirm)](const bool accept, uint32_t entered_passkey) { |
| bt_log(TRACE, "bt-host", "got peer response: %s, \"%u\"", accept ? "accept" : "reject", |
| entered_passkey); |
| confirm(accept); |
| }); |
| } |
| |
| 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 |