| // Copyright 2023 The Pigweed Authors |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); you may not |
| // use this file except in compliance with the License. You may obtain a copy of |
| // the License at |
| // |
| // https://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
| // License for the specific language governing permissions and limitations under |
| // the License. |
| |
| #include "pw_bluetooth_sapphire/internal/host/hci/low_energy_connector.h" |
| |
| #include <endian.h> |
| |
| #include "pw_bluetooth_sapphire/internal/host/common/assert.h" |
| #include "pw_bluetooth_sapphire/internal/host/common/log.h" |
| #include "pw_bluetooth_sapphire/internal/host/hci-spec/defaults.h" |
| #include "pw_bluetooth_sapphire/internal/host/hci-spec/protocol.h" |
| #include "pw_bluetooth_sapphire/internal/host/hci/local_address_delegate.h" |
| #include "pw_bluetooth_sapphire/internal/host/hci/util.h" |
| #include "pw_bluetooth_sapphire/internal/host/transport/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( |
| Transport::WeakPtr hci, |
| LocalAddressDelegate* local_addr_delegate, |
| pw::async::Dispatcher& dispatcher, |
| IncomingConnectionDelegate delegate) |
| : pw_dispatcher_(dispatcher), |
| hci_(std::move(hci)), |
| local_addr_delegate_(local_addr_delegate), |
| delegate_(std::move(delegate)), |
| weak_self_(this) { |
| BT_DEBUG_ASSERT(hci_.is_alive()); |
| BT_DEBUG_ASSERT(local_addr_delegate_); |
| BT_DEBUG_ASSERT(delegate_); |
| |
| auto self = weak_self_.GetWeakPtr(); |
| event_handler_id_ = hci_->command_channel()->AddLEMetaEventHandler( |
| hci_spec::kLEConnectionCompleteSubeventCode, |
| [self](const EmbossEventPacket& event) { |
| if (self.is_alive()) { |
| return self->OnConnectionCompleteEvent(event); |
| } |
| return CommandChannel::EventCallbackResult::kRemove; |
| }); |
| |
| request_timeout_task_.set_function( |
| [this](pw::async::Context& /*ctx*/, pw::Status status) { |
| if (status.ok()) { |
| OnCreateConnectionTimeout(); |
| } |
| }); |
| } |
| |
| LowEnergyConnector::~LowEnergyConnector() { |
| if (hci_.is_alive() && hci_->command_channel()) { |
| hci_->command_channel()->RemoveEventHandler(event_handler_id_); |
| } |
| if (request_pending()) |
| Cancel(); |
| } |
| |
| 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)); |
| |
| local_addr_delegate_->EnsureLocalAddress( |
| [this, |
| use_accept_list, |
| 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_accept_list, |
| peer_address, |
| scan_interval, |
| scan_window, |
| initial_parameters, |
| std::move(callback), |
| timeout); |
| }); |
| |
| return true; |
| } |
| |
| 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_parameters, |
| 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; |
| |
| auto request = EmbossCommandPacket::New< |
| pw::bluetooth::emboss::LECreateConnectionCommandWriter>( |
| hci_spec::kLECreateConnection); |
| auto params = request.view_t(); |
| params.le_scan_interval().Write(scan_interval); |
| params.le_scan_window().Write(scan_window); |
| params.initiator_filter_policy().Write( |
| use_accept_list ? pw::bluetooth::emboss::GenericEnableParam::ENABLE |
| : pw::bluetooth::emboss::GenericEnableParam::DISABLE); |
| |
| // TODO(armansito): Use the resolved address types for <5.0 LE Privacy. |
| params.peer_address_type().Write( |
| peer_address.IsPublic() ? pw::bluetooth::emboss::LEAddressType::PUBLIC |
| : pw::bluetooth::emboss::LEAddressType::RANDOM); |
| params.peer_address().CopyFrom(peer_address.value().view()); |
| |
| params.own_address_type().Write( |
| local_address.IsPublic() |
| ? pw::bluetooth::emboss::LEOwnAddressType::PUBLIC |
| : pw::bluetooth::emboss::LEOwnAddressType::RANDOM); |
| |
| params.connection_interval_min().Write(initial_parameters.min_interval()); |
| params.connection_interval_max().Write(initial_parameters.max_interval()); |
| params.max_latency().Write(initial_parameters.max_latency()); |
| params.supervision_timeout().Write(initial_parameters.supervision_timeout()); |
| params.min_connection_event_length().Write(0x0000); |
| params.max_connection_event_length().Write(0x0000); |
| |
| // 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); |
| }; |
| |
| hci_->command_channel()->SendCommand( |
| std::move(request), complete_cb, hci_spec::kCommandStatusEventCode); |
| } |
| |
| void LowEnergyConnector::Cancel() { CancelInternal(false); } |
| |
| 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< |
| pw::bluetooth::emboss::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); |
| } |
| |
| CommandChannel::EventCallbackResult |
| LowEnergyConnector::OnConnectionCompleteEvent(const EmbossEventPacket& event) { |
| BT_DEBUG_ASSERT(event.event_code() == hci_spec::kLEMetaEventCode); |
| BT_DEBUG_ASSERT(event.view<pw::bluetooth::emboss::LEMetaEventView>() |
| .subevent_code() |
| .Read() == hci_spec::kLEConnectionCompleteSubeventCode); |
| |
| auto params = |
| event.view<pw::bluetooth::emboss::LEConnectionCompleteSubeventView>(); |
| |
| // First check if this event is related to the currently pending request. |
| const bool matches_pending_request = |
| pending_request_ && (pending_request_->peer_address.value() == |
| DeviceAddressBytes{params.peer_address()}); |
| |
| if (Result<> result = event.ToResult(); result.is_error()) { |
| 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) { |
| result = ToResult(HostError::kTimedOut); |
| } else if (params.status().Read() == |
| pw::bluetooth::emboss::StatusCode::UNKNOWN_CONNECTION_ID) { |
| result = ToResult(HostError::kCanceled); |
| } |
| OnCreateConnectionComplete(result, nullptr); |
| } else { |
| bt_log(WARN, |
| "hci-le", |
| "unexpected connection complete event with error received: %s", |
| bt_str(result)); |
| } |
| return CommandChannel::EventCallbackResult::kContinue; |
| } |
| |
| hci_spec::ConnectionHandle handle = params.connection_handle().Read(); |
| DeviceAddress peer_address( |
| DeviceAddress::LePeerAddrToDeviceAddr(params.peer_address_type().Read()), |
| DeviceAddressBytes(params.peer_address())); |
| hci_spec::LEConnectionParameters connection_params( |
| params.connection_interval().UncheckedRead(), |
| params.peripheral_latency().UncheckedRead(), |
| params.supervision_timeout().UncheckedRead()); |
| |
| // 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, params.role().Read(), 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 = |
| std::make_unique<LowEnergyConnection>(handle, |
| pending_request_->local_address, |
| peer_address, |
| connection_params, |
| params.role().Read(), |
| 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)); |
| return CommandChannel::EventCallbackResult::kContinue; |
| } |
| |
| 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 |