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