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 #pragma once
16 
17 #include <fuchsia/bluetooth/le/cpp/fidl.h>
18 
19 #include <memory>
20 #include <unordered_map>
21 
22 #include "lib/fidl/cpp/binding.h"
23 #include "pw_bluetooth_sapphire/fuchsia/host/fidl/gatt_client_server.h"
24 #include "pw_bluetooth_sapphire/fuchsia/host/fidl/low_energy_connection_server.h"
25 #include "pw_bluetooth_sapphire/fuchsia/host/fidl/server_base.h"
26 #include "pw_bluetooth_sapphire/internal/host/common/macros.h"
27 #include "pw_bluetooth_sapphire/internal/host/gap/low_energy_connection_manager.h"
28 #include "pw_bluetooth_sapphire/internal/host/gap/low_energy_discovery_manager.h"
29 #include "pw_bluetooth_sapphire/internal/host/gap/peer_cache.h"
30 
31 namespace bthost {
32 
33 // Implements the low_energy::Central FIDL interface.
34 class LowEnergyCentralServer
35     : public AdapterServerBase<fuchsia::bluetooth::le::Central> {
36  public:
37   // The maximum number of peers that will be queued for a
38   // ScanResultWatcher.Watch call. This hard limit prevents unbounded memory
39   // usage for unresponsive clients. The value is mostly arbitrary, as queued
40   // `PeerId`s are small and peak memory usage, occurring when creating a vector
41   // of FIDL `le.Peer`s, is limited by the size of the FIDL channel.
42   constexpr static const size_t kMaxPendingScanResultWatcherPeers = 100;
43 
44   LowEnergyCentralServer(
45       bt::gap::Adapter::WeakPtr adapter,
46       ::fidl::InterfaceRequest<fuchsia::bluetooth::le::Central> request,
47       bt::gatt::GATT::WeakPtr gatt);
48   ~LowEnergyCentralServer() override;
49 
50   // Returns the connection pointer in the connections_deprecated_ map, if it
51   // exists. The pointer will be nullptr if a request is pending. Should only be
52   // used for testing.
53   std::optional<bt::gap::LowEnergyConnectionHandle*> FindConnectionForTesting(
54       bt::PeerId identifier);
55 
56  private:
57   class ScanResultWatcherServer
58       : public ServerBase<fuchsia::bluetooth::le::ScanResultWatcher> {
59    public:
60     using WatchCallbackOnce =
61         fit::callback<void(std::vector<fuchsia::bluetooth::le::Peer>)>;
62 
63     // `error_cb` will be called when the client closes the protocol.
64     ScanResultWatcherServer(
65         bt::gap::Adapter::WeakPtr adapter,
66         fidl::InterfaceRequest<fuchsia::bluetooth::le::ScanResultWatcher>
67             watcher,
68         fit::callback<void()> error_cb);
69     ~ScanResultWatcherServer() override = default;
70 
71     // Closes the protocol and sends `epitaph` as the epitaph. Idempotent.
72     void Close(zx_status_t epitaph);
73 
74     // Queue `peers` to be sent in response to `Watch()`.
75     void AddPeers(std::unordered_set<bt::PeerId> peers);
76 
77     // fuchsia::bluetooth::le::ScanResultWatcher overrides:
78     void Watch(WatchCallback callback) override;
79 
80    private:
81     // If the client has a pending `Watch()`, send the maximum number of peers
82     // that will fit in the channel.
83     void MaybeSendPeers();
84 
85     bt::gap::Adapter::WeakPtr adapter_;
86     std::unordered_set<bt::PeerId> updated_peers_;
87     WatchCallbackOnce watch_callback_ = nullptr;
88     fit::callback<void()> error_callback_;
89   };
90 
91   // ScanInstance represents a call to `Scan` that has not stopped yet.
92   class ScanInstance {
93    public:
94     using ScanCompleteCallback = fit::callback<void()>;
95 
96     ScanInstance(
97         bt::gap::Adapter::WeakPtr adapter,
98         LowEnergyCentralServer* central_server,
99         std::vector<fuchsia::bluetooth::le::Filter> filters,
100         fidl::InterfaceRequest<fuchsia::bluetooth::le::ScanResultWatcher>
101             watcher,
102         ScanCallback cb);
103     ~ScanInstance();
104     // Closes the ScanResultWatcher protocol with the epitaph `status` and sends
105     // an empty response to `Scan`. Idempotent.
106     void Close(zx_status_t status);
107 
108     // Queue peers to be sent to the client via `ScanResultWatcher.Watch`.
109     // `peers` will be filtered by the client's `ScanOptions` filters before
110     // being sent.
111     void FilterAndAddPeers(std::unordered_set<bt::PeerId> peers);
112 
113    private:
114     std::unique_ptr<bt::gap::LowEnergyDiscoverySession> scan_session_;
115     ScanResultWatcherServer result_watcher_;
116     // Callback used to send an empty response to the client's `Scan()` call.
117     ScanCompleteCallback scan_complete_callback_;
118     bt::gap::PeerCache::CallbackId peer_updated_callback_id_;
119     // The filters specified in `ScanOptions`.
120     std::vector<bt::gap::DiscoveryFilter> filters_;
121     LowEnergyCentralServer* central_server_;
122     bt::gap::Adapter::WeakPtr adapter_;
123     WeakSelf<ScanInstance> weak_self_;
124   };
125 
126   // fuchsia::bluetooth::le::Central overrides:
127   void Scan(fuchsia::bluetooth::le::ScanOptions options,
128             fidl::InterfaceRequest<fuchsia::bluetooth::le::ScanResultWatcher>
129                 result_watcher,
130             ScanCallback callback) override;
131   void Connect(fuchsia::bluetooth::PeerId id,
132                fuchsia::bluetooth::le::ConnectionOptions options,
133                fidl::InterfaceRequest<::fuchsia::bluetooth::le::Connection>
134                    request) override;
135 
136   void GetPeripherals(::fidl::VectorPtr<::std::string> service_uuids,
137                       GetPeripheralsCallback callback) override;
138   void GetPeripheral(::std::string identifier,
139                      GetPeripheralCallback callback) override;
140   void StartScan(fuchsia::bluetooth::le::ScanFilterPtr filter,
141                  StartScanCallback callback) override;
142   void StopScan() override;
143   void ConnectPeripheral(
144       ::std::string identifier,
145       fuchsia::bluetooth::le::ConnectionOptions connection_options,
146       ::fidl::InterfaceRequest<fuchsia::bluetooth::gatt::Client> client_request,
147       ConnectPeripheralCallback callback) override;
148   void DisconnectPeripheral(::std::string identifier,
149                             DisconnectPeripheralCallback callback) override;
150 
151   // fuchsia::bluetooth::le::ChannelListenerRegistry overrides:
152   void ListenL2cap(
153       fuchsia::bluetooth::le::ChannelListenerRegistryListenL2capRequest request,
154       ListenL2capCallback callback) override;
155 
156   // Called by |scan_session_| when a device is discovered.
157   void OnScanResult(const bt::gap::Peer& peer);
158 
159   // Notifies the delegate that the scan state for this Central has changed.
160   void NotifyScanStateChanged(bool scanning);
161 
162   // Notifies the delegate that the device with the given identifier has been
163   // disconnected.
164   void NotifyPeripheralDisconnected(bt::PeerId peer_id);
165 
ClearScan()166   void ClearScan() { scan_instance_.reset(); }
167 
168   // GATT is used to construct GattClientServers upon connection.
169   bt::gatt::GATT::WeakPtr gatt_;
170 
171   // Stores active GATT client FIDL servers. Only 1 client server per peer may
172   // exist.
173   std::unordered_map<bt::PeerId, std::unique_ptr<GattClientServer>>
174       gatt_client_servers_;
175 
176   // The currently active LE discovery session. This is initialized when a
177   // client requests to perform a scan.
178   bool requesting_scan_deprecated_;
179   std::unique_ptr<bt::gap::LowEnergyDiscoverySession> scan_session_deprecated_;
180 
181   std::unique_ptr<ScanInstance> scan_instance_;
182 
183   // This client's connection references. A client can hold a connection to
184   // multiple peers. Each key is a peer identifier. Each value is
185   //   a. nullptr, if a connect request to this device is currently pending.
186   //   b. a valid reference if this Central is holding a connection reference to
187   //   this device.
188   std::unordered_map<bt::PeerId, std::unique_ptr<LowEnergyConnectionServer>>
189       connections_;
190   std::unordered_map<bt::PeerId,
191                      std::unique_ptr<bt::gap::LowEnergyConnectionHandle>>
192       connections_deprecated_;
193 
194   // Keep this as the last member to make sure that all weak pointers are
195   // invalidated before other members get destroyed.
196   WeakSelf<LowEnergyCentralServer> weak_self_;
197 
198   BT_DISALLOW_COPY_AND_ASSIGN_ALLOW_MOVE(LowEnergyCentralServer);
199 };
200 
201 }  // namespace bthost
202