blob: ff2fcbe7a56081d21215f9d82fc914300ac86b97 [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 <memory>
#include <zircon/status.h>
#include <ddk/binding.h>
using namespace btlib;
namespace bthost {
namespace {
void CopyUUIDBytes(bt_gatt_uuid_t* dest, const common::UUID source) {
memcpy(dest->bytes, source.value().data(), sizeof(dest->bytes));
}
bt_gatt_err_t ATTErrorToDDKError(btlib::att::ErrorCode error) {
// Both of these enums *should* be identical and values.
// Being explict so we get compiler warnings if either changes.
switch (error) {
case btlib::att::ErrorCode::kNoError:
return BT_GATT_ERR_NO_ERROR;
case btlib::att::ErrorCode::kInvalidHandle:
return BT_GATT_ERR_INVALID_HANDLE;
case btlib::att::ErrorCode::kReadNotPermitted:
return BT_GATT_ERR_READ_NOT_PERMITTED;
case btlib::att::ErrorCode::kWriteNotPermitted:
return BT_GATT_ERR_WRITE_NOT_PERMITTED;
case btlib::att::ErrorCode::kInvalidPDU:
return BT_GATT_ERR_INVALID_PDU;
case btlib::att::ErrorCode::kInsufficientAuthentication:
return BT_GATT_ERR_INSUFFICIENT_AUTHENTICATION;
case btlib::att::ErrorCode::kRequestNotSupported:
return BT_GATT_ERR_REQUEST_NOT_SUPPORTED;
case btlib::att::ErrorCode::kInvalidOffset:
return BT_GATT_ERR_INVALID_OFFSET;
case btlib::att::ErrorCode::kInsufficientAuthorization:
return BT_GATT_ERR_INSUFFICIENT_AUTHORIZATION;
case btlib::att::ErrorCode::kPrepareQueueFull:
return BT_GATT_ERR_PREPARE_QUEUE_FULL;
case btlib::att::ErrorCode::kAttributeNotFound:
return BT_GATT_ERR_ATTRIBUTE_NOT_FOUND;
case btlib::att::ErrorCode::kAttributeNotLong:
return BT_GATT_ERR_INVALID_ATTRIBUTE_VALUE_LENGTH;
case btlib::att::ErrorCode::kInsufficientEncryptionKeySize:
return BT_GATT_ERR_INSUFFICIENT_ENCRYPTION_KEY_SIZE;
case btlib::att::ErrorCode::kInvalidAttributeValueLength:
return BT_GATT_ERR_INVALID_ATTRIBUTE_VALUE_LENGTH;
case btlib::att::ErrorCode::kUnlikelyError:
return BT_GATT_ERR_UNLIKELY_ERROR;
case btlib::att::ErrorCode::kInsufficientEncryption:
return BT_GATT_ERR_INSUFFICIENT_ENCRYPTION;
case btlib::att::ErrorCode::kUnsupportedGroupType:
return BT_GATT_ERR_UNSUPPORTED_GROUP_TYPE;
case btlib::att::ErrorCode::kInsufficientResources:
return BT_GATT_ERR_INSUFFICIENT_RESOURCES;
}
return BT_GATT_ERR_NO_ERROR;
}
} // namespace
GattRemoteServiceDevice::GattRemoteServiceDevice(
zx_device_t* parent_device, const std::string& peer_id,
fbl::RefPtr<btlib::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_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 eiher 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 common::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;
common::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},
};
FXL_VLOG(1) << "bt-gatt-svc binding to UUID16(" << uuid16 << "), UUID128_1("
<< uuid01 << "), UUID128_2(" << uuid02 << "), UUID128_3("
<< uuid03 << "), UUID128_4(" << uuid04 << ") peer(" << 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;
FXL_LOG(ERROR) << "bt-host: Failed to publish child gatt device: "
<< zx_status_get_string(status);
return status;
}
loop_.StartThread("bt-host bt-gatt-svc");
return status;
}
void GattRemoteServiceDevice::Unbind() {
FXL_VLOG(1) << "bt-gatt-svc: Unbinding service";
async::PostTask(loop_.dispatcher(), [this]() { loop_.Shutdown(); });
loop_.JoinThreads();
}
void GattRemoteServiceDevice::Release() { dev_ = nullptr; }
zx_status_t GattRemoteServiceDevice::Connect(void* cookie,
bt_gatt_connect_cb connect_cb) {
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());
unsigned long 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].descriptors =
new bt_gatt_descriptor_t[descriptors.size()];
ddk_chars[char_idx].num_descriptors = descriptors.size();
unsigned long desc_idx = 0;
for (auto& descriptor : descriptors) {
ddk_chars[char_idx].descriptors[desc_idx].id =
static_cast<bt_gatt_id_t>(descriptor.id());
CopyUUIDBytes(&ddk_chars[char_idx].descriptors[desc_idx].type,
descriptor.info().type);
desc_idx++;
}
} else {
ddk_chars[char_idx].num_descriptors = 0;
ddk_chars[char_idx].descriptors = nullptr;
}
char_idx++;
}
FXL_VLOG(1) << "bt-gatt-svc: connecting. discovered " << char_idx
<< " characteristics.";
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].descriptors != nullptr) {
delete[] ddk_chars[char_idx].descriptors;
ddk_chars[char_idx].descriptors = nullptr;
}
}
},
loop_.dispatcher());
});
return ZX_OK;
}
void GattRemoteServiceDevice::Stop() {
// TODO(zbowling): Unregister notifications on the remote service.
// We may replace this with an explict unregister for notifications instead.
}
zx_status_t GattRemoteServiceDevice::ReadCharacteristic(
bt_gatt_id_t id, void* cookie, bt_gatt_read_characteristic_cb read_cb) {
auto read_callback = [id, cookie, read_cb](att::Status status,
const common::ByteBuffer& buff) {
bt_gatt_err_t error_code = ATTErrorToDDKError(status.protocol_error());
bt_gatt_status_t ddk_status = {
.status = ZX_OK,
.att_ecode = error_code,
};
read_cb(cookie, ddk_status, id, buff.data(), buff.size());
};
service_->ReadCharacteristic(static_cast<btlib::gatt::IdType>(id),
std::move(read_callback), loop_.dispatcher());
return ZX_OK;
}
zx_status_t GattRemoteServiceDevice::ReadLongCharacteristic(
bt_gatt_id_t id, void* cookie, uint16_t offset, size_t max_bytes,
bt_gatt_read_characteristic_cb read_cb) {
auto read_callback = [id, cookie, read_cb](att::Status status,
const common::ByteBuffer& buff) {
bt_gatt_err_t error_code = ATTErrorToDDKError(status.protocol_error());
bt_gatt_status_t ddk_status = {
.status = ZX_OK,
.att_ecode = error_code,
};
read_cb(cookie, ddk_status, id, buff.data(), buff.size());
};
service_->ReadLongCharacteristic(static_cast<btlib::gatt::IdType>(id), offset,
max_bytes, std::move(read_callback),
loop_.dispatcher());
return ZX_OK;
}
zx_status_t GattRemoteServiceDevice::WriteCharacteristic(
bt_gatt_id_t id, void* cookie, const uint8_t* buff, size_t len,
bt_gatt_status_cb write_cb) {
std::vector<uint8_t> data(buff, buff + len);
if (write_cb == nullptr) {
service_->WriteCharacteristicWithoutResponse(
static_cast<btlib::gatt::IdType>(id), std::move(data));
} else {
auto status_callback = [cookie, id, write_cb](btlib::att::Status status) {
bt_gatt_err_t error_code = ATTErrorToDDKError(status.protocol_error());
bt_gatt_status_t ddk_status = {
.status = ZX_OK,
.att_ecode = error_code,
};
write_cb(cookie, ddk_status, id);
};
service_->WriteCharacteristic(static_cast<btlib::gatt::IdType>(id),
std::move(data), std::move(status_callback),
loop_.dispatcher());
}
return ZX_OK;
}
zx_status_t GattRemoteServiceDevice::EnableNotifications(
bt_gatt_id_t id, void* cookie, bt_gatt_status_cb status_cb,
bt_gatt_notification_value_cb value_cb) {
auto notif_callback = [cookie, id, value_cb](const common::ByteBuffer& buff) {
value_cb(cookie, id, buff.data(), buff.size());
};
auto status_callback = [cookie, id, status_cb](
btlib::att::Status status,
btlib::gatt::IdType handler_id) {
bt_gatt_err_t error_code = ATTErrorToDDKError(status.protocol_error());
bt_gatt_status_t ddk_status = {
.status = ZX_OK,
.att_ecode = error_code,
};
status_cb(cookie, ddk_status, id);
};
service_->EnableNotifications(static_cast<btlib::gatt::IdType>(id),
notif_callback, std::move(status_callback),
loop_.dispatcher());
return ZX_OK;
}
} // namespace bthost