| // 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 "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/hci/low_energy_connector.h" |
| |
| #include <endian.h> |
| |
| #include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/common/assert.h" |
| #include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/common/log.h" |
| #include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/hci-spec/protocol.h" |
| #include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/hci/local_address_delegate.h" |
| #include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/transport/transport.h" |
| |
| namespace bt::hci { |
| using hci_spec::ConnectionHandle; |
| using hci_spec::LEConnectionParameters; |
| using pw::bluetooth::emboss::ConnectionRole; |
| using pw::bluetooth::emboss::GenericEnableParam; |
| using pw::bluetooth::emboss::LEAddressType; |
| using pw::bluetooth::emboss::LEConnectionCompleteSubeventView; |
| using pw::bluetooth::emboss::LECreateConnectionCancelCommandView; |
| using pw::bluetooth::emboss::LECreateConnectionCommandWriter; |
| using pw::bluetooth::emboss::LEEnhancedConnectionCompleteSubeventV1View; |
| using pw::bluetooth::emboss::LEExtendedCreateConnectionCommandV1Writer; |
| using pw::bluetooth::emboss::LEMetaEventView; |
| using pw::bluetooth::emboss::LEOwnAddressType; |
| using pw::bluetooth::emboss::LEPeerAddressType; |
| using pw::bluetooth::emboss::StatusCode; |
| |
| LowEnergyConnector::PendingRequest::PendingRequest( |
| const DeviceAddress& peer_address, StatusCallback status_callback) |
| : peer_address(peer_address), status_callback(std::move(status_callback)) {} |
| |
| LowEnergyConnector::LowEnergyConnector( |
| Transport::WeakPtr hci, |
| LocalAddressDelegate* local_addr_delegate, |
| pw::async::Dispatcher& dispatcher, |
| IncomingConnectionDelegate delegate, |
| bool use_extended_operations) |
| : pw_dispatcher_(dispatcher), |
| hci_(std::move(hci)), |
| local_addr_delegate_(local_addr_delegate), |
| delegate_(std::move(delegate)), |
| use_extended_operations_(use_extended_operations), |
| weak_self_(this) { |
| request_timeout_task_.set_function( |
| [this](pw::async::Context& /*ctx*/, pw::Status status) { |
| if (status.ok()) { |
| OnCreateConnectionTimeout(); |
| } |
| }); |
| |
| CommandChannel::EventHandlerId id = |
| hci_->command_channel()->AddLEMetaEventHandler( |
| hci_spec::kLEConnectionCompleteSubeventCode, |
| [this](const EmbossEventPacket& event) { |
| OnConnectionCompleteEvent<LEConnectionCompleteSubeventView>(event); |
| return CommandChannel::EventCallbackResult::kContinue; |
| }); |
| event_handler_ids_.insert(id); |
| |
| id = hci_->command_channel()->AddLEMetaEventHandler( |
| hci_spec::kLEEnhancedConnectionCompleteSubeventCode, |
| [this](const EmbossEventPacket& event) { |
| OnConnectionCompleteEvent<LEEnhancedConnectionCompleteSubeventV1View>( |
| event); |
| return CommandChannel::EventCallbackResult::kContinue; |
| }); |
| event_handler_ids_.insert(id); |
| } |
| |
| LowEnergyConnector::~LowEnergyConnector() { |
| if (request_pending()) { |
| Cancel(); |
| } |
| |
| if (hci_.is_alive() && hci_->command_channel()) { |
| for (CommandChannel::EventHandlerId id : event_handler_ids_) { |
| hci_->command_channel()->RemoveEventHandler(id); |
| } |
| } |
| } |
| |
| bool LowEnergyConnector::CreateConnection( |
| bool use_accept_list, |
| const DeviceAddress& peer_address, |
| uint16_t scan_interval, |
| uint16_t scan_window, |
| const hci_spec::LEPreferredConnectionParameters& initial_parameters, |
| StatusCallback status_callback, |
| pw::chrono::SystemClock::duration timeout) { |
| BT_DEBUG_ASSERT(status_callback); |
| BT_DEBUG_ASSERT(timeout.count() > 0); |
| |
| if (request_pending()) { |
| return false; |
| } |
| |
| BT_DEBUG_ASSERT(!request_timeout_task_.is_pending()); |
| pending_request_ = PendingRequest(peer_address, std::move(status_callback)); |
| |
| if (use_local_identity_address_) { |
| // Use the identity address if privacy override was enabled. |
| DeviceAddress address = local_addr_delegate_->identity_address(); |
| CreateConnectionInternal(address, |
| use_accept_list, |
| peer_address, |
| scan_interval, |
| scan_window, |
| initial_parameters, |
| std::move(status_callback), |
| timeout); |
| return true; |
| } |
| |
| local_addr_delegate_->EnsureLocalAddress( |
| [=, callback = std::move(status_callback)]( |
| const DeviceAddress& address) mutable { |
| CreateConnectionInternal(address, |
| use_accept_list, |
| peer_address, |
| scan_interval, |
| scan_window, |
| initial_parameters, |
| std::move(callback), |
| timeout); |
| }); |
| |
| return true; |
| } |
| |
| std::optional<DeviceAddress> LowEnergyConnector::pending_peer_address() const { |
| if (pending_request_) { |
| return pending_request_->peer_address; |
| } |
| |
| return std::nullopt; |
| } |
| |
| void LowEnergyConnector::CreateConnectionInternal( |
| const DeviceAddress& local_address, |
| bool use_accept_list, |
| const DeviceAddress& peer_address, |
| uint16_t scan_interval, |
| uint16_t scan_window, |
| const hci_spec::LEPreferredConnectionParameters& initial_params, |
| StatusCallback status_callback, |
| pw::chrono::SystemClock::duration timeout) { |
| if (!hci_.is_alive()) { |
| return; |
| } |
| |
| // 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; |
| } |
| |
| BT_DEBUG_ASSERT(!pending_request_->initiating); |
| |
| pending_request_->initiating = true; |
| pending_request_->local_address = local_address; |
| |
| // HCI Command Status Event will be sent as our completion callback. |
| auto self = weak_self_.GetWeakPtr(); |
| auto complete_cb = [self, timeout](auto id, const EventPacket& event) { |
| BT_DEBUG_ASSERT(event.event_code() == hci_spec::kCommandStatusEventCode); |
| |
| if (!self.is_alive()) { |
| return; |
| } |
| |
| Result<> result = event.ToResult(); |
| if (result.is_error()) { |
| self->OnCreateConnectionComplete(result, 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_.PostAfter(timeout); |
| }; |
| |
| std::optional<EmbossCommandPacket> request; |
| if (use_extended_operations_) { |
| request.emplace(BuildExtendedCreateConnectionPacket(local_address, |
| peer_address, |
| initial_params, |
| use_accept_list, |
| scan_interval, |
| scan_window)); |
| } else { |
| request.emplace(BuildCreateConnectionPacket(local_address, |
| peer_address, |
| initial_params, |
| use_accept_list, |
| scan_interval, |
| scan_window)); |
| } |
| |
| hci_->command_channel()->SendCommand(std::move(request.value()), |
| complete_cb, |
| hci_spec::kCommandStatusEventCode); |
| } |
| |
| EmbossCommandPacket LowEnergyConnector::BuildExtendedCreateConnectionPacket( |
| const DeviceAddress& local_address, |
| const DeviceAddress& peer_address, |
| const hci_spec::LEPreferredConnectionParameters& initial_params, |
| bool use_accept_list, |
| uint16_t scan_interval, |
| uint16_t scan_window) { |
| // The LE Extended Create Connection Command ends with a variable amount of |
| // data: per PHY connection settings. Depending on the PHYs we select to scan |
| // on when connecting, the variable amount of data at the end of the packet |
| // grows. Currently, we scan on all available PHYs. Thus, we use the maximum |
| // size of this packet. |
| size_t max_size = pw::bluetooth::emboss::LEExtendedCreateConnectionCommandV1:: |
| MaxSizeInBytes(); |
| |
| auto packet = |
| EmbossCommandPacket::New<LEExtendedCreateConnectionCommandV1Writer>( |
| hci_spec::kLEExtendedCreateConnection, max_size); |
| auto params = packet.view_t(); |
| |
| if (use_accept_list) { |
| params.initiator_filter_policy().Write(GenericEnableParam::ENABLE); |
| } else { |
| params.initiator_filter_policy().Write(GenericEnableParam::DISABLE); |
| } |
| |
| // TODO(http://b/328311582): Use the resolved address types for <5.0 LE |
| // Privacy. |
| if (peer_address.IsPublic()) { |
| params.peer_address_type().Write(LEPeerAddressType::PUBLIC); |
| } else { |
| params.peer_address_type().Write(LEPeerAddressType::RANDOM); |
| } |
| |
| if (local_address.IsPublic()) { |
| params.own_address_type().Write(LEOwnAddressType::PUBLIC); |
| } else { |
| params.own_address_type().Write(LEOwnAddressType::RANDOM); |
| } |
| |
| params.peer_address().CopyFrom(peer_address.value().view()); |
| |
| // We scan on all available PHYs for a connection |
| params.initiating_phys().le_1m().Write(true); |
| params.initiating_phys().le_2m().Write(true); |
| params.initiating_phys().le_coded().Write(true); |
| |
| for (int i = 0; i < params.num_entries().Read(); i++) { |
| params.data()[i].scan_interval().Write(scan_interval); |
| params.data()[i].scan_window().Write(scan_window); |
| params.data()[i].connection_interval_min().Write( |
| initial_params.min_interval()); |
| params.data()[i].connection_interval_max().Write( |
| initial_params.max_interval()); |
| params.data()[i].max_latency().Write(initial_params.max_latency()); |
| params.data()[i].supervision_timeout().Write( |
| initial_params.supervision_timeout()); |
| |
| // These fields provide the expected minimum and maximum duration of |
| // connection events. We have no preference, so we set them to zero and let |
| // the Controller decide. Some Controllers require |
| // max_ce_length < max_connection_interval * 2. |
| params.data()[i].min_connection_event_length().Write(0x0000); |
| params.data()[i].max_connection_event_length().Write(0x0000); |
| } |
| |
| return packet; |
| } |
| |
| EmbossCommandPacket LowEnergyConnector::BuildCreateConnectionPacket( |
| const DeviceAddress& local_address, |
| const DeviceAddress& peer_address, |
| const hci_spec::LEPreferredConnectionParameters& initial_params, |
| bool use_accept_list, |
| uint16_t scan_interval, |
| uint16_t scan_window) { |
| auto packet = EmbossCommandPacket::New<LECreateConnectionCommandWriter>( |
| hci_spec::kLECreateConnection); |
| auto params = packet.view_t(); |
| |
| if (use_accept_list) { |
| params.initiator_filter_policy().Write(GenericEnableParam::ENABLE); |
| } else { |
| params.initiator_filter_policy().Write(GenericEnableParam::DISABLE); |
| } |
| |
| // TODO(http://b/328311582): Use the resolved address types for <5.0 LE |
| // Privacy. |
| if (peer_address.IsPublic()) { |
| params.peer_address_type().Write(LEAddressType::PUBLIC); |
| } else { |
| params.peer_address_type().Write(LEAddressType::RANDOM); |
| } |
| |
| if (local_address.IsPublic()) { |
| params.own_address_type().Write(LEOwnAddressType::PUBLIC); |
| } else { |
| params.own_address_type().Write(LEOwnAddressType::RANDOM); |
| } |
| |
| params.le_scan_interval().Write(scan_interval); |
| params.le_scan_window().Write(scan_window); |
| params.peer_address().CopyFrom(peer_address.value().view()); |
| params.connection_interval_min().Write(initial_params.min_interval()); |
| params.connection_interval_max().Write(initial_params.max_interval()); |
| params.max_latency().Write(initial_params.max_latency()); |
| params.supervision_timeout().Write(initial_params.supervision_timeout()); |
| |
| // These fields provide the expected minimum and maximum duration of |
| // connection events. We have no preference, so we set them to zero and let |
| // the Controller decide. Some Controllers require |
| // max_ce_length < max_connection_interval * 2. |
| params.min_connection_event_length().Write(0x0000); |
| params.max_connection_event_length().Write(0x0000); |
| |
| return packet; |
| } |
| |
| void LowEnergyConnector::CancelInternal(bool timed_out) { |
| BT_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 && hci_.is_alive()) { |
| 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 = EmbossCommandPacket::New<LECreateConnectionCancelCommandView>( |
| hci_spec::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(ToResult(HostError::kCanceled), nullptr); |
| } |
| |
| template <typename T> |
| void LowEnergyConnector::OnConnectionCompleteEvent( |
| const EmbossEventPacket& event) { |
| auto params = event.view<T>(); |
| |
| DeviceAddress::Type address_type = |
| DeviceAddress::LeAddrToDeviceAddr(params.peer_address_type().Read()); |
| DeviceAddressBytes address_bytes = DeviceAddressBytes(params.peer_address()); |
| DeviceAddress peer_address = DeviceAddress(address_type, address_bytes); |
| |
| // First check if this event is related to the currently pending request. |
| const bool matches_pending_request = |
| pending_request_ && |
| (pending_request_->peer_address.value() == peer_address.value()); |
| |
| if (Result<> result = event.ToResult(); result.is_error()) { |
| if (!matches_pending_request) { |
| bt_log(WARN, |
| "hci-le", |
| "unexpected connection complete event with error received: %s", |
| bt_str(result)); |
| return; |
| } |
| |
| // 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) { |
| result = ToResult(HostError::kTimedOut); |
| } else if (params.status().Read() == StatusCode::UNKNOWN_CONNECTION_ID) { |
| result = ToResult(HostError::kCanceled); |
| } |
| |
| OnCreateConnectionComplete(result, nullptr); |
| return; |
| } |
| |
| ConnectionRole role = params.role().Read(); |
| ConnectionHandle handle = params.connection_handle().Read(); |
| LEConnectionParameters connection_params = |
| LEConnectionParameters(params.connection_interval().Read(), |
| params.peripheral_latency().Read(), |
| params.supervision_timeout().Read()); |
| |
| // 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; |
| } |
| |
| // A new link layer connection was created. Create an object to track this |
| // connection. Destroying this object will disconnect the link. |
| auto connection = |
| std::make_unique<LowEnergyConnection>(handle, |
| pending_request_->local_address, |
| peer_address, |
| connection_params, |
| role, |
| hci_); |
| |
| Result<> result = fit::ok(); |
| if (pending_request_->timed_out) { |
| result = ToResult(HostError::kTimedOut); |
| } else if (pending_request_->canceled) { |
| result = ToResult(HostError::kCanceled); |
| } |
| |
| // If we were requested to cancel the connection after the logical link |
| // is created we disconnect it. |
| if (result.is_error()) { |
| connection = nullptr; |
| } |
| |
| OnCreateConnectionComplete(result, std::move(connection)); |
| } |
| |
| void LowEnergyConnector::OnCreateConnectionComplete( |
| Result<> result, std::unique_ptr<LowEnergyConnection> link) { |
| BT_DEBUG_ASSERT(pending_request_); |
| bt_log(DEBUG, "hci-le", "connection complete - status: %s", bt_str(result)); |
| |
| request_timeout_task_.Cancel(); |
| |
| auto status_cb = std::move(pending_request_->status_callback); |
| pending_request_.reset(); |
| |
| status_cb(result, std::move(link)); |
| } |
| |
| void LowEnergyConnector::OnCreateConnectionTimeout() { |
| BT_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 filter accept list. |
| CancelInternal(true); |
| } |
| } // namespace bt::hci |