| // 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/bredr_connection_request.h" |
| |
| #include "pw_bluetooth_sapphire/internal/host/common/log.h" |
| #include "pw_bluetooth_sapphire/internal/host/hci-spec/protocol.h" |
| |
| namespace bt::hci { |
| |
| EmbossCommandPacket CreateConnectionPacket( |
| DeviceAddress address, |
| std::optional<pw::bluetooth::emboss::PageScanRepetitionMode> |
| page_scan_repetition_mode, |
| std::optional<uint16_t> clock_offset) { |
| auto request = EmbossCommandPacket::New< |
| pw::bluetooth::emboss::CreateConnectionCommandWriter>( |
| hci_spec::kCreateConnection); |
| auto params = request.view_t(); |
| params.bd_addr().CopyFrom(address.value().view()); |
| params.packet_type().BackingStorage().WriteUInt(kEnableAllPacketTypes); |
| |
| // The Page Scan Repetition Mode of the remote device as retrieved by Inquiry. |
| // If we do not have one for the device, opt for R2 so we will send for at |
| // least 2.56s |
| if (page_scan_repetition_mode) { |
| params.page_scan_repetition_mode().Write(*page_scan_repetition_mode); |
| } else { |
| params.page_scan_repetition_mode().Write( |
| pw::bluetooth::emboss::PageScanRepetitionMode::R2_); |
| } |
| |
| params.reserved().Write(0); // Reserved, must be set to 0. |
| |
| // Clock Offset. The lower 15 bits are set to the clock offset as retrieved |
| // by an Inquiry. The highest bit is set to 1 if the rest of this parameter |
| // is valid. If we don't have one, use the default. |
| if (clock_offset) { |
| params.clock_offset().valid().Write(true); |
| params.clock_offset().clock_offset().Write(*clock_offset); |
| } else { |
| params.clock_offset().valid().Write(false); |
| } |
| |
| params.allow_role_switch().Write( |
| pw::bluetooth::emboss::GenericEnableParam::DISABLE); |
| |
| return request; |
| } |
| |
| void BrEdrConnectionRequest::CreateConnection( |
| CommandChannel* command_channel, |
| std::optional<uint16_t> clock_offset, |
| std::optional<pw::bluetooth::emboss::PageScanRepetitionMode> |
| page_scan_repetition_mode, |
| pw::chrono::SystemClock::duration timeout, |
| OnCompleteDelegate on_command_fail) { |
| BT_DEBUG_ASSERT(timeout.count() > 0); |
| |
| // HCI Command Status Event will be sent as our completion callback. |
| auto self = weak_self_.GetWeakPtr(); |
| auto complete_cb = [self, |
| timeout, |
| peer_id = peer_id_, |
| on_command_fail = std::move(on_command_fail)]( |
| auto, const EventPacket& event) { |
| BT_DEBUG_ASSERT(event.event_code() == hci_spec::kCommandStatusEventCode); |
| |
| if (!self.is_alive()) |
| return; |
| |
| Result<> status = event.ToResult(); |
| if (status.is_error()) { |
| on_command_fail(status, peer_id); |
| } else { |
| // Both CommandChannel and the controller perform some scheduling, so log |
| // when the controller finally acknowledges Create Connection to observe |
| // outgoing connection sequencing. |
| // TODO(fxbug.dev/42173957): Added to investigate timing and can be |
| // removed if it adds no value |
| bt_log(INFO, |
| "hci-bredr", |
| "Create Connection for peer %s successfully dispatched", |
| bt_str(peer_id)); |
| |
| // 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 BrEdr Connection Complete event. |
| self->timeout_task_.PostAfter(timeout); |
| } |
| }; |
| |
| auto packet = CreateConnectionPacket( |
| peer_address_, page_scan_repetition_mode, clock_offset); |
| |
| bt_log(INFO, |
| "hci-bredr", |
| "initiating connection request (peer: %s)", |
| bt_str(peer_id_)); |
| command_channel->SendCommand(std::move(packet), |
| std::move(complete_cb), |
| hci_spec::kCommandStatusEventCode); |
| } |
| |
| // Status is either a Success or an Error value |
| Result<> BrEdrConnectionRequest::CompleteRequest(Result<> status) { |
| bt_log(INFO, |
| "hci-bredr", |
| "connection complete (status: %s, peer: %s)", |
| bt_str(status), |
| bt_str(peer_id_)); |
| timeout_task_.Cancel(); |
| |
| if (status.is_error()) { |
| if (state_ == RequestState::kTimedOut) { |
| return ToResult(HostError::kTimedOut); |
| } |
| if (status == |
| ToResult(pw::bluetooth::emboss::StatusCode::UNKNOWN_CONNECTION_ID)) { |
| // The "Unknown Connection Identifier" error code is returned if this |
| // event was sent due to a successful cancellation via the |
| // HCI_Create_Connection_Cancel command |
| // See Core Spec v5.0 Vol 2, Part E, Section 7.1.7 |
| state_ = RequestState::kCanceled; |
| return ToResult(HostError::kCanceled); |
| } |
| } |
| state_ = RequestState::kSuccess; |
| return status; |
| } |
| |
| void BrEdrConnectionRequest::Timeout() { |
| // If the request was cancelled, this handler will have been removed |
| BT_ASSERT(state_ == RequestState::kPending); |
| bt_log(INFO, |
| "hci-bredr", |
| "create connection timed out: canceling request (peer: %s)", |
| bt_str(peer_id_)); |
| state_ = RequestState::kTimedOut; |
| timeout_task_.Cancel(); |
| } |
| |
| bool BrEdrConnectionRequest::Cancel() { |
| if (state_ == RequestState::kSuccess) { |
| bt_log(DEBUG, |
| "hci-bredr", |
| "connection has already succeeded (peer: %s)", |
| bt_str(peer_id_)); |
| return false; |
| } |
| if (state_ != RequestState::kPending) { |
| bt_log(WARN, |
| "hci-bredr", |
| "connection attempt already canceled! (peer: %s)", |
| bt_str(peer_id_)); |
| return false; |
| } |
| // TODO(fxbug.dev/42143836) - We should correctly handle cancels due to a |
| // disconnect call during a pending connection creation attempt |
| bt_log(INFO, |
| "hci-bredr", |
| "canceling connection request (peer: %s)", |
| bt_str(peer_id_)); |
| state_ = RequestState::kCanceled; |
| timeout_task_.Cancel(); |
| return true; |
| } |
| |
| BrEdrConnectionRequest::~BrEdrConnectionRequest() { Cancel(); } |
| |
| } // namespace bt::hci |