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:
parent
954eb4face
commit
37f9cb75cc
7 changed files with 396 additions and 1 deletions
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
28
tests/bluetooth/tester/src/btp/btp_ots.h
Normal file
28
tests/bluetooth/tester/src/btp/btp_ots.h
Normal 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;
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
339
tests/bluetooth/tester/src/btp_ots.c
Normal file
339
tests/bluetooth/tester/src/btp_ots.c
Normal 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, ¶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;
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue