blob: ba71660a59424cde53b4043ae73ab9dd9a30b8e7 [file] [log] [blame]
// Copyright 2017 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 "bearer.h"
#include <lib/async/default.h>
#include "garnet/drivers/bluetooth/lib/common/slab_allocator.h"
#include "garnet/drivers/bluetooth/lib/l2cap/channel.h"
#include "lib/fxl/strings/string_printf.h"
namespace btlib {
namespace att {
// static
constexpr Bearer::HandlerId Bearer::kInvalidHandlerId;
constexpr Bearer::TransactionId Bearer::kInvalidTransactionId;
namespace {
MethodType GetMethodType(OpCode opcode) {
// We treat all packets as a command if the command bit was set. An
// unrecognized command will always be ignored (so it is OK to return kCommand
// here if, for example, |opcode| is a response with the command-bit set).
if (opcode & kCommandFlag)
return MethodType::kCommand;
switch (opcode) {
case kInvalidOpCode:
return MethodType::kInvalid;
case kExchangeMTURequest:
case kFindInformationRequest:
case kFindByTypeValueRequest:
case kReadByTypeRequest:
case kReadRequest:
case kReadBlobRequest:
case kReadMultipleRequest:
case kReadByGroupTypeRequest:
case kWriteRequest:
case kPrepareWriteRequest:
case kExecuteWriteRequest:
return MethodType::kRequest;
case kErrorResponse:
case kExchangeMTUResponse:
case kFindInformationResponse:
case kFindByTypeValueResponse:
case kReadByTypeResponse:
case kReadResponse:
case kReadBlobResponse:
case kReadMultipleResponse:
case kReadByGroupTypeResponse:
case kWriteResponse:
case kPrepareWriteResponse:
case kExecuteWriteResponse:
return MethodType::kResponse;
case kNotification:
return MethodType::kNotification;
case kIndication:
return MethodType::kIndication;
case kConfirmation:
return MethodType::kConfirmation;
// These are redundant with the check above but are included for
// completeness.
case kWriteCommand:
case kSignedWriteCommand:
return MethodType::kCommand;
default:
break;
}
// Everything else will be treated as an incoming request.
return MethodType::kRequest;
}
// Returns the corresponding originating transaction opcode for
// |transaction_end_code|, where the latter must correspond to a response or
// confirmation.
OpCode MatchingTransactionCode(OpCode transaction_end_code) {
switch (transaction_end_code) {
case kExchangeMTUResponse:
return kExchangeMTURequest;
case kFindInformationResponse:
return kFindInformationRequest;
case kFindByTypeValueResponse:
return kFindByTypeValueRequest;
case kReadByTypeResponse:
return kReadByTypeRequest;
case kReadResponse:
return kReadRequest;
case kReadBlobResponse:
return kReadBlobRequest;
case kReadMultipleResponse:
return kReadMultipleRequest;
case kReadByGroupTypeResponse:
return kReadByGroupTypeRequest;
case kWriteResponse:
return kWriteRequest;
case kPrepareWriteResponse:
return kPrepareWriteRequest;
case kExecuteWriteResponse:
return kExecuteWriteRequest;
case kConfirmation:
return kIndication;
default:
break;
}
return kInvalidOpCode;
}
} // namespace
// static
fxl::RefPtr<Bearer> Bearer::Create(fbl::RefPtr<l2cap::Channel> chan) {
auto bearer = fxl::AdoptRef(new Bearer(std::move(chan)));
return bearer->Activate() ? bearer : nullptr;
}
Bearer::PendingTransaction::PendingTransaction(OpCode opcode,
TransactionCallback callback,
ErrorCallback error_callback,
common::ByteBufferPtr pdu)
: opcode(opcode),
callback(std::move(callback)),
error_callback(std::move(error_callback)),
pdu(std::move(pdu)) {
FXL_DCHECK(this->callback);
FXL_DCHECK(this->error_callback);
FXL_DCHECK(this->pdu);
}
Bearer::PendingRemoteTransaction::PendingRemoteTransaction(TransactionId id,
OpCode opcode)
: id(id), opcode(opcode) {}
Bearer::TransactionQueue::TransactionQueue(TransactionQueue&& other)
: queue_(std::move(other.queue_)), current_(std::move(other.current_)) {
// The move constructor is only used during shut down below. So we simply
// cancel the task and not worry about moving it.
other.timeout_task_.Cancel();
}
Bearer::PendingTransactionPtr Bearer::TransactionQueue::ClearCurrent() {
FXL_DCHECK(current_);
FXL_DCHECK(timeout_task_.is_pending());
timeout_task_.Cancel();
return std::move(current_);
}
void Bearer::TransactionQueue::Enqueue(PendingTransactionPtr transaction) {
queue_.push_back(std::move(transaction));
}
void Bearer::TransactionQueue::TrySendNext(l2cap::Channel* chan,
async::Task::Handler timeout_cb,
uint32_t timeout_ms) {
FXL_DCHECK(chan);
// Abort if a transaction is currently pending.
if (current())
return;
// Advance to the next transaction.
current_ = queue_.pop_front();
if (current()) {
FXL_DCHECK(!timeout_task_.is_pending());
timeout_task_.set_handler(std::move(timeout_cb));
timeout_task_.PostDelayed(async_get_default_dispatcher(), zx::msec(timeout_ms));
chan->Send(std::move(current()->pdu));
}
}
void Bearer::TransactionQueue::Reset() {
timeout_task_.Cancel();
queue_.clear();
current_ = nullptr;
}
void Bearer::TransactionQueue::InvokeErrorAll(Status status) {
if (current_) {
current_->error_callback(status, kInvalidHandle);
}
for (const auto& t : queue_) {
if (t.error_callback)
t.error_callback(status, kInvalidHandle);
}
}
Bearer::Bearer(fbl::RefPtr<l2cap::Channel> chan)
: chan_(std::move(chan)),
next_remote_transaction_id_(1u),
next_handler_id_(1u) {
FXL_DCHECK(chan_);
if (chan_->link_type() == hci::Connection::LinkType::kLE) {
min_mtu_ = kLEMinMTU;
} else {
min_mtu_ = kBREDRMinMTU;
}
mtu_ = min_mtu();
preferred_mtu_ =
std::max(min_mtu(), std::min(chan_->tx_mtu(), chan_->rx_mtu()));
}
Bearer::~Bearer() {
FXL_DCHECK(thread_checker_.IsCreationThreadCurrent());
rx_task_.Cancel();
chan_ = nullptr;
request_queue_.Reset();
indication_queue_.Reset();
}
bool Bearer::Activate() {
FXL_DCHECK(thread_checker_.IsCreationThreadCurrent());
rx_task_.Reset(fit::bind_member(this, &Bearer::OnRxBFrame));
chan_closed_cb_.Reset(fit::bind_member(this, &Bearer::OnChannelClosed));
return chan_->Activate(rx_task_.callback(), chan_closed_cb_.callback(),
async_get_default_dispatcher());
}
void Bearer::ShutDown() {
if (is_open())
ShutDownInternal(false /* due_to_timeout */);
}
void Bearer::ShutDownInternal(bool due_to_timeout) {
FXL_DCHECK(is_open());
FXL_DCHECK(thread_checker_.IsCreationThreadCurrent());
FXL_VLOG(1) << "att: Bearer shutting down";
rx_task_.Cancel();
chan_closed_cb_.Cancel();
// This will have no effect if the channel is already closed (e.g. if
// ShutDown() was called by OnChannelClosed()).
chan_->SignalLinkError();
chan_ = nullptr;
// Move the contents to temporaries. This prevents a potential memory
// corruption in InvokeErrorAll if the Bearer gets deleted by one of the
// invoked error callbacks.
TransactionQueue req_queue(std::move(request_queue_));
TransactionQueue ind_queue(std::move(indication_queue_));
if (closed_cb_)
closed_cb_();
// Terminate all remaining procedures with an error. This is safe even if
// the bearer got deleted by |closed_cb_|.
Status status(due_to_timeout ? common::HostError::kTimedOut
: common::HostError::kFailed);
req_queue.InvokeErrorAll(status);
ind_queue.InvokeErrorAll(status);
}
bool Bearer::StartTransaction(common::ByteBufferPtr pdu,
TransactionCallback callback,
ErrorCallback error_callback) {
FXL_DCHECK(pdu);
FXL_DCHECK(callback);
FXL_DCHECK(error_callback);
return SendInternal(std::move(pdu), std::move(callback), std::move(error_callback));
}
bool Bearer::SendWithoutResponse(common::ByteBufferPtr pdu) {
FXL_DCHECK(pdu);
return SendInternal(std::move(pdu), {}, {});
}
bool Bearer::SendInternal(common::ByteBufferPtr pdu,
TransactionCallback callback,
ErrorCallback error_callback) {
FXL_DCHECK(thread_checker_.IsCreationThreadCurrent());
if (!is_open()) {
FXL_VLOG(2) << "att: Bearer closed";
return false;
}
if (!IsPacketValid(*pdu)) {
FXL_VLOG(1) << "att: Packet has bad length!";
return false;
}
PacketReader reader(pdu.get());
MethodType type = GetMethodType(reader.opcode());
TransactionQueue* tq = nullptr;
switch (type) {
case MethodType::kCommand:
case MethodType::kNotification:
if (callback || error_callback) {
FXL_VLOG(1) << "att: Method not a transaction";
return false;
}
// Send the command. No flow control is necessary.
chan_->Send(std::move(pdu));
return true;
case MethodType::kRequest:
tq = &request_queue_;
break;
case MethodType::kIndication:
tq = &indication_queue_;
break;
default:
FXL_VLOG(1) << "att: invalid opcode: "
<< static_cast<unsigned>(reader.opcode());
return false;
}
if (!callback || !error_callback) {
FXL_VLOG(1) << "att: Transaction requires callbacks";
return false;
}
tq->Enqueue(std::make_unique<PendingTransaction>(
reader.opcode(), std::move(callback), std::move(error_callback), std::move(pdu)));
TryStartNextTransaction(tq);
return true;
}
Bearer::HandlerId Bearer::RegisterHandler(OpCode opcode,
Handler handler) {
FXL_DCHECK(handler);
if (!is_open())
return kInvalidHandlerId;
if (handlers_.find(opcode) != handlers_.end()) {
FXL_VLOG(1) << fxl::StringPrintf(
"att: Can only register one Handler per opcode (0x%02x)", opcode);
return kInvalidHandlerId;
}
HandlerId id = NextHandlerId();
if (id == kInvalidHandlerId)
return kInvalidHandlerId;
auto res = handler_id_map_.emplace(id, opcode);
FXL_CHECK(res.second) << "att: Handler ID got reused (id: " << id << ")";
handlers_[opcode] = std::move(handler);
return id;
}
void Bearer::UnregisterHandler(HandlerId id) {
FXL_DCHECK(thread_checker_.IsCreationThreadCurrent());
FXL_DCHECK(id != kInvalidHandlerId);
auto iter = handler_id_map_.find(id);
if (iter == handler_id_map_.end()) {
FXL_VLOG(1) << "att: Cannot unregister unknown handler id: " << id;
return;
}
OpCode opcode = iter->second;
handlers_.erase(opcode);
}
bool Bearer::Reply(TransactionId tid, common::ByteBufferPtr pdu) {
FXL_DCHECK(thread_checker_.IsCreationThreadCurrent());
FXL_DCHECK(pdu);
if (tid == kInvalidTransactionId)
return false;
if (!is_open()) {
FXL_VLOG(2) << "att: Bearer closed";
return false;
}
if (!IsPacketValid(*pdu)) {
FXL_VLOG(1) << "att: Invalid response PDU";
return false;
}
RemoteTransaction* pending = FindRemoteTransaction(tid);
if (!pending)
return false;
PacketReader reader(pdu.get());
// Use ReplyWithError() instead.
if (reader.opcode() == kErrorResponse)
return false;
OpCode pending_opcode = (*pending)->opcode;
if (pending_opcode != MatchingTransactionCode(reader.opcode())) {
FXL_VLOG(1) << fxl::StringPrintf(
"att: opcode does not match pending (pending: 0x%02x, given: 0x%02x)",
pending_opcode, reader.opcode());
return false;
}
pending->Reset();
chan_->Send(std::move(pdu));
return true;
}
bool Bearer::ReplyWithError(TransactionId id,
Handle handle,
ErrorCode error_code) {
FXL_DCHECK(thread_checker_.IsCreationThreadCurrent());
RemoteTransaction* pending = FindRemoteTransaction(id);
if (!pending)
return false;
OpCode pending_opcode = (*pending)->opcode;
if (pending_opcode == kIndication) {
FXL_VLOG(1) << "att: Cannot respond to an indication with error!";
return false;
}
pending->Reset();
SendErrorResponse(pending_opcode, handle, error_code);
return true;
}
bool Bearer::IsPacketValid(const common::ByteBuffer& packet) {
return packet.size() != 0u && packet.size() <= mtu_;
}
void Bearer::TryStartNextTransaction(TransactionQueue* tq) {
FXL_DCHECK(is_open());
FXL_DCHECK(tq);
tq->TrySendNext(chan_.get(),
[this](async_dispatcher_t*, async::Task*, zx_status_t status) {
if (status == ZX_OK)
ShutDownInternal(true /* due_to_timeout */);
},
kTransactionTimeoutMs);
}
void Bearer::SendErrorResponse(OpCode request_opcode,
Handle attribute_handle,
ErrorCode error_code) {
FXL_DCHECK(thread_checker_.IsCreationThreadCurrent());
auto buffer =
common::NewSlabBuffer(sizeof(Header) + sizeof(ErrorResponseParams));
FXL_CHECK(buffer);
PacketWriter packet(kErrorResponse, buffer.get());
auto* payload = packet.mutable_payload<ErrorResponseParams>();
payload->request_opcode = request_opcode;
payload->attribute_handle = htole16(attribute_handle);
payload->error_code = error_code;
chan_->Send(std::move(buffer));
}
void Bearer::HandleEndTransaction(TransactionQueue* tq,
const PacketReader& packet) {
FXL_DCHECK(thread_checker_.IsCreationThreadCurrent());
FXL_DCHECK(is_open());
FXL_DCHECK(tq);
if (!tq->current()) {
FXL_VLOG(1) << fxl::StringPrintf(
"att: received unexpected transaction PDU (opcode: 0x%02x)",
packet.opcode());
ShutDown();
return;
}
bool report_error = false;
OpCode target_opcode;
ErrorCode error_code = ErrorCode::kNoError;
Handle attr_in_error = kInvalidHandle;
if (packet.opcode() == kErrorResponse) {
// We should never hit this branch for indications.
FXL_DCHECK(tq->current()->opcode != kIndication);
if (packet.payload_size() == sizeof(ErrorResponseParams)) {
report_error = true;
const auto& payload = packet.payload<ErrorResponseParams>();
target_opcode = payload.request_opcode;
error_code = payload.error_code;
attr_in_error = le16toh(payload.attribute_handle);
} else {
FXL_VLOG(2) << "att: Received malformed error response";
// Invalid opcode will fail the opcode comparison below.
target_opcode = kInvalidOpCode;
}
} else {
target_opcode = MatchingTransactionCode(packet.opcode());
}
FXL_DCHECK(tq->current()->opcode != kInvalidOpCode);
if (tq->current()->opcode != target_opcode) {
FXL_VLOG(1) << fxl::StringPrintf(
"att: Received bad transaction PDU (opcode: 0x%02x)", packet.opcode());
ShutDown();
return;
}
// The transaction is complete. Send out the next queued transaction and
// notify the callback.
auto transaction = tq->ClearCurrent();
FXL_DCHECK(transaction);
TryStartNextTransaction(tq);
if (!report_error)
transaction->callback(packet);
else if (transaction->error_callback)
transaction->error_callback(Status(error_code), attr_in_error);
}
Bearer::HandlerId Bearer::NextHandlerId() {
auto id = next_handler_id_;
// This will stop incrementing if this were overflows and always return
// kInvalidHandlerId.
if (next_handler_id_ != kInvalidHandlerId)
next_handler_id_++;
return id;
}
Bearer::TransactionId Bearer::NextRemoteTransactionId() {
auto id = next_remote_transaction_id_;
next_remote_transaction_id_++;
// Increment extra in the case of overflow.
if (next_remote_transaction_id_ == kInvalidTransactionId)
next_remote_transaction_id_++;
return id;
}
void Bearer::HandleBeginTransaction(RemoteTransaction* currently_pending,
const PacketReader& packet) {
FXL_DCHECK(thread_checker_.IsCreationThreadCurrent());
FXL_DCHECK(currently_pending);
if (currently_pending->HasValue()) {
FXL_VLOG(1) << fxl::StringPrintf(
"att: A transaction is already pending! (opcode: 0x%02x)",
packet.opcode());
ShutDown();
return;
}
auto iter = handlers_.find(packet.opcode());
if (iter == handlers_.end()) {
FXL_VLOG(1) << fxl::StringPrintf(
"att: No handler registered for opcode 0x%02x", packet.opcode());
SendErrorResponse(packet.opcode(), 0, ErrorCode::kRequestNotSupported);
return;
}
auto id = NextRemoteTransactionId();
*currently_pending = PendingRemoteTransaction(id, packet.opcode());
iter->second(id, packet);
}
Bearer::RemoteTransaction* Bearer::FindRemoteTransaction(TransactionId id) {
FXL_DCHECK(thread_checker_.IsCreationThreadCurrent());
if (remote_request_ && remote_request_->id == id) {
return &remote_request_;
}
if (remote_indication_ && remote_indication_->id == id) {
return &remote_indication_;
}
FXL_VLOG(1) << "att: id " << id << " does not match any transaction";
return nullptr;
}
void Bearer::HandlePDUWithoutResponse(const PacketReader& packet) {
FXL_DCHECK(thread_checker_.IsCreationThreadCurrent());
auto iter = handlers_.find(packet.opcode());
if (iter == handlers_.end()) {
FXL_VLOG(1) << fxl::StringPrintf(
"att: Dropping unhandled packet (opcode: 0x%02x)", packet.opcode());
return;
}
iter->second(kInvalidTransactionId, packet);
}
void Bearer::OnChannelClosed() {
FXL_DCHECK(thread_checker_.IsCreationThreadCurrent());
// This will deactivate the channel and notify |closed_cb_|.
ShutDown();
}
void Bearer::OnRxBFrame(const l2cap::SDU& sdu) {
FXL_DCHECK(is_open());
FXL_DCHECK(thread_checker_.IsCreationThreadCurrent());
uint16_t length = sdu.length();
// An ATT PDU should at least contain the opcode.
if (length < sizeof(OpCode)) {
FXL_VLOG(1) << "att: PDU too short!";
ShutDown();
return;
}
if (length > mtu_) {
FXL_VLOG(1) << "att: PDU exceeds MTU!";
ShutDown();
return;
}
// The following will read the entire ATT PDU in a single call.
l2cap::SDU::Reader reader(&sdu);
reader.ReadNext(length, [this, length](const common::ByteBuffer& att_pdu) {
FXL_CHECK(att_pdu.size() == length);
PacketReader packet(&att_pdu);
MethodType type = GetMethodType(packet.opcode());
switch (type) {
case MethodType::kResponse:
HandleEndTransaction(&request_queue_, packet);
break;
case MethodType::kConfirmation:
HandleEndTransaction(&indication_queue_, packet);
break;
case MethodType::kRequest:
HandleBeginTransaction(&remote_request_, packet);
break;
case MethodType::kIndication:
HandleBeginTransaction(&remote_indication_, packet);
break;
case MethodType::kNotification:
case MethodType::kCommand:
HandlePDUWithoutResponse(packet);
break;
default:
FXL_VLOG(2) << fxl::StringPrintf("att: Unsupported opcode: 0x%02x",
packet.opcode());
SendErrorResponse(packet.opcode(), 0, ErrorCode::kRequestNotSupported);
break;
}
});
}
} // namespace att
} // namespace btlib