311 lines
7.6 KiB
C
311 lines
7.6 KiB
C
/*
|
|
* 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 <sys/printk.h>
|
|
#include <sys/byteorder.h>
|
|
#include <zephyr.h>
|
|
|
|
#include <bluetooth/gatt.h>
|
|
#include <bluetooth/services/ots.h>
|
|
#include "ots_internal.h"
|
|
#include "ots_obj_manager_internal.h"
|
|
#include "ots_dir_list_internal.h"
|
|
|
|
#include <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;
|
|
|
|
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",
|
|
log_strdup(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;
|
|
}
|
|
}
|