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/low_energy_central_server.h"
16
17 #include <pw_assert/check.h>
18 #include <zircon/types.h>
19
20 #include <utility>
21
22 #include "fuchsia/bluetooth/le/cpp/fidl.h"
23 #include "pw_bluetooth_sapphire/fuchsia/host/fidl/gatt_client_server.h"
24 #include "pw_bluetooth_sapphire/fuchsia/host/fidl/helpers.h"
25 #include "pw_bluetooth_sapphire/fuchsia/host/fidl/measure_tape/hlcpp_measure_tape_for_peer.h"
26 #include "pw_bluetooth_sapphire/internal/host/common/error.h"
27 #include "pw_bluetooth_sapphire/internal/host/common/log.h"
28 #include "pw_bluetooth_sapphire/internal/host/hci-spec/constants.h"
29 #include "pw_bluetooth_sapphire/internal/host/sm/types.h"
30
31 using fuchsia::bluetooth::ErrorCode;
32 using fuchsia::bluetooth::Int8;
33 using fuchsia::bluetooth::Status;
34
35 using bt::sm::BondableMode;
36 using fuchsia::bluetooth::gatt::Client;
37 using fuchsia::bluetooth::le::ScanFilterPtr;
38 namespace fble = fuchsia::bluetooth::le;
39 namespace measure_fble = measure_tape::fuchsia::bluetooth::le;
40
41 namespace bthost {
42
43 namespace {
44
ConnectionOptionsFromFidl(const fble::ConnectionOptions & options)45 bt::gap::LowEnergyConnectionOptions ConnectionOptionsFromFidl(
46 const fble::ConnectionOptions& options) {
47 BondableMode bondable_mode =
48 (!options.has_bondable_mode() || options.bondable_mode())
49 ? BondableMode::Bondable
50 : BondableMode::NonBondable;
51
52 std::optional<bt::UUID> service_uuid =
53 options.has_service_filter()
54 ? std::optional(fidl_helpers::UuidFromFidl(options.service_filter()))
55 : std::nullopt;
56
57 return bt::gap::LowEnergyConnectionOptions{.bondable_mode = bondable_mode,
58 .service_uuid = service_uuid};
59 }
60
61 } // namespace
62
LowEnergyCentralServer(bt::gap::Adapter::WeakPtr adapter,fidl::InterfaceRequest<Central> request,bt::gatt::GATT::WeakPtr gatt)63 LowEnergyCentralServer::LowEnergyCentralServer(
64 bt::gap::Adapter::WeakPtr adapter,
65 fidl::InterfaceRequest<Central> request,
66 bt::gatt::GATT::WeakPtr gatt)
67 : AdapterServerBase(std::move(adapter), this, std::move(request)),
68 gatt_(std::move(gatt)),
69 requesting_scan_deprecated_(false),
70 weak_self_(this) {
71 PW_CHECK(gatt_.is_alive());
72 }
73
~LowEnergyCentralServer()74 LowEnergyCentralServer::~LowEnergyCentralServer() {
75 if (scan_instance_) {
76 scan_instance_->Close(ZX_OK);
77 scan_instance_.reset();
78 }
79 }
80
81 std::optional<bt::gap::LowEnergyConnectionHandle*>
FindConnectionForTesting(bt::PeerId identifier)82 LowEnergyCentralServer::FindConnectionForTesting(bt::PeerId identifier) {
83 auto conn_iter = connections_deprecated_.find(identifier);
84 if (conn_iter != connections_deprecated_.end()) {
85 return conn_iter->second.get();
86 }
87 return std::nullopt;
88 }
89
ScanResultWatcherServer(bt::gap::Adapter::WeakPtr adapter,fidl::InterfaceRequest<fuchsia::bluetooth::le::ScanResultWatcher> watcher,fit::callback<void ()> error_cb)90 LowEnergyCentralServer::ScanResultWatcherServer::ScanResultWatcherServer(
91 bt::gap::Adapter::WeakPtr adapter,
92 fidl::InterfaceRequest<fuchsia::bluetooth::le::ScanResultWatcher> watcher,
93 fit::callback<void()> error_cb)
94 : ServerBase(this, std::move(watcher)),
95 adapter_(std::move(adapter)),
96 error_callback_(std::move(error_cb)) {
97 set_error_handler([this](auto) {
98 bt_log(DEBUG, "fidl", "ScanResultWatcher client closed, stopping scan");
99 PW_CHECK(error_callback_);
100 error_callback_();
101 });
102 }
103
Close(zx_status_t epitaph)104 void LowEnergyCentralServer::ScanResultWatcherServer::Close(
105 zx_status_t epitaph) {
106 binding()->Close(epitaph);
107 }
108
AddPeers(std::unordered_set<bt::PeerId> peers)109 void LowEnergyCentralServer::ScanResultWatcherServer::AddPeers(
110 std::unordered_set<bt::PeerId> peers) {
111 while (!peers.empty() &&
112 updated_peers_.size() < kMaxPendingScanResultWatcherPeers) {
113 updated_peers_.insert(peers.extract(peers.begin()));
114 }
115
116 if (!peers.empty()) {
117 bt_log(
118 WARN,
119 "fidl",
120 "Maximum pending peers (%zu) reached, dropping %zu peers from results",
121 kMaxPendingScanResultWatcherPeers,
122 peers.size());
123 }
124
125 MaybeSendPeers();
126 }
127
Watch(WatchCallback callback)128 void LowEnergyCentralServer::ScanResultWatcherServer::Watch(
129 WatchCallback callback) {
130 bt_log(TRACE, "fidl", "%s", __FUNCTION__);
131 if (watch_callback_) {
132 bt_log(WARN,
133 "fidl",
134 "%s: called before previous call completed",
135 __FUNCTION__);
136 Close(ZX_ERR_CANCELED);
137 PW_CHECK(error_callback_);
138 error_callback_();
139 return;
140 }
141 watch_callback_ = std::move(callback);
142 MaybeSendPeers();
143 }
144
MaybeSendPeers()145 void LowEnergyCentralServer::ScanResultWatcherServer::MaybeSendPeers() {
146 if (updated_peers_.empty() || !watch_callback_) {
147 return;
148 }
149
150 // Send as many peers as will fit in the channel.
151 const size_t kVectorOverhead =
152 sizeof(fidl_message_header_t) + sizeof(fidl_vector_t);
153 const size_t kMaxBytes = ZX_CHANNEL_MAX_MSG_BYTES - kVectorOverhead;
154 size_t bytes_used = 0;
155 std::vector<fble::Peer> peers;
156 while (!updated_peers_.empty()) {
157 bt::PeerId peer_id = *updated_peers_.begin();
158 bt::gap::Peer* peer = adapter_->peer_cache()->FindById(peer_id);
159 if (!peer) {
160 // The peer has been removed from the peer cache since it was queued, so
161 // the stale peer ID should not be sent to the client.
162 updated_peers_.erase(peer_id);
163 continue;
164 }
165
166 fble::Peer fidl_peer = fidl_helpers::PeerToFidlLe(*peer);
167 measure_fble::Size peer_size = measure_fble::Measure(fidl_peer);
168 PW_CHECK(peer_size.num_handles == 0,
169 "Expected fuchsia.bluetooth.le/Peer to not have handles, but "
170 "%zu handles found",
171 peer_size.num_handles);
172 bytes_used += peer_size.num_bytes;
173 if (bytes_used > kMaxBytes) {
174 // Don't remove the peer that exceeded the size limit. It will be sent in
175 // the next batch.
176 break;
177 }
178
179 updated_peers_.erase(peer_id);
180 peers.emplace_back(std::move(fidl_peer));
181 }
182
183 // It is possible that all queued peers were stale, so there is nothing to
184 // send.
185 if (peers.empty()) {
186 return;
187 }
188
189 watch_callback_(std::move(peers));
190 }
191
ScanInstance(bt::gap::Adapter::WeakPtr adapter,LowEnergyCentralServer * central_server,std::vector<fuchsia::bluetooth::le::Filter> fidl_filters,fidl::InterfaceRequest<fuchsia::bluetooth::le::ScanResultWatcher> watcher,ScanCallback cb)192 LowEnergyCentralServer::ScanInstance::ScanInstance(
193 bt::gap::Adapter::WeakPtr adapter,
194 LowEnergyCentralServer* central_server,
195 std::vector<fuchsia::bluetooth::le::Filter> fidl_filters,
196 fidl::InterfaceRequest<fuchsia::bluetooth::le::ScanResultWatcher> watcher,
197 ScanCallback cb)
198 : result_watcher_(adapter,
199 std::move(watcher),
200 /*error_cb=*/
201 [this] {
202 Close(ZX_OK);
203 central_server_->ClearScan();
204 }),
205 scan_complete_callback_(std::move(cb)),
206 central_server_(central_server),
207 adapter_(std::move(adapter)),
208 weak_self_(this) {
209 std::transform(fidl_filters.begin(),
210 fidl_filters.end(),
211 std::back_inserter(filters_),
212 fidl_helpers::DiscoveryFilterFromFidl);
213
214 // Send all current peers in peer cache that match filters.
215 std::unordered_set<bt::PeerId> initial_peers;
__anon1c49fc7b0402(const bt::gap::Peer& peer) 216 adapter_->peer_cache()->ForEach([&](const bt::gap::Peer& peer) {
217 initial_peers.emplace(peer.identifier());
218 });
219 FilterAndAddPeers(std::move(initial_peers));
220
221 // Subscribe to updated peers.
222 peer_updated_callback_id_ = adapter_->peer_cache()->add_peer_updated_callback(
__anon1c49fc7b0502(const bt::gap::Peer& peer) 223 [this](const bt::gap::Peer& peer) {
224 FilterAndAddPeers({peer.identifier()});
225 });
226
227 auto self = weak_self_.GetWeakPtr();
228 adapter_->le()->StartDiscovery(
__anon1c49fc7b0602(auto session) 229 /*active=*/true, filters_, [self](auto session) {
230 if (!self.is_alive()) {
231 bt_log(
232 TRACE, "fidl", "ignoring LE discovery session for canceled Scan");
233 return;
234 }
235
236 if (!session) {
237 bt_log(WARN, "fidl", "failed to start LE discovery session");
238 self->Close(ZX_ERR_INTERNAL);
239 self->central_server_->ClearScan();
240 return;
241 }
242
243 session->set_error_callback([self] {
244 if (!self.is_alive()) {
245 bt_log(TRACE,
246 "fidl",
247 "ignoring LE discovery session error for canceled Scan");
248 return;
249 }
250
251 bt_log(DEBUG,
252 "fidl",
253 "canceling Scan due to LE discovery session error");
254 self->Close(ZX_ERR_INTERNAL);
255 self->central_server_->ClearScan();
256 });
257
258 self->scan_session_ = std::move(session);
259 });
260 }
261
~ScanInstance()262 LowEnergyCentralServer::ScanInstance::~ScanInstance() {
263 // If this scan instance has not already been closed with a more specific
264 // status, close with an error status.
265 Close(ZX_ERR_INTERNAL);
266 adapter_->peer_cache()->remove_peer_updated_callback(
267 peer_updated_callback_id_);
268 }
269
Close(zx_status_t status)270 void LowEnergyCentralServer::ScanInstance::Close(zx_status_t status) {
271 if (scan_complete_callback_) {
272 result_watcher_.Close(status);
273 scan_complete_callback_();
274 }
275 }
276
FilterAndAddPeers(std::unordered_set<bt::PeerId> peers)277 void LowEnergyCentralServer::ScanInstance::FilterAndAddPeers(
278 std::unordered_set<bt::PeerId> peers) {
279 // Remove peers that don't match any filters.
280 for (auto peers_iter = peers.begin(); peers_iter != peers.end();) {
281 bt::gap::Peer* peer = adapter_->peer_cache()->FindById(*peers_iter);
282 if (!peer || !peer->le()) {
283 peers_iter = peers.erase(peers_iter);
284 continue;
285 }
286 bool matches_any = false;
287 for (const bt::hci::DiscoveryFilter& filter : filters_) {
288 // TODO(fxbug.dev/42111894): Match peer names that are not in advertising
289 // data. This might require implementing a new peer filtering class, as
290 // DiscoveryFilter only filters advertising data.
291 if (filter.MatchLowEnergyResult(peer->le()->parsed_advertising_data(),
292 peer->connectable(),
293 peer->rssi())) {
294 matches_any = true;
295 break;
296 }
297 }
298 if (!matches_any) {
299 peers_iter = peers.erase(peers_iter);
300 continue;
301 }
302 peers_iter++;
303 }
304
305 result_watcher_.AddPeers(std::move(peers));
306 }
307
Scan(fuchsia::bluetooth::le::ScanOptions options,fidl::InterfaceRequest<fuchsia::bluetooth::le::ScanResultWatcher> result_watcher,ScanCallback callback)308 void LowEnergyCentralServer::Scan(
309 fuchsia::bluetooth::le::ScanOptions options,
310 fidl::InterfaceRequest<fuchsia::bluetooth::le::ScanResultWatcher>
311 result_watcher,
312 ScanCallback callback) {
313 bt_log(DEBUG, "fidl", "%s", __FUNCTION__);
314
315 if (scan_instance_ || requesting_scan_deprecated_ ||
316 scan_session_deprecated_) {
317 bt_log(INFO, "fidl", "%s: scan already in progress", __FUNCTION__);
318 result_watcher.Close(ZX_ERR_ALREADY_EXISTS);
319 callback();
320 return;
321 }
322
323 if (!options.has_filters() || options.filters().empty()) {
324 bt_log(INFO, "fidl", "%s: no scan filters specified", __FUNCTION__);
325 result_watcher.Close(ZX_ERR_INVALID_ARGS);
326 callback();
327 return;
328 }
329
330 scan_instance_ =
331 std::make_unique<ScanInstance>(adapter()->AsWeakPtr(),
332 this,
333 std::move(*options.mutable_filters()),
334 std::move(result_watcher),
335 std::move(callback));
336 }
337
Connect(fuchsia::bluetooth::PeerId id,fble::ConnectionOptions options,fidl::InterfaceRequest<fble::Connection> request)338 void LowEnergyCentralServer::Connect(
339 fuchsia::bluetooth::PeerId id,
340 fble::ConnectionOptions options,
341 fidl::InterfaceRequest<fble::Connection> request) {
342 bt::PeerId peer_id(id.value);
343 bt_log(INFO, "fidl", "%s: (peer: %s)", __FUNCTION__, bt_str(peer_id));
344
345 auto conn_iter = connections_.find(peer_id);
346 if (conn_iter != connections_.end()) {
347 bt_log(
348 INFO,
349 "fidl",
350 "%s: connection %s (peer: %s)",
351 __FUNCTION__,
352 (conn_iter->second == nullptr ? "request pending" : "already exists"),
353 bt_str(peer_id));
354 request.Close(ZX_ERR_ALREADY_BOUND);
355 return;
356 }
357
358 auto self = weak_self_.GetWeakPtr();
359 auto conn_cb =
360 [self, peer_id, request = std::move(request)](
361 bt::gap::Adapter::LowEnergy::ConnectionResult result) mutable {
362 if (!self.is_alive())
363 return;
364
365 auto conn_iter = self->connections_.find(peer_id);
366 PW_CHECK(conn_iter != self->connections_.end());
367 PW_CHECK(conn_iter->second == nullptr);
368
369 if (result.is_error()) {
370 bt_log(INFO,
371 "fidl",
372 "Connect: failed to connect to peer (peer: %s)",
373 bt_str(peer_id));
374 self->connections_.erase(peer_id);
375 request.Close(ZX_ERR_NOT_CONNECTED);
376 return;
377 }
378
379 auto conn_ref = std::move(result).value();
380 PW_CHECK(conn_ref);
381 PW_CHECK(peer_id == conn_ref->peer_identifier());
382
383 auto closed_cb = [self, peer_id] {
384 if (self.is_alive()) {
385 self->connections_.erase(peer_id);
386 }
387 };
388 auto server =
389 std::make_unique<LowEnergyConnectionServer>(self->adapter(),
390 self->gatt_,
391 std::move(conn_ref),
392 request.TakeChannel(),
393 std::move(closed_cb));
394
395 PW_CHECK(!conn_iter->second);
396 conn_iter->second = std::move(server);
397 };
398
399 // An entry for the connection must be created here so that a synchronous call
400 // to conn_cb below does not cause conn_cb to treat the connection as
401 // cancelled.
402 connections_[peer_id] = nullptr;
403
404 adapter()->le()->Connect(
405 peer_id, std::move(conn_cb), ConnectionOptionsFromFidl(options));
406 }
407
GetPeripherals(::fidl::VectorPtr<::std::string> service_uuids,GetPeripheralsCallback callback)408 void LowEnergyCentralServer::GetPeripherals(
409 ::fidl::VectorPtr<::std::string> service_uuids,
410 GetPeripheralsCallback callback) {
411 bt_log(ERROR, "fidl", "GetPeripherals() not implemented");
412 }
413
GetPeripheral(::std::string identifier,GetPeripheralCallback callback)414 void LowEnergyCentralServer::GetPeripheral(::std::string identifier,
415 GetPeripheralCallback callback) {
416 bt_log(ERROR, "fidl", "GetPeripheral() not implemented");
417 }
418
StartScan(ScanFilterPtr filter,StartScanCallback callback)419 void LowEnergyCentralServer::StartScan(ScanFilterPtr filter,
420 StartScanCallback callback) {
421 bt_log(DEBUG, "fidl", "%s", __FUNCTION__);
422
423 if (requesting_scan_deprecated_) {
424 bt_log(DEBUG, "fidl", "%s: scan request already in progress", __FUNCTION__);
425 callback(fidl_helpers::NewFidlError(ErrorCode::IN_PROGRESS,
426 "Scan request in progress"));
427 return;
428 }
429
430 if (filter && !fidl_helpers::IsScanFilterValid(*filter)) {
431 bt_log(WARN, "fidl", "%s: invalid scan filter given", __FUNCTION__);
432 callback(fidl_helpers::NewFidlError(ErrorCode::INVALID_ARGUMENTS,
433 "ScanFilter contains an invalid UUID"));
434 return;
435 }
436
437 std::vector<bt::hci::DiscoveryFilter> discovery_filters;
438 if (filter) {
439 bt::hci::DiscoveryFilter discovery_filter;
440 fidl_helpers::PopulateDiscoveryFilter(*filter, &discovery_filter);
441 discovery_filters.emplace_back(discovery_filter);
442 }
443
444 requesting_scan_deprecated_ = true;
445 adapter()->le()->StartDiscovery(
446 /*active=*/true,
447 discovery_filters,
448 [self = weak_self_.GetWeakPtr(),
449 filter = std::move(filter),
450 callback = std::move(callback),
451 func = __FUNCTION__](auto session) {
452 if (!self.is_alive())
453 return;
454
455 self->requesting_scan_deprecated_ = false;
456
457 if (!session) {
458 bt_log(
459 WARN, "fidl", "%s: failed to start LE discovery session", func);
460 callback(fidl_helpers::NewFidlError(
461 ErrorCode::FAILED, "Failed to start discovery session"));
462 return;
463 }
464
465 session->SetResultCallback([self](const auto& peer) {
466 if (self.is_alive())
467 self->OnScanResult(peer);
468 });
469
470 session->set_error_callback([self] {
471 if (self.is_alive()) {
472 // Clean up the session and notify the delegate.
473 self->StopScan();
474 }
475 });
476
477 self->scan_session_deprecated_ = std::move(session);
478 self->NotifyScanStateChanged(true);
479 callback(Status());
480 });
481 }
482
StopScan()483 void LowEnergyCentralServer::StopScan() {
484 bt_log(DEBUG, "fidl", "StopScan()");
485
486 if (!scan_session_deprecated_) {
487 bt_log(DEBUG,
488 "fidl",
489 "%s: no active discovery session; nothing to do",
490 __FUNCTION__);
491 return;
492 }
493
494 scan_session_deprecated_ = nullptr;
495 NotifyScanStateChanged(false);
496 }
497
ConnectPeripheral(::std::string identifier,fuchsia::bluetooth::le::ConnectionOptions connection_options,::fidl::InterfaceRequest<Client> client_request,ConnectPeripheralCallback callback)498 void LowEnergyCentralServer::ConnectPeripheral(
499 ::std::string identifier,
500 fuchsia::bluetooth::le::ConnectionOptions connection_options,
501 ::fidl::InterfaceRequest<Client> client_request,
502 ConnectPeripheralCallback callback) {
503 bt_log(INFO, "fidl", "%s: (peer: %s)", __FUNCTION__, identifier.c_str());
504
505 auto peer_id = fidl_helpers::PeerIdFromString(identifier);
506 if (!peer_id.has_value()) {
507 bt_log(WARN,
508 "fidl",
509 "%s: invalid peer id : %s",
510 __FUNCTION__,
511 identifier.c_str());
512 callback(fidl_helpers::NewFidlError(ErrorCode::INVALID_ARGUMENTS,
513 "invalid peer ID"));
514 return;
515 }
516
517 auto iter = connections_deprecated_.find(*peer_id);
518 if (iter != connections_deprecated_.end()) {
519 if (iter->second) {
520 bt_log(INFO,
521 "fidl",
522 "%s: already connected to %s",
523 __FUNCTION__,
524 bt_str(*peer_id));
525 callback(fidl_helpers::NewFidlError(
526 ErrorCode::ALREADY, "Already connected to requested peer"));
527 } else {
528 bt_log(INFO,
529 "fidl",
530 "%s: connect request pending (peer: %s)",
531 __FUNCTION__,
532 bt_str(*peer_id));
533 callback(fidl_helpers::NewFidlError(ErrorCode::IN_PROGRESS,
534 "Connect request pending"));
535 }
536 return;
537 }
538
539 auto self = weak_self_.GetWeakPtr();
540 auto conn_cb = [self,
541 callback = callback.share(),
542 peer_id = *peer_id,
543 request = std::move(client_request),
544 func = __FUNCTION__](auto result) mutable {
545 if (!self.is_alive())
546 return;
547
548 auto iter = self->connections_deprecated_.find(peer_id);
549 if (iter == self->connections_deprecated_.end()) {
550 bt_log(
551 INFO,
552 "fidl",
553 "%s: connect request canceled during connection procedure (peer: %s)",
554 func,
555 bt_str(peer_id));
556 auto error = fidl_helpers::NewFidlError(ErrorCode::FAILED,
557 "Connect request canceled");
558 callback(std::move(error));
559 return;
560 }
561
562 if (result.is_error()) {
563 bt_log(INFO,
564 "fidl",
565 "%s: failed to connect to peer (peer: %s)",
566 func,
567 bt_str(peer_id));
568 self->connections_deprecated_.erase(peer_id);
569 callback(fidl_helpers::ResultToFidlDeprecated(
570 bt::ToResult(result.error_value()), "failed to connect"));
571 return;
572 }
573
574 auto conn_ref = std::move(result).value();
575 PW_CHECK(conn_ref);
576 PW_CHECK(peer_id == conn_ref->peer_identifier());
577
578 if (self->gatt_client_servers_.find(peer_id) !=
579 self->gatt_client_servers_.end()) {
580 bt_log(WARN,
581 "fidl",
582 "only 1 gatt.Client FIDL handle allowed per peer (%s)",
583 bt_str(peer_id));
584 // The handle owned by |request| will be closed.
585 return;
586 }
587
588 auto server = std::make_unique<GattClientServer>(
589 peer_id, self->gatt_, std::move(request));
590 server->set_error_handler([self, peer_id](zx_status_t status) {
591 if (self.is_alive()) {
592 bt_log(DEBUG, "bt-host", "GATT client disconnected");
593 self->gatt_client_servers_.erase(peer_id);
594 }
595 });
596 self->gatt_client_servers_.emplace(peer_id, std::move(server));
597
598 conn_ref->set_closed_callback([self, peer_id] {
599 if (self.is_alive() &&
600 self->connections_deprecated_.erase(peer_id) != 0) {
601 bt_log(INFO,
602 "fidl",
603 "peripheral connection closed (peer: %s)",
604 bt_str(peer_id));
605 self->gatt_client_servers_.erase(peer_id);
606 self->NotifyPeripheralDisconnected(peer_id);
607 }
608 });
609
610 PW_CHECK(!iter->second);
611 iter->second = std::move(conn_ref);
612 callback(Status());
613 };
614
615 // An entry for the connection must be created here so that a synchronous call
616 // to conn_cb below does not cause conn_cb to treat the connection as
617 // cancelled.
618 connections_deprecated_[*peer_id] = nullptr;
619
620 adapter()->le()->Connect(*peer_id,
621 std::move(conn_cb),
622 ConnectionOptionsFromFidl(connection_options));
623 }
624
DisconnectPeripheral(::std::string identifier,DisconnectPeripheralCallback callback)625 void LowEnergyCentralServer::DisconnectPeripheral(
626 ::std::string identifier, DisconnectPeripheralCallback callback) {
627 auto peer_id = fidl_helpers::PeerIdFromString(identifier);
628 if (!peer_id.has_value()) {
629 bt_log(WARN,
630 "fidl",
631 "%s: invalid peer id : %s",
632 __FUNCTION__,
633 identifier.c_str());
634 callback(fidl_helpers::NewFidlError(ErrorCode::INVALID_ARGUMENTS,
635 "invalid peer ID"));
636 return;
637 }
638
639 auto iter = connections_deprecated_.find(*peer_id);
640 if (iter == connections_deprecated_.end()) {
641 bt_log(INFO,
642 "fidl",
643 "%s: client not connected to peer (peer: %s)",
644 __FUNCTION__,
645 identifier.c_str());
646 callback(Status());
647 return;
648 }
649
650 // If a request to this peer is pending then the request will be canceled.
651 bool was_pending = !iter->second;
652 connections_deprecated_.erase(iter);
653
654 if (was_pending) {
655 bt_log(INFO,
656 "fidl",
657 "%s: canceling connection request (peer: %s)",
658 __FUNCTION__,
659 bt_str(*peer_id));
660 } else {
661 gatt_client_servers_.erase(*peer_id);
662 NotifyPeripheralDisconnected(*peer_id);
663 }
664
665 callback(Status());
666 }
667
ListenL2cap(fble::ChannelListenerRegistryListenL2capRequest request,ListenL2capCallback callback)668 void LowEnergyCentralServer::ListenL2cap(
669 fble::ChannelListenerRegistryListenL2capRequest request,
670 ListenL2capCallback callback) {
671 // TODO(fxbug.dev/42178956): Implement ListenL2cap.
672 fble::ChannelListenerRegistry_ListenL2cap_Result result;
673 callback(std::move(result.set_err(ZX_ERR_NOT_SUPPORTED)));
674 }
675
OnScanResult(const bt::gap::Peer & peer)676 void LowEnergyCentralServer::OnScanResult(const bt::gap::Peer& peer) {
677 auto fidl_device = fidl_helpers::NewLERemoteDevice(peer);
678 if (!fidl_device) {
679 return;
680 }
681
682 if (peer.rssi() != bt::hci_spec::kRSSIInvalid) {
683 fidl_device->rssi = std::make_unique<Int8>();
684 fidl_device->rssi->value = peer.rssi();
685 }
686
687 binding()->events().OnDeviceDiscovered(std::move(*fidl_device));
688 }
689
NotifyScanStateChanged(bool scanning)690 void LowEnergyCentralServer::NotifyScanStateChanged(bool scanning) {
691 binding()->events().OnScanStateChanged(scanning);
692 }
693
NotifyPeripheralDisconnected(bt::PeerId peer_id)694 void LowEnergyCentralServer::NotifyPeripheralDisconnected(bt::PeerId peer_id) {
695 binding()->events().OnPeripheralDisconnected(peer_id.ToString());
696 }
697
698 } // namespace bthost
699