blob: a78ffc03e8b30cdf2d80a863290f416d043030e7 [file] [log] [blame]
// Copyright 2018 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 <zircon/assert.h>
#include "garnet/drivers/bluetooth/lib/common/log.h"
#include "garnet/drivers/bluetooth/lib/common/slab_allocator.h"
#include "gatt_defs.h"
using btlib::common::HostError;
namespace btlib {
using att::StatusCallback;
using common::BufferView;
using common::HostError;
namespace gatt {
namespace {
common::MutableByteBufferPtr NewPDU(size_t param_size) {
auto pdu = common::NewSlabBuffer(sizeof(att::Header) + param_size);
if (!pdu) {
bt_log(TRACE, "att", "out of memory");
}
return pdu;
}
template <att::UUIDType Format,
typename EntryType = att::InformationData<Format>>
bool ProcessDescriptorDiscoveryResponse(
att::Handle range_start,
att::Handle range_end,
BufferView entries,
Client::DescriptorCallback desc_callback,
att::Handle* out_last_handle) {
ZX_DEBUG_ASSERT(out_last_handle);
if (entries.size() % sizeof(EntryType)) {
bt_log(TRACE, "gatt", "malformed information data list");
return false;
}
att::Handle last_handle = range_end;
while (entries.size()) {
const EntryType& entry = entries.As<EntryType>();
DescriptorData desc;
desc.handle = le16toh(entry.handle);
// Stop and report an error if the server erroneously responds with
// an attribute outside the requested range.
if (desc.handle > range_end || desc.handle < range_start) {
bt_log(TRACE, "gatt",
"descriptor handle out of range (handle: %#.4x, "
"range: %#.4x - %#.4x)",
desc.handle, range_start, range_end);
return false;
}
// The handles must be strictly increasing.
if (last_handle != range_end && desc.handle <= last_handle) {
bt_log(TRACE, "gatt", "descriptor handles not strictly increasing");
return false;
}
last_handle = desc.handle;
desc.type = common::UUID(entry.uuid);
// Notify the handler.
desc_callback(desc);
entries = entries.view(sizeof(EntryType));
}
*out_last_handle = last_handle;
return true;
}
} // namespace
class Impl final : public Client {
public:
explicit Impl(fxl::RefPtr<att::Bearer> bearer)
: att_(bearer), weak_ptr_factory_(this) {
ZX_DEBUG_ASSERT(att_);
auto handler = [this](auto txn_id, const att::PacketReader& pdu) {
ZX_DEBUG_ASSERT(pdu.opcode() == att::kNotification ||
pdu.opcode() == att::kIndication);
if (pdu.payload_size() < sizeof(att::NotificationParams)) {
// Received a malformed notification. Disconnect the link.
bt_log(TRACE, "gatt", "malformed notification/indication PDU");
att_->ShutDown();
return;
}
bool is_ind = pdu.opcode() == att::kIndication;
const auto& params = pdu.payload<att::NotificationParams>();
att::Handle handle = le16toh(params.handle);
// Auto-confirm indications.
if (is_ind) {
auto pdu = NewPDU(0u);
if (pdu) {
att::PacketWriter(att::kConfirmation, pdu.get());
att_->Reply(txn_id, std::move(pdu));
} else {
att_->ReplyWithError(txn_id, handle,
att::ErrorCode::kInsufficientResources);
}
}
// Run the handler
if (notification_handler_) {
notification_handler_(
is_ind, handle,
BufferView(params.value, pdu.payload_size() - sizeof(att::Handle)));
} else {
bt_log(SPEW, "gatt", "dropped notification/indication without handler");
}
};
not_handler_id_ = att_->RegisterHandler(att::kNotification, handler);
ind_handler_id_ = att_->RegisterHandler(att::kIndication, handler);
}
~Impl() override {
att_->UnregisterHandler(not_handler_id_);
att_->UnregisterHandler(ind_handler_id_);
}
private:
fxl::WeakPtr<Client> AsWeakPtr() override {
return weak_ptr_factory_.GetWeakPtr();
}
uint16_t mtu() const override { return att_->mtu(); }
void ExchangeMTU(MTUCallback mtu_cb) override {
auto pdu = NewPDU(sizeof(att::ExchangeMTURequestParams));
if (!pdu) {
mtu_cb(att::Status(HostError::kOutOfMemory), 0);
return;
}
att::PacketWriter writer(att::kExchangeMTURequest, pdu.get());
auto params = writer.mutable_payload<att::ExchangeMTURequestParams>();
params->client_rx_mtu = htole16(att_->preferred_mtu());
auto rsp_cb = BindCallback([this, mtu_cb = mtu_cb.share()](
const att::PacketReader& rsp) {
ZX_DEBUG_ASSERT(rsp.opcode() == att::kExchangeMTUResponse);
if (rsp.payload_size() != sizeof(att::ExchangeMTUResponseParams)) {
// Received a malformed response. Disconnect the link.
att_->ShutDown();
mtu_cb(att::Status(HostError::kPacketMalformed), 0);
return;
}
const auto& rsp_params = rsp.payload<att::ExchangeMTUResponseParams>();
uint16_t server_mtu = le16toh(rsp_params.server_rx_mtu);
// If the minimum value is less than the default MTU, then go with the
// default MTU (Vol 3, Part F, 3.4.2.2).
uint16_t final_mtu =
std::max(att::kLEMinMTU, std::min(server_mtu, att_->preferred_mtu()));
att_->set_mtu(final_mtu);
mtu_cb(att::Status(), final_mtu);
});
auto error_cb = BindErrorCallback(
[this, mtu_cb = mtu_cb.share()](att::Status status, att::Handle handle) {
// "If the Error Response is sent by the server with the Error Code
// set to Request Not Supported, [...] the default MTU shall be used
// (Vol 3, Part G, 4.3.1)"
if (status.is_protocol_error() &&
status.protocol_error() == att::ErrorCode::kRequestNotSupported) {
bt_log(TRACE, "gatt",
"peer does not support MTU exchange: using default");
att_->set_mtu(att::kLEMinMTU);
mtu_cb(status, att::kLEMinMTU);
return;
}
bt_log(TRACE, "gatt", "MTU exchange failed: %s",
status.ToString().c_str());
mtu_cb(status, 0);
});
att_->StartTransaction(std::move(pdu), std::move(rsp_cb), std::move(error_cb));
}
void DiscoverPrimaryServices(ServiceCallback svc_callback,
StatusCallback status_callback) override {
DiscoverPrimaryServicesInternal(att::kHandleMin, att::kHandleMax,
std::move(svc_callback),
std::move(status_callback));
}
void DiscoverPrimaryServicesInternal(att::Handle start,
att::Handle end,
ServiceCallback svc_callback,
StatusCallback status_callback) {
auto pdu = NewPDU(sizeof(att::ReadByGroupTypeRequestParams16));
if (!pdu) {
status_callback(att::Status(HostError::kOutOfMemory));
return;
}
att::PacketWriter writer(att::kReadByGroupTypeRequest, pdu.get());
auto* params =
writer.mutable_payload<att::ReadByGroupTypeRequestParams16>();
params->start_handle = htole16(start);
params->end_handle = htole16(end);
params->type = htole16(types::kPrimaryService16);
auto rsp_cb = BindCallback([this, svc_cb = std::move(svc_callback),
res_cb = status_callback.share()](
const att::PacketReader& rsp) mutable {
ZX_DEBUG_ASSERT(rsp.opcode() == att::kReadByGroupTypeResponse);
if (rsp.payload_size() < sizeof(att::ReadByGroupTypeResponseParams)) {
// Received malformed response. Disconnect the link.
bt_log(TRACE, "gatt", "received malformed Read By Group Type response");
att_->ShutDown();
res_cb(att::Status(HostError::kPacketMalformed));
return;
}
const auto& rsp_params =
rsp.payload<att::ReadByGroupTypeResponseParams>();
uint8_t entry_length = rsp_params.length;
// We expect the returned attribute value to be a 16-bit or 128-bit
// service UUID.
constexpr size_t kAttrDataSize16 =
sizeof(att::AttributeGroupDataEntry) + sizeof(att::AttributeType16);
constexpr size_t kAttrDataSize128 =
sizeof(att::AttributeGroupDataEntry) + sizeof(att::AttributeType128);
if (entry_length != kAttrDataSize16 && entry_length != kAttrDataSize128) {
bt_log(TRACE, "gatt", "invalid attribute data length");
att_->ShutDown();
res_cb(att::Status(HostError::kPacketMalformed));
return;
}
BufferView attr_data_list(rsp_params.attribute_data_list,
rsp.payload_size() - 1);
if (attr_data_list.size() % entry_length) {
bt_log(TRACE, "gatt", "malformed attribute data list");
att_->ShutDown();
res_cb(att::Status(HostError::kPacketMalformed));
return;
}
att::Handle last_handle = att::kHandleMax;
while (attr_data_list.size()) {
const auto& entry = attr_data_list.As<att::AttributeGroupDataEntry>();
ServiceData service;
service.range_start = le16toh(entry.start_handle);
service.range_end = le16toh(entry.group_end_handle);
if (service.range_end < service.range_start) {
bt_log(TRACE, "gatt", "received malformed service range values");
res_cb(att::Status(HostError::kPacketMalformed));
return;
}
last_handle = service.range_end;
BufferView value(entry.value, entry_length - (2 * sizeof(att::Handle)));
// This must succeed as we have performed the appropriate checks above.
__UNUSED bool result = common::UUID::FromBytes(value, &service.type);
ZX_DEBUG_ASSERT(result);
// Notify the handler.
svc_cb(service);
attr_data_list = attr_data_list.view(entry_length);
}
// The procedure is over if we have reached the end of the handle range.
if (last_handle == att::kHandleMax) {
res_cb(att::Status());
return;
}
// Request the next batch.
DiscoverPrimaryServicesInternal(last_handle + 1, att::kHandleMax,
std::move(svc_cb), std::move(res_cb));
});
auto error_cb =
BindErrorCallback([this, res_cb = status_callback.share()](att::Status status,
att::Handle handle) {
// An Error Response code of "Attribute Not Found" indicates the end
// of the procedure (v5.0, Vol 3, Part G, 4.4.1).
if (status.is_protocol_error() &&
status.protocol_error() == att::ErrorCode::kAttributeNotFound) {
res_cb(att::Status());
return;
}
res_cb(status);
});
att_->StartTransaction(std::move(pdu), std::move(rsp_cb), std::move(error_cb));
}
void DiscoverCharacteristics(att::Handle range_start,
att::Handle range_end,
CharacteristicCallback chrc_callback,
StatusCallback status_callback) override {
ZX_DEBUG_ASSERT(range_start <= range_end);
ZX_DEBUG_ASSERT(chrc_callback);
ZX_DEBUG_ASSERT(status_callback);
if (range_start == range_end) {
status_callback(att::Status());
return;
}
auto pdu = NewPDU(sizeof(att::ReadByTypeRequestParams16));
if (!pdu) {
status_callback(att::Status(HostError::kOutOfMemory));
return;
}
att::PacketWriter writer(att::kReadByTypeRequest, pdu.get());
auto* params = writer.mutable_payload<att::ReadByTypeRequestParams16>();
params->start_handle = htole16(range_start);
params->end_handle = htole16(range_end);
params->type = htole16(types::kCharacteristicDeclaration16);
auto rsp_cb = BindCallback(
[this, range_start, range_end, chrc_cb = std::move(chrc_callback),
res_cb =
status_callback.share()](const att::PacketReader& rsp) mutable {
ZX_DEBUG_ASSERT(rsp.opcode() == att::kReadByTypeResponse);
if (rsp.payload_size() < sizeof(att::ReadByTypeResponseParams)) {
bt_log(TRACE, "gatt", "received malformed Read By Type response");
att_->ShutDown();
res_cb(att::Status(HostError::kPacketMalformed));
return;
}
const auto& rsp_params = rsp.payload<att::ReadByTypeResponseParams>();
uint8_t entry_length = rsp_params.length;
// The characteristic declaration value contains:
// 1 octet: properties
// 2 octets: value handle
// 2 or 16 octets: UUID
constexpr size_t kCharacDeclSize16 = sizeof(Properties) +
sizeof(att::Handle) +
sizeof(att::AttributeType16);
constexpr size_t kCharacDeclSize128 = sizeof(Properties) +
sizeof(att::Handle) +
sizeof(att::AttributeType128);
constexpr size_t kAttributeDataSize16 =
sizeof(att::AttributeData) + kCharacDeclSize16;
constexpr size_t kAttributeDataSize128 =
sizeof(att::AttributeData) + kCharacDeclSize128;
if (entry_length != kAttributeDataSize16 &&
entry_length != kAttributeDataSize128) {
bt_log(TRACE, "gatt", "invalid attribute data length");
att_->ShutDown();
res_cb(att::Status(HostError::kPacketMalformed));
return;
}
BufferView attr_data_list(rsp_params.attribute_data_list,
rsp.payload_size() - 1);
if (attr_data_list.size() % entry_length) {
bt_log(TRACE, "gatt", "malformed attribute data list");
att_->ShutDown();
res_cb(att::Status(HostError::kPacketMalformed));
return;
}
att::Handle last_handle = range_end;
while (attr_data_list.size()) {
const auto& entry = attr_data_list.As<att::AttributeData>();
BufferView value(entry.value, entry_length - sizeof(att::Handle));
CharacteristicData chrc;
chrc.handle = le16toh(entry.handle);
chrc.properties = value[0];
chrc.value_handle = le16toh(value.view(1, 2).As<att::Handle>());
// Vol 3, Part G, 3.3: "The Characteristic Value declaration shall
// exist immediately following the characteristic declaration."
if (chrc.value_handle != chrc.handle + 1) {
bt_log(TRACE, "gatt", "characteristic value doesn't follow decl");
res_cb(att::Status(HostError::kPacketMalformed));
return;
}
// Stop and report an error if the server erroneously responds with
// an attribute outside the requested range.
if (chrc.handle > range_end || chrc.handle < range_start) {
bt_log(TRACE, "gatt",
"characteristic handle out of range (handle: %#.4x, "
"range: %#.4x - %#.4x)",
chrc.handle, range_start, range_end);
res_cb(att::Status(HostError::kPacketMalformed));
return;
}
// The handles must be strictly increasing. Check this so that a
// server cannot fool us into sending requests forever.
if (last_handle != range_end && chrc.handle <= last_handle) {
bt_log(TRACE, "gatt", "handles are not strictly increasing");
res_cb(att::Status(HostError::kPacketMalformed));
return;
}
last_handle = chrc.handle;
// This must succeed as we have performed the necessary checks
// above.
__UNUSED bool result =
common::UUID::FromBytes(value.view(3), &chrc.type);
ZX_DEBUG_ASSERT(result);
// Notify the handler.
chrc_cb(chrc);
attr_data_list = attr_data_list.view(entry_length);
}
// The procedure is over if we have reached the end of the handle
// range.
if (last_handle == range_end) {
res_cb(att::Status());
return;
}
// Request the next batch.
DiscoverCharacteristics(last_handle + 1, range_end,
std::move(chrc_cb), std::move(res_cb));
});
auto error_cb =
BindErrorCallback([this, res_cb = status_callback.share()](att::Status status,
att::Handle handle) {
// An Error Response code of "Attribute Not Found" indicates the end
// of the procedure (v5.0, Vol 3, Part G, 4.6.1).
if (status.is_protocol_error() &&
status.protocol_error() == att::ErrorCode::kAttributeNotFound) {
res_cb(att::Status());
return;
}
res_cb(status);
});
att_->StartTransaction(std::move(pdu), std::move(rsp_cb), std::move(error_cb));
}
void DiscoverDescriptors(att::Handle range_start,
att::Handle range_end,
DescriptorCallback desc_callback,
StatusCallback status_callback) override {
ZX_DEBUG_ASSERT(range_start <= range_end);
ZX_DEBUG_ASSERT(desc_callback);
ZX_DEBUG_ASSERT(status_callback);
auto pdu = NewPDU(sizeof(att::FindInformationRequestParams));
if (!pdu) {
status_callback(att::Status(HostError::kOutOfMemory));
return;
}
att::PacketWriter writer(att::kFindInformationRequest, pdu.get());
auto* params = writer.mutable_payload<att::FindInformationRequestParams>();
params->start_handle = htole16(range_start);
params->end_handle = htole16(range_end);
auto rsp_cb = BindCallback([this, range_start, range_end,
desc_cb = std::move(desc_callback),
res_cb = status_callback.share()](
const att::PacketReader& rsp) mutable {
ZX_DEBUG_ASSERT(rsp.opcode() == att::kFindInformationResponse);
if (rsp.payload_size() < sizeof(att::FindInformationResponseParams)) {
bt_log(TRACE, "gatt", "received malformed Find Information response");
att_->ShutDown();
res_cb(att::Status(HostError::kPacketMalformed));
return;
}
const auto& rsp_params =
rsp.payload<att::FindInformationResponseParams>();
BufferView entries = rsp.payload_data().view(sizeof(rsp_params.format));
att::Handle last_handle;
bool result;
switch (rsp_params.format) {
case att::UUIDType::k16Bit:
result = ProcessDescriptorDiscoveryResponse<att::UUIDType::k16Bit>(
range_start, range_end, entries, desc_cb.share(), &last_handle);
break;
case att::UUIDType::k128Bit:
result = ProcessDescriptorDiscoveryResponse<att::UUIDType::k128Bit>(
range_start, range_end, entries, desc_cb.share(), &last_handle);
break;
default:
bt_log(TRACE, "gatt", "invalid information data format");
result = false;
break;
}
if (!result) {
att_->ShutDown();
res_cb(att::Status(HostError::kPacketMalformed));
return;
}
// The procedure is over if we have reached the end of the handle range.
if (last_handle == range_end) {
res_cb(att::Status());
return;
}
// Request the next batch.
DiscoverDescriptors(last_handle + 1, range_end, std::move(desc_cb),
std::move(res_cb));
});
auto error_cb =
BindErrorCallback([this, res_cb = status_callback.share()](att::Status status,
att::Handle handle) {
// An Error Response code of "Attribute Not Found" indicates the end
// of the procedure (v5.0, Vol 3, Part G, 4.7.1).
if (status.is_protocol_error() &&
status.protocol_error() == att::ErrorCode::kAttributeNotFound) {
res_cb(att::Status());
return;
}
res_cb(status);
});
att_->StartTransaction(std::move(pdu), std::move(rsp_cb), std::move(error_cb));
}
void ReadRequest(att::Handle handle, ReadCallback callback) override {
auto pdu = NewPDU(sizeof(att::ReadRequestParams));
if (!pdu) {
callback(att::Status(HostError::kOutOfMemory), BufferView());
return;
}
att::PacketWriter writer(att::kReadRequest, pdu.get());
auto params = writer.mutable_payload<att::ReadRequestParams>();
params->handle = htole16(handle);
auto rsp_cb = BindCallback(
[this, callback = callback.share()](const att::PacketReader& rsp) {
ZX_DEBUG_ASSERT(rsp.opcode() == att::kReadResponse);
callback(att::Status(), rsp.payload_data());
});
auto error_cb =
BindErrorCallback([this, callback = callback.share()](
att::Status status, att::Handle handle) {
bt_log(TRACE, "gatt", "read request failed: %s, handle %#.4x",
status.ToString().c_str(), handle);
callback(status, BufferView());
});
if (!att_->StartTransaction(std::move(pdu), std::move(rsp_cb),
std::move(error_cb))) {
callback(att::Status(HostError::kPacketMalformed), BufferView());
}
}
void ReadBlobRequest(att::Handle handle, uint16_t offset,
ReadCallback callback) override {
auto pdu = NewPDU(sizeof(att::ReadBlobRequestParams));
if (!pdu) {
callback(att::Status(HostError::kOutOfMemory), BufferView());
return;
}
att::PacketWriter writer(att::kReadBlobRequest, pdu.get());
auto params = writer.mutable_payload<att::ReadBlobRequestParams>();
params->handle = htole16(handle);
params->offset = htole16(offset);
auto rsp_cb = BindCallback(
[this, callback = callback.share()](const att::PacketReader& rsp) {
ZX_DEBUG_ASSERT(rsp.opcode() == att::kReadBlobResponse);
callback(att::Status(), rsp.payload_data());
});
auto error_cb =
BindErrorCallback([this, callback = callback.share()](
att::Status status, att::Handle handle) {
bt_log(TRACE, "gatt", "read blob request failed: %s, handle: %#.4x",
status.ToString().c_str(), handle);
callback(status, BufferView());
});
if (!att_->StartTransaction(std::move(pdu), std::move(rsp_cb),
std::move(error_cb))) {
callback(att::Status(HostError::kPacketMalformed), BufferView());
}
}
void WriteRequest(att::Handle handle,
const common::ByteBuffer& value,
StatusCallback callback) override {
const size_t payload_size = sizeof(att::WriteRequestParams) + value.size();
if (sizeof(att::OpCode) + payload_size > att_->mtu()) {
bt_log(SPEW, "gatt", "write request payload exceeds MTU");
callback(att::Status(HostError::kPacketMalformed));
return;
}
auto pdu = NewPDU(payload_size);
if (!pdu) {
callback(att::Status(HostError::kOutOfMemory));
return;
}
att::PacketWriter writer(att::kWriteRequest, pdu.get());
auto params = writer.mutable_payload<att::WriteRequestParams>();
params->handle = htole16(handle);
auto value_view =
writer.mutable_payload_data().mutable_view(sizeof(att::Handle));
value.Copy(&value_view);
auto rsp_cb = BindCallback(
[this, callback = callback.share()](const att::PacketReader& rsp) {
ZX_DEBUG_ASSERT(rsp.opcode() == att::kWriteResponse);
if (rsp.payload_size()) {
att_->ShutDown();
callback(att::Status(HostError::kPacketMalformed));
return;
}
callback(att::Status());
});
auto error_cb =
BindErrorCallback([this, callback = callback.share()](
att::Status status, att::Handle handle) {
bt_log(TRACE, "gatt", "write request failed: %s, handle: %#.2x",
status.ToString().c_str(), handle);
callback(status);
});
if (!att_->StartTransaction(std::move(pdu), std::move(rsp_cb), std::move(error_cb))) {
callback(att::Status(HostError::kPacketMalformed));
}
}
void WriteWithoutResponse(att::Handle handle,
const common::ByteBuffer& value) override {
const size_t payload_size = sizeof(att::WriteRequestParams) + value.size();
if (sizeof(att::OpCode) + payload_size > att_->mtu()) {
bt_log(SPEW, "gatt", "write request payload exceeds MTU");
return;
}
auto pdu = NewPDU(payload_size);
if (!pdu) {
return;
}
att::PacketWriter writer(att::kWriteCommand, pdu.get());
auto params = writer.mutable_payload<att::WriteRequestParams>();
params->handle = htole16(handle);
auto value_view =
writer.mutable_payload_data().mutable_view(sizeof(att::Handle));
value.Copy(&value_view);
att_->SendWithoutResponse(std::move(pdu));
}
void SetNotificationHandler(NotificationCallback handler) override {
notification_handler_ = std::move(handler);
}
// Wraps |callback| in a TransactionCallback that only runs if this Client is
// still alive.
att::Bearer::TransactionCallback BindCallback(
att::Bearer::TransactionCallback callback) {
return [self = weak_ptr_factory_.GetWeakPtr(), callback = std::move(callback)](const auto& rsp) {
if (self) {
callback(rsp);
}
};
}
// Wraps |callback| in a ErrorCallback that only runs if this Client is still
// alive.
att::Bearer::ErrorCallback BindErrorCallback(
att::Bearer::ErrorCallback callback) {
return [self = weak_ptr_factory_.GetWeakPtr(), callback = std::move(callback)](
att::Status status, att::Handle handle) {
if (self) {
callback(status, handle);
}
};
}
fxl::RefPtr<att::Bearer> att_;
att::Bearer::HandlerId not_handler_id_;
att::Bearer::HandlerId ind_handler_id_;
NotificationCallback notification_handler_;
fxl::WeakPtrFactory<Client> weak_ptr_factory_;
FXL_DISALLOW_COPY_AND_ASSIGN(Impl);
};
// static
std::unique_ptr<Client> Client::Create(fxl::RefPtr<att::Bearer> bearer) {
return std::make_unique<Impl>(std::move(bearer));
}
} // namespace gatt
} // namespace btlib