| /** @file |
| * @brief Bluetooth Object Transfer Client |
| * |
| * Copyright (c) 2020-2022 Nordic Semiconductor ASA |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #include <zephyr/kernel.h> |
| #include <zephyr/types.h> |
| |
| #include <zephyr/device.h> |
| #include <zephyr/init.h> |
| #include <zephyr/sys/byteorder.h> |
| #include <zephyr/sys/check.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 LOG_LEVEL CONFIG_BT_OTS_CLIENT_LOG_LEVEL |
| #include <zephyr/logging/log.h> |
| 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); |
| } |
| /* 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; |
| |
| 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) { |
| 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; |
| |
| 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) |
| { |
| 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; |
| |
| bt_uuid_create(uuid, data, length); |
| |
| 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((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((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); |
| bt_uuid_create(&rec->metadata.type.uuid, |
| uuid, BT_UUID_SIZE_128); |
| } 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"); |
| } |
| } |
| } |