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