| /* |
| * 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/kernel.h> |
| |
| #include <zephyr/bluetooth/gatt.h> |
| #include <zephyr/bluetooth/services/ots.h> |
| #include "ots_internal.h" |
| #include "ots_obj_manager_internal.h" |
| #include "ots_dir_list_internal.h" |
| |
| #include <zephyr/logging/log.h> |
| |
| LOG_MODULE_DECLARE(bt_ots, CONFIG_BT_OTS_LOG_LEVEL); |
| |
| #define OLCP_PROC_TYPE_SIZE 1 |
| #define OLCP_RES_MAX_SIZE 7 |
| |
| static enum bt_gatt_ots_olcp_res_code obj_manager_to_olcp_err_map(int err) |
| { |
| switch (-err) { |
| case EINVAL: |
| return BT_GATT_OTS_OLCP_RES_OBJECT_ID_NOT_FOUND; |
| case ENFILE: |
| return BT_GATT_OTS_OLCP_RES_OUT_OF_BONDS; |
| case ENOENT: |
| default: |
| return BT_GATT_OTS_OLCP_RES_NO_OBJECT; |
| } |
| } |
| |
| static enum bt_gatt_ots_olcp_res_code olcp_first_proc_execute( |
| struct bt_ots *ots) |
| { |
| int err; |
| struct bt_gatt_ots_object *first_obj; |
| |
| err = bt_gatt_ots_obj_manager_first_obj_get(ots->obj_manager, |
| &first_obj); |
| if (err) { |
| return obj_manager_to_olcp_err_map(err); |
| } |
| |
| ots->cur_obj = first_obj; |
| |
| return BT_GATT_OTS_OLCP_RES_SUCCESS; |
| } |
| |
| static enum bt_gatt_ots_olcp_res_code olcp_last_proc_execute( |
| struct bt_ots *ots) |
| { |
| int err; |
| struct bt_gatt_ots_object *last_obj; |
| |
| err = bt_gatt_ots_obj_manager_last_obj_get(ots->obj_manager, |
| &last_obj); |
| if (err) { |
| return obj_manager_to_olcp_err_map(err); |
| } |
| |
| ots->cur_obj = last_obj; |
| |
| return BT_GATT_OTS_OLCP_RES_SUCCESS; |
| } |
| |
| static enum bt_gatt_ots_olcp_res_code olcp_prev_proc_execute( |
| struct bt_ots *ots) |
| { |
| int err; |
| struct bt_gatt_ots_object *prev_obj; |
| |
| if (!ots->cur_obj) { |
| return BT_GATT_OTS_OLCP_RES_OPERATION_FAILED; |
| } |
| err = bt_gatt_ots_obj_manager_prev_obj_get(ots->obj_manager, |
| ots->cur_obj, |
| &prev_obj); |
| if (err) { |
| return obj_manager_to_olcp_err_map(err); |
| } |
| |
| ots->cur_obj = prev_obj; |
| |
| return BT_GATT_OTS_OLCP_RES_SUCCESS; |
| } |
| |
| static enum bt_gatt_ots_olcp_res_code olcp_next_proc_execute( |
| struct bt_ots *ots) |
| { |
| int err; |
| struct bt_gatt_ots_object *next_obj; |
| |
| if (!ots->cur_obj) { |
| return BT_GATT_OTS_OLCP_RES_OPERATION_FAILED; |
| } |
| err = bt_gatt_ots_obj_manager_next_obj_get(ots->obj_manager, |
| ots->cur_obj, |
| &next_obj); |
| if (err) { |
| return obj_manager_to_olcp_err_map(err); |
| } |
| |
| ots->cur_obj = next_obj; |
| |
| return BT_GATT_OTS_OLCP_RES_SUCCESS; |
| } |
| |
| static enum bt_gatt_ots_olcp_res_code olcp_goto_proc_execute( |
| struct bt_ots *ots, uint64_t id) |
| { |
| int err; |
| struct bt_gatt_ots_object *id_obj; |
| |
| err = bt_gatt_ots_obj_manager_obj_get(ots->obj_manager, |
| id, |
| &id_obj); |
| if (err) { |
| return obj_manager_to_olcp_err_map(err); |
| } |
| |
| ots->cur_obj = id_obj; |
| |
| return BT_GATT_OTS_OLCP_RES_SUCCESS; |
| } |
| |
| static enum bt_gatt_ots_olcp_res_code olcp_proc_execute( |
| struct bt_ots *ots, struct bt_gatt_ots_olcp_proc *proc) |
| { |
| LOG_DBG("Executing OLCP procedure with 0x%02X Op Code", proc->type); |
| |
| switch (proc->type) { |
| case BT_GATT_OTS_OLCP_PROC_FIRST: |
| return olcp_first_proc_execute(ots); |
| case BT_GATT_OTS_OLCP_PROC_LAST: |
| return olcp_last_proc_execute(ots); |
| case BT_GATT_OTS_OLCP_PROC_PREV: |
| return olcp_prev_proc_execute(ots); |
| case BT_GATT_OTS_OLCP_PROC_NEXT: |
| return olcp_next_proc_execute(ots); |
| case BT_GATT_OTS_OLCP_PROC_GOTO: |
| return olcp_goto_proc_execute(ots, proc->goto_params.id); |
| case BT_GATT_OTS_OLCP_PROC_ORDER: |
| case BT_GATT_OTS_OLCP_PROC_REQ_NUM_OBJS: |
| case BT_GATT_OTS_OLCP_PROC_CLEAR_MARKING: |
| default: |
| return BT_GATT_OTS_OLCP_RES_PROC_NOT_SUP; |
| } |
| }; |
| |
| static int olcp_command_decode(const uint8_t *buf, uint16_t len, |
| struct bt_gatt_ots_olcp_proc *proc) |
| { |
| if (len < OLCP_PROC_TYPE_SIZE) { |
| return -ENODATA; |
| } |
| |
| memset(proc, 0, sizeof(*proc)); |
| |
| proc->type = *buf++; |
| len -= OLCP_PROC_TYPE_SIZE; |
| |
| switch (proc->type) { |
| case BT_GATT_OTS_OLCP_PROC_FIRST: |
| case BT_GATT_OTS_OLCP_PROC_LAST: |
| case BT_GATT_OTS_OLCP_PROC_PREV: |
| case BT_GATT_OTS_OLCP_PROC_NEXT: |
| if (len != 0) { |
| return -EBADMSG; |
| } |
| |
| return 0; |
| case BT_GATT_OTS_OLCP_PROC_GOTO: |
| if (len != BT_GATT_OTS_OLCP_GOTO_PARAMS_SIZE) { |
| return -EBADMSG; |
| } |
| proc->goto_params.id = sys_get_le48(buf); |
| |
| return 0; |
| default: |
| break; |
| } |
| |
| return -ENOTSUP; |
| } |
| |
| static void olcp_ind_cb(struct bt_conn *conn, |
| struct bt_gatt_indicate_params *params, |
| uint8_t err) |
| { |
| LOG_DBG("Received OLCP Indication ACK with status: 0x%04X", err); |
| } |
| |
| static int olcp_ind_send(const struct bt_gatt_attr *olcp_attr, |
| enum bt_gatt_ots_olcp_proc_type req_op_code, |
| enum bt_gatt_ots_olcp_res_code olcp_status) |
| { |
| uint8_t olcp_res[OLCP_RES_MAX_SIZE]; |
| uint16_t olcp_res_len = 0; |
| struct bt_ots *ots = (struct bt_ots *) olcp_attr->user_data; |
| |
| /* Encode OLCP Response */ |
| olcp_res[olcp_res_len++] = BT_GATT_OTS_OLCP_PROC_RESP; |
| olcp_res[olcp_res_len++] = req_op_code; |
| olcp_res[olcp_res_len++] = olcp_status; |
| |
| /* Prepare indication parameters */ |
| memset(&ots->olcp_ind.params, 0, sizeof(ots->olcp_ind.params)); |
| memcpy(&ots->olcp_ind.attr, olcp_attr, sizeof(ots->olcp_ind.attr)); |
| ots->olcp_ind.params.attr = olcp_attr; |
| ots->olcp_ind.params.func = olcp_ind_cb; |
| ots->olcp_ind.params.data = olcp_res; |
| ots->olcp_ind.params.len = olcp_res_len; |
| #if defined(CONFIG_BT_EATT) |
| ots->olcp_ind.params.chan_opt = BT_ATT_CHAN_OPT_NONE; |
| #endif /* CONFIG_BT_EATT */ |
| |
| LOG_DBG("Sending OLCP indication"); |
| |
| return bt_gatt_indicate(NULL, &ots->olcp_ind.params); |
| } |
| |
| ssize_t bt_gatt_ots_olcp_write(struct bt_conn *conn, |
| const struct bt_gatt_attr *attr, |
| const void *buf, uint16_t len, |
| uint16_t offset, uint8_t flags) |
| { |
| struct bt_gatt_ots_object *old_obj; |
| enum bt_gatt_ots_olcp_res_code olcp_status; |
| int decode_status; |
| struct bt_gatt_ots_olcp_proc olcp_proc; |
| struct bt_ots *ots = (struct bt_ots *) attr->user_data; |
| |
| LOG_DBG("Object List Control Point GATT Write Operation"); |
| |
| if (!ots->olcp_ind.is_enabled) { |
| LOG_WRN("OLCP indications not enabled"); |
| return BT_GATT_ERR(BT_ATT_ERR_CCC_IMPROPER_CONF); |
| } |
| |
| if (offset != 0) { |
| LOG_ERR("Invalid offset of OLCP Write Request"); |
| return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET); |
| } |
| |
| old_obj = ots->cur_obj; |
| |
| decode_status = olcp_command_decode(buf, len, &olcp_proc); |
| switch (decode_status) { |
| case 0: |
| olcp_status = olcp_proc_execute(ots, &olcp_proc); |
| if (olcp_status != BT_GATT_OTS_OLCP_RES_SUCCESS) { |
| LOG_WRN("OLCP Write error status: 0x%02X", olcp_status); |
| } else if (old_obj != ots->cur_obj) { |
| char id[BT_OTS_OBJ_ID_STR_LEN]; |
| |
| bt_ots_obj_id_to_str(ots->cur_obj->id, id, |
| sizeof(id)); |
| LOG_DBG("Selecting a new Current Object with id: %s", |
| id); |
| |
| if (IS_ENABLED(CONFIG_BT_OTS_DIR_LIST_OBJ)) { |
| bt_ots_dir_list_selected(ots->dir_list, ots->obj_manager, |
| ots->cur_obj); |
| } |
| |
| if (ots->cb->obj_selected) { |
| ots->cb->obj_selected(ots, conn, ots->cur_obj->id); |
| } |
| } |
| break; |
| case -ENOTSUP: |
| olcp_status = BT_GATT_OTS_OLCP_RES_PROC_NOT_SUP; |
| LOG_WRN("OLCP unsupported procedure type: 0x%02X", olcp_proc.type); |
| break; |
| case -EBADMSG: |
| LOG_ERR("Invalid length of OLCP Write Request for 0x%02X " |
| "Op Code", olcp_proc.type); |
| return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN); |
| case -ENODATA: |
| LOG_ERR("Invalid size of OLCP Write Request"); |
| return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN); |
| default: |
| LOG_ERR("Invalid return code from olcp_command_decode: %d", decode_status); |
| return BT_GATT_ERR(BT_ATT_ERR_UNLIKELY); |
| } |
| |
| olcp_ind_send(attr, olcp_proc.type, olcp_status); |
| return len; |
| } |
| |
| void bt_gatt_ots_olcp_cfg_changed(const struct bt_gatt_attr *attr, |
| uint16_t value) |
| { |
| struct bt_gatt_ots_indicate *olcp_ind = |
| CONTAINER_OF((struct _bt_gatt_ccc *) attr->user_data, |
| struct bt_gatt_ots_indicate, ccc); |
| |
| LOG_DBG("Object List Control Point CCCD value: 0x%04X", value); |
| |
| olcp_ind->is_enabled = false; |
| if (value == BT_GATT_CCC_INDICATE) { |
| olcp_ind->is_enabled = true; |
| } |
| } |