blob: 54e73ce62fdb0c62d08da64e613f8fa05da33a42 [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 <ddk/binding.h>
#include <zircon/status.h>
#include <memory>
#include "src/connectivity/bluetooth/core/bt-host/common/log.h"
using namespace bt;
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;
}
} // namespace
GattRemoteServiceDevice::GattRemoteServiceDevice(
zx_device_t* parent_device, bt::gatt::PeerId peer_id,
fbl::RefPtr<bt::gatt::RemoteService> service)
: loop_(&kAsyncLoopConfigNoAttachToThread),
parent_device_(parent_device),
dev_(nullptr),
peer_id_(peer_id),
service_(service) {
dev_proto_.version = DEVICE_OPS_VERSION;
dev_proto_.unbind = &GattRemoteServiceDevice::DdkUnbind;
dev_proto_.release = &GattRemoteServiceDevice::DdkRelease;
}
GattRemoteServiceDevice::~GattRemoteServiceDevice() {
if (dev_ != nullptr) {
device_remove(dev_);
dev_ = nullptr;
}
}
bt_gatt_svc_protocol_ops_t GattRemoteServiceDevice::proto_ops_ = {
.connect = &GattRemoteServiceDevice::OpConnect,
.stop = &GattRemoteServiceDevice::OpStop,
.read_characteristic = &GattRemoteServiceDevice::OpReadCharacteristic,
.read_long_characteristic =
&GattRemoteServiceDevice::OpReadLongCharacteristic,
.write_characteristic = &GattRemoteServiceDevice::OpWriteCharacteristic,
.enable_notifications = &GattRemoteServiceDevice::OpEnableNotifications,
};
zx_status_t GattRemoteServiceDevice::Bind() {
// 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]));
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},
};
bt_log(TRACE, "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_));
device_add_args_t args = {
.version = DEVICE_ADD_ARGS_VERSION,
.name = "bt-gatt-svc",
.ctx = this,
.ops = &dev_proto_,
.proto_id = ZX_PROTOCOL_BT_GATT_SVC,
.proto_ops = &proto_ops_,
.props = props,
.prop_count = 5,
.flags = 0,
};
zx_status_t status = device_add(parent_device_, &args, &dev_);
if (status != ZX_OK) {
dev_ = nullptr;
bt_log(ERROR, "bt-host",
"bt-gatt-svc: failed to publish child gatt device: %s",
zx_status_get_string(status));
return status;
}
loop_.StartThread("bt-host bt-gatt-svc");
return status;
}
void GattRemoteServiceDevice::Unbind() {
bt_log(TRACE, "bt-host", "bt-gatt-svc: unbinding service");
async::PostTask(loop_.dispatcher(), [this]() { loop_.Shutdown(); });
loop_.JoinThreads();
}
void GattRemoteServiceDevice::Release() { dev_ = nullptr; }
void GattRemoteServiceDevice::Connect(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 (auto& chr : chrcs) {
ddk_chars[char_idx].id = static_cast<bt_gatt_id_t>(chr.id());
CopyUUIDBytes(&ddk_chars[char_idx].type, chr.info().type);
ddk_chars[char_idx].properties = chr.info().properties;
// TODO(zbowling): remote extended properties are not implemented.
// ddk_chars[char_idx].extended_properties =
// chr.info().extended_properties;
auto& descriptors = chr.descriptors();
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& descriptor : descriptors) {
ddk_chars[char_idx].descriptor_list[desc_idx].id =
static_cast<bt_gatt_id_t>(descriptor.id());
CopyUUIDBytes(
&ddk_chars[char_idx].descriptor_list[desc_idx].type,
descriptor.info().type);
desc_idx++;
}
} else {
ddk_chars[char_idx].descriptor_count = 0;
ddk_chars[char_idx].descriptor_list = nullptr;
}
char_idx++;
}
bt_log(TRACE, "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::Stop() {
// TODO(zbowling): Unregister notifications on the remote service.
// We may replace this with an explicit unregister for notifications instead.
}
void GattRemoteServiceDevice::ReadCharacteristic(
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());
};
service_->ReadCharacteristic(static_cast<bt::gatt::IdType>(id),
std::move(read_callback), loop_.dispatcher());
return;
}
void GattRemoteServiceDevice::ReadLongCharacteristic(
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());
};
service_->ReadLongCharacteristic(static_cast<bt::gatt::IdType>(id), offset,
max_bytes, std::move(read_callback),
loop_.dispatcher());
return;
}
void GattRemoteServiceDevice::WriteCharacteristic(
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);
if (write_cb == nullptr) {
service_->WriteCharacteristicWithoutResponse(
static_cast<bt::gatt::IdType>(id), 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(static_cast<bt::gatt::IdType>(id),
std::move(data), std::move(status_callback),
loop_.dispatcher());
}
return;
}
void GattRemoteServiceDevice::EnableNotifications(
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);
};
service_->EnableNotifications(static_cast<bt::gatt::IdType>(id),
notif_callback, std::move(status_callback),
loop_.dispatcher());
return;
}
} // namespace bthost