bluetooth: tester: Add support for OTS

This implemnets OTS service and provides OTS instance with two objects
for behavior valid and invalid test cases. Service implementation is
inspired by peripheral_ots sample.

Signed-off-by: Szymon Janc <szymon.janc@codecoup.pl>
This commit is contained in:
Szymon Janc 2024-04-24 09:09:49 +02:00 committed by Alberto Escolar
commit 37f9cb75cc
7 changed files with 396 additions and 1 deletions

View file

@ -94,3 +94,7 @@ endif()
if(CONFIG_BT_TMAP)
target_sources(app PRIVATE src/btp_tmap.c)
endif()
if(CONFIG_BT_OTS)
target_sources(app PRIVATE src/btp_ots.c)
endif()

View file

@ -39,3 +39,12 @@ CONFIG_BT_RX_STACK_SIZE=4096
CONFIG_BT_TINYCRYPT_ECC=y
CONFIG_BT_TESTING=y
CONFIG_UTF8=y
CONFIG_BT_OTS=y
CONFIG_BT_OTS_DIR_LIST_OBJ=y
CONFIG_BT_OTS_OBJ_NAME_WRITE_SUPPORT=y
CONFIG_BT_OTS_OACP_WRITE_SUPPORT=y
CONFIG_BT_OTS_OACP_PATCH_SUPPORT=y
CONFIG_BT_OTS_OACP_CREATE_SUPPORT=y
CONFIG_BT_OTS_OACP_DELETE_SUPPORT=y
CONFIG_BT_OTS_OACP_CHECKSUM_SUPPORT=y

View file

@ -37,6 +37,7 @@
#include "btp_cap.h"
#include "btp_tbs.h"
#include "btp_tmap.h"
#include "btp_ots.h"
#define BTP_MTU 1024
#define BTP_DATA_MAX_SIZE (BTP_MTU - sizeof(struct btp_hdr))
@ -73,8 +74,9 @@
#define BTP_SERVICE_ID_CAP 26
#define BTP_SERVICE_ID_TBS 27
#define BTP_SERVICE_ID_TMAP 28
#define BTP_SERVICE_ID_OTS 29
#define BTP_SERVICE_ID_MAX BTP_SERVICE_ID_TMAP
#define BTP_SERVICE_ID_MAX BTP_SERVICE_ID_OTS
#define BTP_STATUS_SUCCESS 0x00
#define BTP_STATUS_FAILED 0x01

View file

@ -0,0 +1,28 @@
/* btp_ots.h - Bluetooth OTS tester headers */
/*
* Copyright (c) 2024 Codecoup
*
* SPDX-License-Identifier: Apache-2.0
*/
/* OTS commands */
#define BTP_OTS_READ_SUPPORTED_COMMANDS 0x01
struct btp_ots_read_supported_commands_rp {
uint8_t data[0];
} __packed;
#define BTP_OTS_REGISTER_OBJECT_FLAGS_SKIP_UNSUPPORTED_PROPS 0x01
#define BTP_OTS_REGISTER_OBJECT 0x02
struct btp_ots_register_object_cmd {
uint8_t flags;
uint32_t ots_props;
uint32_t alloc_size;
uint32_t current_size;
uint8_t name_len;
uint8_t name[0];
} __packed;
struct btp_ots_register_object_rp {
uint64_t object_id;
} __packed;

View file

@ -135,3 +135,6 @@ uint8_t tester_unregister_tbs(void);
uint8_t tester_init_tmap(void);
uint8_t tester_unregister_tmap(void);
uint8_t tester_init_ots(void);
uint8_t tester_unregister_ots(void);

View file

@ -245,6 +245,11 @@ static uint8_t register_service(const void *cmd, uint16_t cmd_len,
status = tester_init_tmap();
break;
#endif /* CONFIG_BT_TMAP */
#if defined(CONFIG_BT_OTS)
case BTP_SERVICE_ID_OTS:
status = tester_init_ots();
break;
#endif /* CONFIG_BT_OTS */
default:
LOG_WRN("unknown id: 0x%02x", cp->id);
status = BTP_STATUS_FAILED;
@ -387,6 +392,11 @@ static uint8_t unregister_service(const void *cmd, uint16_t cmd_len,
status = tester_unregister_tmap();
break;
#endif /* CONFIG_BT_TMAP */
#if defined(CONFIG_BT_OTS)
case BTP_SERVICE_ID_OTS:
status = tester_unregister_ots();
break;
#endif /* CONFIG_BT_OTS */
default:
LOG_WRN("unknown id: 0x%x", cp->id);
status = BTP_STATUS_FAILED;

View file

@ -0,0 +1,339 @@
/* btp_ots.c - Bluetooth OTS Tester */
/*
* Copyright (c) 2024 Codecoup
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/bluetooth/services/ots.h>
#include <zephyr/sys/byteorder.h>
#include <stdint.h>
#include <zephyr/logging/log.h>
#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, &param);
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;
}