blob: 8cd25edb8234d560923c83331bdb98b5db878e75 [file] [log] [blame]
/*
* 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_dir_list_internal.h"
#include <logging/log.h>
LOG_MODULE_DECLARE(bt_ots, CONFIG_BT_OTS_LOG_LEVEL);
#define OACP_PROC_TYPE_SIZE 1
#define OACP_RES_MAX_SIZE 3
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)
{
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;
}
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)
{
switch (proc->type) {
case BT_GATT_OTS_OACP_PROC_READ:
return oacp_read_proc_validate(conn, ots, proc);
case BT_GATT_OTS_OACP_PROC_CREATE:
case BT_GATT_OTS_OACP_PROC_DELETE:
case BT_GATT_OTS_OACP_PROC_CHECKSUM_CALC:
case BT_GATT_OTS_OACP_PROC_EXECUTE:
case BT_GATT_OTS_OACP_PROC_WRITE:
case BT_GATT_OTS_OACP_PROC_ABORT:
default:
return BT_GATT_OTS_OACP_RES_OPCODE_NOT_SUP;
}
};
static enum bt_gatt_ots_oacp_res_code oacp_command_decode(
const uint8_t *buf, uint16_t len,
struct bt_gatt_ots_oacp_proc *proc)
{
struct net_buf_simple net_buf;
net_buf_simple_init_with_data(&net_buf, (void *) buf, len);
proc->type = net_buf_simple_pull_u8(&net_buf);
switch (proc->type) {
case BT_GATT_OTS_OACP_PROC_CREATE:
proc->create_params.size = net_buf_simple_pull_le32(&net_buf);
bt_uuid_create(&proc->create_params.type.uuid, net_buf.data,
net_buf.len);
net_buf_simple_pull_mem(&net_buf, net_buf.len);
break;
case BT_GATT_OTS_OACP_PROC_DELETE:
break;
case BT_GATT_OTS_OACP_PROC_CHECKSUM_CALC:
proc->cs_calc_params.offset =
net_buf_simple_pull_le32(&net_buf);
proc->cs_calc_params.len =
net_buf_simple_pull_le32(&net_buf);
break;
case BT_GATT_OTS_OACP_PROC_EXECUTE:
break;
case BT_GATT_OTS_OACP_PROC_READ:
proc->read_params.offset =
net_buf_simple_pull_le32(&net_buf);
proc->read_params.len =
net_buf_simple_pull_le32(&net_buf);
return BT_GATT_OTS_OACP_RES_SUCCESS;
case BT_GATT_OTS_OACP_PROC_WRITE:
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);
break;
case BT_GATT_OTS_OACP_PROC_ABORT:
default:
break;
}
LOG_WRN("OACP unsupported procedure type");
return BT_GATT_OTS_OACP_RES_OPCODE_NOT_SUP;
}
static bool oacp_command_len_verify(struct bt_gatt_ots_oacp_proc *proc,
uint16_t len)
{
uint16_t ref_len = OACP_PROC_TYPE_SIZE;
switch (proc->type) {
case BT_GATT_OTS_OACP_PROC_CREATE:
{
struct bt_ots_obj_type *type;
ref_len += sizeof(proc->create_params.size);
type = &proc->create_params.type;
if (type->uuid.type == BT_UUID_TYPE_16) {
ref_len += sizeof(type->uuid_16.val);
} else if (type->uuid.type == BT_UUID_TYPE_128) {
ref_len += sizeof(type->uuid_128.val);
} else {
return true;
}
} break;
case BT_GATT_OTS_OACP_PROC_DELETE:
break;
case BT_GATT_OTS_OACP_PROC_CHECKSUM_CALC:
ref_len += sizeof(proc->cs_calc_params);
break;
case BT_GATT_OTS_OACP_PROC_EXECUTE:
break;
case BT_GATT_OTS_OACP_PROC_READ:
ref_len += sizeof(proc->read_params);
break;
case BT_GATT_OTS_OACP_PROC_WRITE:
ref_len += sizeof(proc->write_params);
break;
case BT_GATT_OTS_OACP_PROC_ABORT:
break;
default:
return true;
}
return (len == ref_len);
}
static void oacp_read_proc_cb(struct bt_gatt_ots_l2cap *l2cap_ctx,
struct bt_conn *conn)
{
int err;
uint8_t *obj_chunk;
uint32_t offset;
uint32_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, &obj_chunk, len, offset);
} else {
len = ots->cb->obj_read(ots, conn, ots->cur_obj->id, &obj_chunk,
len, offset);
}
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");
}
}
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);
switch (ots->cur_obj->state.type) {
case BT_GATT_OTS_OBJECT_READ_OP_STATE:
oacp_read_proc_execute(ots, conn);
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,
enum bt_gatt_ots_oacp_proc_type req_op_code,
enum bt_gatt_ots_oacp_res_code oacp_status)
{
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++] = req_op_code;
oacp_res[oacp_res_len++] = oacp_status;
/* 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;
struct bt_gatt_ots_oacp_proc oacp_proc;
struct bt_ots *ots = (struct bt_ots *) attr->user_data;
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);
}
oacp_status = oacp_command_decode(buf, len, &oacp_proc);
if (!oacp_command_len_verify(&oacp_proc, len)) {
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);
}
if (oacp_status == BT_GATT_OTS_OACP_RES_SUCCESS) {
oacp_status = oacp_proc_validate(conn, ots, &oacp_proc);
}
if (oacp_status != BT_GATT_OTS_OACP_RES_SUCCESS) {
LOG_WRN("OACP Write error status: 0x%02X", oacp_status);
}
oacp_ind_send(attr, oacp_proc.type, oacp_status);
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;
}
}