/* btp_ots.c - Bluetooth OTS Tester */ /* * Copyright (c) 2024 Codecoup * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include #include #define LOG_MODULE_NAME bttester_ots LOG_MODULE_REGISTER(LOG_MODULE_NAME, CONFIG_BTTESTER_LOG_LEVEL); #include "btp/btp.h" #define OBJ_POOL_SIZE CONFIG_BT_OTS_MAX_OBJ_CNT #define OBJ_MAX_SIZE 100 static struct object { uint8_t data[OBJ_MAX_SIZE]; char name[CONFIG_BT_OTS_OBJ_MAX_NAME_LEN + 1]; bool in_use; } objects[OBJ_POOL_SIZE]; struct object_creation_data { struct object *object; struct bt_ots_obj_size size; uint32_t props; }; #define OTS_OBJ_ID_TO_OBJ_IDX(id) (((id) - BT_OTS_OBJ_ID_MIN) % ARRAY_SIZE(objects)) static struct object_creation_data *object_being_created; static struct bt_ots *ots; static uint8_t ots_supported_commands(const void *cmd, uint16_t cmd_len, void *rsp, uint16_t *rsp_len) { struct btp_ots_read_supported_commands_rp *rp = rsp; tester_set_bit(rp->data, BTP_OTS_READ_SUPPORTED_COMMANDS); tester_set_bit(rp->data, BTP_OTS_REGISTER_OBJECT); *rsp_len = sizeof(*rp) + 1; return BTP_STATUS_SUCCESS; } static struct object *get_object(void) { for (size_t i = 0; i < ARRAY_SIZE(objects); i++) { if (!objects[i].in_use) { objects[i].in_use = true; return &objects[i]; } } return NULL; } static uint8_t register_object(const void *cmd, uint16_t cmd_len, void *rsp, uint16_t *rsp_len) { const struct btp_ots_register_object_cmd *cp = cmd; struct btp_ots_register_object_rp *rp = rsp; struct object_creation_data obj_data; struct bt_ots_obj_add_param param; uint32_t supported_props = 0; struct object *obj; uint32_t props; int err; if ((cmd_len < sizeof(*cp)) || (cmd_len != sizeof(*cp) + cp->name_len)) { return BTP_STATUS_FAILED; } if (cp->name_len == 0 || cp->name_len > CONFIG_BT_OTS_OBJ_MAX_NAME_LEN) { return BTP_STATUS_FAILED; } /* all supported props (execute, append, truncate not supported) */ BT_OTS_OBJ_SET_PROP_DELETE(supported_props); BT_OTS_OBJ_SET_PROP_READ(supported_props); BT_OTS_OBJ_SET_PROP_WRITE(supported_props); BT_OTS_OBJ_SET_PROP_PATCH(supported_props); props = sys_le32_to_cpu(cp->ots_props); if (cp->flags & BTP_OTS_REGISTER_OBJECT_FLAGS_SKIP_UNSUPPORTED_PROPS) { props &= supported_props; } obj = get_object(); if (!obj) { return BTP_STATUS_FAILED; } (void)memset(&obj_data, 0, sizeof(obj_data)); memcpy(obj->name, cp->name, cp->name_len); obj_data.object = obj; obj_data.size.cur = sys_le32_to_cpu(cp->current_size); obj_data.size.alloc = sys_le32_to_cpu(cp->alloc_size); obj_data.props = props; /* bt_ots_obj_add() lacks user_data so we need to use global for * passing this */ object_being_created = &obj_data; param.size = obj_data.size.alloc; param.type.uuid.type = BT_UUID_TYPE_16; param.type.uuid_16.val = BT_UUID_OTS_TYPE_UNSPECIFIED_VAL; err = bt_ots_obj_add(ots, ¶m); object_being_created = NULL; if (err < 0) { memset(obj, 0, sizeof(*obj)); return BTP_STATUS_FAILED; } rp->object_id = sys_cpu_to_le64(err); *rsp_len = sizeof(*rp); return BTP_STATUS_SUCCESS; } static const struct btp_handler ots_handlers[] = { { .opcode = BTP_OTS_READ_SUPPORTED_COMMANDS, .index = BTP_INDEX_NONE, .expect_len = 0, .func = ots_supported_commands }, { .opcode = BTP_OTS_REGISTER_OBJECT, .index = 0, .expect_len = BTP_HANDLER_LENGTH_VARIABLE, .func = register_object }, }; static int ots_obj_created(struct bt_ots *ots, struct bt_conn *conn, uint64_t id, const struct bt_ots_obj_add_param *add_param, struct bt_ots_obj_created_desc *created_desc) { struct object *obj; LOG_DBG("id=%"PRIu64" size=%u", id, add_param->size); /* TS suggests to use OTS service UUID for testing this */ if (conn && bt_uuid_cmp(&add_param->type.uuid, BT_UUID_OTS) == 0) { return -ENOTSUP; } if (add_param->size > OBJ_MAX_SIZE) { return -ENOMEM; } if (conn || !object_being_created) { uint32_t obj_index = OTS_OBJ_ID_TO_OBJ_IDX(id); if (obj_index >= OBJ_POOL_SIZE) { return -ENOMEM; } obj = &objects[obj_index]; if (obj->in_use) { return -ENOMEM; } obj->in_use = false; created_desc->name = obj->name; created_desc->size.alloc = OBJ_MAX_SIZE; BT_OTS_OBJ_SET_PROP_READ(created_desc->props); BT_OTS_OBJ_SET_PROP_WRITE(created_desc->props); BT_OTS_OBJ_SET_PROP_PATCH(created_desc->props); BT_OTS_OBJ_SET_PROP_DELETE(created_desc->props); } else { obj = object_being_created->object; created_desc->name = obj->name; created_desc->size = object_being_created->size; created_desc->props = object_being_created->props; } return 0; } static int ots_obj_deleted(struct bt_ots *ots, struct bt_conn *conn, uint64_t id) { uint32_t obj_index = OTS_OBJ_ID_TO_OBJ_IDX(id); struct object *obj; LOG_DBG("id=%"PRIu64, id); if (obj_index >= OBJ_POOL_SIZE) { return -ENOENT; } obj = &objects[obj_index]; memset(obj, 0, sizeof(*obj)); return 0; } static void ots_obj_selected(struct bt_ots *ots, struct bt_conn *conn, uint64_t id) { LOG_DBG("id=%"PRIu64, id); } static ssize_t ots_obj_read(struct bt_ots *ots, struct bt_conn *conn, uint64_t id, void **data, size_t len, off_t offset) { uint32_t obj_index = OTS_OBJ_ID_TO_OBJ_IDX(id); LOG_DBG("id=%"PRIu64" data=%p offset=%ld len=%zu", id, data, (long)offset, len); if (!data) { return 0; } if (obj_index >= OBJ_POOL_SIZE) { return -ENOENT; } *data = &objects[obj_index].data[offset]; return len; } static ssize_t ots_obj_write(struct bt_ots *ots, struct bt_conn *conn, uint64_t id, const void *data, size_t len, off_t offset, size_t rem) { uint32_t obj_index = OTS_OBJ_ID_TO_OBJ_IDX(id); LOG_DBG("id=%"PRIu64" data=%p offset=%ld len=%zu", id, data, (long)offset, len); if (obj_index >= OBJ_POOL_SIZE) { return -ENOENT; } (void)memcpy(&objects[obj_index].data[offset], data, len); return len; } static void ots_obj_name_written(struct bt_ots *ots, struct bt_conn *conn, uint64_t id, const char *cur_name, const char *new_name) { LOG_DBG("id=%"PRIu64"cur_name=%s new_name=%s", id, cur_name, new_name); } static int ots_obj_cal_checksum(struct bt_ots *ots, struct bt_conn *conn, uint64_t id, off_t offset, size_t len, void **data) { uint32_t obj_index = OTS_OBJ_ID_TO_OBJ_IDX(id); if (obj_index >= OBJ_POOL_SIZE) { return -ENOENT; } *data = &objects[obj_index].data[offset]; return 0; } static struct bt_ots_cb ots_callbacks = { .obj_created = ots_obj_created, .obj_deleted = ots_obj_deleted, .obj_selected = ots_obj_selected, .obj_read = ots_obj_read, .obj_write = ots_obj_write, .obj_name_written = ots_obj_name_written, .obj_cal_checksum = ots_obj_cal_checksum, }; static int ots_init(void) { int err; struct bt_ots_init_param ots_init; /* Configure OTS initialization. */ (void)memset(&ots_init, 0, sizeof(ots_init)); BT_OTS_OACP_SET_FEAT_READ(ots_init.features.oacp); BT_OTS_OACP_SET_FEAT_WRITE(ots_init.features.oacp); BT_OTS_OACP_SET_FEAT_CREATE(ots_init.features.oacp); BT_OTS_OACP_SET_FEAT_DELETE(ots_init.features.oacp); BT_OTS_OACP_SET_FEAT_CHECKSUM(ots_init.features.oacp); BT_OTS_OACP_SET_FEAT_PATCH(ots_init.features.oacp); BT_OTS_OLCP_SET_FEAT_GO_TO(ots_init.features.olcp); ots_init.cb = &ots_callbacks; /* Initialize OTS instance. */ err = bt_ots_init(ots, &ots_init); if (err) { LOG_ERR("Failed to init OTS (err:%d)\n", err); return err; } return 0; } uint8_t tester_init_ots(void) { int err; /* TODO there is no API to return OTS instance to pool */ if (!ots) { ots = bt_ots_free_instance_get(); } if (!ots) { return BTP_STATUS_FAILED; } err = ots_init(); if (err) { return BTP_STATUS_VAL(err); } tester_register_command_handlers(BTP_SERVICE_ID_OTS, ots_handlers, ARRAY_SIZE(ots_handlers)); return BTP_STATUS_SUCCESS; } uint8_t tester_unregister_ots(void) { memset(objects, 0, sizeof(objects)); return BTP_STATUS_SUCCESS; }