1 // Copyright 2023 The Pigweed Authors
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License"); you may not
4 // use this file except in compliance with the License. You may obtain a copy of
5 // the License at
6 //
7 // https://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12 // License for the specific language governing permissions and limitations under
13 // the License.
14
15 #include "pw_bluetooth_sapphire/internal/host/hci/low_energy_connector.h"
16
17 #include <endian.h>
18
19 #include "pw_bluetooth_sapphire/internal/host/common/assert.h"
20 #include "pw_bluetooth_sapphire/internal/host/common/log.h"
21 #include "pw_bluetooth_sapphire/internal/host/hci-spec/defaults.h"
22 #include "pw_bluetooth_sapphire/internal/host/hci-spec/protocol.h"
23 #include "pw_bluetooth_sapphire/internal/host/hci/local_address_delegate.h"
24 #include "pw_bluetooth_sapphire/internal/host/hci/util.h"
25 #include "pw_bluetooth_sapphire/internal/host/transport/transport.h"
26
27 namespace bt::hci {
28
PendingRequest(const DeviceAddress & peer_address,StatusCallback status_callback)29 LowEnergyConnector::PendingRequest::PendingRequest(
30 const DeviceAddress& peer_address, StatusCallback status_callback)
31 : peer_address(peer_address), status_callback(std::move(status_callback)) {}
32
LowEnergyConnector(Transport::WeakPtr hci,LocalAddressDelegate * local_addr_delegate,pw::async::Dispatcher & dispatcher,IncomingConnectionDelegate delegate)33 LowEnergyConnector::LowEnergyConnector(
34 Transport::WeakPtr hci,
35 LocalAddressDelegate* local_addr_delegate,
36 pw::async::Dispatcher& dispatcher,
37 IncomingConnectionDelegate delegate)
38 : pw_dispatcher_(dispatcher),
39 hci_(std::move(hci)),
40 local_addr_delegate_(local_addr_delegate),
41 delegate_(std::move(delegate)),
42 weak_self_(this) {
43 BT_DEBUG_ASSERT(hci_.is_alive());
44 BT_DEBUG_ASSERT(local_addr_delegate_);
45 BT_DEBUG_ASSERT(delegate_);
46
47 auto self = weak_self_.GetWeakPtr();
48 event_handler_id_ = hci_->command_channel()->AddLEMetaEventHandler(
49 hci_spec::kLEConnectionCompleteSubeventCode,
50 [self](const EmbossEventPacket& event) {
51 if (self.is_alive()) {
52 return self->OnConnectionCompleteEvent(event);
53 }
54 return CommandChannel::EventCallbackResult::kRemove;
55 });
56
57 request_timeout_task_.set_function(
58 [this](pw::async::Context& /*ctx*/, pw::Status status) {
59 if (status.ok()) {
60 OnCreateConnectionTimeout();
61 }
62 });
63 }
64
~LowEnergyConnector()65 LowEnergyConnector::~LowEnergyConnector() {
66 if (hci_.is_alive() && hci_->command_channel()) {
67 hci_->command_channel()->RemoveEventHandler(event_handler_id_);
68 }
69 if (request_pending())
70 Cancel();
71 }
72
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)73 bool LowEnergyConnector::CreateConnection(
74 bool use_accept_list,
75 const DeviceAddress& peer_address,
76 uint16_t scan_interval,
77 uint16_t scan_window,
78 const hci_spec::LEPreferredConnectionParameters& initial_parameters,
79 StatusCallback status_callback,
80 pw::chrono::SystemClock::duration timeout) {
81 BT_DEBUG_ASSERT(status_callback);
82 BT_DEBUG_ASSERT(timeout.count() > 0);
83
84 if (request_pending())
85 return false;
86
87 BT_DEBUG_ASSERT(!request_timeout_task_.is_pending());
88 pending_request_ = PendingRequest(peer_address, std::move(status_callback));
89
90 local_addr_delegate_->EnsureLocalAddress(
91 [this,
92 use_accept_list,
93 peer_address,
94 scan_interval,
95 scan_window,
96 initial_parameters,
97 callback = std::move(status_callback),
98 timeout](const auto& address) mutable {
99 // Use the identity address if privacy override was enabled.
100 CreateConnectionInternal(use_local_identity_address_
101 ? local_addr_delegate_->identity_address()
102 : address,
103 use_accept_list,
104 peer_address,
105 scan_interval,
106 scan_window,
107 initial_parameters,
108 std::move(callback),
109 timeout);
110 });
111
112 return true;
113 }
114
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)115 void LowEnergyConnector::CreateConnectionInternal(
116 const DeviceAddress& local_address,
117 bool use_accept_list,
118 const DeviceAddress& peer_address,
119 uint16_t scan_interval,
120 uint16_t scan_window,
121 const hci_spec::LEPreferredConnectionParameters& initial_parameters,
122 StatusCallback status_callback,
123 pw::chrono::SystemClock::duration timeout) {
124 if (!hci_.is_alive()) {
125 return;
126 }
127 // Check if the connection request was canceled via Cancel().
128 if (!pending_request_ || pending_request_->canceled) {
129 bt_log(DEBUG,
130 "hci-le",
131 "connection request was canceled while obtaining local address");
132 pending_request_.reset();
133 return;
134 }
135
136 BT_DEBUG_ASSERT(!pending_request_->initiating);
137
138 pending_request_->initiating = true;
139 pending_request_->local_address = local_address;
140
141 auto request = EmbossCommandPacket::New<
142 pw::bluetooth::emboss::LECreateConnectionCommandWriter>(
143 hci_spec::kLECreateConnection);
144 auto params = request.view_t();
145 params.le_scan_interval().Write(scan_interval);
146 params.le_scan_window().Write(scan_window);
147 params.initiator_filter_policy().Write(
148 use_accept_list ? pw::bluetooth::emboss::GenericEnableParam::ENABLE
149 : pw::bluetooth::emboss::GenericEnableParam::DISABLE);
150
151 // TODO(armansito): Use the resolved address types for <5.0 LE Privacy.
152 params.peer_address_type().Write(
153 peer_address.IsPublic() ? pw::bluetooth::emboss::LEAddressType::PUBLIC
154 : pw::bluetooth::emboss::LEAddressType::RANDOM);
155 params.peer_address().CopyFrom(peer_address.value().view());
156
157 params.own_address_type().Write(
158 local_address.IsPublic()
159 ? pw::bluetooth::emboss::LEOwnAddressType::PUBLIC
160 : pw::bluetooth::emboss::LEOwnAddressType::RANDOM);
161
162 params.connection_interval_min().Write(initial_parameters.min_interval());
163 params.connection_interval_max().Write(initial_parameters.max_interval());
164 params.max_latency().Write(initial_parameters.max_latency());
165 params.supervision_timeout().Write(initial_parameters.supervision_timeout());
166 params.min_connection_event_length().Write(0x0000);
167 params.max_connection_event_length().Write(0x0000);
168
169 // HCI Command Status Event will be sent as our completion callback.
170 auto self = weak_self_.GetWeakPtr();
171 auto complete_cb = [self, timeout](auto id, const EventPacket& event) {
172 BT_DEBUG_ASSERT(event.event_code() == hci_spec::kCommandStatusEventCode);
173
174 if (!self.is_alive())
175 return;
176
177 Result<> result = event.ToResult();
178 if (result.is_error()) {
179 self->OnCreateConnectionComplete(result, nullptr);
180 return;
181 }
182
183 // The request was started but has not completed; initiate the command
184 // timeout period. NOTE: The request will complete when the controller
185 // asynchronously notifies us of with a LE Connection Complete event.
186 self->request_timeout_task_.Cancel();
187 self->request_timeout_task_.PostAfter(timeout);
188 };
189
190 hci_->command_channel()->SendCommand(
191 std::move(request), complete_cb, hci_spec::kCommandStatusEventCode);
192 }
193
Cancel()194 void LowEnergyConnector::Cancel() { CancelInternal(false); }
195
CancelInternal(bool timed_out)196 void LowEnergyConnector::CancelInternal(bool timed_out) {
197 BT_DEBUG_ASSERT(request_pending());
198
199 if (pending_request_->canceled) {
200 bt_log(WARN, "hci-le", "connection attempt already canceled!");
201 return;
202 }
203
204 // At this point we do not know whether the pending connection request has
205 // completed or not (it may have completed in the controller but that does not
206 // mean that we have processed the corresponding LE Connection Complete
207 // event). Below we mark the request as canceled and tell the controller to
208 // cancel its pending connection attempt.
209 pending_request_->canceled = true;
210 pending_request_->timed_out = timed_out;
211
212 request_timeout_task_.Cancel();
213
214 // Tell the controller to cancel the connection initiation attempt if a
215 // request is outstanding. Otherwise there is no need to talk to the
216 // controller.
217 if (pending_request_->initiating && hci_.is_alive()) {
218 bt_log(
219 DEBUG, "hci-le", "telling controller to cancel LE connection attempt");
220 auto complete_cb = [](auto id, const EventPacket& event) {
221 hci_is_error(
222 event, WARN, "hci-le", "failed to cancel connection request");
223 };
224 auto cancel = EmbossCommandPacket::New<
225 pw::bluetooth::emboss::LECreateConnectionCancelCommandView>(
226 hci_spec::kLECreateConnectionCancel);
227 hci_->command_channel()->SendCommand(std::move(cancel), complete_cb);
228
229 // A connection complete event will be generated by the controller after
230 // processing the cancel command.
231 return;
232 }
233
234 bt_log(DEBUG, "hci-le", "connection initiation aborted");
235 OnCreateConnectionComplete(ToResult(HostError::kCanceled), nullptr);
236 }
237
238 CommandChannel::EventCallbackResult
OnConnectionCompleteEvent(const EmbossEventPacket & event)239 LowEnergyConnector::OnConnectionCompleteEvent(const EmbossEventPacket& event) {
240 BT_DEBUG_ASSERT(event.event_code() == hci_spec::kLEMetaEventCode);
241 BT_DEBUG_ASSERT(event.view<pw::bluetooth::emboss::LEMetaEventView>()
242 .subevent_code()
243 .Read() == hci_spec::kLEConnectionCompleteSubeventCode);
244
245 auto params =
246 event.view<pw::bluetooth::emboss::LEConnectionCompleteSubeventView>();
247
248 // First check if this event is related to the currently pending request.
249 const bool matches_pending_request =
250 pending_request_ && (pending_request_->peer_address.value() ==
251 DeviceAddressBytes{params.peer_address()});
252
253 if (Result<> result = event.ToResult(); result.is_error()) {
254 if (matches_pending_request) {
255 // The "Unknown Connect Identifier" error code is returned if this event
256 // was sent due to a successful cancelation via the
257 // HCI_LE_Create_Connection_Cancel command (sent by Cancel()).
258 if (pending_request_->timed_out) {
259 result = ToResult(HostError::kTimedOut);
260 } else if (params.status().Read() ==
261 pw::bluetooth::emboss::StatusCode::UNKNOWN_CONNECTION_ID) {
262 result = ToResult(HostError::kCanceled);
263 }
264 OnCreateConnectionComplete(result, nullptr);
265 } else {
266 bt_log(WARN,
267 "hci-le",
268 "unexpected connection complete event with error received: %s",
269 bt_str(result));
270 }
271 return CommandChannel::EventCallbackResult::kContinue;
272 }
273
274 hci_spec::ConnectionHandle handle = params.connection_handle().Read();
275 DeviceAddress peer_address(
276 DeviceAddress::LePeerAddrToDeviceAddr(params.peer_address_type().Read()),
277 DeviceAddressBytes(params.peer_address()));
278 hci_spec::LEConnectionParameters connection_params(
279 params.connection_interval().UncheckedRead(),
280 params.peripheral_latency().UncheckedRead(),
281 params.supervision_timeout().UncheckedRead());
282
283 // If the connection did not match a pending request then we pass the
284 // information down to the incoming connection delegate.
285 if (!matches_pending_request) {
286 delegate_(handle, params.role().Read(), peer_address, connection_params);
287 return CommandChannel::EventCallbackResult::kContinue;
288 }
289
290 // A new link layer connection was created. Create an object to track this
291 // connection. Destroying this object will disconnect the link.
292 auto connection =
293 std::make_unique<LowEnergyConnection>(handle,
294 pending_request_->local_address,
295 peer_address,
296 connection_params,
297 params.role().Read(),
298 hci_);
299
300 Result<> result = fit::ok();
301 if (pending_request_->timed_out) {
302 result = ToResult(HostError::kTimedOut);
303 } else if (pending_request_->canceled) {
304 result = ToResult(HostError::kCanceled);
305 }
306
307 // If we were requested to cancel the connection after the logical link
308 // is created we disconnect it.
309 if (result.is_error()) {
310 connection = nullptr;
311 }
312
313 OnCreateConnectionComplete(result, std::move(connection));
314 return CommandChannel::EventCallbackResult::kContinue;
315 }
316
OnCreateConnectionComplete(Result<> result,std::unique_ptr<LowEnergyConnection> link)317 void LowEnergyConnector::OnCreateConnectionComplete(
318 Result<> result, std::unique_ptr<LowEnergyConnection> link) {
319 BT_DEBUG_ASSERT(pending_request_);
320
321 bt_log(DEBUG, "hci-le", "connection complete - status: %s", bt_str(result));
322
323 request_timeout_task_.Cancel();
324
325 auto status_cb = std::move(pending_request_->status_callback);
326 pending_request_.reset();
327
328 status_cb(result, std::move(link));
329 }
330
OnCreateConnectionTimeout()331 void LowEnergyConnector::OnCreateConnectionTimeout() {
332 BT_DEBUG_ASSERT(pending_request_);
333 bt_log(INFO, "hci-le", "create connection timed out: canceling request");
334
335 // TODO(armansito): This should cancel the connection attempt only if the
336 // connection attempt isn't using the filter accept list.
337 CancelInternal(true);
338 }
339
340 } // namespace bt::hci
341