1 // Copyright 2023 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 #include <lib/fit/function.h>
17 
18 #include <queue>
19 #include <unordered_set>
20 
21 #include "pw_bluetooth_sapphire/internal/host/common/inspect.h"
22 #include "pw_bluetooth_sapphire/internal/host/common/macros.h"
23 #include "pw_bluetooth_sapphire/internal/host/gap/peer.h"
24 #include "pw_bluetooth_sapphire/internal/host/transport/command_channel.h"
25 #include "pw_bluetooth_sapphire/internal/host/transport/control_packets.h"
26 #include "pw_bluetooth_sapphire/internal/host/transport/error.h"
27 
28 namespace bt::gap {
29 
30 class PeerCache;
31 
32 class BrEdrDiscoverySession;
33 class BrEdrDiscoverableSession;
34 
35 // BrEdrDiscoveryManager implements discovery for BR/EDR peers.  We provide a
36 // mechanism for multiple clients to simultaneously request discovery.  Peers
37 // discovered will be added to the PeerCache.
38 //
39 // Only one instance of BrEdrDiscoveryManager should be created for a bt-host.
40 //
41 // Request discovery using RequestDiscovery() which will provide a
42 // BrEdrDiscoverySession object in the |callback| when discovery is started.
43 // Ownership of this session is passed to the caller; when no sessions exist,
44 // discovery is halted.
45 //
46 // TODO(jamuraa): Name resolution should also happen here. (fxbug.dev/42165961)
47 //
48 // This class is not thread-safe, BrEdrDiscoverySessions should be created and
49 // accessed on the same thread the BrEdrDiscoveryManager is created.
50 class BrEdrDiscoveryManager final {
51  public:
52   // |peer_cache| MUST out-live this BrEdrDiscoveryManager.
53   BrEdrDiscoveryManager(pw::async::Dispatcher& pw_dispatcher,
54                         hci::CommandChannel::WeakPtr cmd,
55                         pw::bluetooth::emboss::InquiryMode mode,
56                         PeerCache* peer_cache);
57 
58   ~BrEdrDiscoveryManager();
59 
60   // Starts discovery and reports the status via |callback|. If discovery has
61   // been successfully started, the callback will receive a session object that
62   // it owns. If no sessions are owned, peer discovery is stopped.
63   using DiscoveryCallback =
64       fit::function<void(const hci::Result<>& status,
65                          std::unique_ptr<BrEdrDiscoverySession> session)>;
66   void RequestDiscovery(DiscoveryCallback callback);
67 
68   // Returns whether a discovery session is active.
discovering()69   bool discovering() const { return !discovering_.empty(); }
70 
71   // Requests this device be discoverable. We are discoverable as long as
72   // anyone holds a discoverable session.
73   using DiscoverableCallback =
74       fit::function<void(const hci::Result<>& status,
75                          std::unique_ptr<BrEdrDiscoverableSession> session)>;
76   void RequestDiscoverable(DiscoverableCallback callback);
77 
discoverable()78   bool discoverable() const { return !discoverable_.empty(); }
79 
80   // Updates local name of BrEdrDiscoveryManager.
81   // Updates the extended inquiry response to include the new |name|.
82   void UpdateLocalName(std::string name, hci::ResultFunction<> callback);
83 
84   // Returns the BR/EDR local name used for EIR.
local_name()85   std::string local_name() const { return local_name_; }
86 
87   // Attach discovery manager inspect node as a child node of |parent|.
88   void AttachInspect(inspect::Node& parent, std::string name);
89 
90   using WeakPtr = WeakSelf<BrEdrDiscoveryManager>::WeakPtr;
91 
92  private:
93   friend class BrEdrDiscoverySession;
94   friend class BrEdrDiscoverableSession;
95 
96   // Starts the inquiry procedure if any sessions exist.
97   void MaybeStartInquiry();
98 
99   // Stops the inquiry procedure.
100   void StopInquiry();
101 
102   // Used to receive Inquiry Results.
103   hci::CommandChannel::EventCallbackResult InquiryResult(
104       const hci::EventPacket& event);
105 
106   // Used to receive Inquiry Results.
107   hci::CommandChannel::EventCallbackResult InquiryResultWithRssi(
108       const hci::EventPacket& event);
109 
110   // Used to receive Inquiry Results.
111   hci::CommandChannel::EventCallbackResult ExtendedInquiryResult(
112       const hci::EventPacket& event);
113 
114   // Creates and stores a new session object and returns it.
115   std::unique_ptr<BrEdrDiscoverySession> AddDiscoverySession();
116 
117   // Removes |session_| from the active sessions.
118   void RemoveDiscoverySession(BrEdrDiscoverySession* session);
119 
120   // Invalidates all discovery sessions, invoking their error callbacks.
121   void InvalidateDiscoverySessions();
122 
123   // Sets the Inquiry Scan to the correct state given discoverable sessions,
124   // pending requests and the current scan state.
125   void SetInquiryScan();
126 
127   // Writes the Inquiry Scan Settings to the controller.
128   // If |interlaced| is true, and the controller does not support interlaces
129   // inquiry scan mode, standard mode is used.
130   void WriteInquiryScanSettings(uint16_t interval,
131                                 uint16_t window,
132                                 bool interlaced);
133 
134   // Creates and stores a new session object and returns it.
135   std::unique_ptr<BrEdrDiscoverableSession> AddDiscoverableSession();
136 
137   // Removes |session_| from the active sessions.
138   void RemoveDiscoverableSession(BrEdrDiscoverableSession* session);
139 
140   // Called when |peers| have been updated with new inquiry data.
141   void NotifyPeersUpdated(const std::unordered_set<Peer*>& peers);
142 
143   // Sends a RemoteNameRequest to the peer with |id|.
144   void RequestPeerName(PeerId id);
145 
146   // Updates the EIR response data with |name|.
147   // Currently, only the name field in EIR is supported.
148   void UpdateEIRResponseData(std::string name, hci::ResultFunction<> callback);
149 
150   // Updates the Inspect properties
151   void UpdateInspectProperties();
152 
153   // The Command channel
154   hci::CommandChannel::WeakPtr cmd_;
155 
156   struct InspectProperties {
157     inspect::Node node;
158 
159     inspect::UintProperty discoverable_sessions;
160     inspect::UintProperty pending_discoverable_sessions;
161     inspect::UintProperty discoverable_sessions_count;
162     inspect::UintProperty last_discoverable_length_sec;
163 
164     inspect::UintProperty discovery_sessions;
165     inspect::UintProperty discovery_sessions_count;
166     inspect::UintProperty last_discovery_length_sec;
167 
168     std::optional<pw::chrono::SystemClock::time_point>
169         discoverable_started_time;
170     std::optional<pw::chrono::SystemClock::time_point> inquiry_started_time;
171 
172     void Initialize(inspect::Node node);
173     void Update(size_t discoverable_count,
174                 size_t pending_discoverable_count,
175                 size_t discovery_count,
176                 pw::chrono::SystemClock::time_point now);
177   };
178 
179   InspectProperties inspect_properties_;
180 
181   // The dispatcher that we use for invoking callbacks asynchronously.
182   pw::async::Dispatcher& dispatcher_;
183 
184   // Peer cache to use.
185   // We hold a raw pointer is because it must out-live us.
186   PeerCache* cache_;
187 
188   // The local name that was last successfully written to the controller.
189   std::string local_name_;
190 
191   // The list of discovering sessions. We store raw pointers here as we
192   // don't own the sessions.  Sessions notify us when they are destroyed to
193   // maintain this list.
194   //
195   // When |discovering_| becomes empty then scanning is stopped.
196   std::unordered_set<BrEdrDiscoverySession*> discovering_;
197   // Sessions that have been removed but are still active.
198   // Inquiry persists until we receive a Inquiry Complete event.
199   // TODO(fxbug.dev/42145646): we should not need these once we can Inquiry
200   // Cancel.
201   std::unordered_set<BrEdrDiscoverySession*> zombie_discovering_;
202 
203   // The set of peers that we have pending name requests for.
204   std::unordered_set<PeerId> requesting_names_;
205 
206   // The set of callbacks that are waiting on inquiry to start.
207   std::queue<DiscoveryCallback> pending_discovery_;
208 
209   // The list of discoverable sessions. We store raw pointers here as we
210   // don't own the sessions.  Sessions notify us when they are destroyed to
211   // maintain this list.
212   //
213   // When |discoverable_| becomes empty then inquiry scan is disabled.
214   std::unordered_set<BrEdrDiscoverableSession*> discoverable_;
215 
216   // The set of callbacks that are waiting on inquiry scan to be active.
217   std::queue<hci::ResultFunction<>> pending_discoverable_;
218 
219   // The Handler IDs of the event handlers for inquiry results.
220   hci::CommandChannel::EventHandlerId result_handler_id_;
221   hci::CommandChannel::EventHandlerId rssi_handler_id_;
222   hci::CommandChannel::EventHandlerId eir_handler_id_;
223 
224   // The inquiry mode that we should use.
225   pw::bluetooth::emboss::InquiryMode desired_inquiry_mode_;
226   // The current inquiry mode.
227   pw::bluetooth::emboss::InquiryMode current_inquiry_mode_;
228 
229   WeakSelf<BrEdrDiscoveryManager> weak_self_;
230 
231   BT_DISALLOW_COPY_AND_ASSIGN_ALLOW_MOVE(BrEdrDiscoveryManager);
232 };
233 
234 class BrEdrDiscoverySession final {
235  public:
236   // Destroying a session instance ends this discovery session. Discovery may
237   // continue if other clients have started discovery sesisons.
238   ~BrEdrDiscoverySession();
239 
240   // Set a result callback that will be notified whenever a result is returned
241   // from the controller.  You will get duplicate results when using this
242   // method.
243   // Prefer PeerCache.add_peer_updated_callback() instead.
244   using PeerFoundCallback = fit::function<void(const Peer& peer)>;
set_result_callback(PeerFoundCallback callback)245   void set_result_callback(PeerFoundCallback callback) {
246     peer_found_callback_ = std::move(callback);
247   }
248 
249   // Set a callback to be notified if the session becomes inactive because
250   // of internal errors.
set_error_callback(fit::closure callback)251   void set_error_callback(fit::closure callback) {
252     error_callback_ = std::move(callback);
253   }
254 
255  private:
256   friend class BrEdrDiscoveryManager;
257 
258   // Used by the BrEdrDiscoveryManager to create a session.
259   explicit BrEdrDiscoverySession(BrEdrDiscoveryManager::WeakPtr manager);
260 
261   // Called by the BrEdrDiscoveryManager when a peer report is found.
262   void NotifyDiscoveryResult(const Peer& peer) const;
263 
264   // Marks this session as ended because of an error.
265   void NotifyError() const;
266 
267   BrEdrDiscoveryManager::WeakPtr manager_;
268   fit::closure error_callback_;
269   PeerFoundCallback peer_found_callback_;
270 
271   BT_DISALLOW_COPY_AND_ASSIGN_ALLOW_MOVE(BrEdrDiscoverySession);
272 };
273 
274 class BrEdrDiscoverableSession final {
275  public:
276   // Destroying a session instance relinquishes the request.
277   // The peer may still be discoverable if others are requesting so.
278   ~BrEdrDiscoverableSession();
279 
280  private:
281   friend class BrEdrDiscoveryManager;
282 
283   // Used by the BrEdrDiscoveryManager to create a session.
284   explicit BrEdrDiscoverableSession(BrEdrDiscoveryManager::WeakPtr manager);
285 
286   BrEdrDiscoveryManager::WeakPtr manager_;
287 
288   BT_DISALLOW_COPY_AND_ASSIGN_ALLOW_MOVE(BrEdrDiscoverableSession);
289 };
290 
291 }  // namespace bt::gap
292