/** @file * @brief Bluetooth Object Transfer Client * * Copyright (c) 2020-2022 Nordic Semiconductor ASA * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include #include #include #include #include #include #include #include #include #include #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 LOG_LEVEL CONFIG_BT_OTS_CLIENT_LOG_LEVEL #include LOG_MODULE_REGISTER(bt_otc); /* 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; uint32_t sent_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 int oacp_write(struct bt_conn *conn, struct bt_otc_internal_instance_t *inst, const void *buf, uint32_t len, uint32_t offset, enum bt_ots_oacp_write_op_mode mode); static int oacp_checksum(struct bt_conn *conn, struct bt_otc_internal_instance_t *inst, uint32_t offset, uint32_t len); 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 */ LOG_ERR("Unexpected call, context: %p, conn: %p", l2cap_ctx, (void *)conn); } static void write_obj_tx_done(struct bt_gatt_ots_l2cap *l2cap_ctx, struct bt_conn *conn) { int err; size_t written; if (cur_inst == NULL) { LOG_ERR("OTS instance invalid\n"); return; } written = cur_inst->sent_size; LOG_DBG("ctx: %p, conn: %p, written: %d", l2cap_ctx, (void *)conn, written); err = bt_gatt_ots_l2cap_disconnect(l2cap_ctx); if (err < 0) { LOG_WRN("Disconnecting L2CAP returned error %d", err); } if ((cur_inst->otc_inst != NULL) && (cur_inst->otc_inst->cb != NULL)) { if (cur_inst->otc_inst->cb->obj_data_written) { cur_inst->otc_inst->cb->obj_data_written(0, conn, written); } } cur_inst = NULL; } 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; LOG_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) { LOG_WRN("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; LOG_DBG("Received the whole object (%u bytes). " "Disconnecting L2CAP CoC", rcv_size); err = bt_gatt_ots_l2cap_disconnect(l2cap_ctx); if (err < 0) { LOG_WRN("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; LOG_DBG("Stopped receiving after%u bytes. " "Disconnecting L2CAP CoC", rcv_size); err = bt_gatt_ots_l2cap_disconnect(l2cap_ctx); if (err < 0) { LOG_WRN("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) { LOG_DBG("L2CAP closed, context: %p, conn: %p", l2cap_ctx, (void *)conn); if (cur_inst) { cur_inst = NULL; } } /* 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) { LOG_DBG("Request OP Code: %s", lit_request[req_opcode]); LOG_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) { LOG_DBG("Request OP Code: %s", lit_olcp_request[req_opcode]); LOG_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]; } } LOG_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); } LOG_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; if (length < sizeof(op_code)) { LOG_DBG("Invalid indication length: %u", length); return; } net_buf_simple_init_with_data(&net_buf, (void *)data, length); op_code = net_buf_simple_pull_u8(&net_buf); LOG_DBG("OLCP indication"); if (op_code == BT_GATT_OTS_OLCP_PROC_RESP) { if (net_buf.len < (sizeof(uint8_t) + sizeof(uint8_t))) { LOG_DBG("Invalid indication length for op_code %u: %u", op_code, net_buf.len); return; } 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: LOG_DBG("First"); on_object_selected(conn, result_code, otc_inst); break; case BT_GATT_OTS_OLCP_PROC_LAST: LOG_DBG("Last"); on_object_selected(conn, result_code, otc_inst); break; case BT_GATT_OTS_OLCP_PROC_PREV: LOG_DBG("Previous"); on_object_selected(conn, result_code, otc_inst); break; case BT_GATT_OTS_OLCP_PROC_NEXT: LOG_DBG("Next"); on_object_selected(conn, result_code, otc_inst); break; case BT_GATT_OTS_OLCP_PROC_GOTO: LOG_DBG("Goto"); on_object_selected(conn, result_code, otc_inst); break; case BT_GATT_OTS_OLCP_PROC_ORDER: LOG_DBG("Order"); on_object_selected(conn, result_code, otc_inst); break; case BT_GATT_OTS_OLCP_PROC_REQ_NUM_OBJS: LOG_DBG("Request number of objects"); if (net_buf.len == sizeof(uint32_t)) { uint32_t obj_cnt = net_buf_simple_pull_le32(&net_buf); LOG_DBG("Number of objects %u", obj_cnt); } break; case BT_GATT_OTS_OLCP_PROC_CLEAR_MARKING: LOG_DBG("Clear marking"); break; default: LOG_DBG("Invalid indication req opcode %u", req_opcode); break; } } else { LOG_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; enum bt_gatt_ots_oacp_proc_type req_opcode; enum bt_gatt_ots_oacp_res_code result_code; uint32_t checksum; struct net_buf_simple net_buf; if (length < sizeof(op_code)) { LOG_DBG("Invalid indication length: %u", length); return; } net_buf_simple_init_with_data(&net_buf, (void *)data, length); op_code = net_buf_simple_pull_u8(&net_buf); LOG_DBG("OACP indication"); if (op_code == BT_GATT_OTS_OACP_PROC_RESP) { if (net_buf.len >= (sizeof(req_opcode) + sizeof(result_code))) { req_opcode = net_buf_simple_pull_u8(&net_buf); result_code = net_buf_simple_pull_u8(&net_buf); } else { LOG_ERR("Invalid indication data len %u", net_buf.len); return; } if (req_opcode == BT_GATT_OTS_OACP_PROC_CHECKSUM_CALC) { if (net_buf.len == sizeof(checksum)) { checksum = net_buf_simple_pull_le32(&net_buf); LOG_DBG("Object checksum 0x%08x\n", checksum); if (otc_inst->cb->obj_checksum_calculated) { otc_inst->cb->obj_checksum_calculated( otc_inst, conn, result_code, checksum); } } else { LOG_ERR("Invalid indication data len %u after opcode and result " "pulled", net_buf.len); return; } } print_oacp_response(req_opcode, result_code); } else { LOG_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) { LOG_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) { LOG_ERR("Instance not found"); return BT_GATT_ITER_STOP; } inst->busy = false; if (err) { LOG_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); LOG_DBG("features : oacp 0x%x, olcp 0x%x", inst->otc_inst->features.oacp, inst->otc_inst->features.olcp); } else { LOG_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; } LOG_DBG("%u", i); err = bt_gatt_ots_l2cap_register(&otc_insts[i].l2cap_ctx); if (err) { LOG_WRN("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) { LOG_WRN("Invalid Connection"); return -ENOTCONN; } else if (!otc_inst) { LOG_ERR("Invalid OTC instance"); return -EINVAL; } else if (!otc_inst->feature_handle) { LOG_DBG("Handle not set"); return -EINVAL; } inst = lookup_inst_by_handle(otc_inst->start_handle); if (!inst) { LOG_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; } LOG_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); LOG_DBG("Write %s (0x%02X)", err ? "failed" : "successful", err); if (!inst) { LOG_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) { CHECKIF(!BT_OTS_VALID_OBJ_ID(obj_id)) { LOG_DBG("Invalid object ID 0x%016llx", obj_id); return -EINVAL; } if (OTS_CLIENT_INST_COUNT > 0) { struct bt_otc_internal_instance_t *inst; uint8_t param[BT_OTS_OBJ_ID_SIZE]; if (!conn) { LOG_WRN("Invalid Connection"); return -ENOTCONN; } else if (!otc_inst) { LOG_ERR("Invalid OTC instance"); return -EINVAL; } else if (!otc_inst->olcp_handle) { LOG_DBG("Handle not set"); return -EINVAL; } inst = lookup_inst_by_handle(otc_inst->start_handle); if (!inst) { LOG_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); } LOG_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) { LOG_WRN("Invalid Connection"); return -ENOTCONN; } else if (!otc_inst) { LOG_ERR("Invalid OTC instance"); return -EINVAL; } else if (!otc_inst->olcp_handle) { LOG_DBG("Handle not set"); return -EINVAL; } inst = lookup_inst_by_handle(otc_inst->start_handle); if (!inst) { LOG_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); } LOG_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) { LOG_WRN("Invalid Connection"); return -ENOTCONN; } else if (!otc_inst) { LOG_ERR("Invalid OTC instance"); return -EINVAL; } else if (!otc_inst->olcp_handle) { LOG_DBG("Handle not set"); return -EINVAL; } inst = lookup_inst_by_handle(otc_inst->start_handle); if (!inst) { LOG_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); } LOG_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) { LOG_WRN("Invalid Connection"); return -ENOTCONN; } else if (!otc_inst) { LOG_ERR("Invalid OTC instance"); return -EINVAL; } else if (!otc_inst->olcp_handle) { LOG_DBG("Handle not set"); return -EINVAL; } inst = lookup_inst_by_handle(otc_inst->start_handle); if (!inst) { LOG_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); } LOG_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) { LOG_WRN("Invalid Connection"); return -ENOTCONN; } else if (!otc_inst) { LOG_ERR("Invalid OTC instance"); return -EINVAL; } else if (!otc_inst->olcp_handle) { LOG_DBG("Handle not set"); return -EINVAL; } inst = lookup_inst_by_handle(otc_inst->start_handle); if (!inst) { LOG_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); } LOG_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); LOG_DBG("handle %d, length %u", params->single.handle, length); if (!inst) { LOG_ERR("Instance not found"); return BT_GATT_ITER_STOP; } if (err) { LOG_DBG("err: 0x%02X", err); } else if (data) { if (length != OTS_SIZE_LEN) { LOG_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); LOG_DBG("Object Size : current size %u, " "allocated size %u", cur_object->size.cur, cur_object->size.alloc); if (cur_object->size.cur == 0) { LOG_WRN("Obj size read returned a current " "size of 0"); } else if (cur_object->size.cur > cur_object->size.alloc && cur_object->size.alloc != 0) { LOG_WRN("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) { LOG_WRN("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); LOG_DBG("handle %d, length %u", params->single.handle, length); if (!inst) { LOG_ERR("Instance not found"); return BT_GATT_ITER_STOP; } if (err) { LOG_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)); LOG_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)); LOG_INF("Read Obj Id %s not selected obj Id %s", t, str); } else { LOG_INF("Read Obj Id confirmed correct Obj Id"); cur_object->id = obj_id; BT_OTS_SET_METADATA_REQ_ID(inst->metadata_read); } } else { LOG_DBG("Invalid length %u (expected %u)", length, BT_OTS_OBJ_ID_SIZE); err = BT_ATT_ERR_INVALID_ATTRIBUTE_LEN; } } if (err) { LOG_WRN("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); LOG_DBG("handle %d, length %u", params->single.handle, length); if (!inst) { LOG_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 { LOG_WRN("Invalid length %u (expected max %u)", length, CONFIG_BT_OTS_OBJ_MAX_NAME_LEN); err = BT_ATT_ERR_INVALID_ATTRIBUTE_LEN; } } if (err) { LOG_WRN("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); LOG_DBG("handle %d, length %u", params->single.handle, length); if (!inst) { LOG_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; if (!bt_uuid_create(uuid, data, length)) { return BT_GATT_ITER_STOP; } bt_uuid_to_str(uuid, uuid_str, sizeof(uuid_str)); LOG_DBG("UUID type read: %s", uuid_str); BT_OTS_SET_METADATA_REQ_TYPE(inst->metadata_read); } else { LOG_WRN("Invalid length %u (expected max %u)", length, OTS_TYPE_MAX_LEN); err = BT_ATT_ERR_INVALID_ATTRIBUTE_LEN; } } if (err) { LOG_WRN("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); LOG_DBG("handle %d, length %u", params->single.handle, length); if (!inst) { LOG_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 { LOG_WRN("Invalid length %u (expected max %u)", length, BT_OTS_DATE_TIME_FIELD_SIZE); err = BT_ATT_ERR_INVALID_ATTRIBUTE_LEN; } } if (err) { LOG_WRN("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); LOG_DBG("handle %d, length %u", params->single.handle, length); if (!inst) { LOG_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 { LOG_WRN("Invalid length %u (expected max %u)", length, BT_OTS_DATE_TIME_FIELD_SIZE); err = BT_ATT_ERR_INVALID_ATTRIBUTE_LEN; } } if (err) { LOG_WRN("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) { LOG_DBG("Handle not set"); return -EINVAL; } else if (cb == NULL) { LOG_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); LOG_INF("handle %d, length %u", params->single.handle, length); if (!inst) { LOG_ERR("Instance not found"); return BT_GATT_ITER_STOP; } if (err) { LOG_WRN("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); LOG_INF("Object properties (raw) : 0x%x", cur_object->props); if (!BT_OTS_OBJ_GET_PROP_READ(cur_object->props)) { LOG_WRN("Obj properties: Obj read not supported"); } BT_OTS_SET_METADATA_REQ_PROPS(inst->metadata_read); } else { LOG_WRN("Invalid length %u (expected %u)", length, OTS_PROPERTIES_LEN); cb_err = BT_ATT_ERR_INVALID_ATTRIBUTE_LEN; } if (err) { LOG_WRN("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); LOG_DBG("Write %s (0x%02X)", err ? "failed" : "successful", err); if (!inst) { LOG_ERR("Instance not found"); return; } inst->busy = false; } static void write_oacp_cp_write_req_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); uint32_t len; LOG_DBG("Write Object request %s (0x%02X)", err ? "failed" : "successful", err); if (!inst) { LOG_ERR("Instance not found"); return; } len = inst->l2cap_ctx.tx.len; inst->l2cap_ctx.tx.len = 0; err = bt_gatt_ots_l2cap_send(&inst->l2cap_ctx, inst->l2cap_ctx.tx.data, len); if (err) { LOG_WRN("L2CAP CoC error: %d while trying to execute OACP " "Read procedure", err); } 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) { LOG_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) { LOG_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; } static int oacp_write(struct bt_conn *conn, struct bt_otc_internal_instance_t *inst, const void *buf, uint32_t len, uint32_t offset, enum bt_ots_oacp_write_op_mode mode) { int err; struct bt_gatt_ots_l2cap *l2cap; if (!inst->otc_inst->oacp_handle) { LOG_DBG("Handle not set"); return -EINVAL; } else if (inst->busy) { return -EBUSY; } else if (cur_inst) { return -EBUSY; } err = bt_gatt_ots_l2cap_connect(conn, &l2cap); if (err) { LOG_DBG("Could not connect l2cap: %d", err); return err; } l2cap->tx_done = write_obj_tx_done; l2cap->rx_done = rx_done; l2cap->closed = chan_closed; l2cap->tx.data = (uint8_t *)buf; l2cap->tx.len = len; net_buf_simple_reset(&otc_tx_buf); /* OP Code */ net_buf_simple_add_u8(&otc_tx_buf, BT_GATT_OTS_OACP_PROC_WRITE); /* Offset */ net_buf_simple_add_le32(&otc_tx_buf, offset); /* Len */ net_buf_simple_add_le32(&otc_tx_buf, len); /* Mode, truncate or not */ net_buf_simple_add_u8(&otc_tx_buf, mode); 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_write_req_cb; inst->sent_size = len; err = bt_gatt_write(conn, &inst->otc_inst->write_params); if (!err) { inst->busy = true; cur_inst = inst; } inst->rcvd_size = 0; return err; } static int oacp_checksum(struct bt_conn *conn, struct bt_otc_internal_instance_t *inst, uint32_t offset, uint32_t len) { int err; if (!inst->otc_inst->oacp_handle) { LOG_DBG("Handle not set"); return -EINVAL; } else if (inst->busy) { LOG_DBG("Client is busy"); return -EBUSY; } else if (cur_inst) { LOG_DBG("Previous operation is not finished"); return -EBUSY; } net_buf_simple_reset(&otc_tx_buf); /* OP Code */ net_buf_simple_add_u8(&otc_tx_buf, BT_GATT_OTS_OACP_PROC_CHECKSUM_CALC); /* Offset */ net_buf_simple_add_le32(&otc_tx_buf, offset); /* Len */ net_buf_simple_add_le32(&otc_tx_buf, 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->oacp_handle; inst->otc_inst->write_params.func = write_oacp_cp_cb; err = bt_gatt_write(conn, &inst->otc_inst->write_params); if (err != 0) { inst->busy = true; cur_inst = inst; } 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) { LOG_WRN("Invalid Connection"); return -ENOTCONN; } else if (!otc_inst) { LOG_ERR("Invalid OTC instance"); return -EINVAL; } inst = lookup_inst_by_handle(otc_inst->start_handle); if (!inst) { LOG_ERR("Invalid OTC instance"); return -EINVAL; } if (otc_inst->cur_object.size.cur == 0) { LOG_WRN("Unknown object size"); return -EINVAL; } return oacp_read(conn, inst); } int bt_ots_client_write_object_data(struct bt_ots_client *otc_inst, struct bt_conn *conn, const void *buf, size_t len, off_t offset, enum bt_ots_oacp_write_op_mode mode) { struct bt_otc_internal_instance_t *inst; CHECKIF(!conn) { LOG_WRN("Invalid Connection"); return -ENOTCONN; } CHECKIF(!otc_inst) { LOG_ERR("Invalid OTC instance"); return -EINVAL; } CHECKIF((mode != BT_OTS_OACP_WRITE_OP_MODE_NONE) && (mode != BT_OTS_OACP_WRITE_OP_MODE_TRUNCATE)) { LOG_ERR("Invalid write object mode parameter %d", mode); return -EINVAL; } /* OTS_v10.pdf Table 3.9: Object Action Control Point Procedure Requirements * Offset and Length field are UINT32 Length */ CHECKIF(len > UINT32_MAX) { LOG_ERR("length %zu exceeds UINT32", len); return -EINVAL; } CHECKIF(len == 0) { LOG_ERR("length equals zero"); return -EINVAL; } CHECKIF((sizeof(offset) > sizeof(uint32_t) && (offset > UINT32_MAX)) || (offset < 0)) { LOG_ERR("offset %ld exceeds UINT32 and must be >= 0", offset); return -EINVAL; } CHECKIF(offset > otc_inst->cur_object.size.cur) { LOG_ERR("offset %ld exceeds cur size %zu", offset, otc_inst->cur_object.size.cur); return -EINVAL; } CHECKIF((offset < otc_inst->cur_object.size.cur) && !BT_OTS_OBJ_GET_PROP_PATCH(otc_inst->cur_object.props)) { LOG_ERR("Patch is not supported"); return -EACCES; } CHECKIF(((len + offset) > otc_inst->cur_object.size.alloc) && !BT_OTS_OBJ_GET_PROP_APPEND(otc_inst->cur_object.props)) { LOG_ERR("APPEND is not supported. Invalid new end of object %lu alloc %zu." , (len + offset), otc_inst->cur_object.size.alloc); return -EINVAL; } inst = lookup_inst_by_handle(otc_inst->start_handle); if (!inst) { LOG_ERR("Invalid OTC instance"); return -EINVAL; } return oacp_write(conn, inst, buf, (uint32_t)len, (uint32_t)offset, mode); } int bt_ots_client_get_object_checksum(struct bt_ots_client *otc_inst, struct bt_conn *conn, off_t offset, size_t len) { struct bt_otc_internal_instance_t *inst; CHECKIF(!conn) { LOG_DBG("Invalid Connection"); return -ENOTCONN; } CHECKIF(!otc_inst) { LOG_DBG("Invalid OTC instance"); return -EINVAL; } /* OTS_v10.pdf Table 3.9: Object Action Control Point Procedure Requirements * Offset and Length field are UINT32 Length */ CHECKIF(len > UINT32_MAX) { LOG_DBG("length %zu exceeds UINT32", len); return -EINVAL; } CHECKIF(len == 0) { LOG_DBG("length equals zero"); return -EINVAL; } CHECKIF((sizeof(offset) > sizeof(uint32_t) && (offset > UINT32_MAX)) || (offset < 0)) { LOG_DBG("offset exceeds %ld UINT32 and must be >= 0", offset); return -EINVAL; } CHECKIF((len + offset) > otc_inst->cur_object.size.cur) { LOG_DBG("The sum of offset (%ld) and length (%zu) exceed the Current Size %lu " "alloc %zu.", offset, len, (len + offset), otc_inst->cur_object.size.cur); return -EINVAL; } inst = lookup_inst_by_handle(otc_inst->start_handle); if (!inst) { LOG_DBG("Invalid OTC instance"); return -EINVAL; } return oacp_checksum(conn, inst, (uint32_t)offset, (uint32_t)len); } 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; LOG_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) { LOG_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) { LOG_WRN("Invalid Connection"); return -ENOTCONN; } else if (!otc_inst) { LOG_ERR("Invalid OTC instance"); return -EINVAL; } else if (!metadata) { LOG_WRN("No metadata to read"); return -ENOEXEC; } inst = lookup_inst_by_handle(otc_inst->start_handle); if (!inst) { LOG_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) { LOG_WRN("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) { LOG_WRN("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_OTS_CLIENT_LOG_LEVEL_DBG)) { char t[BT_OTS_OBJ_ID_STR_LEN]; (void)bt_ots_obj_id_to_str(rec->metadata.id, t, sizeof(t)); LOG_DBG("Object ID 0x%s", t); } if ((start_len - buf->len) + sizeof(uint8_t) > rec->len) { LOG_WRN("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) { LOG_WRN("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)) { LOG_WRN("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) { LOG_WRN("incorrect DirListing record, reclen %u too short, " "does not include flags", rec->len); return -EINVAL; } rec->flags = net_buf_simple_pull_u8(buf); LOG_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) { LOG_WRN("incorrect DirListing record, reclen %u " "flags indicates uuid128, too short", rec->len); LOG_INF("flags 0x%x", rec->flags); return -EINVAL; } uuid = net_buf_simple_pull_mem(buf, BT_UUID_SIZE_128); if (!bt_uuid_create(&rec->metadata.type.uuid, uuid, BT_UUID_SIZE_128)) { LOG_DBG("Failed to create UUID"); return -EINVAL; } } else { if ((start_len - buf->len) + BT_UUID_SIZE_16 > rec->len) { LOG_WRN("incorrect DirListing record, reclen %u " "flags indicates uuid16, too short", rec->len); LOG_INF("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) { LOG_WRN("incorrect DirListing record, reclen %u " "flags indicates cur_size, too short", rec->len); LOG_INF("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) { LOG_WRN("incorrect DirListing record, reclen %u " "flags indicates allocated size, too short", rec->len); LOG_INF("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) { LOG_WRN("incorrect DirListing record, reclen %u " "too short flags indicates first_created", rec->len); LOG_INF("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) { LOG_WRN("incorrect DirListing record, reclen %u " "flags indicates las_mod, too short", rec->len); LOG_INF("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) { LOG_WRN("incorrect DirListing record, reclen %u " "flags indicates properties, too short", rec->len); LOG_INF("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)) { LOG_WRN("incorrect DirListing record, len %u too short", net_buf.len); return -EINVAL; } LOG_DBG("Decoding record %u", count); ret = decode_record(&net_buf, &record); if (ret < 0) { LOG_WRN("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) { LOG_INF("--- 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)); LOG_INF("Object ID: 0x%s", t); LOG_INF("Object name: %s", metadata->name_c); LOG_INF("Object Current Size: %u", metadata->size.cur); LOG_INF("Object Allocate Size: %u", metadata->size.alloc); if (!bt_uuid_cmp(&metadata->type.uuid, BT_UUID_OTS_TYPE_MPL_ICON)) { LOG_INF("Type: Icon Obj Type"); } else if (!bt_uuid_cmp(&metadata->type.uuid, BT_UUID_OTS_TYPE_TRACK_SEGMENT)) { LOG_INF("Type: Track Segment Obj Type"); } else if (!bt_uuid_cmp(&metadata->type.uuid, BT_UUID_OTS_TYPE_TRACK)) { LOG_INF("Type: Track Obj Type"); } else if (!bt_uuid_cmp(&metadata->type.uuid, BT_UUID_OTS_TYPE_GROUP)) { LOG_INF("Type: Group Obj Type"); } else if (!bt_uuid_cmp(&metadata->type.uuid, BT_UUID_OTS_DIRECTORY_LISTING)) { LOG_INF("Type: Directory Listing"); } LOG_INF("Properties:0x%x", metadata->props); if (BT_OTS_OBJ_GET_PROP_APPEND(metadata->props)) { LOG_INF(" - append permitted"); } if (BT_OTS_OBJ_GET_PROP_DELETE(metadata->props)) { LOG_INF(" - delete permitted"); } if (BT_OTS_OBJ_GET_PROP_EXECUTE(metadata->props)) { LOG_INF(" - execute permitted"); } if (BT_OTS_OBJ_GET_PROP_MARKED(metadata->props)) { LOG_INF(" - marked"); } if (BT_OTS_OBJ_GET_PROP_PATCH(metadata->props)) { LOG_INF(" - patch permitted"); } if (BT_OTS_OBJ_GET_PROP_READ(metadata->props)) { LOG_INF(" - read permitted"); } if (BT_OTS_OBJ_GET_PROP_TRUNCATE(metadata->props)) { LOG_INF(" - truncate permitted"); } if (BT_OTS_OBJ_GET_PROP_WRITE(metadata->props)) { LOG_INF(" - write permitted"); } } }