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