1 // Copyright 2024 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/fuchsia/host/fidl/host_server.h"
16
17 #include <cpp-string/string_printf.h>
18 #include <lib/fpromise/result.h>
19 #include <zircon/errors.h>
20
21 #include <utility>
22 #include <variant>
23
24 #include "fuchsia/bluetooth/host/cpp/fidl.h"
25 #include "pw_bluetooth_sapphire/fuchsia/host/fidl/gatt2_server_server.h"
26 #include "pw_bluetooth_sapphire/fuchsia/host/fidl/gatt_server_server.h"
27 #include "pw_bluetooth_sapphire/fuchsia/host/fidl/helpers.h"
28 #include "pw_bluetooth_sapphire/fuchsia/host/fidl/low_energy_central_server.h"
29 #include "pw_bluetooth_sapphire/fuchsia/host/fidl/low_energy_peripheral_server.h"
30 #include "pw_bluetooth_sapphire/fuchsia/host/fidl/profile_server.h"
31 #include "pw_bluetooth_sapphire/internal/host/common/assert.h"
32 #include "pw_bluetooth_sapphire/internal/host/common/identifier.h"
33 #include "pw_bluetooth_sapphire/internal/host/common/log.h"
34 #include "pw_bluetooth_sapphire/internal/host/gap/adapter.h"
35 #include "pw_bluetooth_sapphire/internal/host/gap/bonding_data.h"
36 #include "pw_bluetooth_sapphire/internal/host/gap/bredr_connection_manager.h"
37 #include "pw_bluetooth_sapphire/internal/host/gap/bredr_discovery_manager.h"
38 #include "pw_bluetooth_sapphire/internal/host/gap/gap.h"
39 #include "pw_bluetooth_sapphire/internal/host/gap/low_energy_discovery_manager.h"
40 #include "pw_bluetooth_sapphire/internal/host/sm/types.h"
41 #include "pw_bluetooth_sapphire/internal/host/sm/util.h"
42
43 namespace bthost {
44
45 namespace fbt = fuchsia::bluetooth;
46 namespace fsys = fuchsia::bluetooth::sys;
47 namespace fhost = fuchsia::bluetooth::host;
48
49 using bt::PeerId;
50 using bt::gap::BrEdrSecurityModeToString;
51 using bt::gap::LeSecurityModeToString;
52 using bt::sm::IOCapability;
53 using fidl_helpers::BrEdrSecurityModeFromFidl;
54 using fidl_helpers::HostErrorToFidl;
55 using fidl_helpers::LeSecurityModeFromFidl;
56 using fidl_helpers::NewFidlError;
57 using fidl_helpers::PeerIdFromString;
58 using fidl_helpers::ResultToFidl;
59 using fidl_helpers::SecurityLevelFromFidl;
60
HostServer(zx::channel channel,const bt::gap::Adapter::WeakPtr & adapter,bt::gatt::GATT::WeakPtr gatt)61 HostServer::HostServer(zx::channel channel,
62 const bt::gap::Adapter::WeakPtr& adapter,
63 bt::gatt::GATT::WeakPtr gatt)
64 : AdapterServerBase(adapter, this, std::move(channel)),
65 pairing_delegate_(nullptr),
66 gatt_(std::move(gatt)),
67 requesting_background_scan_(false),
68 requesting_discoverable_(false),
69 io_capability_(IOCapability::kNoInputNoOutput),
70 weak_self_(this),
71 weak_pairing_(this) {
72 PW_CHECK(gatt_.is_alive());
73
74 auto self = weak_self_.GetWeakPtr();
75 adapter->peer_cache()->set_peer_bonded_callback([self](const auto& peer) {
76 if (self.is_alive()) {
77 self->OnPeerBonded(peer);
78 }
79 });
80 adapter->set_auto_connect_callback([self](auto conn_ref) {
81 if (self.is_alive()) {
82 self->RegisterLowEnergyConnection(std::move(conn_ref),
83 /*auto_connect=*/true);
84 }
85 });
86
87 // Watch for changes in LE address.
88 adapter->le()->register_address_changed_callback([self]() {
89 if (self.is_alive()) {
90 self->NotifyInfoChange();
91 }
92 });
93
94 // Initialize the HostInfo getter with the initial state.
95 NotifyInfoChange();
96 }
97
~HostServer()98 HostServer::~HostServer() { Shutdown(); }
99
RequestProtocol(fhost::ProtocolRequest request)100 void HostServer::RequestProtocol(fhost::ProtocolRequest request) {
101 switch (request.Which()) {
102 case fhost::ProtocolRequest::Tag::kCentral:
103 BindServer<LowEnergyCentralServer>(
104 adapter()->AsWeakPtr(), std::move(request.central()), gatt_);
105 break;
106 case fhost::ProtocolRequest::Tag::kPeripheral:
107 BindServer<LowEnergyPeripheralServer>(
108 adapter()->AsWeakPtr(), gatt_, std::move(request.peripheral()));
109 break;
110 case fhost::ProtocolRequest::Tag::kGattServer:
111 BindServer<GattServerServer>(gatt_->GetWeakPtr(),
112 std::move(request.gatt_server()));
113 break;
114 case fhost::ProtocolRequest::Tag::kGatt2Server:
115 BindServer<Gatt2ServerServer>(gatt_->GetWeakPtr(),
116 std::move(request.gatt2_server()));
117 break;
118 case fhost::ProtocolRequest::Tag::kProfile:
119 BindServer<ProfileServer>(adapter()->AsWeakPtr(),
120 std::move(request.profile()));
121 break;
122 case fhost::ProtocolRequest::Tag::kPrivilegedPeripheral:
123 BindServer<LowEnergyPrivilegedPeripheralServer>(
124 adapter()->AsWeakPtr(),
125 gatt_,
126 std::move(request.privileged_peripheral()));
127 break;
128 default:
129 bt_log(WARN, "fidl", "received unknown protocol request");
130 // The unknown protocol will be closed when `request` is destroyed.
131 break;
132 }
133 }
134
WatchState(WatchStateCallback callback)135 void HostServer::WatchState(WatchStateCallback callback) {
136 info_getter_.Watch([cb = std::move(callback)](fsys::HostInfo info) {
137 cb(fhost::Host_WatchState_Result::WithResponse(
138 fhost::Host_WatchState_Response(std::move(info))));
139 });
140 }
141
SetLocalData(fsys::HostData host_data)142 void HostServer::SetLocalData(fsys::HostData host_data) {
143 if (host_data.has_irk()) {
144 bt_log(DEBUG, "fidl", "assign IRK");
145 if (adapter()->le()) {
146 adapter()->le()->set_irk(host_data.irk().value);
147 }
148 }
149 }
150
SetPeerWatcher(::fidl::InterfaceRequest<::fuchsia::bluetooth::host::PeerWatcher> peer_watcher)151 void HostServer::SetPeerWatcher(
152 ::fidl::InterfaceRequest<::fuchsia::bluetooth::host::PeerWatcher>
153 peer_watcher) {
154 if (peer_watcher_server_.has_value()) {
155 peer_watcher.Close(ZX_ERR_ALREADY_BOUND);
156 return;
157 }
158 peer_watcher_server_.emplace(
159 std::move(peer_watcher), adapter()->peer_cache(), this);
160 }
161
SetLocalName(::std::string local_name,SetLocalNameCallback callback)162 void HostServer::SetLocalName(::std::string local_name,
163 SetLocalNameCallback callback) {
164 PW_DCHECK(!local_name.empty());
165 adapter()->SetLocalName(std::move(local_name),
166 [self = weak_self_.GetWeakPtr(),
167 callback = std::move(callback)](auto status) {
168 // Send adapter state update on success and if the
169 // connection is still open.
170 if (status.is_ok() && self.is_alive()) {
171 self->NotifyInfoChange();
172 }
173 callback(ResultToFidl(status));
174 });
175 }
176
177 // TODO(fxbug.dev/42110379): Add a unit test for this method.
SetDeviceClass(fbt::DeviceClass device_class,SetDeviceClassCallback callback)178 void HostServer::SetDeviceClass(fbt::DeviceClass device_class,
179 SetDeviceClassCallback callback) {
180 // Device Class values must only contain data in the lower 3 bytes.
181 if (device_class.value >= 1 << 24) {
182 callback(fpromise::error(fsys::Error::INVALID_ARGUMENTS));
183 return;
184 }
185 bt::DeviceClass dev_class(device_class.value);
186 adapter()->SetDeviceClass(dev_class,
187 [callback = std::move(callback)](auto status) {
188 callback(ResultToFidl(status));
189 });
190 }
191
StartLEDiscovery()192 void HostServer::StartLEDiscovery() {
193 if (!adapter()->le()) {
194 StopDiscovery(ZX_ERR_INTERNAL);
195 return;
196 }
197
198 adapter()->le()->StartDiscovery(
199 /*active=*/true, [self = weak_self_.GetWeakPtr()](auto session) {
200 // End the new session if this AdapterServer got destroyed in the
201 // meantime (e.g. because the client disconnected).
202 if (!self.is_alive() || self->discovery_session_servers_.empty()) {
203 return;
204 }
205
206 if (!session) {
207 bt_log(ERROR, "fidl", "failed to start active LE discovery session");
208 self->StopDiscovery(ZX_ERR_INTERNAL);
209 return;
210 }
211
212 // Set up a general-discovery filter for connectable devices.
213 // NOTE(armansito): This currently has no effect since peer updates
214 // are driven by PeerCache events. |session|'s "result callback" is
215 // unused.
216 session->filter()->set_connectable(true);
217 session->filter()->SetGeneralDiscoveryFlags();
218
219 self->le_discovery_session_ = std::move(session);
220
221 // Send the adapter state update.
222 self->NotifyInfoChange();
223 });
224 }
225
StartDiscovery(::fuchsia::bluetooth::host::HostStartDiscoveryRequest request)226 void HostServer::StartDiscovery(
227 ::fuchsia::bluetooth::host::HostStartDiscoveryRequest request) {
228 bt_log(DEBUG, "fidl", "%s", __FUNCTION__);
229 PW_DCHECK(adapter().is_alive());
230
231 if (!request.has_token()) {
232 bt_log(WARN, "fidl", "missing Discovery token");
233 return;
234 }
235 fidl::InterfaceRequest<fhost::DiscoverySession> token =
236 std::move(*request.mutable_token());
237
238 std::unique_ptr<DiscoverySessionServer> server =
239 std::make_unique<DiscoverySessionServer>(std::move(token), this);
240 DiscoverySessionServer* server_ptr = server.get();
241 discovery_session_servers_.emplace(server_ptr, std::move(server));
242
243 // If there were existing sessions, then discovery is already
244 // starting/started.
245 if (discovery_session_servers_.size() != 1) {
246 return;
247 }
248
249 if (!adapter()->bredr()) {
250 StartLEDiscovery();
251 return;
252 }
253 // TODO(jamuraa): start these in parallel instead of sequence
254 adapter()->bredr()->RequestDiscovery([self = weak_self_.GetWeakPtr(),
255 func = __FUNCTION__](
256 bt::hci::Result<> result,
257 auto session) mutable {
258 if (!self.is_alive() || self->discovery_session_servers_.empty()) {
259 return;
260 }
261
262 if (result.is_error() || !session) {
263 bt_log(
264 ERROR, "fidl", "%s: failed to start BR/EDR discovery session", func);
265 self->StopDiscovery(ZX_ERR_INTERNAL);
266 return;
267 }
268
269 self->bredr_discovery_session_ = std::move(session);
270 self->StartLEDiscovery();
271 });
272 }
273
StopDiscovery(zx_status_t epitaph,bool notify_info_change)274 void HostServer::StopDiscovery(zx_status_t epitaph, bool notify_info_change) {
275 bool discovering = le_discovery_session_ || bredr_discovery_session_;
276 bredr_discovery_session_ = nullptr;
277 le_discovery_session_ = nullptr;
278 for (auto& [server, _] : discovery_session_servers_) {
279 server->Close(epitaph);
280 }
281 discovery_session_servers_.clear();
282
283 if (discovering && notify_info_change) {
284 NotifyInfoChange();
285 }
286 }
287
OnDiscoverySessionServerClose(DiscoverySessionServer * server)288 void HostServer::OnDiscoverySessionServerClose(DiscoverySessionServer* server) {
289 server->Close(ZX_ERR_CANCELED);
290 discovery_session_servers_.erase(server);
291 if (discovery_session_servers_.empty()) {
292 StopDiscovery(ZX_ERR_CANCELED);
293 }
294 }
295
SetConnectable(bool connectable,SetConnectableCallback callback)296 void HostServer::SetConnectable(bool connectable,
297 SetConnectableCallback callback) {
298 bt_log(INFO, "fidl", "%s: %s", __FUNCTION__, connectable ? "true" : "false");
299
300 auto classic = adapter()->bredr();
301 if (!classic) {
302 callback(fpromise::error(fsys::Error::NOT_SUPPORTED));
303 return;
304 }
305 classic->SetConnectable(connectable,
306 [callback = std::move(callback)](const auto& result) {
307 callback(ResultToFidl(result));
308 });
309 }
310
RestoreBonds(::std::vector<fsys::BondingData> bonds,fhost::BondingDelegate::RestoreBondsCallback callback)311 void HostServer::RestoreBonds(
312 ::std::vector<fsys::BondingData> bonds,
313 fhost::BondingDelegate::RestoreBondsCallback callback) {
314 bt_log(INFO, "fidl", "%s", __FUNCTION__);
315
316 if (bonds.empty()) {
317 // Nothing to do. Reply with an empty list.
318 callback(fhost::BondingDelegate_RestoreBonds_Result::WithResponse(
319 fhost::BondingDelegate_RestoreBonds_Response()));
320 return;
321 }
322
323 std::vector<fsys::BondingData> errors;
324 for (auto& bond : bonds) {
325 if (!bond.has_identifier() || !bond.has_address() ||
326 !(bond.has_le_bond() || bond.has_bredr_bond())) {
327 bt_log(ERROR,
328 "fidl",
329 "%s: BondingData mandatory fields missing!",
330 __FUNCTION__);
331 errors.push_back(std::move(bond));
332 continue;
333 }
334
335 auto address = fidl_helpers::AddressFromFidlBondingData(bond);
336 if (!address) {
337 bt_log(ERROR,
338 "fidl",
339 "%s: BondingData address missing or invalid!",
340 __FUNCTION__);
341 errors.push_back(std::move(bond));
342 continue;
343 }
344
345 bt::gap::BondingData bd;
346 bd.identifier = bt::PeerId{bond.identifier().value};
347 bd.address = *address;
348 if (bond.has_name()) {
349 bd.name = {bond.name()};
350 }
351
352 if (bond.has_le_bond()) {
353 bd.le_pairing_data =
354 fidl_helpers::LePairingDataFromFidl(*address, bond.le_bond());
355 }
356 if (bond.has_bredr_bond()) {
357 bd.bredr_link_key = fidl_helpers::BredrKeyFromFidl(bond.bredr_bond());
358 bd.bredr_services =
359 fidl_helpers::BredrServicesFromFidl(bond.bredr_bond());
360 }
361
362 // TODO(fxbug.dev/42137736): Convert bond.bredr.services to
363 // BondingData::bredr_services
364 if (!adapter()->AddBondedPeer(bd)) {
365 bt_log(ERROR,
366 "fidl",
367 "%s: failed to restore bonding data entry",
368 __FUNCTION__);
369 errors.push_back(std::move(bond));
370 }
371 }
372
373 callback(fhost::BondingDelegate_RestoreBonds_Result::WithResponse(
374 fhost::BondingDelegate_RestoreBonds_Response(std::move(errors))));
375 }
376
OnPeerBonded(const bt::gap::Peer & peer)377 void HostServer::OnPeerBonded(const bt::gap::Peer& peer) {
378 bt_log(DEBUG, "fidl", "%s", __FUNCTION__);
379 if (bonding_delegate_server_) {
380 bonding_delegate_server_->OnNewBondingData(peer);
381 }
382 }
383
RegisterLowEnergyConnection(std::unique_ptr<bt::gap::LowEnergyConnectionHandle> conn_ref,bool auto_connect)384 void HostServer::RegisterLowEnergyConnection(
385 std::unique_ptr<bt::gap::LowEnergyConnectionHandle> conn_ref,
386 bool auto_connect) {
387 PW_DCHECK(conn_ref);
388
389 bt::PeerId id = conn_ref->peer_identifier();
390 auto iter = le_connections_.find(id);
391 if (iter != le_connections_.end()) {
392 bt_log(
393 WARN,
394 "fidl",
395 "%s: peer already connected; connection reference dropped (peer: %s)",
396 __FUNCTION__,
397 bt_str(id));
398 return;
399 }
400
401 bt_log(DEBUG,
402 "fidl",
403 "LE peer connected (%s): %s ",
404 (auto_connect ? "auto" : "direct"),
405 bt_str(id));
406 conn_ref->set_closed_callback([self = weak_self_.GetWeakPtr(), id] {
407 if (self.is_alive())
408 self->le_connections_.erase(id);
409 });
410 le_connections_[id] = std::move(conn_ref);
411 }
412
SetDiscoverable(bool discoverable,SetDiscoverableCallback callback)413 void HostServer::SetDiscoverable(bool discoverable,
414 SetDiscoverableCallback callback) {
415 bt_log(INFO, "fidl", "%s(%s)", __FUNCTION__, discoverable ? "true" : "false");
416 // TODO(fxbug.dev/42177512): advertise LE here
417 if (!discoverable) {
418 bredr_discoverable_session_ = nullptr;
419 NotifyInfoChange();
420 callback(fpromise::ok());
421 return;
422 }
423 if (discoverable && requesting_discoverable_) {
424 bt_log(DEBUG, "fidl", "%s already in progress", __FUNCTION__);
425 callback(fpromise::error(fsys::Error::IN_PROGRESS));
426 return;
427 }
428 requesting_discoverable_ = true;
429 if (!adapter()->bredr()) {
430 callback(fpromise::error(fsys::Error::FAILED));
431 return;
432 }
433 adapter()->bredr()->RequestDiscoverable([self = weak_self_.GetWeakPtr(),
434 callback = std::move(callback),
435 func = __FUNCTION__](
436 bt::hci::Result<> result,
437 auto session) {
438 if (!self.is_alive()) {
439 callback(fpromise::error(fsys::Error::FAILED));
440 return;
441 }
442
443 if (!self->requesting_discoverable_) {
444 callback(fpromise::error(fsys::Error::CANCELED));
445 return;
446 }
447
448 if (result.is_error() || !session) {
449 bt_log(ERROR, "fidl", "%s: failed (result: %s)", func, bt_str(result));
450 fpromise::result<void, fsys::Error> fidl_result = ResultToFidl(result);
451 if (result.is_ok()) {
452 PW_CHECK(session == nullptr);
453 fidl_result = fpromise::error(fsys::Error::FAILED);
454 }
455 self->requesting_discoverable_ = false;
456 callback(std::move(fidl_result));
457 return;
458 }
459
460 self->bredr_discoverable_session_ = std::move(session);
461 self->requesting_discoverable_ = false;
462 self->NotifyInfoChange();
463 callback(fpromise::ok());
464 });
465 }
466
EnableBackgroundScan(bool enabled)467 void HostServer::EnableBackgroundScan(bool enabled) {
468 bt_log(INFO, "fidl", "%s background scan", (enabled ? "enable" : "disable"));
469 if (!adapter()->le()) {
470 bt_log(ERROR, "fidl", "%s: adapter does not support LE", __FUNCTION__);
471 return;
472 }
473
474 if (!enabled) {
475 requesting_background_scan_ = false;
476 le_background_scan_ = nullptr;
477 return;
478 }
479
480 // If a scan is already starting or is in progress, there is nothing to do to
481 // enable the scan.
482 if (requesting_background_scan_ || le_background_scan_) {
483 return;
484 }
485
486 requesting_background_scan_ = true;
487 adapter()->le()->StartDiscovery(
488 /*active=*/false, [self = weak_self_.GetWeakPtr()](auto session) {
489 if (!self.is_alive()) {
490 return;
491 }
492
493 // Background scan may have been disabled while discovery was starting.
494 if (!self->requesting_background_scan_) {
495 return;
496 }
497
498 if (!session) {
499 bt_log(ERROR, "fidl", "failed to start LE background scan");
500 self->le_background_scan_ = nullptr;
501 self->requesting_background_scan_ = false;
502 return;
503 }
504
505 self->le_background_scan_ = std::move(session);
506 self->requesting_background_scan_ = false;
507 });
508 }
509
EnablePrivacy(bool enabled)510 void HostServer::EnablePrivacy(bool enabled) {
511 bt_log(INFO,
512 "fidl",
513 "%s: %s LE privacy",
514 __FUNCTION__,
515 (enabled ? "enable" : "disable"));
516 if (adapter()->le()) {
517 adapter()->le()->EnablePrivacy(enabled);
518 }
519 }
520
SetBrEdrSecurityMode(fsys::BrEdrSecurityMode mode)521 void HostServer::SetBrEdrSecurityMode(fsys::BrEdrSecurityMode mode) {
522 std::optional<bt::gap::BrEdrSecurityMode> gap_mode =
523 BrEdrSecurityModeFromFidl(mode);
524 if (!gap_mode.has_value()) {
525 bt_log(WARN, "fidl", "%s: Unrecognized BR/EDR security mode", __FUNCTION__);
526 return;
527 }
528
529 bt_log(INFO,
530 "fidl",
531 "%s: %s",
532 __FUNCTION__,
533 BrEdrSecurityModeToString(gap_mode.value()));
534 if (adapter()->bredr()) {
535 adapter()->bredr()->SetBrEdrSecurityMode(gap_mode.value());
536 }
537 }
538
SetLeSecurityMode(fsys::LeSecurityMode mode)539 void HostServer::SetLeSecurityMode(fsys::LeSecurityMode mode) {
540 bt::gap::LESecurityMode gap_mode = LeSecurityModeFromFidl(mode);
541 bt_log(
542 INFO, "fidl", "%s: %s", __FUNCTION__, LeSecurityModeToString(gap_mode));
543 if (adapter()->le()) {
544 adapter()->le()->SetLESecurityMode(gap_mode);
545 }
546 }
547
SetPairingDelegate(fsys::InputCapability input,fsys::OutputCapability output,::fidl::InterfaceHandle<fsys::PairingDelegate> delegate)548 void HostServer::SetPairingDelegate(
549 fsys::InputCapability input,
550 fsys::OutputCapability output,
551 ::fidl::InterfaceHandle<fsys::PairingDelegate> delegate) {
552 bool cleared = !delegate;
553 pairing_delegate_.Bind(std::move(delegate));
554
555 if (cleared) {
556 bt_log(INFO, "fidl", "%s: PairingDelegate cleared", __FUNCTION__);
557 ResetPairingDelegate();
558 return;
559 }
560
561 io_capability_ = fidl_helpers::IoCapabilityFromFidl(input, output);
562 bt_log(INFO,
563 "fidl",
564 "%s: PairingDelegate assigned (I/O capability: %s)",
565 __FUNCTION__,
566 bt::sm::util::IOCapabilityToString(io_capability_).c_str());
567
568 auto pairing = weak_pairing_.GetWeakPtr();
569 auto self = weak_self_.GetWeakPtr();
570 adapter()->SetPairingDelegate(pairing);
571 pairing_delegate_.set_error_handler([self, func = __FUNCTION__](
572 zx_status_t status) {
573 bt_log(
574 INFO, "fidl", "%s error handler: PairingDelegate disconnected", func);
575 if (self.is_alive()) {
576 self->ResetPairingDelegate();
577 }
578 });
579 }
580
581 // Attempt to connect to peer identified by |peer_id|. The peer must be
582 // in our peer cache. We will attempt to connect technologies (LowEnergy,
583 // Classic or Dual-Mode) as the peer claims to support when discovered
Connect(fbt::PeerId peer_id,ConnectCallback callback)584 void HostServer::Connect(fbt::PeerId peer_id, ConnectCallback callback) {
585 bt::PeerId id{peer_id.value};
586 bt_log(INFO, "fidl", "%s: (peer: %s)", __FUNCTION__, bt_str(id));
587
588 auto peer = adapter()->peer_cache()->FindById(id);
589 if (!peer) {
590 // We don't support connecting to peers that are not in our cache
591 bt_log(WARN,
592 "fidl",
593 "%s: peer not found in peer cache (peer: %s)",
594 __FUNCTION__,
595 bt_str(id));
596 callback(fpromise::error(fsys::Error::PEER_NOT_FOUND));
597 return;
598 }
599
600 // TODO(fxbug.dev/42075069): Dual-mode currently not supported; if the peer
601 // supports BR/EDR we prefer BR/EDR. If a dual-mode peer, we should attempt to
602 // connect both protocols.
603 if (peer->bredr()) {
604 ConnectBrEdr(id, std::move(callback));
605 return;
606 }
607
608 ConnectLowEnergy(id, std::move(callback));
609 }
610
611 // Attempt to disconnect the peer identified by |peer_id| from all transports.
612 // If the peer is already not connected, return success. If the peer is
613 // disconnected succesfully, return success.
Disconnect(fbt::PeerId peer_id,DisconnectCallback callback)614 void HostServer::Disconnect(fbt::PeerId peer_id, DisconnectCallback callback) {
615 bt::PeerId id{peer_id.value};
616 bt_log(INFO, "fidl", "%s: (peer: %s)", __FUNCTION__, bt_str(id));
617
618 bool le_disc = adapter()->le() ? adapter()->le()->Disconnect(id) : true;
619 bool bredr_disc = adapter()->bredr()
620 ? adapter()->bredr()->Disconnect(
621 id, bt::gap::DisconnectReason::kApiRequest)
622 : true;
623 if (le_disc && bredr_disc) {
624 callback(fpromise::ok());
625 } else {
626 bt_log(WARN, "fidl", "%s: failed (peer: %s)", __FUNCTION__, bt_str(id));
627 callback(fpromise::error(fsys::Error::FAILED));
628 }
629 }
630
ConnectLowEnergy(PeerId peer_id,ConnectCallback callback)631 void HostServer::ConnectLowEnergy(PeerId peer_id, ConnectCallback callback) {
632 auto self = weak_self_.GetWeakPtr();
633 auto on_complete = [self,
634 callback = std::move(callback),
635 peer_id,
636 func = __FUNCTION__](auto result) {
637 if (result.is_error()) {
638 bt_log(INFO,
639 "fidl",
640 "%s: failed to connect LE transport to peer (peer: %s)",
641 func,
642 bt_str(peer_id));
643 callback(fpromise::error(HostErrorToFidl(result.error_value())));
644 return;
645 }
646
647 // We must be connected and to the right peer
648 auto connection = std::move(result).value();
649 PW_CHECK(connection);
650 PW_CHECK(peer_id == connection->peer_identifier());
651
652 callback(fpromise::ok());
653
654 if (self.is_alive())
655 self->RegisterLowEnergyConnection(std::move(connection),
656 /*auto_connect=*/false);
657 };
658
659 adapter()->le()->Connect(
660 peer_id, std::move(on_complete), bt::gap::LowEnergyConnectionOptions());
661 }
662
663 // Initiate an outgoing Br/Edr connection, unless already connected
664 // Br/Edr connections are host-wide, and stored in BrEdrConnectionManager
ConnectBrEdr(PeerId peer_id,ConnectCallback callback)665 void HostServer::ConnectBrEdr(PeerId peer_id, ConnectCallback callback) {
666 auto on_complete = [callback = std::move(callback),
667 peer_id,
668 func = __FUNCTION__](auto status, auto connection) {
669 if (status.is_error()) {
670 PW_CHECK(!connection);
671 bt_log(INFO,
672 "fidl",
673 "%s: failed to connect BR/EDR transport to peer (peer: %s)",
674 func,
675 bt_str(peer_id));
676 callback(fpromise::error(HostErrorToFidl(status.error_value())));
677 return;
678 }
679
680 // We must be connected and to the right peer
681 PW_CHECK(connection);
682 PW_CHECK(peer_id == connection->peer_id());
683
684 callback(fpromise::ok());
685 };
686
687 if (!adapter()->bredr()->Connect(peer_id, std::move(on_complete))) {
688 bt_log(
689 INFO,
690 "fidl",
691 "%s: failed to initiate BR/EDR transport connection to peer (peer: %s)",
692 __FUNCTION__,
693 bt_str(peer_id));
694 callback(fpromise::error(fsys::Error::FAILED));
695 }
696 }
697
Forget(fbt::PeerId peer_id,ForgetCallback callback)698 void HostServer::Forget(fbt::PeerId peer_id, ForgetCallback callback) {
699 bt::PeerId id{peer_id.value};
700 auto peer = adapter()->peer_cache()->FindById(id);
701 if (!peer) {
702 bt_log(DEBUG, "fidl", "peer %s to forget wasn't found", bt_str(id));
703 callback(fpromise::ok());
704 return;
705 }
706
707 const bool le_disconnected =
708 adapter()->le() ? adapter()->le()->Disconnect(id) : true;
709 const bool bredr_disconnected =
710 adapter()->bredr() ? adapter()->bredr()->Disconnect(
711 id, bt::gap::DisconnectReason::kApiRequest)
712 : true;
713 const bool peer_removed = adapter()->peer_cache()->RemoveDisconnectedPeer(id);
714
715 if (!le_disconnected || !bredr_disconnected) {
716 const auto message =
717 bt_lib_cpp_string::StringPrintf("link(s) failed to close:%s%s",
718 le_disconnected ? "" : " LE",
719 bredr_disconnected ? "" : " BR/EDR");
720 callback(fpromise::error(fsys::Error::FAILED));
721 } else {
722 PW_CHECK(peer_removed);
723 callback(fpromise::ok());
724 }
725 }
726
Pair(fbt::PeerId id,fsys::PairingOptions options,PairCallback callback)727 void HostServer::Pair(fbt::PeerId id,
728 fsys::PairingOptions options,
729 PairCallback callback) {
730 auto peer_id = bt::PeerId(id.value);
731 auto peer = adapter()->peer_cache()->FindById(peer_id);
732 if (!peer) {
733 bt_log(WARN, "fidl", "%s: unknown peer %s", __FUNCTION__, bt_str(peer_id));
734 // We don't support pairing to peers that are not in our cache
735 callback(fpromise::error(fsys::Error::PEER_NOT_FOUND));
736 return;
737 }
738 // If options specifies a transport preference for LE or BR/EDR, we use that.
739 // Otherwise, we use whatever transport exists, defaulting to LE for dual-mode
740 // connections.
741 bool pair_bredr = !peer->le();
742 if (options.has_transport() &&
743 options.transport() != fsys::TechnologyType::DUAL_MODE) {
744 pair_bredr = (options.transport() == fsys::TechnologyType::CLASSIC);
745 }
746 if (pair_bredr) {
747 PairBrEdr(peer_id, std::move(callback));
748 return;
749 }
750 PairLowEnergy(peer_id, std::move(options), std::move(callback));
751 }
752
PairLowEnergy(PeerId peer_id,fsys::PairingOptions options,PairCallback callback)753 void HostServer::PairLowEnergy(PeerId peer_id,
754 fsys::PairingOptions options,
755 PairCallback callback) {
756 std::optional<bt::sm::SecurityLevel> security_level;
757 if (options.has_le_security_level()) {
758 security_level = SecurityLevelFromFidl(options.le_security_level());
759 if (!security_level.has_value()) {
760 bt_log(WARN,
761 "fidl",
762 "%s: pairing options missing LE security level (peer: %s)",
763 __FUNCTION__,
764 bt_str(peer_id));
765 callback(fpromise::error(fsys::Error::INVALID_ARGUMENTS));
766 return;
767 }
768 } else {
769 security_level = bt::sm::SecurityLevel::kAuthenticated;
770 }
771 bt::sm::BondableMode bondable_mode = bt::sm::BondableMode::Bondable;
772 if (options.has_bondable_mode() &&
773 options.bondable_mode() == fsys::BondableMode::NON_BONDABLE) {
774 bondable_mode = bt::sm::BondableMode::NonBondable;
775 }
776 auto on_complete = [peer_id,
777 callback = std::move(callback),
778 func = __FUNCTION__](bt::sm::Result<> status) {
779 if (status.is_error()) {
780 bt_log(
781 WARN, "fidl", "%s: failed to pair (peer: %s)", func, bt_str(peer_id));
782 callback(fpromise::error(HostErrorToFidl(status.error_value())));
783 } else {
784 callback(fpromise::ok());
785 }
786 };
787 PW_CHECK(adapter()->le());
788 adapter()->le()->Pair(
789 peer_id, *security_level, bondable_mode, std::move(on_complete));
790 }
791
PairBrEdr(PeerId peer_id,PairCallback callback)792 void HostServer::PairBrEdr(PeerId peer_id, PairCallback callback) {
793 auto on_complete = [peer_id,
794 callback = std::move(callback),
795 func = __FUNCTION__](bt::hci::Result<> status) {
796 if (status.is_error()) {
797 bt_log(
798 WARN, "fidl", "%s: failed to pair (peer: %s)", func, bt_str(peer_id));
799 callback(fpromise::error(HostErrorToFidl(status.error_value())));
800 } else {
801 callback(fpromise::ok());
802 }
803 };
804 // TODO(fxbug.dev/42135898): Add security parameter to Pair and use that here
805 // instead of hardcoding default.
806 bt::gap::BrEdrSecurityRequirements security{.authentication = false,
807 .secure_connections = false};
808 PW_CHECK(adapter()->bredr());
809 adapter()->bredr()->Pair(peer_id, security, std::move(on_complete));
810 }
811
Shutdown()812 void HostServer::Shutdown() {
813 bt_log(INFO, "fidl", "closing FIDL handles");
814
815 // Invalidate all weak pointers. This will guarantee that all pending tasks
816 // that reference this HostServer will return early if they run in the future.
817 weak_self_.InvalidatePtrs();
818
819 // Destroy all FIDL bindings.
820 servers_.clear();
821
822 // Cancel pending requests.
823 requesting_discoverable_ = false;
824 requesting_background_scan_ = false;
825
826 le_background_scan_ = nullptr;
827 bredr_discoverable_session_ = nullptr;
828
829 StopDiscovery(ZX_ERR_CANCELED, /*notify_info_change=*/false);
830
831 // Drop all connections that are attached to this HostServer.
832 le_connections_.clear();
833
834 if (adapter()->le()) {
835 // Stop background scan if enabled.
836 adapter()->le()->EnablePrivacy(false);
837 adapter()->le()->set_irk(std::nullopt);
838 }
839
840 // Disallow future pairing.
841 pairing_delegate_ = nullptr;
842 ResetPairingDelegate();
843
844 // Send adapter state change.
845 if (binding()->is_bound()) {
846 NotifyInfoChange();
847 }
848 }
849
SetBondingDelegate(::fidl::InterfaceRequest<::fuchsia::bluetooth::host::BondingDelegate> request)850 void HostServer::SetBondingDelegate(
851 ::fidl::InterfaceRequest<::fuchsia::bluetooth::host::BondingDelegate>
852 request) {
853 if (bonding_delegate_server_.has_value()) {
854 request.Close(ZX_ERR_ALREADY_BOUND);
855 return;
856 }
857 bonding_delegate_server_.emplace(std::move(request), this);
858 }
859
handle_unknown_method(uint64_t ordinal,bool method_has_response)860 void HostServer::handle_unknown_method(uint64_t ordinal,
861 bool method_has_response) {
862 bt_log(WARN, "fidl", "Received unknown method with ordinal: %lu", ordinal);
863 }
864
Stop()865 void HostServer::DiscoverySessionServer::Stop() {
866 host_->OnDiscoverySessionServerClose(this);
867 }
868
handle_unknown_method(uint64_t ordinal,bool method_has_response)869 void HostServer::DiscoverySessionServer::handle_unknown_method(
870 uint64_t ordinal, bool method_has_response) {
871 bt_log(WARN, "fidl", "Received unknown method with ordinal: %lu", ordinal);
872 }
873
DiscoverySessionServer(fidl::InterfaceRequest<::fuchsia::bluetooth::host::DiscoverySession> request,HostServer * host)874 HostServer::DiscoverySessionServer::DiscoverySessionServer(
875 fidl::InterfaceRequest<::fuchsia::bluetooth::host::DiscoverySession>
876 request,
877 HostServer* host)
878 : ServerBase(this, std::move(request)), host_(host) {
879 binding()->set_error_handler([this, host](zx_status_t /*status*/) {
880 host->OnDiscoverySessionServerClose(this);
881 });
882 }
883
PeerWatcherServer(::fidl::InterfaceRequest<::fuchsia::bluetooth::host::PeerWatcher> request,bt::gap::PeerCache * peer_cache,HostServer * host)884 HostServer::PeerWatcherServer::PeerWatcherServer(
885 ::fidl::InterfaceRequest<::fuchsia::bluetooth::host::PeerWatcher> request,
886 bt::gap::PeerCache* peer_cache,
887 HostServer* host)
888 : ServerBase(this, std::move(request)),
889 peer_cache_(peer_cache),
890 host_(host),
891 weak_self_(this) {
892 auto self = weak_self_.GetWeakPtr();
893
894 peer_updated_callback_id_ =
895 peer_cache_->add_peer_updated_callback([self](const auto& peer) {
896 if (self.is_alive()) {
897 self->OnPeerUpdated(peer);
898 }
899 });
900 peer_cache_->set_peer_removed_callback([self](const auto& identifier) {
901 if (self.is_alive()) {
902 self->OnPeerRemoved(identifier);
903 }
904 });
905
906 // Initialize the peer watcher with all known connectable peers that are in
907 // the cache.
908 peer_cache_->ForEach(
909 [this](const bt::gap::Peer& peer) { OnPeerUpdated(peer); });
910
911 binding()->set_error_handler(
912 [this](zx_status_t /*status*/) { host_->peer_watcher_server_.reset(); });
913 }
914
~PeerWatcherServer()915 HostServer::PeerWatcherServer::~PeerWatcherServer() {
916 // Unregister PeerCache callbacks.
917 peer_cache_->remove_peer_updated_callback(peer_updated_callback_id_);
918 peer_cache_->set_peer_removed_callback(nullptr);
919 }
920
OnPeerUpdated(const bt::gap::Peer & peer)921 void HostServer::PeerWatcherServer::OnPeerUpdated(const bt::gap::Peer& peer) {
922 if (!peer.connectable()) {
923 return;
924 }
925
926 updated_.insert(peer.identifier());
927 removed_.erase(peer.identifier());
928 MaybeCallCallback();
929 }
930
OnPeerRemoved(bt::PeerId id)931 void HostServer::PeerWatcherServer::OnPeerRemoved(bt::PeerId id) {
932 updated_.erase(id);
933 removed_.insert(id);
934 MaybeCallCallback();
935 }
936
MaybeCallCallback()937 void HostServer::PeerWatcherServer::MaybeCallCallback() {
938 if (!callback_) {
939 return;
940 }
941
942 if (!removed_.empty()) {
943 Removed removed_fidl;
944 for (const bt::PeerId& id : removed_) {
945 removed_fidl.push_back(fbt::PeerId{id.value()});
946 }
947 removed_.clear();
948 callback_(fhost::PeerWatcher_GetNext_Result::WithResponse(
949 fhost::PeerWatcher_GetNext_Response::WithRemoved(
950 std::move(removed_fidl))));
951 callback_ = nullptr;
952 return;
953 }
954
955 if (!updated_.empty()) {
956 Updated updated_fidl;
957 for (const bt::PeerId& id : updated_) {
958 bt::gap::Peer* peer = peer_cache_->FindById(id);
959 // All ids in |updated_| are assumed to be valid as they would otherwise
960 // be in |removed_|.
961 PW_CHECK(peer);
962 updated_fidl.push_back(fidl_helpers::PeerToFidl(*peer));
963 }
964 updated_.clear();
965 callback_(fhost::PeerWatcher_GetNext_Result::WithResponse(
966 fhost::PeerWatcher_GetNext_Response::WithUpdated(
967 std::move(updated_fidl))));
968 callback_ = nullptr;
969 return;
970 }
971 }
972
GetNext(::fuchsia::bluetooth::host::PeerWatcher::GetNextCallback callback)973 void HostServer::PeerWatcherServer::GetNext(
974 ::fuchsia::bluetooth::host::PeerWatcher::GetNextCallback callback) {
975 if (callback_) {
976 binding()->Close(ZX_ERR_BAD_STATE);
977 host_->peer_watcher_server_.reset();
978 return;
979 }
980 callback_ = std::move(callback);
981 MaybeCallCallback();
982 }
983
handle_unknown_method(uint64_t ordinal,bool method_has_response)984 void HostServer::PeerWatcherServer::handle_unknown_method(
985 uint64_t ordinal, bool method_has_response) {
986 bt_log(WARN,
987 "fidl",
988 "PeerWatcher received unknown method with ordinal %lu",
989 ordinal);
990 }
991
BondingDelegateServer(::fidl::InterfaceRequest<::fuchsia::bluetooth::host::BondingDelegate> request,HostServer * host)992 HostServer::BondingDelegateServer::BondingDelegateServer(
993 ::fidl::InterfaceRequest<::fuchsia::bluetooth::host::BondingDelegate>
994 request,
995 HostServer* host)
996 : ServerBase(this, std::move(request)), host_(host) {
997 binding()->set_error_handler(
998 [this](zx_status_t status) { host_->bonding_delegate_server_.reset(); });
999 // Initialize the peer watcher with all known bonded peers that are in the
1000 // cache.
1001 host_->adapter()->peer_cache()->ForEach([this](const bt::gap::Peer& peer) {
1002 if (peer.bonded()) {
1003 OnNewBondingData(peer);
1004 }
1005 });
1006 }
1007
OnNewBondingData(const bt::gap::Peer & peer)1008 void HostServer::BondingDelegateServer::OnNewBondingData(
1009 const bt::gap::Peer& peer) {
1010 updated_.push(
1011 fidl_helpers::PeerToFidlBondingData(host_->adapter().get(), peer));
1012 MaybeNotifyWatchBonds();
1013 }
1014
RestoreBonds(::std::vector<::fuchsia::bluetooth::sys::BondingData> bonds,RestoreBondsCallback callback)1015 void HostServer::BondingDelegateServer::RestoreBonds(
1016 ::std::vector<::fuchsia::bluetooth::sys::BondingData> bonds,
1017 RestoreBondsCallback callback) {
1018 host_->RestoreBonds(std::move(bonds), std::move(callback));
1019 }
WatchBonds(WatchBondsCallback callback)1020 void HostServer::BondingDelegateServer::WatchBonds(
1021 WatchBondsCallback callback) {
1022 if (watch_bonds_cb_) {
1023 binding()->Close(ZX_ERR_ALREADY_EXISTS);
1024 host_->bonding_delegate_server_.reset();
1025 return;
1026 }
1027 watch_bonds_cb_ = std::move(callback);
1028 MaybeNotifyWatchBonds();
1029 }
1030
handle_unknown_method(uint64_t ordinal,bool method_has_response)1031 void HostServer::BondingDelegateServer::handle_unknown_method(
1032 uint64_t ordinal, bool method_has_response) {
1033 bt_log(WARN,
1034 "fidl",
1035 "BondingDelegate received unknown method with ordinal %lu",
1036 ordinal);
1037 }
1038
1039 // TODO(fxbug.dev/42158854): Support notifying removed bonds.
MaybeNotifyWatchBonds()1040 void HostServer::BondingDelegateServer::MaybeNotifyWatchBonds() {
1041 if (!watch_bonds_cb_ || updated_.empty()) {
1042 return;
1043 }
1044
1045 watch_bonds_cb_(fhost::BondingDelegate_WatchBonds_Result::WithResponse(
1046 ::fuchsia::bluetooth::host::BondingDelegate_WatchBonds_Response::
1047 WithUpdated(std::move(updated_.front()))));
1048 updated_.pop();
1049 }
1050
io_capability() const1051 bt::sm::IOCapability HostServer::io_capability() const {
1052 bt_log(DEBUG,
1053 "fidl",
1054 "I/O capability: %s",
1055 bt::sm::util::IOCapabilityToString(io_capability_).c_str());
1056 return io_capability_;
1057 }
1058
CompletePairing(PeerId id,bt::sm::Result<> status)1059 void HostServer::CompletePairing(PeerId id, bt::sm::Result<> status) {
1060 bt_log(DEBUG,
1061 "fidl",
1062 "pairing complete for peer: %s, status: %s",
1063 bt_str(id),
1064 bt_str(status));
1065 PW_DCHECK(pairing_delegate_);
1066 pairing_delegate_->OnPairingComplete(fbt::PeerId{id.value()}, status.is_ok());
1067 }
1068
ConfirmPairing(PeerId id,ConfirmCallback confirm)1069 void HostServer::ConfirmPairing(PeerId id, ConfirmCallback confirm) {
1070 bt_log(
1071 DEBUG, "fidl", "pairing confirmation request for peer: %s", bt_str(id));
1072 DisplayPairingRequest(
1073 id, std::nullopt, fsys::PairingMethod::CONSENT, std::move(confirm));
1074 }
1075
DisplayPasskey(PeerId id,uint32_t passkey,DisplayMethod method,ConfirmCallback confirm)1076 void HostServer::DisplayPasskey(PeerId id,
1077 uint32_t passkey,
1078 DisplayMethod method,
1079 ConfirmCallback confirm) {
1080 auto fidl_method = fsys::PairingMethod::PASSKEY_DISPLAY;
1081 if (method == DisplayMethod::kComparison) {
1082 bt_log(
1083 DEBUG, "fidl", "compare passkey %06u on peer: %s", passkey, bt_str(id));
1084 fidl_method = fsys::PairingMethod::PASSKEY_COMPARISON;
1085 } else {
1086 bt_log(
1087 DEBUG, "fidl", "enter passkey %06u on peer: %s", passkey, bt_str(id));
1088 }
1089 DisplayPairingRequest(id, passkey, fidl_method, std::move(confirm));
1090 }
1091
RequestPasskey(PeerId id,PasskeyResponseCallback respond)1092 void HostServer::RequestPasskey(PeerId id, PasskeyResponseCallback respond) {
1093 bt_log(DEBUG, "fidl", "passkey request for peer: %s", bt_str(id));
1094 auto found_peer = adapter()->peer_cache()->FindById(id);
1095 PW_CHECK(found_peer);
1096 auto peer = fidl_helpers::PeerToFidl(*found_peer);
1097
1098 PW_CHECK(pairing_delegate_);
1099 pairing_delegate_->OnPairingRequest(
1100 std::move(peer),
1101 fsys::PairingMethod::PASSKEY_ENTRY,
1102 0u,
1103 [respond = std::move(respond), id, func = __FUNCTION__](
1104 const bool accept, uint32_t entered_passkey) mutable {
1105 if (!respond) {
1106 bt_log(WARN,
1107 "fidl",
1108 "%s: The PairingDelegate invoked the Pairing Request callback "
1109 "more than once, which "
1110 "should not happen (peer: %s)",
1111 func,
1112 bt_str(id));
1113 return;
1114 }
1115 bt_log(INFO,
1116 "fidl",
1117 "%s: got PairingDelegate response: %s with passkey code \"%u\" "
1118 "(peer: %s)",
1119 func,
1120 accept ? "accept" : "reject",
1121 entered_passkey,
1122 bt_str(id));
1123 if (!accept) {
1124 respond(-1);
1125 } else {
1126 respond(entered_passkey);
1127 }
1128 });
1129 }
1130
DisplayPairingRequest(bt::PeerId id,std::optional<uint32_t> passkey,fsys::PairingMethod method,ConfirmCallback confirm)1131 void HostServer::DisplayPairingRequest(bt::PeerId id,
1132 std::optional<uint32_t> passkey,
1133 fsys::PairingMethod method,
1134 ConfirmCallback confirm) {
1135 auto found_peer = adapter()->peer_cache()->FindById(id);
1136 PW_CHECK(found_peer);
1137 auto peer = fidl_helpers::PeerToFidl(*found_peer);
1138
1139 PW_CHECK(pairing_delegate_);
1140 uint32_t displayed_passkey = passkey ? *passkey : 0u;
1141 pairing_delegate_->OnPairingRequest(
1142 std::move(peer),
1143 method,
1144 displayed_passkey,
1145 [confirm = std::move(confirm), id, func = __FUNCTION__](
1146 const bool accept, uint32_t entered_passkey) mutable {
1147 if (!confirm) {
1148 bt_log(WARN,
1149 "fidl",
1150 "%s: The PairingDelegate invoked the Pairing Request callback "
1151 "more than once, which "
1152 "should not happen (peer: %s)",
1153 func,
1154 bt_str(id));
1155 return;
1156 }
1157 bt_log(INFO,
1158 "fidl",
1159 "%s: got PairingDelegate response: %s, \"%u\" (peer: %s)",
1160 func,
1161 accept ? "accept" : "reject",
1162 entered_passkey,
1163 bt_str(id));
1164 confirm(accept);
1165 });
1166 }
1167
OnConnectionError(Server * server)1168 void HostServer::OnConnectionError(Server* server) {
1169 PW_DCHECK(server);
1170 servers_.erase(server);
1171 }
1172
ResetPairingDelegate()1173 void HostServer::ResetPairingDelegate() {
1174 io_capability_ = IOCapability::kNoInputNoOutput;
1175 adapter()->SetPairingDelegate(PairingDelegate::WeakPtr());
1176 }
1177
NotifyInfoChange()1178 void HostServer::NotifyInfoChange() {
1179 info_getter_.Set(fidl_helpers::HostInfoToFidl(adapter().get()));
1180 }
1181
1182 } // namespace bthost
1183