| // 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 "garnet/drivers/bluetooth/lib/gap/adapter.h" |
| |
| #include "garnet/drivers/bluetooth/host/gatt_host.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_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 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::Key; |
| using fuchsia::bluetooth::control::LEData; |
| using fuchsia::bluetooth::control::LTK; |
| |
| 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), |
| io_capability_(IOCapability::kNoInputNoOutput), |
| weak_ptr_factory_(this) { |
| FXL_DCHECK(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 = weak_ptr_factory_.GetWeakPtr()](const auto& identifier) { |
| if (self) { |
| self->OnRemoteDeviceRemoved(identifier); |
| } |
| }); |
| adapter->remote_device_cache()->set_device_bonded_callback( |
| [self = weak_ptr_factory_.GetWeakPtr()](const auto& device) { |
| if (self) { |
| self->OnRemoteDeviceBonded(device); |
| } |
| }); |
| } |
| |
| void HostServer::GetInfo(GetInfoCallback callback) { |
| callback(fidl_helpers::NewAdapterInfo(*adapter())); |
| } |
| |
| void HostServer::SetLocalName(::fidl::StringPtr local_name, |
| SetLocalNameCallback callback) { |
| adapter()->SetLocalName( |
| local_name, [self = weak_ptr_factory_.GetWeakPtr(), |
| callback = std::move(callback)](auto status) { |
| 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 (!session) { |
| FXL_VLOG(1) << "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. |
| 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) { |
| FXL_VLOG(1) << "Adapter StartDiscovery()"; |
| FXL_DCHECK(adapter()); |
| |
| if (le_discovery_session_ || requesting_discovery_) { |
| FXL_VLOG(1) << "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 (!status || !session) { |
| FXL_VLOG(1) << "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) { |
| FXL_VLOG(1) << "Adapter StopDiscovery()"; |
| if (!le_discovery_session_) { |
| FXL_VLOG(1) << "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) { |
| FXL_VLOG(1) << "Adapter SetConnectable(" << connectable << ")"; |
| |
| 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( |
| ::fidl::VectorPtr<fuchsia::bluetooth::control::BondingData> bonds, |
| AddBondedDevicesCallback callback) { |
| if (!bonds) { |
| callback(fidl_helpers::NewFidlError(ErrorCode::NOT_SUPPORTED, |
| "No bonds were added")); |
| return; |
| } |
| |
| for (auto& bond : *bonds) { |
| // If LE Bond |
| if (bond.le) { |
| auto ltk = std::move(bond.le->ltk); |
| auto security = |
| fidl_helpers::NewSecurityLevel(ltk->key.security_properties); |
| |
| // Setup LTK to store |
| btlib::common::UInt128 key_data; |
| std::copy(ltk->key.value.begin(), ltk->key.value.begin() + 16, |
| key_data.begin()); |
| auto link_key = btlib::hci::LinkKey(key_data, ltk->rand, ltk->ediv); |
| auto store_ltk = btlib::sm::LTK(security, link_key); |
| |
| // Store the built ltk with the address |
| auto addr = btlib::common::DeviceAddress( |
| fidl_helpers::NewAddrType(bond.le->address_type), bond.le->address); |
| auto resp = adapter()->AddBondedDevice(bond.identifier, addr, store_ltk); |
| if (!resp) { |
| callback(fidl_helpers::NewFidlError( |
| ErrorCode::FAILED, "Devices were already present in cache")); |
| return; |
| } |
| } |
| } |
| callback(Status()); |
| } |
| |
| void HostServer::OnRemoteDeviceBonded( |
| const ::btlib::gap::RemoteDevice& remote_device) { |
| FXL_VLOG(1) << "HostServer::OnRemoteDeviceBonded"; |
| BondingData data; |
| data.identifier = remote_device.identifier().c_str(); |
| |
| // If the bond is LE |
| if (remote_device.technology() == btlib::gap::TechnologyType::kLowEnergy) { |
| data.le = LEData::New(); |
| data.le->address = remote_device.address().value().ToString(); |
| |
| if (remote_device.ltk()) { |
| data.le->ltk = fuchsia::bluetooth::control::LTK::New(); |
| // Set security properties |
| auto key_data = remote_device.ltk()->key().value().data(); |
| std::copy(key_data, key_data + 16, data.le->ltk->key.value.begin()); |
| data.le->ltk->key.security_properties.authenticated = |
| remote_device.ltk()->security().authenticated(); |
| data.le->ltk->key.security_properties.secure_connections = |
| remote_device.ltk()->security().secure_connections(); |
| data.le->ltk->key.security_properties.encryption_key_size = |
| remote_device.ltk()->security().enc_key_size(); |
| |
| data.le->ltk->key_size = remote_device.ltk()->security().enc_key_size(); |
| data.le->ltk->rand = remote_device.ltk()->key().rand(); |
| data.le->ltk->ediv = remote_device.ltk()->key().ediv(); |
| } |
| } |
| |
| this->binding()->events().OnNewBondingData(std::move(data)); |
| } |
| |
| void HostServer::SetDiscoverable(bool discoverable, |
| SetDiscoverableCallback callback) { |
| FXL_VLOG(1) << "Adapter SetDiscoverable(" << discoverable << ")"; |
| // 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_) { |
| FXL_VLOG(1) << "Discoverable already being set"; |
| 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 (!status || !session) { |
| FXL_VLOG(1) << "Failed to set discoverable"; |
| callback( |
| fidl_helpers::StatusToFidl(status, "Failed to set discoverable")); |
| self->requesting_discoverable_ = false; |
| } |
| self->bredr_discoverable_session_ = std::move(session); |
| AdapterState state; |
| state.discoverable = Bool::New(); |
| state.discoverable->value = true; |
| self->binding()->events().OnAdapterStateChanged(std::move(state)); |
| callback(Status()); |
| }); |
| } |
| |
| 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::SetPairingDelegate( |
| ::fuchsia::bluetooth::control::InputCapabilityType input, |
| ::fuchsia::bluetooth::control::OutputCapabilityType output, |
| ::fidl::InterfaceHandle<::fuchsia::bluetooth::control::PairingDelegate> |
| delegate) { |
| io_capability_ = fidl_helpers::NewIoCapability(input, output); |
| |
| auto self = weak_ptr_factory_.GetWeakPtr(); |
| adapter()->SetPairingDelegate(delegate ? self : fxl::WeakPtr<HostServer>()); |
| |
| pairing_delegate_.Bind(std::move(delegate)); |
| pairing_delegate_.set_error_handler([self] { |
| if (self) { |
| self->adapter()->le_connection_manager()->SetPairingDelegate( |
| fxl::WeakPtr<PairingDelegate>()); |
| FXL_VLOG(1) << "bt-host: Pairing Delegate disconnected"; |
| } |
| }); |
| } |
| |
| void HostServer::RequestProfile( |
| fidl::InterfaceRequest<fuchsia::bluetooth::bredr::Profile> request) { |
| BindServer<ProfileServer>(std::move(request)); |
| } |
| |
| void HostServer::Close() { |
| FXL_VLOG(1) << "bthost: Closing FIDL handles"; |
| |
| // Destroy all bindings. |
| servers_.clear(); |
| gatt_host_->CloseServers(); |
| } |
| |
| btlib::sm::IOCapability HostServer::io_capability() const { |
| FXL_VLOG(1) << "bthost: io capability: " |
| << btlib::sm::util::IOCapabilityToString(io_capability_); |
| return io_capability_; |
| } |
| |
| void HostServer::CompletePairing(std::string id, btlib::sm::Status status) { |
| FXL_LOG(INFO) << "bthost: Pairing complete for device: " << id |
| << ", status: " << status.ToString(); |
| |
| // TODO(armansito): implement |
| } |
| |
| void HostServer::ConfirmPairing(std::string id, ConfirmCallback confirm) { |
| FXL_LOG(INFO) << "bthost: Pairing request for device: " << id; |
| // TODO(armansito): Call out to PairingDelegate FIDL interface |
| confirm(true); |
| } |
| |
| void HostServer::DisplayPasskey(std::string id, uint32_t passkey, |
| ConfirmCallback confirm) { |
| FXL_LOG(INFO) << "bthost: Pairing request for device: " << id; |
| |
| // TODO(armansito): Call out to PairingDelegate FIDL interface |
| FXL_LOG(INFO) << fxl::StringPrintf("bthost: Enter passkey: %06u", passkey); |
| |
| confirm(true); |
| } |
| |
| void HostServer::RequestPasskey(std::string id, |
| PasskeyResponseCallback respond) { |
| // TODO(armansito): Call out to PairingDelegate FIDL interface |
| respond(-1); |
| } |
| |
| void HostServer::OnConnectionError(Server* server) { |
| FXL_DCHECK(server); |
| servers_.erase(server); |
| } |
| |
| void HostServer::OnRemoteDeviceUpdated( |
| const ::btlib::gap::RemoteDevice& remote_device) { |
| auto fidl_device = fidl_helpers::NewRemoteDevice(remote_device); |
| if (!fidl_device) { |
| FXL_VLOG(1) << "Ignoring malformed device update"; |
| return; |
| } |
| |
| this->binding()->events().OnDeviceUpdated(std::move(*fidl_device)); |
| } |
| |
| void HostServer::OnRemoteDeviceRemoved(const std::string& identifier) { |
| this->binding()->events().OnDeviceRemoved(identifier); |
| } |
| |
| } // namespace bthost |