blob: 9b850eddef46689095063f1be4b7ff02014728fe [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 "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/gatt/client.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/att/att.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/common/assert.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/common/log.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/common/slab_allocator.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/common/trace.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/gatt/gatt_defs.h"
#pragma clang diagnostic ignored "-Wshadow"
using bt::HostError;
namespace bt::gatt {
namespace {
MutableByteBufferPtr NewPDU(size_t param_size) {
auto pdu = NewBuffer(sizeof(att::Header) + param_size);
if (!pdu) {
bt_log(DEBUG, "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) {
BT_DEBUG_ASSERT(out_last_handle);
if (entries.size() % sizeof(EntryType)) {
bt_log(DEBUG, "gatt", "malformed information data list");
return false;
}
att::Handle last_handle = range_end;
while (entries.size()) {
const EntryType& entry = entries.To<EntryType>();
att::Handle 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(DEBUG,
"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(DEBUG, "gatt", "descriptor handles not strictly increasing");
return false;
}
last_handle = desc_handle;
// Notify the handler.
desc_callback(DescriptorData(desc_handle, UUID(entry.uuid)));
entries = entries.view(sizeof(EntryType));
}
*out_last_handle = last_handle;
return true;
}
} // namespace
class Impl final : public Client {
public:
explicit Impl(att::Bearer::WeakPtr bearer)
: att_(std::move(bearer)), weak_self_(this) {
BT_DEBUG_ASSERT(att_.is_alive());
auto handler = [this](auto txn_id, const att::PacketReader& pdu) {
BT_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(DEBUG, "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);
size_t value_size = pdu.payload_size() - sizeof(att::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);
}
}
bool maybe_truncated = false;
// If the value is the max size that fits in the MTU, it may be truncated.
if (value_size ==
att_->mtu() - sizeof(att::OpCode) - sizeof(att::Handle)) {
maybe_truncated = true;
}
// Run the handler
if (notification_handler_) {
notification_handler_(is_ind,
handle,
BufferView(params.value, value_size),
maybe_truncated);
} else {
bt_log(
TRACE, "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_);
}
using WeakPtr = WeakSelf<Client>::WeakPtr;
WeakPtr GetWeakPtr() override { return weak_self_.GetWeakPtr(); }
private:
uint16_t mtu() const override { return att_->mtu(); }
void ExchangeMTU(MTUCallback mtu_cb) override {
auto pdu = NewPDU(sizeof(att::ExchangeMTURequestParams));
if (!pdu) {
mtu_cb(fit::error(att::Error(HostError::kOutOfMemory)));
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 = [this, mtu_cb = std::move(mtu_cb)](
att::Bearer::TransactionResult result) mutable {
if (result.is_ok()) {
const att::PacketReader& rsp = result.value();
BT_DEBUG_ASSERT(rsp.opcode() == att::kExchangeMTUResponse);
if (rsp.payload_size() != sizeof(att::ExchangeMTUResponseParams)) {
// Received a malformed response. Disconnect the link.
att_->ShutDown();
mtu_cb(fit::error(att::Error(HostError::kPacketMalformed)));
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(fit::ok(final_mtu));
return;
}
const auto& [error, handle] = result.error_value();
// "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 (error.is(att::ErrorCode::kRequestNotSupported)) {
bt_log(
DEBUG, "gatt", "peer does not support MTU exchange: using default");
att_->set_mtu(att::kLEMinMTU);
mtu_cb(fit::error(error));
return;
}
bt_log(DEBUG, "gatt", "MTU exchange failed: %s", bt_str(error));
mtu_cb(fit::error(error));
};
att_->StartTransaction(std::move(pdu), BindCallback(std::move(rsp_cb)));
}
void DiscoverServices(ServiceKind kind,
ServiceCallback svc_callback,
att::ResultFunction<> status_callback) override {
DiscoverServicesInRange(kind,
att::kHandleMin,
att::kHandleMax,
std::move(svc_callback),
std::move(status_callback));
}
void DiscoverServicesInRange(ServiceKind kind,
att::Handle range_start,
att::Handle range_end,
ServiceCallback svc_callback,
att::ResultFunction<> status_callback) override {
BT_ASSERT(range_start <= range_end);
auto pdu = NewPDU(sizeof(att::ReadByGroupTypeRequestParams16));
if (!pdu) {
status_callback(ToResult(HostError::kOutOfMemory));
return;
}
att::PacketWriter writer(att::kReadByGroupTypeRequest, pdu.get());
auto* params =
writer.mutable_payload<att::ReadByGroupTypeRequestParams16>();
params->start_handle = htole16(range_start);
params->end_handle = htole16(range_end);
params->type =
htole16(kind == ServiceKind::PRIMARY ? types::kPrimaryService16
: types::kSecondaryService16);
auto rsp_cb = [this,
kind,
range_start,
range_end,
svc_cb = std::move(svc_callback),
res_cb = std::move(status_callback)](
att::Bearer::TransactionResult result) mutable {
if (result.is_error()) {
const att::Error& error = result.error_value().first;
// An Error Response code of "Attribute Not Found" indicates the end of
// the procedure (v5.0, Vol 3, Part G, 4.4.1).
if (error.is(att::ErrorCode::kAttributeNotFound)) {
res_cb(fit::ok());
return;
}
res_cb(fit::error(error));
return;
}
const att::PacketReader& rsp = result.value();
BT_DEBUG_ASSERT(rsp.opcode() == att::kReadByGroupTypeResponse);
TRACE_DURATION("bluetooth",
"gatt::Client::DiscoverServicesInRange rsp_cb",
"size",
rsp.size());
if (rsp.payload_size() < sizeof(att::ReadByGroupTypeResponseParams)) {
// Received malformed response. Disconnect the link.
bt_log(DEBUG, "gatt", "received malformed Read By Group Type response");
att_->ShutDown();
res_cb(ToResult(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(DEBUG, "gatt", "invalid attribute data length");
att_->ShutDown();
res_cb(ToResult(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(DEBUG, "gatt", "malformed attribute data list");
att_->ShutDown();
res_cb(ToResult(HostError::kPacketMalformed));
return;
}
std::optional<att::Handle> last_handle;
while (attr_data_list.size()) {
att::Handle start = le16toh(
attr_data_list
.ReadMember<&att::AttributeGroupDataEntry::start_handle>());
att::Handle end = le16toh(
attr_data_list
.ReadMember<&att::AttributeGroupDataEntry::group_end_handle>());
if (end < start) {
bt_log(DEBUG, "gatt", "received malformed service range values");
res_cb(ToResult(HostError::kPacketMalformed));
return;
}
if (start < range_start || start > range_end) {
bt_log(DEBUG,
"gatt",
"received service range values outside of requested range");
res_cb(ToResult(HostError::kPacketMalformed));
return;
}
// "The Attribute Data List is ordered sequentially based on the
// attribute handles." (Core Spec v5.3, Vol 3, Part F, Sec 3.4.4.10)
if (last_handle.has_value() && start <= last_handle.value()) {
bt_log(DEBUG, "gatt", "received services out of order");
res_cb(ToResult(HostError::kPacketMalformed));
return;
}
// This must succeed as we have performed the appropriate checks above.
auto uuid_bytes =
attr_data_list.view(offsetof(att::AttributeGroupDataEntry, value),
entry_length - (2 * sizeof(att::Handle)));
UUID uuid(uuid_bytes);
ServiceData service(kind, start, end, uuid);
last_handle = service.range_end;
// 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.has_value() || last_handle.value() == range_end) {
res_cb(fit::ok());
return;
}
// Request the next batch.
DiscoverServicesInRange(kind,
last_handle.value() + 1,
range_end,
std::move(svc_cb),
std::move(res_cb));
};
att_->StartTransaction(std::move(pdu), BindCallback(std::move(rsp_cb)));
}
void DiscoverServicesWithUuids(ServiceKind kind,
ServiceCallback svc_cb,
att::ResultFunction<> status_cb,
std::vector<UUID> uuids) override {
DiscoverServicesWithUuidsInRange(kind,
att::kHandleMin,
att::kHandleMax,
std::move(svc_cb),
std::move(status_cb),
std::move(uuids));
}
void DiscoverServicesWithUuidsInRange(ServiceKind kind,
att::Handle range_start,
att::Handle range_end,
ServiceCallback svc_callback,
att::ResultFunction<> status_callback,
std::vector<UUID> uuids) override {
BT_ASSERT(range_start <= range_end);
BT_ASSERT(!uuids.empty());
UUID uuid = uuids.back();
uuids.pop_back();
auto recursive_status_cb = [this,
range_start,
range_end,
kind,
svc_cb = svc_callback.share(),
status_cb = std::move(status_callback),
remaining_uuids =
std::move(uuids)](auto status) mutable {
// Base case
if (status.is_error() || remaining_uuids.empty()) {
status_cb(status);
return;
}
// Recursively discover with the remaining UUIDs.
DiscoverServicesWithUuidsInRange(kind,
range_start,
range_end,
std::move(svc_cb),
std::move(status_cb),
std::move(remaining_uuids));
};
// Discover the last uuid in uuids.
DiscoverServicesByUuidInRange(kind,
range_start,
range_end,
std::move(svc_callback),
std::move(recursive_status_cb),
uuid);
}
void DiscoverServicesByUuidInRange(ServiceKind kind,
att::Handle start,
att::Handle end,
ServiceCallback svc_callback,
att::ResultFunction<> status_callback,
UUID uuid) {
size_t uuid_size_bytes = uuid.CompactSize(/* allow 32 bit UUIDs */ false);
auto pdu =
NewPDU(sizeof(att::FindByTypeValueRequestParams) + uuid_size_bytes);
if (!pdu) {
status_callback(ToResult(HostError::kOutOfMemory));
return;
}
att::PacketWriter writer(att::kFindByTypeValueRequest, pdu.get());
auto* params = writer.mutable_payload<att::FindByTypeValueRequestParams>();
params->start_handle = htole16(start);
params->end_handle = htole16(end);
params->type =
htole16(kind == ServiceKind::PRIMARY ? types::kPrimaryService16
: types::kSecondaryService16);
MutableBufferView value_view(params->value, uuid_size_bytes);
uuid.ToBytes(&value_view, /* allow 32 bit UUIDs */ false);
auto rsp_cb = [this,
kind,
discovery_range_start = start,
discovery_range_end = end,
svc_cb = std::move(svc_callback),
res_cb = std::move(status_callback),
uuid](att::Bearer::TransactionResult result) mutable {
if (result.is_error()) {
const att::Error& error = result.error_value().first;
// An Error Response code of "Attribute Not Found" indicates the end of
// the procedure (v5.0, Vol 3, Part G, 4.4.2).
if (error.is(att::ErrorCode::kAttributeNotFound)) {
res_cb(fit::ok());
return;
}
res_cb(fit::error(error));
return;
}
const att::PacketReader& rsp = result.value();
BT_DEBUG_ASSERT(rsp.opcode() == att::kFindByTypeValueResponse);
size_t payload_size = rsp.payload_size();
if (payload_size < 1 ||
payload_size % sizeof(att::FindByTypeValueResponseParams) != 0) {
// Received malformed response. Disconnect the link.
bt_log(DEBUG,
"gatt",
"received malformed Find By Type Value response with size %zu",
payload_size);
att_->ShutDown();
res_cb(ToResult(HostError::kPacketMalformed));
return;
}
BufferView handle_list = rsp.payload_data();
std::optional<att::Handle> last_handle;
while (handle_list.size()) {
const auto& entry = handle_list.To<att::HandlesInformationList>();
att::Handle start = le16toh(entry.handle);
att::Handle end = le16toh(entry.group_end_handle);
if (end < start) {
bt_log(DEBUG, "gatt", "received malformed service range values");
res_cb(ToResult(HostError::kPacketMalformed));
return;
}
if (start < discovery_range_start || start > discovery_range_end) {
bt_log(DEBUG,
"gatt",
"received service range values outside of requested range");
res_cb(ToResult(HostError::kPacketMalformed));
return;
}
// "The Handles Information List is ordered sequentially based on the
// found attribute handles." (Core Spec v5.3, Vol 3, Part F,
// Sec 3.4.3.4)
if (last_handle.has_value() && start <= last_handle.value()) {
bt_log(DEBUG, "gatt", "received services out of order");
res_cb(ToResult(HostError::kPacketMalformed));
return;
}
ServiceData service(kind, start, end, uuid);
// Notify the handler.
svc_cb(service);
// HandlesInformationList is a single element of the list.
size_t entry_length = sizeof(att::HandlesInformationList);
handle_list = handle_list.view(entry_length);
last_handle = service.range_end;
}
// The procedure is over if we have reached the end of the handle range.
if (!last_handle.has_value() ||
last_handle.value() == discovery_range_end) {
res_cb(fit::ok());
return;
}
// Request the next batch.
DiscoverServicesByUuidInRange(kind,
last_handle.value() + 1,
discovery_range_end,
std::move(svc_cb),
std::move(res_cb),
uuid);
};
att_->StartTransaction(std::move(pdu), BindCallback(std::move(rsp_cb)));
}
void DiscoverCharacteristics(att::Handle range_start,
att::Handle range_end,
CharacteristicCallback chrc_callback,
att::ResultFunction<> status_callback) override {
BT_ASSERT(range_start <= range_end);
BT_ASSERT(chrc_callback);
BT_ASSERT(status_callback);
if (range_start == range_end) {
status_callback(fit::ok());
return;
}
auto read_by_type_cb = [this,
range_end,
chrc_cb = std::move(chrc_callback),
res_cb = status_callback.share()](
ReadByTypeResult result) mutable {
TRACE_DURATION("bluetooth",
"gatt::Client::DiscoverCharacteristics read_by_type_cb");
if (result.is_error()) {
const auto error = result.error_value().error;
// An Error Response code of "Attribute Not Found" indicates the end
// of the procedure (v5.0, Vol 3, Part G, 4.6.1).
if (error.is(att::ErrorCode::kAttributeNotFound)) {
res_cb(fit::ok());
return;
}
res_cb(fit::error(error));
return;
}
auto& attributes = result.value();
// ReadByTypeRequest() should return an error result if there are no
// attributes in a success response.
BT_ASSERT(!attributes.empty());
for (auto& char_attr : attributes) {
Properties properties = 0u;
att::Handle value_handle = 0u;
UUID value_uuid;
// The characteristic declaration value contains:
// 1 octet: properties
// 2 octets: value handle
// 2 or 16 octets: UUID
if (char_attr.value.size() ==
sizeof(CharacteristicDeclarationAttributeValue<
att::UUIDType::k16Bit>)) {
auto attr_value = char_attr.value.To<
CharacteristicDeclarationAttributeValue<att::UUIDType::k16Bit>>();
properties = attr_value.properties;
value_handle = le16toh(attr_value.value_handle);
value_uuid = UUID(attr_value.value_uuid);
} else if (char_attr.value.size() ==
sizeof(CharacteristicDeclarationAttributeValue<
att::UUIDType::k128Bit>)) {
auto attr_value =
char_attr.value.To<CharacteristicDeclarationAttributeValue<
att::UUIDType::k128Bit>>();
properties = attr_value.properties;
value_handle = le16toh(attr_value.value_handle);
value_uuid = UUID(attr_value.value_uuid);
} else {
bt_log(DEBUG,
"gatt",
"invalid characteristic declaration attribute value size");
att_->ShutDown();
res_cb(ToResult(HostError::kPacketMalformed));
return;
}
// Vol 3, Part G, 3.3: "The Characteristic Value declaration shall
// exist immediately following the characteristic declaration."
if (value_handle != char_attr.handle + 1) {
bt_log(
DEBUG, "gatt", "characteristic value doesn't follow declaration");
res_cb(ToResult(HostError::kPacketMalformed));
return;
}
// Notify the handler. By default, there are no extended properties to
// report.
chrc_cb(CharacteristicData(properties,
/*ext_props=*/std::nullopt,
char_attr.handle,
value_handle,
value_uuid));
}
// The procedure is over if we have reached the end of the handle
// range.
const auto last_handle = attributes.back().handle;
if (last_handle == range_end) {
res_cb(fit::ok());
return;
}
// Request the next batch.
DiscoverCharacteristics(
last_handle + 1, range_end, std::move(chrc_cb), std::move(res_cb));
};
ReadByTypeRequest(types::kCharacteristicDeclaration,
range_start,
range_end,
std::move(read_by_type_cb));
}
void DiscoverDescriptors(att::Handle range_start,
att::Handle range_end,
DescriptorCallback desc_callback,
att::ResultFunction<> status_callback) override {
BT_DEBUG_ASSERT(range_start <= range_end);
BT_DEBUG_ASSERT(desc_callback);
BT_DEBUG_ASSERT(status_callback);
auto pdu = NewPDU(sizeof(att::FindInformationRequestParams));
if (!pdu) {
status_callback(ToResult(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 = [this,
range_start,
range_end,
desc_cb = std::move(desc_callback),
res_cb = std::move(status_callback)](
att::Bearer::TransactionResult result) mutable {
if (result.is_error()) {
const att::Error& error = result.error_value().first;
// An Error Response code of "Attribute Not Found" indicates the end of
// the procedure (v5.0, Vol 3, Part G, 4.7.1).
if (error.is(att::ErrorCode::kAttributeNotFound)) {
res_cb(fit::ok());
return;
}
res_cb(fit::error(error));
return;
}
const att::PacketReader& rsp = result.value();
BT_DEBUG_ASSERT(rsp.opcode() == att::kFindInformationResponse);
TRACE_DURATION("bluetooth",
"gatt::Client::DiscoverDescriptors rsp_cb",
"size",
rsp.size());
if (rsp.payload_size() < sizeof(att::FindInformationResponseParams)) {
bt_log(DEBUG, "gatt", "received malformed Find Information response");
att_->ShutDown();
res_cb(ToResult(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 well_formed;
switch (rsp_params.format) {
case att::UUIDType::k16Bit:
well_formed =
ProcessDescriptorDiscoveryResponse<att::UUIDType::k16Bit>(
range_start,
range_end,
entries,
desc_cb.share(),
&last_handle);
break;
case att::UUIDType::k128Bit:
well_formed =
ProcessDescriptorDiscoveryResponse<att::UUIDType::k128Bit>(
range_start,
range_end,
entries,
desc_cb.share(),
&last_handle);
break;
default:
bt_log(DEBUG, "gatt", "invalid information data format");
well_formed = false;
break;
}
if (!well_formed) {
att_->ShutDown();
res_cb(ToResult(HostError::kPacketMalformed));
return;
}
// The procedure is over if we have reached the end of the handle range.
if (last_handle == range_end) {
res_cb(fit::ok());
return;
}
// Request the next batch.
DiscoverDescriptors(
last_handle + 1, range_end, std::move(desc_cb), std::move(res_cb));
};
att_->StartTransaction(std::move(pdu), BindCallback(std::move(rsp_cb)));
}
void ReadRequest(att::Handle handle, ReadCallback callback) override {
auto pdu = NewPDU(sizeof(att::ReadRequestParams));
if (!pdu) {
callback(ToResult(HostError::kOutOfMemory),
BufferView(),
/*maybe_truncated=*/false);
return;
}
att::PacketWriter writer(att::kReadRequest, pdu.get());
auto params = writer.mutable_payload<att::ReadRequestParams>();
params->handle = htole16(handle);
auto rsp_cb = [this, callback = std::move(callback)](
att::Bearer::TransactionResult result) {
if (result.is_ok()) {
const att::PacketReader& rsp = result.value();
BT_DEBUG_ASSERT(rsp.opcode() == att::kReadResponse);
bool maybe_truncated =
(rsp.payload_size() != att::kMaxAttributeValueLength) &&
(rsp.payload_size() == (mtu() - sizeof(rsp.opcode())));
callback(fit::ok(), rsp.payload_data(), maybe_truncated);
return;
}
const auto& [error, handle] = result.error_value();
bt_log(DEBUG,
"gatt",
"read request failed: %s, handle %#.4x",
bt_str(error),
handle);
callback(fit::error(error), BufferView(), /*maybe_truncated=*/false);
};
att_->StartTransaction(std::move(pdu), BindCallback(std::move(rsp_cb)));
}
void ReadByTypeRequest(const UUID& type,
att::Handle start_handle,
att::Handle end_handle,
ReadByTypeCallback callback) override {
size_t type_size = type.CompactSize(/*allow_32bit=*/false);
BT_ASSERT(type_size == sizeof(uint16_t) || type_size == sizeof(UInt128));
auto pdu = NewPDU(type_size == sizeof(uint16_t)
? sizeof(att::ReadByTypeRequestParams16)
: sizeof(att::ReadByTypeRequestParams128));
if (!pdu) {
callback(fit::error(
ReadByTypeError{Error(HostError::kOutOfMemory), std::nullopt}));
return;
}
att::PacketWriter writer(att::kReadByTypeRequest, pdu.get());
if (type_size == sizeof(uint16_t)) {
auto params = writer.mutable_payload<att::ReadByTypeRequestParams16>();
params->start_handle = htole16(start_handle);
params->end_handle = htole16(end_handle);
auto type_view = MutableBufferView(&params->type, sizeof(params->type));
type.ToBytes(&type_view, /*allow_32bit=*/false);
} else {
auto params = writer.mutable_payload<att::ReadByTypeRequestParams128>();
params->start_handle = htole16(start_handle);
params->end_handle = htole16(end_handle);
auto type_view = MutableBufferView(&params->type, sizeof(params->type));
type.ToBytes(&type_view, /*allow_32bit=*/false);
}
auto rsp_cb = [this,
callback = std::move(callback),
start_handle,
end_handle](att::Bearer::TransactionResult result) {
if (result.is_error()) {
const auto& [error, handle] = result.error_value();
bt_log(DEBUG,
"gatt",
"read by type request failed: %s, handle %#.4x",
bt_str(error),
handle);
// Only some errors have handles.
std::optional<att::Handle> cb_handle =
handle ? std::optional(handle) : std::nullopt;
callback(fit::error(ReadByTypeError{error, cb_handle}));
return;
}
const att::PacketReader& rsp = result.value();
BT_ASSERT(rsp.opcode() == att::kReadByTypeResponse);
if (rsp.payload_size() < sizeof(att::ReadByTypeResponseParams)) {
callback(fit::error(
ReadByTypeError{Error(HostError::kPacketMalformed), std::nullopt}));
return;
}
const auto& params = rsp.payload<att::ReadByTypeResponseParams>();
// The response contains a list of attribute handle-value pairs of uniform
// length.
const size_t list_size = rsp.payload_size() - sizeof(params.length);
const size_t pair_size = params.length;
// Success response must:
// a) Specify valid pair length (at least the size of a handle).
// b) Have at least 1 pair (otherwise the Attribute Not Found error should
// have been
// sent).
// c) Have a list size that is evenly divisible by pair size.
if (pair_size < sizeof(att::Handle) || list_size < sizeof(att::Handle) ||
list_size % pair_size != 0) {
callback(fit::error(
ReadByTypeError{Error(HostError::kPacketMalformed), std::nullopt}));
return;
}
std::vector<ReadByTypeValue> attributes;
BufferView attr_list_view(params.attribute_data_list,
rsp.payload_size() - sizeof(params.length));
while (attr_list_view.size() >= params.length) {
const BufferView pair_view = attr_list_view.view(0, pair_size);
const att::Handle handle = le16toh(pair_view.To<att::Handle>());
if (handle < start_handle || handle > end_handle) {
bt_log(TRACE,
"gatt",
"client received read by type response with handle outside of "
"requested range");
callback(fit::error(ReadByTypeError{
Error(HostError::kPacketMalformed), std::nullopt}));
return;
}
if (!attributes.empty() && attributes.back().handle >= handle) {
bt_log(TRACE,
"gatt",
"client received read by type response with handles in "
"non-increasing order");
callback(fit::error(ReadByTypeError{
Error(HostError::kPacketMalformed), std::nullopt}));
return;
}
auto value_view = pair_view.view(sizeof(att::Handle));
// The value may be truncated if it maxes out the length parameter or
// the MTU, whichever is smaller (Core Spec v5.2, Vol 3, Part F,
// Sec 3.4.4).
const size_t mtu_max_value_size =
mtu() - sizeof(att::kReadByTypeResponse) -
sizeof(att::ReadByTypeResponseParams) - sizeof(att::Handle);
bool maybe_truncated =
(value_view.size() ==
std::min(static_cast<size_t>(att::kMaxReadByTypeValueLength),
mtu_max_value_size));
attributes.push_back(
ReadByTypeValue{handle, value_view, maybe_truncated});
// Advance list view to next pair (or end of list).
attr_list_view = attr_list_view.view(pair_size);
}
BT_ASSERT(attr_list_view.size() == 0);
callback(fit::ok(std::move(attributes)));
};
att_->StartTransaction(std::move(pdu), BindCallback(std::move(rsp_cb)));
}
void ReadBlobRequest(att::Handle handle,
uint16_t offset,
ReadCallback callback) override {
auto pdu = NewPDU(sizeof(att::ReadBlobRequestParams));
if (!pdu) {
callback(ToResult(HostError::kOutOfMemory),
BufferView(),
/*maybe_truncated=*/false);
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 = [this, offset, callback = std::move(callback)](
att::Bearer::TransactionResult result) {
if (result.is_ok()) {
const att::PacketReader& rsp = result.value();
BT_DEBUG_ASSERT(rsp.opcode() == att::kReadBlobResponse);
bool maybe_truncated =
(static_cast<size_t>(offset) + rsp.payload_size() !=
att::kMaxAttributeValueLength) &&
(rsp.payload_data().size() == (mtu() - sizeof(att::OpCode)));
callback(fit::ok(), rsp.payload_data(), maybe_truncated);
return;
}
const auto& [error, handle] = result.error_value();
bt_log(DEBUG,
"gatt",
"read blob request failed: %s, handle: %#.4x",
bt_str(error),
handle);
callback(fit::error(error), BufferView(), /*maybe_truncated=*/false);
};
att_->StartTransaction(std::move(pdu), BindCallback(std::move(rsp_cb)));
}
void WriteRequest(att::Handle handle,
const ByteBuffer& value,
att::ResultFunction<> callback) override {
const size_t payload_size = sizeof(att::WriteRequestParams) + value.size();
if (sizeof(att::OpCode) + payload_size > att_->mtu()) {
bt_log(TRACE, "gatt", "write request payload exceeds MTU");
callback(ToResult(HostError::kPacketMalformed));
return;
}
auto pdu = NewPDU(payload_size);
if (!pdu) {
callback(ToResult(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 = [this, callback = std::move(callback)](
att::Bearer::TransactionResult result) {
if (result.is_error()) {
const auto& [error, handle] = result.error_value();
bt_log(DEBUG,
"gatt",
"write request failed: %s, handle: %#.2x",
bt_str(error),
handle);
callback(fit::error(error));
return;
}
const att::PacketReader& rsp = result.value();
BT_DEBUG_ASSERT(rsp.opcode() == att::kWriteResponse);
if (rsp.payload_size()) {
att_->ShutDown();
callback(ToResult(HostError::kPacketMalformed));
return;
}
callback(fit::ok());
};
att_->StartTransaction(std::move(pdu), BindCallback(std::move(rsp_cb)));
}
// An internal object for storing the write queue, callback, and reliability
// mode of a long write operation.
struct PreparedWrite {
bt::att::PrepareWriteQueue prep_write_queue;
bt::att::ResultFunction<> callback;
ReliableMode reliable_mode;
};
void ExecutePrepareWrites(att::PrepareWriteQueue prep_write_queue,
ReliableMode reliable_mode,
att::ResultFunction<> callback) override {
PreparedWrite new_request;
new_request.prep_write_queue = std::move(prep_write_queue);
new_request.callback = std::move(callback);
new_request.reliable_mode = std::move(reliable_mode);
long_write_queue_.push(std::move(new_request));
// If the |long_write_queue| has a pending request, then appending this
// request will be sufficient, otherwise kick off the request.
if (long_write_queue_.size() == 1) {
ProcessWriteQueue(std::move(long_write_queue_.front()));
}
}
void ProcessWriteQueue(PreparedWrite prep_write) {
if (!prep_write.prep_write_queue.empty()) {
att::QueuedWrite prep_write_request =
std::move(prep_write.prep_write_queue.front());
// A copy of the |prep_write_request| is made to pass into the capture
// list for |prep_write_cb|. It will be used to validate the echoed blob.
auto prep_write_copy = att::QueuedWrite(prep_write_request.handle(),
prep_write_request.offset(),
prep_write_request.value());
prep_write.prep_write_queue.pop();
auto prep_write_cb = [this,
prep_write = std::move(prep_write),
requested_blob = std::move(prep_write_copy)](
att::Result<> status,
const ByteBuffer& blob) mutable {
// If the write fails, cancel the prep writes and then move on to the
// next long write in the queue. The device will echo the value written
// in the blob, according to the spec (Vol 3, Part G, 4.9.4). The offset
// and value will be verified if the requested |prep_write.mode| is
// enabled (Vol 3, Part G, 4.9.5).
if (prep_write.reliable_mode == ReliableMode::kEnabled) {
if (blob.size() < sizeof(att::PrepareWriteResponseParams)) {
// The response blob is malformed.
status = ToResult(HostError::kNotReliable);
} else {
auto blob_offset = le16toh(
blob.ReadMember<&att::PrepareWriteResponseParams::offset>());
auto blob_value =
blob.view(sizeof(att::PrepareWriteResponseParams));
if ((blob_offset != requested_blob.offset()) ||
!(blob_value == requested_blob.value())) {
status = ToResult(HostError::kNotReliable);
}
}
}
if (status.is_error()) {
auto exec_write_cb = [this,
callback = std::move(prep_write.callback),
prep_write_status =
status](att::Result<> status) mutable {
// In this case return the original failure status. This effectively
// overrides the ExecuteWrite status.
callback(prep_write_status);
// Now that this request is complete, remove it from the overall
// queue.
BT_DEBUG_ASSERT(!long_write_queue_.empty());
long_write_queue_.pop();
if (long_write_queue_.size() > 0) {
ProcessWriteQueue(std::move(long_write_queue_.front()));
}
};
ExecuteWriteRequest(att::ExecuteWriteFlag::kCancelAll,
std::move(exec_write_cb));
return;
}
ProcessWriteQueue(std::move(prep_write));
};
PrepareWriteRequest(prep_write_request.handle(),
prep_write_request.offset(),
std::move(prep_write_request.value()),
std::move(prep_write_cb));
}
// End of this write, send and prepare for next item in overall write queue
else {
auto exec_write_cb = [this, callback = std::move(prep_write.callback)](
att::Result<> status) mutable {
callback(status);
// Now that this request is complete, remove it from the overall
// queue.
BT_DEBUG_ASSERT(!long_write_queue_.empty());
long_write_queue_.pop();
// If the super queue still has any long writes left to execute,
// initiate them
if (long_write_queue_.size() > 0) {
ProcessWriteQueue(std::move(long_write_queue_.front()));
}
};
ExecuteWriteRequest(att::ExecuteWriteFlag::kWritePending,
std::move(exec_write_cb));
}
}
void PrepareWriteRequest(att::Handle handle,
uint16_t offset,
const ByteBuffer& part_value,
PrepareCallback callback) override {
const size_t payload_size =
sizeof(att::PrepareWriteRequestParams) + part_value.size();
if (sizeof(att::OpCode) + payload_size > att_->mtu()) {
bt_log(TRACE, "gatt", "prepare write request payload exceeds MTU");
callback(ToResult(HostError::kPacketMalformed), BufferView());
return;
}
auto pdu = NewPDU(payload_size);
if (!pdu) {
callback(ToResult(HostError::kOutOfMemory), BufferView());
return;
}
att::PacketWriter writer(att::kPrepareWriteRequest, pdu.get());
auto params = writer.mutable_payload<att::PrepareWriteRequestParams>();
params->handle = htole16(handle);
params->offset = htole16(offset);
auto header_size = sizeof(att::Handle) + sizeof(uint16_t);
auto value_view = writer.mutable_payload_data().mutable_view(header_size);
part_value.Copy(&value_view);
auto rsp_cb = [callback = std::move(callback)](
att::Bearer::TransactionResult result) {
if (result.is_ok()) {
const att::PacketReader& rsp = result.value();
BT_DEBUG_ASSERT(rsp.opcode() == att::kPrepareWriteResponse);
callback(fit::ok(), rsp.payload_data());
return;
}
const auto& [error, handle] = result.error_value();
bt_log(DEBUG,
"gatt",
"prepare write request failed: %s, handle:"
"%#.4x",
bt_str(error),
handle);
callback(fit::error(error), BufferView());
};
att_->StartTransaction(std::move(pdu), BindCallback(std::move(rsp_cb)));
}
void ExecuteWriteRequest(att::ExecuteWriteFlag flag,
att::ResultFunction<> callback) override {
const size_t payload_size = sizeof(att::ExecuteWriteRequestParams);
if (sizeof(att::OpCode) + payload_size > att_->mtu()) {
// This really shouldn't happen because we aren't consuming any actual
// payload here, but just in case...
bt_log(TRACE, "gatt", "execute write request size exceeds MTU");
callback(ToResult(HostError::kPacketMalformed));
return;
}
auto pdu = NewPDU(payload_size);
if (!pdu) {
callback(ToResult(HostError::kOutOfMemory));
return;
}
att::PacketWriter writer(att::kExecuteWriteRequest, pdu.get());
auto params = writer.mutable_payload<att::ExecuteWriteRequestParams>();
params->flags = flag;
auto rsp_cb = [this, callback = std::move(callback)](
att::Bearer::TransactionResult result) {
if (result.is_ok()) {
const att::PacketReader& rsp = result.value();
BT_DEBUG_ASSERT(rsp.opcode() == att::kExecuteWriteResponse);
if (rsp.payload_size()) {
att_->ShutDown();
callback(ToResult(HostError::kPacketMalformed));
return;
}
callback(fit::ok());
return;
}
const att::Error& error = result.error_value().first;
bt_log(DEBUG, "gatt", "execute write request failed: %s", bt_str(error));
callback(fit::error(error));
};
att_->StartTransaction(std::move(pdu), BindCallback(std::move(rsp_cb)));
}
void WriteWithoutResponse(att::Handle handle,
const ByteBuffer& value,
att::ResultFunction<> callback) override {
const size_t payload_size = sizeof(att::WriteRequestParams) + value.size();
if (sizeof(att::OpCode) + payload_size > att_->mtu()) {
bt_log(DEBUG, "gatt", "write request payload exceeds MTU");
callback(ToResult(HostError::kFailed));
return;
}
auto pdu = NewPDU(payload_size);
if (!pdu) {
callback(ToResult(HostError::kOutOfMemory));
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);
[[maybe_unused]] bool _ = att_->SendWithoutResponse(std::move(pdu));
callback(fit::ok());
}
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_self_.GetWeakPtr(),
callback = std::move(callback)](auto rsp) mutable {
if (self.is_alive()) {
callback(rsp);
}
};
}
att::Bearer::WeakPtr att_;
att::Bearer::HandlerId not_handler_id_;
att::Bearer::HandlerId ind_handler_id_;
NotificationCallback notification_handler_;
// |long_write_queue_| contains long write requests, their
// associated callbacks and reliable write modes.
// Series of PrepareWrites are executed or cancelled at the same time so
// this is used to block while a single series is processed.
//
// While the top element is processed, the |PrepareWriteQueue| and callback
// will be empty and will be popped once the queue is cancelled or executed.
// Following the processing of each queue, the client will automatically
// process the next queue in the |long_write_queue_|.
std::queue<PreparedWrite> long_write_queue_;
WeakSelf<Client> weak_self_;
BT_DISALLOW_COPY_AND_ASSIGN_ALLOW_MOVE(Impl);
};
// static
std::unique_ptr<Client> Client::Create(att::Bearer::WeakPtr bearer) {
return std::make_unique<Impl>(std::move(bearer));
}
} // namespace bt::gatt