| // Copyright 2017 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 "low_energy_connector.h" |
| |
| #include <endian.h> |
| #include <lib/async/default.h> |
| #include <zircon/assert.h> |
| |
| #include "src/connectivity/bluetooth/core/bt-host/common/log.h" |
| #include "src/connectivity/bluetooth/core/bt-host/hci-spec/defaults.h" |
| #include "src/connectivity/bluetooth/core/bt-host/hci-spec/protocol.h" |
| #include "src/connectivity/bluetooth/core/bt-host/hci-spec/util.h" |
| #include "src/connectivity/bluetooth/core/bt-host/hci/local_address_delegate.h" |
| #include "src/connectivity/bluetooth/core/bt-host/hci/transport.h" |
| |
| namespace bt::hci { |
| |
| LowEnergyConnector::PendingRequest::PendingRequest(const DeviceAddress& peer_address, |
| StatusCallback status_callback) |
| : peer_address(peer_address), status_callback(std::move(status_callback)) {} |
| |
| LowEnergyConnector::LowEnergyConnector(fxl::WeakPtr<Transport> hci, |
| LocalAddressDelegate* local_addr_delegate, |
| async_dispatcher_t* dispatcher, |
| IncomingConnectionDelegate delegate) |
| : dispatcher_(dispatcher), |
| hci_(hci), |
| local_addr_delegate_(local_addr_delegate), |
| delegate_(std::move(delegate)), |
| weak_ptr_factory_(this) { |
| ZX_DEBUG_ASSERT(dispatcher_); |
| ZX_DEBUG_ASSERT(hci_); |
| ZX_DEBUG_ASSERT(local_addr_delegate_); |
| ZX_DEBUG_ASSERT(delegate_); |
| |
| auto self = weak_ptr_factory_.GetWeakPtr(); |
| event_handler_id_ = hci_->command_channel()->AddLEMetaEventHandler( |
| kLEConnectionCompleteSubeventCode, [self](const auto& event) { |
| if (self) { |
| return self->OnConnectionCompleteEvent(event); |
| } |
| return CommandChannel::EventCallbackResult::kRemove; |
| }); |
| } |
| |
| LowEnergyConnector::~LowEnergyConnector() { |
| hci_->command_channel()->RemoveEventHandler(event_handler_id_); |
| if (request_pending()) |
| Cancel(); |
| } |
| |
| bool LowEnergyConnector::CreateConnection(bool use_whitelist, const DeviceAddress& peer_address, |
| uint16_t scan_interval, uint16_t scan_window, |
| const LEPreferredConnectionParameters& initial_parameters, |
| StatusCallback status_callback, zx::duration timeout) { |
| ZX_DEBUG_ASSERT(thread_checker_.is_thread_valid()); |
| ZX_DEBUG_ASSERT(status_callback); |
| ZX_DEBUG_ASSERT(timeout.get() > 0); |
| |
| if (request_pending()) |
| return false; |
| |
| ZX_DEBUG_ASSERT(!request_timeout_task_.is_pending()); |
| pending_request_ = PendingRequest(peer_address, std::move(status_callback)); |
| |
| local_addr_delegate_->EnsureLocalAddress( |
| [this, use_whitelist, peer_address, scan_interval, scan_window, initial_parameters, |
| callback = std::move(status_callback), timeout](const auto& address) mutable { |
| // Use the identity address if privacy override was enabled. |
| CreateConnectionInternal( |
| use_local_identity_address_ ? local_addr_delegate_->identity_address() : address, |
| use_whitelist, peer_address, scan_interval, scan_window, initial_parameters, |
| std::move(callback), timeout); |
| }); |
| |
| return true; |
| } |
| |
| void LowEnergyConnector::CreateConnectionInternal( |
| const DeviceAddress& local_address, bool use_whitelist, const DeviceAddress& peer_address, |
| uint16_t scan_interval, uint16_t scan_window, |
| const LEPreferredConnectionParameters& initial_parameters, StatusCallback status_callback, |
| zx::duration timeout) { |
| // Check if the connection request was canceled via Cancel(). |
| if (!pending_request_ || pending_request_->canceled) { |
| bt_log(DEBUG, "hci-le", "connection request was canceled while obtaining local address"); |
| pending_request_.reset(); |
| return; |
| } |
| |
| ZX_DEBUG_ASSERT(!pending_request_->initiating); |
| |
| pending_request_->initiating = true; |
| pending_request_->local_address = local_address; |
| |
| auto request = CommandPacket::New(kLECreateConnection, sizeof(LECreateConnectionCommandParams)); |
| auto params = request->mutable_payload<LECreateConnectionCommandParams>(); |
| params->scan_interval = htole16(scan_interval); |
| params->scan_window = htole16(scan_window); |
| params->initiator_filter_policy = |
| use_whitelist ? GenericEnableParam::kEnable : GenericEnableParam::kDisable; |
| |
| // TODO(armansito): Use the resolved address types for <5.0 LE Privacy. |
| params->peer_address_type = |
| peer_address.IsPublic() ? LEAddressType::kPublic : LEAddressType::kRandom; |
| params->peer_address = peer_address.value(); |
| |
| params->own_address_type = |
| local_address.IsPublic() ? LEOwnAddressType::kPublic : LEOwnAddressType::kRandom; |
| |
| params->conn_interval_min = htole16(initial_parameters.min_interval()); |
| params->conn_interval_max = htole16(initial_parameters.max_interval()); |
| params->conn_latency = htole16(initial_parameters.max_latency()); |
| params->supervision_timeout = htole16(initial_parameters.supervision_timeout()); |
| params->minimum_ce_length = 0x0000; |
| params->maximum_ce_length = 0x0000; |
| |
| // HCI Command Status Event will be sent as our completion callback. |
| auto self = weak_ptr_factory_.GetWeakPtr(); |
| auto complete_cb = [self, timeout](auto id, const EventPacket& event) { |
| ZX_DEBUG_ASSERT(event.event_code() == kCommandStatusEventCode); |
| |
| if (!self) |
| return; |
| |
| Status status = event.ToStatus(); |
| if (!status) { |
| self->OnCreateConnectionComplete(Status(status), nullptr); |
| return; |
| } |
| |
| // The request was started but has not completed; initiate the command |
| // timeout period. NOTE: The request will complete when the controller |
| // asynchronously notifies us of with a LE Connection Complete event. |
| self->request_timeout_task_.Cancel(); |
| self->request_timeout_task_.PostDelayed(async_get_default_dispatcher(), timeout); |
| }; |
| |
| hci_->command_channel()->SendCommand(std::move(request), complete_cb, kCommandStatusEventCode); |
| } |
| |
| void LowEnergyConnector::Cancel() { CancelInternal(false); } |
| |
| void LowEnergyConnector::CancelInternal(bool timed_out) { |
| ZX_DEBUG_ASSERT(request_pending()); |
| |
| if (pending_request_->canceled) { |
| bt_log(WARN, "hci-le", "connection attempt already canceled!"); |
| return; |
| } |
| |
| // At this point we do not know whether the pending connection request has |
| // completed or not (it may have completed in the controller but that does not |
| // mean that we have processed the corresponding LE Connection Complete |
| // event). Below we mark the request as canceled and tell the controller to |
| // cancel its pending connection attempt. |
| pending_request_->canceled = true; |
| pending_request_->timed_out = timed_out; |
| |
| request_timeout_task_.Cancel(); |
| |
| // Tell the controller to cancel the connection initiation attempt if a |
| // request is outstanding. Otherwise there is no need to talk to the |
| // controller. |
| if (pending_request_->initiating) { |
| bt_log(DEBUG, "hci-le", "telling controller to cancel LE connection attempt"); |
| auto complete_cb = [](auto id, const EventPacket& event) { |
| hci_is_error(event, WARN, "hci-le", "failed to cancel connection request"); |
| }; |
| auto cancel = CommandPacket::New(kLECreateConnectionCancel); |
| hci_->command_channel()->SendCommand(std::move(cancel), complete_cb); |
| |
| // A connection complete event will be generated by the controller after processing the cancel |
| // command. |
| return; |
| } |
| |
| bt_log(DEBUG, "hci-le", "connection initiation aborted"); |
| OnCreateConnectionComplete(Status(HostError::kCanceled), nullptr); |
| } |
| |
| CommandChannel::EventCallbackResult LowEnergyConnector::OnConnectionCompleteEvent( |
| const EventPacket& event) { |
| ZX_DEBUG_ASSERT(event.event_code() == kLEMetaEventCode); |
| ZX_DEBUG_ASSERT(event.params<LEMetaEventParams>().subevent_code == |
| kLEConnectionCompleteSubeventCode); |
| |
| auto params = event.le_event_params<LEConnectionCompleteSubeventParams>(); |
| ZX_ASSERT(params); |
| |
| // First check if this event is related to the currently pending request. |
| const bool matches_pending_request = |
| pending_request_ && (pending_request_->peer_address.value() == params->peer_address); |
| |
| Status status(params->status); |
| if (!status) { |
| if (matches_pending_request) { |
| // The "Unknown Connect Identifier" error code is returned if this event |
| // was sent due to a successful cancelation via the |
| // HCI_LE_Create_Connection_Cancel command (sent by Cancel()). |
| if (pending_request_->timed_out) { |
| status = Status(HostError::kTimedOut); |
| } else if (params->status == StatusCode::kUnknownConnectionId) { |
| status = Status(HostError::kCanceled); |
| } |
| OnCreateConnectionComplete(status, nullptr); |
| } else { |
| bt_log(WARN, "hci-le", "unexpected connection complete event with error received: %s", |
| status.ToString().c_str()); |
| } |
| return CommandChannel::EventCallbackResult::kContinue; |
| } |
| |
| ConnectionHandle handle = le16toh(params->connection_handle); |
| Connection::Role role = (params->role == ConnectionRole::kMaster) ? Connection::Role::kMaster |
| : Connection::Role::kSlave; |
| DeviceAddress peer_address(AddressTypeFromHCI(params->peer_address_type), params->peer_address); |
| LEConnectionParameters connection_params(le16toh(params->conn_interval), |
| le16toh(params->conn_latency), |
| le16toh(params->supervision_timeout)); |
| |
| // If the connection did not match a pending request then we pass the |
| // information down to the incoming connection delegate. |
| if (!matches_pending_request) { |
| delegate_(handle, role, peer_address, connection_params); |
| return CommandChannel::EventCallbackResult::kContinue; |
| } |
| |
| // A new link layer connection was created. Create an object to track this |
| // connection. Destroying this object will disconnect the link. |
| auto connection = Connection::CreateLE(handle, role, pending_request_->local_address, |
| peer_address, connection_params, hci_); |
| |
| if (pending_request_->timed_out) { |
| status = Status(HostError::kTimedOut); |
| } else if (pending_request_->canceled) { |
| status = Status(HostError::kCanceled); |
| } else { |
| status = Status(); |
| } |
| |
| // If we were requested to cancel the connection after the logical link |
| // is created we disconnect it. |
| if (!status) { |
| connection = nullptr; |
| } |
| OnCreateConnectionComplete(status, std::move(connection)); |
| return CommandChannel::EventCallbackResult::kContinue; |
| } |
| |
| void LowEnergyConnector::OnCreateConnectionComplete(Status status, ConnectionPtr link) { |
| ZX_DEBUG_ASSERT(pending_request_); |
| |
| bt_log(DEBUG, "hci-le", "connection complete - status: %s", status.ToString().c_str()); |
| |
| request_timeout_task_.Cancel(); |
| |
| auto status_cb = std::move(pending_request_->status_callback); |
| pending_request_.reset(); |
| |
| status_cb(status, std::move(link)); |
| } |
| |
| void LowEnergyConnector::OnCreateConnectionTimeout() { |
| ZX_DEBUG_ASSERT(pending_request_); |
| bt_log(INFO, "hci-le", "create connection timed out: canceling request"); |
| |
| // TODO(armansito): This should cancel the connection attempt only if the |
| // connection attempt isn't using the white list. |
| CancelInternal(true); |
| } |
| |
| } // namespace bt::hci |