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 <pw_assert/check.h>
18
19 #include "pw_bluetooth_sapphire/internal/host/common/log.h"
20 #include "pw_bluetooth_sapphire/internal/host/hci-spec/protocol.h"
21 #include "pw_bluetooth_sapphire/internal/host/hci/local_address_delegate.h"
22 #include "pw_bluetooth_sapphire/internal/host/transport/transport.h"
23
24 namespace bt::hci {
25 using hci_spec::ConnectionHandle;
26 using hci_spec::LEConnectionParameters;
27 using pw::bluetooth::emboss::ConnectionRole;
28 using pw::bluetooth::emboss::GenericEnableParam;
29 using pw::bluetooth::emboss::LEAddressType;
30 using pw::bluetooth::emboss::LEConnectionCompleteSubeventView;
31 using pw::bluetooth::emboss::LECreateConnectionCancelCommandView;
32 using pw::bluetooth::emboss::LECreateConnectionCommandWriter;
33 using pw::bluetooth::emboss::LEEnhancedConnectionCompleteSubeventV1View;
34 using pw::bluetooth::emboss::LEExtendedCreateConnectionCommandV1Writer;
35 using pw::bluetooth::emboss::LEMetaEventView;
36 using pw::bluetooth::emboss::LEOwnAddressType;
37 using pw::bluetooth::emboss::LEPeerAddressTypeNoAnon;
38 using pw::bluetooth::emboss::StatusCode;
39
PendingRequest(const DeviceAddress & init_peer_address,StatusCallback init_status_callback)40 LowEnergyConnector::PendingRequest::PendingRequest(
41 const DeviceAddress& init_peer_address, StatusCallback init_status_callback)
42 : peer_address(init_peer_address),
43 status_callback(std::move(init_status_callback)) {}
44
LowEnergyConnector(Transport::WeakPtr hci,LocalAddressDelegate * local_addr_delegate,pw::async::Dispatcher & dispatcher,IncomingConnectionDelegate delegate,bool use_extended_operations)45 LowEnergyConnector::LowEnergyConnector(
46 Transport::WeakPtr hci,
47 LocalAddressDelegate* local_addr_delegate,
48 pw::async::Dispatcher& dispatcher,
49 IncomingConnectionDelegate delegate,
50 bool use_extended_operations)
51 : pw_dispatcher_(dispatcher),
52 hci_(std::move(hci)),
53 local_addr_delegate_(local_addr_delegate),
54 delegate_(std::move(delegate)),
55 use_extended_operations_(use_extended_operations),
56 weak_self_(this) {
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 CommandChannel::EventHandlerId id =
65 hci_->command_channel()->AddLEMetaEventHandler(
66 hci_spec::kLEConnectionCompleteSubeventCode,
67 [this](const EventPacket& event) {
68 OnConnectionCompleteEvent<LEConnectionCompleteSubeventView>(event);
69 return CommandChannel::EventCallbackResult::kContinue;
70 });
71 event_handler_ids_.insert(id);
72
73 id = hci_->command_channel()->AddLEMetaEventHandler(
74 hci_spec::kLEEnhancedConnectionCompleteSubeventCode,
75 [this](const EventPacket& event) {
76 OnConnectionCompleteEvent<LEEnhancedConnectionCompleteSubeventV1View>(
77 event);
78 return CommandChannel::EventCallbackResult::kContinue;
79 });
80 event_handler_ids_.insert(id);
81 }
82
~LowEnergyConnector()83 LowEnergyConnector::~LowEnergyConnector() {
84 if (request_pending()) {
85 Cancel();
86 }
87
88 if (hci_.is_alive() && hci_->command_channel()) {
89 for (CommandChannel::EventHandlerId id : event_handler_ids_) {
90 hci_->command_channel()->RemoveEventHandler(id);
91 }
92 }
93 }
94
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)95 bool LowEnergyConnector::CreateConnection(
96 bool use_accept_list,
97 const DeviceAddress& peer_address,
98 uint16_t scan_interval,
99 uint16_t scan_window,
100 const hci_spec::LEPreferredConnectionParameters& initial_parameters,
101 StatusCallback status_callback,
102 pw::chrono::SystemClock::duration timeout) {
103 PW_DCHECK(status_callback);
104 PW_DCHECK(timeout.count() > 0);
105
106 if (request_pending()) {
107 return false;
108 }
109
110 PW_DCHECK(!request_timeout_task_.is_pending());
111 pending_request_ = PendingRequest(peer_address, std::move(status_callback));
112
113 if (use_local_identity_address_) {
114 // Use the identity address if privacy override was enabled.
115 DeviceAddress address = local_addr_delegate_->identity_address();
116 CreateConnectionInternal(address,
117 use_accept_list,
118 peer_address,
119 scan_interval,
120 scan_window,
121 initial_parameters,
122 std::move(status_callback),
123 timeout);
124 return true;
125 }
126
127 local_addr_delegate_->EnsureLocalAddress(
128 /*address_type=*/std::nullopt,
129 [this,
130 use_accept_list,
131 peer_address,
132 scan_interval,
133 scan_window,
134 initial_parameters,
135 timeout,
136 callback = std::move(status_callback)](
137 fit::result<HostError, const DeviceAddress> result) mutable {
138 if (result.is_error()) {
139 callback(fit::error(result.error_value()), nullptr);
140 return;
141 }
142 CreateConnectionInternal(result.value(),
143 use_accept_list,
144 peer_address,
145 scan_interval,
146 scan_window,
147 initial_parameters,
148 std::move(callback),
149 timeout);
150 });
151
152 return true;
153 }
154
pending_peer_address() const155 std::optional<DeviceAddress> LowEnergyConnector::pending_peer_address() const {
156 if (pending_request_) {
157 return pending_request_->peer_address;
158 }
159
160 return std::nullopt;
161 }
162
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,pw::chrono::SystemClock::duration timeout)163 void LowEnergyConnector::CreateConnectionInternal(
164 const DeviceAddress& local_address,
165 bool use_accept_list,
166 const DeviceAddress& peer_address,
167 uint16_t scan_interval,
168 uint16_t scan_window,
169 const hci_spec::LEPreferredConnectionParameters& initial_params,
170 StatusCallback,
171 pw::chrono::SystemClock::duration timeout) {
172 if (!hci_.is_alive()) {
173 return;
174 }
175
176 // Check if the connection request was canceled via Cancel().
177 if (!pending_request_ || pending_request_->canceled) {
178 bt_log(DEBUG,
179 "hci-le",
180 "connection request was canceled while obtaining local address");
181 pending_request_.reset();
182 return;
183 }
184
185 PW_DCHECK(!pending_request_->initiating);
186
187 pending_request_->initiating = true;
188 pending_request_->local_address = local_address;
189
190 // HCI Command Status Event will be sent as our completion callback.
191 auto self = weak_self_.GetWeakPtr();
192 auto complete_cb = [self, timeout](auto, const EventPacket& event) {
193 PW_DCHECK(event.event_code() == hci_spec::kCommandStatusEventCode);
194
195 if (!self.is_alive()) {
196 return;
197 }
198
199 Result<> result = event.ToResult();
200 if (result.is_error()) {
201 self->OnCreateConnectionComplete(result, nullptr);
202 return;
203 }
204
205 // The request was started but has not completed; initiate the command
206 // timeout period. NOTE: The request will complete when the controller
207 // asynchronously notifies us of with a LE Connection Complete event.
208 self->request_timeout_task_.Cancel();
209 self->request_timeout_task_.PostAfter(timeout);
210 };
211
212 std::optional<CommandPacket> request;
213 if (use_extended_operations_) {
214 request.emplace(BuildExtendedCreateConnectionPacket(local_address,
215 peer_address,
216 initial_params,
217 use_accept_list,
218 scan_interval,
219 scan_window));
220 } else {
221 request.emplace(BuildCreateConnectionPacket(local_address,
222 peer_address,
223 initial_params,
224 use_accept_list,
225 scan_interval,
226 scan_window));
227 }
228
229 hci_->command_channel()->SendCommand(std::move(request.value()),
230 complete_cb,
231 hci_spec::kCommandStatusEventCode);
232 }
233
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)234 CommandPacket LowEnergyConnector::BuildExtendedCreateConnectionPacket(
235 const DeviceAddress& local_address,
236 const DeviceAddress& peer_address,
237 const hci_spec::LEPreferredConnectionParameters& initial_params,
238 bool use_accept_list,
239 uint16_t scan_interval,
240 uint16_t scan_window) {
241 // The LE Extended Create Connection Command ends with a variable amount of
242 // data: per PHY connection settings. Depending on the PHYs we select to scan
243 // on when connecting, the variable amount of data at the end of the packet
244 // grows. Currently, we scan on all available PHYs. Thus, we use the maximum
245 // size of this packet.
246 size_t max_size = pw::bluetooth::emboss::LEExtendedCreateConnectionCommandV1::
247 MaxSizeInBytes();
248
249 auto packet = CommandPacket::New<LEExtendedCreateConnectionCommandV1Writer>(
250 hci_spec::kLEExtendedCreateConnection, max_size);
251 auto params = packet.view_t();
252
253 if (use_accept_list) {
254 params.initiator_filter_policy().Write(GenericEnableParam::ENABLE);
255 } else {
256 params.initiator_filter_policy().Write(GenericEnableParam::DISABLE);
257 }
258
259 // TODO(b/328311582): Use the resolved address types for <5.0 LE
260 // Privacy.
261 if (peer_address.IsPublic()) {
262 params.peer_address_type().Write(LEPeerAddressTypeNoAnon::PUBLIC);
263 } else {
264 params.peer_address_type().Write(LEPeerAddressTypeNoAnon::RANDOM);
265 }
266
267 if (local_address.IsPublic()) {
268 params.own_address_type().Write(LEOwnAddressType::PUBLIC);
269 } else {
270 params.own_address_type().Write(LEOwnAddressType::RANDOM);
271 }
272
273 params.peer_address().CopyFrom(peer_address.value().view());
274
275 // We scan on all available PHYs for a connection
276 params.initiating_phys().le_1m().Write(true);
277 params.initiating_phys().le_2m().Write(true);
278 params.initiating_phys().le_coded().Write(true);
279
280 for (int i = 0; i < params.num_entries().Read(); i++) {
281 params.data()[i].scan_interval().Write(scan_interval);
282 params.data()[i].scan_window().Write(scan_window);
283 params.data()[i].connection_interval_min().Write(
284 initial_params.min_interval());
285 params.data()[i].connection_interval_max().Write(
286 initial_params.max_interval());
287 params.data()[i].max_latency().Write(initial_params.max_latency());
288 params.data()[i].supervision_timeout().Write(
289 initial_params.supervision_timeout());
290
291 // These fields provide the expected minimum and maximum duration of
292 // connection events. We have no preference, so we set them to zero and let
293 // the Controller decide. Some Controllers require
294 // max_ce_length < max_connection_interval * 2.
295 params.data()[i].min_connection_event_length().Write(0x0000);
296 params.data()[i].max_connection_event_length().Write(0x0000);
297 }
298
299 return packet;
300 }
301
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)302 CommandPacket LowEnergyConnector::BuildCreateConnectionPacket(
303 const DeviceAddress& local_address,
304 const DeviceAddress& peer_address,
305 const hci_spec::LEPreferredConnectionParameters& initial_params,
306 bool use_accept_list,
307 uint16_t scan_interval,
308 uint16_t scan_window) {
309 auto packet = CommandPacket::New<LECreateConnectionCommandWriter>(
310 hci_spec::kLECreateConnection);
311 auto params = packet.view_t();
312
313 if (use_accept_list) {
314 params.initiator_filter_policy().Write(GenericEnableParam::ENABLE);
315 } else {
316 params.initiator_filter_policy().Write(GenericEnableParam::DISABLE);
317 }
318
319 // TODO(b/328311582): Use the resolved address types for <5.0 LE
320 // Privacy.
321 if (peer_address.IsPublic()) {
322 params.peer_address_type().Write(LEAddressType::PUBLIC);
323 } else {
324 params.peer_address_type().Write(LEAddressType::RANDOM);
325 }
326
327 if (local_address.IsPublic()) {
328 params.own_address_type().Write(LEOwnAddressType::PUBLIC);
329 } else {
330 params.own_address_type().Write(LEOwnAddressType::RANDOM);
331 }
332
333 params.le_scan_interval().Write(scan_interval);
334 params.le_scan_window().Write(scan_window);
335 params.peer_address().CopyFrom(peer_address.value().view());
336 params.connection_interval_min().Write(initial_params.min_interval());
337 params.connection_interval_max().Write(initial_params.max_interval());
338 params.max_latency().Write(initial_params.max_latency());
339 params.supervision_timeout().Write(initial_params.supervision_timeout());
340
341 // These fields provide the expected minimum and maximum duration of
342 // connection events. We have no preference, so we set them to zero and let
343 // the Controller decide. Some Controllers require
344 // max_ce_length < max_connection_interval * 2.
345 params.min_connection_event_length().Write(0x0000);
346 params.max_connection_event_length().Write(0x0000);
347
348 return packet;
349 }
350
CancelInternal(bool timed_out)351 void LowEnergyConnector::CancelInternal(bool timed_out) {
352 PW_DCHECK(request_pending());
353
354 if (pending_request_->canceled) {
355 bt_log(WARN, "hci-le", "connection attempt already canceled!");
356 return;
357 }
358
359 // At this point we do not know whether the pending connection request has
360 // completed or not (it may have completed in the controller but that does not
361 // mean that we have processed the corresponding LE Connection Complete
362 // event). Below we mark the request as canceled and tell the controller to
363 // cancel its pending connection attempt.
364 pending_request_->canceled = true;
365 pending_request_->timed_out = timed_out;
366
367 request_timeout_task_.Cancel();
368
369 // Tell the controller to cancel the connection initiation attempt if a
370 // request is outstanding. Otherwise there is no need to talk to the
371 // controller.
372 if (pending_request_->initiating && hci_.is_alive()) {
373 bt_log(
374 DEBUG, "hci-le", "telling controller to cancel LE connection attempt");
375 auto complete_cb = [](auto, const EventPacket& event) {
376 HCI_IS_ERROR(
377 event, WARN, "hci-le", "failed to cancel connection request");
378 };
379 auto cancel = CommandPacket::New<LECreateConnectionCancelCommandView>(
380 hci_spec::kLECreateConnectionCancel);
381 hci_->command_channel()->SendCommand(std::move(cancel), complete_cb);
382
383 // A connection complete event will be generated by the controller after
384 // processing the cancel command.
385 return;
386 }
387
388 bt_log(DEBUG, "hci-le", "connection initiation aborted");
389 OnCreateConnectionComplete(ToResult(HostError::kCanceled), nullptr);
390 }
391
392 template <typename T>
OnConnectionCompleteEvent(const EventPacket & event)393 void LowEnergyConnector::OnConnectionCompleteEvent(const EventPacket& event) {
394 auto params = event.view<T>();
395
396 DeviceAddress::Type address_type =
397 DeviceAddress::LeAddrToDeviceAddr(params.peer_address_type().Read());
398 DeviceAddressBytes address_bytes = DeviceAddressBytes(params.peer_address());
399 DeviceAddress peer_address = DeviceAddress(address_type, address_bytes);
400
401 // First check if this event is related to the currently pending request.
402 const bool matches_pending_request =
403 pending_request_ &&
404 (pending_request_->peer_address.value() == peer_address.value());
405
406 if (Result<> result = event.ToResult(); result.is_error()) {
407 if (!matches_pending_request) {
408 bt_log(WARN,
409 "hci-le",
410 "unexpected connection complete event with error received: %s",
411 bt_str(result));
412 return;
413 }
414
415 // The "Unknown Connect Identifier" error code is returned if this event
416 // was sent due to a successful cancelation via the
417 // HCI_LE_Create_Connection_Cancel command (sent by Cancel()).
418 if (pending_request_->timed_out) {
419 result = ToResult(HostError::kTimedOut);
420 } else if (params.status().Read() == StatusCode::UNKNOWN_CONNECTION_ID) {
421 result = ToResult(HostError::kCanceled);
422 }
423
424 OnCreateConnectionComplete(result, nullptr);
425 return;
426 }
427
428 ConnectionRole role = params.role().Read();
429 ConnectionHandle handle = params.connection_handle().Read();
430 LEConnectionParameters connection_params =
431 LEConnectionParameters(params.connection_interval().Read(),
432 params.peripheral_latency().Read(),
433 params.supervision_timeout().Read());
434
435 // If the connection did not match a pending request then we pass the
436 // information down to the incoming connection delegate.
437 if (!matches_pending_request) {
438 delegate_(handle, role, peer_address, connection_params);
439 return;
440 }
441
442 // A new link layer connection was created. Create an object to track this
443 // connection. Destroying this object will disconnect the link.
444 auto connection =
445 std::make_unique<LowEnergyConnection>(handle,
446 pending_request_->local_address,
447 peer_address,
448 connection_params,
449 role,
450 hci_);
451
452 Result<> result = fit::ok();
453 if (pending_request_->timed_out) {
454 result = ToResult(HostError::kTimedOut);
455 } else if (pending_request_->canceled) {
456 result = ToResult(HostError::kCanceled);
457 }
458
459 // If we were requested to cancel the connection after the logical link
460 // is created we disconnect it.
461 if (result.is_error()) {
462 connection = nullptr;
463 }
464
465 OnCreateConnectionComplete(result, std::move(connection));
466 }
467
OnCreateConnectionComplete(Result<> result,std::unique_ptr<LowEnergyConnection> link)468 void LowEnergyConnector::OnCreateConnectionComplete(
469 Result<> result, std::unique_ptr<LowEnergyConnection> link) {
470 PW_DCHECK(pending_request_);
471 bt_log(DEBUG, "hci-le", "connection complete - status: %s", bt_str(result));
472
473 request_timeout_task_.Cancel();
474
475 auto status_cb = std::move(pending_request_->status_callback);
476 pending_request_.reset();
477
478 status_cb(result, std::move(link));
479 }
480
OnCreateConnectionTimeout()481 void LowEnergyConnector::OnCreateConnectionTimeout() {
482 PW_DCHECK(pending_request_);
483 bt_log(INFO, "hci-le", "create connection timed out: canceling request");
484
485 // TODO(armansito): This should cancel the connection attempt only if the
486 // connection attempt isn't using the filter accept list.
487 CancelInternal(true);
488 }
489 } // namespace bt::hci
490