blob: 3ffa4decfde78bedcea45a555596dcdfa86d5ae5 [file] [log] [blame]
/*
* Copyright (c) 2021 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/types.h>
#include <stddef.h>
#include <string.h>
#include <errno.h>
#include <zephyr/sys/dlist.h>
#include <zephyr/sys/byteorder.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_DECLARE(bt_ots, CONFIG_BT_OTS_LOG_LEVEL);
static struct bt_ots_dir_list dir_lists[CONFIG_BT_OTS_MAX_INST_CNT];
static size_t dir_list_object_record_size(const struct bt_gatt_ots_object *obj)
{
uint16_t len;
size_t obj_name_len;
/* Record Length */
len = sizeof(len);
/* ID */
len += BT_OTS_OBJ_ID_SIZE;
/* Name length (single octect is used for the name length) */
len += sizeof(uint8_t);
/* Name */
obj_name_len = strlen(obj->metadata.name);
__ASSERT(obj_name_len > 0 && obj_name_len <= CONFIG_BT_OTS_OBJ_MAX_NAME_LEN,
"Dir list object len is incorrect %zu", obj_name_len);
len += obj_name_len;
/* Flags */
len += sizeof(uint8_t);
/* Object type */
if (obj->metadata.type.uuid.type == BT_UUID_TYPE_16) {
len += BT_UUID_SIZE_16;
} else {
len += BT_UUID_SIZE_128;
}
/* Object Current size */
len += sizeof(obj->metadata.size.cur);
/* Object properties */
len += sizeof(obj->metadata.props);
__ASSERT(len >= DIR_LIST_OBJ_RECORD_MIN_SIZE,
"Dir list object len is too small %u", len);
__ASSERT(len <= DIR_LIST_OBJ_RECORD_MAX_SIZE,
"Dir list object len is too large %u", len);
return len;
}
static void dir_list_object_encode(const struct bt_gatt_ots_object *obj,
struct net_buf_simple *net_buf)
{
uint8_t flags = 0;
uint8_t *start;
uint16_t len;
size_t obj_name_len;
BT_OTS_DIR_LIST_SET_FLAG_PROPERTIES(flags);
BT_OTS_DIR_LIST_SET_FLAG_CUR_SIZE(flags);
if (obj->metadata.type.uuid.type == BT_UUID_TYPE_128) {
BT_OTS_DIR_LIST_SET_FLAG_TYPE_128(flags);
}
/* skip 16bits at the beginning of the record for the record's length */
start = net_buf_simple_add(net_buf, sizeof(len));
/* ID */
net_buf_simple_add_le48(net_buf, obj->id);
/* Name length */
obj_name_len = strlen(obj->metadata.name);
__ASSERT(obj_name_len > 0 && obj_name_len <= CONFIG_BT_OTS_OBJ_MAX_NAME_LEN,
"Dir list object len is incorrect %zu", obj_name_len);
net_buf_simple_add_u8(net_buf, obj_name_len);
/* Name */
net_buf_simple_add_mem(net_buf, obj->metadata.name, obj_name_len);
/* Flags */
net_buf_simple_add_u8(net_buf, flags);
/* encode Object type */
if (obj->metadata.type.uuid.type == BT_UUID_TYPE_16) {
net_buf_simple_add_le16(net_buf, obj->metadata.type.uuid_16.val);
} else {
net_buf_simple_add_mem(net_buf, obj->metadata.type.uuid_128.val,
BT_UUID_SIZE_128);
}
/* encode Object Current size */
net_buf_simple_add_le32(net_buf, obj->metadata.size.cur);
/* Object properties */
net_buf_simple_add_le32(net_buf, obj->metadata.props);
len = net_buf_simple_tail(net_buf) - start;
__ASSERT(len >= DIR_LIST_OBJ_RECORD_MIN_SIZE,
"Dir list object len is too small %u", len);
__ASSERT(len <= DIR_LIST_OBJ_RECORD_MAX_SIZE,
"Dir list object len is too large %u", len);
/* Update the record length at the beginning */
sys_put_le16(len, start);
}
static void bt_ots_dir_list_reset_anchor(struct bt_ots_dir_list *dir_list, void *obj_manager)
{
dir_list->anchor_offset = 0;
bt_gatt_ots_obj_manager_first_obj_get(obj_manager, &dir_list->anchor_object);
}
static int bt_ots_dir_list_search_forward(struct bt_ots_dir_list *dir_list, void *obj_manager,
off_t offset)
{
int err;
char id_str[BT_OTS_OBJ_ID_STR_LEN];
struct bt_gatt_ots_object *obj = dir_list->anchor_object;
size_t rec_len = dir_list_object_record_size(obj);
bt_ots_obj_id_to_str(obj->id, id_str, sizeof(id_str));
LOG_DBG("Searching forward for offset %ld starting at %ld with object ID %s",
(long)offset, (long)dir_list->anchor_offset, id_str);
while (dir_list->anchor_offset + rec_len <= offset) {
err = bt_gatt_ots_obj_manager_next_obj_get(obj_manager, obj, &obj);
if (err) {
return err;
}
dir_list->anchor_offset += rec_len;
dir_list->anchor_object = obj;
rec_len = dir_list_object_record_size(obj);
}
return 0;
}
static int bt_ots_dir_list_search_backward(struct bt_ots_dir_list *dir_list, void *obj_manager,
off_t offset)
{
int err;
char id_str[BT_OTS_OBJ_ID_STR_LEN];
struct bt_gatt_ots_object *obj = dir_list->anchor_object;
bt_ots_obj_id_to_str(obj->id, id_str, sizeof(id_str));
LOG_DBG("Searching backward for offset %ld starting at %ld with object ID %s",
(long)offset, (long)dir_list->anchor_offset, id_str);
while (dir_list->anchor_offset > offset) {
err = bt_gatt_ots_obj_manager_prev_obj_get(obj_manager, obj, &obj);
if (err) {
return err;
}
dir_list->anchor_offset -= dir_list_object_record_size(obj);
dir_list->anchor_object = obj;
}
return 0;
}
static int bt_ots_dir_list_search(struct bt_ots_dir_list *dir_list, void *obj_manager, off_t offset)
{
int err = 0;
char id_str[BT_OTS_OBJ_ID_STR_LEN];
/* decide start location and direction of movement based on offset, we can only choose
* current anchor point, beginning, or end as those are the only places where we know
* the associated object that builds up the record.
*/
if (offset >= dir_list->anchor_offset) {
const size_t last = dir_list->dir_list_obj->metadata.size.cur;
const size_t mid = dir_list->anchor_offset + (last - dir_list->anchor_offset) / 2;
if (offset < mid) {
err = bt_ots_dir_list_search_forward(dir_list, obj_manager, offset);
} else {
size_t rec_len;
LOG_DBG("Offset %ld is closer to %zu than %ld, start from end",
(long)offset, last, (long)dir_list->anchor_offset);
bt_gatt_ots_obj_manager_last_obj_get(obj_manager, &dir_list->anchor_object);
rec_len = dir_list_object_record_size(dir_list->anchor_object);
dir_list->anchor_offset = last - rec_len;
err = bt_ots_dir_list_search_backward(dir_list, obj_manager, offset);
}
} else {
const size_t mid = dir_list->anchor_offset / 2;
if (offset < mid) {
LOG_DBG("Offset %ld is closer to 0 than %ld, start from beginning",
(long)offset, (long)dir_list->anchor_offset);
bt_ots_dir_list_reset_anchor(dir_list, obj_manager);
err = bt_ots_dir_list_search_forward(dir_list, obj_manager, offset);
} else {
err = bt_ots_dir_list_search_backward(dir_list, obj_manager, offset);
}
}
if (err) {
return err;
}
bt_ots_obj_id_to_str(dir_list->anchor_object->id, id_str, sizeof(id_str));
LOG_DBG("Found offset %ld starting at %ld in object with ID %s",
(long)offset, (long)dir_list->anchor_offset, id_str);
return 0;
}
static void dir_list_update_size(struct bt_ots_dir_list *dir_list, void *obj_manager)
{
struct bt_gatt_ots_object *obj;
int err;
size_t len = 0;
err = bt_gatt_ots_obj_manager_first_obj_get(obj_manager, &obj);
__ASSERT(err == 0 && obj == dir_list->dir_list_obj,
"Expecting first object to be the Directory Listing Object");
do {
len += dir_list_object_record_size(obj);
err = bt_gatt_ots_obj_manager_next_obj_get(obj_manager, obj, &obj);
} while (!err);
LOG_DBG("Update directory listing current size to 0x%zx", len);
dir_list->dir_list_obj->metadata.size.cur = len;
}
void bt_ots_dir_list_selected(struct bt_ots_dir_list *dir_list, void *obj_manager,
struct bt_gatt_ots_object *cur_obj)
{
if (dir_list->dir_list_obj != cur_obj) {
/* We only need to update the object directory listing if it is currently selected,
* as we otherwise only create it when it is selected.
*/
return;
}
bt_ots_dir_list_reset_anchor(dir_list, obj_manager);
dir_list_update_size(dir_list, obj_manager);
}
void bt_ots_dir_list_init(struct bt_ots_dir_list **dir_list, void *obj_manager)
{
struct bt_gatt_ots_object *dir_list_obj;
int err;
static char *dir_list_obj_name = CONFIG_BT_OTS_DIR_LIST_OBJ_NAME;
__ASSERT(*dir_list == NULL, "Already initialized");
for (size_t i = 0; i < ARRAY_SIZE(dir_lists); i++) {
if (!dir_lists[i].dir_list_obj) {
*dir_list = &dir_lists[i];
}
}
__ASSERT(*dir_list, "Could not assign Directory Listing Object");
__ASSERT(strlen(dir_list_obj_name) <= CONFIG_BT_OTS_OBJ_MAX_NAME_LEN,
"BT_OTS_DIR_LIST_OBJ_NAME shall be less than or equal to %u octets",
CONFIG_BT_OTS_OBJ_MAX_NAME_LEN);
err = bt_gatt_ots_obj_manager_obj_add(obj_manager, &dir_list_obj);
__ASSERT(!err, "Could not add Directory Listing Object for object manager %p", obj_manager);
memset(&dir_list_obj->metadata, 0, sizeof(dir_list_obj->metadata));
dir_list_obj->metadata.name = dir_list_obj_name;
dir_list_obj->metadata.size.alloc = DIR_LIST_MAX_SIZE;
dir_list_obj->metadata.type.uuid.type = BT_UUID_TYPE_16;
dir_list_obj->metadata.type.uuid_16.val = BT_UUID_OTS_DIRECTORY_LISTING_VAL;
BT_OTS_OBJ_SET_PROP_READ(dir_list_obj->metadata.props);
(*dir_list)->dir_list_obj = dir_list_obj;
bt_ots_dir_list_reset_anchor(*dir_list, obj_manager);
dir_list_update_size(*dir_list, obj_manager);
}
ssize_t bt_ots_dir_list_content_get(struct bt_ots_dir_list *dir_list, void *obj_manager,
void **data, size_t len, off_t offset)
{
int err;
size_t last_rec_len;
size_t rec_len;
off_t rec_offset;
struct bt_gatt_ots_object *obj;
err = bt_ots_dir_list_search(dir_list, obj_manager, offset);
if (err) {
return err;
}
net_buf_simple_init_with_data(&dir_list->net_buf, dir_list->_content,
sizeof(dir_list->_content));
net_buf_simple_reset(&dir_list->net_buf);
obj = dir_list->anchor_object;
rec_offset = dir_list->anchor_offset;
last_rec_len = 0;
rec_len = dir_list_object_record_size(obj);
while (net_buf_simple_tailroom(&dir_list->net_buf) >= rec_len) {
dir_list_object_encode(obj, &dir_list->net_buf);
dir_list->anchor_object = obj;
dir_list->anchor_offset += last_rec_len;
if (dir_list->net_buf.len - (offset - rec_offset) >= len) {
/* we have encoded as much data as the client has asked */
break;
}
err = bt_gatt_ots_obj_manager_next_obj_get(obj_manager, obj, &obj);
if (err) {
/* there are no more objects to encode */
break;
}
last_rec_len = rec_len;
rec_len = dir_list_object_record_size(obj);
}
*data = dir_list->net_buf.data + (offset - rec_offset);
return MIN(len, dir_list->net_buf.len - (offset - rec_offset));
}
bool bt_ots_dir_list_is_idle(const struct bt_ots_dir_list *dir_list)
{
return dir_list->dir_list_obj->state.type == BT_GATT_OTS_OBJECT_IDLE_STATE;
}