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 <zircon/types.h>
18
19 #include <utility>
20
21 #include "fuchsia/bluetooth/le/cpp/fidl.h"
22 #include "pw_bluetooth_sapphire/fuchsia/host/fidl/gatt_client_server.h"
23 #include "pw_bluetooth_sapphire/fuchsia/host/fidl/helpers.h"
24 #include "pw_bluetooth_sapphire/fuchsia/host/fidl/measure_tape/hlcpp_measure_tape_for_peer.h"
25 #include "pw_bluetooth_sapphire/internal/host/common/assert.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;
__anon7c1151fb0402(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(
__anon7c1151fb0502(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(
__anon7c1151fb0602(auto session) 229 /*active=*/true, [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::gap::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 if (scan_session_deprecated_) {
438 // A scan is already in progress. Update its filter and report success.
439 scan_session_deprecated_->filter()->Reset();
440 fidl_helpers::PopulateDiscoveryFilter(*filter,
441 scan_session_deprecated_->filter());
442 callback(Status());
443 return;
444 }
445
446 requesting_scan_deprecated_ = true;
447 adapter()->le()->StartDiscovery(
448 /*active=*/true,
449 [self = weak_self_.GetWeakPtr(),
450 filter = std::move(filter),
451 callback = std::move(callback),
452 func = __FUNCTION__](auto session) {
453 if (!self.is_alive())
454 return;
455
456 self->requesting_scan_deprecated_ = false;
457
458 if (!session) {
459 bt_log(
460 WARN, "fidl", "%s: failed to start LE discovery session", func);
461 callback(fidl_helpers::NewFidlError(
462 ErrorCode::FAILED, "Failed to start discovery session"));
463 return;
464 }
465
466 // Assign the filter contents if a filter was provided.
467 if (filter)
468 fidl_helpers::PopulateDiscoveryFilter(*filter, session->filter());
469
470 session->SetResultCallback([self](const auto& peer) {
471 if (self.is_alive())
472 self->OnScanResult(peer);
473 });
474
475 session->set_error_callback([self] {
476 if (self.is_alive()) {
477 // Clean up the session and notify the delegate.
478 self->StopScan();
479 }
480 });
481
482 self->scan_session_deprecated_ = std::move(session);
483 self->NotifyScanStateChanged(true);
484 callback(Status());
485 });
486 }
487
StopScan()488 void LowEnergyCentralServer::StopScan() {
489 bt_log(DEBUG, "fidl", "StopScan()");
490
491 if (!scan_session_deprecated_) {
492 bt_log(DEBUG,
493 "fidl",
494 "%s: no active discovery session; nothing to do",
495 __FUNCTION__);
496 return;
497 }
498
499 scan_session_deprecated_ = nullptr;
500 NotifyScanStateChanged(false);
501 }
502
ConnectPeripheral(::std::string identifier,fuchsia::bluetooth::le::ConnectionOptions connection_options,::fidl::InterfaceRequest<Client> client_request,ConnectPeripheralCallback callback)503 void LowEnergyCentralServer::ConnectPeripheral(
504 ::std::string identifier,
505 fuchsia::bluetooth::le::ConnectionOptions connection_options,
506 ::fidl::InterfaceRequest<Client> client_request,
507 ConnectPeripheralCallback callback) {
508 bt_log(INFO, "fidl", "%s: (peer: %s)", __FUNCTION__, identifier.c_str());
509
510 auto peer_id = fidl_helpers::PeerIdFromString(identifier);
511 if (!peer_id.has_value()) {
512 bt_log(WARN,
513 "fidl",
514 "%s: invalid peer id : %s",
515 __FUNCTION__,
516 identifier.c_str());
517 callback(fidl_helpers::NewFidlError(ErrorCode::INVALID_ARGUMENTS,
518 "invalid peer ID"));
519 return;
520 }
521
522 auto iter = connections_deprecated_.find(*peer_id);
523 if (iter != connections_deprecated_.end()) {
524 if (iter->second) {
525 bt_log(INFO,
526 "fidl",
527 "%s: already connected to %s",
528 __FUNCTION__,
529 bt_str(*peer_id));
530 callback(fidl_helpers::NewFidlError(
531 ErrorCode::ALREADY, "Already connected to requested peer"));
532 } else {
533 bt_log(INFO,
534 "fidl",
535 "%s: connect request pending (peer: %s)",
536 __FUNCTION__,
537 bt_str(*peer_id));
538 callback(fidl_helpers::NewFidlError(ErrorCode::IN_PROGRESS,
539 "Connect request pending"));
540 }
541 return;
542 }
543
544 auto self = weak_self_.GetWeakPtr();
545 auto conn_cb = [self,
546 callback = callback.share(),
547 peer_id = *peer_id,
548 request = std::move(client_request),
549 func = __FUNCTION__](auto result) mutable {
550 if (!self.is_alive())
551 return;
552
553 auto iter = self->connections_deprecated_.find(peer_id);
554 if (iter == self->connections_deprecated_.end()) {
555 bt_log(
556 INFO,
557 "fidl",
558 "%s: connect request canceled during connection procedure (peer: %s)",
559 func,
560 bt_str(peer_id));
561 auto error = fidl_helpers::NewFidlError(ErrorCode::FAILED,
562 "Connect request canceled");
563 callback(std::move(error));
564 return;
565 }
566
567 if (result.is_error()) {
568 bt_log(INFO,
569 "fidl",
570 "%s: failed to connect to peer (peer: %s)",
571 func,
572 bt_str(peer_id));
573 self->connections_deprecated_.erase(peer_id);
574 callback(fidl_helpers::ResultToFidlDeprecated(
575 bt::ToResult(result.error_value()), "failed to connect"));
576 return;
577 }
578
579 auto conn_ref = std::move(result).value();
580 PW_CHECK(conn_ref);
581 PW_CHECK(peer_id == conn_ref->peer_identifier());
582
583 if (self->gatt_client_servers_.find(peer_id) !=
584 self->gatt_client_servers_.end()) {
585 bt_log(WARN,
586 "fidl",
587 "only 1 gatt.Client FIDL handle allowed per peer (%s)",
588 bt_str(peer_id));
589 // The handle owned by |request| will be closed.
590 return;
591 }
592
593 auto server = std::make_unique<GattClientServer>(
594 peer_id, self->gatt_, std::move(request));
595 server->set_error_handler([self, peer_id](zx_status_t status) {
596 if (self.is_alive()) {
597 bt_log(DEBUG, "bt-host", "GATT client disconnected");
598 self->gatt_client_servers_.erase(peer_id);
599 }
600 });
601 self->gatt_client_servers_.emplace(peer_id, std::move(server));
602
603 conn_ref->set_closed_callback([self, peer_id] {
604 if (self.is_alive() &&
605 self->connections_deprecated_.erase(peer_id) != 0) {
606 bt_log(INFO,
607 "fidl",
608 "peripheral connection closed (peer: %s)",
609 bt_str(peer_id));
610 self->gatt_client_servers_.erase(peer_id);
611 self->NotifyPeripheralDisconnected(peer_id);
612 }
613 });
614
615 PW_CHECK(!iter->second);
616 iter->second = std::move(conn_ref);
617 callback(Status());
618 };
619
620 // An entry for the connection must be created here so that a synchronous call
621 // to conn_cb below does not cause conn_cb to treat the connection as
622 // cancelled.
623 connections_deprecated_[*peer_id] = nullptr;
624
625 adapter()->le()->Connect(*peer_id,
626 std::move(conn_cb),
627 ConnectionOptionsFromFidl(connection_options));
628 }
629
DisconnectPeripheral(::std::string identifier,DisconnectPeripheralCallback callback)630 void LowEnergyCentralServer::DisconnectPeripheral(
631 ::std::string identifier, DisconnectPeripheralCallback callback) {
632 auto peer_id = fidl_helpers::PeerIdFromString(identifier);
633 if (!peer_id.has_value()) {
634 bt_log(WARN,
635 "fidl",
636 "%s: invalid peer id : %s",
637 __FUNCTION__,
638 identifier.c_str());
639 callback(fidl_helpers::NewFidlError(ErrorCode::INVALID_ARGUMENTS,
640 "invalid peer ID"));
641 return;
642 }
643
644 auto iter = connections_deprecated_.find(*peer_id);
645 if (iter == connections_deprecated_.end()) {
646 bt_log(INFO,
647 "fidl",
648 "%s: client not connected to peer (peer: %s)",
649 __FUNCTION__,
650 identifier.c_str());
651 callback(Status());
652 return;
653 }
654
655 // If a request to this peer is pending then the request will be canceled.
656 bool was_pending = !iter->second;
657 connections_deprecated_.erase(iter);
658
659 if (was_pending) {
660 bt_log(INFO,
661 "fidl",
662 "%s: canceling connection request (peer: %s)",
663 __FUNCTION__,
664 bt_str(*peer_id));
665 } else {
666 gatt_client_servers_.erase(*peer_id);
667 NotifyPeripheralDisconnected(*peer_id);
668 }
669
670 callback(Status());
671 }
672
ListenL2cap(fble::ChannelListenerRegistryListenL2capRequest request,ListenL2capCallback callback)673 void LowEnergyCentralServer::ListenL2cap(
674 fble::ChannelListenerRegistryListenL2capRequest request,
675 ListenL2capCallback callback) {
676 // TODO(fxbug.dev/42178956): Implement ListenL2cap.
677 fble::ChannelListenerRegistry_ListenL2cap_Result result;
678 callback(std::move(result.set_err(ZX_ERR_NOT_SUPPORTED)));
679 }
680
OnScanResult(const bt::gap::Peer & peer)681 void LowEnergyCentralServer::OnScanResult(const bt::gap::Peer& peer) {
682 auto fidl_device = fidl_helpers::NewLERemoteDevice(peer);
683 if (!fidl_device) {
684 return;
685 }
686
687 if (peer.rssi() != bt::hci_spec::kRSSIInvalid) {
688 fidl_device->rssi = std::make_unique<Int8>();
689 fidl_device->rssi->value = peer.rssi();
690 }
691
692 binding()->events().OnDeviceDiscovered(std::move(*fidl_device));
693 }
694
NotifyScanStateChanged(bool scanning)695 void LowEnergyCentralServer::NotifyScanStateChanged(bool scanning) {
696 binding()->events().OnScanStateChanged(scanning);
697 }
698
NotifyPeripheralDisconnected(bt::PeerId peer_id)699 void LowEnergyCentralServer::NotifyPeripheralDisconnected(bt::PeerId peer_id) {
700 binding()->events().OnPeripheralDisconnected(peer_id.ToString());
701 }
702
703 } // namespace bthost
704