| // 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 "bredr_connection_manager.h" |
| |
| #include <zircon/assert.h> |
| |
| #include "garnet/drivers/bluetooth/lib/common/log.h" |
| #include "garnet/drivers/bluetooth/lib/gap/remote_device_cache.h" |
| #include "garnet/drivers/bluetooth/lib/hci/connection.h" |
| #include "garnet/drivers/bluetooth/lib/hci/hci_constants.h" |
| #include "garnet/drivers/bluetooth/lib/hci/sequential_command_runner.h" |
| #include "garnet/drivers/bluetooth/lib/hci/transport.h" |
| |
| namespace btlib { |
| namespace gap { |
| |
| using common::HostError; |
| |
| namespace { |
| |
| void SetPageScanEnabled(bool enabled, fxl::RefPtr<hci::Transport> hci, |
| async_dispatcher_t* dispatcher, |
| hci::StatusCallback cb) { |
| ZX_DEBUG_ASSERT(cb); |
| auto read_enable = hci::CommandPacket::New(hci::kReadScanEnable); |
| auto finish_enable_cb = [enabled, dispatcher, hci, finish_cb = std::move(cb)]( |
| auto, const hci::EventPacket& event) mutable { |
| if (hci_is_error(event, WARN, "gap-bredr", "read scan enable failed")) { |
| finish_cb(event.ToStatus()); |
| return; |
| } |
| |
| auto params = event.return_params<hci::ReadScanEnableReturnParams>(); |
| uint8_t scan_type = params->scan_enable; |
| if (enabled) { |
| scan_type |= static_cast<uint8_t>(hci::ScanEnableBit::kPage); |
| } else { |
| scan_type &= ~static_cast<uint8_t>(hci::ScanEnableBit::kPage); |
| } |
| auto write_enable = hci::CommandPacket::New( |
| hci::kWriteScanEnable, sizeof(hci::WriteScanEnableCommandParams)); |
| write_enable->mutable_view() |
| ->mutable_payload<hci::WriteScanEnableCommandParams>() |
| ->scan_enable = scan_type; |
| hci->command_channel()->SendCommand( |
| std::move(write_enable), dispatcher, |
| [cb = std::move(finish_cb), enabled]( |
| auto, const hci::EventPacket& event) { cb(event.ToStatus()); }); |
| }; |
| hci->command_channel()->SendCommand(std::move(read_enable), dispatcher, |
| std::move(finish_enable_cb)); |
| } |
| |
| } // namespace |
| |
| hci::CommandChannel::EventHandlerId BrEdrConnectionManager::AddEventHandler( |
| const hci::EventCode& code, hci::CommandChannel::EventCallback cb) { |
| auto self = weak_ptr_factory_.GetWeakPtr(); |
| auto event_id = hci_->command_channel()->AddEventHandler( |
| code, |
| [self, callback = std::move(cb)](const auto& event) { |
| if (self) { |
| callback(event); |
| } |
| }, |
| dispatcher_); |
| ZX_DEBUG_ASSERT(event_id); |
| return event_id; |
| } |
| |
| BrEdrConnectionManager::BrEdrConnectionManager( |
| fxl::RefPtr<hci::Transport> hci, RemoteDeviceCache* device_cache, |
| fbl::RefPtr<data::Domain> data_domain, bool use_interlaced_scan) |
| : hci_(hci), |
| cache_(device_cache), |
| data_domain_(data_domain), |
| interrogator_(cache_, hci_, async_get_default_dispatcher()), |
| page_scan_interval_(0), |
| page_scan_window_(0), |
| use_interlaced_scan_(use_interlaced_scan), |
| dispatcher_(async_get_default_dispatcher()), |
| weak_ptr_factory_(this) { |
| ZX_DEBUG_ASSERT(hci_); |
| ZX_DEBUG_ASSERT(cache_); |
| ZX_DEBUG_ASSERT(data_domain_); |
| ZX_DEBUG_ASSERT(dispatcher_); |
| |
| hci_cmd_runner_ = |
| std::make_unique<hci::SequentialCommandRunner>(dispatcher_, hci_); |
| |
| // Register event handlers |
| conn_complete_handler_id_ = AddEventHandler( |
| hci::kConnectionCompleteEventCode, |
| fbl::BindMember(this, &BrEdrConnectionManager::OnConnectionComplete)); |
| conn_request_handler_id_ = AddEventHandler( |
| hci::kConnectionRequestEventCode, |
| fbl::BindMember(this, &BrEdrConnectionManager::OnConnectionRequest)); |
| disconn_cmpl_handler_id_ = AddEventHandler( |
| hci::kDisconnectionCompleteEventCode, |
| fbl::BindMember(this, &BrEdrConnectionManager::OnDisconnectionComplete)); |
| io_cap_req_handler_id_ = AddEventHandler( |
| hci::kIOCapabilityRequestEventCode, |
| fbl::BindMember(this, &BrEdrConnectionManager::OnIOCapabilitiesRequest)); |
| user_conf_handler_id_ = AddEventHandler( |
| hci::kUserConfirmationRequestEventCode, |
| fbl::BindMember(this, |
| &BrEdrConnectionManager::OnUserConfirmationRequest)); |
| }; |
| |
| BrEdrConnectionManager::~BrEdrConnectionManager() { |
| // Disconnect any connections that we're holding. |
| connections_.clear(); |
| SetPageScanEnabled(false, hci_, dispatcher_, [](const auto) {}); |
| hci_->command_channel()->RemoveEventHandler(conn_request_handler_id_); |
| hci_->command_channel()->RemoveEventHandler(conn_complete_handler_id_); |
| hci_->command_channel()->RemoveEventHandler(disconn_cmpl_handler_id_); |
| hci_->command_channel()->RemoveEventHandler(io_cap_req_handler_id_); |
| hci_->command_channel()->RemoveEventHandler(user_conf_handler_id_); |
| } |
| |
| void BrEdrConnectionManager::SetConnectable(bool connectable, |
| hci::StatusCallback status_cb) { |
| auto self = weak_ptr_factory_.GetWeakPtr(); |
| if (!connectable) { |
| SetPageScanEnabled(false, hci_, dispatcher_, |
| [self, cb = std::move(status_cb)](const auto& status) { |
| if (self) { |
| self->page_scan_interval_ = 0; |
| self->page_scan_window_ = 0; |
| } else if (status) { |
| cb(hci::Status(common::HostError::kFailed)); |
| return; |
| } |
| cb(status); |
| }); |
| return; |
| } |
| |
| WritePageScanSettings( |
| hci::kPageScanR1Interval, hci::kPageScanR1Window, use_interlaced_scan_, |
| [self, cb = std::move(status_cb)](const auto& status) mutable { |
| if (bt_is_error(status, WARN, "gap-bredr", |
| "Write Page Scan Settings failed")) { |
| cb(status); |
| return; |
| } |
| if (!self) { |
| cb(hci::Status(common::HostError::kFailed)); |
| return; |
| } |
| SetPageScanEnabled(true, self->hci_, self->dispatcher_, std::move(cb)); |
| }); |
| } |
| |
| void BrEdrConnectionManager::SetPairingDelegate( |
| fxl::WeakPtr<PairingDelegate> delegate) { |
| // TODO(armansito): implement |
| } |
| |
| std::string BrEdrConnectionManager::GetPeerId( |
| hci::ConnectionHandle handle) const { |
| auto it = connections_.find(handle); |
| if (it == connections_.end()) { |
| return ""; |
| } |
| |
| auto* device = cache_->FindDeviceByAddress(it->second->peer_address()); |
| ZX_DEBUG_ASSERT_MSG(device, "Couldn't find device for handle %#.4x", handle); |
| return device->identifier(); |
| } |
| |
| void BrEdrConnectionManager::WritePageScanSettings(uint16_t interval, |
| uint16_t window, |
| bool interlaced, |
| hci::StatusCallback cb) { |
| auto self = weak_ptr_factory_.GetWeakPtr(); |
| if (!hci_cmd_runner_->IsReady()) { |
| // TODO(jamuraa): could run the three "settings" commands in parallel and |
| // remove the sequence runner. |
| cb(hci::Status(common::HostError::kInProgress)); |
| return; |
| } |
| |
| auto write_activity = |
| hci::CommandPacket::New(hci::kWritePageScanActivity, |
| sizeof(hci::WritePageScanActivityCommandParams)); |
| auto* activity_params = |
| write_activity->mutable_view() |
| ->mutable_payload<hci::WritePageScanActivityCommandParams>(); |
| activity_params->page_scan_interval = htole16(interval); |
| activity_params->page_scan_window = htole16(window); |
| |
| hci_cmd_runner_->QueueCommand( |
| std::move(write_activity), |
| [self, interval, window](const hci::EventPacket& event) { |
| if (!self || hci_is_error(event, WARN, "gap-bredr", |
| "write page scan activity failed")) { |
| return; |
| } |
| |
| self->page_scan_interval_ = interval; |
| self->page_scan_window_ = window; |
| |
| bt_log(SPEW, "gap-bredr", "page scan activity updated"); |
| }); |
| |
| auto write_type = hci::CommandPacket::New( |
| hci::kWritePageScanType, sizeof(hci::WritePageScanTypeCommandParams)); |
| auto* type_params = |
| write_type->mutable_view() |
| ->mutable_payload<hci::WritePageScanTypeCommandParams>(); |
| type_params->page_scan_type = (interlaced ? hci::PageScanType::kInterlacedScan |
| : hci::PageScanType::kStandardScan); |
| |
| hci_cmd_runner_->QueueCommand( |
| std::move(write_type), [self, interlaced](const hci::EventPacket& event) { |
| if (!self || hci_is_error(event, WARN, "gap-bredr", |
| "write page scan type failed")) { |
| return; |
| } |
| |
| self->page_scan_type_ = (interlaced ? hci::PageScanType::kInterlacedScan |
| : hci::PageScanType::kStandardScan); |
| |
| bt_log(SPEW, "gap-bredr", "page scan type updated"); |
| }); |
| |
| hci_cmd_runner_->RunCommands(std::move(cb)); |
| } |
| |
| void BrEdrConnectionManager::OnConnectionRequest( |
| const hci::EventPacket& event) { |
| ZX_DEBUG_ASSERT(event.event_code() == hci::kConnectionRequestEventCode); |
| const auto& params = |
| event.view().payload<hci::ConnectionRequestEventParams>(); |
| std::string link_type_str = |
| params.link_type == hci::LinkType::kACL ? "ACL" : "(e)SCO"; |
| |
| bt_log(TRACE, "gap-bredr", "%s conn request from %s (%s)", |
| link_type_str.c_str(), params.bd_addr.ToString().c_str(), |
| params.class_of_device.ToString().c_str()); |
| |
| if (params.link_type == hci::LinkType::kACL) { |
| // Accept the connection, performing a role switch. We receive a |
| // Connection Complete event when the connection is complete, and finish |
| // the link then. |
| bt_log(INFO, "gap-bredr", "accept incoming connection"); |
| |
| auto accept = hci::CommandPacket::New( |
| hci::kAcceptConnectionRequest, |
| sizeof(hci::AcceptConnectionRequestCommandParams)); |
| auto accept_params = |
| accept->mutable_view() |
| ->mutable_payload<hci::AcceptConnectionRequestCommandParams>(); |
| accept_params->bd_addr = params.bd_addr; |
| accept_params->role = hci::ConnectionRole::kMaster; |
| |
| hci_->command_channel()->SendCommand(std::move(accept), dispatcher_, |
| nullptr, hci::kCommandStatusEventCode); |
| return; |
| } |
| |
| // Reject this connection. |
| bt_log(INFO, "gap-bredr", "reject unsupported connection"); |
| |
| auto reject = hci::CommandPacket::New( |
| hci::kRejectConnectionRequest, |
| sizeof(hci::RejectConnectionRequestCommandParams)); |
| auto reject_params = |
| reject->mutable_view() |
| ->mutable_payload<hci::RejectConnectionRequestCommandParams>(); |
| reject_params->bd_addr = params.bd_addr; |
| reject_params->reason = hci::StatusCode::kConnectionRejectedBadBdAddr; |
| |
| hci_->command_channel()->SendCommand(std::move(reject), dispatcher_, nullptr, |
| hci::kCommandStatusEventCode); |
| } |
| |
| void BrEdrConnectionManager::OnConnectionComplete( |
| const hci::EventPacket& event) { |
| ZX_DEBUG_ASSERT(event.event_code() == hci::kConnectionCompleteEventCode); |
| const auto& params = |
| event.view().payload<hci::ConnectionCompleteEventParams>(); |
| bt_log(TRACE, "gap-bredr", |
| "%s connection complete (status %#.2x, handle: %#.4x)", |
| params.bd_addr.ToString().c_str(), params.status, |
| params.connection_handle); |
| if (hci_is_error(event, WARN, "gap-bredr", "connection error")) { |
| return; |
| } |
| common::DeviceAddress addr(common::DeviceAddress::Type::kBREDR, |
| params.bd_addr); |
| |
| // TODO(jamuraa): support non-master connections. |
| auto conn_ptr = hci::Connection::CreateACL( |
| params.connection_handle, hci::Connection::Role::kMaster, |
| common::DeviceAddress(), // TODO(armansito): Pass local BD_ADDR here. |
| addr, hci_); |
| |
| if (params.link_type != hci::LinkType::kACL) { |
| // Drop the connection if we don't support it. |
| return; |
| } |
| |
| RemoteDevice* device = cache_->FindDeviceByAddress(addr); |
| if (!device) { |
| device = cache_->NewDevice(addr, true); |
| } |
| |
| device->MutBrEdr().SetConnectionState( |
| RemoteDevice::ConnectionState::kInitializing); |
| |
| // Interrogate this device to find out its version/capabilities. |
| interrogator_.Start( |
| device->identifier(), std::move(conn_ptr), |
| [device, self = weak_ptr_factory_.GetWeakPtr()](auto status, |
| auto conn_ptr) { |
| if (bt_is_error(status, WARN, "gap-bredr", |
| "interrogate failed, dropping connection")) { |
| return; |
| } |
| |
| bt_log(SPEW, "gap-bredr", "interrogation complete for %#.4x", |
| conn_ptr->handle()); |
| |
| if (!self) { |
| return; |
| } |
| |
| // TODO(armansito): Implement this callback. |
| auto security_callback = [](hci::ConnectionHandle handle, |
| sm::SecurityLevel level, auto cb) { |
| bt_log(INFO, "gap-bredr", |
| "Ignoring security upgrade request; not implemented"); |
| cb(sm::Status(HostError::kNotSupported)); |
| }; |
| |
| // Register with L2CAP to handle services on the ACL signaling channel. |
| self->data_domain_->AddACLConnection( |
| conn_ptr->handle(), conn_ptr->role(), |
| [self, conn_ptr = conn_ptr->WeakPtr()] { |
| if (!self || !conn_ptr) { |
| return; |
| } |
| |
| bt_log(ERROR, "gap-bredr", |
| "Link error received, closing connection %#.4x", |
| conn_ptr->handle()); |
| |
| // Clean up after receiving the DisconnectComplete event. |
| // TODO(NET-1442): Test link error behavior using FakeDevice. |
| conn_ptr->Close(); |
| }, |
| std::move(security_callback), self->dispatcher_); |
| |
| self->connections_.emplace(conn_ptr->handle(), std::move(conn_ptr)); |
| device->MutBrEdr().SetConnectionState( |
| RemoteDevice::ConnectionState::kConnected); |
| // TODO(NET-1019): Start SDP service discovery. |
| }); |
| } |
| |
| void BrEdrConnectionManager::OnDisconnectionComplete( |
| const hci::EventPacket& event) { |
| ZX_DEBUG_ASSERT(event.event_code() == hci::kDisconnectionCompleteEventCode); |
| const auto& params = |
| event.view().payload<hci::DisconnectionCompleteEventParams>(); |
| |
| hci::ConnectionHandle handle = le16toh(params.connection_handle); |
| if (hci_is_error(event, WARN, "gap-bredr", |
| "HCI disconnection error handle %#.4x", handle)) { |
| return; |
| } |
| |
| auto it = connections_.find(handle); |
| |
| if (it == connections_.end()) { |
| bt_log(TRACE, "gap-bredr", "disconnect from unknown handle %#.4x", handle); |
| return; |
| } |
| |
| auto* device = cache_->FindDeviceByAddress(it->second->peer_address()); |
| ZX_DEBUG_ASSERT_MSG(device, "Couldn't find RemoteDevice for handle: %#.4x", |
| handle); |
| device->MutBrEdr().SetConnectionState( |
| RemoteDevice::ConnectionState::kNotConnected); |
| auto conn = std::move(it->second); |
| connections_.erase(it); |
| |
| bt_log(INFO, "gap-bredr", |
| "%s disconnected - %s, handle: %#.4x, reason: %#.2x", |
| device->identifier().c_str(), event.ToStatus().ToString().c_str(), |
| handle, params.reason); |
| |
| data_domain_->RemoveConnection(handle); |
| |
| // Connection is already closed, so we don't need to send a disconnect. |
| conn->set_closed(); |
| } |
| |
| void BrEdrConnectionManager::OnIOCapabilitiesRequest( |
| const hci::EventPacket& event) { |
| ZX_DEBUG_ASSERT(event.event_code() == hci::kIOCapabilityRequestEventCode); |
| const auto& params = |
| event.view().payload<hci::IOCapabilityRequestEventParams>(); |
| |
| auto reply = hci::CommandPacket::New( |
| hci::kIOCapabilityRequestReply, |
| sizeof(hci::IOCapabilityRequestReplyCommandParams)); |
| auto reply_params = |
| reply->mutable_view() |
| ->mutable_payload<hci::IOCapabilityRequestReplyCommandParams>(); |
| |
| reply_params->bd_addr = params.bd_addr; |
| // TODO(jamuraa, NET-882): ask the PairingDelegate if it's set what the IO |
| // capabilities it has. |
| reply_params->io_capability = hci::IOCapability::kNoInputNoOutput; |
| // TODO(NET-1155): Add OOB status from RemoteDeviceCache. |
| reply_params->oob_data_present = 0x00; // None present. |
| // TODO(jamuraa): Determine this based on the service requirements. |
| reply_params->auth_requirements = hci::AuthRequirements::kNoBonding; |
| |
| hci_->command_channel()->SendCommand(std::move(reply), dispatcher_, nullptr); |
| } |
| |
| void BrEdrConnectionManager::OnUserConfirmationRequest( |
| const hci::EventPacket& event) { |
| ZX_DEBUG_ASSERT(event.event_code() == hci::kUserConfirmationRequestEventCode); |
| const auto& params = |
| event.view().payload<hci::UserConfirmationRequestEventParams>(); |
| |
| bt_log(INFO, "gap-bredr", "auto-confirming pairing from %s (%lu)", |
| params.bd_addr.ToString().c_str(), params.numeric_value); |
| |
| // TODO(jamuraa, NET-882): if we are not NoInput/NoOutput then we need to ask |
| // the pairing delegate. This currently will auto accept any pairing |
| // (JustWorks) |
| auto reply = hci::CommandPacket::New( |
| hci::kUserConfirmationRequestReply, |
| sizeof(hci::UserConfirmationRequestReplyCommandParams)); |
| auto reply_params = |
| reply->mutable_view() |
| ->mutable_payload<hci::UserConfirmationRequestReplyCommandParams>(); |
| |
| reply_params->bd_addr = params.bd_addr; |
| |
| hci_->command_channel()->SendCommand(std::move(reply), dispatcher_, nullptr); |
| } |
| |
| } // namespace gap |
| } // namespace btlib |