• 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 <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