• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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