• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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