bluetooth: ots: Add object write procedure

Add the ability to perform an OTS Write Procedure with only Patch
support configuration.

Signed-off-by: Abe Kohandel <abe.kohandel@gmail.com>
This commit is contained in:
Abe Kohandel 2021-07-31 17:03:03 -07:00 committed by Christopher Friedt
commit 997bbcb000
10 changed files with 400 additions and 21 deletions

View file

@ -23,6 +23,7 @@ extern "C" {
#include <zephyr/types.h> #include <zephyr/types.h>
#include <sys/byteorder.h> #include <sys/byteorder.h>
#include <sys/types.h>
#include <sys/util.h> #include <sys/util.h>
#include <bluetooth/conn.h> #include <bluetooth/conn.h>
#include <bluetooth/uuid.h> #include <bluetooth/uuid.h>
@ -546,6 +547,36 @@ struct bt_ots_cb {
uint64_t id, uint8_t **data, uint32_t len, uint64_t id, uint8_t **data, uint32_t len,
uint32_t offset); uint32_t offset);
/** @brief Object write callback
*
* This callback is called multiple times during the Object write
* operation. OTS module will keep providing successive Object
* fragments to the application until the write operation is
* completed. The offset and length of each write fragment is
* validated by the OTS module to be within the allocated size
* of the object. The remaining length indicates data length
* remaining to be written and will decrease each write iteration
* until it reaches 0 in the last write fragment.
*
* @param ots OTS instance.
* @param conn The connection that wrote object.
* @param id Object ID.
* @param data Next chunk of data to be written.
* @param len Length of the current chunk of data in the buffer.
* @param offset Object data offset.
* @param rem Remaining length in the write operation.
*
* @return Number of bytes written in case of success, if the number
* of bytes written does not match len, -EIO is returned to
* the L2CAP layer.
* @return A negative value in case of an error.
* @return -EINPROGRESS has a special meaning and is unsupported at
* the moment. It should not be returned.
*/
ssize_t (*obj_write)(struct bt_ots *ots, struct bt_conn *conn, uint64_t id,
const void *data, size_t len, off_t offset,
size_t rem);
/** @brief Object name written callback /** @brief Object name written callback
* *
* This callback is called when the object name is written. * This callback is called when the object name is written.

View file

@ -4,5 +4,7 @@ CONFIG_BT_DEVICE_NAME="Zephyr OTS"
CONFIG_BT_OTS=y CONFIG_BT_OTS=y
CONFIG_BT_OTS_DIR_LIST_OBJ=y CONFIG_BT_OTS_DIR_LIST_OBJ=y
CONFIG_BT_OTS_OBJ_NAME_WRITE_SUPPORT=y CONFIG_BT_OTS_OBJ_NAME_WRITE_SUPPORT=y
CONFIG_BT_OTS_OACP_WRITE_SUPPORT=y
CONFIG_BT_OTS_OACP_PATCH_SUPPORT=y
CONFIG_LOG=y CONFIG_LOG=y

View file

@ -137,6 +137,24 @@ static uint32_t ots_obj_read(struct bt_ots *ots, struct bt_conn *conn,
return len; return len;
} }
static ssize_t ots_obj_write(struct bt_ots *ots, struct bt_conn *conn,
uint64_t id, const void *data, size_t len,
off_t offset, size_t rem)
{
char id_str[BT_OTS_OBJ_ID_STR_LEN];
uint32_t obj_index = (id % ARRAY_SIZE(objects));
bt_ots_obj_id_to_str(id, id_str, sizeof(id_str));
printk("Object with %s ID is being written\n"
"Offset = %lu, Length = %zu, Remaining= %zu\n",
id_str, (long)offset, len, rem);
memcpy(&objects[obj_index].data[offset], data, len);
return len;
}
void ots_obj_name_written(struct bt_ots *ots, struct bt_conn *conn, uint64_t id, const char *name) void ots_obj_name_written(struct bt_ots *ots, struct bt_conn *conn, uint64_t id, const char *name)
{ {
char id_str[BT_OTS_OBJ_ID_STR_LEN]; char id_str[BT_OTS_OBJ_ID_STR_LEN];
@ -151,6 +169,7 @@ static struct bt_ots_cb ots_callbacks = {
.obj_deleted = ots_obj_deleted, .obj_deleted = ots_obj_deleted,
.obj_selected = ots_obj_selected, .obj_selected = ots_obj_selected,
.obj_read = ots_obj_read, .obj_read = ots_obj_read,
.obj_write = ots_obj_write,
.obj_name_written = ots_obj_name_written, .obj_name_written = ots_obj_name_written,
}; };
@ -162,6 +181,8 @@ static int ots_init(void)
struct bt_ots_obj_metadata obj_init; struct bt_ots_obj_metadata obj_init;
const char * const first_object_name = "first_object.txt"; const char * const first_object_name = "first_object.txt";
const char * const second_object_name = "second_object.gif"; const char * const second_object_name = "second_object.gif";
uint32_t cur_size;
uint32_t alloc_size;
ots = bt_ots_free_instance_get(); ots = bt_ots_free_instance_get();
if (!ots) { if (!ots) {
@ -172,6 +193,8 @@ static int ots_init(void)
/* Configure OTS initialization. */ /* Configure OTS initialization. */
memset(&ots_init, 0, sizeof(ots_init)); memset(&ots_init, 0, sizeof(ots_init));
BT_OTS_OACP_SET_FEAT_READ(ots_init.features.oacp); BT_OTS_OACP_SET_FEAT_READ(ots_init.features.oacp);
BT_OTS_OACP_SET_FEAT_WRITE(ots_init.features.oacp);
BT_OTS_OACP_SET_FEAT_PATCH(ots_init.features.oacp);
BT_OTS_OLCP_SET_FEAT_GO_TO(ots_init.features.olcp); BT_OTS_OLCP_SET_FEAT_GO_TO(ots_init.features.olcp);
ots_init.cb = &ots_callbacks; ots_init.cb = &ots_callbacks;
@ -183,7 +206,9 @@ static int ots_init(void)
} }
/* Prepare first object demo data and add it to the instance. */ /* Prepare first object demo data and add it to the instance. */
for (uint32_t i = 0; i < sizeof(objects[0].data); i++) { cur_size = sizeof(objects[0].data) / 2;
alloc_size = sizeof(objects[0].data);
for (uint32_t i = 0; i < cur_size; i++) {
objects[0].data[i] = i + 1; objects[0].data[i] = i + 1;
} }
@ -195,9 +220,11 @@ static int ots_init(void)
obj_init.name = objects[0].name; obj_init.name = objects[0].name;
obj_init.type.uuid.type = BT_UUID_TYPE_16; obj_init.type.uuid.type = BT_UUID_TYPE_16;
obj_init.type.uuid_16.val = BT_UUID_OTS_TYPE_UNSPECIFIED_VAL; obj_init.type.uuid_16.val = BT_UUID_OTS_TYPE_UNSPECIFIED_VAL;
obj_init.size.cur = sizeof(objects[0].data); obj_init.size.cur = cur_size;
obj_init.size.alloc = sizeof(objects[0].data); obj_init.size.alloc = alloc_size;
BT_OTS_OBJ_SET_PROP_READ(obj_init.props); BT_OTS_OBJ_SET_PROP_READ(obj_init.props);
BT_OTS_OBJ_SET_PROP_WRITE(obj_init.props);
BT_OTS_OBJ_SET_PROP_PATCH(obj_init.props);
err = bt_ots_obj_add(ots, &obj_init); err = bt_ots_obj_add(ots, &obj_init);
if (err) { if (err) {
@ -206,7 +233,9 @@ static int ots_init(void)
} }
/* Prepare second object demo data and add it to the instance. */ /* Prepare second object demo data and add it to the instance. */
for (uint32_t i = 0; i < sizeof(objects[1].data); i++) { cur_size = sizeof(objects[0].data);
alloc_size = sizeof(objects[0].data);
for (uint32_t i = 0; i < cur_size; i++) {
objects[1].data[i] = i * 2; objects[1].data[i] = i * 2;
} }
@ -218,8 +247,8 @@ static int ots_init(void)
obj_init.name = objects[1].name; obj_init.name = objects[1].name;
obj_init.type.uuid.type = BT_UUID_TYPE_16; obj_init.type.uuid.type = BT_UUID_TYPE_16;
obj_init.type.uuid_16.val = BT_UUID_OTS_TYPE_UNSPECIFIED_VAL; obj_init.type.uuid_16.val = BT_UUID_OTS_TYPE_UNSPECIFIED_VAL;
obj_init.size.cur = sizeof(objects[1].data); obj_init.size.cur = cur_size;
obj_init.size.alloc = sizeof(objects[1].data); obj_init.size.alloc = alloc_size;
BT_OTS_OBJ_SET_PROP_READ(obj_init.props); BT_OTS_OBJ_SET_PROP_READ(obj_init.props);
err = bt_ots_obj_add(ots, &obj_init); err = bt_ots_obj_add(ots, &obj_init);

View file

@ -49,6 +49,13 @@ config BT_OTS_OACP_READ_SUPPORT
bool "Support OACP Read Operation" bool "Support OACP Read Operation"
default y default y
config BT_OTS_OACP_WRITE_SUPPORT
bool "Support OACP Write Operation"
config BT_OTS_OACP_PATCH_SUPPORT
bool "Support patching of objects"
depends on BT_OTS_OACP_WRITE_SUPPORT
config BT_OTS_OLCP_GO_TO_SUPPORT config BT_OTS_OLCP_GO_TO_SUPPORT
bool "Support OLCP Go To Operation" bool "Support OLCP Go To Operation"
default y default y

View file

@ -36,8 +36,23 @@ LOG_MODULE_REGISTER(bt_ots, CONFIG_BT_OTS_LOG_LEVEL);
#define OACP_FEAT_BIT_READ 0 #define OACP_FEAT_BIT_READ 0
#endif #endif
#if defined(CONFIG_BT_OTS_OACP_WRITE_SUPPORT)
#define OACP_FEAT_BIT_WRITE BIT(BT_OTS_OACP_FEAT_WRITE)
#else
#define OACP_FEAT_BIT_WRITE 0
#endif
#if defined(CONFIG_BT_OTS_OACP_PATCH_SUPPORT)
#define OACP_FEAT_BIT_PATCH BIT(BT_OTS_OACP_FEAT_PATCH)
#else
#define OACP_FEAT_BIT_PATCH 0
#endif
/* OACP features supported by Kconfig */ /* OACP features supported by Kconfig */
#define OACP_FEAT OACP_FEAT_BIT_READ #define OACP_FEAT ( \
OACP_FEAT_BIT_READ | \
OACP_FEAT_BIT_WRITE | \
OACP_FEAT_BIT_PATCH)
#if defined(CONFIG_BT_OTS_OLCP_GO_TO_SUPPORT) #if defined(CONFIG_BT_OTS_OLCP_GO_TO_SUPPORT)
#define OLCP_FEAT_BIT_GOTO BIT(BT_OTS_OLCP_FEAT_GO_TO) #define OLCP_FEAT_BIT_GOTO BIT(BT_OTS_OLCP_FEAT_GO_TO)
@ -48,6 +63,39 @@ LOG_MODULE_REGISTER(bt_ots, CONFIG_BT_OTS_LOG_LEVEL);
/* OLCP features supported by Kconfig */ /* OLCP features supported by Kconfig */
#define OLCP_FEAT OLCP_FEAT_BIT_GOTO #define OLCP_FEAT OLCP_FEAT_BIT_GOTO
static bool ots_obj_validate_prop_against_oacp(uint32_t prop, uint32_t oacp)
{
if (BT_OTS_OBJ_GET_PROP_DELETE(prop) > 0 && BT_OTS_OACP_GET_FEAT_DELETE(oacp) == 0) {
return false;
}
if (BT_OTS_OBJ_GET_PROP_EXECUTE(prop) > 0 && BT_OTS_OACP_GET_FEAT_EXECUTE(oacp) == 0) {
return false;
}
if (BT_OTS_OBJ_GET_PROP_READ(prop) > 0 && BT_OTS_OACP_GET_FEAT_READ(oacp) == 0) {
return false;
}
if (BT_OTS_OBJ_GET_PROP_WRITE(prop) > 0 && BT_OTS_OACP_GET_FEAT_WRITE(oacp) == 0) {
return false;
}
if (BT_OTS_OBJ_GET_PROP_APPEND(prop) > 0 && BT_OTS_OACP_GET_FEAT_APPEND(oacp) == 0) {
return false;
}
if (BT_OTS_OBJ_GET_PROP_TRUNCATE(prop) > 0 && BT_OTS_OACP_GET_FEAT_TRUNCATE(oacp) == 0) {
return false;
}
if (BT_OTS_OBJ_GET_PROP_PATCH(prop) > 0 && BT_OTS_OACP_GET_FEAT_PATCH(oacp) == 0) {
return false;
}
return true;
}
static ssize_t ots_feature_read(struct bt_conn *conn, static ssize_t ots_feature_read(struct bt_conn *conn,
const struct bt_gatt_attr *attr, void *buf, const struct bt_gatt_attr *attr, void *buf,
uint16_t len, uint16_t offset) uint16_t len, uint16_t offset)
@ -243,6 +291,12 @@ int bt_ots_obj_add(struct bt_ots *ots,
return -EINVAL; return -EINVAL;
} }
CHECKIF(!ots_obj_validate_prop_against_oacp(obj_init->props, ots->features.oacp)) {
LOG_DBG("Object properties (0x%04X) are not a subset of OACP (0x%04X)",
obj_init->props, ots->features.oacp);
return -ENOTSUP;
}
err = bt_gatt_ots_obj_manager_obj_add(ots->obj_manager, &obj); err = bt_gatt_ots_obj_manager_obj_add(ots->obj_manager, &obj);
if (err) { if (err) {
LOG_ERR("No space available in the object manager"); LOG_ERR("No space available in the object manager");
@ -334,6 +388,9 @@ int bt_ots_init(struct bt_ots *ots,
__ASSERT(ots_init->cb->obj_read || __ASSERT(ots_init->cb->obj_read ||
!BT_OTS_OACP_GET_FEAT_READ(ots_init->features.oacp), !BT_OTS_OACP_GET_FEAT_READ(ots_init->features.oacp),
"Callback for object reading is not set"); "Callback for object reading is not set");
__ASSERT(ots_init->cb->obj_write ||
!BT_OTS_OACP_GET_FEAT_WRITE(ots_init->features.oacp),
"Callback for object write is not set");
/* Set callback structure. */ /* Set callback structure. */
ots->cb = ots_init->cb; ots->cb = ots_init->cb;

View file

@ -49,6 +49,8 @@ enum bt_gatt_ots_object_state_type {
BT_GATT_OTS_OBJECT_IDLE_STATE, BT_GATT_OTS_OBJECT_IDLE_STATE,
BT_GATT_OTS_OBJECT_READ_OP_STATE, BT_GATT_OTS_OBJECT_READ_OP_STATE,
BT_GATT_OTS_OBJECT_WRITE_OP_STATE,
}; };
struct bt_gatt_ots_object_state { struct bt_gatt_ots_object_state {
@ -58,6 +60,10 @@ struct bt_gatt_ots_object_state {
struct bt_gatt_ots_oacp_read_params oacp_params; struct bt_gatt_ots_oacp_read_params oacp_params;
uint32_t sent_len; uint32_t sent_len;
} read_op; } read_op;
struct bt_gatt_ots_object_write_op {
struct bt_gatt_ots_oacp_write_params oacp_params;
uint32_t recv_len;
} write_op;
}; };
}; };

View file

@ -31,6 +31,11 @@ LOG_MODULE_DECLARE(bt_ots, CONFIG_BT_OTS_LOG_LEVEL);
NET_BUF_POOL_FIXED_DEFINE(ot_chan_tx_pool, 1, BT_L2CAP_SDU_BUF_SIZE(OT_TX_MTU), NET_BUF_POOL_FIXED_DEFINE(ot_chan_tx_pool, 1, BT_L2CAP_SDU_BUF_SIZE(OT_TX_MTU),
NULL); NULL);
#if (CONFIG_BT_OTS_L2CAP_CHAN_RX_MTU > BT_L2CAP_SDU_RX_MTU)
NET_BUF_POOL_FIXED_DEFINE(ot_chan_rx_pool, 1, CONFIG_BT_OTS_L2CAP_CHAN_RX_MTU,
NULL);
#endif
/* List of Object Transfer Channels. */ /* List of Object Transfer Channels. */
static sys_slist_t channels; static sys_slist_t channels;
@ -65,6 +70,16 @@ static int ots_l2cap_send(struct bt_gatt_ots_l2cap *l2cap_ctx)
return 0; return 0;
} }
#if (CONFIG_BT_OTS_L2CAP_CHAN_RX_MTU > BT_L2CAP_SDU_RX_MTU)
static struct net_buf *l2cap_alloc_buf(struct bt_l2cap_chan *chan)
{
LOG_DBG("Channel %p allocating buffer", chan);
return net_buf_alloc(&ot_chan_rx_pool, K_FOREVER);
}
#endif
static void l2cap_sent(struct bt_l2cap_chan *chan) static void l2cap_sent(struct bt_l2cap_chan *chan)
{ {
struct bt_gatt_ots_l2cap *l2cap_ctx; struct bt_gatt_ots_l2cap *l2cap_ctx;
@ -90,6 +105,21 @@ static void l2cap_sent(struct bt_l2cap_chan *chan)
} }
} }
int l2cap_recv(struct bt_l2cap_chan *chan, struct net_buf *buf)
{
struct bt_gatt_ots_l2cap *l2cap_ctx;
LOG_DBG("Incoming data channel %p received", chan);
l2cap_ctx = CONTAINER_OF(chan, struct bt_gatt_ots_l2cap, ot_chan);
if (!l2cap_ctx->rx_done) {
return -ENODEV;
}
return l2cap_ctx->rx_done(l2cap_ctx, chan->conn, buf);
}
static void l2cap_status(struct bt_l2cap_chan *chan, atomic_t *status) static void l2cap_status(struct bt_l2cap_chan *chan, atomic_t *status)
{ {
LOG_DBG("Channel %p status %u", chan, *status); LOG_DBG("Channel %p status %u", chan, *status);
@ -102,11 +132,23 @@ static void l2cap_connected(struct bt_l2cap_chan *chan)
static void l2cap_disconnected(struct bt_l2cap_chan *chan) static void l2cap_disconnected(struct bt_l2cap_chan *chan)
{ {
struct bt_gatt_ots_l2cap *l2cap_ctx;
LOG_DBG("Channel %p disconnected", chan); LOG_DBG("Channel %p disconnected", chan);
l2cap_ctx = CONTAINER_OF(chan, struct bt_gatt_ots_l2cap, ot_chan);
if (l2cap_ctx->closed) {
l2cap_ctx->closed(l2cap_ctx, chan->conn);
}
} }
static const struct bt_l2cap_chan_ops l2cap_ops = { static const struct bt_l2cap_chan_ops l2cap_ops = {
#if (CONFIG_BT_OTS_L2CAP_CHAN_RX_MTU > BT_L2CAP_SDU_RX_MTU)
.alloc_buf = l2cap_alloc_buf,
#endif
.sent = l2cap_sent, .sent = l2cap_sent,
.recv = l2cap_recv,
.status = l2cap_status, .status = l2cap_status,
.connected = l2cap_connected, .connected = l2cap_connected,
.disconnected = l2cap_disconnected, .disconnected = l2cap_disconnected,

View file

@ -13,6 +13,7 @@ extern "C" {
#include <zephyr/types.h> #include <zephyr/types.h>
#include <sys/slist.h> #include <sys/slist.h>
#include <sys/types.h>
#include <bluetooth/l2cap.h> #include <bluetooth/l2cap.h>
@ -28,6 +29,10 @@ struct bt_gatt_ots_l2cap {
struct bt_gatt_ots_l2cap_tx tx; struct bt_gatt_ots_l2cap_tx tx;
void (*tx_done)(struct bt_gatt_ots_l2cap *l2cap_ctx, void (*tx_done)(struct bt_gatt_ots_l2cap *l2cap_ctx,
struct bt_conn *conn); struct bt_conn *conn);
ssize_t (*rx_done)(struct bt_gatt_ots_l2cap *l2cap_ctx,
struct bt_conn *conn, struct net_buf *buf);
void (*closed)(struct bt_gatt_ots_l2cap *l2cap_ctx,
struct bt_conn *conn);
}; };
bool bt_gatt_ots_l2cap_is_open(struct bt_gatt_ots_l2cap *l2cap_ctx, bool bt_gatt_ots_l2cap_is_open(struct bt_gatt_ots_l2cap *l2cap_ctx,

View file

@ -24,6 +24,27 @@ LOG_MODULE_DECLARE(bt_ots, CONFIG_BT_OTS_LOG_LEVEL);
#define OACP_PROC_TYPE_SIZE 1 #define OACP_PROC_TYPE_SIZE 1
#define OACP_RES_MAX_SIZE 3 #define OACP_RES_MAX_SIZE 3
#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
static enum bt_gatt_ots_oacp_res_code oacp_read_proc_validate( static enum bt_gatt_ots_oacp_res_code oacp_read_proc_validate(
struct bt_conn *conn, struct bt_conn *conn,
struct bt_ots *ots, struct bt_ots *ots,
@ -65,6 +86,74 @@ static enum bt_gatt_ots_oacp_res_code oacp_read_proc_validate(
return BT_GATT_OTS_OACP_RES_SUCCESS; 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( static enum bt_gatt_ots_oacp_res_code oacp_proc_validate(
struct bt_conn *conn, struct bt_conn *conn,
struct bt_ots *ots, struct bt_ots *ots,
@ -73,11 +162,14 @@ static enum bt_gatt_ots_oacp_res_code oacp_proc_validate(
switch (proc->type) { switch (proc->type) {
case BT_GATT_OTS_OACP_PROC_READ: case BT_GATT_OTS_OACP_PROC_READ:
return oacp_read_proc_validate(conn, ots, proc); return oacp_read_proc_validate(conn, ots, proc);
case BT_GATT_OTS_OACP_PROC_WRITE:
#if defined(CONFIG_BT_OTS_OACP_WRITE_SUPPORT)
return oacp_write_proc_validate(conn, ots, proc);
#endif
case BT_GATT_OTS_OACP_PROC_CREATE: case BT_GATT_OTS_OACP_PROC_CREATE:
case BT_GATT_OTS_OACP_PROC_DELETE: case BT_GATT_OTS_OACP_PROC_DELETE:
case BT_GATT_OTS_OACP_PROC_CHECKSUM_CALC: case BT_GATT_OTS_OACP_PROC_CHECKSUM_CALC:
case BT_GATT_OTS_OACP_PROC_EXECUTE: case BT_GATT_OTS_OACP_PROC_EXECUTE:
case BT_GATT_OTS_OACP_PROC_WRITE:
case BT_GATT_OTS_OACP_PROC_ABORT: case BT_GATT_OTS_OACP_PROC_ABORT:
default: default:
return BT_GATT_OTS_OACP_RES_OPCODE_NOT_SUP; return BT_GATT_OTS_OACP_RES_OPCODE_NOT_SUP;
@ -117,13 +209,17 @@ static enum bt_gatt_ots_oacp_res_code oacp_command_decode(
net_buf_simple_pull_le32(&net_buf); net_buf_simple_pull_le32(&net_buf);
return BT_GATT_OTS_OACP_RES_SUCCESS; return BT_GATT_OTS_OACP_RES_SUCCESS;
case BT_GATT_OTS_OACP_PROC_WRITE: case BT_GATT_OTS_OACP_PROC_WRITE:
#if defined(CONFIG_BT_OTS_OACP_WRITE_SUPPORT)
proc->write_params.offset = proc->write_params.offset =
net_buf_simple_pull_le32(&net_buf); net_buf_simple_pull_le32(&net_buf);
proc->write_params.len = proc->write_params.len =
net_buf_simple_pull_le32(&net_buf); net_buf_simple_pull_le32(&net_buf);
proc->write_params.mode = proc->write_params.mode =
net_buf_simple_pull_u8(&net_buf); net_buf_simple_pull_u8(&net_buf);
return BT_GATT_OTS_OACP_RES_SUCCESS;
#else
break; break;
#endif
case BT_GATT_OTS_OACP_PROC_ABORT: case BT_GATT_OTS_OACP_PROC_ABORT:
default: default:
break; break;
@ -141,31 +237,34 @@ static bool oacp_command_len_verify(struct bt_gatt_ots_oacp_proc *proc,
switch (proc->type) { switch (proc->type) {
case BT_GATT_OTS_OACP_PROC_CREATE: case BT_GATT_OTS_OACP_PROC_CREATE:
{ {
struct bt_ots_obj_type *type; struct bt_ots_obj_type *type = &proc->create_params.type;
ref_len += sizeof(proc->create_params.size); switch (type->uuid.type) {
case BT_UUID_TYPE_16:
type = &proc->create_params.type; ref_len += BT_GATT_OTS_OACP_CREATE_UUID16_PARAMS_SIZE;
if (type->uuid.type == BT_UUID_TYPE_16) { break;
ref_len += sizeof(type->uuid_16.val); case BT_UUID_TYPE_32:
} else if (type->uuid.type == BT_UUID_TYPE_128) { ref_len += BT_GATT_OTS_OACP_CREATE_UUID32_PARAMS_SIZE;
ref_len += sizeof(type->uuid_128.val); break;
} else { case BT_UUID_TYPE_128:
return true; ref_len += BT_GATT_OTS_OACP_CREATE_UUID128_PARAMS_SIZE;
break;
default:
return false;
} }
} break; } break;
case BT_GATT_OTS_OACP_PROC_DELETE: case BT_GATT_OTS_OACP_PROC_DELETE:
break; break;
case BT_GATT_OTS_OACP_PROC_CHECKSUM_CALC: case BT_GATT_OTS_OACP_PROC_CHECKSUM_CALC:
ref_len += sizeof(proc->cs_calc_params); ref_len += BT_GATT_OTS_OACP_CS_CALC_PARAMS_SIZE;
break; break;
case BT_GATT_OTS_OACP_PROC_EXECUTE: case BT_GATT_OTS_OACP_PROC_EXECUTE:
break; break;
case BT_GATT_OTS_OACP_PROC_READ: case BT_GATT_OTS_OACP_PROC_READ:
ref_len += sizeof(proc->read_params); ref_len += BT_GATT_OTS_OACP_READ_PARAMS_SIZE;
break; break;
case BT_GATT_OTS_OACP_PROC_WRITE: case BT_GATT_OTS_OACP_PROC_WRITE:
ref_len += sizeof(proc->write_params); ref_len += BT_GATT_OTS_OACP_WRITE_PARAMS_SIZE;
break; break;
case BT_GATT_OTS_OACP_PROC_ABORT: case BT_GATT_OTS_OACP_PROC_ABORT:
break; break;
@ -255,6 +354,78 @@ static void oacp_read_proc_execute(struct bt_ots *ots,
} }
} }
#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, static void oacp_ind_cb(struct bt_conn *conn,
struct bt_gatt_indicate_params *params, struct bt_gatt_indicate_params *params,
uint8_t err) uint8_t err)
@ -267,6 +438,9 @@ static void oacp_ind_cb(struct bt_conn *conn,
case BT_GATT_OTS_OBJECT_READ_OP_STATE: case BT_GATT_OTS_OBJECT_READ_OP_STATE:
oacp_read_proc_execute(ots, conn); oacp_read_proc_execute(ots, conn);
break; break;
case BT_GATT_OTS_OBJECT_WRITE_OP_STATE:
/* procedure execution is driven by L2CAP socket receive */
break;
default: default:
LOG_ERR("Unsupported OTS state: %d", ots->cur_obj->state.type); LOG_ERR("Unsupported OTS state: %d", ots->cur_obj->state.type);
break; break;

View file

@ -60,6 +60,14 @@ enum bt_gatt_ots_oacp_res_code {
BT_GATT_OTS_OACP_RES_OPER_FAILED = 0x0A BT_GATT_OTS_OACP_RES_OPER_FAILED = 0x0A
}; };
#define BT_GATT_OTS_OACP_PROC_WRITE_MODE_TRUNC 1
#define BT_GATT_OTS_OACP_PROC_WRITE_MODE_GET_TRUNC(mode) \
((mode) & BIT(BT_GATT_OTS_OACP_PROC_WRITE_MODE_TRUNC))
#define BT_GATT_OTS_OACP_PROC_WRITE_MODE_GET_RFU(mode) \
((mode) & ~BIT(BT_GATT_OTS_OACP_PROC_WRITE_MODE_TRUNC))
/* Object Action Control Point procedure definition. */ /* Object Action Control Point procedure definition. */
struct bt_gatt_ots_oacp_proc { struct bt_gatt_ots_oacp_proc {
enum bt_gatt_ots_oacp_proc_type type; enum bt_gatt_ots_oacp_proc_type type;
@ -84,6 +92,24 @@ struct bt_gatt_ots_oacp_proc {
}; };
}; };
/* Size of Object Action Control Point create procedure with 16-bit UUID object type */
#define BT_GATT_OTS_OACP_CREATE_UUID16_PARAMS_SIZE 7
/* Size of Object Action Control Point create procedure with 32-bit UUID object type */
#define BT_GATT_OTS_OACP_CREATE_UUID32_PARAMS_SIZE 9
/* Size of Object Action Control Point create procedure with 128-bit UUID object type */
#define BT_GATT_OTS_OACP_CREATE_UUID128_PARAMS_SIZE 21
/* Size of Object Action Control Point checksum calculation procedure */
#define BT_GATT_OTS_OACP_CS_CALC_PARAMS_SIZE 8
/* Size of Object Action Control Point read procedure */
#define BT_GATT_OTS_OACP_READ_PARAMS_SIZE 8
/* Size of Object Action Control Point write procedure */
#define BT_GATT_OTS_OACP_WRITE_PARAMS_SIZE 9
ssize_t bt_gatt_ots_oacp_write(struct bt_conn *conn, ssize_t bt_gatt_ots_oacp_write(struct bt_conn *conn,
const struct bt_gatt_attr *attr, const struct bt_gatt_attr *attr,
const void *buf, uint16_t len, const void *buf, uint16_t len,