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 #include <lib/async/cpp/wait.h>
19 #include <lib/fidl/cpp/binding.h>
20 
21 #include <memory>
22 #include <unordered_map>
23 
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/common/weak_self.h"
28 #include "pw_bluetooth_sapphire/internal/host/gap/adapter.h"
29 #include "pw_bluetooth_sapphire/internal/host/gap/low_energy_advertising_manager.h"
30 #include "pw_bluetooth_sapphire/internal/host/gap/low_energy_connection_manager.h"
31 
32 namespace bthost {
33 
34 // Implements the low_energy::Peripheral FIDL interface.
35 class LowEnergyPeripheralServer
36     : public AdapterServerBase<fuchsia::bluetooth::le::Peripheral> {
37  public:
38   LowEnergyPeripheralServer(
39       bt::gap::Adapter::WeakPtr adapter,
40       bt::gatt::GATT::WeakPtr gatt,
41       fidl::InterfaceRequest<fuchsia::bluetooth::le::Peripheral> request,
42       bool privileged = false);
43   ~LowEnergyPeripheralServer() override;
44 
45   // fuchsia::bluetooth::le::Peripheral overrides:
46   void Advertise(
47       fuchsia::bluetooth::le::AdvertisingParameters parameters,
48       fidl::InterfaceHandle<fuchsia::bluetooth::le::AdvertisedPeripheral>
49           advertised_peripheral,
50       AdvertiseCallback callback) override;
51   void StartAdvertising(
52       fuchsia::bluetooth::le::AdvertisingParameters parameters,
53       ::fidl::InterfaceRequest<fuchsia::bluetooth::le::AdvertisingHandle> token,
54       StartAdvertisingCallback callback) override;
55 
56   // fuchsia::bluetooth::le::ChannelListenerRegistry overrides:
57   void ListenL2cap(
58       fuchsia::bluetooth::le::ChannelListenerRegistryListenL2capRequest request,
59       ListenL2capCallback callback) override;
60 
61   // Returns the connection handle associated with the given |id|, or nullptr if
62   // the peer with |id| is no longer connected. Should only be used for testing.
63   const bt::gap::LowEnergyConnectionHandle* FindConnectionForTesting(
64       bt::PeerId id) const;
65 
66  private:
67   using ConnectionRefPtr = std::unique_ptr<bt::gap::LowEnergyConnectionHandle>;
68   using AdvertisementInstanceId = uint64_t;
69   using ConnectionServerId = uint64_t;
70 
71   // Manages state associated with a single invocation of the
72   // `Peripheral.Advertise` method.
73   class AdvertisementInstance final {
74    public:
75     using AdvertiseCompleteCallback = fit::callback<void(
76         fuchsia::bluetooth::le::Peripheral_Advertise_Result)>;
77 
78     // |complete_cb| will be called to send a Peripheral.Advertise response to
79     // the client when an error occurs or this AdvertisementInstance is
80     // destroyed. This is done so that the FIDL client can determine when the
81     // server has terminated this AdvertisementInstance (this is useful for
82     // reconfiguring an advertisement).
83     AdvertisementInstance(
84         LowEnergyPeripheralServer* peripheral_server,
85         AdvertisementInstanceId id,
86         fuchsia::bluetooth::le::AdvertisingParameters parameters,
87         fidl::InterfaceHandle<fuchsia::bluetooth::le::AdvertisedPeripheral>
88             handle,
89         AdvertiseCompleteCallback complete_cb);
90     ~AdvertisementInstance();
91 
92     // This method is separate from the constructor because HCI-level
93     // advertising may be started many times over the life of this object.
94     void StartAdvertising();
95 
96     // Called when a central connects to us.  When this is called, the
97     // advertisement in |advertisement_id| has been stopped.
98     void OnConnected(bt::gap::AdvertisementId advertisement_id,
99                      bt::gap::Adapter::LowEnergy::ConnectionResult result);
100 
101    private:
102     // After advertising successfully starts, the advertisement instance must be
103     // registered to tie advertising to the lifetime of this object.
104     void Register(bt::gap::AdvertisementInstance instance);
105 
106     // End the advertisement with a result. Idempotent.
107     // This object should be destroyed immediately after calling this method.
108     void CloseWith(
109         fpromise::result<void, fuchsia::bluetooth::le::PeripheralError> result);
110 
111     LowEnergyPeripheralServer* peripheral_server_;
112     AdvertisementInstanceId id_;
113     fuchsia::bluetooth::le::AdvertisingParameters parameters_;
114 
115     // The advertising handle set by Register. When destroyed, advertising will
116     // be stopped.
117     std::optional<bt::gap::AdvertisementInstance> instance_;
118 
119     // The AdvertisedPeripheral protocol representing this advertisement.
120     fidl::InterfacePtr<fuchsia::bluetooth::le::AdvertisedPeripheral>
121         advertised_peripheral_;
122 
123     // Callback used to send a response to the Advertise request that started
124     // this advertisement.
125     AdvertiseCompleteCallback advertise_complete_cb_;
126 
127     WeakSelf<AdvertisementInstance> weak_self_;
128 
129     BT_DISALLOW_COPY_AND_ASSIGN_ALLOW_MOVE(AdvertisementInstance);
130   };
131 
132   class AdvertisementInstanceDeprecated final {
133    public:
134     explicit AdvertisementInstanceDeprecated(
135         fidl::InterfaceRequest<fuchsia::bluetooth::le::AdvertisingHandle>
136             handle);
137     ~AdvertisementInstanceDeprecated();
138 
139     // Begin watching for ZX_CHANNEL_PEER_CLOSED events on the AdvertisingHandle
140     // this was initialized with. The returned status will indicate an error if
141     // wait cannot be initiated (e.g. because the peer closed its end of the
142     // channel).
143     zx_status_t Register(bt::gap::AdvertisementInstance instance);
144 
145     // Returns the ID assigned to this instance, or
146     // bt::gap::kInvalidAdvertisementId if one wasn't assigned.
id()147     bt::gap::AdvertisementId id() const {
148       return instance_ ? instance_->id() : bt::gap::kInvalidAdvertisementId;
149     }
150 
151    private:
152     std::optional<bt::gap::AdvertisementInstance> instance_;
153     fidl::InterfaceRequest<fuchsia::bluetooth::le::AdvertisingHandle> handle_;
154     async::Wait handle_closed_wait_;
155 
156     BT_DISALLOW_COPY_AND_ASSIGN_ALLOW_MOVE(AdvertisementInstanceDeprecated);
157   };
158 
159   // Called when a central connects to us.  When this is called, the
160   // advertisement in |advertisement_id| has been stopped.
161   void OnConnectedDeprecated(
162       bt::gap::AdvertisementId advertisement_id,
163       bt::gap::Adapter::LowEnergy::ConnectionResult result);
164 
165   // Sets up a Connection server and returns the client end.
166   fidl::InterfaceHandle<fuchsia::bluetooth::le::Connection>
167   CreateConnectionServer(
168       std::unique_ptr<bt::gap::LowEnergyConnectionHandle> connection);
169 
170   // Common advertising initiation code shared by Peripheral.{Advertise,
171   // StartAdvertising}. If advertising was initiated by `Advertise`,
172   // `advertisement_instance` must be set to the identifier of the
173   // `AdvertisementInstance` that connections to this advertisement should be
174   // routed to. Otherwise, connections will be sent in a
175   // `Peripheral.OnConnected` event.
176   void StartAdvertisingInternal(
177       fuchsia::bluetooth::le::AdvertisingParameters& parameters,
178       bt::gap::Adapter::LowEnergy::AdvertisingStatusCallback status_cb,
179       std::optional<AdvertisementInstanceId> advertisement_instance =
180           std::nullopt);
181 
RemoveAdvertisingInstance(AdvertisementInstanceId id)182   void RemoveAdvertisingInstance(AdvertisementInstanceId id) {
183     advertisements_.erase(id);
184   }
185 
186   // Represents the current advertising instance:
187   // - Contains no value if advertising was never requested.
188   // - Contains a value while advertising is being (re)enabled and during
189   // advertising.
190   // - May correspond to an invalidated advertising instance if advertising is
191   // stopped by closing
192   //   the AdvertisingHandle.
193   std::optional<AdvertisementInstanceDeprecated> advertisement_deprecated_;
194 
195   // Map of all active advertisement instances associated with a call to
196   // `Advertise`. bt::gap::AdvertisementId cannot be used as a map key because
197   // it is received asynchronously, and we need an advertisement ID to refer to
198   // before advertising starts.
199   // TODO: https://fxbug.dev/42157682 - Support AdvertisedPeripheral protocols
200   // that outlive this Peripheral protocol. This may require passing
201   // AdvertisementInstances to HostServer.
202   AdvertisementInstanceId next_advertisement_instance_id_ = 0u;
203   std::unordered_map<AdvertisementInstanceId, AdvertisementInstance>
204       advertisements_;
205 
206   // Connections that were initiated to this peripheral. A single Peripheral
207   // instance can hold many connections across numerous advertisements that it
208   // initiates during its lifetime.
209   ConnectionServerId next_connection_server_id_ = 0u;
210   std::unordered_map<ConnectionServerId,
211                      std::unique_ptr<LowEnergyConnectionServer>>
212       connections_;
213 
214   bt::gatt::GATT::WeakPtr gatt_;
215 
216   // True if PrivilegedPeripheral created this server. Defaults to false.
217   bool privileged_;
218 
219   // Keep this as the last member to make sure that all weak pointers are
220   // invalidated before other members get destroyed.
221   WeakSelf<LowEnergyPeripheralServer> weak_self_;
222 
223   BT_DISALLOW_COPY_AND_ASSIGN_ALLOW_MOVE(LowEnergyPeripheralServer);
224 };
225 
226 // Implements the fuchsia::bluetooth::le::PrivilegedPeripheral FIDL interface.
227 class LowEnergyPrivilegedPeripheralServer
228     : public AdapterServerBase<fuchsia::bluetooth::le::PrivilegedPeripheral> {
229  public:
230   LowEnergyPrivilegedPeripheralServer(
231       const bt::gap::Adapter::WeakPtr& adapter,
232       bt::gatt::GATT::WeakPtr gatt,
233       fidl::InterfaceRequest<fuchsia::bluetooth::le::PrivilegedPeripheral>
234           request);
235 
236   // fuchsia::bluetooth::le::Peripheral overrides:
237   void Advertise(
238       fuchsia::bluetooth::le::AdvertisingParameters parameters,
239       fidl::InterfaceHandle<fuchsia::bluetooth::le::AdvertisedPeripheral>
240           advertised_peripheral,
241       AdvertiseCallback callback) override;
242   void StartAdvertising(
243       fuchsia::bluetooth::le::AdvertisingParameters parameters,
244       ::fidl::InterfaceRequest<fuchsia::bluetooth::le::AdvertisingHandle> token,
245       StartAdvertisingCallback callback) override;
246 
247   // fuchsia::bluetooth::le::ChannelListenerRegistry overrides:
248   void ListenL2cap(
249       fuchsia::bluetooth::le::ChannelListenerRegistryListenL2capRequest request,
250       ListenL2capCallback callback) override;
251 
252  private:
253   std::unique_ptr<LowEnergyPeripheralServer> le_peripheral_server_;
254 
255   WeakSelf<LowEnergyPrivilegedPeripheralServer> weak_self_;
256 
257   BT_DISALLOW_COPY_AND_ASSIGN_ALLOW_MOVE(LowEnergyPrivilegedPeripheralServer);
258 };
259 
260 }  // namespace bthost
261