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