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/client.h"
16
17 #include <pw_assert/check.h>
18 #include <pw_bytes/endian.h>
19
20 #include <functional>
21 #include <optional>
22
23 namespace bt::sdp {
24
25 namespace {
26
27 // Increased after some particularly slow devices taking a long time for
28 // transactions with continuations.
29 constexpr pw::chrono::SystemClock::duration kTransactionTimeout =
30 std::chrono::seconds(10);
31
32 class Impl final : public Client {
33 public:
34 explicit Impl(l2cap::Channel::WeakPtr channel,
35 pw::async::Dispatcher& dispatcher);
36
37 ~Impl() override;
38
39 private:
40 void ServiceSearchAttributes(
41 std::unordered_set<UUID> search_pattern,
42 const std::unordered_set<AttributeId>& req_attributes,
43 SearchResultFunction result_cb) override;
44
45 // Information about a transaction that hasn't finished yet.
46 struct Transaction {
47 Transaction(TransactionId id,
48 ServiceSearchAttributeRequest req,
49 SearchResultFunction cb);
50 // The TransactionId used for this request. This will be reused until the
51 // transaction is complete.
52 TransactionId id;
53 // Request PDU for this transaction.
54 ServiceSearchAttributeRequest request;
55 // Callback for results.
56 SearchResultFunction callback;
57 // The response, built from responses from the remote server.
58 ServiceSearchAttributeResponse response;
59 };
60
61 // Callbacks for l2cap::Channel
62 void OnRxFrame(ByteBufferPtr data);
63 void OnChannelClosed();
64
65 // Finishes a pending transaction on this client, completing their callbacks.
66 void Finish(TransactionId id);
67
68 // Cancels a pending transaction this client has started, completing the
69 // callback with the given reason as an error.
70 void Cancel(TransactionId id, HostError reason);
71
72 // Cancels all remaining transactions without sending them, with the given
73 // reason as an error.
74 void CancelAll(HostError reason);
75
76 // Get the next available transaction id
77 TransactionId GetNextId();
78
79 // Try to send the next pending request, if possible.
80 void TrySendNextTransaction();
81
82 pw::async::Dispatcher& pw_dispatcher_;
83 // The channel that this client is running on.
84 l2cap::ScopedChannel channel_;
85 // THe next transaction id that we should use
86 TransactionId next_tid_ = 0;
87 // Any transactions that are not completed.
88 std::unordered_map<TransactionId, Transaction> pending_;
89 // Timeout for the current transaction. false if none are waiting for a
90 // response.
91 std::optional<SmartTask> pending_timeout_;
92
93 WeakSelf<Impl> weak_self_{this};
94
95 BT_DISALLOW_COPY_AND_ASSIGN_ALLOW_MOVE(Impl);
96 };
97
Impl(l2cap::Channel::WeakPtr channel,pw::async::Dispatcher & dispatcher)98 Impl::Impl(l2cap::Channel::WeakPtr channel, pw::async::Dispatcher& dispatcher)
99 : pw_dispatcher_(dispatcher), channel_(std::move(channel)) {
100 auto self = weak_self_.GetWeakPtr();
101 bool activated = channel_->Activate(
102 [self](auto packet) {
103 if (self.is_alive()) {
104 self->OnRxFrame(std::move(packet));
105 }
106 },
107 [self] {
108 if (self.is_alive()) {
109 self->OnChannelClosed();
110 }
111 });
112 if (!activated) {
113 bt_log(INFO, "sdp", "failed to activate channel");
114 channel_ = nullptr;
115 }
116 }
117
~Impl()118 Impl::~Impl() { CancelAll(HostError::kCanceled); }
119
CancelAll(HostError reason)120 void Impl::CancelAll(HostError reason) {
121 // Avoid using |this| in case callbacks destroy this object.
122 auto pending = std::move(pending_);
123 pending_.clear();
124 for (auto& it : pending) {
125 it.second.callback(ToResult(reason).take_error());
126 }
127 }
128
TrySendNextTransaction()129 void Impl::TrySendNextTransaction() {
130 if (pending_timeout_) {
131 // Waiting on a transaction to finish.
132 return;
133 }
134
135 if (!channel_) {
136 bt_log(INFO,
137 "sdp",
138 "Failed to send %zu requests: link closed",
139 pending_.size());
140 CancelAll(HostError::kLinkDisconnected);
141 return;
142 }
143
144 if (pending_.empty()) {
145 return;
146 }
147
148 auto& next = pending_.begin()->second;
149
150 if (!channel_->Send(next.request.GetPDU(next.id))) {
151 bt_log(INFO, "sdp", "Failed to send request: channel send failed");
152 Cancel(next.id, HostError::kFailed);
153 return;
154 }
155
156 auto& timeout = pending_timeout_.emplace(pw_dispatcher_);
157
158 // Timeouts are held in this so it is safe to use.
159 timeout.set_function(
160 [this, id = next.id](pw::async::Context /*ctx*/, pw::Status status) {
161 if (!status.ok()) {
162 return;
163 }
164 bt_log(WARN, "sdp", "Transaction %d timed out, removing!", id);
165 Cancel(id, HostError::kTimedOut);
166 });
167 timeout.PostAfter(kTransactionTimeout);
168 }
169
ServiceSearchAttributes(std::unordered_set<UUID> search_pattern,const std::unordered_set<AttributeId> & req_attributes,SearchResultFunction result_cb)170 void Impl::ServiceSearchAttributes(
171 std::unordered_set<UUID> search_pattern,
172 const std::unordered_set<AttributeId>& req_attributes,
173 SearchResultFunction result_cb) {
174 ServiceSearchAttributeRequest req;
175 req.set_search_pattern(std::move(search_pattern));
176 if (req_attributes.empty()) {
177 req.AddAttributeRange(0, 0xFFFF);
178 } else {
179 for (const auto& id : req_attributes) {
180 req.AddAttribute(id);
181 }
182 }
183 TransactionId next = GetNextId();
184
185 auto [iter, placed] =
186 pending_.try_emplace(next, next, std::move(req), std::move(result_cb));
187 PW_DCHECK(placed, "Should not have repeat transaction ID %u", next);
188
189 TrySendNextTransaction();
190 }
191
Finish(TransactionId id)192 void Impl::Finish(TransactionId id) {
193 auto node = pending_.extract(id);
194 PW_DCHECK(node);
195 auto& state = node.mapped();
196 pending_timeout_.reset();
197 if (!state.callback) {
198 return;
199 }
200 PW_DCHECK(state.response.complete(), "Finished without complete response");
201
202 auto self = weak_self_.GetWeakPtr();
203
204 size_t count = state.response.num_attribute_lists();
205 for (size_t idx = 0; idx <= count; idx++) {
206 if (idx == count) {
207 state.callback(fit::error(Error(HostError::kNotFound)));
208 break;
209 }
210 // |count| and |idx| are at most std::numeric_limits<uint32_t>::max() + 1,
211 // which is caught by the above if statement.
212 PW_DCHECK(idx <= std::numeric_limits<uint32_t>::max());
213 if (!state.callback(fit::ok(std::cref(
214 state.response.attributes(static_cast<uint32_t>(idx)))))) {
215 break;
216 }
217 }
218
219 // Callbacks may have destroyed this object.
220 if (!self.is_alive()) {
221 return;
222 }
223
224 TrySendNextTransaction();
225 }
226
Transaction(TransactionId id_in,ServiceSearchAttributeRequest req,SearchResultFunction cb)227 Impl::Transaction::Transaction(TransactionId id_in,
228 ServiceSearchAttributeRequest req,
229 SearchResultFunction cb)
230 : id(id_in), request(std::move(req)), callback(std::move(cb)) {}
231
Cancel(TransactionId id,HostError reason)232 void Impl::Cancel(TransactionId id, HostError reason) {
233 auto node = pending_.extract(id);
234 if (!node) {
235 return;
236 }
237
238 auto self = weak_self_.GetWeakPtr();
239 node.mapped().callback(ToResult(reason).take_error());
240 if (!self.is_alive()) {
241 return;
242 }
243
244 TrySendNextTransaction();
245 }
246
OnRxFrame(ByteBufferPtr data)247 void Impl::OnRxFrame(ByteBufferPtr data) {
248 TRACE_DURATION("bluetooth", "sdp::Client::Impl::OnRxFrame");
249 // Each SDU in SDP is one request or one response. Core 5.0 Vol 3 Part B, 4.2
250 PacketView<sdp::Header> packet(data.get());
251 size_t pkt_params_len = data->size() - sizeof(Header);
252 uint16_t params_len = pw::bytes::ConvertOrderFrom(
253 cpp20::endian::big, packet.header().param_length);
254 if (params_len != pkt_params_len) {
255 bt_log(INFO,
256 "sdp",
257 "bad params length (len %zu != %u), dropping",
258 pkt_params_len,
259 params_len);
260 return;
261 }
262 packet.Resize(params_len);
263 TransactionId tid =
264 pw::bytes::ConvertOrderFrom(cpp20::endian::big, packet.header().tid);
265 auto it = pending_.find(tid);
266 if (it == pending_.end()) {
267 bt_log(INFO, "sdp", "Received unknown transaction id (%u)", tid);
268 return;
269 }
270 auto& transaction = it->second;
271 fit::result<Error<>> parse_status =
272 transaction.response.Parse(packet.payload_data());
273 if (parse_status.is_error()) {
274 if (parse_status.error_value().is(HostError::kInProgress)) {
275 bt_log(INFO, "sdp", "Requesting continuation of id (%u)", tid);
276 transaction.request.SetContinuationState(
277 transaction.response.ContinuationState());
278 if (!channel_->Send(transaction.request.GetPDU(tid))) {
279 bt_log(INFO, "sdp", "Failed to send continuation of transaction!");
280 }
281 return;
282 }
283 bt_log(INFO,
284 "sdp",
285 "Failed to parse packet for tid %u: %s",
286 tid,
287 bt_str(parse_status));
288 // Drop the transaction with the error.
289 Cancel(tid, parse_status.error_value().host_error());
290 return;
291 }
292 if (transaction.response.complete()) {
293 bt_log(DEBUG, "sdp", "Rx complete, finishing tid %u", tid);
294 Finish(tid);
295 }
296 }
297
OnChannelClosed()298 void Impl::OnChannelClosed() {
299 bt_log(INFO, "sdp", "client channel closed");
300 channel_ = nullptr;
301 CancelAll(HostError::kLinkDisconnected);
302 }
303
GetNextId()304 TransactionId Impl::GetNextId() {
305 TransactionId next = next_tid_++;
306 PW_DCHECK(pending_.size() < std::numeric_limits<TransactionId>::max());
307 while (pending_.count(next)) {
308 next = next_tid_++; // Note: overflow is fine
309 }
310 return next;
311 }
312
313 } // namespace
314
Create(l2cap::Channel::WeakPtr channel,pw::async::Dispatcher & dispatcher)315 std::unique_ptr<Client> Client::Create(l2cap::Channel::WeakPtr channel,
316 pw::async::Dispatcher& dispatcher) {
317 PW_DCHECK(channel.is_alive());
318 return std::make_unique<Impl>(std::move(channel), dispatcher);
319 }
320
321 } // namespace bt::sdp
322