| /* |
| * 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); |