| // 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 "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 { |
| |
| namespace { |
| |
| void SetPageScanEnabled(bool enabled, fxl::RefPtr<hci::Transport> hci, |
| async_dispatcher_t* dispatcher, hci::StatusCallback cb) { |
| FXL_DCHECK(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 (BTEV_TEST_WARN(event, "gap (BR/EDR): 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_); |
| FXL_DCHECK(event_id); |
| return event_id; |
| } |
| |
| BrEdrConnectionManager::BrEdrConnectionManager(fxl::RefPtr<hci::Transport> hci, |
| RemoteDeviceCache* device_cache, |
| bool use_interlaced_scan) |
| : hci_(hci), |
| cache_(device_cache), |
| 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) { |
| FXL_DCHECK(hci_); |
| FXL_DCHECK(cache_); |
| FXL_DCHECK(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 (!status) { |
| FXL_LOG(WARNING) << "gap (BR/EDR): Write Page Scan Settings failed: " |
| << status.ToString(); |
| 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 |
| } |
| |
| 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 (BTEV_TEST_WARN(event, |
| "gap (BR/EDR): write page scan activity failed")) { |
| return; |
| } |
| if (!self) { |
| return; |
| } |
| self->page_scan_interval_ = interval; |
| self->page_scan_window_ = window; |
| FXL_VLOG(2) << "gap (BR/EDR): 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 (BTEV_TEST_WARN(event, |
| "gap (BR/EDR): write page scan type failed")) { |
| return; |
| } |
| if (!self) { |
| return; |
| } |
| self->page_scan_type_ = (interlaced ? hci::PageScanType::kInterlacedScan |
| : hci::PageScanType::kStandardScan); |
| FXL_VLOG(2) << "gap (BR/EDR): page scan type updated"; |
| }); |
| |
| hci_cmd_runner_->RunCommands(std::move(cb)); |
| } |
| |
| void BrEdrConnectionManager::OnConnectionRequest( |
| const hci::EventPacket& event) { |
| FXL_DCHECK(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"; |
| FXL_VLOG(1) << "gap (BR/EDR): " << link_type_str << " conn request from " |
| << params.bd_addr.ToString() << "(" |
| << params.class_of_device.ToString() << ")"; |
| 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. |
| FXL_LOG(INFO) << "gap (BR/EDR): 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. |
| FXL_LOG(INFO) << "gap (BR/EDR): 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) { |
| FXL_DCHECK(event.event_code() == hci::kConnectionCompleteEventCode); |
| const auto& params = |
| event.view().payload<hci::ConnectionCompleteEventParams>(); |
| FXL_VLOG(1) << "gap (BR/EDR): " << params.bd_addr.ToString() |
| << fxl::StringPrintf( |
| " connection complete (status: 0x%02x handle: 0x%04x)", |
| params.status, params.connection_handle); |
| if (BTEV_TEST_WARN(event, "gap (BR/EDR): 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); |
| } |
| // Interrogate this device to find out it's version/capabilities. |
| interrogator_.Start( |
| device->identifier(), std::move(conn_ptr), |
| [device, self = weak_ptr_factory_.GetWeakPtr()](auto status, |
| auto conn_ptr) { |
| if (BT_TEST_WARN( |
| status, |
| "gap (BR/EDR): interrogate failed, dropping connection")) { |
| return; |
| } |
| |
| self->connections_.emplace(device->identifier(), std::move(conn_ptr)); |
| // TODO(NET-406, NET-407): set up the L2CAP signalling channel and |
| // start SDP service discovery. |
| }); |
| } |
| |
| void BrEdrConnectionManager::OnDisconnectionComplete( |
| const hci::EventPacket& event) { |
| FXL_DCHECK(event.event_code() == hci::kDisconnectionCompleteEventCode); |
| const auto& params = |
| event.view().payload<hci::DisconnectionCompleteEventParams>(); |
| |
| hci::ConnectionHandle handle = le16toh(params.connection_handle); |
| if (BTEV_TEST_WARN( |
| event, |
| fxl::StringPrintf( |
| "gap (BR/EDR): HCI disconnection error handle 0x%04x", handle))) { |
| return; |
| } |
| |
| auto it = std::find_if( |
| connections_.begin(), connections_.end(), |
| [handle](const auto& p) { return (p.second->handle() == handle); }); |
| |
| if (it == connections_.end()) { |
| FXL_VLOG(1) << fxl::StringPrintf( |
| "gap (BR/EDR): disconnect from unknown handle 0x%04x", handle); |
| return; |
| } |
| std::string device = it->first; |
| auto conn = std::move(it->second); |
| connections_.erase(it); |
| |
| FXL_LOG(INFO) << fxl::StringPrintf( |
| "gap (BR/EDR): %s disconnected - %s, handle: 0x%04x, reason: 0x%02x", |
| device.c_str(), event.ToStatus().ToString().c_str(), handle, |
| params.reason); |
| |
| // TODO(NET-406): Inform L2CAP that the connection has been disconnected. |
| |
| // Connection is already closed, so we don't need to send a disconnect. |
| conn->set_closed(); |
| } |
| |
| void BrEdrConnectionManager::OnIOCapabilitiesRequest( |
| const hci::EventPacket& event) { |
| FXL_DCHECK(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) { |
| FXL_DCHECK(event.event_code() == hci::kUserConfirmationRequestEventCode); |
| const auto& params = |
| event.view().payload<hci::UserConfirmationRequestEventParams>(); |
| |
| FXL_LOG(INFO) << "gap (BR/EDR): auto-confirming to " << params.bd_addr << " (" |
| << 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 |