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