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:
parent
fde24ac1f2
commit
997bbcb000
10 changed files with 400 additions and 21 deletions
|
@ -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.
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue