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_connection_manager.h"
16
17 #include <cpp-string/string_printf.h>
18 #include <pw_assert/check.h>
19 #include <pw_preprocessor/compiler.h>
20
21 #include <optional>
22 #include <vector>
23
24 #include "pw_bluetooth_sapphire/internal/host/gap/gap.h"
25 #include "pw_bluetooth_sapphire/internal/host/gap/generic_access_client.h"
26 #include "pw_bluetooth_sapphire/internal/host/gap/low_energy_connection.h"
27 #include "pw_bluetooth_sapphire/internal/host/gap/pairing_delegate.h"
28 #include "pw_bluetooth_sapphire/internal/host/gap/peer.h"
29 #include "pw_bluetooth_sapphire/internal/host/gap/peer_cache.h"
30 #include "pw_bluetooth_sapphire/internal/host/gatt/local_service_manager.h"
31 #include "pw_bluetooth_sapphire/internal/host/hci-spec/constants.h"
32 #include "pw_bluetooth_sapphire/internal/host/hci-spec/defaults.h"
33 #include "pw_bluetooth_sapphire/internal/host/hci-spec/protocol.h"
34 #include "pw_bluetooth_sapphire/internal/host/hci-spec/util.h"
35 #include "pw_bluetooth_sapphire/internal/host/hci/local_address_delegate.h"
36 #include "pw_bluetooth_sapphire/internal/host/l2cap/channel_manager.h"
37 #include "pw_bluetooth_sapphire/internal/host/sm/error.h"
38 #include "pw_bluetooth_sapphire/internal/host/sm/security_manager.h"
39 #include "pw_bluetooth_sapphire/internal/host/sm/smp.h"
40 #include "pw_bluetooth_sapphire/internal/host/sm/types.h"
41 #include "pw_bluetooth_sapphire/internal/host/sm/util.h"
42 #include "pw_bluetooth_sapphire/internal/host/transport/transport.h"
43
44 using bt::sm::BondableMode;
45
46 namespace bt::gap {
47
48 namespace {
49
50 // If an auto-connect attempt fails with any of the following error codes, we
51 // will stop auto- connecting to the peer until the next successful connection.
52 // We have only observed this issue with the 0x3e
53 // "kConnectionFailedToBeEstablished" error in the field, but have included
54 // these other errors based on their descriptions in v5.2 Vol. 1 Part F
55 // Section 2.
ShouldStopAlwaysAutoConnecting(pw::bluetooth::emboss::StatusCode err)56 bool ShouldStopAlwaysAutoConnecting(pw::bluetooth::emboss::StatusCode err) {
57 PW_MODIFY_DIAGNOSTICS_PUSH();
58 PW_MODIFY_DIAGNOSTIC(ignored, "-Wswitch-enum");
59 switch (err) {
60 case pw::bluetooth::emboss::StatusCode::CONNECTION_TIMEOUT:
61 case pw::bluetooth::emboss::StatusCode::CONNECTION_REJECTED_SECURITY:
62 case pw::bluetooth::emboss::StatusCode::CONNECTION_ACCEPT_TIMEOUT_EXCEEDED:
63 case pw::bluetooth::emboss::StatusCode::CONNECTION_TERMINATED_BY_LOCAL_HOST:
64 case pw::bluetooth::emboss::StatusCode::CONNECTION_FAILED_TO_BE_ESTABLISHED:
65 return true;
66 default:
67 return false;
68 }
69 PW_MODIFY_DIAGNOSTICS_POP();
70 }
71
72 // During the initial connection to a peripheral we use the initial high
73 // duty-cycle parameters to ensure that initiating procedures (bonding,
74 // encryption setup, service discovery) are completed quickly. Once these
75 // procedures are complete, we will change the connection interval to the
76 // peripheral's preferred connection parameters (see v5.0, Vol 3, Part C,
77 // Section 9.3.12).
78 static const hci_spec::LEPreferredConnectionParameters
79 kInitialConnectionParameters(kLEInitialConnIntervalMin,
80 kLEInitialConnIntervalMax,
81 /*max_latency=*/0,
82 hci_spec::defaults::kLESupervisionTimeout);
83
84 const char* kInspectRequestsNodeName = "pending_requests";
85 const char* kInspectRequestNodeNamePrefix = "pending_request_";
86 const char* kInspectConnectionsNodeName = "connections";
87 const char* kInspectConnectionNodePrefix = "connection_";
88 const char* kInspectOutboundConnectorNodeName = "outbound_connector";
89 const char* kInspectConnectionFailuresPropertyName =
90 "recent_connection_failures";
91
92 const char* kInspectOutgoingSuccessCountNodeName =
93 "outgoing_connection_success_count";
94 const char* kInspectOutgoingFailureCountNodeName =
95 "outgoing_connection_failure_count";
96 const char* kInspectIncomingSuccessCountNodeName =
97 "incoming_connection_success_count";
98 const char* kInspectIncomingFailureCountNodeName =
99 "incoming_connection_failure_count";
100
101 const char* kInspectDisconnectExplicitDisconnectNodeName =
102 "disconnect_explicit_disconnect_count";
103 const char* kInspectDisconnectLinkErrorNodeName = "disconnect_link_error_count";
104 const char* kInspectDisconnectZeroRefNodeName = "disconnect_zero_ref_count";
105 const char* kInspectDisconnectRemoteDisconnectionNodeName =
106 "disconnect_remote_disconnection_count";
107
108 } // namespace
109
LowEnergyConnectionManager(hci::Transport::WeakPtr hci,hci::LocalAddressDelegate * addr_delegate,hci::LowEnergyConnector * connector,PeerCache * peer_cache,l2cap::ChannelManager * l2cap,gatt::GATT::WeakPtr gatt,LowEnergyDiscoveryManager::WeakPtr discovery_manager,sm::SecurityManagerFactory sm_creator,const AdapterState & adapter_state,pw::async::Dispatcher & dispatcher)110 LowEnergyConnectionManager::LowEnergyConnectionManager(
111 hci::Transport::WeakPtr hci,
112 hci::LocalAddressDelegate* addr_delegate,
113 hci::LowEnergyConnector* connector,
114 PeerCache* peer_cache,
115 l2cap::ChannelManager* l2cap,
116 gatt::GATT::WeakPtr gatt,
117 LowEnergyDiscoveryManager::WeakPtr discovery_manager,
118 sm::SecurityManagerFactory sm_creator,
119 const AdapterState& adapter_state,
120 pw::async::Dispatcher& dispatcher)
121 : dispatcher_(dispatcher),
122 hci_(std::move(hci)),
123 security_mode_(LESecurityMode::Mode1),
124 sm_factory_func_(std::move(sm_creator)),
125 request_timeout_(kLECreateConnectionTimeout),
126 peer_cache_(peer_cache),
127 l2cap_(l2cap),
128 gatt_(gatt),
129 adapter_state_(adapter_state),
130 discovery_manager_(discovery_manager),
131 hci_connector_(connector),
132 local_address_delegate_(addr_delegate),
133 weak_self_(this) {
134 PW_DCHECK(peer_cache_);
135 PW_DCHECK(l2cap_);
136 PW_DCHECK(gatt_.is_alive());
137 PW_DCHECK(hci_.is_alive());
138 PW_DCHECK(hci_connector_);
139 PW_DCHECK(local_address_delegate_);
140 }
141
~LowEnergyConnectionManager()142 LowEnergyConnectionManager::~LowEnergyConnectionManager() {
143 bt_log(INFO, "gap-le", "LowEnergyConnectionManager shutting down");
144
145 weak_self_.InvalidatePtrs();
146
147 // Clear |pending_requests_| and notify failure.
148 for (auto& iter : pending_requests_) {
149 iter.second.NotifyCallbacks(fit::error(HostError::kFailed));
150 }
151 pending_requests_.clear();
152
153 current_request_.reset();
154
155 remote_connectors_.clear();
156
157 // Clean up all connections.
158 for (auto& iter : connections_) {
159 CleanUpConnection(std::move(iter.second));
160 }
161
162 connections_.clear();
163 }
164
Connect(PeerId peer_id,ConnectionResultCallback callback,LowEnergyConnectionOptions connection_options)165 void LowEnergyConnectionManager::Connect(
166 PeerId peer_id,
167 ConnectionResultCallback callback,
168 LowEnergyConnectionOptions connection_options) {
169 Peer* peer = peer_cache_->FindById(peer_id);
170 if (!peer) {
171 bt_log(WARN, "gap-le", "peer not found (id: %s)", bt_str(peer_id));
172 callback(fit::error(HostError::kNotFound));
173 return;
174 }
175
176 if (peer->technology() == TechnologyType::kClassic) {
177 bt_log(ERROR,
178 "gap-le",
179 "peer does not support LE: %s",
180 peer->ToString().c_str());
181 callback(fit::error(HostError::kNotFound));
182 return;
183 }
184
185 if (!peer->connectable()) {
186 bt_log(
187 ERROR, "gap-le", "peer not connectable: %s", peer->ToString().c_str());
188 callback(fit::error(HostError::kNotFound));
189 return;
190 }
191
192 // If we are already waiting to connect to |peer_id| then we store
193 // |callback| to be processed after the connection attempt completes (in
194 // either success of failure).
195 auto pending_iter = pending_requests_.find(peer_id);
196 if (pending_iter != pending_requests_.end()) {
197 if (!current_request_) {
198 bt_log(WARN,
199 "gap-le",
200 "Connect called for peer with pending request while no "
201 "current_request_ exists (peer: "
202 "%s)",
203 bt_str(peer_id));
204 }
205 // TODO(fxbug.dev/42144310): Merge connection_options with the options of
206 // the pending request.
207 pending_iter->second.AddCallback(std::move(callback));
208 // TODO(fxbug.dev/42148775): Try to create this connection.
209 return;
210 }
211
212 // Add callback to connecting request if |peer_id| matches.
213 if (current_request_ && current_request_->request.peer_id() == peer_id) {
214 // TODO(fxbug.dev/42144310): Merge connection_options with the options of
215 // the current request.
216 current_request_->request.AddCallback(std::move(callback));
217 return;
218 }
219
220 auto conn_iter = connections_.find(peer_id);
221 if (conn_iter != connections_.end()) {
222 // TODO(fxbug.dev/42144310): Handle connection_options that conflict with
223 // the existing connection.
224 callback(fit::ok(conn_iter->second->AddRef()));
225 return;
226 }
227
228 internal::LowEnergyConnectionRequest request(
229 peer_id,
230 std::move(callback),
231 connection_options,
232 peer->MutLe().RegisterInitializingConnection());
233 request.AttachInspect(
234 inspect_pending_requests_node_,
235 inspect_pending_requests_node_.UniqueName(kInspectRequestNodeNamePrefix));
236 pending_requests_.emplace(peer_id, std::move(request));
237
238 TryCreateNextConnection();
239 }
240
Disconnect(PeerId peer_id,LowEnergyDisconnectReason reason)241 bool LowEnergyConnectionManager::Disconnect(PeerId peer_id,
242 LowEnergyDisconnectReason reason) {
243 auto remote_connector_iter = remote_connectors_.find(peer_id);
244 if (remote_connector_iter != remote_connectors_.end()) {
245 // Result callback will clean up connector.
246 remote_connector_iter->second.connector->Cancel();
247 }
248
249 auto request_iter = pending_requests_.find(peer_id);
250 if (request_iter != pending_requests_.end()) {
251 PW_CHECK(current_request_->request.peer_id() != peer_id);
252 request_iter->second.NotifyCallbacks(fit::error(HostError::kCanceled));
253 pending_requests_.erase(request_iter);
254 }
255
256 if (current_request_ && current_request_->request.peer_id() == peer_id) {
257 // Connector will call result callback to clean up connection.
258 current_request_->connector->Cancel();
259 }
260
261 // Ignore Disconnect for peer that is not pending or connected:
262 auto iter = connections_.find(peer_id);
263 if (iter == connections_.end()) {
264 bt_log(INFO,
265 "gap-le",
266 "Disconnect called for unconnected peer (peer: %s)",
267 bt_str(peer_id));
268 return true;
269 }
270
271 // Handle peer that is already connected:
272
273 // Remove the connection state from the internal map right away.
274 auto conn = std::move(iter->second);
275 connections_.erase(iter);
276
277 // Since this was an intentional disconnect, update the auto-connection
278 // behavior appropriately.
279 peer_cache_->SetAutoConnectBehaviorForIntentionalDisconnect(peer_id);
280
281 bt_log(INFO,
282 "gap-le",
283 "disconnecting (peer: %s, link: %s)",
284 bt_str(conn->peer_id()),
285 bt_str(*conn->link()));
286
287 if (reason == LowEnergyDisconnectReason::kApiRequest) {
288 inspect_properties_.disconnect_explicit_disconnect_count_.Add(1);
289 } else {
290 inspect_properties_.disconnect_link_error_count_.Add(1);
291 }
292
293 CleanUpConnection(std::move(conn));
294 return true;
295 }
296
Pair(PeerId peer_id,sm::SecurityLevel pairing_level,sm::BondableMode bondable_mode,sm::ResultFunction<> cb)297 void LowEnergyConnectionManager::Pair(PeerId peer_id,
298 sm::SecurityLevel pairing_level,
299 sm::BondableMode bondable_mode,
300 sm::ResultFunction<> cb) {
301 auto iter = connections_.find(peer_id);
302 if (iter == connections_.end()) {
303 bt_log(WARN,
304 "gap-le",
305 "cannot pair: peer not connected (peer: %s)",
306 bt_str(peer_id));
307 cb(bt::ToResult(bt::HostError::kNotFound));
308 return;
309 }
310 bt_log(INFO,
311 "gap-le",
312 "pairing with security level: %d (peer: %s)",
313 static_cast<int>(pairing_level),
314 bt_str(peer_id));
315 iter->second->UpgradeSecurity(pairing_level, bondable_mode, std::move(cb));
316 }
317
SetSecurityMode(LESecurityMode mode)318 void LowEnergyConnectionManager::SetSecurityMode(LESecurityMode mode) {
319 security_mode_ = mode;
320 if (mode == LESecurityMode::SecureConnectionsOnly) {
321 // `Disconnect`ing the peer must not be done while iterating through
322 // `connections_` as it removes the connection from `connections_`, hence
323 // the helper vector.
324 std::vector<PeerId> insufficiently_secure_peers;
325 for (auto& [peer_id, connection] : connections_) {
326 if (connection->security().level() !=
327 sm::SecurityLevel::kSecureAuthenticated &&
328 connection->security().level() != sm::SecurityLevel::kNoSecurity) {
329 insufficiently_secure_peers.push_back(peer_id);
330 }
331 }
332 for (PeerId id : insufficiently_secure_peers) {
333 Disconnect(id);
334 }
335 }
336 for (auto& iter : connections_) {
337 iter.second->set_security_mode(mode);
338 }
339 }
340
AttachInspect(inspect::Node & parent,std::string name)341 void LowEnergyConnectionManager::AttachInspect(inspect::Node& parent,
342 std::string name) {
343 inspect_node_ = parent.CreateChild(name);
344 inspect_properties_.recent_connection_failures.AttachInspect(
345 inspect_node_, kInspectConnectionFailuresPropertyName);
346 inspect_pending_requests_node_ =
347 inspect_node_.CreateChild(kInspectRequestsNodeName);
348 inspect_connections_node_ =
349 inspect_node_.CreateChild(kInspectConnectionsNodeName);
350 for (auto& request : pending_requests_) {
351 request.second.AttachInspect(inspect_pending_requests_node_,
352 inspect_pending_requests_node_.UniqueName(
353 kInspectRequestNodeNamePrefix));
354 }
355 for (auto& conn : connections_) {
356 conn.second->AttachInspect(
357 inspect_connections_node_,
358 inspect_connections_node_.UniqueName(kInspectConnectionNodePrefix));
359 }
360 if (current_request_) {
361 current_request_->connector->AttachInspect(
362 inspect_node_, kInspectOutboundConnectorNodeName);
363 }
364
365 inspect_properties_.outgoing_connection_success_count_.AttachInspect(
366 inspect_node_, kInspectOutgoingSuccessCountNodeName);
367 inspect_properties_.outgoing_connection_failure_count_.AttachInspect(
368 inspect_node_, kInspectOutgoingFailureCountNodeName);
369 inspect_properties_.incoming_connection_success_count_.AttachInspect(
370 inspect_node_, kInspectIncomingSuccessCountNodeName);
371 inspect_properties_.incoming_connection_failure_count_.AttachInspect(
372 inspect_node_, kInspectIncomingFailureCountNodeName);
373
374 inspect_properties_.disconnect_explicit_disconnect_count_.AttachInspect(
375 inspect_node_, kInspectDisconnectExplicitDisconnectNodeName);
376 inspect_properties_.disconnect_link_error_count_.AttachInspect(
377 inspect_node_, kInspectDisconnectLinkErrorNodeName);
378 inspect_properties_.disconnect_zero_ref_count_.AttachInspect(
379 inspect_node_, kInspectDisconnectZeroRefNodeName);
380 inspect_properties_.disconnect_remote_disconnection_count_.AttachInspect(
381 inspect_node_, kInspectDisconnectRemoteDisconnectionNodeName);
382 }
383
RegisterRemoteInitiatedLink(std::unique_ptr<hci::LowEnergyConnection> link,sm::BondableMode bondable_mode,ConnectionResultCallback callback)384 void LowEnergyConnectionManager::RegisterRemoteInitiatedLink(
385 std::unique_ptr<hci::LowEnergyConnection> link,
386 sm::BondableMode bondable_mode,
387 ConnectionResultCallback callback) {
388 PW_CHECK(link);
389
390 Peer* peer = UpdatePeerWithLink(*link);
391 auto peer_id = peer->identifier();
392
393 bt_log(INFO,
394 "gap-le",
395 "new remote-initiated link (peer: %s, local addr: %s, link: %s)",
396 bt_str(peer_id),
397 bt_str(link->local_address()),
398 bt_str(*link));
399
400 // TODO(fxbug.dev/42143994): Use own address when storing the connection.
401 // Currently this will refuse the connection and disconnect the link if |peer|
402 // is already connected to us by a different local address.
403 if (connections_.find(peer_id) != connections_.end()) {
404 bt_log(INFO,
405 "gap-le",
406 "multiple links from peer; remote-initiated connection refused "
407 "(peer: %s)",
408 bt_str(peer_id));
409 callback(fit::error(HostError::kFailed));
410 return;
411 }
412
413 if (remote_connectors_.find(peer_id) != remote_connectors_.end()) {
414 bt_log(INFO,
415 "gap-le",
416 "remote connector for peer already exists; connection refused "
417 "(peer: %s)",
418 bt_str(peer_id));
419 callback(fit::error(HostError::kFailed));
420 return;
421 }
422
423 LowEnergyConnectionOptions connection_options{.bondable_mode = bondable_mode};
424 internal::LowEnergyConnectionRequest request(
425 peer_id,
426 std::move(callback),
427 connection_options,
428 peer->MutLe().RegisterInitializingConnection());
429
430 std::unique_ptr<internal::LowEnergyConnector> connector =
431 std::make_unique<internal::LowEnergyConnector>(peer_id,
432 connection_options,
433 hci_,
434 peer_cache_,
435 weak_self_.GetWeakPtr(),
436 l2cap_,
437 gatt_,
438 adapter_state_,
439 dispatcher_,
440 local_address_delegate_);
441 auto [conn_iter, _] = remote_connectors_.emplace(
442 peer_id, RequestAndConnector{std::move(request), std::move(connector)});
443 // Wait until the connector is in the map to start in case the result callback
444 // is called synchronously.
445 auto result_cb =
446 std::bind(&LowEnergyConnectionManager::OnRemoteInitiatedConnectResult,
447 this,
448 peer_id,
449 std::placeholders::_1);
450 conn_iter->second.connector->StartInbound(std::move(link),
451 std::move(result_cb));
452 }
453
SetPairingDelegate(const PairingDelegate::WeakPtr & delegate)454 void LowEnergyConnectionManager::SetPairingDelegate(
455 const PairingDelegate::WeakPtr& delegate) {
456 // TODO(armansito): Add a test case for this once fxbug.dev/42169848 is done.
457 pairing_delegate_ = delegate;
458
459 // Tell existing connections to abort ongoing pairing procedures. The new
460 // delegate will receive calls to PairingDelegate::CompletePairing, unless it
461 // is null.
462 for (auto& iter : connections_) {
463 iter.second->ResetSecurityManager(delegate.is_alive()
464 ? delegate->io_capability()
465 : sm::IOCapability::kNoInputNoOutput);
466 }
467 }
468
OpenL2capChannel(PeerId peer_id,l2cap::Psm psm,l2cap::ChannelParameters params,sm::SecurityLevel security_level,l2cap::ChannelCallback cb)469 void LowEnergyConnectionManager::OpenL2capChannel(
470 PeerId peer_id,
471 l2cap::Psm psm,
472 l2cap::ChannelParameters params,
473 sm::SecurityLevel security_level,
474 l2cap::ChannelCallback cb) {
475 auto connection_iterator = connections_.find(peer_id);
476 if (connection_iterator == connections_.end()) {
477 bt_log(INFO,
478 "gap-le",
479 "can't open l2cap channel: connection not found (peer: %s)",
480 bt_str(peer_id));
481 cb(l2cap::Channel::WeakPtr());
482 return;
483 }
484
485 auto& connection = connection_iterator->second;
486 PW_DCHECK(connection);
487
488 auto pairing_cb = [connection_weak = connection->GetWeakPtr(),
489 open_l2cap_cb = std::move(cb),
490 peer_id,
491 psm,
492 params](sm::Result<> result) mutable {
493 if (!connection_weak.is_alive()) {
494 bt_log(INFO,
495 "gap-le",
496 "can't open l2cap channel: connection destroyed before pairing "
497 "completed (peer: %s)",
498 bt_str(peer_id));
499 open_l2cap_cb(l2cap::Channel::WeakPtr());
500 return;
501 }
502
503 if (result.is_error()) {
504 bt_log(
505 WARN,
506 "gap-le",
507 "can't open l2cap channel: pairing failed with error: %s (peer: %s)",
508 bt_str(result.error_value()),
509 bt_str(peer_id));
510 open_l2cap_cb(l2cap::Channel::WeakPtr());
511 return;
512 }
513
514 connection_weak->OpenL2capChannel(psm, params, std::move(open_l2cap_cb));
515 };
516
517 connection->UpgradeSecurity(
518 security_level, connection->bondable_mode(), std::move(pairing_cb));
519 }
520
SetDisconnectCallbackForTesting(DisconnectCallback callback)521 void LowEnergyConnectionManager::SetDisconnectCallbackForTesting(
522 DisconnectCallback callback) {
523 test_disconn_cb_ = std::move(callback);
524 }
525
ReleaseReference(LowEnergyConnectionHandle * handle)526 void LowEnergyConnectionManager::ReleaseReference(
527 LowEnergyConnectionHandle* handle) {
528 PW_CHECK(handle);
529
530 auto iter = connections_.find(handle->peer_identifier());
531 PW_CHECK(iter != connections_.end());
532
533 iter->second->DropRef(handle);
534 if (iter->second->ref_count() != 0u)
535 return;
536
537 // Move the connection object before erasing the entry.
538 auto conn = std::move(iter->second);
539 connections_.erase(iter);
540
541 bt_log(INFO,
542 "gap-le",
543 "all refs dropped on connection (link: %s, peer: %s)",
544 bt_str(*conn->link()),
545 bt_str(conn->peer_id()));
546 inspect_properties_.disconnect_zero_ref_count_.Add(1);
547 CleanUpConnection(std::move(conn));
548 }
549
TryCreateNextConnection()550 void LowEnergyConnectionManager::TryCreateNextConnection() {
551 if (current_request_.has_value()) {
552 bt_log(DEBUG, "gap-le", "%s: request already in progress", __FUNCTION__);
553 return;
554 }
555
556 if (pending_requests_.empty()) {
557 bt_log(TRACE, "gap-le", "%s: no pending requests remaining", __FUNCTION__);
558 return;
559 }
560
561 for (auto& iter : pending_requests_) {
562 auto peer_id = iter.first;
563 Peer* peer = peer_cache_->FindById(peer_id);
564 if (peer) {
565 auto request_pair = pending_requests_.extract(peer_id);
566 internal::LowEnergyConnectionRequest request =
567 std::move(request_pair.mapped());
568
569 std::unique_ptr<internal::LowEnergyConnector> connector =
570 std::make_unique<internal::LowEnergyConnector>(
571 peer_id,
572 request.connection_options(),
573 hci_,
574 peer_cache_,
575 weak_self_.GetWeakPtr(),
576 l2cap_,
577 gatt_,
578 adapter_state_,
579 dispatcher_,
580 local_address_delegate_);
581 connector->AttachInspect(inspect_node_,
582 kInspectOutboundConnectorNodeName);
583
584 current_request_ =
585 RequestAndConnector{std::move(request), std::move(connector)};
586 // Wait until the connector is in current_request_ to start in case the
587 // result callback is called synchronously.
588 current_request_->connector->StartOutbound(
589 request_timeout_,
590 hci_connector_,
591 discovery_manager_,
592 fit::bind_member<
593 &LowEnergyConnectionManager::OnLocalInitiatedConnectResult>(
594 this));
595 return;
596 }
597
598 bt_log(WARN,
599 "gap-le",
600 "deferring connection attempt (peer: %s)",
601 bt_str(peer_id));
602
603 // TODO(fxbug.dev/42172291): For now the requests for this peer won't
604 // complete until the next peer discovery. This will no longer be an issue
605 // when we use background scanning.
606 }
607 }
608
OnLocalInitiatedConnectResult(hci::Result<std::unique_ptr<internal::LowEnergyConnection>> result)609 void LowEnergyConnectionManager::OnLocalInitiatedConnectResult(
610 hci::Result<std::unique_ptr<internal::LowEnergyConnection>> result) {
611 PW_CHECK(current_request_.has_value());
612
613 internal::LowEnergyConnectionRequest request =
614 std::move(current_request_->request);
615 current_request_.reset();
616
617 if (result.is_error()) {
618 inspect_properties_.outgoing_connection_failure_count_.Add(1);
619 bt_log(INFO,
620 "gap-le",
621 "failed to connect to peer (peer: %s, status: %s)",
622 bt_str(request.peer_id()),
623 bt_str(result));
624 } else {
625 inspect_properties_.outgoing_connection_success_count_.Add(1);
626 bt_log(INFO,
627 "gap-le",
628 "connection request successful (peer: %s)",
629 bt_str(request.peer_id()));
630 }
631
632 ProcessConnectResult(std::move(result), std::move(request));
633 TryCreateNextConnection();
634 }
635
OnRemoteInitiatedConnectResult(PeerId peer_id,hci::Result<std::unique_ptr<internal::LowEnergyConnection>> result)636 void LowEnergyConnectionManager::OnRemoteInitiatedConnectResult(
637 PeerId peer_id,
638 hci::Result<std::unique_ptr<internal::LowEnergyConnection>> result) {
639 auto remote_connector_node = remote_connectors_.extract(peer_id);
640 PW_CHECK(!remote_connector_node.empty());
641
642 internal::LowEnergyConnectionRequest request =
643 std::move(remote_connector_node.mapped().request);
644
645 if (result.is_error()) {
646 inspect_properties_.incoming_connection_failure_count_.Add(1);
647 bt_log(INFO,
648 "gap-le",
649 "failed to complete remote initated connection with peer (peer: %s, "
650 "status: %s)",
651 bt_str(peer_id),
652 bt_str(result));
653 } else {
654 inspect_properties_.incoming_connection_success_count_.Add(1);
655 bt_log(INFO,
656 "gap-le",
657 "remote initiated connection successful (peer: %s)",
658 bt_str(peer_id));
659 }
660
661 ProcessConnectResult(std::move(result), std::move(request));
662 }
663
ProcessConnectResult(hci::Result<std::unique_ptr<internal::LowEnergyConnection>> result,internal::LowEnergyConnectionRequest request)664 void LowEnergyConnectionManager::ProcessConnectResult(
665 hci::Result<std::unique_ptr<internal::LowEnergyConnection>> result,
666 internal::LowEnergyConnectionRequest request) {
667 PeerId peer_id = request.peer_id();
668 if (result.is_error()) {
669 const hci::Error err = result.error_value();
670 Peer* const peer = peer_cache_->FindById(peer_id);
671 // Peer may have been forgotten (causing this error).
672 // A separate connection may have been established in the other direction
673 // while this connection was connecting, in which case the peer state should
674 // not be updated.
675 if (peer && connections_.find(peer->identifier()) == connections_.end()) {
676 if (request.connection_options().auto_connect &&
677 err.is_protocol_error() &&
678 ShouldStopAlwaysAutoConnecting(err.protocol_error())) {
679 // We may see a peer's connectable advertisements, but fail to establish
680 // a connection to the peer (e.g. due to asymmetrical radio TX power).
681 // Unsetting the AutoConnect flag here prevents a loop of "see peer
682 // device, attempt auto-connect, fail to establish connection".
683 peer->MutLe().set_auto_connect_behavior(
684 Peer::AutoConnectBehavior::kSkipUntilNextConnection);
685 }
686 }
687
688 const HostError host_error =
689 err.is_host_error() ? err.host_error() : HostError::kFailed;
690 request.NotifyCallbacks(fit::error(host_error));
691
692 inspect_properties_.recent_connection_failures.Add(1);
693
694 return;
695 }
696
697 InitializeConnection(std::move(result).value(), std::move(request));
698 }
699
InitializeConnection(std::unique_ptr<internal::LowEnergyConnection> connection,internal::LowEnergyConnectionRequest request)700 bool LowEnergyConnectionManager::InitializeConnection(
701 std::unique_ptr<internal::LowEnergyConnection> connection,
702 internal::LowEnergyConnectionRequest request) {
703 PW_CHECK(connection);
704
705 auto peer_id = connection->peer_id();
706
707 // TODO(fxbug.dev/42143994): For now reject having more than one link with the
708 // same peer. This should change once this has more context on the local
709 // destination for remote initiated connections.
710 if (connections_.find(peer_id) != connections_.end()) {
711 bt_log(INFO,
712 "gap-le",
713 "cannot initialize multiple links to same peer; connection refused "
714 "(peer: %s)",
715 bt_str(peer_id));
716 // Notify request that duplicate connection could not be initialized.
717 request.NotifyCallbacks(fit::error(HostError::kFailed));
718 // Do not update peer state, as there is another active LE connection in
719 // connections_ for this peer.
720 return false;
721 }
722
723 Peer* peer = peer_cache_->FindById(peer_id);
724 PW_CHECK(peer);
725
726 connection->AttachInspect(
727 inspect_connections_node_,
728 inspect_connections_node_.UniqueName(kInspectConnectionNodePrefix));
729 connection->set_peer_disconnect_callback(
730 std::bind(&LowEnergyConnectionManager::OnPeerDisconnect,
731 this,
732 connection->link(),
733 std::placeholders::_1));
734 connection->set_error_callback([this, peer_id]() {
735 Disconnect(peer_id, LowEnergyDisconnectReason::kError);
736 });
737
738 auto [conn_iter, inserted] =
739 connections_.try_emplace(peer_id, std::move(connection));
740 PW_CHECK(inserted);
741
742 conn_iter->second->set_peer_conn_token(peer->MutLe().RegisterConnection());
743
744 // Create first ref to ensure that connection is cleaned up on early returns
745 // or if first request callback does not retain a ref.
746 auto first_ref = conn_iter->second->AddRef();
747
748 UpdatePeerWithLink(*conn_iter->second->link());
749
750 bt_log(TRACE,
751 "gap-le",
752 "notifying connection request callbacks (peer: %s)",
753 bt_str(peer_id));
754
755 request.NotifyCallbacks(fit::ok(std::bind(
756 &internal::LowEnergyConnection::AddRef, conn_iter->second.get())));
757
758 return true;
759 }
760
CleanUpConnection(std::unique_ptr<internal::LowEnergyConnection> conn)761 void LowEnergyConnectionManager::CleanUpConnection(
762 std::unique_ptr<internal::LowEnergyConnection> conn) {
763 PW_CHECK(conn);
764
765 // Mark the peer peer as no longer connected.
766 Peer* peer = peer_cache_->FindById(conn->peer_id());
767 PW_CHECK(peer,
768 "A connection was active for an unknown peer! (id: %s)",
769 bt_str(conn->peer_id()));
770 conn.reset();
771 }
772
UpdatePeerWithLink(const hci::LowEnergyConnection & link)773 Peer* LowEnergyConnectionManager::UpdatePeerWithLink(
774 const hci::LowEnergyConnection& link) {
775 Peer* peer = peer_cache_->FindByAddress(link.peer_address());
776 if (!peer) {
777 peer = peer_cache_->NewPeer(link.peer_address(), /*connectable=*/true);
778 }
779 peer->MutLe().SetConnectionParameters(link.low_energy_parameters());
780 peer_cache_->SetAutoConnectBehaviorForSuccessfulConnection(
781 peer->identifier());
782
783 return peer;
784 }
785
OnPeerDisconnect(const hci::Connection * connection,pw::bluetooth::emboss::StatusCode)786 void LowEnergyConnectionManager::OnPeerDisconnect(
787 const hci::Connection* connection, pw::bluetooth::emboss::StatusCode) {
788 auto handle = connection->handle();
789 if (test_disconn_cb_) {
790 test_disconn_cb_(handle);
791 }
792
793 // See if we can find a connection with a matching handle by walking the
794 // connections list.
795 auto iter = FindConnection(handle);
796 if (iter == connections_.end()) {
797 bt_log(WARN,
798 "gap-le",
799 "disconnect from unknown connection handle: %#.4x",
800 handle);
801 return;
802 }
803
804 // Found the connection. Remove the entry from |connections_| before notifying
805 // the "closed" handlers.
806 auto conn = std::move(iter->second);
807 connections_.erase(iter);
808
809 bt_log(INFO,
810 "gap-le",
811 "peer disconnected (peer: %s, handle: %#.4x)",
812 bt_str(conn->peer_id()),
813 handle);
814
815 inspect_properties_.disconnect_remote_disconnection_count_.Add(1);
816
817 CleanUpConnection(std::move(conn));
818 }
819
820 LowEnergyConnectionManager::ConnectionMap::iterator
FindConnection(hci_spec::ConnectionHandle handle)821 LowEnergyConnectionManager::FindConnection(hci_spec::ConnectionHandle handle) {
822 auto iter = connections_.begin();
823 for (; iter != connections_.end(); ++iter) {
824 const auto& conn = *iter->second;
825 if (conn.handle() == handle)
826 break;
827 }
828 return iter;
829 }
830
831 } // namespace bt::gap
832