xref: /aosp_15_r20/external/pigweed/pw_bluetooth_sapphire/fuchsia/host/fidl/low_energy_central_server.cc (revision 61c4878ac05f98d0ceed94b57d316916de578985)
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