From 1e6f36cca734ea34d4958ccfe6569735f2f1075b Mon Sep 17 00:00:00 2001 From: Pirun Lee Date: Tue, 10 Jan 2023 09:52:10 +0800 Subject: [PATCH] Bluetooth: OTS - Add Calculate Checksum support OTS add Calculate checksum feature support. OTS client add object calculate checksum function. Signed-off-by: Pirun Lee --- include/zephyr/bluetooth/services/ots.h | 69 ++++++++++- subsys/bluetooth/services/ots/Kconfig | 3 + subsys/bluetooth/services/ots/ots.c | 11 ++ subsys/bluetooth/services/ots/ots_client.c | 127 ++++++++++++++++++++- subsys/bluetooth/services/ots/ots_oacp.c | 92 +++++++++++++-- 5 files changed, 285 insertions(+), 17 deletions(-) diff --git a/include/zephyr/bluetooth/services/ots.h b/include/zephyr/bluetooth/services/ots.h index cfcb306e074..69a0ec7031a 100644 --- a/include/zephyr/bluetooth/services/ots.h +++ b/include/zephyr/bluetooth/services/ots.h @@ -26,6 +26,7 @@ extern "C" { #include #include #include +#include #include #include #include @@ -725,6 +726,24 @@ struct bt_ots_cb { */ void (*obj_name_written)(struct bt_ots *ots, struct bt_conn *conn, uint64_t id, const char *cur_name, const char *new_name); + + /** @brief Object Calculate checksum callback + * + * This callback is called when the OACP Calculate Checksum procedure is performed. + * Because object data is opaque to OTS, the application is the only one who + * knows where data is and should return pointer of actual object data. + * + * @param[in] ots OTS instance. + * @param[in] conn The connection that wrote object. + * @param[in] id Object ID. + * @param[in] offset The first octet of the object contents need to be calculated. + * @param[in] len The length number of octets object name. + * @param[out] data Pointer of actual object data. + * + * @return 0 to accept, or any negative value to reject. + */ + int (*obj_cal_checksum)(struct bt_ots *ots, struct bt_conn *conn, uint64_t id, + off_t offset, size_t len, void **data); }; /** @brief Descriptor for OTS initialization. */ @@ -888,6 +907,20 @@ struct bt_ots_client_cb { */ void (*obj_data_written)(struct bt_ots_client *ots_inst, struct bt_conn *conn, size_t len); + + /** @brief Callback function when checksum indication is received. + * + * Called when the oacp_ind_handler received response of + * OP BT_GATT_OTS_OACP_PROC_CHECKSUM_CALC. + * + * @param ots_inst Pointer to the OTC instance. + * @param conn The connection to the peer device. + * @param err Error code (bt_gatt_ots_oacp_res_code). + * @param checksum Checksum if error code is BT_GATT_OTS_OACP_RES_SUCCESS, + * otherwise 0. + */ + void (*obj_checksum_calculated)(struct bt_ots_client *ots_inst, struct bt_conn *conn, + int err, uint32_t checksum); }; /** @brief Register an Object Transfer Service Instance. @@ -1011,7 +1044,7 @@ int bt_ots_client_read_object_data(struct bt_ots_client *otc_inst, /** @brief Write the data of the current selected object. * - * This will trigger an OACP write operation for the current size of the object + * This will trigger an OACP write operation for the current object * with a specified offset and then expect transferring the content via the L2CAP CoC. * * The length of the data written to object is returned in the obj_data_written() callback. @@ -1029,6 +1062,24 @@ int bt_ots_client_write_object_data(struct bt_ots_client *otc_inst, struct bt_co const void *buf, size_t len, off_t offset, enum bt_ots_oacp_write_op_mode mode); +/** @brief Get the checksum of the current selected object. + * + * This will trigger an OACP calculate checksum operation for the current object + * with a specified offset and length. + * + * The checksum goes to OACP IND and obj_checksum_calculated() callback. + * + * @param otc_inst Pointer to the OTC instance. + * @param conn Pointer to the connection object. + * @param offset Offset to calculate, usually 0. + * @param len Len of data to calculate checksum for. May be less than the current object's + * size, but shall not be larger. + * + * @return int 0 if success, ERRNO on failure. + */ +int bt_ots_client_get_object_checksum(struct bt_ots_client *otc_inst, struct bt_conn *conn, + off_t offset, size_t len); + /** @brief Directory listing object metadata callback * * If a directory listing is decoded using bt_ots_client_decode_dirlisting(), @@ -1082,6 +1133,22 @@ static inline int bt_ots_obj_id_to_str(uint64_t obj_id, char *str, size_t len) void bt_ots_metadata_display(struct bt_ots_obj_metadata *metadata, uint16_t count); +/** + * @brief Generate IEEE conform CRC32 checksum. + * + * To abstract IEEE implementation to service layer. + * + * @param data Pointer to data on which the CRC should be calculated. + * @param len Data length. + * + * @return CRC32 value. + * + */ +static inline uint32_t bt_ots_client_calc_checksum(const uint8_t *data, size_t len) +{ + return crc32_ieee(data, len); +} + #ifdef __cplusplus } #endif diff --git a/subsys/bluetooth/services/ots/Kconfig b/subsys/bluetooth/services/ots/Kconfig index 89de483b125..25dd5faae46 100644 --- a/subsys/bluetooth/services/ots/Kconfig +++ b/subsys/bluetooth/services/ots/Kconfig @@ -41,6 +41,9 @@ config BT_OTS_OACP_CREATE_SUPPORT depends on BT_OTS_OACP_WRITE_SUPPORT depends on BT_OTS_OBJ_NAME_WRITE_SUPPORT +config BT_OTS_OACP_CHECKSUM_SUPPORT + bool "Support OACP Calculate Checksum operation" + config BT_OTS_OACP_DELETE_SUPPORT bool "Support OACP Delete Operation" diff --git a/subsys/bluetooth/services/ots/ots.c b/subsys/bluetooth/services/ots/ots.c index bec55ea6111..2a2c0b74624 100644 --- a/subsys/bluetooth/services/ots/ots.c +++ b/subsys/bluetooth/services/ots/ots.c @@ -42,6 +42,12 @@ LOG_MODULE_REGISTER(bt_ots, CONFIG_BT_OTS_LOG_LEVEL); #define OACP_FEAT_BIT_DELETE 0 #endif +#if defined(BT_OTS_OACP_CHECKSUM_SUPPORT) +#define OACP_FEAT_BIT_CRC BIT(BT_OTS_OACP_FEAT_CHECKSUM) +#else +#define OACP_FEAT_BIT_CRC 0 +#endif + #if defined(CONFIG_BT_OTS_OACP_READ_SUPPORT) #define OACP_FEAT_BIT_READ BIT(BT_OTS_OACP_FEAT_READ) #else @@ -64,6 +70,7 @@ LOG_MODULE_REGISTER(bt_ots, CONFIG_BT_OTS_LOG_LEVEL); #define OACP_FEAT ( \ OACP_FEAT_BIT_CREATE | \ OACP_FEAT_BIT_DELETE | \ + OACP_FEAT_BIT_CRC | \ OACP_FEAT_BIT_READ | \ OACP_FEAT_BIT_WRITE | \ OACP_FEAT_BIT_PATCH) @@ -443,6 +450,10 @@ int bt_ots_init(struct bt_ots *ots, __ASSERT(ots_init->cb->obj_deleted || !BT_OTS_OACP_GET_FEAT_CREATE(ots_init->features.oacp), "Callback for object deletion is not set and object creation is enabled"); +#if defined(CONFIG_BT_OTS_OACP_CHECKSUM_SUPPORT) + __ASSERT(ots_init->cb->obj_cal_checksum, + "Callback for object calculate checksum is not set"); +#endif __ASSERT(ots_init->cb->obj_read || !BT_OTS_OACP_GET_FEAT_READ(ots_init->features.oacp), "Callback for object reading is not set"); diff --git a/subsys/bluetooth/services/ots/ots_client.c b/subsys/bluetooth/services/ots/ots_client.c index 4fa0f800d71..2e966af6a67 100644 --- a/subsys/bluetooth/services/ots/ots_client.c +++ b/subsys/bluetooth/services/ots/ots_client.c @@ -125,6 +125,8 @@ static int oacp_read(struct bt_conn *conn, 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, @@ -356,6 +358,9 @@ static void oacp_ind_handler(struct bt_conn *conn, 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); @@ -365,10 +370,29 @@ static void oacp_ind_handler(struct bt_conn *conn, LOG_DBG("OACP indication"); if (op_code == BT_GATT_OTS_OACP_PROC_RESP) { - enum bt_gatt_ots_oacp_proc_type req_opcode = - net_buf_simple_pull_u8(&net_buf); - enum bt_gatt_ots_oacp_res_code result_code = - net_buf_simple_pull_u8(&net_buf); + 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); @@ -1263,6 +1287,49 @@ static int oacp_write(struct bt_conn *conn, struct bt_otc_internal_instance_t *i 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) { @@ -1317,7 +1384,7 @@ int bt_ots_client_write_object_data(struct bt_ots_client *otc_inst, * Offset and Length field are UINT32 Length */ CHECKIF(len > UINT32_MAX) { - LOG_ERR("length exceeds UINT32"); + LOG_ERR("length %zu exceeds UINT32", len); return -EINVAL; } @@ -1327,7 +1394,7 @@ int bt_ots_client_write_object_data(struct bt_ots_client *otc_inst, } CHECKIF((offset > UINT32_MAX) || (offset < 0)) { - LOG_ERR("offset exceeds UINT32"); + LOG_ERR("offset %ld exceeds UINT32 and must be >= 0", offset); return -EINVAL; } @@ -1359,6 +1426,54 @@ int bt_ots_client_write_object_data(struct bt_ots_client *otc_inst, 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) { diff --git a/subsys/bluetooth/services/ots/ots_oacp.c b/subsys/bluetooth/services/ots/ots_oacp.c index 2dbb35199c1..7fa418e16ab 100644 --- a/subsys/bluetooth/services/ots/ots_oacp.c +++ b/subsys/bluetooth/services/ots/ots_oacp.c @@ -10,7 +10,9 @@ #include #include #include +#include #include +#include #include #include @@ -23,7 +25,16 @@ LOG_MODULE_DECLARE(bt_ots, CONFIG_BT_OTS_LOG_LEVEL); #define OACP_PROC_TYPE_SIZE 1 -#define OACP_RES_MAX_SIZE 3 +/** + * 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, @@ -161,12 +172,62 @@ exit: } #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, - struct bt_gatt_ots_oacp_proc *proc) + const struct bt_gatt_ots_oacp_proc *proc) { - struct bt_gatt_ots_oacp_read_params *params = &proc->read_params; + 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); @@ -273,7 +334,8 @@ static enum bt_gatt_ots_oacp_res_code oacp_write_proc_validate( 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 bt_gatt_ots_oacp_proc *proc, + struct net_buf_simple *resp_param) { switch (proc->type) { case BT_GATT_OTS_OACP_PROC_READ: @@ -290,7 +352,10 @@ static enum bt_gatt_ots_oacp_res_code oacp_proc_validate( 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: @@ -581,8 +646,9 @@ static void oacp_ind_cb(struct bt_conn *conn, } static int oacp_ind_send(const struct bt_gatt_attr *oacp_attr, - enum bt_gatt_ots_oacp_proc_type req_op_code, - enum bt_gatt_ots_oacp_res_code oacp_status) + 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; @@ -590,9 +656,14 @@ static int oacp_ind_send(const struct bt_gatt_attr *oacp_attr, /* Encode OACP Response */ oacp_res[oacp_res_len++] = BT_GATT_OTS_OACP_PROC_RESP; - oacp_res[oacp_res_len++] = req_op_code; + 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)); @@ -613,8 +684,9 @@ ssize_t bt_gatt_ots_oacp_write(struct bt_conn *conn, { enum bt_gatt_ots_oacp_res_code oacp_status; int decode_status; - struct bt_gatt_ots_oacp_proc oacp_proc; + 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"); @@ -631,7 +703,7 @@ ssize_t bt_gatt_ots_oacp_write(struct bt_conn *conn, decode_status = oacp_command_decode(buf, len, &oacp_proc); switch (decode_status) { case 0: - oacp_status = oacp_proc_validate(conn, ots, &oacp_proc); + 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); } @@ -652,7 +724,7 @@ ssize_t bt_gatt_ots_oacp_write(struct bt_conn *conn, return BT_GATT_ERR(BT_ATT_ERR_UNLIKELY); } - oacp_ind_send(attr, oacp_proc.type, oacp_status); + oacp_ind_send(attr, oacp_proc, oacp_status, &resp_param); return len; }