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