blob: f2ca5abf4a2d81fd11bb3ad566b688004f82a961 [file] [log] [blame]
/** @file
* @brief Bluetooth Object Transfer Client
*
* Copyright (c) 2020-2022 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/zephyr.h>
#include <zephyr/types.h>
#include <zephyr/device.h>
#include <zephyr/init.h>
#include <zephyr/sys/byteorder.h>
#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/conn.h>
#include <zephyr/bluetooth/gatt.h>
#include <zephyr/bluetooth/l2cap.h>
#include <zephyr/bluetooth/services/ots.h>
#include "ots_internal.h"
#include "ots_client_internal.h"
#include "ots_l2cap_internal.h"
#include "ots_dir_list_internal.h"
#include "ots_oacp_internal.h"
#include "ots_olcp_internal.h"
#define BT_DBG_ENABLED IS_ENABLED(CONFIG_BT_DEBUG_OTS_CLIENT)
#define LOG_MODULE_NAME bt_otc
#include "common/log.h"
/* TODO: KConfig options */
#define OTS_CLIENT_INST_COUNT 1
#define OTS_CLIENT_MAX_WRITE_SIZE 23
/* 64-bit value, outside of 48-bit Object ID range */
#define OTS_CLIENT_UNKNOWN_ID 0x0001000000000000
struct dirlisting_record_t {
uint16_t len;
uint8_t flags;
uint8_t name_len;
struct bt_ots_obj_metadata metadata;
};
/**@brief String literals for the OACP result codes. Used for logging output.*/
static const char * const lit_request[] = {
"RFU",
"Create",
"Delete",
"Calculate Checksum",
"Execute",
"Read",
"Write",
"Abort",
};
/**@brief String literals for the OACP result codes. Used for logging output.*/
static const char * const lit_result[] = {
"RFU",
"Success",
"Op Code Not Supported",
"Invalid Parameter",
"Insufficient Resources",
"Invalid Object",
"Channel Unavailable",
"Unsupported Type",
"Procedure Not Permitted",
"Object Locked",
"Operation Failed"
};
/**@brief String literals for the OLCP request codes. Used for logging output.*/
static const char * const lit_olcp_request[] = {
"RFU",
"FIRST",
"LAST",
"PREV",
"NEXT",
"GOTO",
"ORDER",
"REQ_NUM_OBJS",
"CLEAR_MARKING",
};
/**@brief String literals for the OLCP result codes. Used for logging output.*/
static const char * const lit_olcp_result[] = {
"RFU",
"Success",
"Op Code Not Supported",
"Invalid Parameter",
"Operation Failed",
"Out of Bonds",
"Too Many Objects",
"No Object",
"Object ID not found",
};
struct bt_otc_internal_instance_t {
struct bt_ots_client *otc_inst;
struct bt_gatt_ots_l2cap l2cap_ctx;
bool busy;
/** Bitfield that is used to determine how much metadata to read */
uint8_t metadata_to_read;
/** Bitfield of how much metadata has been attempted to read */
uint8_t metadata_read_attempted;
/** Bitfield of how much metadata has been read */
uint8_t metadata_read;
int metadata_err;
uint32_t rcvd_size;
};
/* The profile clients that uses the OTS are responsible for discovery and
* will simply register any OTS instances as pointers, which is stored here.
*/
static struct bt_otc_internal_instance_t otc_insts[OTS_CLIENT_INST_COUNT];
NET_BUF_SIMPLE_DEFINE_STATIC(otc_tx_buf, OTS_CLIENT_MAX_WRITE_SIZE);
static struct bt_otc_internal_instance_t *cur_inst;
static int oacp_read(struct bt_conn *conn,
struct bt_otc_internal_instance_t *inst);
static void read_next_metadata(struct bt_conn *conn,
struct bt_otc_internal_instance_t *inst);
static int read_attr(struct bt_conn *conn,
struct bt_otc_internal_instance_t *inst,
uint16_t handle, bt_gatt_read_func_t cb);
/* L2CAP callbacks */
static void tx_done(struct bt_gatt_ots_l2cap *l2cap_ctx,
struct bt_conn *conn)
{
/* Not doing any writes yet */
BT_ERR("Unexpected call, context: %p, conn: %p", l2cap_ctx, (void *)conn);
}
static ssize_t rx_done(struct bt_gatt_ots_l2cap *l2cap_ctx,
struct bt_conn *conn, struct net_buf *buf)
{
const uint32_t offset = cur_inst->rcvd_size;
bool is_complete = false;
const struct bt_ots_obj_metadata *cur_object =
&cur_inst->otc_inst->cur_object;
int cb_ret;
BT_DBG("Incoming L2CAP data, context: %p, conn: %p, len: %u, offset: %u",
l2cap_ctx, (void *)conn, buf->len, offset);
cur_inst->rcvd_size += buf->len;
if (cur_inst->rcvd_size >= cur_object->size.cur) {
is_complete = true;
}
if (cur_inst->rcvd_size > cur_object->size.cur) {
BT_WARN("Received %u but expected maximum %u",
cur_inst->rcvd_size, cur_object->size.cur);
}
cb_ret = cur_inst->otc_inst->cb->obj_data_read(0, conn, offset,
buf->len, buf->data,
is_complete);
if (is_complete) {
const uint32_t rcv_size = cur_object->size.cur;
int err;
BT_DBG("Received the whole object (%u bytes). "
"Disconnecting L2CAP CoC", rcv_size);
err = bt_gatt_ots_l2cap_disconnect(l2cap_ctx);
if (err < 0) {
BT_WARN("Disconnecting L2CAP returned error %d", err);
}
cur_inst = NULL;
} else if (cb_ret == BT_OTS_STOP) {
const uint32_t rcv_size = cur_object->size.cur;
int err;
BT_DBG("Stopped receiving after%u bytes. "
"Disconnecting L2CAP CoC", rcv_size);
err = bt_gatt_ots_l2cap_disconnect(l2cap_ctx);
if (err < 0) {
BT_WARN("Disconnecting L2CAP returned error %d", err);
}
cur_inst = NULL;
}
return 0;
}
static void chan_closed(struct bt_gatt_ots_l2cap *l2cap_ctx,
struct bt_conn *conn)
{
BT_DBG("L2CAP closed, context: %p, conn: %p", l2cap_ctx, (void *)conn);
}
/* End L2CAP callbacks */
static void print_oacp_response(enum bt_gatt_ots_oacp_proc_type req_opcode,
enum bt_gatt_ots_oacp_res_code result_code)
{
BT_DBG("Request OP Code: %s", lit_request[req_opcode]);
BT_DBG("Result Code : %s", lit_result[result_code]);
}
static void print_olcp_response(enum bt_gatt_ots_olcp_proc_type req_opcode,
enum bt_gatt_ots_olcp_res_code result_code)
{
BT_DBG("Request OP Code: %s",
lit_olcp_request[req_opcode]);
BT_DBG("Result Code : %s",
lit_olcp_result[result_code]);
}
static void date_time_decode(struct net_buf_simple *buf,
struct bt_ots_date_time *p_date_time)
{
p_date_time->year = net_buf_simple_pull_le16(buf);
p_date_time->month = net_buf_simple_pull_u8(buf);
p_date_time->day = net_buf_simple_pull_u8(buf);
p_date_time->hours = net_buf_simple_pull_u8(buf);
p_date_time->minutes = net_buf_simple_pull_u8(buf);
p_date_time->seconds = net_buf_simple_pull_u8(buf);
}
static struct bt_otc_internal_instance_t *lookup_inst_by_handle(uint16_t handle)
{
for (int i = 0; i < ARRAY_SIZE(otc_insts); i++) {
if (otc_insts[i].otc_inst &&
otc_insts[i].otc_inst->start_handle <= handle &&
otc_insts[i].otc_inst->end_handle >= handle) {
return &otc_insts[i];
}
}
BT_DBG("Could not find OTS instance with handle 0x%04x", handle);
return NULL;
}
static void on_object_selected(struct bt_conn *conn,
enum bt_gatt_ots_olcp_res_code res,
struct bt_ots_client *otc_inst)
{
memset(&otc_inst->cur_object, 0, sizeof(otc_inst->cur_object));
otc_inst->cur_object.id = OTS_CLIENT_UNKNOWN_ID;
if (otc_inst->cb->obj_selected) {
otc_inst->cb->obj_selected(otc_inst, conn, res);
}
BT_DBG("Object selected");
}
static void olcp_ind_handler(struct bt_conn *conn,
struct bt_ots_client *otc_inst,
const void *data, uint16_t length)
{
enum bt_gatt_ots_olcp_proc_type op_code;
struct net_buf_simple net_buf;
net_buf_simple_init_with_data(&net_buf, (void *)data, length);
op_code = net_buf_simple_pull_u8(&net_buf);
BT_DBG("OLCP indication");
if (op_code == BT_GATT_OTS_OLCP_PROC_RESP) {
enum bt_gatt_ots_olcp_proc_type req_opcode =
net_buf_simple_pull_u8(&net_buf);
enum bt_gatt_ots_olcp_res_code result_code =
net_buf_simple_pull_u8(&net_buf);
print_olcp_response(req_opcode, result_code);
switch (req_opcode) {
case BT_GATT_OTS_OLCP_PROC_FIRST:
BT_DBG("First");
on_object_selected(conn, result_code, otc_inst);
break;
case BT_GATT_OTS_OLCP_PROC_LAST:
BT_DBG("Last");
on_object_selected(conn, result_code, otc_inst);
break;
case BT_GATT_OTS_OLCP_PROC_PREV:
BT_DBG("Previous");
on_object_selected(conn, result_code, otc_inst);
break;
case BT_GATT_OTS_OLCP_PROC_NEXT:
BT_DBG("Next");
on_object_selected(conn, result_code, otc_inst);
break;
case BT_GATT_OTS_OLCP_PROC_GOTO:
BT_DBG("Goto");
on_object_selected(conn, result_code, otc_inst);
break;
case BT_GATT_OTS_OLCP_PROC_ORDER:
BT_DBG("Order");
on_object_selected(conn, result_code, otc_inst);
break;
case BT_GATT_OTS_OLCP_PROC_REQ_NUM_OBJS:
BT_DBG("Request number of objects");
if (net_buf.len == sizeof(uint32_t)) {
uint32_t obj_cnt =
net_buf_simple_pull_le32(&net_buf);
BT_DBG("Number of objects %u", obj_cnt);
}
break;
case BT_GATT_OTS_OLCP_PROC_CLEAR_MARKING:
BT_DBG("Clear marking");
break;
default:
BT_DBG("Invalid indication req opcode %u", req_opcode);
break;
}
} else {
BT_DBG("Invalid indication opcode %u", op_code);
}
}
static void oacp_ind_handler(struct bt_conn *conn,
struct bt_ots_client *otc_inst,
const void *data, uint16_t length)
{
enum bt_gatt_ots_oacp_proc_type op_code;
struct net_buf_simple net_buf;
net_buf_simple_init_with_data(&net_buf, (void *)data, length);
op_code = net_buf_simple_pull_u8(&net_buf);
BT_DBG("OACP indication");
if (op_code == BT_GATT_OTS_OACP_PROC_RESP) {
enum bt_gatt_ots_oacp_proc_type req_opcode =
net_buf_simple_pull_u8(&net_buf);
enum bt_gatt_ots_oacp_res_code result_code =
net_buf_simple_pull_u8(&net_buf);
print_oacp_response(req_opcode, result_code);
} else {
BT_DBG("Invalid indication opcode %u", op_code);
}
}
uint8_t bt_ots_client_indicate_handler(struct bt_conn *conn,
struct bt_gatt_subscribe_params *params,
const void *data, uint16_t length)
{
uint16_t handle = params->value_handle;
struct bt_otc_internal_instance_t *inst;
if (conn == NULL) {
return BT_GATT_ITER_CONTINUE;
}
inst = lookup_inst_by_handle(handle);
/* TODO: Can we somehow avoid exposing this
* callback via the public API?
*/
if (!inst) {
BT_ERR("Instance not found");
return BT_GATT_ITER_STOP;
}
inst->busy = false;
if (data) {
if (handle == inst->otc_inst->olcp_handle) {
olcp_ind_handler(conn, inst->otc_inst, data, length);
} else if (handle == inst->otc_inst->oacp_handle) {
oacp_ind_handler(conn, inst->otc_inst, data, length);
}
}
return BT_GATT_ITER_CONTINUE;
}
static uint8_t read_feature_cb(struct bt_conn *conn, uint8_t err,
struct bt_gatt_read_params *params,
const void *data, uint16_t length)
{
uint8_t cb_err = err;
struct bt_otc_internal_instance_t *inst =
lookup_inst_by_handle(params->single.handle);
struct net_buf_simple net_buf;
net_buf_simple_init_with_data(&net_buf, (void *)data, length);
if (!inst) {
BT_ERR("Instance not found");
return BT_GATT_ITER_STOP;
}
inst->busy = false;
if (err) {
BT_DBG("err: 0x%02X", err);
} else if (data) {
if (length == OTS_FEATURE_LEN) {
inst->otc_inst->features.oacp =
net_buf_simple_pull_le32(&net_buf);
inst->otc_inst->features.olcp =
net_buf_simple_pull_le32(&net_buf);
BT_DBG("features : oacp 0x%x, olcp 0x%x",
inst->otc_inst->features.oacp,
inst->otc_inst->features.olcp);
} else {
BT_DBG("Invalid length %u (expected %u)",
length, OTS_FEATURE_LEN);
cb_err = BT_ATT_ERR_INVALID_ATTRIBUTE_LEN;
}
}
return BT_GATT_ITER_STOP;
}
int bt_ots_client_register(struct bt_ots_client *otc_inst)
{
for (int i = 0; i < ARRAY_SIZE(otc_insts); i++) {
int err;
if (otc_insts[i].otc_inst) {
continue;
}
BT_DBG("%u", i);
err = bt_gatt_ots_l2cap_register(&otc_insts[i].l2cap_ctx);
if (err) {
BT_WARN("Could not register L2CAP context %d", err);
return err;
}
otc_insts[i].otc_inst = otc_inst;
return 0;
}
return -ENOMEM;
}
int bt_ots_client_unregister(uint8_t index)
{
if (index < ARRAY_SIZE(otc_insts)) {
memset(&otc_insts[index], 0, sizeof(otc_insts[index]));
} else {
return -EINVAL;
}
return 0;
}
int bt_ots_client_read_feature(struct bt_ots_client *otc_inst,
struct bt_conn *conn)
{
if (OTS_CLIENT_INST_COUNT > 0) {
struct bt_otc_internal_instance_t *inst;
int err;
if (!conn) {
BT_WARN("Invalid Connection");
return -ENOTCONN;
} else if (!otc_inst) {
BT_ERR("Invalid OTC instance");
return -EINVAL;
} else if (!otc_inst->feature_handle) {
BT_DBG("Handle not set");
return -EINVAL;
}
inst = lookup_inst_by_handle(otc_inst->start_handle);
if (!inst) {
BT_ERR("Invalid OTC instance");
return -EINVAL;
} else if (inst->busy) {
return -EBUSY;
}
otc_inst->read_proc.func = read_feature_cb;
otc_inst->read_proc.handle_count = 1;
otc_inst->read_proc.single.handle = otc_inst->feature_handle;
otc_inst->read_proc.single.offset = 0U;
err = bt_gatt_read(conn, &otc_inst->read_proc);
if (!err) {
inst->busy = true;
}
return err;
}
BT_DBG("Not supported");
return -EOPNOTSUPP;
}
static void write_olcp_cb(struct bt_conn *conn, uint8_t err,
struct bt_gatt_write_params *params)
{
struct bt_otc_internal_instance_t *inst =
lookup_inst_by_handle(params->handle);
BT_DBG("Write %s (0x%02X)", err ? "failed" : "successful", err);
if (!inst) {
BT_ERR("Instance not found");
return;
}
inst->busy = false;
}
static int write_olcp(struct bt_otc_internal_instance_t *inst,
struct bt_conn *conn, enum bt_gatt_ots_olcp_proc_type opcode,
uint8_t *params, uint8_t param_len)
{
int err;
net_buf_simple_reset(&otc_tx_buf);
net_buf_simple_add_u8(&otc_tx_buf, opcode);
if (param_len && params) {
net_buf_simple_add_mem(&otc_tx_buf, params, param_len);
}
inst->otc_inst->write_params.offset = 0;
inst->otc_inst->write_params.data = otc_tx_buf.data;
inst->otc_inst->write_params.length = otc_tx_buf.len;
inst->otc_inst->write_params.handle = inst->otc_inst->olcp_handle;
inst->otc_inst->write_params.func = write_olcp_cb;
err = bt_gatt_write(conn, &inst->otc_inst->write_params);
if (!err) {
inst->busy = true;
}
return err;
}
int bt_ots_client_select_id(struct bt_ots_client *otc_inst,
struct bt_conn *conn,
uint64_t obj_id)
{
if (OTS_CLIENT_INST_COUNT > 0) {
struct bt_otc_internal_instance_t *inst;
uint8_t param[BT_OTS_OBJ_ID_SIZE];
if (!conn) {
BT_WARN("Invalid Connection");
return -ENOTCONN;
} else if (!otc_inst) {
BT_ERR("Invalid OTC instance");
return -EINVAL;
} else if (!otc_inst->olcp_handle) {
BT_DBG("Handle not set");
return -EINVAL;
}
inst = lookup_inst_by_handle(otc_inst->start_handle);
if (!inst) {
BT_ERR("Invalid OTC instance");
return -EINVAL;
} else if (inst->busy) {
return -EBUSY;
}
/* TODO: Should not update this before ID is read */
otc_inst->cur_object.id = obj_id;
sys_put_le48(obj_id, param);
return write_olcp(inst, conn, BT_GATT_OTS_OLCP_PROC_GOTO,
param, BT_OTS_OBJ_ID_SIZE);
}
BT_DBG("Not supported");
return -EOPNOTSUPP;
}
int bt_ots_client_select_first(struct bt_ots_client *otc_inst,
struct bt_conn *conn)
{
if (OTS_CLIENT_INST_COUNT > 0) {
struct bt_otc_internal_instance_t *inst;
if (!conn) {
BT_WARN("Invalid Connection");
return -ENOTCONN;
} else if (!otc_inst) {
BT_ERR("Invalid OTC instance");
return -EINVAL;
} else if (!otc_inst->olcp_handle) {
BT_DBG("Handle not set");
return -EINVAL;
}
inst = lookup_inst_by_handle(otc_inst->start_handle);
if (!inst) {
BT_ERR("Invalid OTC instance");
return -EINVAL;
} else if (inst->busy) {
return -EBUSY;
}
return write_olcp(inst, conn, BT_GATT_OTS_OLCP_PROC_FIRST,
NULL, 0);
}
BT_DBG("Not supported");
return -EOPNOTSUPP;
}
int bt_ots_client_select_last(struct bt_ots_client *otc_inst,
struct bt_conn *conn)
{
if (OTS_CLIENT_INST_COUNT > 0) {
struct bt_otc_internal_instance_t *inst;
if (!conn) {
BT_WARN("Invalid Connection");
return -ENOTCONN;
} else if (!otc_inst) {
BT_ERR("Invalid OTC instance");
return -EINVAL;
} else if (!otc_inst->olcp_handle) {
BT_DBG("Handle not set");
return -EINVAL;
}
inst = lookup_inst_by_handle(otc_inst->start_handle);
if (!inst) {
BT_ERR("Invalid OTC instance");
return -EINVAL;
} else if (inst->busy) {
return -EBUSY;
}
return write_olcp(inst, conn, BT_GATT_OTS_OLCP_PROC_LAST,
NULL, 0);
}
BT_DBG("Not supported");
return -EOPNOTSUPP;
}
int bt_ots_client_select_next(struct bt_ots_client *otc_inst,
struct bt_conn *conn)
{
if (OTS_CLIENT_INST_COUNT > 0) {
struct bt_otc_internal_instance_t *inst;
if (!conn) {
BT_WARN("Invalid Connection");
return -ENOTCONN;
} else if (!otc_inst) {
BT_ERR("Invalid OTC instance");
return -EINVAL;
} else if (!otc_inst->olcp_handle) {
BT_DBG("Handle not set");
return -EINVAL;
}
inst = lookup_inst_by_handle(otc_inst->start_handle);
if (!inst) {
BT_ERR("Invalid OTC instance");
return -EINVAL;
} else if (inst->busy) {
return -EBUSY;
}
return write_olcp(inst, conn, BT_GATT_OTS_OLCP_PROC_NEXT,
NULL, 0);
}
BT_DBG("Not supported");
return -EOPNOTSUPP;
}
int bt_ots_client_select_prev(struct bt_ots_client *otc_inst,
struct bt_conn *conn)
{
if (OTS_CLIENT_INST_COUNT > 0) {
struct bt_otc_internal_instance_t *inst;
if (!conn) {
BT_WARN("Invalid Connection");
return -ENOTCONN;
} else if (!otc_inst) {
BT_ERR("Invalid OTC instance");
return -EINVAL;
} else if (!otc_inst->olcp_handle) {
BT_DBG("Handle not set");
return -EINVAL;
}
inst = lookup_inst_by_handle(otc_inst->start_handle);
if (!inst) {
BT_ERR("Invalid OTC instance");
return -EINVAL;
} else if (inst->busy) {
return -EBUSY;
}
return write_olcp(inst, conn, BT_GATT_OTS_OLCP_PROC_PREV,
NULL, 0);
}
BT_DBG("Not supported");
return -EOPNOTSUPP;
}
static uint8_t read_object_size_cb(struct bt_conn *conn, uint8_t err,
struct bt_gatt_read_params *params,
const void *data, uint16_t length)
{
struct bt_otc_internal_instance_t *inst =
lookup_inst_by_handle(params->single.handle);
struct net_buf_simple net_buf;
net_buf_simple_init_with_data(&net_buf, (void *)data, length);
BT_DBG("handle %d, length %u", params->single.handle, length);
if (!inst) {
BT_ERR("Instance not found");
return BT_GATT_ITER_STOP;
}
if (err) {
BT_DBG("err: 0x%02X", err);
} else if (data) {
if (length != OTS_SIZE_LEN) {
BT_DBG("Invalid length %u (expected %u)",
length, OTS_SIZE_LEN);
err = BT_ATT_ERR_INVALID_ATTRIBUTE_LEN;
} else {
struct bt_ots_obj_metadata *cur_object =
&inst->otc_inst->cur_object;
cur_object->size.cur =
net_buf_simple_pull_le32(&net_buf);
cur_object->size.alloc =
net_buf_simple_pull_le32(&net_buf);
BT_DBG("Object Size : current size %u, "
"allocated size %u",
cur_object->size.cur,
cur_object->size.alloc);
if (cur_object->size.cur == 0) {
BT_WARN("Obj size read returned a current "
"size of 0");
} else if (cur_object->size.cur >
cur_object->size.alloc &&
cur_object->size.alloc != 0) {
BT_WARN("Allocated size %u is smaller than "
"current size %u",
cur_object->size.alloc,
cur_object->size.cur);
}
BT_OTS_SET_METADATA_REQ_SIZE(inst->metadata_read);
}
}
if (err) {
BT_WARN("err: 0x%02X", err);
if (!inst->metadata_err) {
inst->metadata_err = err;
}
}
read_next_metadata(conn, inst);
return BT_GATT_ITER_STOP;
}
static uint8_t read_obj_id_cb(struct bt_conn *conn, uint8_t err,
struct bt_gatt_read_params *params,
const void *data, uint16_t length)
{
struct bt_otc_internal_instance_t *inst =
lookup_inst_by_handle(params->single.handle);
struct net_buf_simple net_buf;
net_buf_simple_init_with_data(&net_buf, (void *)data, length);
BT_DBG("handle %d, length %u", params->single.handle, length);
if (!inst) {
BT_ERR("Instance not found");
return BT_GATT_ITER_STOP;
}
if (err) {
BT_DBG("err: 0x%02X", err);
} else if (data) {
if (length == BT_OTS_OBJ_ID_SIZE) {
uint64_t obj_id = net_buf_simple_pull_le48(&net_buf);
char t[BT_OTS_OBJ_ID_STR_LEN];
struct bt_ots_obj_metadata *cur_object =
&inst->otc_inst->cur_object;
(void)bt_ots_obj_id_to_str(obj_id, t, sizeof(t));
BT_DBG("Object Id : %s", t);
if (cur_object->id != OTS_CLIENT_UNKNOWN_ID &&
cur_object->id != obj_id) {
char str[BT_OTS_OBJ_ID_STR_LEN];
(void)bt_ots_obj_id_to_str(cur_object->id, str,
sizeof(str));
BT_INFO("Read Obj Id %s not selected obj Id %s",
t, str);
} else {
BT_INFO("Read Obj Id confirmed correct Obj Id");
cur_object->id = obj_id;
BT_OTS_SET_METADATA_REQ_ID(inst->metadata_read);
}
} else {
BT_DBG("Invalid length %u (expected %u)",
length, BT_OTS_OBJ_ID_SIZE);
err = BT_ATT_ERR_INVALID_ATTRIBUTE_LEN;
}
}
if (err) {
BT_WARN("err: 0x%02X", err);
if (!inst->metadata_err) {
inst->metadata_err = err;
}
}
read_next_metadata(conn, inst);
return BT_GATT_ITER_STOP;
}
static uint8_t read_obj_name_cb(struct bt_conn *conn, uint8_t err,
struct bt_gatt_read_params *params,
const void *data, uint16_t length)
{
struct bt_otc_internal_instance_t *inst =
lookup_inst_by_handle(params->single.handle);
BT_DBG("handle %d, length %u", params->single.handle, length);
if (!inst) {
BT_ERR("Instance not found");
return BT_GATT_ITER_STOP;
}
if (data) {
if (length <= CONFIG_BT_OTS_OBJ_MAX_NAME_LEN) {
memcpy(inst->otc_inst->cur_object.name_c, data, length);
inst->otc_inst->cur_object.name_c[length] = '\0';
} else {
BT_WARN("Invalid length %u (expected max %u)",
length, CONFIG_BT_OTS_OBJ_MAX_NAME_LEN);
err = BT_ATT_ERR_INVALID_ATTRIBUTE_LEN;
}
}
if (err) {
BT_WARN("err: 0x%02X", err);
if (!inst->metadata_err) {
inst->metadata_err = err;
}
}
read_next_metadata(conn, inst);
return BT_GATT_ITER_STOP;
}
static uint8_t read_obj_type_cb(struct bt_conn *conn, uint8_t err,
struct bt_gatt_read_params *params,
const void *data, uint16_t length)
{
struct bt_otc_internal_instance_t *inst =
lookup_inst_by_handle(params->single.handle);
BT_DBG("handle %d, length %u", params->single.handle, length);
if (!inst) {
BT_ERR("Instance not found");
return BT_GATT_ITER_STOP;
}
if (data) {
if (length == BT_UUID_SIZE_128 || length == BT_UUID_SIZE_16) {
char uuid_str[BT_UUID_STR_LEN];
struct bt_uuid *uuid =
&inst->otc_inst->cur_object.type.uuid;
bt_uuid_create(uuid, data, length);
bt_uuid_to_str(uuid, uuid_str, sizeof(uuid_str));
BT_DBG("UUID type read: %s", uuid_str);
BT_OTS_SET_METADATA_REQ_TYPE(inst->metadata_read);
} else {
BT_WARN("Invalid length %u (expected max %u)",
length, OTS_TYPE_MAX_LEN);
err = BT_ATT_ERR_INVALID_ATTRIBUTE_LEN;
}
}
if (err) {
BT_WARN("err: 0x%02X", err);
if (!inst->metadata_err) {
inst->metadata_err = err;
}
}
read_next_metadata(conn, inst);
return BT_GATT_ITER_STOP;
}
static uint8_t read_obj_created_cb(struct bt_conn *conn, uint8_t err,
struct bt_gatt_read_params *params,
const void *data, uint16_t length)
{
struct bt_otc_internal_instance_t *inst =
lookup_inst_by_handle(params->single.handle);
struct net_buf_simple net_buf;
net_buf_simple_init_with_data(&net_buf, (void *)data, length);
BT_DBG("handle %d, length %u", params->single.handle, length);
if (!inst) {
BT_ERR("Instance not found");
return BT_GATT_ITER_STOP;
}
if (data) {
if (length <= BT_OTS_DATE_TIME_FIELD_SIZE) {
date_time_decode(
&net_buf,
&inst->otc_inst->cur_object.first_created);
} else {
BT_WARN("Invalid length %u (expected max %u)",
length, BT_OTS_DATE_TIME_FIELD_SIZE);
err = BT_ATT_ERR_INVALID_ATTRIBUTE_LEN;
}
}
if (err) {
BT_WARN("err: 0x%02X", err);
if (!inst->metadata_err) {
inst->metadata_err = err;
}
}
read_next_metadata(conn, inst);
return BT_GATT_ITER_STOP;
}
static uint8_t read_obj_modified_cb(struct bt_conn *conn, uint8_t err,
struct bt_gatt_read_params *params,
const void *data, uint16_t length)
{
struct bt_otc_internal_instance_t *inst =
lookup_inst_by_handle(params->single.handle);
struct net_buf_simple net_buf;
net_buf_simple_init_with_data(&net_buf, (void *)data, length);
BT_DBG("handle %d, length %u", params->single.handle, length);
if (!inst) {
BT_ERR("Instance not found");
return BT_GATT_ITER_STOP;
}
if (data) {
if (length <= BT_OTS_DATE_TIME_FIELD_SIZE) {
date_time_decode(&net_buf,
&inst->otc_inst->cur_object.modified);
} else {
BT_WARN("Invalid length %u (expected max %u)",
length, BT_OTS_DATE_TIME_FIELD_SIZE);
err = BT_ATT_ERR_INVALID_ATTRIBUTE_LEN;
}
}
if (err) {
BT_WARN("err: 0x%02X", err);
if (!inst->metadata_err) {
inst->metadata_err = err;
}
}
read_next_metadata(conn, inst);
return BT_GATT_ITER_STOP;
}
static int read_attr(struct bt_conn *conn,
struct bt_otc_internal_instance_t *inst,
uint16_t handle, bt_gatt_read_func_t cb)
{
if (!handle) {
BT_DBG("Handle not set");
return -EINVAL;
} else if (cb == NULL) {
BT_ERR("No callback set");
return -EINVAL;
}
/* TODO: With EATT we can request multiple metadata to be read at once*/
inst->otc_inst->read_proc.func = cb;
inst->otc_inst->read_proc.handle_count = 1;
inst->otc_inst->read_proc.single.handle = handle;
inst->otc_inst->read_proc.single.offset = 0;
return bt_gatt_read(conn, &inst->otc_inst->read_proc);
}
static uint8_t read_obj_properties_cb(struct bt_conn *conn, uint8_t err,
struct bt_gatt_read_params *params,
const void *data, uint16_t length)
{
uint8_t cb_err = err;
struct bt_otc_internal_instance_t *inst =
lookup_inst_by_handle(params->single.handle);
struct net_buf_simple net_buf;
net_buf_simple_init_with_data(&net_buf, (void *)data, length);
BT_INFO("handle %d, length %u", params->single.handle, length);
if (!inst) {
BT_ERR("Instance not found");
return BT_GATT_ITER_STOP;
}
if (err) {
BT_WARN("err: 0x%02X", err);
} else if (data && length <= OTS_PROPERTIES_LEN) {
struct bt_ots_obj_metadata *cur_object =
&inst->otc_inst->cur_object;
cur_object->props = net_buf_simple_pull_le32(&net_buf);
BT_INFO("Object properties (raw) : 0x%x",
cur_object->props);
if (!BT_OTS_OBJ_GET_PROP_READ(cur_object->props)) {
BT_WARN("Obj properties: Obj read not supported");
}
BT_OTS_SET_METADATA_REQ_PROPS(inst->metadata_read);
} else {
BT_WARN("Invalid length %u (expected %u)",
length, OTS_PROPERTIES_LEN);
cb_err = BT_ATT_ERR_INVALID_ATTRIBUTE_LEN;
}
if (err) {
BT_WARN("err: 0x%02X", err);
if (!inst->metadata_err) {
inst->metadata_err = err;
}
}
read_next_metadata(conn, inst);
return BT_GATT_ITER_STOP;
}
static void write_oacp_cp_cb(struct bt_conn *conn, uint8_t err,
struct bt_gatt_write_params *params)
{
struct bt_otc_internal_instance_t *inst =
lookup_inst_by_handle(params->handle);
BT_DBG("Write %s (0x%02X)", err ? "failed" : "successful", err);
if (!inst) {
BT_ERR("Instance not found");
return;
}
inst->busy = false;
}
static int oacp_read(struct bt_conn *conn,
struct bt_otc_internal_instance_t *inst)
{
int err;
uint32_t offset = 0;
uint32_t length;
struct bt_gatt_ots_l2cap *l2cap;
if (!inst->otc_inst->oacp_handle) {
BT_DBG("Handle not set");
return -EINVAL;
} else if (inst->busy) {
return -EBUSY;
} else if (cur_inst) {
return -EBUSY;
}
/* TODO: How do we ensure that the L2CAP is connected in time for the
* transfer?
*/
err = bt_gatt_ots_l2cap_connect(conn, &l2cap);
if (err) {
BT_DBG("Could not connect l2cap: %d", err);
return err;
}
l2cap->tx_done = tx_done;
l2cap->rx_done = rx_done;
l2cap->closed = chan_closed;
net_buf_simple_reset(&otc_tx_buf);
/* OP Code */
net_buf_simple_add_u8(&otc_tx_buf, BT_GATT_OTS_OACP_PROC_READ);
/* Offset */
net_buf_simple_add_le32(&otc_tx_buf, offset);
/* Len */
length = inst->otc_inst->cur_object.size.cur - offset;
net_buf_simple_add_le32(&otc_tx_buf, length);
inst->otc_inst->write_params.offset = 0;
inst->otc_inst->write_params.data = otc_tx_buf.data;
inst->otc_inst->write_params.length = otc_tx_buf.len;
inst->otc_inst->write_params.handle = inst->otc_inst->oacp_handle;
inst->otc_inst->write_params.func = write_oacp_cp_cb;
err = bt_gatt_write(conn, &inst->otc_inst->write_params);
if (!err) {
inst->busy = true;
}
cur_inst = inst;
inst->rcvd_size = 0;
return err;
}
int bt_ots_client_read_object_data(struct bt_ots_client *otc_inst,
struct bt_conn *conn)
{
struct bt_otc_internal_instance_t *inst;
if (!conn) {
BT_WARN("Invalid Connection");
return -ENOTCONN;
} else if (!otc_inst) {
BT_ERR("Invalid OTC instance");
return -EINVAL;
}
inst = lookup_inst_by_handle(otc_inst->start_handle);
if (!inst) {
BT_ERR("Invalid OTC instance");
return -EINVAL;
}
if (otc_inst->cur_object.size.cur == 0) {
BT_WARN("Unknown object size");
return -EINVAL;
}
return oacp_read(conn, inst);
}
static void read_next_metadata(struct bt_conn *conn,
struct bt_otc_internal_instance_t *inst)
{
uint8_t metadata_remaining =
inst->metadata_to_read ^ inst->metadata_read_attempted;
int err = 0;
BT_DBG("Attempting to read metadata 0x%02X", metadata_remaining);
if (BT_OTS_GET_METADATA_REQ_NAME(metadata_remaining)) {
BT_OTS_SET_METADATA_REQ_NAME(inst->metadata_read_attempted);
err = read_attr(conn, inst, inst->otc_inst->obj_name_handle,
read_obj_name_cb);
} else if (BT_OTS_GET_METADATA_REQ_TYPE(metadata_remaining)) {
BT_OTS_SET_METADATA_REQ_TYPE(inst->metadata_read_attempted);
err = read_attr(conn, inst, inst->otc_inst->obj_type_handle,
read_obj_type_cb);
} else if (BT_OTS_GET_METADATA_REQ_SIZE(metadata_remaining)) {
BT_OTS_SET_METADATA_REQ_SIZE(inst->metadata_read_attempted);
err = read_attr(conn, inst, inst->otc_inst->obj_size_handle,
read_object_size_cb);
} else if (BT_OTS_GET_METADATA_REQ_CREATED(metadata_remaining)) {
BT_OTS_SET_METADATA_REQ_CREATED(inst->metadata_read_attempted);
err = read_attr(conn, inst, inst->otc_inst->obj_created_handle,
read_obj_created_cb);
} else if (BT_OTS_GET_METADATA_REQ_MODIFIED(metadata_remaining)) {
BT_OTS_SET_METADATA_REQ_MODIFIED(inst->metadata_read_attempted);
err = read_attr(conn, inst, inst->otc_inst->obj_modified_handle,
read_obj_modified_cb);
} else if (BT_OTS_GET_METADATA_REQ_ID(metadata_remaining)) {
BT_OTS_SET_METADATA_REQ_ID(inst->metadata_read_attempted);
err = read_attr(conn, inst, inst->otc_inst->obj_id_handle,
read_obj_id_cb);
} else if (BT_OTS_GET_METADATA_REQ_PROPS(metadata_remaining)) {
BT_OTS_SET_METADATA_REQ_PROPS(inst->metadata_read_attempted);
err = read_attr(conn, inst,
inst->otc_inst->obj_properties_handle,
read_obj_properties_cb);
} else {
inst->busy = false;
if (inst->otc_inst->cb->obj_metadata_read) {
inst->otc_inst->cb->obj_metadata_read(
inst->otc_inst, conn, inst->metadata_err,
inst->metadata_read);
}
return;
}
if (err) {
BT_DBG("Metadata read failed (%d), trying next", err);
read_next_metadata(conn, inst);
}
}
int bt_ots_client_read_object_metadata(struct bt_ots_client *otc_inst,
struct bt_conn *conn,
uint8_t metadata)
{
struct bt_otc_internal_instance_t *inst;
if (!conn) {
BT_WARN("Invalid Connection");
return -ENOTCONN;
} else if (!otc_inst) {
BT_ERR("Invalid OTC instance");
return -EINVAL;
} else if (!metadata) {
BT_WARN("No metadata to read");
return -ENOEXEC;
}
inst = lookup_inst_by_handle(otc_inst->start_handle);
if (!inst) {
BT_ERR("Invalid OTC instance");
return -EINVAL;
} else if (inst->busy) {
return -EBUSY;
}
inst->metadata_read = 0;
inst->metadata_to_read = metadata & BT_OTS_METADATA_REQ_ALL;
inst->metadata_read_attempted = 0;
inst->busy = true;
read_next_metadata(conn, inst);
return 0;
}
static int decode_record(struct net_buf_simple *buf,
struct dirlisting_record_t *rec)
{
uint16_t start_len = buf->len;
rec->len = net_buf_simple_pull_le16(buf);
if (rec->len > buf->len) {
BT_WARN("incorrect DirListing record length %u, "
"longer than remaining size %u",
rec->len, buf->len);
return -EINVAL;
}
if ((start_len - buf->len) + BT_OTS_OBJ_ID_SIZE > rec->len) {
BT_WARN("incorrect DirListing record, reclen %u too short, "
"includes only record length",
rec->len);
return -EINVAL;
}
rec->metadata.id = net_buf_simple_pull_le48(buf);
if (IS_ENABLED(CONFIG_BT_DEBUG_OTS_CLIENT)) {
char t[BT_OTS_OBJ_ID_STR_LEN];
(void)bt_ots_obj_id_to_str(rec->metadata.id, t, sizeof(t));
BT_DBG("Object ID 0x%s", t);
}
if ((start_len - buf->len) + sizeof(uint8_t) > rec->len) {
BT_WARN("incorrect DirListing record, reclen %u too short, "
"includes only record length + ObjId",
rec->len);
return -EINVAL;
}
rec->name_len = net_buf_simple_pull_u8(buf);
if (rec->name_len > 0) {
uint8_t *name;
if ((start_len - buf->len) + rec->name_len > rec->len) {
BT_WARN("incorrect DirListing record, remaining length "
"%u shorter than name length %u",
rec->len - (start_len - buf->len),
rec->name_len);
return -EINVAL;
}
if (rec->name_len >= sizeof(rec->metadata.name_c)) {
BT_WARN("Name length %u too long, invalid record",
rec->name_len);
return -EINVAL;
}
name = net_buf_simple_pull_mem(buf, rec->name_len);
memcpy(rec->metadata.name_c, name, rec->name_len);
}
rec->metadata.name_c[rec->name_len] = '\0';
rec->flags = 0;
if ((start_len - buf->len) + sizeof(uint8_t) > rec->len) {
BT_WARN("incorrect DirListing record, reclen %u too short, "
"does not include flags", rec->len);
return -EINVAL;
}
rec->flags = net_buf_simple_pull_u8(buf);
BT_DBG("flags 0x%x", rec->flags);
if (BT_OTS_DIR_LIST_GET_FLAG_TYPE_128(rec->flags)) {
uint8_t *uuid;
if ((start_len - buf->len) + BT_UUID_SIZE_128 > rec->len) {
BT_WARN("incorrect DirListing record, reclen %u "
"flags indicates uuid128, too short",
rec->len);
BT_INFO("flags 0x%x", rec->flags);
return -EINVAL;
}
uuid = net_buf_simple_pull_mem(buf, BT_UUID_SIZE_128);
bt_uuid_create(&rec->metadata.type.uuid,
uuid, BT_UUID_SIZE_128);
} else {
if ((start_len - buf->len) + BT_UUID_SIZE_16 > rec->len) {
BT_WARN("incorrect DirListing record, reclen %u "
"flags indicates uuid16, too short",
rec->len);
BT_INFO("flags 0x%x", rec->flags);
return -EINVAL;
}
rec->metadata.type.uuid_16.val =
net_buf_simple_pull_le16(buf);
}
if (BT_OTS_DIR_LIST_GET_FLAG_CUR_SIZE(rec->flags)) {
if ((start_len - buf->len) + sizeof(uint32_t) > rec->len) {
BT_WARN("incorrect DirListing record, reclen %u "
"flags indicates cur_size, too short",
rec->len);
BT_INFO("flags 0x%x", rec->flags);
return -EINVAL;
}
rec->metadata.size.cur = net_buf_simple_pull_le32(buf);
}
if (BT_OTS_DIR_LIST_GET_FLAG_ALLOC_SIZE(rec->flags)) {
if ((start_len - buf->len) + sizeof(uint32_t) > rec->len) {
BT_WARN("incorrect DirListing record, reclen %u "
"flags indicates allocated size, too short",
rec->len);
BT_INFO("flags 0x%x", rec->flags);
return -EINVAL;
}
rec->metadata.size.alloc = net_buf_simple_pull_le32(buf);
}
if (BT_OTS_DIR_LIST_GET_FLAG_FIRST_CREATED(rec->flags)) {
if ((start_len - buf->len) + BT_OTS_DATE_TIME_FIELD_SIZE > rec->len) {
BT_WARN("incorrect DirListing record, reclen %u "
"too short flags indicates first_created",
rec->len);
BT_INFO("flags 0x%x", rec->flags);
return -EINVAL;
}
date_time_decode(buf, &rec->metadata.first_created);
}
if (BT_OTS_DIR_LIST_GET_FLAG_LAST_MODIFIED(rec->flags)) {
if ((start_len - buf->len) + BT_OTS_DATE_TIME_FIELD_SIZE > rec->len) {
BT_WARN("incorrect DirListing record, reclen %u "
"flags indicates las_mod, too short",
rec->len);
BT_INFO("flags 0x%x", rec->flags);
return -EINVAL;
}
date_time_decode(buf, &rec->metadata.modified);
}
if (BT_OTS_DIR_LIST_GET_FLAG_PROPERTIES(rec->flags)) {
if ((start_len - buf->len) + sizeof(uint32_t) > rec->len) {
BT_WARN("incorrect DirListing record, reclen %u "
"flags indicates properties, too short",
rec->len);
BT_INFO("flags 0x%x", rec->flags);
return -EINVAL;
}
rec->metadata.props = net_buf_simple_pull_le32(buf);
}
return rec->len;
}
int bt_ots_client_decode_dirlisting(uint8_t *data, uint16_t length,
bt_ots_client_dirlisting_cb cb)
{
struct net_buf_simple net_buf;
uint8_t count = 0;
struct dirlisting_record_t record;
net_buf_simple_init_with_data(&net_buf, (void *)data, length);
if (!data || length == 0) {
return -EINVAL;
}
while (net_buf.len) {
int ret;
count++;
if (net_buf.len < sizeof(uint16_t)) {
BT_WARN("incorrect DirListing record, len %u too short",
net_buf.len);
return -EINVAL;
}
BT_DBG("Decoding record %u", count);
ret = decode_record(&net_buf, &record);
if (ret < 0) {
BT_WARN("DirListing, record %u invalid", count);
return ret;
}
ret = cb(&record.metadata);
if (ret == BT_OTS_STOP) {
break;
}
}
return count;
}
void bt_ots_metadata_display(struct bt_ots_obj_metadata *metadata,
uint16_t count)
{
BT_INFO("--- Displaying %u metadata records ---", count);
for (int i = 0; i < count; i++) {
char t[BT_OTS_OBJ_ID_STR_LEN];
(void)bt_ots_obj_id_to_str(metadata->id, t, sizeof(t));
BT_INFO("Object ID: 0x%s", t);
BT_INFO("Object name: %s", metadata->name_c);
BT_INFO("Object Current Size: %u", metadata->size.cur);
BT_INFO("Object Allocate Size: %u", metadata->size.alloc);
if (!bt_uuid_cmp(&metadata->type.uuid,
BT_UUID_OTS_TYPE_MPL_ICON)) {
BT_INFO("Type: Icon Obj Type");
} else if (!bt_uuid_cmp(&metadata->type.uuid,
BT_UUID_OTS_TYPE_TRACK_SEGMENT)) {
BT_INFO("Type: Track Segment Obj Type");
} else if (!bt_uuid_cmp(&metadata->type.uuid,
BT_UUID_OTS_TYPE_TRACK)) {
BT_INFO("Type: Track Obj Type");
} else if (!bt_uuid_cmp(&metadata->type.uuid,
BT_UUID_OTS_TYPE_GROUP)) {
BT_INFO("Type: Group Obj Type");
} else if (!bt_uuid_cmp(&metadata->type.uuid,
BT_UUID_OTS_DIRECTORY_LISTING)) {
BT_INFO("Type: Directory Listing");
}
BT_INFO("Properties:0x%x", metadata->props);
if (BT_OTS_OBJ_GET_PROP_APPEND(metadata->props)) {
BT_INFO(" - append permitted");
}
if (BT_OTS_OBJ_GET_PROP_DELETE(metadata->props)) {
BT_INFO(" - delete permitted");
}
if (BT_OTS_OBJ_GET_PROP_EXECUTE(metadata->props)) {
BT_INFO(" - execute permitted");
}
if (BT_OTS_OBJ_GET_PROP_MARKED(metadata->props)) {
BT_INFO(" - marked");
}
if (BT_OTS_OBJ_GET_PROP_PATCH(metadata->props)) {
BT_INFO(" - patch permitted");
}
if (BT_OTS_OBJ_GET_PROP_READ(metadata->props)) {
BT_INFO(" - read permitted");
}
if (BT_OTS_OBJ_GET_PROP_TRUNCATE(metadata->props)) {
BT_INFO(" - truncate permitted");
}
if (BT_OTS_OBJ_GET_PROP_WRITE(metadata->props)) {
BT_INFO(" - write permitted");
}
}
}