• 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 <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