xref: /aosp_15_r20/external/pigweed/pw_bluetooth_sapphire/host/sdp/client.cc (revision 61c4878ac05f98d0ceed94b57d316916de578985)
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