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 <map>
19 #include <mutex>
20 #include <unordered_map>
21 #include <unordered_set>
22 
23 #include "pw_bluetooth_sapphire/internal/host/common/identifier.h"
24 #include "pw_bluetooth_sapphire/internal/host/sdp/client.h"
25 #include "pw_bluetooth_sapphire/internal/host/sdp/sdp.h"
26 
27 namespace bt::sdp {
28 
29 // The Service Discoverer keeps track of which services are of interest to
30 // the host, and searches for those services on a remote device when directed
31 // to, reporting back to those interested asynchronously.
32 //
33 // Usually only one ServiceDiscoverer will exist per host.
34 // This class is thread-hostile: all functions must be called on the creation
35 // thread.
36 class ServiceDiscoverer final {
37  public:
38   ServiceDiscoverer();
39 
40   // Destroying the ServiceDiscoverer will mean all current searches will end
41   // and all current clients will be disconnected.
42   ~ServiceDiscoverer() = default;
43 
44   using SearchId = uint64_t;
45 
46   constexpr static SearchId kInvalidSearchId = 0u;
47 
48   // Add an interest in discovering a remote service.
49   // Discoverer will search for services with |uuid| in their records, and
50   // return via |callback| all of the attributes specified in |attributes|,
51   // along with the peer's |device_id|.
52   // If |attributes| is empty, all attributes will be requested.
53   // Returns a SearchId can be used to remove the search later if successful,
54   // or kInvalidSearchId if adding the search failed.
55   // |callback| will be called on the creation thread of ServiceDiscoverer.
56   using ResultCallback =
57       fit::function<void(PeerId, const std::map<AttributeId, DataElement>&)>;
58   SearchId AddSearch(const UUID& uuid,
59                      std::unordered_set<AttributeId> attributes,
60                      ResultCallback callback);
61 
62   // Remove a search previously added with AddSearch().
63   // Returns true if a search was removed and false if it was not found.
64   // This function is idempotent.
65   bool RemoveSearch(SearchId id);
66 
67   // Tries to add a single search using the SDP |client| connected to |peer_id|
68   // given for the search identified by |search_id|. Results from the search are
69   // delivered asynchronously via the ResultCallback registered via AddSearch.
70   // Does nothing if |search_id| is not currently registered.
71   // If |client| is nullptr, this search will only be performed if a client is
72   // already open to the peer.
73   void SingleSearch(SearchId search_id,
74                     PeerId peer_id,
75                     std::unique_ptr<Client> client);
76 
77   // Searches for all the registered services using a SDP |client|
78   // asynchronously.  The client is destroyed (disconnected) afterwards.
79   // If a search is already being performed on the same |peer_id|, the client
80   // is immediately dropped.
81   // Returns true if discovery was started, and false otherwise.
82   bool StartServiceDiscovery(PeerId peer_id, std::unique_ptr<Client> client);
83 
84   // Returns the number of searches that will be performed on a
85   // StartServiceDiscovery.
86   size_t search_count() const;
87 
88  private:
89   // A registered search.
90   struct Search {
91     UUID uuid;
92     std::unordered_set<AttributeId> attributes;
93     ResultCallback callback;
94   };
95 
96   // A Discovery Session happens using a Client, and ends when no registered
97   // searches still need to be completed.
98   struct DiscoverySession {
99     std::unique_ptr<Client> client;
100     // The set of Searches that have yet to complete.
101     // Should always be non-empty if this session exists.
102     std::unordered_set<SearchId> active;
103   };
104 
105   // Finish the Discovery Session for |peer_id| searching |search_id|,
106   // releasing the client if all searches are complete.
107   void FinishPeerSearch(PeerId peer_id, SearchId search_id);
108 
109   // Next likely search id
110   SearchId next_id_;
111 
112   // Registered searches
113   std::unordered_map<SearchId, Search> searches_;
114 
115   // Clients that searches are still being performed on, based on the remote
116   // peer id.
117   std::unordered_map<PeerId, DiscoverySession> sessions_;
118 };
119 
120 }  // namespace bt::sdp
121