| // 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 "src/connectivity/bluetooth/core/bt-host/common/log.h" |
| #include "src/connectivity/bluetooth/core/bt-host/gap/peer_cache.h" |
| #include "src/connectivity/bluetooth/core/bt-host/hci/connection.h" |
| #include "src/connectivity/bluetooth/core/bt-host/hci/hci_constants.h" |
| #include "src/connectivity/bluetooth/core/bt-host/hci/sequential_command_runner.h" |
| #include "src/connectivity/bluetooth/core/bt-host/hci/transport.h" |
| |
| namespace bt { |
| namespace gap { |
| |
| using std::unique_ptr; |
| using ConnectionState = Peer::ConnectionState; |
| |
| 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)]( |
| 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, PeerCache* peer_cache, |
| DeviceAddress local_address, fbl::RefPtr<data::Domain> data_domain, |
| bool use_interlaced_scan) |
| : hci_(hci), |
| cache_(peer_cache), |
| local_address_(local_address), |
| 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), |
| request_timeout_(kBrEdrCreateConnectionTimeout), |
| 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)); |
| link_key_request_handler_id_ = AddEventHandler( |
| hci::kLinkKeyRequestEventCode, |
| fbl::BindMember(this, &BrEdrConnectionManager::OnLinkKeyRequest)); |
| link_key_notification_handler_id_ = AddEventHandler( |
| hci::kLinkKeyNotificationEventCode, |
| fbl::BindMember(this, &BrEdrConnectionManager::OnLinkKeyNotification)); |
| 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() { |
| if (pending_request_ && pending_request_->Cancel()) |
| SendCreateConnectionCancelCommand(pending_request_->peer_address()); |
| |
| // 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(link_key_request_handler_id_); |
| hci_->command_channel()->RemoveEventHandler( |
| link_key_notification_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(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(HostError::kFailed)); |
| return; |
| } |
| SetPageScanEnabled(true, self->hci_, self->dispatcher_, std::move(cb)); |
| }); |
| } |
| |
| void BrEdrConnectionManager::SetPairingDelegate( |
| fxl::WeakPtr<PairingDelegate> delegate) { |
| // TODO(armansito): implement |
| } |
| |
| PeerId BrEdrConnectionManager::GetPeerId(hci::ConnectionHandle handle) const { |
| auto it = connections_.find(handle); |
| if (it == connections_.end()) { |
| return kInvalidPeerId; |
| } |
| |
| auto* peer = cache_->FindByAddress(it->second.link().peer_address()); |
| ZX_DEBUG_ASSERT_MSG(peer, "Couldn't find peer for handle %#.4x", handle); |
| return peer->identifier(); |
| } |
| |
| bool BrEdrConnectionManager::OpenL2capChannel(PeerId peer_id, l2cap::PSM psm, |
| SocketCallback cb, |
| async_dispatcher_t* dispatcher) { |
| auto handle = FindConnectionById(peer_id); |
| if (!handle) { |
| return false; |
| } |
| |
| data_domain_->OpenL2capChannel( |
| handle->first, psm, |
| [cb = std::move(cb)](zx::socket s, auto) { cb(std::move(s)); }, |
| dispatcher); |
| return true; |
| } |
| |
| BrEdrConnectionManager::SearchId BrEdrConnectionManager::AddServiceSearch( |
| const UUID& uuid, std::unordered_set<sdp::AttributeId> attributes, |
| BrEdrConnectionManager::SearchCallback callback) { |
| return discoverer_.AddSearch(uuid, std::move(attributes), |
| std::move(callback)); |
| } |
| |
| bool BrEdrConnectionManager::RemoveServiceSearch(SearchId id) { |
| return discoverer_.RemoveSearch(id); |
| } |
| |
| bool BrEdrConnectionManager::Disconnect(PeerId peer_id) { |
| auto handle = FindConnectionById(peer_id); |
| if (!handle) { |
| return false; |
| } |
| |
| auto& connection = connections_.find(handle->first)->second.link(); |
| if (!connection.is_open()) { |
| return false; |
| } |
| |
| connection.Close(); |
| return true; |
| } |
| |
| 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(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)); |
| } |
| |
| std::optional<std::pair<hci::ConnectionHandle, BrEdrConnection*>> |
| BrEdrConnectionManager::FindConnectionById(PeerId peer_id) { |
| auto* const peer = cache_->FindById(peer_id); |
| if (!peer || !peer->bredr() || !peer->bredr()->connected()) { |
| return std::nullopt; |
| } |
| |
| auto it = std::find_if( |
| connections_.begin(), connections_.end(), |
| [peer_id](const auto& c) { return c.second.peer_id() == peer_id; }); |
| |
| // If we're connected, we must have an ID. |
| ZX_ASSERT_MSG(it != connections_.end(), "couldn't find handle for peer %s", |
| bt_str(peer_id)); |
| |
| auto& [handle, conn_ptr] = *it; |
| ZX_ASSERT(conn_ptr.link().ll_type() != hci::Connection::LinkType::kLE); |
| |
| return std::pair(handle, &conn_ptr); |
| } |
| |
| 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); |
| } |
| |
| Peer* BrEdrConnectionManager::FindOrInitPeer(DeviceAddress addr) { |
| Peer* peer = cache_->FindByAddress(addr); |
| if (!peer) { |
| bool connectable = true; |
| peer = cache_->NewPeer(addr, connectable); |
| } |
| return peer; |
| } |
| |
| void BrEdrConnectionManager::OnConnectionComplete( |
| const hci::EventPacket& event) { |
| ZX_DEBUG_ASSERT(event.event_code() == hci::kConnectionCompleteEventCode); |
| |
| const auto& params = |
| event.view().payload<hci::ConnectionCompleteEventParams>(); |
| auto connection_handle = letoh16(params.connection_handle); |
| DeviceAddress addr(DeviceAddress::Type::kBREDR, params.bd_addr); |
| |
| bt_log(TRACE, "gap-bredr", |
| "%s connection complete (status %#.2x, handle: %#.4x)", |
| bt_str(params.bd_addr), params.status, connection_handle); |
| |
| if (pending_request_ && pending_request_->peer_address() == addr) { |
| auto status = hci::Status(params.status); |
| status = pending_request_->CompleteRequest(status); |
| |
| if (!status) |
| OnConnectFailure(status, pending_request_->peer_id()); |
| } |
| |
| if (params.link_type != hci::LinkType::kACL) { |
| // Drop the connection if we don't support it. |
| return; |
| } |
| |
| if (!hci_is_error(event, WARN, "gap-bredr", "connection error")) { |
| InitializeConnection(addr, std::move(connection_handle)); |
| } |
| } |
| |
| // Initialize a full Br/Edr connection from the hci::Connection |link| |
| // Initialization begins the interrogation process, once completed we establish |
| // a fully usable Br/Edr connection |
| void BrEdrConnectionManager::InitializeConnection(DeviceAddress addr, |
| uint16_t connection_handle) { |
| // TODO(BT-288): support non-master connections. |
| auto link = hci::Connection::CreateACL(connection_handle, |
| hci::Connection::Role::kMaster, |
| local_address_, addr, hci_); |
| |
| Peer* peer = FindOrInitPeer(addr); |
| // In Br/Edr, we should never establish more than one link to a given peer |
| ZX_DEBUG_ASSERT(!FindConnectionById(peer->identifier())); |
| peer->MutBrEdr().SetConnectionState(ConnectionState::kInitializing); |
| |
| // Interrogate this peer to find out its version/capabilities. |
| auto self = weak_ptr_factory_.GetWeakPtr(); |
| interrogator_.Start( |
| peer->identifier(), std::move(link), |
| [peer, self](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; |
| self->EstablishConnection(peer, status, std::move(conn_ptr)); |
| }); |
| } |
| |
| // Establish a full BrEdrConnection for a link that has been interrogated |
| void BrEdrConnectionManager::EstablishConnection( |
| Peer* peer, hci::Status status, unique_ptr<hci::Connection> connection) { |
| auto self = weak_ptr_factory_.GetWeakPtr(); |
| |
| auto error_handler = [self, connection = connection->WeakPtr()] { |
| if (!self || !connection) |
| return; |
| bt_log(ERROR, "gap-bredr", "Link error received, closing connection %#.4x", |
| connection->handle()); |
| |
| // Clean up after receiving the DisconnectComplete event. |
| // TODO(BT-70): Test link error behavior using FakePeer. |
| connection->Close(); |
| }; |
| |
| // 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. |
| data_domain_->AddACLConnection(connection->handle(), connection->role(), |
| error_handler, std::move(security_callback), |
| dispatcher_); |
| |
| auto handle = connection->handle(); |
| |
| auto conn = |
| connections_ |
| .try_emplace(handle, peer->identifier(), std::move(connection)) |
| .first; |
| peer->MutBrEdr().SetConnectionState(ConnectionState::kConnected); |
| |
| if (discoverer_.search_count()) { |
| data_domain_->OpenL2capChannel( |
| handle, l2cap::kSDP, |
| [self, peer_id = peer->identifier()](auto channel) { |
| if (!self) |
| return; |
| auto client = sdp::Client::Create(std::move(channel)); |
| |
| self->discoverer_.StartServiceDiscovery(peer_id, std::move(client)); |
| }, |
| dispatcher_); |
| } |
| |
| auto request = connection_requests_.extract(peer->identifier()); |
| if (request) { |
| auto conn_ptr = &(conn->second); |
| request.mapped().NotifyCallbacks(hci::Status(), |
| [conn_ptr] { return conn_ptr; }); |
| // If this was our in-flight request, close it |
| if (peer->address() == pending_request_->peer_address()) { |
| pending_request_.reset(); |
| } |
| TryCreateNextConnection(); |
| } |
| } |
| |
| 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* peer = cache_->FindByAddress(it->second.link().peer_address()); |
| bt_log(INFO, "gap-bredr", |
| "%s disconnected - %s, handle: %#.4x, reason: %#.2x", |
| bt_str(peer->identifier()), bt_str(event.ToStatus()), handle, |
| params.reason); |
| |
| CleanupConnection(handle, connections_.extract(handle).mapped(), true); |
| } |
| |
| void BrEdrConnectionManager::CleanupConnection(hci::ConnectionHandle handle, |
| BrEdrConnection& conn, |
| bool link_already_closed) { |
| auto* peer = cache_->FindByAddress(conn.link().peer_address()); |
| ZX_DEBUG_ASSERT_MSG(peer, "Couldn't find peer for handle: %#.4x", handle); |
| peer->MutBrEdr().SetConnectionState(ConnectionState::kNotConnected); |
| |
| data_domain_->RemoveConnection(handle); |
| |
| if (link_already_closed) { |
| // Connection is already closed, so we don't need to send a disconnect. |
| conn.link().set_closed(); |
| } |
| } |
| |
| void BrEdrConnectionManager::OnLinkKeyRequest(const hci::EventPacket& event) { |
| ZX_DEBUG_ASSERT(event.event_code() == hci::kLinkKeyRequestEventCode); |
| const auto& params = event.view().payload<hci::LinkKeyRequestParams>(); |
| |
| DeviceAddress addr(DeviceAddress::Type::kBREDR, params.bd_addr); |
| |
| auto* peer = cache_->FindByAddress(addr); |
| if (!peer || !peer->bredr()->bonded()) { |
| bt_log(INFO, "gap-bredr", "no known peer with address %s found", |
| addr.ToString().c_str()); |
| |
| auto reply = hci::CommandPacket::New( |
| hci::kLinkKeyRequestNegativeReply, |
| sizeof(hci::LinkKeyRequestNegativeReplyCommandParams)); |
| auto reply_params = |
| reply->mutable_view() |
| ->mutable_payload<hci::LinkKeyRequestNegativeReplyCommandParams>(); |
| |
| reply_params->bd_addr = params.bd_addr; |
| |
| hci_->command_channel()->SendCommand(std::move(reply), dispatcher_, |
| nullptr); |
| return; |
| } |
| |
| bt_log(INFO, "gap-bredr", "recalling link key for bonded peer %s", |
| bt_str(peer->identifier())); |
| |
| auto reply = hci::CommandPacket::New( |
| hci::kLinkKeyRequestReply, sizeof(hci::LinkKeyRequestReplyCommandParams)); |
| auto reply_params = |
| reply->mutable_view() |
| ->mutable_payload<hci::LinkKeyRequestReplyCommandParams>(); |
| |
| reply_params->bd_addr = params.bd_addr; |
| const sm::LTK& link_key = *peer->bredr()->link_key(); |
| ZX_DEBUG_ASSERT(link_key.security().enc_key_size() == 16); |
| const auto& key_value = link_key.key().value(); |
| std::copy(key_value.begin(), key_value.end(), reply_params->link_key); |
| |
| hci_->command_channel()->SendCommand( |
| std::move(reply), dispatcher_, [](auto, const hci::EventPacket& event) { |
| bt_log(SPEW, "gap-bredr", "completed Link Key Request Reply"); |
| }); |
| } |
| |
| void BrEdrConnectionManager::OnLinkKeyNotification( |
| const hci::EventPacket& event) { |
| ZX_DEBUG_ASSERT(event.event_code() == hci::kLinkKeyNotificationEventCode); |
| const auto& params = |
| event.view().payload<hci::LinkKeyNotificationEventParams>(); |
| |
| DeviceAddress addr(DeviceAddress::Type::kBREDR, params.bd_addr); |
| |
| bt_log(TRACE, "gap-bredr", "got link key (type %u) for address %s", |
| params.key_type, addr.ToString().c_str()); |
| |
| auto* peer = cache_->FindByAddress(addr); |
| if (!peer) { |
| bt_log(WARN, "gap-bredr", |
| "no known peer with address %s found; link key not stored", |
| addr.ToString().c_str()); |
| return; |
| } |
| |
| const auto key_type = static_cast<hci::LinkKeyType>(params.key_type); |
| sm::SecurityProperties sec_props; |
| if (key_type == hci::LinkKeyType::kChangedCombination) { |
| if (!peer->bredr() || !peer->bredr()->bonded()) { |
| bt_log(WARN, "gap-bredr", "can't update link key of unbonded peer %s", |
| bt_str(peer->identifier())); |
| return; |
| } |
| |
| // Reuse current properties |
| ZX_DEBUG_ASSERT(peer->bredr()->link_key()); |
| sec_props = peer->bredr()->link_key()->security(); |
| } else { |
| sec_props = sm::SecurityProperties(key_type); |
| } |
| |
| if (sec_props.level() == sm::SecurityLevel::kNoSecurity) { |
| bt_log(WARN, "gap-bredr", |
| "link key for peer %s has insufficient security; not stored", |
| bt_str(peer->identifier())); |
| return; |
| } |
| |
| UInt128 key_value; |
| std::copy(params.link_key, ¶ms.link_key[key_value.size()], |
| key_value.begin()); |
| sm::LTK key(sec_props, hci::LinkKey(key_value, 0, 0)); |
| if (!cache_->StoreBrEdrBond(addr, key)) { |
| bt_log(ERROR, "gap-bredr", "failed to cache bonding data (id: %s)", |
| bt_str(peer->identifier())); |
| } |
| } |
| |
| 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, BT-169): ask the PairingDelegate if it's set what the IO |
| // capabilities it has. |
| reply_params->io_capability = hci::IOCapability::kNoInputNoOutput; |
| // TODO(BT-8): Add OOB status from PeerCache. |
| reply_params->oob_data_present = 0x00; // None present. |
| // TODO(BT-656): Determine this based on the service requirements. |
| reply_params->auth_requirements = hci::AuthRequirements::kGeneralBonding; |
| |
| 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 (%u)", |
| bt_str(params.bd_addr), params.numeric_value); |
| |
| // TODO(jamuraa, BT-169): 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); |
| } |
| |
| bool BrEdrConnectionManager::Connect( |
| PeerId peer_id, ConnectResultCallback on_connection_result) { |
| Peer* peer = cache_->FindById(peer_id); |
| if (!peer) { |
| bt_log(WARN, "gap-bredr", "peer not found (id: %s)", bt_str(peer_id)); |
| return false; |
| } |
| |
| if (peer->technology() == TechnologyType::kLowEnergy) { |
| bt_log(ERROR, "gap-bredr", "peer does not support BrEdr: %s", |
| peer->ToString().c_str()); |
| return false; |
| } |
| |
| // Br/Edr peers should always be connectable by definition |
| ZX_ASSERT(peer->connectable()); |
| |
| // Succeed immediately if there is already an active connection. |
| auto conn = FindConnectionById(peer_id); |
| if (conn) { |
| async::PostTask(dispatcher_, |
| [conn = conn->second, |
| on_result = std::move(on_connection_result)]() mutable { |
| on_result(hci::Status(), conn); |
| }); |
| return true; |
| } |
| |
| // If we are already waiting to connect to |peer_id| then we store |
| // |on_connection_result| to be processed after the connection attempt |
| // completes (in either success of failure). |
| auto pending_iter = connection_requests_.find(peer_id); |
| if (pending_iter != connection_requests_.end()) { |
| pending_iter->second.AddCallback(std::move(on_connection_result)); |
| return true; |
| } |
| // If we are not already connected or pending, initiate a new connection |
| peer->MutBrEdr().SetConnectionState(ConnectionState::kInitializing); |
| connection_requests_.try_emplace(peer_id, peer->address(), |
| std::move(on_connection_result)); |
| |
| TryCreateNextConnection(); |
| |
| return true; |
| } |
| |
| void BrEdrConnectionManager::TryCreateNextConnection() { |
| // There can only be one outstanding BrEdr CreateConnection request at a time |
| if (pending_request_) |
| return; |
| |
| if (connection_requests_.empty()) { |
| bt_log(SPEW, "gap-bredr", "no pending requests remaining"); |
| return; |
| } |
| |
| Peer* peer = nullptr; |
| for (auto& request : connection_requests_) { |
| const auto& next_peer_addr = request.second.address(); |
| peer = cache_->FindByAddress(next_peer_addr); |
| if (peer && peer->bredr()) |
| break; |
| } |
| |
| auto self = weak_ptr_factory_.GetWeakPtr(); |
| auto on_failure = [self](hci::Status status, auto peer_id) { |
| if (self && !status) |
| self->OnConnectFailure(status, peer_id); |
| }; |
| auto on_timeout = [self] { |
| if (self) |
| self->OnRequestTimeout(); |
| }; |
| |
| if (peer) { |
| pending_request_.emplace(peer->identifier(), peer->address(), on_timeout); |
| pending_request_->CreateConnection( |
| hci_->command_channel(), dispatcher_, peer->bredr()->clock_offset(), |
| peer->bredr()->page_scan_repetition_mode(), request_timeout_, |
| on_failure); |
| } else { |
| // If there are no pending requests for peers which are in the cache, try |
| // to connect to a peer which has left the cache, in case it is still |
| // possible |
| auto request = connection_requests_.begin(); |
| if (request != connection_requests_.end()) { |
| auto identifier = request->first; |
| auto address = request->second.address(); |
| |
| pending_request_.emplace(identifier, address, on_timeout); |
| pending_request_->CreateConnection(hci_->command_channel(), dispatcher_, |
| std::nullopt, std::nullopt, |
| request_timeout_, on_failure); |
| } |
| } |
| } |
| |
| void BrEdrConnectionManager::OnConnectFailure(hci::Status status, |
| PeerId peer_id) { |
| // The request failed or timed out. |
| bt_log(ERROR, "gap-bredr", "failed to connect to peer (id: %s)", |
| bt_str(peer_id)); |
| Peer* peer = cache_->FindById(peer_id); |
| // The peer may no longer be in the cache by the time this function is called |
| if (peer) { |
| peer->MutBrEdr().SetConnectionState(ConnectionState::kNotConnected); |
| } |
| |
| pending_request_.reset(); |
| |
| // Notify the matching pending callbacks about the failure. |
| auto request = connection_requests_.extract(peer_id); |
| ZX_DEBUG_ASSERT(request); |
| request.mapped().NotifyCallbacks(status, [] { return nullptr; }); |
| |
| // Process the next pending attempt. |
| TryCreateNextConnection(); |
| } |
| |
| void BrEdrConnectionManager::OnRequestTimeout() { |
| if (pending_request_) { |
| pending_request_->Timeout(); |
| SendCreateConnectionCancelCommand(pending_request_->peer_address()); |
| } |
| } |
| |
| void BrEdrConnectionManager::SendCreateConnectionCancelCommand( |
| DeviceAddress addr) { |
| auto cancel = |
| hci::CommandPacket::New(hci::kCreateConnectionCancel, |
| sizeof(hci::CreateConnectionCancelCommandParams)); |
| auto params = |
| cancel->mutable_view() |
| ->mutable_payload<hci::CreateConnectionCancelCommandParams>(); |
| params->bd_addr = addr.value(); |
| hci_->command_channel()->SendCommand( |
| std::move(cancel), dispatcher_, [](auto, const hci::EventPacket& event) { |
| hci_is_error(event, WARN, "hci-bredr", |
| "failed to cancel connection request"); |
| }); |
| } |
| |
| } // namespace gap |
| } // namespace bt |