blob: 785abcac1e68a70f62f995af3d03e459904f58f6 [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 <zircon/assert.h>
#include "garnet/drivers/bluetooth/host/gatt_host.h"
#include "garnet/drivers/bluetooth/lib/common/log.h"
#include "garnet/drivers/bluetooth/lib/gap/adapter.h"
#include "garnet/drivers/bluetooth/lib/gap/bredr_connection_manager.h"
#include "garnet/drivers/bluetooth/lib/gap/bredr_discovery_manager.h"
#include "garnet/drivers/bluetooth/lib/gap/gap.h"
#include "garnet/drivers/bluetooth/lib/gap/low_energy_discovery_manager.h"
#include "garnet/drivers/bluetooth/lib/sm/util.h"
#include "lib/fxl/logging.h"
#include "lib/fxl/strings/string_number_conversions.h"
#include "lib/fxl/strings/string_printf.h"
#include "helpers.h"
#include "low_energy_central_server.h"
#include "low_energy_peripheral_server.h"
#include "profile_server.h"
namespace bthost {
using btlib::sm::IOCapability;
using fidl_helpers::NewFidlError;
using fuchsia::bluetooth::Bool;
using fuchsia::bluetooth::ErrorCode;
using fuchsia::bluetooth::Status;
using fuchsia::bluetooth::control::AdapterState;
using fuchsia::bluetooth::control::RemoteDevice;
using fuchsia::bluetooth::host::BondingData;
HostServer::HostServer(zx::channel channel,
fxl::WeakPtr<::btlib::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),
weak_ptr_factory_(this) {
ZX_DEBUG_ASSERT(gatt_host_);
auto self = weak_ptr_factory_.GetWeakPtr();
adapter->remote_device_cache()->set_device_updated_callback(
[self](const auto& device) {
if (self) {
self->OnRemoteDeviceUpdated(device);
}
});
adapter->remote_device_cache()->set_device_removed_callback(
[self](const auto& identifier) {
if (self) {
self->OnRemoteDeviceRemoved(identifier);
}
});
adapter->remote_device_cache()->set_device_bonded_callback(
[self](const auto& device) {
if (self) {
self->OnRemoteDeviceBonded(device);
}
});
adapter->set_auto_connect_callback([self](auto conn_ref) {
if (self) {
self->OnConnect(std::move(conn_ref), true);
}
});
}
HostServer::~HostServer() { Close(); }
void HostServer::GetInfo(GetInfoCallback callback) {
callback(fidl_helpers::NewAdapterInfo(*adapter()));
}
void HostServer::ListDevices(ListDevicesCallback callback) {
std::vector<RemoteDevice> fidl_devices;
adapter()->remote_device_cache()->ForEach(
[&fidl_devices](const btlib::gap::RemoteDevice& dev) {
if (dev.connectable()) {
fidl_devices.push_back(fidl_helpers::NewRemoteDevice(dev));
}
});
callback(std::vector<RemoteDevice>(std::move(fidl_devices)));
}
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) {
AdapterState state;
state.local_name = std::move(local_name);
self->binding()->events().OnAdapterStateChanged(std::move(state));
}
callback(fidl_helpers::StatusToFidl(status, "Can't Set Local Name"));
});
}
void HostServer::StartLEDiscovery(StartDiscoveryCallback callback) {
auto le_manager = adapter()->le_discovery_manager();
if (!le_manager) {
callback(fidl_helpers::NewFidlError(ErrorCode::BAD_STATE,
"Adapter is not initialized yet."));
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(
fidl_helpers::NewFidlError(ErrorCode::FAILED, "Adapter Shutdown"));
return;
}
if (!self->requesting_discovery_) {
callback(
fidl_helpers::NewFidlError(ErrorCode::CANCELED, "Request canceled"));
return;
}
if (!session) {
bt_log(TRACE, "bt-host", "failed to start LE discovery session");
callback(fidl_helpers::NewFidlError(
ErrorCode::FAILED, "Failed to start LE discovery session"));
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 OnDeviceUpdated
// events are generated based on RemoteDeviceCache 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.
AdapterState state;
state.discovering = Bool::New();
state.discovering->value = true;
self->binding()->events().OnAdapterStateChanged(std::move(state));
callback(Status());
});
}
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(fidl_helpers::NewFidlError(ErrorCode::IN_PROGRESS,
"Discovery already 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)](
btlib::hci::Status status, auto session) mutable {
if (!self) {
callback(fidl_helpers::NewFidlError(ErrorCode::FAILED,
"Adapter Shutdown"));
return;
}
if (!self->requesting_discovery_) {
callback(fidl_helpers::NewFidlError(ErrorCode::CANCELED,
"Request Canceled"));
return;
}
if (!status || !session) {
bt_log(TRACE, "bt-host", "failed to start BR/EDR discovery session");
callback(fidl_helpers::StatusToFidl(
status, "Failed to start BR/EDR discovery session"));
self->requesting_discovery_ = false;
return;
}
self->bredr_discovery_session_ = std::move(session);
self->StartLEDiscovery(std::move(callback));
});
}
void HostServer::StopDiscovery(StopDiscoveryCallback callback) {
bt_log(TRACE, "bt-host", "StopDiscovery()");
if (!le_discovery_session_) {
bt_log(TRACE, "bt-host", "no active discovery session");
callback(fidl_helpers::NewFidlError(ErrorCode::BAD_STATE,
"No discovery session in progress"));
return;
}
bredr_discovery_session_ = nullptr;
le_discovery_session_ = nullptr;
AdapterState state;
state.discovering = Bool::New();
state.discovering->value = false;
this->binding()->events().OnAdapterStateChanged(std::move(state));
callback(Status());
}
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(fidl_helpers::NewFidlError(ErrorCode::NOT_SUPPORTED,
"Connectable mode not available"));
return;
}
bredr_conn_manager->SetConnectable(
connectable, [callback = std::move(callback)](const auto& status) {
callback(fidl_helpers::StatusToFidl(status));
});
}
void HostServer::AddBondedDevices(::std::vector<BondingData> bonds,
AddBondedDevicesCallback callback) {
bt_log(TRACE, "bt-host", "AddBondedDevices");
if (bonds.empty()) {
callback(fidl_helpers::NewFidlError(ErrorCode::NOT_SUPPORTED,
"No bonds were added"));
return;
}
for (auto& bond : bonds) {
btlib::sm::PairingData bond_data;
if (bond.le) {
bond_data = fidl_helpers::PairingDataFromFidl(*bond.le);
// Report error if bond data is missing a security key.
if (!bond_data.irk && !bond_data.csrk) {
bt_log(ERROR, "bt-host", "LE bond data is missing security key");
callback(fidl_helpers::NewFidlError(ErrorCode::INVALID_ARGUMENTS,
"LE IRK/CSRK missing"));
return;
}
}
// TODO(armansito): Handle BR/EDR data here. For now we skip the entry if LE
// data isn't available.
if (!bond_data.identity_address) {
bt_log(ERROR, "bt-host", "LE bonding data is required");
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()->AddBondedDevice(bond.identifier,
*bond_data.identity_address, bond_data)) {
// TODO(armansito): Continue walking the list if this fails?
callback(fidl_helpers::NewFidlError(
ErrorCode::FAILED,
fxl::StringPrintf("Failed to initialize bonded device (id: %s)",
bond.identifier.c_str())));
return;
}
}
callback(Status());
}
void HostServer::OnRemoteDeviceBonded(
const ::btlib::gap::RemoteDevice& remote_device) {
bt_log(TRACE, "bt-host", "OnRemoteDeviceBonded()");
binding()->events().OnNewBondingData(
fidl_helpers::NewBondingData(*adapter(), remote_device));
}
void HostServer::OnConnect(btlib::gap::LowEnergyConnectionRefPtr conn_ref,
bool auto_connect) {
ZX_DEBUG_ASSERT(conn_ref);
const auto& id = conn_ref->device_identifier();
auto iter = le_connections_.find(id);
if (iter != le_connections_.end()) {
bt_log(WARN, "bt-host", "%s device already connected; reference dropped",
(auto_connect ? "auto-connected" : "manually-connected"));
return;
}
bt_log(TRACE, "bt-host", "LE device %s: %s ",
(auto_connect ? "auto-connected" : "manually_connected"), id.c_str());
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;
AdapterState state;
state.discoverable = Bool::New();
state.discoverable->value = false;
this->binding()->events().OnAdapterStateChanged(std::move(state));
callback(Status());
return;
}
if (discoverable && requesting_discoverable_) {
bt_log(TRACE, "bt-host", "SetDiscoverable already in progress");
callback(fidl_helpers::NewFidlError(ErrorCode::IN_PROGRESS,
"SetDiscoverable already in progress"));
return;
}
requesting_discoverable_ = true;
auto bredr_manager = adapter()->bredr_discovery_manager();
if (!bredr_manager) {
callback(fidl_helpers::NewFidlError(ErrorCode::FAILED,
"Discoverable mode not available"));
return;
}
bredr_manager->RequestDiscoverable(
[self = weak_ptr_factory_.GetWeakPtr(), callback = std::move(callback)](
btlib::hci::Status status, auto session) {
if (!self) {
callback(fidl_helpers::NewFidlError(ErrorCode::FAILED,
"Adapter Shutdown"));
return;
}
if (!self->requesting_discoverable_) {
callback(fidl_helpers::NewFidlError(ErrorCode::CANCELED,
"Request canceled"));
return;
}
if (!status || !session) {
bt_log(TRACE, "bt-host", "failed to set discoverable");
callback(
fidl_helpers::StatusToFidl(status, "Failed to set discoverable"));
self->requesting_discoverable_ = false;
return;
}
self->bredr_discoverable_session_ = std::move(session);
self->requesting_discoverable_ = false;
AdapterState state;
state.discoverable = Bool::New();
state.discoverable->value = true;
self->binding()->events().OnAdapterStateChanged(std::move(state));
callback(Status());
});
}
void HostServer::EnableBackgroundScan(bool enabled) {
bt_log(TRACE, "bt-host", "%s background scan",
(enabled ? "enabled" : "disable"));
auto le_manager = adapter()->le_discovery_manager();
if (le_manager) {
le_manager->EnableBackgroundScan(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)",
btlib::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();
}
});
}
void HostServer::Connect(::std::string device_id,
ConnectCallback callback) {
auto device = adapter()->remote_device_cache()->FindDeviceById(device_id);
if (!device) {
// We don't support connections to devices not in our cache
callback(NewFidlError(ErrorCode::NOT_FOUND,
"Cannot find device with the given ID"));
return;
}
if (!device->le()) {
// TODO(NET-411): implement BR/EDR connect
// TODO(NET-411): If a dual-mode device, we attempt to connect both
// protocols, and if either fails, close the other and return failure
callback(
NewFidlError(ErrorCode::NOT_SUPPORTED,
"Device does not support LowEnergy connections, and "
"outgoing Classic connections are not yet supported"));
return;
}
// TODO(NET-411): Once dual-mode is supported, this logic will vary depending
// on whether we are initiating a BR/EDR connection as well. We may want to
// refactor this into a separate ConnectLowEnergy method.
auto self = weak_ptr_factory_.GetWeakPtr();
auto on_complete = [self, callback = std::move(callback),
peer_id = device_id](auto status, auto conn_ref) {
if (!status) {
ZX_DEBUG_ASSERT(!conn_ref);
bt_log(TRACE, "bt-host", "failed to connect to connect to device (id %s)",
peer_id.c_str());
callback(fidl_helpers::StatusToFidl(status, "failed to connect"));
} else {
ZX_DEBUG_ASSERT(conn_ref);
ZX_DEBUG_ASSERT(peer_id == conn_ref->device_identifier());
callback(Status());
}
if (self) {
self->OnConnect(std::move(conn_ref), false);
}
};
adapter()->le_connection_manager()->Connect(device_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;
// Diff for final adapter state update.
bool send_update = false;
AdapterState state;
// Stop all procedures initiated via host.
if (le_discovery_session_ || bredr_discovery_session_) {
send_update = true;
le_discovery_session_ = nullptr;
bredr_discovery_session_ = nullptr;
state.discovering = Bool::New();
state.discovering->value = false;
}
if (bredr_discoverable_session_) {
send_update = true;
bredr_discoverable_session_ = nullptr;
state.discoverable = Bool::New();
state.discoverable->value = false;
}
// 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);
}
// Disallow future pairing.
pairing_delegate_ = nullptr;
ResetPairingDelegate();
// Send adapter state change.
if (send_update) {
binding()->events().OnAdapterStateChanged(std::move(state));
}
}
btlib::sm::IOCapability HostServer::io_capability() const {
bt_log(TRACE, "bt-host", "I/O capability: %s",
btlib::sm::util::IOCapabilityToString(io_capability_).c_str());
return io_capability_;
}
void HostServer::CompletePairing(std::string id, btlib::sm::Status status) {
bt_log(INFO, "bt-host", "pairing complete for device: %s, status: %s",
id.c_str(), status.ToString().c_str());
ZX_DEBUG_ASSERT(pairing_delegate_);
pairing_delegate_->OnPairingComplete(std::move(id),
fidl_helpers::StatusToFidl(status));
}
void HostServer::ConfirmPairing(std::string id, ConfirmCallback confirm) {
bt_log(INFO, "bt-host", "pairing request for device: %s", id.c_str());
auto found_device = adapter()->remote_device_cache()->FindDeviceById(id);
ZX_DEBUG_ASSERT(found_device);
auto device = fidl_helpers::NewRemoteDevicePtr(*found_device);
ZX_DEBUG_ASSERT(device);
ZX_DEBUG_ASSERT(pairing_delegate_);
pairing_delegate_->OnPairingRequest(
std::move(*device), fuchsia::bluetooth::control::PairingMethod::CONSENT,
nullptr,
[confirm = std::move(confirm)](
const bool success, const std::string passkey) { confirm(success); });
}
void HostServer::DisplayPasskey(std::string id, uint32_t passkey,
ConfirmCallback confirm) {
bt_log(INFO, "bt-host", "pairing request for device: %s", id.c_str());
bt_log(INFO, "bt-host", "enter passkey: %06u", passkey);
auto device = fidl_helpers::NewRemoteDevicePtr(
*adapter()->remote_device_cache()->FindDeviceById(id));
ZX_DEBUG_ASSERT(device);
ZX_DEBUG_ASSERT(pairing_delegate_);
pairing_delegate_->OnPairingRequest(
std::move(*device),
fuchsia::bluetooth::control::PairingMethod::PASSKEY_DISPLAY,
fxl::StringPrintf("%06u", passkey),
[confirm = std::move(confirm)](
const bool success, const std::string passkey) { confirm(success); });
}
void HostServer::RequestPasskey(std::string id,
PasskeyResponseCallback respond) {
auto device = fidl_helpers::NewRemoteDevicePtr(
*adapter()->remote_device_cache()->FindDeviceById(id));
ZX_DEBUG_ASSERT(device);
ZX_DEBUG_ASSERT(pairing_delegate_);
pairing_delegate_->OnPairingRequest(
std::move(*device),
fuchsia::bluetooth::control::PairingMethod::PASSKEY_ENTRY, nullptr,
[respond = std::move(respond)](const bool success,
const std::string passkey) {
if (!success) {
respond(-1);
} else {
uint32_t response;
if (!fxl::StringToNumberWithError<uint32_t>(passkey, &response)) {
bt_log(ERROR, "bt-host", "Unrecognized integer in string: %s",
passkey.c_str());
respond(-1);
} else {
respond(response);
}
}
});
}
void HostServer::OnConnectionError(Server* server) {
ZX_DEBUG_ASSERT(server);
servers_.erase(server);
}
void HostServer::OnRemoteDeviceUpdated(
const ::btlib::gap::RemoteDevice& remote_device) {
if (!remote_device.connectable()) {
return;
}
auto fidl_device = fidl_helpers::NewRemoteDevicePtr(remote_device);
if (!fidl_device) {
bt_log(TRACE, "bt-host", "ignoring malformed device update");
return;
}
this->binding()->events().OnDeviceUpdated(std::move(*fidl_device));
}
void HostServer::OnRemoteDeviceRemoved(const std::string& identifier) {
// TODO(armansito): Notify only if the device is connectable for symmetry with
// OnDeviceUpdated?
this->binding()->events().OnDeviceRemoved(identifier);
}
void HostServer::ResetPairingDelegate() {
io_capability_ = IOCapability::kNoInputNoOutput;
adapter()->SetPairingDelegate(fxl::WeakPtr<HostServer>());
}
} // namespace bthost