blob: eee669629e2abe1edb6e6a690b0c4d8dd7f7245f [file] [log] [blame]
// Copyright 2019 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "client.h"
#include <lib/async/default.h>
#include <optional>
namespace bt::sdp {
namespace {
// Increased after some particularly slow devices taking a long time for transactions with
// continuations.
constexpr zx::duration kTransactionTimeout = zx::sec(10);
class Impl final : public Client {
public:
explicit Impl(fbl::RefPtr<l2cap::Channel> channel);
virtual ~Impl() override;
private:
void ServiceSearchAttributes(std::unordered_set<UUID> search_pattern,
const std::unordered_set<AttributeId>& req_attributes,
SearchResultCallback result_cb,
async_dispatcher_t* cb_dispatcher) override;
// Information about a transaction that hasn't finished yet.
struct Transaction {
Transaction(TransactionId id, ServiceSearchAttributeRequest req, SearchResultCallback cb,
async_dispatcher_t* disp);
// The TransactionId used for this request. This will be reused until the
// transaction is complete.
TransactionId id;
// Request PDU for this transaction.
ServiceSearchAttributeRequest request;
// Callback for results.
SearchResultCallback callback;
// The dispatcher that |callback| is executed on.
async_dispatcher_t* dispatcher;
// The response, built from responses from the remote server.
ServiceSearchAttributeResponse response;
};
// Callbacks for l2cap::Channel
void OnRxFrame(ByteBufferPtr sdu);
void OnChannelClosed();
// Finishes a pending transaction on this client, completing their callbacks.
void Finish(TransactionId id);
// Cancels a pending transaction this client has started, completing the
// callback with the given status.
void Cancel(TransactionId id, Status status);
// Cancels all remaining transactions without sending them, with the given
// status.
void CancelAll(Status status);
// Get the next available transaction id
TransactionId GetNextId();
// Try to send the next pending request, if possible.
void TrySendNextTransaction();
// The channel that this client is running on.
l2cap::ScopedChannel channel_;
// THe next transaction id that we should use
TransactionId next_tid_;
// Any transactions that are not completed.
std::unordered_map<TransactionId, Transaction> pending_;
// Timeout for the current transaction. false if none are waiting for a response.
std::optional<async::TaskClosure> pending_timeout_;
fxl::WeakPtrFactory<Impl> weak_ptr_factory_;
DISALLOW_COPY_AND_ASSIGN_ALLOW_MOVE(Impl);
};
Impl::Impl(fbl::RefPtr<l2cap::Channel> channel)
: channel_(std::move(channel)), next_tid_(0), weak_ptr_factory_(this) {
auto self = weak_ptr_factory_.GetWeakPtr();
bool activated = channel_->Activate(
[self](auto packet) {
if (self) {
self->OnRxFrame(std::move(packet));
}
},
[self] {
if (self) {
self->OnChannelClosed();
}
});
if (!activated) {
bt_log(INFO, "sdp", "failed to activate channel");
channel_ = nullptr;
}
}
Impl::~Impl() { CancelAll(Status(HostError::kCanceled)); }
void Impl::CancelAll(Status status) {
while (!pending_.empty()) {
Cancel(pending_.begin()->first, status);
}
}
void Impl::TrySendNextTransaction() {
if (pending_timeout_) {
// Waiting on a transaction to finish.
return;
}
if (!channel_) {
bt_log(INFO, "sdp", "Failed to send %zu requests: link closed", pending_.size());
CancelAll(Status(HostError::kLinkDisconnected));
}
if (pending_.empty()) {
return;
}
auto& next = pending_.begin()->second;
if (!channel_->Send(next.request.GetPDU(next.id))) {
bt_log(INFO, "sdp", "Failed to send request: channel send failed");
Cancel(next.id, Status(HostError::kFailed));
return;
}
auto& timeout = pending_timeout_.emplace();
// Timeouts are held in this so it is safe to use.
timeout.set_handler([this, id = next.id]() {
bt_log(WARN, "sdp", "Transaction %d timed out, removing!", id);
Cancel(id, Status(HostError::kTimedOut));
});
timeout.PostDelayed(async_get_default_dispatcher(), kTransactionTimeout);
}
void Impl::ServiceSearchAttributes(std::unordered_set<UUID> search_pattern,
const std::unordered_set<AttributeId>& req_attributes,
SearchResultCallback result_cb,
async_dispatcher_t* cb_dispatcher) {
ServiceSearchAttributeRequest req;
req.set_search_pattern(std::move(search_pattern));
if (req_attributes.empty()) {
req.AddAttributeRange(0, 0xFFFF);
} else {
for (const auto& id : req_attributes) {
req.AddAttribute(id);
}
}
TransactionId next = GetNextId();
auto [iter, placed] =
pending_.try_emplace(next, next, std::move(req), std::move(result_cb), cb_dispatcher);
ZX_DEBUG_ASSERT_MSG(placed, "Should not have repeat transaction ID %u", next);
TrySendNextTransaction();
}
void Impl::Finish(TransactionId id) {
auto node = pending_.extract(id);
ZX_DEBUG_ASSERT(node);
auto& state = node.mapped();
pending_timeout_.reset();
if (!state.callback) {
return;
}
ZX_DEBUG_ASSERT_MSG(state.response.complete(), "Finished without complete response");
async::PostTask(state.dispatcher,
[cb = std::move(state.callback), response = std::move(state.response)] {
size_t count = response.num_attribute_lists();
for (size_t idx = 0; idx < count; idx++) {
if (!cb(Status(), response.attributes(idx))) {
return;
}
}
cb(Status(HostError::kNotFound), {});
});
TrySendNextTransaction();
}
Impl::Transaction::Transaction(TransactionId id, ServiceSearchAttributeRequest req,
SearchResultCallback cb, async_dispatcher_t* disp)
: id(id), request(std::move(req)), callback(std::move(cb)), dispatcher(disp) {}
void Impl::Cancel(TransactionId id, Status status) {
auto node = pending_.extract(id);
if (!node) {
return;
}
async::PostTask(node.mapped().dispatcher, [callback = std::move(node.mapped().callback),
status = std::move(status)] { callback(status, {}); });
TrySendNextTransaction();
}
void Impl::OnRxFrame(ByteBufferPtr data) {
TRACE_DURATION("bluetooth", "sdp::Client::Impl::OnRxFrame");
// Each SDU in SDP is one request or one response. Core 5.0 Vol 3 Part B, 4.2
PacketView<sdp::Header> packet(data.get());
size_t pkt_params_len = data->size() - sizeof(Header);
uint16_t params_len = betoh16(packet.header().param_length);
if (params_len != pkt_params_len) {
bt_log(INFO, "sdp", "bad params length (len %zu != %u), dropping", pkt_params_len, params_len);
return;
}
packet.Resize(params_len);
TransactionId tid = betoh16(packet.header().tid);
auto it = pending_.find(tid);
if (it == pending_.end()) {
bt_log(INFO, "sdp", "Received unknown transaction id (%u)", tid);
return;
}
auto& transaction = it->second;
Status parse_status = transaction.response.Parse(packet.payload_data());
if (!parse_status) {
if (parse_status.error() == HostError::kInProgress) {
bt_log(INFO, "sdp", "Requesting continuation of id (%u)", tid);
transaction.request.SetContinuationState(transaction.response.ContinuationState());
if (!channel_->Send(transaction.request.GetPDU(tid))) {
bt_log(INFO, "sdp", "Failed to send continuation of transaction!");
}
return;
}
bt_log(INFO, "sdp", "Failed to parse packet for tid %u: %s", tid,
parse_status.ToString().c_str());
// Drop the transaction with the error.
Cancel(tid, parse_status);
return;
}
if (transaction.response.complete()) {
bt_log(DEBUG, "sdp", "Rx complete, finishing tid %u", tid);
Finish(tid);
}
}
void Impl::OnChannelClosed() {
bt_log(INFO, "sdp", "client channel closed");
channel_ = nullptr;
while (!pending_.empty()) {
Cancel(pending_.begin()->first, Status(HostError::kLinkDisconnected));
}
}
TransactionId Impl::GetNextId() {
TransactionId next = next_tid_++;
ZX_DEBUG_ASSERT(pending_.size() < std::numeric_limits<TransactionId>::max());
while (pending_.count(next)) {
next = next_tid_++; // Note: overflow is fine
}
return next;
}
} // namespace
std::unique_ptr<Client> Client::Create(fbl::RefPtr<l2cap::Channel> channel) {
ZX_DEBUG_ASSERT(channel);
return std::make_unique<Impl>(std::move(channel));
}
} // namespace bt::sdp