• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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