| /* |
| * Copyright (c) 2020 Nordic Semiconductor ASA |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #include <zephyr/types.h> |
| #include <stddef.h> |
| #include <string.h> |
| #include <errno.h> |
| #include <zephyr/sys/printk.h> |
| #include <zephyr/sys/byteorder.h> |
| #include <zephyr/sys/check.h> |
| #include <zephyr/kernel.h> |
| #include <zephyr/sys/crc.h> |
| |
| #include <zephyr/bluetooth/gatt.h> |
| #include <zephyr/bluetooth/services/ots.h> |
| #include "ots_internal.h" |
| #include "ots_dir_list_internal.h" |
| #include "ots_obj_manager_internal.h" |
| |
| #include <zephyr/logging/log.h> |
| |
| LOG_MODULE_DECLARE(bt_ots, CONFIG_BT_OTS_LOG_LEVEL); |
| |
| #define OACP_PROC_TYPE_SIZE 1 |
| /** |
| * OTS_v10.pdf Table 3.10: Format of OACP Response V |
| * OACP Response Value contains |
| * 1 octet Procedure code |
| * 1 octet Request op code |
| * 1 octet Result Code |
| * 4 octet CRC checksum (if present) |
| * Execute operation is not supported |
| **/ |
| #define OACP_RES_MAX_SIZE (3 + sizeof(uint32_t)) |
| |
| #if defined(CONFIG_BT_OTS_OACP_WRITE_SUPPORT) |
| static ssize_t oacp_write_proc_cb(struct bt_gatt_ots_l2cap *l2cap_ctx, |
| struct bt_conn *conn, struct net_buf *buf); |
| |
| static void oacp_l2cap_closed(struct bt_gatt_ots_l2cap *l2cap_ctx, |
| struct bt_conn *conn) |
| { |
| struct bt_ots *ots; |
| |
| ots = CONTAINER_OF(l2cap_ctx, struct bt_ots, l2cap); |
| |
| if (!ots->cur_obj) { |
| return; |
| } |
| |
| ots->cur_obj->state.type = BT_GATT_OTS_OBJECT_IDLE_STATE; |
| l2cap_ctx->rx_done = NULL; |
| l2cap_ctx->tx_done = NULL; |
| } |
| #endif |
| |
| #if defined(CONFIG_BT_OTS_OACP_CREATE_SUPPORT) |
| static enum bt_gatt_ots_oacp_res_code oacp_create_proc_validate( |
| struct bt_conn *conn, |
| struct bt_ots *ots, |
| struct bt_gatt_ots_oacp_proc *proc) |
| { |
| char str[BT_UUID_STR_LEN]; |
| int err; |
| struct bt_gatt_ots_object *obj; |
| const struct bt_ots_obj_add_param param = { |
| .size = proc->create_params.size, |
| .type = proc->create_params.type, |
| }; |
| |
| bt_uuid_to_str(¶m.type.uuid, str, BT_UUID_STR_LEN); |
| LOG_DBG("Validating Create procedure with size: 0x%08X and " |
| "type: %s", param.size, str); |
| |
| if (!BT_OTS_OACP_GET_FEAT_CREATE(ots->features.oacp)) { |
| LOG_DBG("Create Procedure is not supported."); |
| return BT_GATT_OTS_OACP_RES_OPCODE_NOT_SUP; |
| } |
| |
| err = bt_ots_obj_add_internal(ots, conn, ¶m, &obj); |
| if (err) { |
| goto exit; |
| } |
| |
| /* Verify Initialization Metadata */ |
| if (strlen(obj->metadata.name) > 0) { |
| LOG_ERR("Object name shall be a zero length string after object creation."); |
| (void)bt_ots_obj_delete(ots, obj->id); |
| err = -ECANCELED; |
| goto exit; |
| } |
| |
| if (obj->metadata.size.cur > 0) { |
| LOG_ERR("Object current size must be 0."); |
| (void)bt_ots_obj_delete(ots, obj->id); |
| err = -ECANCELED; |
| goto exit; |
| } |
| |
| if (!BT_OTS_OBJ_GET_PROP_WRITE(obj->metadata.props)) { |
| LOG_ERR("Created object must have write property."); |
| (void)bt_ots_obj_delete(ots, obj->id); |
| err = -ECANCELED; |
| goto exit; |
| } |
| |
| ots->cur_obj = obj; |
| ots->cur_obj->state.type = BT_GATT_OTS_OBJECT_IDLE_STATE; |
| |
| LOG_DBG("Create procedure is complete"); |
| |
| exit: |
| switch (err) { |
| case 0: |
| return BT_GATT_OTS_OACP_RES_SUCCESS; |
| case -ENOTSUP: |
| return BT_GATT_OTS_OACP_RES_UNSUP_TYPE; |
| case -ENOMEM: |
| return BT_GATT_OTS_OACP_RES_INSUFF_RES; |
| case -EINVAL: |
| return BT_GATT_OTS_OACP_RES_INV_PARAM; |
| case -ECANCELED: |
| default: |
| return BT_GATT_OTS_OACP_RES_OPER_FAILED; |
| } |
| } |
| #endif |
| |
| #if defined(CONFIG_BT_OTS_OACP_DELETE_SUPPORT) |
| static enum bt_gatt_ots_oacp_res_code oacp_delete_proc_validate( |
| struct bt_conn *conn, |
| struct bt_ots *ots, |
| struct bt_gatt_ots_oacp_proc *proc) |
| { |
| int err; |
| |
| if (!BT_OTS_OACP_GET_FEAT_DELETE(ots->features.oacp)) { |
| LOG_DBG("Delete Procedure is not supported."); |
| return BT_GATT_OTS_OACP_RES_OPCODE_NOT_SUP; |
| } |
| |
| if (!ots->cur_obj) { |
| LOG_DBG("No object is selected."); |
| return BT_GATT_OTS_OACP_RES_INV_OBJ; |
| } |
| |
| if (!BT_OTS_OBJ_GET_PROP_DELETE(ots->cur_obj->metadata.props)) { |
| LOG_DBG("Object properties do not permit deletion."); |
| return BT_GATT_OTS_OACP_RES_NOT_PERMITTED; |
| } |
| |
| err = bt_ots_obj_delete(ots, ots->cur_obj->id); |
| if (err) { |
| LOG_ERR("Deleting object during Delete procedure failed: %d", err); |
| goto exit; |
| } |
| |
| LOG_DBG("Delete procedure is complete"); |
| |
| exit: |
| switch (err) { |
| case 0: |
| return BT_GATT_OTS_OACP_RES_SUCCESS; |
| case -EBUSY: |
| return BT_GATT_OTS_OACP_RES_OBJ_LOCKED; |
| default: |
| return BT_GATT_OTS_OACP_RES_OPER_FAILED; |
| } |
| } |
| #endif |
| |
| #if defined(CONFIG_BT_OTS_OACP_CHECKSUM_SUPPORT) |
| static enum bt_gatt_ots_oacp_res_code oacp_checksum_proc_validate( |
| struct bt_conn *conn, |
| struct bt_ots *ots, |
| struct bt_gatt_ots_oacp_proc *proc, |
| struct net_buf_simple *resp_param) |
| { |
| struct bt_gatt_ots_oacp_cs_calc_params *params = &proc->cs_calc_params; |
| void *obj_data; |
| int err; |
| uint32_t checksum; |
| |
| LOG_DBG("Validating Checksum procedure with offset: 0x%08X and " |
| "length: 0x%08X", params->offset, params->len); |
| |
| if (!ots->cur_obj) { |
| return BT_GATT_OTS_OACP_RES_INV_OBJ; |
| } |
| |
| if (params->offset > ots->cur_obj->metadata.size.cur) { |
| return BT_GATT_OTS_OACP_RES_INV_PARAM; |
| } |
| |
| if ((params->offset + (uint64_t) params->len) > ots->cur_obj->metadata.size.alloc) { |
| return BT_GATT_OTS_OACP_RES_INV_PARAM; |
| } |
| |
| if (ots->cur_obj->state.type != BT_GATT_OTS_OBJECT_IDLE_STATE) { |
| return BT_GATT_OTS_OACP_RES_OBJ_LOCKED; |
| } |
| |
| if (ots->cb->obj_cal_checksum) { |
| err = ots->cb->obj_cal_checksum(ots, conn, ots->cur_obj->id, params->offset, |
| params->len, &obj_data); |
| if (err != 0) { |
| return BT_GATT_OTS_OACP_RES_OPER_FAILED; |
| } |
| |
| checksum = bt_ots_client_calc_checksum((const uint8_t *)obj_data, params->len); |
| net_buf_simple_reserve(resp_param, sizeof(uint32_t)); |
| net_buf_simple_add_le32(resp_param, checksum); |
| LOG_DBG("Calculate from offset %u len %u checksum 0x%08x\n", params->offset, |
| params->len, checksum); |
| return BT_GATT_OTS_OACP_RES_SUCCESS; |
| } else { |
| return BT_GATT_OTS_OACP_RES_OPER_FAILED; |
| } |
| } |
| #endif |
| |
| static enum bt_gatt_ots_oacp_res_code oacp_read_proc_validate( |
| struct bt_conn *conn, |
| struct bt_ots *ots, |
| const struct bt_gatt_ots_oacp_proc *proc) |
| { |
| const struct bt_gatt_ots_oacp_read_params *params = &proc->read_params; |
| |
| LOG_DBG("Validating Read procedure with offset: 0x%08X and " |
| "length: 0x%08X", params->offset, params->len); |
| |
| if (!ots->cur_obj) { |
| return BT_GATT_OTS_OACP_RES_INV_OBJ; |
| } |
| |
| if (!BT_OTS_OBJ_GET_PROP_READ(ots->cur_obj->metadata.props)) { |
| return BT_GATT_OTS_OACP_RES_NOT_PERMITTED; |
| } |
| |
| if (!bt_gatt_ots_l2cap_is_open(&ots->l2cap, conn)) { |
| return BT_GATT_OTS_OACP_RES_CHAN_UNAVAIL; |
| } |
| |
| if ((params->offset + (uint64_t) params->len) > |
| ots->cur_obj->metadata.size.cur) { |
| return BT_GATT_OTS_OACP_RES_INV_PARAM; |
| } |
| |
| if (ots->cur_obj->state.type != BT_GATT_OTS_OBJECT_IDLE_STATE) { |
| return BT_GATT_OTS_OACP_RES_OBJ_LOCKED; |
| } |
| |
| ots->cur_obj->state.type = BT_GATT_OTS_OBJECT_READ_OP_STATE; |
| ots->cur_obj->state.read_op.sent_len = 0; |
| memcpy(&ots->cur_obj->state.read_op.oacp_params, &proc->read_params, |
| sizeof(ots->cur_obj->state.read_op.oacp_params)); |
| |
| LOG_DBG("Read procedure is accepted"); |
| |
| return BT_GATT_OTS_OACP_RES_SUCCESS; |
| } |
| |
| #if defined(CONFIG_BT_OTS_OACP_WRITE_SUPPORT) |
| static enum bt_gatt_ots_oacp_res_code oacp_write_proc_validate( |
| struct bt_conn *conn, |
| struct bt_ots *ots, |
| struct bt_gatt_ots_oacp_proc *proc) |
| { |
| struct bt_gatt_ots_oacp_write_params *params = &proc->write_params; |
| |
| LOG_DBG("Validating Write procedure with offset: 0x%08X and " |
| "length: 0x%08X", params->offset, params->len); |
| |
| if (!ots->cur_obj) { |
| return BT_GATT_OTS_OACP_RES_INV_OBJ; |
| } |
| |
| if (!BT_OTS_OBJ_GET_PROP_WRITE(ots->cur_obj->metadata.props)) { |
| return BT_GATT_OTS_OACP_RES_NOT_PERMITTED; |
| } |
| |
| /* patching is attempted */ |
| if (params->offset < ots->cur_obj->metadata.size.cur) { |
| if (!BT_OTS_OACP_GET_FEAT_PATCH(ots->features.oacp)) { |
| return BT_GATT_OTS_OACP_RES_NOT_PERMITTED; |
| } |
| if (!BT_OTS_OBJ_GET_PROP_PATCH(ots->cur_obj->metadata.props)) { |
| return BT_GATT_OTS_OACP_RES_NOT_PERMITTED; |
| } |
| } |
| |
| /* truncation is not supported */ |
| if (BT_GATT_OTS_OACP_PROC_WRITE_MODE_GET_TRUNC(params->mode)) { |
| return BT_GATT_OTS_OACP_RES_NOT_PERMITTED; |
| } |
| |
| if (!bt_gatt_ots_l2cap_is_open(&ots->l2cap, conn)) { |
| return BT_GATT_OTS_OACP_RES_CHAN_UNAVAIL; |
| } |
| |
| if (BT_GATT_OTS_OACP_PROC_WRITE_MODE_GET_RFU(params->mode)) { |
| return BT_GATT_OTS_OACP_RES_INV_PARAM; |
| } |
| |
| if (params->offset > ots->cur_obj->metadata.size.cur) { |
| return BT_GATT_OTS_OACP_RES_INV_PARAM; |
| } |
| |
| /* append is not supported */ |
| if ((params->offset + (uint64_t) params->len) > ots->cur_obj->metadata.size.alloc) { |
| return BT_GATT_OTS_OACP_RES_INV_PARAM; |
| } |
| |
| if (ots->cur_obj->state.type != BT_GATT_OTS_OBJECT_IDLE_STATE) { |
| return BT_GATT_OTS_OACP_RES_OBJ_LOCKED; |
| } |
| |
| ots->l2cap.rx_done = oacp_write_proc_cb; |
| ots->l2cap.closed = oacp_l2cap_closed; |
| ots->cur_obj->state.type = BT_GATT_OTS_OBJECT_WRITE_OP_STATE; |
| ots->cur_obj->state.write_op.recv_len = 0; |
| memcpy(&ots->cur_obj->state.write_op.oacp_params, params, |
| sizeof(ots->cur_obj->state.write_op.oacp_params)); |
| |
| LOG_DBG("Write procedure is accepted"); |
| |
| return BT_GATT_OTS_OACP_RES_SUCCESS; |
| } |
| #endif |
| |
| static enum bt_gatt_ots_oacp_res_code oacp_proc_validate( |
| struct bt_conn *conn, |
| struct bt_ots *ots, |
| struct bt_gatt_ots_oacp_proc *proc, |
| struct net_buf_simple *resp_param) |
| { |
| switch (proc->type) { |
| case BT_GATT_OTS_OACP_PROC_READ: |
| return oacp_read_proc_validate(conn, ots, proc); |
| #if defined(CONFIG_BT_OTS_OACP_WRITE_SUPPORT) |
| case BT_GATT_OTS_OACP_PROC_WRITE: |
| return oacp_write_proc_validate(conn, ots, proc); |
| #endif |
| #if defined(CONFIG_BT_OTS_OACP_CREATE_SUPPORT) |
| case BT_GATT_OTS_OACP_PROC_CREATE: |
| return oacp_create_proc_validate(conn, ots, proc); |
| #endif |
| #if defined(CONFIG_BT_OTS_OACP_DELETE_SUPPORT) |
| case BT_GATT_OTS_OACP_PROC_DELETE: |
| return oacp_delete_proc_validate(conn, ots, proc); |
| #endif |
| #if defined(CONFIG_BT_OTS_OACP_CHECKSUM_SUPPORT) |
| case BT_GATT_OTS_OACP_PROC_CHECKSUM_CALC: |
| return oacp_checksum_proc_validate(conn, ots, proc, resp_param); |
| #endif |
| case BT_GATT_OTS_OACP_PROC_EXECUTE: |
| case BT_GATT_OTS_OACP_PROC_ABORT: |
| default: |
| return BT_GATT_OTS_OACP_RES_OPCODE_NOT_SUP; |
| } |
| }; |
| |
| static int oacp_command_decode(const uint8_t *buf, uint16_t len, |
| struct bt_gatt_ots_oacp_proc *proc) |
| { |
| struct net_buf_simple net_buf; |
| |
| if (len < OACP_PROC_TYPE_SIZE) { |
| return -ENODATA; |
| } |
| |
| net_buf_simple_init_with_data(&net_buf, (void *) buf, len); |
| |
| proc->type = net_buf_simple_pull_u8(&net_buf); |
| switch (proc->type) { |
| #if defined(CONFIG_BT_OTS_OACP_CREATE_SUPPORT) |
| case BT_GATT_OTS_OACP_PROC_CREATE: |
| if (net_buf.len < BT_GATT_OTS_OACP_CREATE_GENERIC_PARAMS_SIZE) { |
| return -EBADMSG; |
| } |
| proc->create_params.size = net_buf_simple_pull_le32(&net_buf); |
| if (!bt_uuid_create(&proc->create_params.type.uuid, net_buf.data, |
| net_buf.len)) { |
| return -EBADMSG; |
| } |
| net_buf_simple_pull_mem(&net_buf, net_buf.len); |
| |
| /* Only 16-bit and 128-bit UUIDs are supported */ |
| switch (proc->create_params.type.uuid.type) { |
| case BT_UUID_TYPE_16: |
| case BT_UUID_TYPE_128: |
| return 0; |
| default: |
| break; |
| } |
| |
| return -EBADMSG; |
| #endif |
| #if defined(CONFIG_BT_OTS_OACP_DELETE_SUPPORT) |
| case BT_GATT_OTS_OACP_PROC_DELETE: |
| if (net_buf.len != 0) { |
| return -EBADMSG; |
| } |
| |
| return 0; |
| #endif |
| case BT_GATT_OTS_OACP_PROC_CHECKSUM_CALC: |
| if (net_buf.len != BT_GATT_OTS_OACP_CS_CALC_PARAMS_SIZE) { |
| return -EBADMSG; |
| } |
| proc->cs_calc_params.offset = |
| net_buf_simple_pull_le32(&net_buf); |
| proc->cs_calc_params.len = |
| net_buf_simple_pull_le32(&net_buf); |
| |
| return 0; |
| case BT_GATT_OTS_OACP_PROC_EXECUTE: |
| if (net_buf.len != 0) { |
| return -EBADMSG; |
| } |
| |
| return 0; |
| case BT_GATT_OTS_OACP_PROC_READ: |
| if (net_buf.len != BT_GATT_OTS_OACP_READ_PARAMS_SIZE) { |
| return -EBADMSG; |
| } |
| proc->read_params.offset = |
| net_buf_simple_pull_le32(&net_buf); |
| proc->read_params.len = |
| net_buf_simple_pull_le32(&net_buf); |
| |
| return 0; |
| #if defined(CONFIG_BT_OTS_OACP_WRITE_SUPPORT) |
| case BT_GATT_OTS_OACP_PROC_WRITE: |
| if (net_buf.len != BT_GATT_OTS_OACP_WRITE_PARAMS_SIZE) { |
| return -EBADMSG; |
| } |
| proc->write_params.offset = |
| net_buf_simple_pull_le32(&net_buf); |
| proc->write_params.len = |
| net_buf_simple_pull_le32(&net_buf); |
| proc->write_params.mode = |
| net_buf_simple_pull_u8(&net_buf); |
| |
| return 0; |
| #endif |
| case BT_GATT_OTS_OACP_PROC_ABORT: |
| default: |
| break; |
| } |
| |
| return -ENOTSUP; |
| } |
| |
| static void oacp_read_proc_cb(struct bt_gatt_ots_l2cap *l2cap_ctx, |
| struct bt_conn *conn) |
| { |
| int err; |
| void *obj_chunk; |
| off_t offset; |
| ssize_t len; |
| struct bt_ots *ots; |
| struct bt_gatt_ots_object_read_op *read_op; |
| |
| ots = CONTAINER_OF(l2cap_ctx, struct bt_ots, l2cap); |
| read_op = &ots->cur_obj->state.read_op; |
| offset = read_op->oacp_params.offset + read_op->sent_len; |
| |
| if (read_op->sent_len >= read_op->oacp_params.len) { |
| LOG_DBG("OACP Read Op over L2CAP is completed"); |
| |
| if (read_op->sent_len > read_op->oacp_params.len) { |
| LOG_WRN("More bytes sent that the client requested"); |
| } |
| |
| ots->cur_obj->state.type = BT_GATT_OTS_OBJECT_IDLE_STATE; |
| |
| if (IS_ENABLED(CONFIG_BT_OTS_DIR_LIST_OBJ) && |
| ots->cur_obj->id == OTS_OBJ_ID_DIR_LIST) { |
| return; |
| } |
| |
| ots->cb->obj_read(ots, conn, ots->cur_obj->id, NULL, 0, |
| offset); |
| return; |
| } |
| |
| len = read_op->oacp_params.len - read_op->sent_len; |
| if (IS_ENABLED(CONFIG_BT_OTS_DIR_LIST_OBJ) && |
| ots->cur_obj->id == OTS_OBJ_ID_DIR_LIST) { |
| len = bt_ots_dir_list_content_get(ots->dir_list, ots->obj_manager, |
| &obj_chunk, len, offset); |
| } else { |
| len = ots->cb->obj_read(ots, conn, ots->cur_obj->id, &obj_chunk, |
| len, offset); |
| } |
| |
| if (len < 0) { |
| LOG_ERR("OCAP Read Op failed with error: %zd", len); |
| |
| bt_gatt_ots_l2cap_disconnect(&ots->l2cap); |
| ots->cur_obj->state.type = BT_GATT_OTS_OBJECT_IDLE_STATE; |
| |
| return; |
| } |
| |
| ots->l2cap.tx_done = oacp_read_proc_cb; |
| err = bt_gatt_ots_l2cap_send(&ots->l2cap, obj_chunk, len); |
| if (err) { |
| LOG_ERR("L2CAP CoC error: %d while trying to execute OACP " |
| "Read procedure", err); |
| ots->cur_obj->state.type = BT_GATT_OTS_OBJECT_IDLE_STATE; |
| } else { |
| read_op->sent_len += len; |
| } |
| } |
| |
| static void oacp_read_proc_execute(struct bt_ots *ots, |
| struct bt_conn *conn) |
| { |
| struct bt_gatt_ots_oacp_read_params *params = |
| &ots->cur_obj->state.read_op.oacp_params; |
| |
| if (!ots->cur_obj) { |
| LOG_ERR("Invalid Current Object on OACP Read procedure"); |
| return; |
| } |
| |
| LOG_DBG("Executing Read procedure with offset: 0x%08X and " |
| "length: 0x%08X", params->offset, params->len); |
| |
| if (IS_ENABLED(CONFIG_BT_OTS_DIR_LIST_OBJ) && |
| ots->cur_obj->id == OTS_OBJ_ID_DIR_LIST) { |
| oacp_read_proc_cb(&ots->l2cap, conn); |
| } else if (ots->cb->obj_read) { |
| oacp_read_proc_cb(&ots->l2cap, conn); |
| } else { |
| ots->cur_obj->state.type = BT_GATT_OTS_OBJECT_IDLE_STATE; |
| LOG_ERR("OTS Read operation failed: " |
| "there is no OTS Read callback"); |
| } |
| } |
| |
| #if defined(CONFIG_BT_OTS_OACP_WRITE_SUPPORT) |
| static ssize_t oacp_write_proc_cb(struct bt_gatt_ots_l2cap *l2cap_ctx, |
| struct bt_conn *conn, struct net_buf *buf) |
| { |
| struct bt_gatt_ots_object_write_op *write_op; |
| struct bt_ots *ots; |
| off_t offset; |
| size_t rem; |
| size_t len; |
| ssize_t rc; |
| |
| ots = CONTAINER_OF(l2cap_ctx, struct bt_ots, l2cap); |
| |
| if (!ots->cur_obj) { |
| LOG_ERR("Invalid Current Object on OACP Write procedure"); |
| return -ENODEV; |
| } |
| |
| if (!ots->cb->obj_write) { |
| LOG_ERR("OTS Write operation failed: " |
| "there is no OTS Write callback"); |
| ots->cur_obj->state.type = BT_GATT_OTS_OBJECT_IDLE_STATE; |
| return -ENODEV; |
| } |
| |
| write_op = &ots->cur_obj->state.write_op; |
| offset = write_op->oacp_params.offset + write_op->recv_len; |
| len = buf->len; |
| if (write_op->recv_len + len > write_op->oacp_params.len) { |
| LOG_WRN("More bytes received than the client indicated"); |
| len = write_op->oacp_params.len - write_op->recv_len; |
| } |
| rem = write_op->oacp_params.len - (write_op->recv_len + len); |
| |
| rc = ots->cb->obj_write(ots, conn, ots->cur_obj->id, buf->data, len, |
| offset, rem); |
| |
| if (rc < 0) { |
| len = 0; |
| |
| /* |
| * Returning an EINPROGRESS return code results in the write buffer not being |
| * released by the l2cap layer. This is an unsupported use case at the moment. |
| */ |
| if (rc == -EINPROGRESS) { |
| LOG_ERR("Unsupported error code %zd returned by object write callback", rc); |
| } |
| |
| LOG_ERR("OTS Write operation failed with error: %zd", rc); |
| ots->cur_obj->state.type = BT_GATT_OTS_OBJECT_IDLE_STATE; |
| } else { |
| /* Return -EIO as an error if all of data was not written */ |
| if (rc != len) { |
| len = rc; |
| rc = -EIO; |
| } |
| } |
| |
| write_op->recv_len += len; |
| if (write_op->recv_len == write_op->oacp_params.len) { |
| LOG_DBG("OACP Write Op over L2CAP is completed"); |
| ots->cur_obj->state.type = BT_GATT_OTS_OBJECT_IDLE_STATE; |
| } |
| |
| if (offset + len > ots->cur_obj->metadata.size.cur) { |
| ots->cur_obj->metadata.size.cur = offset + len; |
| } |
| |
| return rc; |
| } |
| #endif |
| |
| static void oacp_ind_cb(struct bt_conn *conn, |
| struct bt_gatt_indicate_params *params, |
| uint8_t err) |
| { |
| struct bt_ots *ots = (struct bt_ots *) params->attr->user_data; |
| |
| LOG_DBG("Received OACP Indication ACK with status: 0x%04X", err); |
| |
| if (!ots->cur_obj) { |
| LOG_DBG("There is no object associated with this ACK"); |
| return; |
| } |
| |
| switch (ots->cur_obj->state.type) { |
| case BT_GATT_OTS_OBJECT_READ_OP_STATE: |
| oacp_read_proc_execute(ots, conn); |
| break; |
| case BT_GATT_OTS_OBJECT_WRITE_OP_STATE: |
| /* procedure execution is driven by L2CAP socket receive */ |
| break; |
| case BT_GATT_OTS_OBJECT_IDLE_STATE: |
| /* procedure is not in progress and was already completed */ |
| break; |
| default: |
| LOG_ERR("Unsupported OTS state: %d", ots->cur_obj->state.type); |
| break; |
| } |
| } |
| |
| static int oacp_ind_send(const struct bt_gatt_attr *oacp_attr, |
| struct bt_gatt_ots_oacp_proc oacp_proc, |
| enum bt_gatt_ots_oacp_res_code oacp_status, |
| struct net_buf_simple *resp_param) |
| { |
| uint8_t oacp_res[OACP_RES_MAX_SIZE]; |
| uint16_t oacp_res_len = 0; |
| struct bt_ots *ots = (struct bt_ots *) oacp_attr->user_data; |
| |
| /* Encode OACP Response */ |
| oacp_res[oacp_res_len++] = BT_GATT_OTS_OACP_PROC_RESP; |
| oacp_res[oacp_res_len++] = oacp_proc.type; |
| oacp_res[oacp_res_len++] = oacp_status; |
| |
| if (oacp_proc.type == BT_GATT_OTS_OACP_PROC_CHECKSUM_CALC) { |
| sys_put_le32(net_buf_simple_pull_le32(resp_param), (oacp_res + oacp_res_len)); |
| oacp_res_len += sizeof(uint32_t); |
| } |
| |
| /* Prepare indication parameters */ |
| memset(&ots->oacp_ind.params, 0, sizeof(ots->oacp_ind.params)); |
| memcpy(&ots->oacp_ind.attr, oacp_attr, sizeof(ots->oacp_ind.attr)); |
| ots->oacp_ind.params.attr = &ots->oacp_ind.attr; |
| ots->oacp_ind.params.func = oacp_ind_cb; |
| ots->oacp_ind.params.data = oacp_res; |
| ots->oacp_ind.params.len = oacp_res_len; |
| |
| LOG_DBG("Sending OACP indication"); |
| |
| return bt_gatt_indicate(NULL, &ots->oacp_ind.params); |
| } |
| |
| ssize_t bt_gatt_ots_oacp_write(struct bt_conn *conn, |
| const struct bt_gatt_attr *attr, |
| const void *buf, uint16_t len, |
| uint16_t offset, uint8_t flags) |
| { |
| enum bt_gatt_ots_oacp_res_code oacp_status; |
| int decode_status; |
| struct bt_gatt_ots_oacp_proc oacp_proc = {0}; |
| struct bt_ots *ots = (struct bt_ots *) attr->user_data; |
| struct net_buf_simple resp_param; |
| |
| LOG_DBG("Object Action Control Point GATT Write Operation"); |
| |
| if (!ots->oacp_ind.is_enabled) { |
| LOG_WRN("OACP indications not enabled"); |
| return BT_GATT_ERR(BT_ATT_ERR_CCC_IMPROPER_CONF); |
| } |
| |
| if (offset != 0) { |
| LOG_ERR("Invalid offset of OACP Write Request"); |
| return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET); |
| } |
| |
| decode_status = oacp_command_decode(buf, len, &oacp_proc); |
| switch (decode_status) { |
| case 0: |
| oacp_status = oacp_proc_validate(conn, ots, &oacp_proc, &resp_param); |
| if (oacp_status != BT_GATT_OTS_OACP_RES_SUCCESS) { |
| LOG_WRN("OACP Write error status: 0x%02X", oacp_status); |
| } |
| break; |
| case -ENOTSUP: |
| oacp_status = BT_GATT_OTS_OACP_RES_OPCODE_NOT_SUP; |
| LOG_WRN("OACP unsupported procedure type: 0x%02X", oacp_proc.type); |
| break; |
| case -EBADMSG: |
| LOG_ERR("Invalid length of OACP Write Request for 0x%02X " |
| "Op Code", oacp_proc.type); |
| return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN); |
| case -ENODATA: |
| LOG_ERR("Invalid length of OACP Write Request"); |
| return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN); |
| default: |
| LOG_ERR("Invalid return code from oacp_command_decode: %d", decode_status); |
| return BT_GATT_ERR(BT_ATT_ERR_UNLIKELY); |
| } |
| |
| oacp_ind_send(attr, oacp_proc, oacp_status, &resp_param); |
| return len; |
| } |
| |
| void bt_gatt_ots_oacp_cfg_changed(const struct bt_gatt_attr *attr, |
| uint16_t value) |
| { |
| struct bt_gatt_ots_indicate *oacp_ind = |
| CONTAINER_OF((struct _bt_gatt_ccc *) attr->user_data, |
| struct bt_gatt_ots_indicate, ccc); |
| |
| LOG_DBG("Object Action Control Point CCCD value: 0x%04X", value); |
| |
| oacp_ind->is_enabled = false; |
| if (value == BT_GATT_CCC_INDICATE) { |
| oacp_ind->is_enabled = true; |
| } |
| } |