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_preprocessor/compiler.h>
19
20 #include <optional>
21 #include <vector>
22
23 #include "pw_bluetooth_sapphire/internal/host/common/assert.h"
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 auto [conn_iter, _] = remote_connectors_.emplace(
441 peer_id, RequestAndConnector{std::move(request), std::move(connector)});
442 // Wait until the connector is in the map to start in case the result callback
443 // is called synchronously.
444 auto result_cb =
445 std::bind(&LowEnergyConnectionManager::OnRemoteInitiatedConnectResult,
446 this,
447 peer_id,
448 std::placeholders::_1);
449 conn_iter->second.connector->StartInbound(std::move(link),
450 std::move(result_cb));
451 }
452
SetPairingDelegate(const PairingDelegate::WeakPtr & delegate)453 void LowEnergyConnectionManager::SetPairingDelegate(
454 const PairingDelegate::WeakPtr& delegate) {
455 // TODO(armansito): Add a test case for this once fxbug.dev/42169848 is done.
456 pairing_delegate_ = delegate;
457
458 // Tell existing connections to abort ongoing pairing procedures. The new
459 // delegate will receive calls to PairingDelegate::CompletePairing, unless it
460 // is null.
461 for (auto& iter : connections_) {
462 iter.second->ResetSecurityManager(delegate.is_alive()
463 ? delegate->io_capability()
464 : sm::IOCapability::kNoInputNoOutput);
465 }
466 }
467
OpenL2capChannel(PeerId peer_id,l2cap::Psm psm,l2cap::ChannelParameters params,sm::SecurityLevel security_level,l2cap::ChannelCallback cb)468 void LowEnergyConnectionManager::OpenL2capChannel(
469 PeerId peer_id,
470 l2cap::Psm psm,
471 l2cap::ChannelParameters params,
472 sm::SecurityLevel security_level,
473 l2cap::ChannelCallback cb) {
474 auto connection_iterator = connections_.find(peer_id);
475 if (connection_iterator == connections_.end()) {
476 bt_log(INFO,
477 "gap-le",
478 "can't open l2cap channel: connection not found (peer: %s)",
479 bt_str(peer_id));
480 cb(l2cap::Channel::WeakPtr());
481 return;
482 }
483
484 auto& connection = connection_iterator->second;
485 PW_DCHECK(connection);
486
487 auto pairing_cb = [connection_weak = connection->GetWeakPtr(),
488 open_l2cap_cb = std::move(cb),
489 peer_id,
490 psm,
491 params](sm::Result<> result) mutable {
492 if (!connection_weak.is_alive()) {
493 bt_log(INFO,
494 "gap-le",
495 "can't open l2cap channel: connection destroyed before pairing "
496 "completed (peer: %s)",
497 bt_str(peer_id));
498 open_l2cap_cb(l2cap::Channel::WeakPtr());
499 return;
500 }
501
502 if (result.is_error()) {
503 bt_log(
504 WARN,
505 "gap-le",
506 "can't open l2cap channel: pairing failed with error: %s (peer: %s)",
507 bt_str(result.error_value()),
508 bt_str(peer_id));
509 open_l2cap_cb(l2cap::Channel::WeakPtr());
510 return;
511 }
512
513 connection_weak->OpenL2capChannel(psm, params, std::move(open_l2cap_cb));
514 };
515
516 connection->UpgradeSecurity(
517 security_level, connection->bondable_mode(), std::move(pairing_cb));
518 }
519
SetDisconnectCallbackForTesting(DisconnectCallback callback)520 void LowEnergyConnectionManager::SetDisconnectCallbackForTesting(
521 DisconnectCallback callback) {
522 test_disconn_cb_ = std::move(callback);
523 }
524
ReleaseReference(LowEnergyConnectionHandle * handle)525 void LowEnergyConnectionManager::ReleaseReference(
526 LowEnergyConnectionHandle* handle) {
527 PW_CHECK(handle);
528
529 auto iter = connections_.find(handle->peer_identifier());
530 PW_CHECK(iter != connections_.end());
531
532 iter->second->DropRef(handle);
533 if (iter->second->ref_count() != 0u)
534 return;
535
536 // Move the connection object before erasing the entry.
537 auto conn = std::move(iter->second);
538 connections_.erase(iter);
539
540 bt_log(INFO,
541 "gap-le",
542 "all refs dropped on connection (link: %s, peer: %s)",
543 bt_str(*conn->link()),
544 bt_str(conn->peer_id()));
545 inspect_properties_.disconnect_zero_ref_count_.Add(1);
546 CleanUpConnection(std::move(conn));
547 }
548
TryCreateNextConnection()549 void LowEnergyConnectionManager::TryCreateNextConnection() {
550 if (current_request_.has_value()) {
551 bt_log(DEBUG, "gap-le", "%s: request already in progress", __FUNCTION__);
552 return;
553 }
554
555 if (pending_requests_.empty()) {
556 bt_log(TRACE, "gap-le", "%s: no pending requests remaining", __FUNCTION__);
557 return;
558 }
559
560 for (auto& iter : pending_requests_) {
561 auto peer_id = iter.first;
562 Peer* peer = peer_cache_->FindById(peer_id);
563 if (peer) {
564 auto request_pair = pending_requests_.extract(peer_id);
565 internal::LowEnergyConnectionRequest request =
566 std::move(request_pair.mapped());
567
568 std::unique_ptr<internal::LowEnergyConnector> connector =
569 std::make_unique<internal::LowEnergyConnector>(
570 peer_id,
571 request.connection_options(),
572 hci_,
573 peer_cache_,
574 weak_self_.GetWeakPtr(),
575 l2cap_,
576 gatt_,
577 adapter_state_,
578 dispatcher_);
579 connector->AttachInspect(inspect_node_,
580 kInspectOutboundConnectorNodeName);
581
582 current_request_ =
583 RequestAndConnector{std::move(request), std::move(connector)};
584 // Wait until the connector is in current_request_ to start in case the
585 // result callback is called synchronously.
586 current_request_->connector->StartOutbound(
587 request_timeout_,
588 hci_connector_,
589 discovery_manager_,
590 fit::bind_member<
591 &LowEnergyConnectionManager::OnLocalInitiatedConnectResult>(
592 this));
593 return;
594 }
595
596 bt_log(WARN,
597 "gap-le",
598 "deferring connection attempt (peer: %s)",
599 bt_str(peer_id));
600
601 // TODO(fxbug.dev/42172291): For now the requests for this peer won't
602 // complete until the next peer discovery. This will no longer be an issue
603 // when we use background scanning.
604 }
605 }
606
OnLocalInitiatedConnectResult(hci::Result<std::unique_ptr<internal::LowEnergyConnection>> result)607 void LowEnergyConnectionManager::OnLocalInitiatedConnectResult(
608 hci::Result<std::unique_ptr<internal::LowEnergyConnection>> result) {
609 PW_CHECK(current_request_.has_value());
610
611 internal::LowEnergyConnectionRequest request =
612 std::move(current_request_->request);
613 current_request_.reset();
614
615 if (result.is_error()) {
616 inspect_properties_.outgoing_connection_failure_count_.Add(1);
617 bt_log(INFO,
618 "gap-le",
619 "failed to connect to peer (peer: %s, status: %s)",
620 bt_str(request.peer_id()),
621 bt_str(result));
622 } else {
623 inspect_properties_.outgoing_connection_success_count_.Add(1);
624 bt_log(INFO,
625 "gap-le",
626 "connection request successful (peer: %s)",
627 bt_str(request.peer_id()));
628 }
629
630 ProcessConnectResult(std::move(result), std::move(request));
631 TryCreateNextConnection();
632 }
633
OnRemoteInitiatedConnectResult(PeerId peer_id,hci::Result<std::unique_ptr<internal::LowEnergyConnection>> result)634 void LowEnergyConnectionManager::OnRemoteInitiatedConnectResult(
635 PeerId peer_id,
636 hci::Result<std::unique_ptr<internal::LowEnergyConnection>> result) {
637 auto remote_connector_node = remote_connectors_.extract(peer_id);
638 PW_CHECK(!remote_connector_node.empty());
639
640 internal::LowEnergyConnectionRequest request =
641 std::move(remote_connector_node.mapped().request);
642
643 if (result.is_error()) {
644 inspect_properties_.incoming_connection_failure_count_.Add(1);
645 bt_log(INFO,
646 "gap-le",
647 "failed to complete remote initated connection with peer (peer: %s, "
648 "status: %s)",
649 bt_str(peer_id),
650 bt_str(result));
651 } else {
652 inspect_properties_.incoming_connection_success_count_.Add(1);
653 bt_log(INFO,
654 "gap-le",
655 "remote initiated connection successful (peer: %s)",
656 bt_str(peer_id));
657 }
658
659 ProcessConnectResult(std::move(result), std::move(request));
660 }
661
ProcessConnectResult(hci::Result<std::unique_ptr<internal::LowEnergyConnection>> result,internal::LowEnergyConnectionRequest request)662 void LowEnergyConnectionManager::ProcessConnectResult(
663 hci::Result<std::unique_ptr<internal::LowEnergyConnection>> result,
664 internal::LowEnergyConnectionRequest request) {
665 PeerId peer_id = request.peer_id();
666 if (result.is_error()) {
667 const hci::Error err = result.error_value();
668 Peer* const peer = peer_cache_->FindById(peer_id);
669 // Peer may have been forgotten (causing this error).
670 // A separate connection may have been established in the other direction
671 // while this connection was connecting, in which case the peer state should
672 // not be updated.
673 if (peer && connections_.find(peer->identifier()) == connections_.end()) {
674 if (request.connection_options().auto_connect &&
675 err.is_protocol_error() &&
676 ShouldStopAlwaysAutoConnecting(err.protocol_error())) {
677 // We may see a peer's connectable advertisements, but fail to establish
678 // a connection to the peer (e.g. due to asymmetrical radio TX power).
679 // Unsetting the AutoConnect flag here prevents a loop of "see peer
680 // device, attempt auto-connect, fail to establish connection".
681 peer->MutLe().set_auto_connect_behavior(
682 Peer::AutoConnectBehavior::kSkipUntilNextConnection);
683 }
684 }
685
686 const HostError host_error =
687 err.is_host_error() ? err.host_error() : HostError::kFailed;
688 request.NotifyCallbacks(fit::error(host_error));
689
690 inspect_properties_.recent_connection_failures.Add(1);
691
692 return;
693 }
694
695 InitializeConnection(std::move(result).value(), std::move(request));
696 }
697
InitializeConnection(std::unique_ptr<internal::LowEnergyConnection> connection,internal::LowEnergyConnectionRequest request)698 bool LowEnergyConnectionManager::InitializeConnection(
699 std::unique_ptr<internal::LowEnergyConnection> connection,
700 internal::LowEnergyConnectionRequest request) {
701 PW_CHECK(connection);
702
703 auto peer_id = connection->peer_id();
704
705 // TODO(fxbug.dev/42143994): For now reject having more than one link with the
706 // same peer. This should change once this has more context on the local
707 // destination for remote initiated connections.
708 if (connections_.find(peer_id) != connections_.end()) {
709 bt_log(INFO,
710 "gap-le",
711 "cannot initialize multiple links to same peer; connection refused "
712 "(peer: %s)",
713 bt_str(peer_id));
714 // Notify request that duplicate connection could not be initialized.
715 request.NotifyCallbacks(fit::error(HostError::kFailed));
716 // Do not update peer state, as there is another active LE connection in
717 // connections_ for this peer.
718 return false;
719 }
720
721 Peer* peer = peer_cache_->FindById(peer_id);
722 PW_CHECK(peer);
723
724 connection->AttachInspect(
725 inspect_connections_node_,
726 inspect_connections_node_.UniqueName(kInspectConnectionNodePrefix));
727 connection->set_peer_disconnect_callback(
728 std::bind(&LowEnergyConnectionManager::OnPeerDisconnect,
729 this,
730 connection->link(),
731 std::placeholders::_1));
732 connection->set_error_callback([this, peer_id]() {
733 Disconnect(peer_id, LowEnergyDisconnectReason::kError);
734 });
735
736 auto [conn_iter, inserted] =
737 connections_.try_emplace(peer_id, std::move(connection));
738 PW_CHECK(inserted);
739
740 conn_iter->second->set_peer_conn_token(peer->MutLe().RegisterConnection());
741
742 // Create first ref to ensure that connection is cleaned up on early returns
743 // or if first request callback does not retain a ref.
744 auto first_ref = conn_iter->second->AddRef();
745
746 UpdatePeerWithLink(*conn_iter->second->link());
747
748 bt_log(TRACE,
749 "gap-le",
750 "notifying connection request callbacks (peer: %s)",
751 bt_str(peer_id));
752
753 request.NotifyCallbacks(fit::ok(std::bind(
754 &internal::LowEnergyConnection::AddRef, conn_iter->second.get())));
755
756 return true;
757 }
758
CleanUpConnection(std::unique_ptr<internal::LowEnergyConnection> conn)759 void LowEnergyConnectionManager::CleanUpConnection(
760 std::unique_ptr<internal::LowEnergyConnection> conn) {
761 PW_CHECK(conn);
762
763 // Mark the peer peer as no longer connected.
764 Peer* peer = peer_cache_->FindById(conn->peer_id());
765 PW_CHECK(peer,
766 "A connection was active for an unknown peer! (id: %s)",
767 bt_str(conn->peer_id()));
768 conn.reset();
769 }
770
UpdatePeerWithLink(const hci::LowEnergyConnection & link)771 Peer* LowEnergyConnectionManager::UpdatePeerWithLink(
772 const hci::LowEnergyConnection& link) {
773 Peer* peer = peer_cache_->FindByAddress(link.peer_address());
774 if (!peer) {
775 peer = peer_cache_->NewPeer(link.peer_address(), /*connectable=*/true);
776 }
777 peer->MutLe().SetConnectionParameters(link.low_energy_parameters());
778 peer_cache_->SetAutoConnectBehaviorForSuccessfulConnection(
779 peer->identifier());
780
781 return peer;
782 }
783
OnPeerDisconnect(const hci::Connection * connection,pw::bluetooth::emboss::StatusCode)784 void LowEnergyConnectionManager::OnPeerDisconnect(
785 const hci::Connection* connection, pw::bluetooth::emboss::StatusCode) {
786 auto handle = connection->handle();
787 if (test_disconn_cb_) {
788 test_disconn_cb_(handle);
789 }
790
791 // See if we can find a connection with a matching handle by walking the
792 // connections list.
793 auto iter = FindConnection(handle);
794 if (iter == connections_.end()) {
795 bt_log(WARN,
796 "gap-le",
797 "disconnect from unknown connection handle: %#.4x",
798 handle);
799 return;
800 }
801
802 // Found the connection. Remove the entry from |connections_| before notifying
803 // the "closed" handlers.
804 auto conn = std::move(iter->second);
805 connections_.erase(iter);
806
807 bt_log(INFO,
808 "gap-le",
809 "peer disconnected (peer: %s, handle: %#.4x)",
810 bt_str(conn->peer_id()),
811 handle);
812
813 inspect_properties_.disconnect_remote_disconnection_count_.Add(1);
814
815 CleanUpConnection(std::move(conn));
816 }
817
818 LowEnergyConnectionManager::ConnectionMap::iterator
FindConnection(hci_spec::ConnectionHandle handle)819 LowEnergyConnectionManager::FindConnection(hci_spec::ConnectionHandle handle) {
820 auto iter = connections_.begin();
821 for (; iter != connections_.end(); ++iter) {
822 const auto& conn = *iter->second;
823 if (conn.handle() == handle)
824 break;
825 }
826 return iter;
827 }
828
829 } // namespace bt::gap
830