blob: e225b8005216373d0a9e9f6466d281ba9ad151ca [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 "gatt_remote_service_device.h"
#include <zircon/status.h>
#include <memory>
#include <ddk/binding.h>
#include "src/connectivity/bluetooth/core/bt-host/common/log.h"
#include "src/connectivity/bluetooth/core/bt-host/gatt/gatt_defs.h"
using namespace bt;
using bt::gatt::CharacteristicHandle;
namespace bthost {
namespace {
void CopyUUIDBytes(bt_gatt_uuid_t* dest, const UUID source) {
memcpy(dest->bytes, source.value().data(), sizeof(dest->bytes));
}
bt_gatt_err_t AttErrorToDdkError(bt::att::ErrorCode error) {
// Both of these enums *should* be identical and values.
// Being explicit so we get compiler warnings if either changes.
switch (error) {
case bt::att::ErrorCode::kNoError:
return BT_GATT_ERR_NO_ERROR;
case bt::att::ErrorCode::kInvalidHandle:
return BT_GATT_ERR_INVALID_HANDLE;
case bt::att::ErrorCode::kReadNotPermitted:
return BT_GATT_ERR_READ_NOT_PERMITTED;
case bt::att::ErrorCode::kWriteNotPermitted:
return BT_GATT_ERR_WRITE_NOT_PERMITTED;
case bt::att::ErrorCode::kInvalidPDU:
return BT_GATT_ERR_INVALID_PDU;
case bt::att::ErrorCode::kInsufficientAuthentication:
return BT_GATT_ERR_INSUFFICIENT_AUTHENTICATION;
case bt::att::ErrorCode::kRequestNotSupported:
return BT_GATT_ERR_REQUEST_NOT_SUPPORTED;
case bt::att::ErrorCode::kInvalidOffset:
return BT_GATT_ERR_INVALID_OFFSET;
case bt::att::ErrorCode::kInsufficientAuthorization:
return BT_GATT_ERR_INSUFFICIENT_AUTHORIZATION;
case bt::att::ErrorCode::kPrepareQueueFull:
return BT_GATT_ERR_PREPARE_QUEUE_FULL;
case bt::att::ErrorCode::kAttributeNotFound:
return BT_GATT_ERR_ATTRIBUTE_NOT_FOUND;
case bt::att::ErrorCode::kAttributeNotLong:
return BT_GATT_ERR_INVALID_ATTRIBUTE_VALUE_LENGTH;
case bt::att::ErrorCode::kInsufficientEncryptionKeySize:
return BT_GATT_ERR_INSUFFICIENT_ENCRYPTION_KEY_SIZE;
case bt::att::ErrorCode::kInvalidAttributeValueLength:
return BT_GATT_ERR_INVALID_ATTRIBUTE_VALUE_LENGTH;
case bt::att::ErrorCode::kUnlikelyError:
return BT_GATT_ERR_UNLIKELY_ERROR;
case bt::att::ErrorCode::kInsufficientEncryption:
return BT_GATT_ERR_INSUFFICIENT_ENCRYPTION;
case bt::att::ErrorCode::kUnsupportedGroupType:
return BT_GATT_ERR_UNSUPPORTED_GROUP_TYPE;
case bt::att::ErrorCode::kInsufficientResources:
return BT_GATT_ERR_INSUFFICIENT_RESOURCES;
}
return BT_GATT_ERR_NO_ERROR;
}
zx_status_t HostErrorToZxError(bt::HostError error) {
switch (error) {
case bt::HostError::kNoError:
return ZX_OK;
case bt::HostError::kNotFound:
return ZX_ERR_NOT_FOUND;
case bt::HostError::kNotReady:
return ZX_ERR_SHOULD_WAIT;
case bt::HostError::kTimedOut:
return ZX_ERR_TIMED_OUT;
case bt::HostError::kInvalidParameters:
return ZX_ERR_INVALID_ARGS;
case bt::HostError::kCanceled:
return ZX_ERR_CANCELED;
case bt::HostError::kNotSupported:
return ZX_ERR_NOT_SUPPORTED;
case bt::HostError::kLinkDisconnected:
return ZX_ERR_CONNECTION_ABORTED;
case bt::HostError::kOutOfMemory:
return ZX_ERR_NO_MEMORY;
default:
return ZX_ERR_INTERNAL;
}
}
bt_gatt_status_t AttStatusToDdkStatus(const bt::att::Status& status) {
bt_gatt_status_t ddk_status = {
.status = HostErrorToZxError(status.error()),
.att_ecode = BT_GATT_ERR_NO_ERROR,
};
if (status.is_protocol_error()) {
ddk_status.att_ecode = AttErrorToDdkError(status.protocol_error());
}
return ddk_status;
}
// TODO(fxbug.dev/63450): The 64 bit `bt_gatt_id_t` can overflow the 16 bits of a bt:att::Handle
// that underlies CharacteristicHandle when directly casted. Fix this.
bt::gatt::CharacteristicHandle CharacteristicHandleFromBanjo(bt_gatt_id_t banjo_id) {
if (banjo_id & 0xFFFF) {
bt_log(ERROR, "bt-host",
"bt-gatt-svc: Casting a 64-bit FIDL GATT ID with `bits[16, 63] != 0` to 16-bit "
"Characteristic Handle");
}
return bt::gatt::CharacteristicHandle(static_cast<bt::att::Handle>(banjo_id));
}
} // namespace
GattRemoteServiceDevice::GattRemoteServiceDevice(zx_device_t* parent, bt::gatt::PeerId peer_id,
fbl::RefPtr<bt::gatt::RemoteService> service)
: ddk::Device<GattRemoteServiceDevice, ddk::Unbindable>(parent),
loop_(&kAsyncLoopConfigNoAttachToCurrentThread),
peer_id_(peer_id),
service_(service) {}
// static
zx_status_t GattRemoteServiceDevice::Publish(zx_device_t* parent, bt::gatt::PeerId peer_id,
fbl::RefPtr<bt::gatt::RemoteService> service) {
ZX_DEBUG_ASSERT(parent);
ZX_DEBUG_ASSERT(service);
// The bind program of an attaching device driver can either bind using to the
// well known short 16 bit UUID of the service if available or the full 128
// bit UUID (split across 4 32 bit values).
const UUID& uuid = service->uuid();
uint32_t uuid16 = 0;
if (uuid.CompactSize() == 2) {
uuid16 = le16toh(*reinterpret_cast<const uint16_t*>(uuid.CompactView().data()));
}
uint32_t uuid01, uuid02, uuid03, uuid04 = 0;
UInt128 uuid_bytes = uuid.value();
uuid01 = le32toh(*reinterpret_cast<uint32_t*>(&uuid_bytes[0]));
uuid02 = le32toh(*reinterpret_cast<uint32_t*>(&uuid_bytes[4]));
uuid03 = le32toh(*reinterpret_cast<uint32_t*>(&uuid_bytes[8]));
uuid04 = le32toh(*reinterpret_cast<uint32_t*>(&uuid_bytes[12]));
bt_log(DEBUG, "bt-host",
"bt-gatt-svc: binding to UUID16(%#04x), UUID128(1: %08x, 2: %08x, 3: %08x, 4: %08x), "
"peer: %s",
uuid16, uuid01, uuid02, uuid03, uuid04, bt_str(peer_id));
zx_device_prop_t props[] = {
{BIND_BT_GATT_SVC_UUID16, 0, uuid16}, {BIND_BT_GATT_SVC_UUID128_1, 0, uuid01},
{BIND_BT_GATT_SVC_UUID128_2, 0, uuid02}, {BIND_BT_GATT_SVC_UUID128_3, 0, uuid03},
{BIND_BT_GATT_SVC_UUID128_4, 0, uuid04},
};
auto args = ddk::DeviceAddArgs("bt-gatt-svc").set_props(props);
auto dev = std::make_unique<GattRemoteServiceDevice>(parent, peer_id, service);
TRACE_DURATION_BEGIN("bluetooth", "GattRemoteServiceDevice::Bind DdkAdd");
zx_status_t status = dev->DdkAdd(args);
TRACE_DURATION_END("bluetooth", "GattRemoteServiceDevice::Bind DdkAdd");
if (status != ZX_OK) {
bt_log(ERROR, "bt-gatt-svc", "failed to publish device: %s", zx_status_get_string(status));
return status;
}
// Kick off the dedicated dispatcher where our children will process GATT events from the stack.
dev->StartThread();
// The DDK owns the memory now.
__UNUSED auto* ptr = dev.release();
return status;
}
void GattRemoteServiceDevice::DdkRelease() {
bt_log(TRACE, "bt-host", "bt-gatt-svc: release");
// The DDK no longer owns this context object, so delete it.
delete this;
}
void GattRemoteServiceDevice::DdkUnbind(ddk::UnbindTxn txn) {
bt_log(DEBUG, "bt-host", "bt-gatt-svc: unbind");
async::PostTask(loop_.dispatcher(), [this]() { loop_.Shutdown(); });
loop_.JoinThreads();
txn.Reply();
}
void GattRemoteServiceDevice::StartThread() {
// NOTE: This method is expected to run on the bt-host thread.
ZX_DEBUG_ASSERT(thread_checker_.is_thread_valid());
TRACE_DURATION_BEGIN("bluetooth", "GattRemoteServiceDevice::StartThread");
// Start processing GATT tasks.
loop_.StartThread("bt-host bt-gatt-svc");
// There are a few possible race conditions between the "GATT service removed handler" and the
// GattRemoteServiceDevice destruction. The following race conditions *could* lead to a
// use-after-free:
//
// * During initialization, it is possible for Publish() to fail to add the bt-gatt-svc device
// if the service handler runs while bt-host is unbinding. We register the removal handler
// only after the device has been successfully created, to ensure that it doesn't get run
// after Publish() returns early and the context object gets deleted.
//
// * Under regular conditions that lead to unbinding a bt-gatt-svc.
//
// A bt-gatt-svc device can be unbound during the following events:
//
// 1. The parent HostDevice finishes its unbind, which triggers its children to unbind.
// 2. The removed handler gets called due to a Bluetooth stack event, such as a disconnection
// or a change in the peer's ATT database.
// 3. The removed handler gets called while HostDevice is shutting down, as part of the
// Bluetooth stack's clean up procedure. The GATT layer always notifies this callback during
// its destruction.
//
// The following invariants *should* prevent this callback to run with a dangling pointer in the
// unbind conditions:
//
// a. HostDevice always drains and shuts down its event loop completely before it finishes
// unbinding.
// b. GattRemoteServiceDevice always drains and shuts down its own |loop_| before its unbind
// finishes, and thus this callback must finish running before release gets called.
// c. #1 is not an issue because DdkUnbind should only get called after HostDevice finishes
// unbinding.
// d. #3 is not an issue because it is guaranteed to occur before HostDevice finishes unbinding,
// due to invariant a.
// e. #2 is not an issue because it involves regular operation outside of shutdown.
service_->AddRemovedHandler([this] { DdkAsyncRemove(); }, loop_.dispatcher());
TRACE_DURATION_END("bluetooth", "GattRemoteServiceDevice::StartThread");
}
void GattRemoteServiceDevice::BtGattSvcConnect(bt_gatt_svc_connect_callback connect_cb,
void* cookie) {
async::PostTask(loop_.dispatcher(), [this, connect_cb, cookie]() {
service_->DiscoverCharacteristics(
[connect_cb, cookie](att::Status cb_status, const auto& chrcs) {
auto ddk_chars = std::make_unique<bt_gatt_chr[]>(chrcs.size());
size_t char_idx = 0;
for (const auto& [id, chrc_pair] : chrcs) {
const auto& [chr, descriptors] = chrc_pair;
ddk_chars[char_idx].id = static_cast<bt_gatt_id_t>(id.value);
CopyUUIDBytes(&ddk_chars[char_idx].type, chr.type);
ddk_chars[char_idx].properties = chr.properties;
// TODO(zbowling): remote extended properties are not implemented.
// ddk_chars[char_idx].extended_properties =
// chr.extended_properties;
if (descriptors.size() > 0) {
ddk_chars[char_idx].descriptor_list = new bt_gatt_descriptor_t[descriptors.size()];
ddk_chars[char_idx].descriptor_count = descriptors.size();
size_t desc_idx = 0;
for (auto& [id, descriptor] : descriptors) {
ddk_chars[char_idx].descriptor_list[desc_idx].id =
static_cast<bt_gatt_id_t>(id.value);
CopyUUIDBytes(&ddk_chars[char_idx].descriptor_list[desc_idx].type, descriptor.type);
desc_idx++;
}
} else {
ddk_chars[char_idx].descriptor_count = 0;
ddk_chars[char_idx].descriptor_list = nullptr;
}
char_idx++;
}
bt_log(DEBUG, "bt-host", "bt-gatt-svc: connected; discovered %zu characteristics",
char_idx);
bt_gatt_status_t status = {.status = ZX_OK};
connect_cb(cookie, &status, ddk_chars.get(), char_idx);
// Cleanup.
for (char_idx = 0; char_idx < chrcs.size(); char_idx++) {
if (ddk_chars[char_idx].descriptor_list != nullptr) {
delete[] ddk_chars[char_idx].descriptor_list;
ddk_chars[char_idx].descriptor_list = nullptr;
}
}
},
loop_.dispatcher());
});
return;
}
void GattRemoteServiceDevice::BtGattSvcStop() {
// TODO(zbowling): Unregister notifications on the remote service.
// We may replace this with an explicit unregister for notifications instead.
}
void GattRemoteServiceDevice::BtGattSvcReadCharacteristic(
bt_gatt_id_t id, bt_gatt_svc_read_characteristic_callback read_cb, void* cookie) {
auto read_callback = [id, cookie, read_cb](att::Status status, const ByteBuffer& buff) {
bt_gatt_status_t ddk_status = AttStatusToDdkStatus(status);
read_cb(cookie, &ddk_status, id, buff.data(), buff.size());
};
// TODO(fxbug.dev/63450): Fix CharacteristicHandleFromBanjo to not cast a uint64_t into uint16_t.
service_->ReadCharacteristic(CharacteristicHandleFromBanjo(id), std::move(read_callback),
loop_.dispatcher());
return;
}
void GattRemoteServiceDevice::BtGattSvcReadLongCharacteristic(
bt_gatt_id_t id, uint16_t offset, size_t max_bytes,
bt_gatt_svc_read_characteristic_callback read_cb, void* cookie) {
auto read_callback = [id, cookie, read_cb](att::Status status, const ByteBuffer& buff) {
bt_gatt_status_t ddk_status = AttStatusToDdkStatus(status);
read_cb(cookie, &ddk_status, id, buff.data(), buff.size());
};
// TODO(fxbug.dev/63450): Fix CharacteristicHandleFromBanjo to not cast a uint64_t into uint16_t.
service_->ReadLongCharacteristic(CharacteristicHandleFromBanjo(id), offset, max_bytes,
std::move(read_callback), loop_.dispatcher());
return;
}
void GattRemoteServiceDevice::BtGattSvcWriteCharacteristic(
bt_gatt_id_t id, const void* buff, size_t len,
bt_gatt_svc_write_characteristic_callback write_cb, void* cookie) {
auto* buf = static_cast<const uint8_t*>(buff);
std::vector<uint8_t> data(buf, buf + len);
// TODO(fxbug.dev/63450): Fix CharacteristicHandleFromBanjo to not cast a uint64_t into uint16_t.
auto handle = CharacteristicHandleFromBanjo(id);
if (write_cb == nullptr) {
service_->WriteCharacteristicWithoutResponse(handle, std::move(data));
} else {
auto status_callback = [cookie, id, write_cb](bt::att::Status status) {
bt_gatt_status_t ddk_status = AttStatusToDdkStatus(status);
write_cb(cookie, &ddk_status, id);
};
service_->WriteCharacteristic(handle, std::move(data), std::move(status_callback),
loop_.dispatcher());
}
return;
}
void GattRemoteServiceDevice::BtGattSvcEnableNotifications(
bt_gatt_id_t id, const bt_gatt_notification_value_t* value,
bt_gatt_svc_enable_notifications_callback status_cb, void* cookie) {
auto value_cb = *value;
auto notif_callback = [id, value_cb](const ByteBuffer& buff) {
value_cb.callback(value_cb.ctx, id, buff.data(), buff.size());
};
auto status_callback = [cookie, id, status_cb](bt::att::Status status,
bt::gatt::IdType handler_id) {
bt_gatt_status_t ddk_status = AttStatusToDdkStatus(status);
status_cb(cookie, &ddk_status, id);
};
// TODO(fxbug.dev/63450): Fix CharacteristicHandleFromBanjo to not cast a uint64_t into uint16_t.
service_->EnableNotifications(CharacteristicHandleFromBanjo(id), notif_callback,
std::move(status_callback), loop_.dispatcher());
return;
}
} // namespace bthost