OTS add Calculate checksum feature support. OTS client add object calculate checksum function. Signed-off-by: Pirun Lee <pirun.lee@nordicsemi.no>
670 lines
18 KiB
C
670 lines
18 KiB
C
/*
|
|
* Copyright (c) 2020 Nordic Semiconductor ASA
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#include <zephyr/types.h>
|
|
#include <stddef.h>
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
#include <zephyr/init.h>
|
|
#include <zephyr/sys/printk.h>
|
|
#include <zephyr/sys/byteorder.h>
|
|
#include <zephyr/kernel.h>
|
|
|
|
#include <zephyr/bluetooth/bluetooth.h>
|
|
#include <zephyr/bluetooth/l2cap.h>
|
|
#include <zephyr/bluetooth/conn.h>
|
|
#include <zephyr/bluetooth/uuid.h>
|
|
#include <zephyr/bluetooth/gatt.h>
|
|
|
|
#include <zephyr/sys/check.h>
|
|
|
|
#include <zephyr/bluetooth/services/ots.h>
|
|
#include "ots_internal.h"
|
|
#include "ots_obj_manager_internal.h"
|
|
#include "ots_dir_list_internal.h"
|
|
|
|
#include <zephyr/logging/log.h>
|
|
|
|
LOG_MODULE_REGISTER(bt_ots, CONFIG_BT_OTS_LOG_LEVEL);
|
|
|
|
#if defined(CONFIG_BT_OTS_OACP_CREATE_SUPPORT)
|
|
#define OACP_FEAT_BIT_CREATE BIT(BT_OTS_OACP_FEAT_CREATE)
|
|
#else
|
|
#define OACP_FEAT_BIT_CREATE 0
|
|
#endif
|
|
|
|
#if defined(CONFIG_BT_OTS_OACP_DELETE_SUPPORT)
|
|
#define OACP_FEAT_BIT_DELETE BIT(BT_OTS_OACP_FEAT_DELETE)
|
|
#else
|
|
#define OACP_FEAT_BIT_DELETE 0
|
|
#endif
|
|
|
|
#if defined(BT_OTS_OACP_CHECKSUM_SUPPORT)
|
|
#define OACP_FEAT_BIT_CRC BIT(BT_OTS_OACP_FEAT_CHECKSUM)
|
|
#else
|
|
#define OACP_FEAT_BIT_CRC 0
|
|
#endif
|
|
|
|
#if defined(CONFIG_BT_OTS_OACP_READ_SUPPORT)
|
|
#define OACP_FEAT_BIT_READ BIT(BT_OTS_OACP_FEAT_READ)
|
|
#else
|
|
#define OACP_FEAT_BIT_READ 0
|
|
#endif
|
|
|
|
#if defined(CONFIG_BT_OTS_OACP_WRITE_SUPPORT)
|
|
#define OACP_FEAT_BIT_WRITE BIT(BT_OTS_OACP_FEAT_WRITE)
|
|
#else
|
|
#define OACP_FEAT_BIT_WRITE 0
|
|
#endif
|
|
|
|
#if defined(CONFIG_BT_OTS_OACP_PATCH_SUPPORT)
|
|
#define OACP_FEAT_BIT_PATCH BIT(BT_OTS_OACP_FEAT_PATCH)
|
|
#else
|
|
#define OACP_FEAT_BIT_PATCH 0
|
|
#endif
|
|
|
|
/* OACP features supported by Kconfig */
|
|
#define OACP_FEAT ( \
|
|
OACP_FEAT_BIT_CREATE | \
|
|
OACP_FEAT_BIT_DELETE | \
|
|
OACP_FEAT_BIT_CRC | \
|
|
OACP_FEAT_BIT_READ | \
|
|
OACP_FEAT_BIT_WRITE | \
|
|
OACP_FEAT_BIT_PATCH)
|
|
|
|
#if defined(CONFIG_BT_OTS_OLCP_GO_TO_SUPPORT)
|
|
#define OLCP_FEAT_BIT_GOTO BIT(BT_OTS_OLCP_FEAT_GO_TO)
|
|
#else
|
|
#define OLCP_FEAT_BIT_GOTO 0
|
|
#endif
|
|
|
|
/* OLCP features supported by Kconfig */
|
|
#define OLCP_FEAT OLCP_FEAT_BIT_GOTO
|
|
|
|
static bool ots_obj_validate_prop_against_oacp(uint32_t prop, uint32_t oacp)
|
|
{
|
|
if (BT_OTS_OBJ_GET_PROP_DELETE(prop) > 0 && BT_OTS_OACP_GET_FEAT_DELETE(oacp) == 0) {
|
|
return false;
|
|
}
|
|
|
|
if (BT_OTS_OBJ_GET_PROP_EXECUTE(prop) > 0 && BT_OTS_OACP_GET_FEAT_EXECUTE(oacp) == 0) {
|
|
return false;
|
|
}
|
|
|
|
if (BT_OTS_OBJ_GET_PROP_READ(prop) > 0 && BT_OTS_OACP_GET_FEAT_READ(oacp) == 0) {
|
|
return false;
|
|
}
|
|
|
|
if (BT_OTS_OBJ_GET_PROP_WRITE(prop) > 0 && BT_OTS_OACP_GET_FEAT_WRITE(oacp) == 0) {
|
|
return false;
|
|
}
|
|
|
|
if (BT_OTS_OBJ_GET_PROP_APPEND(prop) > 0 && BT_OTS_OACP_GET_FEAT_APPEND(oacp) == 0) {
|
|
return false;
|
|
}
|
|
|
|
if (BT_OTS_OBJ_GET_PROP_TRUNCATE(prop) > 0 && BT_OTS_OACP_GET_FEAT_TRUNCATE(oacp) == 0) {
|
|
return false;
|
|
}
|
|
|
|
if (BT_OTS_OBJ_GET_PROP_PATCH(prop) > 0 && BT_OTS_OACP_GET_FEAT_PATCH(oacp) == 0) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static ssize_t ots_feature_read(struct bt_conn *conn,
|
|
const struct bt_gatt_attr *attr, void *buf,
|
|
uint16_t len, uint16_t offset)
|
|
{
|
|
struct bt_ots *ots = (struct bt_ots *) attr->user_data;
|
|
|
|
LOG_DBG("OTS Feature GATT Read Operation");
|
|
|
|
return bt_gatt_attr_read(conn, attr, buf, len, offset, &ots->features,
|
|
sizeof(ots->features));
|
|
}
|
|
|
|
static ssize_t ots_obj_name_read(struct bt_conn *conn,
|
|
const struct bt_gatt_attr *attr, void *buf,
|
|
uint16_t len, uint16_t offset)
|
|
{
|
|
struct bt_ots *ots = (struct bt_ots *) attr->user_data;
|
|
|
|
LOG_DBG("OTS Object Name GATT Read Operation");
|
|
|
|
if (!ots->cur_obj) {
|
|
LOG_DBG("No Current Object selected in OTS!");
|
|
return BT_GATT_ERR(BT_GATT_OTS_OBJECT_NOT_SELECTED);
|
|
}
|
|
|
|
return bt_gatt_attr_read(conn, attr, buf, len, offset,
|
|
ots->cur_obj->metadata.name,
|
|
strlen(ots->cur_obj->metadata.name));
|
|
}
|
|
|
|
#if defined(CONFIG_BT_OTS_OBJ_NAME_WRITE_SUPPORT)
|
|
ssize_t ots_obj_name_write(struct bt_conn *conn,
|
|
const struct bt_gatt_attr *attr,
|
|
const void *buf, uint16_t len,
|
|
uint16_t offset, uint8_t flags)
|
|
{
|
|
struct bt_ots *ots = (struct bt_ots *) attr->user_data;
|
|
struct bt_gatt_ots_object *obj = NULL;
|
|
int rc = 0;
|
|
char name[CONFIG_BT_OTS_OBJ_MAX_NAME_LEN + 1];
|
|
|
|
LOG_DBG("OTS Object Name GATT Write Operation");
|
|
|
|
if (!ots->cur_obj) {
|
|
LOG_DBG("No Current Object selected in OTS!");
|
|
return BT_GATT_ERR(BT_GATT_OTS_OBJECT_NOT_SELECTED);
|
|
}
|
|
|
|
if (IS_ENABLED(CONFIG_BT_OTS_DIR_LIST_OBJ) &&
|
|
ots->cur_obj->id == OTS_OBJ_ID_DIR_LIST) {
|
|
LOG_DBG("Rejecting name write for the directory list object.");
|
|
return BT_GATT_ERR(BT_GATT_OTS_WRITE_REQUEST_REJECTED);
|
|
}
|
|
|
|
if (offset > 0) {
|
|
LOG_DBG("Rejecting a long write, offset must be 0!");
|
|
return BT_GATT_ERR(BT_GATT_OTS_WRITE_REQUEST_REJECTED);
|
|
}
|
|
|
|
if (len > CONFIG_BT_OTS_OBJ_MAX_NAME_LEN) {
|
|
LOG_DBG("Object name is too long!");
|
|
return BT_GATT_ERR(BT_GATT_OTS_WRITE_REQUEST_REJECTED);
|
|
}
|
|
|
|
/* Construct a temporary name for duplication detection */
|
|
memcpy(name, buf, len);
|
|
name[len] = '\0';
|
|
|
|
rc = bt_gatt_ots_obj_manager_first_obj_get(ots->obj_manager, &obj);
|
|
while (rc == 0) {
|
|
if (obj != ots->cur_obj && strcmp(name, obj->metadata.name) == 0) {
|
|
LOG_DBG("Object name is duplicated!");
|
|
return BT_GATT_ERR(BT_GATT_OTS_OBJECT_NAME_ALREADY_EXISTS);
|
|
}
|
|
rc = bt_gatt_ots_obj_manager_next_obj_get(ots->obj_manager, obj, &obj);
|
|
}
|
|
|
|
/* No duplicate detected, notify application and update real object name */
|
|
if (ots->cb->obj_name_written) {
|
|
ots->cb->obj_name_written(ots, conn, ots->cur_obj->id,
|
|
ots->cur_obj->metadata.name, name);
|
|
}
|
|
|
|
strcpy(ots->cur_obj->metadata.name, name);
|
|
|
|
return len;
|
|
}
|
|
#endif
|
|
|
|
static ssize_t ots_obj_type_read(struct bt_conn *conn,
|
|
const struct bt_gatt_attr *attr, void *buf,
|
|
uint16_t len, uint16_t offset)
|
|
{
|
|
struct bt_ots *ots = (struct bt_ots *) attr->user_data;
|
|
struct bt_ots_obj_metadata *obj_meta;
|
|
|
|
LOG_DBG("OTS Object Type GATT Read Operation");
|
|
|
|
if (!ots->cur_obj) {
|
|
LOG_DBG("No Current Object selected in OTS!");
|
|
return BT_GATT_ERR(BT_GATT_OTS_OBJECT_NOT_SELECTED);
|
|
}
|
|
|
|
obj_meta = &ots->cur_obj->metadata;
|
|
switch (obj_meta->type.uuid.type) {
|
|
case BT_UUID_TYPE_16:
|
|
return bt_gatt_attr_read(conn, attr, buf, len, offset,
|
|
&obj_meta->type.uuid_16.val,
|
|
sizeof(obj_meta->type.uuid_16.val));
|
|
case BT_UUID_TYPE_128:
|
|
return bt_gatt_attr_read(conn, attr, buf, len, offset,
|
|
obj_meta->type.uuid_128.val,
|
|
sizeof(obj_meta->type.uuid_128.val));
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
static ssize_t ots_obj_size_read(struct bt_conn *conn,
|
|
const struct bt_gatt_attr *attr, void *buf,
|
|
uint16_t len, uint16_t offset)
|
|
{
|
|
struct bt_ots *ots = (struct bt_ots *) attr->user_data;
|
|
|
|
LOG_DBG("OTS Object Size GATT Read Operation");
|
|
|
|
if (!ots->cur_obj) {
|
|
LOG_DBG("No Current Object selected in OTS!");
|
|
return BT_GATT_ERR(BT_GATT_OTS_OBJECT_NOT_SELECTED);
|
|
}
|
|
|
|
return bt_gatt_attr_read(conn, attr, buf, len, offset,
|
|
&ots->cur_obj->metadata.size,
|
|
sizeof(ots->cur_obj->metadata.size));
|
|
}
|
|
|
|
static ssize_t ots_obj_id_read(struct bt_conn *conn,
|
|
const struct bt_gatt_attr *attr, void *buf,
|
|
uint16_t len, uint16_t offset)
|
|
{
|
|
struct bt_ots *ots = (struct bt_ots *) attr->user_data;
|
|
uint8_t id[BT_OTS_OBJ_ID_SIZE];
|
|
char id_str[BT_OTS_OBJ_ID_STR_LEN];
|
|
|
|
LOG_DBG("OTS Object ID GATT Read Operation");
|
|
|
|
if (!ots->cur_obj) {
|
|
LOG_DBG("No Current Object selected in OTS!");
|
|
return BT_GATT_ERR(BT_GATT_OTS_OBJECT_NOT_SELECTED);
|
|
}
|
|
|
|
sys_put_le48(ots->cur_obj->id, id);
|
|
|
|
bt_ots_obj_id_to_str(ots->cur_obj->id, id_str,
|
|
sizeof(id_str));
|
|
LOG_DBG("Current Object ID: %s", id_str);
|
|
|
|
return bt_gatt_attr_read(conn, attr, buf, len, offset, id, sizeof(id));
|
|
}
|
|
|
|
static ssize_t ots_obj_prop_read(struct bt_conn *conn,
|
|
const struct bt_gatt_attr *attr, void *buf,
|
|
uint16_t len, uint16_t offset)
|
|
{
|
|
struct bt_ots *ots = (struct bt_ots *) attr->user_data;
|
|
|
|
LOG_DBG("OTS Object Properties GATT Read Operation");
|
|
|
|
if (!ots->cur_obj) {
|
|
LOG_DBG("No Current Object selected in OTS!");
|
|
return BT_GATT_ERR(BT_GATT_OTS_OBJECT_NOT_SELECTED);
|
|
}
|
|
|
|
return bt_gatt_attr_read(conn, attr, buf, len, offset,
|
|
&ots->cur_obj->metadata.props,
|
|
sizeof(ots->cur_obj->metadata.props));
|
|
}
|
|
|
|
int bt_ots_obj_add_internal(struct bt_ots *ots, struct bt_conn *conn,
|
|
const struct bt_ots_obj_add_param *param,
|
|
struct bt_gatt_ots_object **obj)
|
|
{
|
|
int err;
|
|
struct bt_gatt_ots_object *new_obj;
|
|
struct bt_ots_obj_created_desc created_desc;
|
|
|
|
if (IS_ENABLED(CONFIG_BT_OTS_DIR_LIST_OBJ) && ots->dir_list &&
|
|
!bt_ots_dir_list_is_idle(ots->dir_list)) {
|
|
LOG_DBG("Directory Listing Object is being read");
|
|
return -EBUSY;
|
|
}
|
|
|
|
err = bt_gatt_ots_obj_manager_obj_add(ots->obj_manager, &new_obj);
|
|
if (err) {
|
|
LOG_ERR("No space available in the object manager");
|
|
return err;
|
|
}
|
|
|
|
(void)memset(&created_desc, 0, sizeof(created_desc));
|
|
|
|
if (ots->cb->obj_created) {
|
|
err = ots->cb->obj_created(ots, NULL, new_obj->id, param, &created_desc);
|
|
|
|
if (err) {
|
|
(void)bt_gatt_ots_obj_manager_obj_delete(new_obj);
|
|
|
|
return err;
|
|
}
|
|
|
|
if (!ots_obj_validate_prop_against_oacp(created_desc.props, ots->features.oacp)) {
|
|
LOG_ERR("Object properties (0x%04X) are not a subset of OACP (0x%04X)",
|
|
created_desc.props, ots->features.oacp);
|
|
|
|
(void)bt_ots_obj_delete(ots, new_obj->id);
|
|
return -ECANCELED;
|
|
}
|
|
|
|
if (created_desc.name == NULL) {
|
|
LOG_ERR("Object name must be set by application after object creation.");
|
|
|
|
(void)bt_ots_obj_delete(ots, new_obj->id);
|
|
return -ECANCELED;
|
|
}
|
|
|
|
if (created_desc.size.alloc < param->size) {
|
|
LOG_ERR("Object allocated size must >= requested size.");
|
|
|
|
(void)bt_ots_obj_delete(ots, new_obj->id);
|
|
return -ECANCELED;
|
|
}
|
|
}
|
|
|
|
new_obj->metadata.type = param->type;
|
|
new_obj->metadata.name = created_desc.name;
|
|
new_obj->metadata.size = created_desc.size;
|
|
new_obj->metadata.props = created_desc.props;
|
|
|
|
if (obj) {
|
|
*obj = new_obj;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int bt_ots_obj_add(struct bt_ots *ots, const struct bt_ots_obj_add_param *param)
|
|
{
|
|
int err;
|
|
size_t name_len;
|
|
struct bt_gatt_ots_object *obj;
|
|
|
|
err = bt_ots_obj_add_internal(ots, NULL, param, &obj);
|
|
if (err) {
|
|
return err;
|
|
}
|
|
|
|
name_len = strlen(obj->metadata.name);
|
|
if (name_len == 0 || name_len > CONFIG_BT_OTS_OBJ_MAX_NAME_LEN) {
|
|
LOG_ERR("Invalid name length %zu", name_len);
|
|
|
|
(void)bt_ots_obj_delete(ots, obj->id);
|
|
return -ECANCELED;
|
|
}
|
|
|
|
if (obj->metadata.size.cur > param->size) {
|
|
LOG_ERR("Object current size must be less than or equal to requested size.");
|
|
|
|
(void)bt_ots_obj_delete(ots, obj->id);
|
|
return -ECANCELED;
|
|
}
|
|
|
|
return obj->id;
|
|
}
|
|
|
|
int bt_ots_obj_delete(struct bt_ots *ots, uint64_t id)
|
|
{
|
|
int err;
|
|
struct bt_gatt_ots_object *obj;
|
|
|
|
err = bt_gatt_ots_obj_manager_obj_get(ots->obj_manager, id, &obj);
|
|
if (err) {
|
|
return err;
|
|
}
|
|
|
|
if (obj->state.type != BT_GATT_OTS_OBJECT_IDLE_STATE) {
|
|
return -EBUSY;
|
|
}
|
|
|
|
if (IS_ENABLED(CONFIG_BT_OTS_DIR_LIST_OBJ) && ots->dir_list &&
|
|
!bt_ots_dir_list_is_idle(ots->dir_list)) {
|
|
LOG_DBG("Directory Listing Object is being read");
|
|
return -EBUSY;
|
|
}
|
|
|
|
if (ots->cb->obj_deleted) {
|
|
err = ots->cb->obj_deleted(ots, NULL, obj->id);
|
|
if (err) {
|
|
return err;
|
|
}
|
|
}
|
|
|
|
err = bt_gatt_ots_obj_manager_obj_delete(obj);
|
|
if (err) {
|
|
return err;
|
|
}
|
|
|
|
if (ots->cur_obj == obj) {
|
|
ots->cur_obj = NULL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
#if defined(CONFIG_BT_OTS_SECONDARY_SVC)
|
|
void *bt_ots_svc_decl_get(struct bt_ots *ots)
|
|
{
|
|
return ots->service->attrs;
|
|
}
|
|
#endif
|
|
|
|
int bt_ots_init(struct bt_ots *ots,
|
|
struct bt_ots_init *ots_init)
|
|
{
|
|
int err;
|
|
|
|
if (!ots || !ots_init || !ots_init->cb) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
__ASSERT(ots_init->cb->obj_created,
|
|
"Callback for object creation is not set");
|
|
__ASSERT(ots_init->cb->obj_deleted ||
|
|
!BT_OTS_OACP_GET_FEAT_CREATE(ots_init->features.oacp),
|
|
"Callback for object deletion is not set and object creation is enabled");
|
|
#if defined(CONFIG_BT_OTS_OACP_CHECKSUM_SUPPORT)
|
|
__ASSERT(ots_init->cb->obj_cal_checksum,
|
|
"Callback for object calculate checksum is not set");
|
|
#endif
|
|
__ASSERT(ots_init->cb->obj_read ||
|
|
!BT_OTS_OACP_GET_FEAT_READ(ots_init->features.oacp),
|
|
"Callback for object reading is not set");
|
|
__ASSERT(ots_init->cb->obj_write ||
|
|
!BT_OTS_OACP_GET_FEAT_WRITE(ots_init->features.oacp),
|
|
"Callback for object write is not set");
|
|
|
|
/* Set callback structure. */
|
|
ots->cb = ots_init->cb;
|
|
|
|
/* Check OACP supported features against Kconfig. */
|
|
if (ots_init->features.oacp & (~((uint32_t) OACP_FEAT))) {
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
__ASSERT(!BT_OTS_OACP_GET_FEAT_CREATE(ots_init->features.oacp) ||
|
|
BT_OTS_OACP_GET_FEAT_WRITE(ots_init->features.oacp),
|
|
"Object creation requires object write to be supported");
|
|
|
|
ots->features.oacp = ots_init->features.oacp;
|
|
LOG_DBG("OACP features: 0x%04X", ots->features.oacp);
|
|
|
|
/* Check OLCP supported features against Kconfig. */
|
|
if (ots_init->features.olcp & (~((uint32_t) OLCP_FEAT))) {
|
|
return -ENOTSUP;
|
|
}
|
|
ots->features.olcp = ots_init->features.olcp;
|
|
LOG_DBG("OLCP features: 0x%04X", ots->features.olcp);
|
|
|
|
/* Register L2CAP context. */
|
|
err = bt_gatt_ots_l2cap_register(&ots->l2cap);
|
|
if (err) {
|
|
return err;
|
|
}
|
|
|
|
err = bt_gatt_service_register(ots->service);
|
|
if (err) {
|
|
bt_gatt_ots_l2cap_unregister(&ots->l2cap);
|
|
|
|
return err;
|
|
}
|
|
|
|
if (IS_ENABLED(CONFIG_BT_OTS_DIR_LIST_OBJ)) {
|
|
bt_ots_dir_list_init(&ots->dir_list, ots->obj_manager);
|
|
}
|
|
|
|
LOG_DBG("Initialized OTS");
|
|
|
|
return 0;
|
|
}
|
|
|
|
#if defined(CONFIG_BT_OTS_SECONDARY_SVC)
|
|
#define BT_GATT_OTS_SERVICE BT_GATT_SECONDARY_SERVICE
|
|
#else
|
|
#define BT_GATT_OTS_SERVICE BT_GATT_PRIMARY_SERVICE
|
|
#endif
|
|
|
|
#if defined(CONFIG_BT_OTS_OBJ_NAME_WRITE_SUPPORT)
|
|
#define BT_OTS_OBJ_NAME_GATT_CHRC (BT_GATT_CHRC_READ | BT_GATT_CHRC_WRITE)
|
|
#define BT_OTS_OBJ_NAME_GATT_PERM (BT_GATT_PERM_READ | BT_GATT_PERM_WRITE)
|
|
#define BT_OTS_OBJ_NAME_GATT_WRITE (ots_obj_name_write)
|
|
#else
|
|
#define BT_OTS_OBJ_NAME_GATT_CHRC (BT_GATT_CHRC_READ)
|
|
#define BT_OTS_OBJ_NAME_GATT_PERM (BT_GATT_PERM_READ)
|
|
#define BT_OTS_OBJ_NAME_GATT_WRITE (NULL)
|
|
#endif
|
|
|
|
#define BT_GATT_OTS_ATTRS(_ots) { \
|
|
BT_GATT_OTS_SERVICE(BT_UUID_OTS), \
|
|
BT_GATT_CHARACTERISTIC(BT_UUID_OTS_FEATURE, \
|
|
BT_GATT_CHRC_READ, BT_GATT_PERM_READ, \
|
|
ots_feature_read, NULL, &_ots), \
|
|
BT_GATT_CHARACTERISTIC(BT_UUID_OTS_NAME, \
|
|
BT_OTS_OBJ_NAME_GATT_CHRC, BT_OTS_OBJ_NAME_GATT_PERM, \
|
|
ots_obj_name_read, BT_OTS_OBJ_NAME_GATT_WRITE, &_ots), \
|
|
BT_GATT_CHARACTERISTIC(BT_UUID_OTS_TYPE, \
|
|
BT_GATT_CHRC_READ, BT_GATT_PERM_READ, \
|
|
ots_obj_type_read, NULL, &_ots), \
|
|
BT_GATT_CHARACTERISTIC(BT_UUID_OTS_SIZE, \
|
|
BT_GATT_CHRC_READ, BT_GATT_PERM_READ, \
|
|
ots_obj_size_read, NULL, &_ots), \
|
|
BT_GATT_CHARACTERISTIC(BT_UUID_OTS_ID, \
|
|
BT_GATT_CHRC_READ, BT_GATT_PERM_READ, \
|
|
ots_obj_id_read, NULL, &_ots), \
|
|
BT_GATT_CHARACTERISTIC(BT_UUID_OTS_PROPERTIES, \
|
|
BT_GATT_CHRC_READ, BT_GATT_PERM_READ, \
|
|
ots_obj_prop_read, NULL, &_ots), \
|
|
BT_GATT_CHARACTERISTIC(BT_UUID_OTS_ACTION_CP, \
|
|
BT_GATT_CHRC_WRITE | BT_GATT_CHRC_INDICATE, \
|
|
BT_GATT_PERM_WRITE, NULL, \
|
|
bt_gatt_ots_oacp_write, &_ots), \
|
|
BT_GATT_CCC_MANAGED(&_ots.oacp_ind.ccc, \
|
|
BT_GATT_PERM_READ | BT_GATT_PERM_WRITE), \
|
|
BT_GATT_CHARACTERISTIC(BT_UUID_OTS_LIST_CP, \
|
|
BT_GATT_CHRC_WRITE | BT_GATT_CHRC_INDICATE, \
|
|
BT_GATT_PERM_WRITE, NULL, \
|
|
bt_gatt_ots_olcp_write, &_ots), \
|
|
BT_GATT_CCC_MANAGED(&_ots.olcp_ind.ccc, \
|
|
BT_GATT_PERM_READ | BT_GATT_PERM_WRITE) \
|
|
}
|
|
|
|
#define BT_GATT_OTS_INSTANCE_LIST_SIZE (ARRAY_SIZE(ots_instances))
|
|
#define BT_GATT_OTS_INSTANCE_LIST_START ots_instances
|
|
#define BT_GATT_OTS_INSTANCE_LIST_END \
|
|
(&ots_instances[BT_GATT_OTS_INSTANCE_LIST_SIZE])
|
|
|
|
#define BT_GATT_OTS_SERVICE_LIST_START ots_service_list
|
|
|
|
static struct bt_ots ots_instances[CONFIG_BT_OTS_MAX_INST_CNT];
|
|
static uint32_t instance_cnt;
|
|
BT_GATT_SERVICE_INSTANCE_DEFINE(ots_service_list, ots_instances,
|
|
CONFIG_BT_OTS_MAX_INST_CNT,
|
|
BT_GATT_OTS_ATTRS);
|
|
|
|
static void ots_delete_empty_name_objects(struct bt_ots *ots, struct bt_conn *conn)
|
|
{
|
|
char id_str[BT_OTS_OBJ_ID_STR_LEN];
|
|
struct bt_gatt_ots_object *obj;
|
|
struct bt_gatt_ots_object *next_obj;
|
|
int err;
|
|
|
|
err = bt_gatt_ots_obj_manager_first_obj_get(ots->obj_manager, &next_obj);
|
|
while (!err) {
|
|
obj = next_obj;
|
|
|
|
/* Get the next object before we potentially delete the current object and
|
|
* no longer can get the next object
|
|
*/
|
|
err = bt_gatt_ots_obj_manager_next_obj_get(ots->obj_manager, obj, &next_obj);
|
|
|
|
if (strlen(obj->metadata.name) == 0) {
|
|
bt_ots_obj_id_to_str(obj->id, id_str, sizeof(id_str));
|
|
LOG_DBG("Deleting object with %s ID due to empty name", id_str);
|
|
|
|
if (ots->cb && ots->cb->obj_deleted) {
|
|
ots->cb->obj_deleted(ots, conn, obj->id);
|
|
}
|
|
|
|
if (bt_gatt_ots_obj_manager_obj_delete(obj)) {
|
|
LOG_ERR("Failed to remove object with %s ID from object manager",
|
|
id_str);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void ots_conn_disconnected(struct bt_conn *conn, uint8_t reason)
|
|
{
|
|
uint32_t index;
|
|
struct bt_ots *instance;
|
|
|
|
for (instance = BT_GATT_OTS_INSTANCE_LIST_START, index = 0;
|
|
index < instance_cnt;
|
|
instance++, index++) {
|
|
|
|
LOG_DBG("Processing disconnect for OTS instance %u", index);
|
|
|
|
if (instance->cur_obj != NULL) {
|
|
__ASSERT(instance->cur_obj->state.type == BT_GATT_OTS_OBJECT_IDLE_STATE,
|
|
"The current object is expected to be in idle state as part "
|
|
"of cleanup of the L2CAP channel connection close.");
|
|
instance->cur_obj = NULL;
|
|
}
|
|
|
|
ots_delete_empty_name_objects(instance, conn);
|
|
}
|
|
}
|
|
|
|
BT_CONN_CB_DEFINE(conn_callbacks) = {
|
|
.disconnected = ots_conn_disconnected,
|
|
};
|
|
|
|
struct bt_ots *bt_ots_free_instance_get(void)
|
|
{
|
|
if (instance_cnt >= BT_GATT_OTS_INSTANCE_LIST_SIZE) {
|
|
return NULL;
|
|
}
|
|
|
|
return &BT_GATT_OTS_INSTANCE_LIST_START[instance_cnt++];
|
|
}
|
|
|
|
static int bt_gatt_ots_instances_prepare(const struct device *dev)
|
|
{
|
|
uint32_t index;
|
|
struct bt_ots *instance;
|
|
|
|
for (instance = BT_GATT_OTS_INSTANCE_LIST_START, index = 0;
|
|
instance != BT_GATT_OTS_INSTANCE_LIST_END;
|
|
instance++, index++) {
|
|
/* Assign an object pool to the OTS instance. */
|
|
instance->obj_manager = bt_gatt_ots_obj_manager_assign();
|
|
|
|
if (!instance->obj_manager) {
|
|
LOG_ERR("OTS Object manager instance not available");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
/* Assign pointer to the service descriptor. */
|
|
instance->service = &BT_GATT_OTS_SERVICE_LIST_START[index];
|
|
|
|
/* Initialize CCC descriptors for characteristics with
|
|
* indication properties.
|
|
*/
|
|
instance->oacp_ind.ccc.cfg_changed =
|
|
bt_gatt_ots_oacp_cfg_changed;
|
|
instance->olcp_ind.ccc.cfg_changed =
|
|
bt_gatt_ots_olcp_cfg_changed;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
SYS_INIT(bt_gatt_ots_instances_prepare, APPLICATION,
|
|
CONFIG_KERNEL_INIT_PRIORITY_DEFAULT);
|