blob: d73d0d8befb0f8678b2f743370f458a90a66e836 [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/log.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 {
using common::HostError;
using common::NewSlabBuffer;
// static
constexpr Bearer::HandlerId Bearer::kInvalidHandlerId;
constexpr Bearer::TransactionId Bearer::kInvalidTransactionId;
namespace {
// Returns the security level that is required to resolve the given ATT error
// code and the current security properties of the link, according to the table
// in v5.0, Vol 3, Part C, 10.3.2 (table 10.2). A security upgrade is not
// required if the returned value equals sm::SecurityLevel::kNoSecurity.
// TODO(armansito): Supporting requesting Secure Connections in addition to the
// encrypted/MITM dimensions.
sm::SecurityLevel CheckSecurity(ErrorCode ecode,
const sm::SecurityProperties& security) {
bool encrypted = (security.level() != sm::SecurityLevel::kNoSecurity);
switch (ecode) {
// "Insufficient Encryption" error code is specified for cases when the peer
// is paired (i.e. a LTK or STK exists for it) but the link is not
// encrypted. We treat this as equivalent to "Insufficient Authentication"
// sent on an unencrypted link.
case ErrorCode::kInsufficientEncryption:
encrypted = false;
[[fallthrough]];
// We achieve authorization by pairing which requires a confirmation from
// the host's pairing delegate.
// TODO(armansito): Allow for this to be satisfied with a simple user
// confirmation if we're not paired?
case ErrorCode::kInsufficientAuthorization:
case ErrorCode::kInsufficientAuthentication:
// If the link is already authenticated we cannot request a further
// upgrade.
// TODO(armansito): Take into account "secure connections" once it's
// supported.
if (security.authenticated()) {
return sm::SecurityLevel::kNoSecurity;
}
return encrypted ? sm::SecurityLevel::kAuthenticated
: sm::SecurityLevel::kEncrypted;
// Our SMP implementation always claims to support the maximum encryption
// key size. If the key size is too small then the peer must support a
// smaller size and we cannot upgrade the key.
case ErrorCode::kInsufficientEncryptionKeySize:
break;
default:
break;
}
return sm::SecurityLevel::kNoSecurity;
}
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)),
security_retry_level(sm::SecurityLevel::kNoSecurity) {
ZX_DEBUG_ASSERT(this->callback);
ZX_DEBUG_ASSERT(this->error_callback);
ZX_DEBUG_ASSERT(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() {
ZX_DEBUG_ASSERT(current_);
ZX_DEBUG_ASSERT(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) {
ZX_DEBUG_ASSERT(chan);
// Abort if a transaction is currently pending.
if (current())
return;
// Advance to the next transaction.
current_ = queue_.pop_front();
while (current()) {
ZX_DEBUG_ASSERT(!timeout_task_.is_pending());
ZX_DEBUG_ASSERT(current()->pdu);
// We copy the PDU payload in case it needs to be retried following a
// security upgrade.
auto pdu = NewSlabBuffer(current()->pdu->size());
if (pdu) {
current()->pdu->Copy(pdu.get());
timeout_task_.set_handler(std::move(timeout_cb));
timeout_task_.PostDelayed(async_get_default_dispatcher(),
zx::msec(timeout_ms));
chan->Send(std::move(pdu));
break;
}
bt_log(SPEW, "att", "Failed to start transaction: out of memory!");
auto t = std::move(current_);
t->error_callback(Status(HostError::kOutOfMemory), kInvalidHandle);
// Process the next command until we can send OR we have drained the queue.
current_ = queue_.pop_front();
}
}
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),
weak_ptr_factory_(this) {
ZX_DEBUG_ASSERT(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() {
ZX_DEBUG_ASSERT(thread_checker_.IsCreationThreadCurrent());
rx_task_.Cancel();
chan_ = nullptr;
request_queue_.Reset();
indication_queue_.Reset();
}
bool Bearer::Activate() {
ZX_DEBUG_ASSERT(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) {
ZX_DEBUG_ASSERT(is_open());
ZX_DEBUG_ASSERT(thread_checker_.IsCreationThreadCurrent());
bt_log(TRACE, "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 ? HostError::kTimedOut : HostError::kFailed);
req_queue.InvokeErrorAll(status);
ind_queue.InvokeErrorAll(status);
}
bool Bearer::StartTransaction(common::ByteBufferPtr pdu,
TransactionCallback callback,
ErrorCallback error_callback) {
ZX_DEBUG_ASSERT(pdu);
ZX_DEBUG_ASSERT(callback);
ZX_DEBUG_ASSERT(error_callback);
return SendInternal(std::move(pdu), std::move(callback),
std::move(error_callback));
}
bool Bearer::SendWithoutResponse(common::ByteBufferPtr pdu) {
ZX_DEBUG_ASSERT(pdu);
return SendInternal(std::move(pdu), {}, {});
}
bool Bearer::SendInternal(common::ByteBufferPtr pdu,
TransactionCallback callback,
ErrorCallback error_callback) {
ZX_DEBUG_ASSERT(thread_checker_.IsCreationThreadCurrent());
if (!is_open()) {
bt_log(SPEW, "att", "bearer closed; cannot send packet");
return false;
}
if (!IsPacketValid(*pdu)) {
bt_log(TRACE, "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) {
bt_log(TRACE, "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:
bt_log(TRACE, "att", "invalid opcode: %#.2x", reader.opcode());
return false;
}
if (!callback || !error_callback) {
bt_log(TRACE, "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) {
ZX_DEBUG_ASSERT(handler);
if (!is_open())
return kInvalidHandlerId;
if (handlers_.find(opcode) != handlers_.end()) {
bt_log(TRACE, "att", "can only register one handler per opcode (%#.2x)",
opcode);
return kInvalidHandlerId;
}
HandlerId id = NextHandlerId();
if (id == kInvalidHandlerId)
return kInvalidHandlerId;
auto res = handler_id_map_.emplace(id, opcode);
ZX_ASSERT_MSG(res.second, "handler ID got reused (id: %zu)", id);
handlers_[opcode] = std::move(handler);
return id;
}
void Bearer::UnregisterHandler(HandlerId id) {
ZX_DEBUG_ASSERT(thread_checker_.IsCreationThreadCurrent());
ZX_DEBUG_ASSERT(id != kInvalidHandlerId);
auto iter = handler_id_map_.find(id);
if (iter == handler_id_map_.end()) {
bt_log(TRACE, "att", "cannot unregister unknown handler id: %u", id);
return;
}
OpCode opcode = iter->second;
handlers_.erase(opcode);
}
bool Bearer::Reply(TransactionId tid, common::ByteBufferPtr pdu) {
ZX_DEBUG_ASSERT(thread_checker_.IsCreationThreadCurrent());
ZX_DEBUG_ASSERT(pdu);
if (tid == kInvalidTransactionId)
return false;
if (!is_open()) {
bt_log(SPEW, "att", "bearer closed; cannot reply");
return false;
}
if (!IsPacketValid(*pdu)) {
bt_log(TRACE, "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())) {
bt_log(TRACE, "att", "opcodes do not match (pending: %#.2x, given: %#.2x)",
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) {
ZX_DEBUG_ASSERT(thread_checker_.IsCreationThreadCurrent());
RemoteTransaction* pending = FindRemoteTransaction(id);
if (!pending)
return false;
OpCode pending_opcode = (*pending)->opcode;
if (pending_opcode == kIndication) {
bt_log(TRACE, "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) {
ZX_DEBUG_ASSERT(tq);
if (!is_open()) {
bt_log(SPEW, "att", "Cannot process transactions; bearer is closed");
return;
}
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) {
ZX_DEBUG_ASSERT(thread_checker_.IsCreationThreadCurrent());
auto buffer = NewSlabBuffer(sizeof(Header) + sizeof(ErrorResponseParams));
ZX_ASSERT(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) {
ZX_DEBUG_ASSERT(thread_checker_.IsCreationThreadCurrent());
ZX_DEBUG_ASSERT(is_open());
ZX_DEBUG_ASSERT(tq);
if (!tq->current()) {
bt_log(TRACE, "att", "received unexpected transaction PDU (opcode: %#.2x)",
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.
ZX_DEBUG_ASSERT(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 {
bt_log(TRACE, "att", "received malformed error response");
// Invalid opcode will fail the opcode comparison below.
target_opcode = kInvalidOpCode;
}
} else {
target_opcode = MatchingTransactionCode(packet.opcode());
}
ZX_DEBUG_ASSERT(tq->current()->opcode != kInvalidOpCode);
if (tq->current()->opcode != target_opcode) {
bt_log(TRACE, "att", "received bad transaction PDU (opcode: %#.2x)",
packet.opcode());
ShutDown();
return;
}
// The transaction is complete.
auto transaction = tq->ClearCurrent();
ZX_DEBUG_ASSERT(transaction);
sm::SecurityLevel security_requirement =
CheckSecurity(error_code, chan_->security());
if (transaction->security_retry_level >= security_requirement ||
security_requirement <= chan_->security().level()) {
// Resolve the transaction.
if (!report_error) {
transaction->callback(packet);
} else if (transaction->error_callback) {
transaction->error_callback(Status(error_code), attr_in_error);
}
// Send out the next queued transaction
TryStartNextTransaction(tq);
return;
}
bt_log(SPEW, "att",
"Received security error for transaction %#.2x; requesting upgrade to "
"level: %s",
error_code, sm::LevelToString(security_requirement));
chan_->UpgradeSecurity(
security_requirement,
[self = weak_ptr_factory_.GetWeakPtr(), error_code, attr_in_error,
security_requirement,
t = std::move(transaction)](sm::Status status) mutable {
// If the security upgrade failed or the bearer got destroyed, then
// resolve the transaction with the original error.
if (!self || !status) {
t->error_callback(Status(error_code), attr_in_error);
return;
}
ZX_DEBUG_ASSERT(self->thread_checker_.IsCreationThreadCurrent());
// TODO(armansito): Notify the upper layer to re-initiate service
// discovery and other necessary procedures (see Vol 3, Part C,
// 10.3.2).
// Re-send the request as described in Vol 3, Part G, 8.1. Since |t| was
// originally resolved with an Error Response, it must have come out of
// |request_queue_|.
ZX_DEBUG_ASSERT(GetMethodType(t->opcode) == MethodType::kRequest);
t->security_retry_level = security_requirement;
self->request_queue_.Enqueue(std::move(t));
self->TryStartNextTransaction(&self->request_queue_);
});
// Move on to the next queued transaction.
TryStartNextTransaction(tq);
}
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) {
ZX_DEBUG_ASSERT(thread_checker_.IsCreationThreadCurrent());
ZX_DEBUG_ASSERT(currently_pending);
if (currently_pending->has_value()) {
bt_log(TRACE, "att", "A transaction is already pending! (opcode: %#.2x)",
packet.opcode());
ShutDown();
return;
}
auto iter = handlers_.find(packet.opcode());
if (iter == handlers_.end()) {
bt_log(TRACE, "att", "no handler registered for opcode %#.2x",
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) {
ZX_DEBUG_ASSERT(thread_checker_.IsCreationThreadCurrent());
if (remote_request_ && remote_request_->id == id) {
return &remote_request_;
}
if (remote_indication_ && remote_indication_->id == id) {
return &remote_indication_;
}
bt_log(TRACE, "att", "id %u does not match any transaction", id);
return nullptr;
}
void Bearer::HandlePDUWithoutResponse(const PacketReader& packet) {
ZX_DEBUG_ASSERT(thread_checker_.IsCreationThreadCurrent());
auto iter = handlers_.find(packet.opcode());
if (iter == handlers_.end()) {
bt_log(TRACE, "att", "dropping unhandled packet (opcode: %#.2x)",
packet.opcode());
return;
}
iter->second(kInvalidTransactionId, packet);
}
void Bearer::OnChannelClosed() {
ZX_DEBUG_ASSERT(thread_checker_.IsCreationThreadCurrent());
// This will deactivate the channel and notify |closed_cb_|.
ShutDown();
}
void Bearer::OnRxBFrame(const l2cap::SDU& sdu) {
ZX_DEBUG_ASSERT(is_open());
ZX_DEBUG_ASSERT(thread_checker_.IsCreationThreadCurrent());
uint16_t length = sdu.length();
// An ATT PDU should at least contain the opcode.
if (length < sizeof(OpCode)) {
bt_log(TRACE, "att", "PDU too short!");
ShutDown();
return;
}
if (length > mtu_) {
bt_log(TRACE, "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) {
ZX_ASSERT(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:
bt_log(TRACE, "att", "Unsupported opcode: %#.2x", packet.opcode());
SendErrorResponse(packet.opcode(), 0, ErrorCode::kRequestNotSupported);
break;
}
});
}
} // namespace att
} // namespace btlib