• 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/gap/low_energy_connector.h"
16 
17 #include <pw_assert/check.h>
18 
19 #include "pw_bluetooth_sapphire/internal/host/gap/peer_cache.h"
20 
21 namespace bt::gap::internal {
22 
23 namespace {
24 
25 // During the initial connection to a peripheral we use the initial high
26 // duty-cycle parameters to ensure that initiating procedures (bonding,
27 // encryption setup, service discovery) are completed quickly. Once these
28 // procedures are complete, we will change the connection interval to the
29 // peripheral's preferred connection parameters (see v5.0, Vol 3, Part C,
30 // Section 9.3.12).
31 static const hci_spec::LEPreferredConnectionParameters
32     kInitialConnectionParameters(kLEInitialConnIntervalMin,
33                                  kLEInitialConnIntervalMax,
34                                  /*max_latency=*/0,
35                                  hci_spec::defaults::kLESupervisionTimeout);
36 
37 constexpr int kMaxConnectionAttempts = 3;
38 constexpr int kRetryExponentialBackoffBase = 2;
39 
40 constexpr const char* kInspectPeerIdPropertyName = "peer_id";
41 constexpr const char* kInspectConnectionAttemptPropertyName =
42     "connection_attempt";
43 constexpr const char* kInspectStatePropertyName = "state";
44 constexpr const char* kInspectIsOutboundPropertyName = "is_outbound";
45 
46 }  // namespace
47 
LowEnergyConnector(PeerId peer_id,LowEnergyConnectionOptions options,hci::Transport::WeakPtr hci,PeerCache * peer_cache,WeakSelf<LowEnergyConnectionManager>::WeakPtr conn_mgr,l2cap::ChannelManager * l2cap,gatt::GATT::WeakPtr gatt,const AdapterState & adapter_state,pw::async::Dispatcher & dispatcher,hci::LocalAddressDelegate * local_address_delegate)48 LowEnergyConnector::LowEnergyConnector(
49     PeerId peer_id,
50     LowEnergyConnectionOptions options,
51     hci::Transport::WeakPtr hci,
52     PeerCache* peer_cache,
53     WeakSelf<LowEnergyConnectionManager>::WeakPtr conn_mgr,
54     l2cap::ChannelManager* l2cap,
55     gatt::GATT::WeakPtr gatt,
56     const AdapterState& adapter_state,
57     pw::async::Dispatcher& dispatcher,
58     hci::LocalAddressDelegate* local_address_delegate)
59     : dispatcher_(dispatcher),
60       peer_id_(peer_id),
61       peer_cache_(peer_cache),
62       l2cap_(l2cap),
63       gatt_(std::move(gatt)),
64       adapter_state_(adapter_state),
65       options_(options),
66       hci_(std::move(hci)),
67       le_connection_manager_(std::move(conn_mgr)),
68       local_address_delegate_(local_address_delegate) {
69   PW_CHECK(peer_cache_);
70   PW_CHECK(l2cap_);
71   PW_CHECK(gatt_.is_alive());
72   PW_CHECK(hci_.is_alive());
73   PW_CHECK(le_connection_manager_.is_alive());
74 
75   cmd_ = hci_->command_channel()->AsWeakPtr();
76   PW_CHECK(cmd_.is_alive());
77 
78   auto peer = peer_cache_->FindById(peer_id_);
79   PW_CHECK(peer);
80   peer_address_ = peer->address();
81 
82   request_create_connection_task_.set_function(
83       [this](pw::async::Context /*ctx*/, pw::Status status) {
84         if (status.ok()) {
85           RequestCreateConnection();
86         }
87       });
88 }
89 
~LowEnergyConnector()90 LowEnergyConnector::~LowEnergyConnector() {
91   if (*state_ != State::kComplete && *state_ != State::kFailed) {
92     bt_log(
93         WARN,
94         "gap-le",
95         "destroying LowEnergyConnector before procedure completed (peer: %s)",
96         bt_str(peer_id_));
97     NotifyFailure(ToResult(HostError::kCanceled));
98   }
99 
100   if (hci_connector_ && hci_connector_->request_pending()) {
101     // NOTE: LowEnergyConnector will be unable to wait for the connection to be
102     // canceled. The hci::LowEnergyConnector may still be waiting to cancel the
103     // connection when a later gap::internal::LowEnergyConnector is created.
104     hci_connector_->Cancel();
105   }
106 }
107 
StartOutbound(pw::chrono::SystemClock::duration request_timeout,hci::LowEnergyConnector * connector,LowEnergyDiscoveryManager::WeakPtr discovery_manager,ResultCallback cb)108 void LowEnergyConnector::StartOutbound(
109     pw::chrono::SystemClock::duration request_timeout,
110     hci::LowEnergyConnector* connector,
111     LowEnergyDiscoveryManager::WeakPtr discovery_manager,
112     ResultCallback cb) {
113   PW_CHECK(*state_ == State::kDefault);
114   PW_CHECK(discovery_manager.is_alive());
115   PW_CHECK(connector);
116   PW_CHECK(request_timeout.count() != 0);
117   hci_connector_ = connector;
118   discovery_manager_ = std::move(discovery_manager);
119   hci_request_timeout_ = request_timeout;
120   result_cb_ = std::move(cb);
121   set_is_outbound(true);
122 
123   EnsureLocalAddress();
124 }
125 
StartInbound(std::unique_ptr<hci::LowEnergyConnection> connection,ResultCallback cb)126 void LowEnergyConnector::StartInbound(
127     std::unique_ptr<hci::LowEnergyConnection> connection, ResultCallback cb) {
128   PW_CHECK(*state_ == State::kDefault);
129   PW_CHECK(connection);
130   // Connection address should resolve to same peer as the given peer ID.
131   Peer* conn_peer = peer_cache_->FindByAddress(connection->peer_address());
132   PW_CHECK(conn_peer);
133   PW_CHECK(peer_id_ == conn_peer->identifier(),
134            "peer_id_ (%s) != connection peer (%s)",
135            bt_str(peer_id_),
136            bt_str(conn_peer->identifier()));
137   result_cb_ = std::move(cb);
138   set_is_outbound(false);
139 
140   if (!InitializeConnection(std::move(connection))) {
141     return;
142   }
143 
144   StartInterrogation();
145 }
146 
Cancel()147 void LowEnergyConnector::Cancel() {
148   bt_log(INFO,
149          "gap-le",
150          "canceling connector (peer: %s, state: %s)",
151          bt_str(peer_id_),
152          StateToString(*state_));
153 
154   switch (*state_) {
155     case State::kDefault:
156       // There is nothing to do if cancel is called before the procedure has
157       // started. There is no result callback to call yet.
158       break;
159     case State::kEnsuringLocalAddress:
160       NotifyFailure(ToResult(HostError::kCanceled));
161       break;
162     case State::kConnecting:
163       // The connector will call the result callback with a cancelled result.
164       hci_connector_->Cancel();
165       break;
166     case State::kInterrogating:
167       // The interrogator will call the result callback with a cancelled result.
168       interrogator_->Cancel();
169       break;
170     case State::kPauseBeforeConnectionRetry:
171       request_create_connection_task_.Cancel();
172       NotifyFailure(ToResult(HostError::kCanceled));
173       break;
174     case State::kAwaitingConnectionFailedToBeEstablishedDisconnect:
175       // Waiting for disconnect complete, nothing to do.
176     case State::kComplete:
177     case State::kFailed:
178       // Cancelling completed/failed connector is a no-op.
179       break;
180   }
181 }
182 
AttachInspect(inspect::Node & parent,std::string name)183 void LowEnergyConnector::AttachInspect(inspect::Node& parent,
184                                        std::string name) {
185   inspect_node_ = parent.CreateChild(name);
186   inspect_properties_.peer_id = inspect_node_.CreateString(
187       kInspectPeerIdPropertyName, peer_id_.ToString());
188   connection_attempt_.AttachInspect(inspect_node_,
189                                     kInspectConnectionAttemptPropertyName);
190   state_.AttachInspect(inspect_node_, kInspectStatePropertyName);
191   if (is_outbound_.has_value()) {
192     inspect_properties_.is_outbound =
193         inspect_node_.CreateBool(kInspectIsOutboundPropertyName, *is_outbound_);
194   }
195 }
196 
StateToString(State state)197 const char* LowEnergyConnector::StateToString(State state) {
198   switch (state) {
199     case State::kDefault:
200       return "Default";
201     case State::kEnsuringLocalAddress:
202       return "EnsuringLocalAddress";
203     case State::kConnecting:
204       return "Connecting";
205     case State::kInterrogating:
206       return "Interrogating";
207     case State::kAwaitingConnectionFailedToBeEstablishedDisconnect:
208       return "AwaitingConnectionFailedToBeEstablishedDisconnect";
209     case State::kPauseBeforeConnectionRetry:
210       return "PauseBeforeConnectionRetry";
211     case State::kComplete:
212       return "Complete";
213     case State::kFailed:
214       return "Failed";
215   }
216 }
217 
EnsureLocalAddress()218 void LowEnergyConnector::EnsureLocalAddress() {
219   PW_CHECK(*state_ == State::kDefault);
220   state_.Set(State::kEnsuringLocalAddress);
221   local_address_delegate_->EnsureLocalAddress(
222       /*address_type=*/std::nullopt, [self = weak_self_.GetWeakPtr()](auto) {
223         if (!self.is_alive() || *self->state_ == State::kFailed) {
224           return;
225         }
226         self->RequestCreateConnection();
227       });
228 }
229 
RequestCreateConnection()230 void LowEnergyConnector::RequestCreateConnection() {
231   // When the peer disconnects during/after interrogation, a retry may be
232   // initiated by calling this method.
233   PW_CHECK(*state_ == State::kDefault ||
234            *state_ == State::kEnsuringLocalAddress ||
235            *state_ == State::kPauseBeforeConnectionRetry);
236 
237   // Pause discovery until connection complete.
238   std::optional<LowEnergyDiscoveryManager::PauseToken> pause_token;
239   if (discovery_manager_.is_alive()) {
240     pause_token = discovery_manager_->PauseDiscovery();
241   }
242 
243   auto self = weak_self_.GetWeakPtr();
244   auto status_cb = [self, pause = std::move(pause_token)](hci::Result<> status,
245                                                           auto link) {
246     if (self.is_alive()) {
247       self->OnConnectResult(status, std::move(link));
248     }
249   };
250 
251   state_.Set(State::kConnecting);
252 
253   // TODO(fxbug.dev/42149416): Use slow interval & window for auto connections
254   // during background scan.
255   PW_CHECK(hci_connector_->CreateConnection(
256       /*use_accept_list=*/false,
257       peer_address_,
258       kLEScanFastInterval,
259       kLEScanFastWindow,
260       kInitialConnectionParameters,
261       std::move(status_cb),
262       hci_request_timeout_));
263 }
264 
OnConnectResult(hci::Result<> status,std::unique_ptr<hci::LowEnergyConnection> link)265 void LowEnergyConnector::OnConnectResult(
266     hci::Result<> status, std::unique_ptr<hci::LowEnergyConnection> link) {
267   if (status.is_error()) {
268     bt_log(INFO,
269            "gap-le",
270            "failed to connect to peer (id: %s, status: %s)",
271            bt_str(peer_id_),
272            bt_str(status));
273 
274     NotifyFailure(status);
275     return;
276   }
277   PW_CHECK(link);
278 
279   bt_log(INFO,
280          "gap-le",
281          "connection request successful (peer: %s)",
282          bt_str(peer_id_));
283 
284   if (InitializeConnection(std::move(link))) {
285     StartInterrogation();
286   }
287 }
288 
InitializeConnection(std::unique_ptr<hci::LowEnergyConnection> link)289 bool LowEnergyConnector::InitializeConnection(
290     std::unique_ptr<hci::LowEnergyConnection> link) {
291   PW_CHECK(link);
292 
293   auto peer_disconnect_cb =
294       fit::bind_member<&LowEnergyConnector::OnPeerDisconnect>(this);
295   auto error_cb = [this]() { NotifyFailure(); };
296 
297   Peer* peer = peer_cache_->FindById(peer_id_);
298   PW_CHECK(peer);
299   auto connection = LowEnergyConnection::Create(peer->GetWeakPtr(),
300                                                 std::move(link),
301                                                 options_,
302                                                 peer_disconnect_cb,
303                                                 error_cb,
304                                                 le_connection_manager_,
305                                                 l2cap_,
306                                                 gatt_,
307                                                 hci_,
308                                                 dispatcher_);
309   if (!connection) {
310     bt_log(WARN,
311            "gap-le",
312            "connection initialization failed (peer: %s)",
313            bt_str(peer_id_));
314     NotifyFailure();
315     return false;
316   }
317 
318   connection_ = std::move(connection);
319   return true;
320 }
321 
StartInterrogation()322 void LowEnergyConnector::StartInterrogation() {
323   PW_CHECK((*is_outbound_ && *state_ == State::kConnecting) ||
324            (!*is_outbound_ && *state_ == State::kDefault));
325   PW_CHECK(connection_);
326 
327   state_.Set(State::kInterrogating);
328   auto peer = peer_cache_->FindById(peer_id_);
329   PW_CHECK(peer);
330   bool sca_supported =
331       adapter_state_.SupportedCommands().le_request_peer_sca().Read();
332   interrogator_.emplace(
333       peer->GetWeakPtr(), connection_->handle(), cmd_, sca_supported);
334   interrogator_->Start(
335       fit::bind_member<&LowEnergyConnector::OnInterrogationComplete>(this));
336 }
337 
OnInterrogationComplete(hci::Result<> status)338 void LowEnergyConnector::OnInterrogationComplete(hci::Result<> status) {
339   // If a disconnect event is received before interrogation completes, state_
340   // will be either kFailed or kPauseBeforeConnectionRetry depending on the
341   // status of the disconnect.
342   PW_CHECK(*state_ == State::kInterrogating || *state_ == State::kFailed ||
343            *state_ == State::kPauseBeforeConnectionRetry);
344   if (*state_ == State::kFailed ||
345       *state_ == State::kPauseBeforeConnectionRetry) {
346     return;
347   }
348 
349   PW_CHECK(connection_);
350 
351   // If the controller responds to an interrogation command with the 0x3e
352   // "kConnectionFailedToBeEstablished" error, it will send a Disconnection
353   // Complete event soon after. Wait for this event before initiating a retry.
354   if (status == ToResult(pw::bluetooth::emboss::StatusCode::
355                              CONNECTION_FAILED_TO_BE_ESTABLISHED)) {
356     bt_log(INFO,
357            "gap-le",
358            "Received kConnectionFailedToBeEstablished during interrogation. "
359            "Waiting for Disconnect "
360            "Complete. (peer: %s)",
361            bt_str(peer_id_));
362     state_.Set(State::kAwaitingConnectionFailedToBeEstablishedDisconnect);
363     return;
364   }
365 
366   if (status.is_error()) {
367     bt_log(INFO,
368            "gap-le",
369            "interrogation failed with %s (peer: %s)",
370            bt_str(status),
371            bt_str(peer_id_));
372     NotifyFailure();
373     return;
374   }
375 
376   connection_->OnInterrogationComplete();
377   NotifySuccess();
378 }
379 
OnPeerDisconnect(pw::bluetooth::emboss::StatusCode status_code)380 void LowEnergyConnector::OnPeerDisconnect(
381     pw::bluetooth::emboss::StatusCode status_code) {
382   // The peer can't disconnect while connecting, and we unregister from
383   // disconnects after kFailed & kComplete.
384   PW_CHECK(
385       *state_ == State::kInterrogating ||
386           *state_ == State::kAwaitingConnectionFailedToBeEstablishedDisconnect,
387       "Received peer disconnect during invalid state (state: %s, status: %s)",
388       StateToString(*state_),
389       bt_str(ToResult(status_code)));
390   if (*state_ == State::kInterrogating &&
391       status_code != pw::bluetooth::emboss::StatusCode::
392                          CONNECTION_FAILED_TO_BE_ESTABLISHED) {
393     NotifyFailure(ToResult(status_code));
394     return;
395   }
396 
397   // state_ is kAwaitingConnectionFailedToBeEstablished or kInterrogating with a
398   // 0x3e error, so retry connection
399   if (!MaybeRetryConnection()) {
400     NotifyFailure(ToResult(status_code));
401   }
402 }
403 
MaybeRetryConnection()404 bool LowEnergyConnector::MaybeRetryConnection() {
405   // Only retry outbound connections.
406   if (*is_outbound_ && *connection_attempt_ < kMaxConnectionAttempts - 1) {
407     connection_.reset();
408     state_.Set(State::kPauseBeforeConnectionRetry);
409 
410     // Exponential backoff (2s, 4s, 8s, ...)
411     std::chrono::seconds retry_delay(kRetryExponentialBackoffBase
412                                      << *connection_attempt_);
413 
414     connection_attempt_.Set(*connection_attempt_ + 1);
415     bt_log(INFO,
416            "gap-le",
417            "Retrying connection in %llds (peer: %s, attempt: %d)",
418            retry_delay.count(),
419            bt_str(peer_id_),
420            *connection_attempt_);
421     request_create_connection_task_.PostAfter(retry_delay);
422     return true;
423   }
424   return false;
425 }
426 
NotifySuccess()427 void LowEnergyConnector::NotifySuccess() {
428   PW_CHECK(*state_ == State::kInterrogating);
429   PW_CHECK(connection_);
430   PW_CHECK(result_cb_);
431 
432   state_.Set(State::kComplete);
433 
434   // LowEnergyConnectionManager should immediately set handlers to replace these
435   // ones.
436   connection_->set_peer_disconnect_callback([peer_id = peer_id_](auto) {
437     PW_CRASH("Peer disconnected without handler set (peer: %s)",
438              bt_str(peer_id));
439   });
440 
441   connection_->set_error_callback([peer_id = peer_id_]() {
442     PW_CRASH("connection error without handler set (peer: %s)",
443              bt_str(peer_id));
444   });
445 
446   result_cb_(fit::ok(std::move(connection_)));
447 }
448 
NotifyFailure(hci::Result<> status)449 void LowEnergyConnector::NotifyFailure(hci::Result<> status) {
450   state_.Set(State::kFailed);
451   // The result callback must only be called once, so extraneous failures should
452   // be ignored.
453   if (result_cb_) {
454     result_cb_(fit::error(status.take_error()));
455   }
456 }
457 
set_is_outbound(bool is_outbound)458 void LowEnergyConnector::set_is_outbound(bool is_outbound) {
459   is_outbound_ = is_outbound;
460   inspect_properties_.is_outbound =
461       inspect_node_.CreateBool(kInspectIsOutboundPropertyName, is_outbound);
462 }
463 
464 }  // namespace bt::gap::internal
465