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