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 #include "pw_bluetooth_sapphire/internal/host/sdp/service_discoverer.h"
16
17 #include <cinttypes>
18 #include <functional>
19
20 namespace bt::sdp {
21
ServiceDiscoverer()22 ServiceDiscoverer::ServiceDiscoverer() : next_id_(1) {}
23
AddSearch(const UUID & uuid,std::unordered_set<AttributeId> attributes,ResultCallback callback)24 ServiceDiscoverer::SearchId ServiceDiscoverer::AddSearch(
25 const UUID& uuid,
26 std::unordered_set<AttributeId> attributes,
27 ResultCallback callback) {
28 Search s;
29 s.uuid = uuid;
30 s.attributes = std::move(attributes);
31 s.callback = std::move(callback);
32 PW_DCHECK(next_id_ < std::numeric_limits<ServiceDiscoverer::SearchId>::max());
33 ServiceDiscoverer::SearchId id = next_id_++;
34 auto [it, placed] = searches_.emplace(id, std::move(s));
35 PW_DCHECK(placed, "Should always be able to place new search");
36 return id;
37 }
38
RemoveSearch(SearchId id)39 bool ServiceDiscoverer::RemoveSearch(SearchId id) {
40 auto it = sessions_.begin();
41 while (it != sessions_.end()) {
42 if (it->second.active.erase(id) && it->second.active.empty()) {
43 it = sessions_.erase(it);
44 } else {
45 it++;
46 }
47 }
48 return searches_.erase(id);
49 }
50
SingleSearch(SearchId search_id,PeerId peer_id,std::unique_ptr<Client> client)51 void ServiceDiscoverer::SingleSearch(SearchId search_id,
52 PeerId peer_id,
53 std::unique_ptr<Client> client) {
54 auto session_iter = sessions_.find(peer_id);
55 if (session_iter == sessions_.end()) {
56 if (client == nullptr) {
57 // Can't do a search if we don't have an open channel
58 bt_log(WARN,
59 "sdp",
60 "Can't start a new session without a channel (peer_id %s)",
61 bt_str(peer_id));
62 return;
63 }
64 // Setup the session.
65 DiscoverySession session;
66 session.client = std::move(client);
67 auto placed = sessions_.emplace(peer_id, std::move(session));
68 PW_DCHECK(placed.second);
69 session_iter = placed.first;
70 }
71 PW_DCHECK(session_iter != sessions_.end());
72 auto search_it = searches_.find(search_id);
73 if (search_it == searches_.end()) {
74 bt_log(INFO, "sdp", "Couldn't find search with id %" PRIu64, search_id);
75 return;
76 }
77 Search& search = search_it->second;
78 Client::SearchResultFunction result_cb =
79 [this, peer_id, search_id](
80 fit::result<
81 Error<>,
82 std::reference_wrapper<const std::map<AttributeId, DataElement>>>
83 attributes_result) {
84 auto it = searches_.find(search_id);
85 if (it == searches_.end() || attributes_result.is_error()) {
86 FinishPeerSearch(peer_id, search_id);
87 return false;
88 }
89 it->second.callback(peer_id, attributes_result.value());
90 return true;
91 };
92 session_iter->second.active.emplace(search_id);
93 session_iter->second.client->ServiceSearchAttributes(
94 {search.uuid}, search.attributes, std::move(result_cb));
95 }
96
StartServiceDiscovery(PeerId peer_id,std::unique_ptr<Client> client)97 bool ServiceDiscoverer::StartServiceDiscovery(PeerId peer_id,
98 std::unique_ptr<Client> client) {
99 // If discovery is already happening on this peer, then we can't start it
100 // again.
101 if (sessions_.count(peer_id)) {
102 return false;
103 }
104 // If there aren't any searches to do, we're done.
105 if (searches_.empty()) {
106 return true;
107 }
108 for (auto& [search_id, _] : searches_) {
109 SingleSearch(search_id, peer_id, std::move(client));
110 client = nullptr;
111 }
112 return true;
113 }
114
search_count() const115 size_t ServiceDiscoverer::search_count() const { return searches_.size(); }
116
FinishPeerSearch(PeerId peer_id,SearchId search_id)117 void ServiceDiscoverer::FinishPeerSearch(PeerId peer_id, SearchId search_id) {
118 auto it = sessions_.find(peer_id);
119 if (it == sessions_.end()) {
120 bt_log(INFO,
121 "sdp",
122 "Couldn't find session to finish search for peer %s",
123 bt_str(peer_id));
124 return;
125 }
126 if (it->second.active.erase(search_id) && it->second.active.empty()) {
127 // This peer search is over.
128 sessions_.erase(it);
129 }
130 }
131
132 } // namespace bt::sdp
133