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.h"
16
17 #include "pw_bluetooth_sapphire/internal/host/gap/low_energy_connection_manager.h"
18 #include "pw_bluetooth_sapphire/internal/host/sm/security_manager.h"
19
20 namespace bt::gap::internal {
21
22 namespace {
23
24 constexpr const char* kInspectPeerIdPropertyName = "peer_id";
25 constexpr const char* kInspectPeerAddressPropertyName = "peer_address";
26 constexpr const char* kInspectRefsPropertyName = "ref_count";
27
28 // Connection parameters to use when the peer's preferred connection parameters
29 // are not known.
30 static const hci_spec::LEPreferredConnectionParameters
31 kDefaultPreferredConnectionParameters(
32 hci_spec::defaults::kLEConnectionIntervalMin,
33 hci_spec::defaults::kLEConnectionIntervalMax,
34 /*max_latency=*/0,
35 hci_spec::defaults::kLESupervisionTimeout);
36
37 } // namespace
38
Create(Peer::WeakPtr peer,std::unique_ptr<hci::LowEnergyConnection> link,LowEnergyConnectionOptions connection_options,PeerDisconnectCallback peer_disconnect_cb,ErrorCallback error_cb,WeakSelf<LowEnergyConnectionManager>::WeakPtr conn_mgr,l2cap::ChannelManager * l2cap,gatt::GATT::WeakPtr gatt,hci::Transport::WeakPtr hci,pw::async::Dispatcher & dispatcher)39 std::unique_ptr<LowEnergyConnection> LowEnergyConnection::Create(
40 Peer::WeakPtr peer,
41 std::unique_ptr<hci::LowEnergyConnection> link,
42 LowEnergyConnectionOptions connection_options,
43 PeerDisconnectCallback peer_disconnect_cb,
44 ErrorCallback error_cb,
45 WeakSelf<LowEnergyConnectionManager>::WeakPtr conn_mgr,
46 l2cap::ChannelManager* l2cap,
47 gatt::GATT::WeakPtr gatt,
48 hci::Transport::WeakPtr hci,
49 pw::async::Dispatcher& dispatcher) {
50 // Catch any errors/disconnects during connection initialization so that they
51 // are reported by returning a nullptr. This is less error-prone than calling
52 // the user's callbacks during initialization.
53 bool error = false;
54 auto peer_disconnect_cb_temp = [&error](auto) { error = true; };
55 auto error_cb_temp = [&error] { error = true; };
56 // TODO(fxbug.dev/325646523): Only create an IsoStreamManager
57 // instance if our adapter supports Isochronous streams.
58 std::unique_ptr<iso::IsoStreamManager> iso_mgr =
59 std::make_unique<iso::IsoStreamManager>(link->handle(),
60 hci->GetWeakPtr());
61 std::unique_ptr<LowEnergyConnection> connection(
62 new LowEnergyConnection(std::move(peer),
63 std::move(link),
64 connection_options,
65 std::move(peer_disconnect_cb_temp),
66 std::move(error_cb_temp),
67 std::move(conn_mgr),
68 std::move(iso_mgr),
69 l2cap,
70 std::move(gatt),
71 std::move(hci),
72 dispatcher));
73
74 // This looks strange, but it is possible for InitializeFixedChannels() to
75 // trigger an error and still return true, so |error| can change between the
76 // first and last check.
77 if (error || !connection->InitializeFixedChannels() || error) {
78 return nullptr;
79 }
80
81 // Now it is safe to set the user's callbacks, as no more errors/disconnects
82 // can be signaled before returning.
83 connection->set_peer_disconnect_callback(std::move(peer_disconnect_cb));
84 connection->set_error_callback(std::move(error_cb));
85 return connection;
86 }
87
LowEnergyConnection(Peer::WeakPtr peer,std::unique_ptr<hci::LowEnergyConnection> link,LowEnergyConnectionOptions connection_options,PeerDisconnectCallback peer_disconnect_cb,ErrorCallback error_cb,WeakSelf<LowEnergyConnectionManager>::WeakPtr conn_mgr,std::unique_ptr<iso::IsoStreamManager> iso_mgr,l2cap::ChannelManager * l2cap,gatt::GATT::WeakPtr gatt,hci::Transport::WeakPtr hci,pw::async::Dispatcher & dispatcher)88 LowEnergyConnection::LowEnergyConnection(
89 Peer::WeakPtr peer,
90 std::unique_ptr<hci::LowEnergyConnection> link,
91 LowEnergyConnectionOptions connection_options,
92 PeerDisconnectCallback peer_disconnect_cb,
93 ErrorCallback error_cb,
94 WeakSelf<LowEnergyConnectionManager>::WeakPtr conn_mgr,
95 std::unique_ptr<iso::IsoStreamManager> iso_mgr,
96 l2cap::ChannelManager* l2cap,
97 gatt::GATT::WeakPtr gatt,
98 hci::Transport::WeakPtr hci,
99 pw::async::Dispatcher& dispatcher)
100 : dispatcher_(dispatcher),
101 peer_(std::move(peer)),
102 link_(std::move(link)),
103 connection_options_(connection_options),
104 conn_mgr_(std::move(conn_mgr)),
105 iso_mgr_(std::move(iso_mgr)),
106 l2cap_(l2cap),
107 gatt_(std::move(gatt)),
108 hci_(std::move(hci)),
109 peer_disconnect_callback_(std::move(peer_disconnect_cb)),
110 error_callback_(std::move(error_cb)),
111 refs_(/*convert=*/[](const auto& refs) { return refs.size(); }),
112 weak_self_(this),
113 weak_delegate_(this) {
114 PW_CHECK(peer_.is_alive());
115 PW_CHECK(link_);
116 PW_CHECK(conn_mgr_.is_alive());
117 PW_CHECK(gatt_.is_alive());
118 PW_CHECK(hci_.is_alive());
119 PW_CHECK(peer_disconnect_callback_);
120 PW_CHECK(error_callback_);
121 cmd_ = hci_->command_channel()->AsWeakPtr();
122 PW_CHECK(cmd_.is_alive());
123
124 link_->set_peer_disconnect_callback(
__anona1aa19130502(const auto&, auto reason) 125 [this](const auto&, auto reason) { peer_disconnect_callback_(reason); });
126
127 RegisterEventHandlers();
128 StartConnectionPauseTimeout();
129 }
130
~LowEnergyConnection()131 LowEnergyConnection::~LowEnergyConnection() {
132 cmd_->RemoveEventHandler(conn_update_cmpl_handler_id_);
133
134 // Unregister this link from the GATT profile and the L2CAP plane. This
135 // invalidates all L2CAP channels that are associated with this link.
136 gatt_->RemoveConnection(peer_id());
137 l2cap_->RemoveConnection(link_->handle());
138
139 // Notify all active references that the link is gone. This will
140 // synchronously notify all refs.
141 CloseRefs();
142 }
143
144 std::unique_ptr<bt::gap::LowEnergyConnectionHandle>
AddRef()145 LowEnergyConnection::AddRef() {
146 auto self = GetWeakPtr();
147 auto release_cb = [self](LowEnergyConnectionHandle* handle) {
148 if (self.is_alive()) {
149 self->conn_mgr_->ReleaseReference(handle);
150 }
151 };
152 auto accept_cis_cb = [self](iso::CigCisIdentifier id,
153 iso::CisEstablishedCallback cb) {
154 PW_CHECK(self.is_alive());
155 return self->AcceptCis(id, std::move(cb));
156 };
157 auto bondable_cb = [self] {
158 PW_CHECK(self.is_alive());
159 return self->bondable_mode();
160 };
161 auto security_cb = [self] {
162 PW_CHECK(self.is_alive());
163 return self->security();
164 };
165 auto role_cb = [self] {
166 PW_CHECK(self.is_alive());
167 return self->role();
168 };
169 std::unique_ptr<bt::gap::LowEnergyConnectionHandle> conn_ref(
170 new LowEnergyConnectionHandle(peer_id(),
171 handle(),
172 std::move(release_cb),
173 std::move(accept_cis_cb),
174 std::move(bondable_cb),
175 std::move(security_cb),
176 std::move(role_cb)));
177 PW_CHECK(conn_ref);
178
179 refs_.Mutable()->insert(conn_ref.get());
180
181 bt_log(DEBUG,
182 "gap-le",
183 "added ref (peer: %s, handle %#.4x, count: %zu)",
184 bt_str(peer_id()),
185 handle(),
186 ref_count());
187
188 return conn_ref;
189 }
190
DropRef(LowEnergyConnectionHandle * ref)191 void LowEnergyConnection::DropRef(LowEnergyConnectionHandle* ref) {
192 PW_DCHECK(ref);
193
194 size_t res = refs_.Mutable()->erase(ref);
195 PW_CHECK(res == 1u, "DropRef called with wrong connection reference");
196 bt_log(DEBUG,
197 "gap-le",
198 "dropped ref (peer: %s, handle: %#.4x, count: %zu)",
199 bt_str(peer_id()),
200 handle(),
201 ref_count());
202 }
203
204 // Registers this connection with L2CAP and initializes the fixed channel
205 // protocols.
InitializeFixedChannels()206 [[nodiscard]] bool LowEnergyConnection::InitializeFixedChannels() {
207 auto self = GetWeakPtr();
208 // Ensure error_callback_ is only called once if link_error_cb is called
209 // multiple times.
210 auto link_error_cb = [self]() {
211 if (self.is_alive() && self->error_callback_) {
212 self->error_callback_();
213 }
214 };
215 auto update_conn_params_cb = [self](auto params) {
216 if (self.is_alive()) {
217 self->OnNewLEConnectionParams(params);
218 }
219 };
220 auto security_upgrade_cb = [self](auto handle, auto level, auto cb) {
221 if (!self.is_alive()) {
222 return;
223 }
224
225 bt_log(INFO,
226 "gap-le",
227 "received security upgrade request on L2CAP channel (level: %s, "
228 "peer: %s, handle: %#.4x)",
229 sm::LevelToString(level),
230 bt_str(self->peer_id()),
231 handle);
232 PW_CHECK(self->handle() == handle);
233 self->OnSecurityRequest(level, std::move(cb));
234 };
235 l2cap::ChannelManager::LEFixedChannels fixed_channels =
236 l2cap_->AddLEConnection(link_->handle(),
237 link_->role(),
238 std::move(link_error_cb),
239 update_conn_params_cb,
240 security_upgrade_cb);
241
242 return OnL2capFixedChannelsOpened(std::move(fixed_channels.att),
243 std::move(fixed_channels.smp),
244 connection_options_);
245 }
246
247 // Used to respond to protocol/service requests for increased security.
OnSecurityRequest(sm::SecurityLevel level,sm::ResultFunction<> cb)248 void LowEnergyConnection::OnSecurityRequest(sm::SecurityLevel level,
249 sm::ResultFunction<> cb) {
250 PW_CHECK(sm_);
251 sm_->UpgradeSecurity(
252 level,
253 [callback = std::move(cb), peer_id = peer_id(), handle = handle()](
254 sm::Result<> status, const auto& sp) {
255 bt_log(INFO,
256 "gap-le",
257 "pairing status: %s, properties: %s (peer: %s, handle: %#.4x)",
258 bt_str(status),
259 bt_str(sp),
260 bt_str(peer_id),
261 handle);
262 callback(status);
263 });
264 }
265
266 // Handles a pairing request (i.e. security upgrade) received from "higher
267 // levels", likely initiated from GAP. This will only be used by pairing
268 // requests that are initiated in the context of testing. May only be called on
269 // an already-established connection.
UpgradeSecurity(sm::SecurityLevel level,sm::BondableMode bondable_mode,sm::ResultFunction<> cb)270 void LowEnergyConnection::UpgradeSecurity(sm::SecurityLevel level,
271 sm::BondableMode bondable_mode,
272 sm::ResultFunction<> cb) {
273 PW_CHECK(sm_);
274 sm_->set_bondable_mode(bondable_mode);
275 OnSecurityRequest(level, std::move(cb));
276 }
277
set_security_mode(LESecurityMode mode)278 void LowEnergyConnection::set_security_mode(LESecurityMode mode) {
279 PW_CHECK(sm_);
280 sm_->set_security_mode(mode);
281 }
282
bondable_mode() const283 sm::BondableMode LowEnergyConnection::bondable_mode() const {
284 PW_CHECK(sm_);
285 return sm_->bondable_mode();
286 }
287
security() const288 sm::SecurityProperties LowEnergyConnection::security() const {
289 PW_CHECK(sm_);
290 return sm_->security();
291 }
292
293 // Cancels any on-going pairing procedures and sets up SMP to use the provided
294 // new I/O capabilities for future pairing procedures.
ResetSecurityManager(sm::IOCapability ioc)295 void LowEnergyConnection::ResetSecurityManager(sm::IOCapability ioc) {
296 sm_->Reset(ioc);
297 }
298
OnInterrogationComplete()299 void LowEnergyConnection::OnInterrogationComplete() {
300 PW_CHECK(!interrogation_completed_);
301 interrogation_completed_ = true;
302 MaybeUpdateConnectionParameters();
303 }
304
OpenL2capChannel(l2cap::Psm psm,l2cap::ChannelParameters params,l2cap::ChannelCallback cb)305 void LowEnergyConnection::OpenL2capChannel(l2cap::Psm psm,
306 l2cap::ChannelParameters params,
307 l2cap::ChannelCallback cb) {
308 bt_log(DEBUG,
309 "gap-le",
310 "opening l2cap channel on psm %#.4x (peer: %s)",
311 psm,
312 bt_str(peer_id()));
313 l2cap_->OpenL2capChannel(link()->handle(), psm, params, std::move(cb));
314 }
315
AcceptCis(iso::CigCisIdentifier id,iso::CisEstablishedCallback cb)316 iso::AcceptCisStatus LowEnergyConnection::AcceptCis(
317 iso::CigCisIdentifier id, iso::CisEstablishedCallback cb) {
318 if (role() != pw::bluetooth::emboss::ConnectionRole::PERIPHERAL) {
319 return iso::AcceptCisStatus::kNotPeripheral;
320 }
321 return iso_mgr_->AcceptCis(id, std::move(cb));
322 }
323
AttachInspect(inspect::Node & parent,std::string name)324 void LowEnergyConnection::AttachInspect(inspect::Node& parent,
325 std::string name) {
326 inspect_node_ = parent.CreateChild(name);
327 inspect_properties_.peer_id = inspect_node_.CreateString(
328 kInspectPeerIdPropertyName, peer_id().ToString());
329 inspect_properties_.peer_address = inspect_node_.CreateString(
330 kInspectPeerAddressPropertyName,
331 link_.get() ? link_->peer_address().ToString() : "");
332 refs_.AttachInspect(inspect_node_, kInspectRefsPropertyName);
333 }
334
StartConnectionPauseTimeout()335 void LowEnergyConnection::StartConnectionPauseTimeout() {
336 if (link_->role() == pw::bluetooth::emboss::ConnectionRole::CENTRAL) {
337 StartConnectionPauseCentralTimeout();
338 } else {
339 StartConnectionPausePeripheralTimeout();
340 }
341 }
342
RegisterEventHandlers()343 void LowEnergyConnection::RegisterEventHandlers() {
344 auto self = GetWeakPtr();
345 conn_update_cmpl_handler_id_ = cmd_->AddLEMetaEventHandler(
346 hci_spec::kLEConnectionUpdateCompleteSubeventCode,
347 [self](const hci::EventPacket& event) {
348 if (self.is_alive()) {
349 self->OnLEConnectionUpdateComplete(event);
350 return hci::CommandChannel::EventCallbackResult::kContinue;
351 }
352 return hci::CommandChannel::EventCallbackResult::kRemove;
353 });
354 }
355
356 // Connection parameter updates by the peripheral are not allowed until the
357 // central has been idle for kLEConnectionPauseCentral and
358 // kLEConnectionPausePeripheral has passed since the connection was established
359 // (Core Spec v5.2, Vol 3, Part C, Sec 9.3.12).
360 // TODO(fxbug.dev/42159733): Wait to update connection parameters until all
361 // initialization procedures have completed.
StartConnectionPausePeripheralTimeout()362 void LowEnergyConnection::StartConnectionPausePeripheralTimeout() {
363 PW_CHECK(!conn_pause_peripheral_timeout_.has_value());
364 conn_pause_peripheral_timeout_.emplace(
365 dispatcher_, [this](pw::async::Context /*ctx*/, pw::Status status) {
366 if (!status.ok()) {
367 return;
368 }
369 // Destroying this task will invalidate the capture list,
370 // so we need to save a self pointer.
371 auto self = this;
372 conn_pause_peripheral_timeout_.reset();
373 self->MaybeUpdateConnectionParameters();
374 });
375 conn_pause_peripheral_timeout_->PostAfter(kLEConnectionPausePeripheral);
376 }
377
378 // Connection parameter updates by the central are not allowed until the central
379 // is idle and the peripheral has been idle for kLEConnectionPauseCentral (Core
380 // Spec v5.2, Vol 3, Part C, Sec 9.3.12).
381 // TODO(fxbug.dev/42159733): Wait to update connection parameters until all
382 // initialization procedures have completed.
StartConnectionPauseCentralTimeout()383 void LowEnergyConnection::StartConnectionPauseCentralTimeout() {
384 PW_CHECK(!conn_pause_central_timeout_.has_value());
385 conn_pause_central_timeout_.emplace(
386 dispatcher_, [this](pw::async::Context /*ctx*/, pw::Status status) {
387 if (!status.ok()) {
388 return;
389 }
390 // Destroying this task will invalidate the capture list, so
391 // we need to save a self pointer.
392 auto self = this;
393 conn_pause_central_timeout_.reset();
394 self->MaybeUpdateConnectionParameters();
395 });
396 conn_pause_central_timeout_->PostAfter(kLEConnectionPauseCentral);
397 }
398
OnL2capFixedChannelsOpened(l2cap::Channel::WeakPtr att,l2cap::Channel::WeakPtr smp,LowEnergyConnectionOptions connection_options)399 bool LowEnergyConnection::OnL2capFixedChannelsOpened(
400 l2cap::Channel::WeakPtr att,
401 l2cap::Channel::WeakPtr smp,
402 LowEnergyConnectionOptions connection_options) {
403 bt_log(DEBUG,
404 "gap-le",
405 "ATT and SMP fixed channels open (peer: %s)",
406 bt_str(peer_id()));
407
408 // Obtain existing pairing data, if any.
409 std::optional<sm::LTK> ltk;
410
411 if (peer_->le() && peer_->le()->bond_data()) {
412 // Legacy pairing allows both devices to generate and exchange LTKs. "The
413 // Central must have the security information (LTK, EDIV, and Rand)
414 // distributed by the Peripheral in LE legacy [...] to setup an encrypted
415 // session" (v5.3, Vol. 3 Part H 2.4.4.2). For Secure Connections peer_ltk
416 // and local_ltk will be equal, so this check is unnecessary but correct.
417 ltk = (link()->role() == pw::bluetooth::emboss::ConnectionRole::CENTRAL)
418 ? peer_->le()->bond_data()->peer_ltk
419 : peer_->le()->bond_data()->local_ltk;
420 }
421
422 // Obtain the local I/O capabilities from the delegate. Default to
423 // NoInputNoOutput if no delegate is available.
424 auto io_cap = sm::IOCapability::kNoInputNoOutput;
425 if (conn_mgr_->pairing_delegate().is_alive()) {
426 io_cap = conn_mgr_->pairing_delegate()->io_capability();
427 }
428 LESecurityMode security_mode = conn_mgr_->security_mode();
429 sm_ = conn_mgr_->sm_factory_func()(link_->GetWeakPtr(),
430 std::move(smp),
431 io_cap,
432 weak_delegate_.GetWeakPtr(),
433 connection_options.bondable_mode,
434 security_mode,
435 dispatcher_);
436
437 // Provide SMP with the correct LTK from a previous pairing with the peer, if
438 // it exists. This will start encryption if the local device is the link-layer
439 // central.
440 if (ltk) {
441 bt_log(INFO,
442 "gap-le",
443 "assigning existing LTK (peer: %s, handle: %#.4x)",
444 bt_str(peer_id()),
445 handle());
446 sm_->AssignLongTermKey(*ltk);
447 }
448
449 return InitializeGatt(std::move(att), connection_options.service_uuid);
450 }
451
OnNewLEConnectionParams(const hci_spec::LEPreferredConnectionParameters & params)452 void LowEnergyConnection::OnNewLEConnectionParams(
453 const hci_spec::LEPreferredConnectionParameters& params) {
454 bt_log(INFO,
455 "gap-le",
456 "LE connection parameters received (peer: %s, handle: %#.4x)",
457 bt_str(peer_id()),
458 link_->handle());
459
460 PW_CHECK(peer_.is_alive());
461
462 peer_->MutLe().SetPreferredConnectionParameters(params);
463
464 UpdateConnectionParams(params);
465 }
466
RequestConnectionParameterUpdate(const hci_spec::LEPreferredConnectionParameters & params)467 void LowEnergyConnection::RequestConnectionParameterUpdate(
468 const hci_spec::LEPreferredConnectionParameters& params) {
469 PW_CHECK(link_->role() == pw::bluetooth::emboss::ConnectionRole::PERIPHERAL,
470 "tried to send connection parameter update request as central");
471
472 PW_CHECK(peer_.is_alive());
473 // Ensure interrogation has completed.
474 PW_CHECK(peer_->le()->feature_interrogation_complete());
475
476 // TODO(fxbug.dev/42126713): check local controller support for LL Connection
477 // Parameters Request procedure (mask is currently in Adapter le state,
478 // consider propagating down)
479 bool ll_connection_parameters_req_supported =
480 peer_->le()->features().has_value() &&
481 (peer_->le()->features()->le_features &
482 static_cast<uint64_t>(hci_spec::LESupportedFeature::
483 kConnectionParametersRequestProcedure));
484
485 bt_log(TRACE,
486 "gap-le",
487 "ll connection parameters req procedure supported: %s",
488 ll_connection_parameters_req_supported ? "true" : "false");
489
490 if (ll_connection_parameters_req_supported) {
491 auto self = weak_self_.GetWeakPtr();
492 auto status_cb = [self, params](hci::Result<> status) {
493 if (!self.is_alive()) {
494 return;
495 }
496
497 self->HandleRequestConnectionParameterUpdateCommandStatus(params, status);
498 };
499
500 UpdateConnectionParams(params, std::move(status_cb));
501 } else {
502 L2capRequestConnectionParameterUpdate(params);
503 }
504 }
505
HandleRequestConnectionParameterUpdateCommandStatus(hci_spec::LEPreferredConnectionParameters params,hci::Result<> status)506 void LowEnergyConnection::HandleRequestConnectionParameterUpdateCommandStatus(
507 hci_spec::LEPreferredConnectionParameters params, hci::Result<> status) {
508 // The next LE Connection Update complete event is for this command iff the
509 // command |status| is success.
510 if (status.is_error()) {
511 if (status ==
512 ToResult(
513 pw::bluetooth::emboss::StatusCode::UNSUPPORTED_REMOTE_FEATURE)) {
514 // Retry connection parameter update with l2cap if the peer doesn't
515 // support LL procedure.
516 bt_log(INFO,
517 "gap-le",
518 "peer does not support HCI LE Connection Update command, trying "
519 "l2cap request (peer: %s)",
520 bt_str(peer_id()));
521 L2capRequestConnectionParameterUpdate(params);
522 }
523 return;
524 }
525
526 // Note that this callback is for the Connection Update Complete event, not
527 // the Connection Update status event, which is handled by the above code (see
528 // v5.2, Vol. 4, Part E 7.7.15 / 7.7.65.3).
529 le_conn_update_complete_command_callback_ =
530 [this, params](pw::bluetooth::emboss::StatusCode completion_status) {
531 // Retry connection parameter update with l2cap if the peer doesn't
532 // support LL procedure.
533 if (completion_status ==
534 pw::bluetooth::emboss::StatusCode::UNSUPPORTED_REMOTE_FEATURE) {
535 bt_log(INFO,
536 "gap-le",
537 "peer does not support HCI LE Connection Update command, "
538 "trying l2cap request "
539 "(peer: %s)",
540 bt_str(peer_id()));
541 L2capRequestConnectionParameterUpdate(params);
542 }
543 };
544 }
545
L2capRequestConnectionParameterUpdate(const hci_spec::LEPreferredConnectionParameters & params)546 void LowEnergyConnection::L2capRequestConnectionParameterUpdate(
547 const hci_spec::LEPreferredConnectionParameters& params) {
548 PW_CHECK(
549 link_->role() == pw::bluetooth::emboss::ConnectionRole::PERIPHERAL,
550 "tried to send l2cap connection parameter update request as central");
551
552 bt_log(DEBUG,
553 "gap-le",
554 "sending l2cap connection parameter update request (peer: %s)",
555 bt_str(peer_id()));
556
557 auto response_cb = [handle = handle(), peer_id = peer_id()](bool accepted) {
558 if (accepted) {
559 bt_log(DEBUG,
560 "gap-le",
561 "peer accepted l2cap connection parameter update request (peer: "
562 "%s, handle: %#.4x)",
563 bt_str(peer_id),
564 handle);
565 } else {
566 bt_log(INFO,
567 "gap-le",
568 "peer rejected l2cap connection parameter update request (peer: "
569 "%s, handle: %#.4x)",
570 bt_str(peer_id),
571 handle);
572 }
573 };
574
575 // TODO(fxbug.dev/42126716): don't send request until after
576 // kLEConnectionParameterTimeout of an l2cap conn parameter update response
577 // being received (Core Spec v5.2, Vol 3, Part C, Sec 9.3.9).
578 l2cap_->RequestConnectionParameterUpdate(
579 handle(), params, std::move(response_cb));
580 }
581
UpdateConnectionParams(const hci_spec::LEPreferredConnectionParameters & params,StatusCallback status_cb)582 void LowEnergyConnection::UpdateConnectionParams(
583 const hci_spec::LEPreferredConnectionParameters& params,
584 StatusCallback status_cb) {
585 bt_log(DEBUG,
586 "gap-le",
587 "updating connection parameters (peer: %s)",
588 bt_str(peer_id()));
589 auto command = hci::CommandPacket::New<
590 pw::bluetooth::emboss::LEConnectionUpdateCommandWriter>(
591 hci_spec::kLEConnectionUpdate);
592 auto view = command.view_t();
593 view.connection_handle().Write(handle());
594 // TODO(fxbug.dev/42074287): Handle invalid connection parameters before
595 // sending them to the controller.
596 view.connection_interval_min().UncheckedWrite(params.min_interval());
597 view.connection_interval_max().UncheckedWrite(params.max_interval());
598 view.max_latency().UncheckedWrite(params.max_latency());
599 view.supervision_timeout().UncheckedWrite(params.supervision_timeout());
600 view.min_connection_event_length().Write(0x0000);
601 view.max_connection_event_length().Write(0x0000);
602
603 auto status_cb_wrapper = [handle = handle(), cb = std::move(status_cb)](
604 auto, const hci::EventPacket& event) mutable {
605 PW_CHECK(event.event_code() == hci_spec::kCommandStatusEventCode);
606 HCI_IS_ERROR(event,
607 TRACE,
608 "gap-le",
609 "controller rejected connection parameters (handle: %#.4x)",
610 handle);
611 if (cb) {
612 cb(event.ToResult());
613 }
614 };
615
616 cmd_->SendCommand(std::move(command),
617 std::move(status_cb_wrapper),
618 hci_spec::kCommandStatusEventCode);
619 }
620
OnLEConnectionUpdateComplete(const hci::EventPacket & event)621 void LowEnergyConnection::OnLEConnectionUpdateComplete(
622 const hci::EventPacket& event) {
623 PW_CHECK(event.event_code() == hci_spec::kLEMetaEventCode);
624 auto view = event.view<pw::bluetooth::emboss::LEMetaEventView>();
625 PW_CHECK(view.subevent_code().Read() ==
626 hci_spec::kLEConnectionUpdateCompleteSubeventCode);
627
628 auto payload = event.view<
629 pw::bluetooth::emboss::LEConnectionUpdateCompleteSubeventView>();
630 hci_spec::ConnectionHandle handle = payload.connection_handle().Read();
631
632 // Ignore events for other connections.
633 if (handle != link_->handle()) {
634 return;
635 }
636
637 // This event may be the result of the LE Connection Update command.
638 if (le_conn_update_complete_command_callback_) {
639 le_conn_update_complete_command_callback_(payload.status().Read());
640 }
641
642 if (payload.status().Read() != pw::bluetooth::emboss::StatusCode::SUCCESS) {
643 bt_log(WARN,
644 "gap-le",
645 "HCI LE Connection Update Complete event with error "
646 "(peer: %s, status: %#.2hhx, handle: %#.4x)",
647 bt_str(peer_id()),
648 static_cast<unsigned char>(payload.status().Read()),
649 handle);
650
651 return;
652 }
653
654 bt_log(
655 INFO, "gap-le", "conn. parameters updated (peer: %s)", bt_str(peer_id()));
656
657 hci_spec::LEConnectionParameters params(
658 payload.connection_interval().UncheckedRead(),
659 payload.peripheral_latency().UncheckedRead(),
660 payload.supervision_timeout().UncheckedRead());
661 link_->set_low_energy_parameters(params);
662
663 PW_CHECK(peer_.is_alive());
664 peer_->MutLe().SetConnectionParameters(params);
665 }
666
MaybeUpdateConnectionParameters()667 void LowEnergyConnection::MaybeUpdateConnectionParameters() {
668 if (connection_parameters_update_requested_ || conn_pause_central_timeout_ ||
669 conn_pause_peripheral_timeout_ || !interrogation_completed_) {
670 return;
671 }
672
673 connection_parameters_update_requested_ = true;
674
675 if (link_->role() == pw::bluetooth::emboss::ConnectionRole::CENTRAL) {
676 // If the GAP service preferred connection parameters characteristic has not
677 // been read by now, just use the default parameters.
678 // TODO(fxbug.dev/42144795): Wait for preferred connection parameters to be
679 // read.
680 PW_CHECK(peer_.is_alive());
681 auto conn_params = peer_->le()->preferred_connection_parameters().value_or(
682 kDefaultPreferredConnectionParameters);
683 UpdateConnectionParams(conn_params);
684 } else {
685 RequestConnectionParameterUpdate(kDefaultPreferredConnectionParameters);
686 }
687 }
688
InitializeGatt(l2cap::Channel::WeakPtr att_channel,std::optional<UUID> service_uuid)689 bool LowEnergyConnection::InitializeGatt(l2cap::Channel::WeakPtr att_channel,
690 std::optional<UUID> service_uuid) {
691 att_bearer_ = att::Bearer::Create(std::move(att_channel), dispatcher_);
692 if (!att_bearer_) {
693 // This can happen if the link closes before the Bearer activates the
694 // channel.
695 bt_log(WARN, "gatt", "failed to initialize ATT bearer");
696 return false;
697 }
698
699 // The att::Bearer object is owned by LowEnergyConnection, so it outlives the
700 // gatt::Server and Client objects. As such, they can safely take WeakPtrs to
701 // the Bearer.
702 auto server_factory =
703 [att_bearer = att_bearer_->GetWeakPtr()](
704 PeerId peer_id,
705 gatt::LocalServiceManager::WeakPtr local_services) mutable {
706 return gatt::Server::Create(
707 peer_id, std::move(local_services), std::move(att_bearer));
708 };
709 std::unique_ptr<gatt::Client> gatt_client =
710 gatt::Client::Create(att_bearer_->GetWeakPtr());
711 gatt_->AddConnection(
712 peer_id(), std::move(gatt_client), std::move(server_factory));
713
714 std::vector<UUID> service_uuids;
715 if (service_uuid) {
716 // TODO(fxbug.dev/42144310): De-duplicate services.
717 service_uuids = {*service_uuid, kGenericAccessService};
718 }
719 gatt_->InitializeClient(peer_id(), std::move(service_uuids));
720
721 auto self = weak_self_.GetWeakPtr();
722 gatt_->ListServices(
723 peer_id(), {kGenericAccessService}, [self](auto status, auto services) {
724 if (self.is_alive()) {
725 self->OnGattServicesResult(status, std::move(services));
726 }
727 });
728
729 return true;
730 }
731
OnGattServicesResult(att::Result<> status,gatt::ServiceList services)732 void LowEnergyConnection::OnGattServicesResult(att::Result<> status,
733 gatt::ServiceList services) {
734 if (bt_is_error(status,
735 INFO,
736 "gap-le",
737 "error discovering GAP service (peer: %s)",
738 bt_str(peer_id()))) {
739 return;
740 }
741
742 if (services.empty()) {
743 // The GAP service is mandatory for both central and peripheral, so this is
744 // unexpected.
745 bt_log(
746 INFO, "gap-le", "GAP service not found (peer: %s)", bt_str(peer_id()));
747 return;
748 }
749
750 gap_service_client_.emplace(peer_id(), services.front());
751 auto self = weak_self_.GetWeakPtr();
752
753 gap_service_client_->ReadDeviceName([self](att::Result<std::string> result) {
754 if (!self.is_alive() || result.is_error()) {
755 return;
756 }
757
758 self->peer_->RegisterName(result.value(),
759 Peer::NameSource::kGenericAccessService);
760 });
761
762 gap_service_client_->ReadAppearance([self](att::Result<uint16_t> result) {
763 if (!self.is_alive() || result.is_error()) {
764 return;
765 }
766
767 self->peer_->SetAppearance(result.value());
768 });
769
770 if (!peer_->le()->preferred_connection_parameters().has_value()) {
771 gap_service_client_->ReadPeripheralPreferredConnectionParameters(
772 [self](att::Result<hci_spec::LEPreferredConnectionParameters> result) {
773 if (!self.is_alive()) {
774 return;
775 }
776
777 if (result.is_error()) {
778 bt_log(INFO,
779 "gap-le",
780 "error reading peripheral preferred connection parameters "
781 "(status: %s, peer: %s)",
782 ::bt::internal::ToString(result).c_str(),
783 bt_str(self->peer_id()));
784 return;
785 }
786
787 auto params = result.value();
788 self->peer_->MutLe().SetPreferredConnectionParameters(params);
789 });
790 }
791 }
792
CloseRefs()793 void LowEnergyConnection::CloseRefs() {
794 for (auto* ref : *refs_.Mutable()) {
795 ref->MarkClosed();
796 }
797
798 refs_.Mutable()->clear();
799 }
800
OnNewPairingData(const sm::PairingData & pairing_data)801 void LowEnergyConnection::OnNewPairingData(
802 const sm::PairingData& pairing_data) {
803 const std::optional<sm::LTK> ltk =
804 pairing_data.peer_ltk ? pairing_data.peer_ltk : pairing_data.local_ltk;
805 // Consider the pairing temporary if no link key was received. This
806 // means we'll remain encrypted with the STK without creating a bond and
807 // reinitiate pairing when we reconnect in the future.
808 if (!ltk.has_value()) {
809 bt_log(INFO,
810 "gap-le",
811 "temporarily paired with peer (peer: %s)",
812 bt_str(peer_id()));
813 return;
814 }
815
816 bt_log(INFO,
817 "gap-le",
818 "new %s pairing data: [%s%s%s%s%s%s] (peer: %s)",
819 ltk->security().secure_connections() ? "secure connections" : "legacy",
820 pairing_data.peer_ltk ? "peer_ltk " : "",
821 pairing_data.local_ltk ? "local_ltk " : "",
822 pairing_data.irk ? "irk " : "",
823 pairing_data.cross_transport_key ? "ct_key " : "",
824 pairing_data.identity_address
825 ? bt_lib_cpp_string::StringPrintf(
826 "(identity: %s) ", bt_str(*pairing_data.identity_address))
827 .c_str()
828 : "",
829 pairing_data.csrk ? "csrk " : "",
830 bt_str(peer_id()));
831
832 if (!peer_->MutLe().StoreBond(pairing_data)) {
833 bt_log(ERROR,
834 "gap-le",
835 "failed to cache bonding data (id: %s)",
836 bt_str(peer_id()));
837 }
838 }
839
OnPairingComplete(sm::Result<> status)840 void LowEnergyConnection::OnPairingComplete(sm::Result<> status) {
841 bt_log(INFO,
842 "gap-le",
843 "pairing complete (status: %s, peer: %s)",
844 bt_str(status),
845 bt_str(peer_id()));
846
847 auto delegate = conn_mgr_->pairing_delegate();
848 if (delegate.is_alive()) {
849 delegate->CompletePairing(peer_id(), status);
850 }
851 }
852
OnAuthenticationFailure(hci::Result<> status)853 void LowEnergyConnection::OnAuthenticationFailure(hci::Result<> status) {
854 // TODO(armansito): Clear bonding data from the remote peer cache as any
855 // stored link key is not valid.
856 bt_log(WARN,
857 "gap-le",
858 "link layer authentication failed (status: %s, peer: %s)",
859 bt_str(status),
860 bt_str(peer_id()));
861 }
862
OnNewSecurityProperties(const sm::SecurityProperties & sec)863 void LowEnergyConnection::OnNewSecurityProperties(
864 const sm::SecurityProperties& sec) {
865 bt_log(INFO,
866 "gap-le",
867 "new link security properties (properties: %s, peer: %s)",
868 bt_str(sec),
869 bt_str(peer_id()));
870 // Update the data plane with the correct link security level.
871 l2cap_->AssignLinkSecurityProperties(link_->handle(), sec);
872 }
873
874 std::optional<sm::IdentityInfo>
OnIdentityInformationRequest()875 LowEnergyConnection::OnIdentityInformationRequest() {
876 if (!conn_mgr_->local_address_delegate()->irk()) {
877 bt_log(TRACE, "gap-le", "no local identity information to exchange");
878 return std::nullopt;
879 }
880
881 bt_log(DEBUG,
882 "gap-le",
883 "will distribute local identity information (peer: %s)",
884 bt_str(peer_id()));
885 sm::IdentityInfo id_info;
886 id_info.irk = *conn_mgr_->local_address_delegate()->irk();
887 id_info.address = conn_mgr_->local_address_delegate()->identity_address();
888
889 return id_info;
890 }
891
ConfirmPairing(ConfirmCallback confirm)892 void LowEnergyConnection::ConfirmPairing(ConfirmCallback confirm) {
893 bt_log(INFO,
894 "gap-le",
895 "pairing delegate request for pairing confirmation w/ no passkey "
896 "(peer: %s)",
897 bt_str(peer_id()));
898
899 auto delegate = conn_mgr_->pairing_delegate();
900 if (!delegate.is_alive()) {
901 bt_log(ERROR,
902 "gap-le",
903 "rejecting pairing without a PairingDelegate! (peer: %s)",
904 bt_str(peer_id()));
905 confirm(false);
906 } else {
907 delegate->ConfirmPairing(peer_id(), std::move(confirm));
908 }
909 }
910
DisplayPasskey(uint32_t passkey,sm::Delegate::DisplayMethod method,ConfirmCallback confirm)911 void LowEnergyConnection::DisplayPasskey(uint32_t passkey,
912 sm::Delegate::DisplayMethod method,
913 ConfirmCallback confirm) {
914 bt_log(INFO,
915 "gap-le",
916 "pairing delegate request (method: %s, peer: %s)",
917 sm::util::DisplayMethodToString(method).c_str(),
918 bt_str(peer_id()));
919
920 auto delegate = conn_mgr_->pairing_delegate();
921 if (!delegate.is_alive()) {
922 bt_log(ERROR, "gap-le", "rejecting pairing without a PairingDelegate!");
923 confirm(false);
924 } else {
925 delegate->DisplayPasskey(peer_id(), passkey, method, std::move(confirm));
926 }
927 }
928
RequestPasskey(PasskeyResponseCallback respond)929 void LowEnergyConnection::RequestPasskey(PasskeyResponseCallback respond) {
930 bt_log(INFO,
931 "gap-le",
932 "pairing delegate request for passkey entry (peer: %s)",
933 bt_str(peer_id()));
934
935 auto delegate = conn_mgr_->pairing_delegate();
936 if (!delegate.is_alive()) {
937 bt_log(ERROR,
938 "gap-le",
939 "rejecting pairing without a PairingDelegate! (peer: %s)",
940 bt_str(peer_id()));
941 respond(-1);
942 } else {
943 delegate->RequestPasskey(peer_id(), std::move(respond));
944 }
945 }
946
947 } // namespace bt::gap::internal
948