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