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