bluetooth: services: add Object Transfer service
Added GATT Object Transfer Service implementation. Signed-off-by: Kamil Piszczek <Kamil.Piszczek@nordicsemi.no>
This commit is contained in:
parent
aafb8a50bf
commit
0a7a8fd137
15 changed files with 2548 additions and 0 deletions
641
include/bluetooth/services/ots.h
Normal file
641
include/bluetooth/services/ots.h
Normal file
|
@ -0,0 +1,641 @@
|
|||
/*
|
||||
* Copyright (c) 2020 Nordic Semiconductor ASA
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#ifndef ZEPHYR_INCLUDE_BLUETOOTH_SERVICES_OTS_H_
|
||||
#define ZEPHYR_INCLUDE_BLUETOOTH_SERVICES_OTS_H_
|
||||
|
||||
/**
|
||||
* @brief Object Transfer Service (OTS)
|
||||
* @defgroup bt_ots Object Transfer Service (OTS)
|
||||
* @ingroup bluetooth
|
||||
* @{
|
||||
*
|
||||
* [Experimental] Users should note that the APIs can change
|
||||
* as a part of ongoing development.
|
||||
*/
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <zephyr/types.h>
|
||||
#include <sys/byteorder.h>
|
||||
#include <sys/util.h>
|
||||
#include <bluetooth/conn.h>
|
||||
#include <bluetooth/uuid.h>
|
||||
|
||||
/** @brief Size of OTS object ID (in bytes). */
|
||||
#define BT_OTS_OBJ_ID_SIZE 6
|
||||
|
||||
/** @brief Length of OTS object ID string (in bytes). */
|
||||
#define BT_OTS_OBJ_ID_STR_LEN 15
|
||||
|
||||
/** @brief Type of an OTS object. */
|
||||
struct bt_ots_obj_type {
|
||||
union {
|
||||
/* Used to indicate UUID type */
|
||||
struct bt_uuid uuid;
|
||||
|
||||
/* 16-bit UUID value */
|
||||
struct bt_uuid_16 uuid_16;
|
||||
|
||||
/* 128-bit UUID value */
|
||||
struct bt_uuid_128 uuid_128;
|
||||
};
|
||||
};
|
||||
|
||||
/** @brief Properties of an OTS object. */
|
||||
enum {
|
||||
/** Bit 0 Deletion of this object is permitted */
|
||||
BT_OTS_OBJ_PROP_DELETE = 0,
|
||||
|
||||
/** Bit 1 Execution of this object is permitted */
|
||||
BT_OTS_OBJ_PROP_EXECUTE = 1,
|
||||
|
||||
/** Bit 2 Reading this object is permitted */
|
||||
BT_OTS_OBJ_PROP_READ = 2,
|
||||
|
||||
/** Bit 3 Writing data to this object is permitted */
|
||||
BT_OTS_OBJ_PROP_WRITE = 3,
|
||||
|
||||
/** @brief Bit 4 Appending data to this object is permitted.
|
||||
*
|
||||
* Appending data increases its Allocated Size.
|
||||
*/
|
||||
BT_OTS_OBJ_PROP_APPEND = 4,
|
||||
|
||||
/** Bit 5 Truncation of this object is permitted */
|
||||
BT_OTS_OBJ_PROP_TRUNCATE = 5,
|
||||
|
||||
/** @brief Bit 6 Patching this object is permitted
|
||||
*
|
||||
* Patching this object overwrites some of
|
||||
* the object's existing contents.
|
||||
*/
|
||||
BT_OTS_OBJ_PROP_PATCH = 6,
|
||||
|
||||
/** Bit 7 This object is a marked object */
|
||||
BT_OTS_OBJ_PROP_MARKED = 7,
|
||||
};
|
||||
|
||||
/** @brief Set @ref BT_OTS_OBJ_PROP_DELETE property.
|
||||
*
|
||||
* @param prop Object properties.
|
||||
*/
|
||||
#define BT_OTS_OBJ_SET_PROP_DELETE(prop) \
|
||||
WRITE_BIT(prop, BT_OTS_OBJ_PROP_DELETE, 1)
|
||||
|
||||
/** @brief Set @ref BT_OTS_OBJ_PROP_EXECUTE property.
|
||||
*
|
||||
* @param prop Object properties.
|
||||
*/
|
||||
#define BT_OTS_OBJ_SET_PROP_EXECUTE(prop) \
|
||||
WRITE_BIT(prop, BT_OTS_OBJ_PROP_EXECUTE, 1)
|
||||
|
||||
/** @brief Set @ref BT_OTS_OBJ_PROP_READ property.
|
||||
*
|
||||
* @param prop Object properties.
|
||||
*/
|
||||
#define BT_OTS_OBJ_SET_PROP_READ(prop) \
|
||||
WRITE_BIT(prop, BT_OTS_OBJ_PROP_READ, 1)
|
||||
|
||||
/** @brief Set @ref BT_OTS_OBJ_PROP_WRITE property.
|
||||
*
|
||||
* @param prop Object properties.
|
||||
*/
|
||||
#define BT_OTS_OBJ_SET_PROP_WRITE(prop) \
|
||||
WRITE_BIT(prop, BT_OTS_OBJ_PROP_WRITE, 1)
|
||||
|
||||
/** @brief Set @ref BT_OTS_OBJ_PROP_APPEND property.
|
||||
*
|
||||
* @param prop Object properties.
|
||||
*/
|
||||
#define BT_OTS_OBJ_SET_PROP_APPEND(prop) \
|
||||
WRITE_BIT(prop, BT_OTS_OBJ_PROP_APPEND, 1)
|
||||
|
||||
/** @brief Set @ref BT_OTS_OBJ_PROP_TRUNCATE property.
|
||||
*
|
||||
* @param prop Object properties.
|
||||
*/
|
||||
#define BT_OTS_OBJ_SET_PROP_TRUNCATE(prop) \
|
||||
WRITE_BIT(prop, BT_OTS_OBJ_PROP_TRUNCATE, 1)
|
||||
|
||||
/** @brief Set @ref BT_OTS_OBJ_PROP_PATCH property.
|
||||
*
|
||||
* @param prop Object properties.
|
||||
*/
|
||||
#define BT_OTS_OBJ_SET_PROP_PATCH(prop) \
|
||||
WRITE_BIT(prop, BT_OTS_OBJ_PROP_PATCH, 1)
|
||||
|
||||
/** @brief Set @ref BT_OTS_OBJ_SET_PROP_MARKED property.
|
||||
*
|
||||
* @param prop Object properties.
|
||||
*/
|
||||
#define BT_OTS_OBJ_SET_PROP_MARKED(prop) \
|
||||
WRITE_BIT(prop, BT_OTS_OBJ_PROP_MARKED, 1)
|
||||
|
||||
/** @brief Get @ref BT_OTS_OBJ_PROP_DELETE property.
|
||||
*
|
||||
* @param prop Object properties.
|
||||
*/
|
||||
#define BT_OTS_OBJ_GET_PROP_DELETE(prop) \
|
||||
((prop) & BIT(BT_OTS_OBJ_PROP_DELETE))
|
||||
|
||||
/** @brief Get @ref BT_OTS_OBJ_PROP_EXECUTE property.
|
||||
*
|
||||
* @param prop Object properties.
|
||||
*/
|
||||
#define BT_OTS_OBJ_GET_PROP_EXECUTE(prop) \
|
||||
((prop) & BIT(BT_OTS_OBJ_PROP_EXECUTE))
|
||||
|
||||
/** @brief Get @ref BT_OTS_OBJ_PROP_READ property.
|
||||
*
|
||||
* @param prop Object properties.
|
||||
*/
|
||||
#define BT_OTS_OBJ_GET_PROP_READ(prop) \
|
||||
((prop) & BIT(BT_OTS_OBJ_PROP_READ))
|
||||
|
||||
/** @brief Get @ref BT_OTS_OBJ_PROP_WRITE property.
|
||||
*
|
||||
* @param prop Object properties.
|
||||
*/
|
||||
#define BT_OTS_OBJ_GET_PROP_WRITE(prop) \
|
||||
((prop) & BIT(BT_OTS_OBJ_PROP_WRITE))
|
||||
|
||||
/** @brief Get @ref BT_OTS_OBJ_PROP_APPEND property.
|
||||
*
|
||||
* @param prop Object properties.
|
||||
*/
|
||||
#define BT_OTS_OBJ_GET_PROP_APPEND(prop) \
|
||||
((prop) & BIT(BT_OTS_OBJ_PROP_APPEND))
|
||||
|
||||
/** @brief Get @ref BT_OTS_OBJ_PROP_TRUNCATE property.
|
||||
*
|
||||
* @param prop Object properties.
|
||||
*/
|
||||
#define BT_OTS_OBJ_GET_PROP_TRUNCATE(prop) \
|
||||
((prop) & BIT(BT_OTS_OBJ_PROP_TRUNCATE))
|
||||
|
||||
/** @brief Get @ref BT_OTS_OBJ_PROP_PATCH property.
|
||||
*
|
||||
* @param prop Object properties.
|
||||
*/
|
||||
#define BT_OTS_OBJ_GET_PROP_PATCH(prop) \
|
||||
((prop) & BIT(BT_OTS_OBJ_PROP_PATCH))
|
||||
|
||||
/** @brief Get @ref BT_OTS_OBJ_PROP_MARKED property.
|
||||
*
|
||||
* @param prop Object properties.
|
||||
*/
|
||||
#define BT_OTS_OBJ_GET_PROP_MARKED(prop) \
|
||||
((prop) & BIT(BT_OTS_OBJ_PROP_MARKED))
|
||||
|
||||
/** @brief Descriptor for OTS Object Size parameter. */
|
||||
struct bt_ots_obj_size {
|
||||
/* Current Size */
|
||||
uint32_t cur;
|
||||
|
||||
/* Allocated Size */
|
||||
uint32_t alloc;
|
||||
} __packed;
|
||||
|
||||
/** @brief Descriptor for OTS object initialization. */
|
||||
struct bt_ots_obj_metadata {
|
||||
/* Object Name */
|
||||
char *name;
|
||||
|
||||
/* Object Type */
|
||||
struct bt_ots_obj_type type;
|
||||
|
||||
/* Object Size */
|
||||
struct bt_ots_obj_size size;
|
||||
|
||||
/* Object Properties */
|
||||
uint32_t props;
|
||||
};
|
||||
|
||||
/** @brief Object Action Control Point Feature bits. */
|
||||
enum {
|
||||
/** Bit 0 OACP Create Op Code Supported */
|
||||
BT_OTS_OACP_FEAT_CREATE = 0,
|
||||
|
||||
/** Bit 1 OACP Delete Op Code Supported */
|
||||
BT_OTS_OACP_FEAT_DELETE = 1,
|
||||
|
||||
/** Bit 2 OACP Calculate Checksum Op Code Supported */
|
||||
BT_OTS_OACP_FEAT_CHECKSUM = 2,
|
||||
|
||||
/** Bit 3 OACP Execute Op Code Supported */
|
||||
BT_OTS_OACP_FEAT_EXECUTE = 3,
|
||||
|
||||
/** Bit 4 OACP Read Op Code Supported */
|
||||
BT_OTS_OACP_FEAT_READ = 4,
|
||||
|
||||
/** Bit 5 OACP Write Op Code Supported */
|
||||
BT_OTS_OACP_FEAT_WRITE = 5,
|
||||
|
||||
/** Bit 6 Appending Additional Data to Objects Supported */
|
||||
BT_OTS_OACP_FEAT_APPEND = 6,
|
||||
|
||||
/** Bit 7 Truncation of Objects Supported */
|
||||
BT_OTS_OACP_FEAT_TRUNCATE = 7,
|
||||
|
||||
/** Bit 8 Patching of Objects Supported */
|
||||
BT_OTS_OACP_FEAT_PATCH = 8,
|
||||
|
||||
/** Bit 9 OACP Abort Op Code Supported */
|
||||
BT_OTS_OACP_FEAT_ABORT = 9,
|
||||
};
|
||||
|
||||
/** @brief Set @ref BT_OTS_OACP_SET_FEAT_CREATE feature.
|
||||
*
|
||||
* @param feat OTS features.
|
||||
*/
|
||||
#define BT_OTS_OACP_SET_FEAT_CREATE(feat) \
|
||||
WRITE_BIT(feat, BT_OTS_OACP_FEAT_CREATE, 1)
|
||||
|
||||
/** @brief Set @ref BT_OTS_OACP_FEAT_DELETE feature.
|
||||
*
|
||||
* @param feat OTS features.
|
||||
*/
|
||||
#define BT_OTS_OACP_SET_FEAT_DELETE(feat) \
|
||||
WRITE_BIT(feat, BT_OTS_OACP_FEAT_DELETE, 1)
|
||||
|
||||
/** @brief Set @ref BT_OTS_OACP_FEAT_CHECKSUM feature.
|
||||
*
|
||||
* @param feat OTS features.
|
||||
*/
|
||||
#define BT_OTS_OACP_SET_FEAT_CHECKSUM(feat) \
|
||||
WRITE_BIT(feat, BT_OTS_OACP_FEAT_CHECKSUM, 1)
|
||||
|
||||
/** @brief Set @ref BT_OTS_OACP_FEAT_EXECUTE feature.
|
||||
*
|
||||
* @param feat OTS features.
|
||||
*/
|
||||
#define BT_OTS_OACP_SET_FEAT_EXECUTE(feat) \
|
||||
WRITE_BIT(feat, BT_OTS_OACP_FEAT_EXECUTE, 1)
|
||||
|
||||
/** @brief Set @ref BT_OTS_OACP_FEAT_READ feature.
|
||||
*
|
||||
* @param feat OTS features.
|
||||
*/
|
||||
#define BT_OTS_OACP_SET_FEAT_READ(feat) \
|
||||
WRITE_BIT(feat, BT_OTS_OACP_FEAT_READ, 1)
|
||||
|
||||
/** @brief Set @ref BT_OTS_OACP_FEAT_WRITE feature.
|
||||
*
|
||||
* @param feat OTS features.
|
||||
*/
|
||||
#define BT_OTS_OACP_SET_FEAT_WRITE(feat) \
|
||||
WRITE_BIT(feat, BT_OTS_OACP_FEAT_WRITE, 1)
|
||||
|
||||
/** @brief Set @ref BT_OTS_OACP_FEAT_APPEND feature.
|
||||
*
|
||||
* @param feat OTS features.
|
||||
*/
|
||||
#define BT_OTS_OACP_SET_FEAT_APPEND(feat) \
|
||||
WRITE_BIT(feat, BT_OTS_OACP_FEAT_APPEND, 1)
|
||||
|
||||
/** @brief Set @ref BT_OTS_OACP_FEAT_TRUNCATE feature.
|
||||
*
|
||||
* @param feat OTS features.
|
||||
*/
|
||||
#define BT_OTS_OACP_SET_FEAT_TRUNCATE(feat) \
|
||||
WRITE_BIT(feat, BT_OTS_OACP_FEAT_TRUNCATE, 1)
|
||||
|
||||
/** @brief Set @ref BT_OTS_OACP_FEAT_PATCH feature.
|
||||
*
|
||||
* @param feat OTS features.
|
||||
*/
|
||||
#define BT_OTS_OACP_SET_FEAT_PATCH(feat) \
|
||||
WRITE_BIT(feat, BT_OTS_OACP_FEAT_PATCH, 1)
|
||||
|
||||
/** @brief Set @ref BT_OTS_OACP_FEAT_ABORT feature.
|
||||
*
|
||||
* @param feat OTS features.
|
||||
*/
|
||||
#define BT_OTS_OACP_SET_FEAT_ABORT(feat) \
|
||||
WRITE_BIT(feat, BT_OTS_OACP_FEAT_ABORT, 1)
|
||||
|
||||
/** @brief Get @ref BT_OTS_OACP_FEAT_CREATE feature.
|
||||
*
|
||||
* @param feat OTS features.
|
||||
*/
|
||||
#define BT_OTS_OACP_GET_FEAT_CREATE(feat) \
|
||||
((feat) & BIT(BT_OTS_OACP_FEAT_CREATE))
|
||||
|
||||
/** @brief Get @ref BT_OTS_OACP_FEAT_DELETE feature.
|
||||
*
|
||||
* @param feat OTS features.
|
||||
*/
|
||||
#define BT_OTS_OACP_GET_FEAT_DELETE(feat) \
|
||||
((feat) & BIT(BT_OTS_OACP_FEAT_DELETE))
|
||||
|
||||
/** @brief Get @ref BT_OTS_OACP_FEAT_CHECKSUM feature.
|
||||
*
|
||||
* @param feat OTS features.
|
||||
*/
|
||||
#define BT_OTS_OACP_GET_FEAT_CHECKSUM(feat) \
|
||||
((feat) & BIT(BT_OTS_OACP_FEAT_CHECKSUM))
|
||||
|
||||
/** @brief Get @ref BT_OTS_OACP_FEAT_EXECUTE feature.
|
||||
*
|
||||
* @param feat OTS features.
|
||||
*/
|
||||
#define BT_OTS_OACP_GET_FEAT_EXECUTE(feat) \
|
||||
((feat) & BIT(BT_OTS_OACP_FEAT_EXECUTE))
|
||||
|
||||
/** @brief Get @ref BT_OTS_OACP_FEAT_READ feature.
|
||||
*
|
||||
* @param feat OTS features.
|
||||
*/
|
||||
#define BT_OTS_OACP_GET_FEAT_READ(feat) \
|
||||
((feat) & BIT(BT_OTS_OACP_FEAT_READ))
|
||||
|
||||
/** @brief Get @ref BT_OTS_OACP_FEAT_WRITE feature.
|
||||
*
|
||||
* @param feat OTS features.
|
||||
*/
|
||||
#define BT_OTS_OACP_GET_FEAT_WRITE(feat) \
|
||||
((feat) & BIT(BT_OTS_OACP_FEAT_WRITE))
|
||||
|
||||
/** @brief Get @ref BT_OTS_OACP_FEAT_APPEND feature.
|
||||
*
|
||||
* @param feat OTS features.
|
||||
*/
|
||||
#define BT_OTS_OACP_GET_FEAT_APPEND(feat) \
|
||||
((feat) & BIT(BT_OTS_OACP_FEAT_APPEND))
|
||||
|
||||
/** @brief Get @ref BT_OTS_OACP_FEAT_TRUNCATE feature.
|
||||
*
|
||||
* @param feat OTS features.
|
||||
*/
|
||||
#define BT_OTS_OACP_GET_FEAT_TRUNCATE(feat) \
|
||||
((feat) & BIT(BT_OTS_OACP_FEAT_TRUNCATE))
|
||||
|
||||
/** @brief Get @ref BT_OTS_OACP_FEAT_PATCH feature.
|
||||
*
|
||||
* @param feat OTS features.
|
||||
*/
|
||||
#define BT_OTS_OACP_GET_FEAT_PATCH(feat) \
|
||||
((feat) & BIT(BT_OTS_OACP_FEAT_PATCH))
|
||||
|
||||
/** @brief Get @ref BT_OTS_OACP_FEAT_ABORT feature.
|
||||
*
|
||||
* @param feat OTS features.
|
||||
*/
|
||||
#define BT_OTS_OACP_GET_FEAT_ABORT(feat) \
|
||||
((feat) & BIT(BT_OTS_OACP_FEAT_ABORT))
|
||||
|
||||
/** @brief Object List Control Point Feature bits. */
|
||||
enum {
|
||||
/** Bit 0 OLCP Go To Op Code Supported */
|
||||
BT_OTS_OLCP_FEAT_GO_TO = 0,
|
||||
|
||||
/** Bit 1 OLCP Order Op Code Supported */
|
||||
BT_OTS_OLCP_FEAT_ORDER = 1,
|
||||
|
||||
/** Bit 2 OLCP Request Number of Objects Op Code Supported */
|
||||
BT_OTS_OLCP_FEAT_NUM_REQ = 2,
|
||||
|
||||
/** Bit 3 OLCP Clear Marking Op Code Supported*/
|
||||
BT_OTS_OLCP_FEAT_CLEAR = 3,
|
||||
};
|
||||
|
||||
/** @brief Set @ref BT_OTS_OLCP_FEAT_GO_TO feature.
|
||||
*
|
||||
* @param feat OTS features.
|
||||
*/
|
||||
#define BT_OTS_OLCP_SET_FEAT_GO_TO(feat) \
|
||||
WRITE_BIT(feat, BT_OTS_OLCP_FEAT_GO_TO, 1)
|
||||
|
||||
/** @brief Set @ref BT_OTS_OLCP_FEAT_ORDER feature.
|
||||
*
|
||||
* @param feat OTS features.
|
||||
*/
|
||||
#define BT_OTS_OLCP_SET_FEAT_ORDER(feat) \
|
||||
WRITE_BIT(feat, BT_OTS_OLCP_FEAT_ORDER, 1)
|
||||
|
||||
/** @brief Set @ref BT_OTS_OLCP_FEAT_NUM_REQ feature.
|
||||
*
|
||||
* @param feat OTS features.
|
||||
*/
|
||||
#define BT_OTS_OLCP_SET_FEAT_NUM_REQ(feat) \
|
||||
WRITE_BIT(feat, BT_OTS_OLCP_FEAT_NUM_REQ, 1)
|
||||
|
||||
/** @brief Set @ref BT_OTS_OLCP_FEAT_CLEAR feature.
|
||||
*
|
||||
* @param feat OTS features.
|
||||
*/
|
||||
#define BT_OTS_OLCP_SET_FEAT_CLEAR(feat) \
|
||||
WRITE_BIT(feat, BT_OTS_OLCP_FEAT_CLEAR, 1)
|
||||
|
||||
/** @brief Get @ref BT_OTS_OLCP_GET_FEAT_GO_TO feature.
|
||||
*
|
||||
* @param feat OTS features.
|
||||
*/
|
||||
#define BT_OTS_OLCP_GET_FEAT_GO_TO(feat) \
|
||||
((feat) & BIT(BT_OTS_OLCP_FEAT_GO_TO))
|
||||
|
||||
/** @brief Get @ref BT_OTS_OLCP_GET_FEAT_ORDER feature.
|
||||
*
|
||||
* @param feat OTS features.
|
||||
*/
|
||||
#define BT_OTS_OLCP_GET_FEAT_ORDER(feat) \
|
||||
((feat) & BIT(BT_OTS_OLCP_FEAT_ORDER))
|
||||
|
||||
/** @brief Get @ref BT_OTS_OLCP_GET_FEAT_NUM_REQ feature.
|
||||
*
|
||||
* @param feat OTS features.
|
||||
*/
|
||||
#define BT_OTS_OLCP_GET_FEAT_NUM_REQ(feat) \
|
||||
((feat) & BIT(BT_OTS_OLCP_FEAT_NUM_REQ))
|
||||
|
||||
/** @brief Get @ref BT_OTS_OLCP_GET_FEAT_CLEAR feature.
|
||||
*
|
||||
* @param feat OTS features.
|
||||
*/
|
||||
#define BT_OTS_OLCP_GET_FEAT_CLEAR(feat) \
|
||||
((feat) & BIT(BT_OTS_OLCP_FEAT_CLEAR))
|
||||
|
||||
/**@brief Features of the OTS. */
|
||||
struct bt_ots_feat {
|
||||
/* OACP Features */
|
||||
uint32_t oacp;
|
||||
|
||||
/* OLCP Features */
|
||||
uint32_t olcp;
|
||||
} __packed;
|
||||
|
||||
/** @brief Opaque OTS instance. */
|
||||
struct bt_ots;
|
||||
|
||||
/** @brief OTS callback structure. */
|
||||
struct bt_ots_cb {
|
||||
/** @brief Object created callback
|
||||
*
|
||||
* This callback is called whenever a new object is created.
|
||||
* Application can reject this request by returning an error
|
||||
* when it does not have necessary resources to hold this new
|
||||
* object. This callback is also triggered when the server
|
||||
* creates a new object with bt_ots_obj_add() API.
|
||||
*
|
||||
* @param ots OTS instance.
|
||||
* @param conn The connection that is requesting object creation or
|
||||
* NULL if object is created by the following function:
|
||||
* bt_ots_obj_add().
|
||||
* @param id Object ID.
|
||||
* @param init Object initialization metadata.
|
||||
*
|
||||
* @return 0 in case of success or negative value in case of error.
|
||||
* Possible return values:
|
||||
* -ENOMEM if no available space for new object.
|
||||
*/
|
||||
int (*obj_created)(struct bt_ots *ots, struct bt_conn *conn,
|
||||
uint64_t id,
|
||||
const struct bt_ots_obj_metadata *init);
|
||||
|
||||
/** @brief Object deleted callback
|
||||
*
|
||||
* This callback is called whenever an object is deleted. It is
|
||||
* also triggered when the server deletes an object with
|
||||
* bt_ots_obj_delete() API.
|
||||
*
|
||||
* @param ots OTS instance.
|
||||
* @param conn The connection that deleted the object or NULL if
|
||||
* this request came from the server.
|
||||
* @param id Object ID.
|
||||
*/
|
||||
void (*obj_deleted)(struct bt_ots *ots, struct bt_conn *conn,
|
||||
uint64_t id);
|
||||
|
||||
/** @brief Object selected callback
|
||||
*
|
||||
* This callback is called on successful object selection.
|
||||
*
|
||||
* @param ots OTS instance.
|
||||
* @param conn The connection that selected new object.
|
||||
* @param id Object ID.
|
||||
*/
|
||||
void (*obj_selected)(struct bt_ots *ots, struct bt_conn *conn,
|
||||
uint64_t id);
|
||||
|
||||
/** @brief Object read callback
|
||||
*
|
||||
* This callback is called multiple times during the Object read
|
||||
* operation. OTS module will keep requesting successive Object
|
||||
* fragments from the application until the read operation is
|
||||
* completed. The end of read operation is indicated by NULL data
|
||||
* parameter.
|
||||
*
|
||||
* @param ots OTS instance.
|
||||
* @param conn The connection that read object.
|
||||
* @param id Object ID.
|
||||
* @param data In: NULL once the read operations is completed.
|
||||
* Out: Next chunk of data to be sent.
|
||||
* @param len Remaining length requested by the client.
|
||||
* @param offset Object data offset.
|
||||
*
|
||||
* @return Data length to be sent via data parameter. This value
|
||||
* shall be smaller or equal to the len parameter.
|
||||
*/
|
||||
uint32_t (*obj_read)(struct bt_ots *ots, struct bt_conn *conn,
|
||||
uint64_t id, uint8_t **data, uint32_t len,
|
||||
uint32_t offset);
|
||||
};
|
||||
|
||||
/** @brief Descriptor for OTS initialization. */
|
||||
struct bt_ots_init {
|
||||
/* OTS features */
|
||||
struct bt_ots_feat features;
|
||||
|
||||
/* Callbacks */
|
||||
struct bt_ots_cb *cb;
|
||||
};
|
||||
|
||||
/** @brief Add an object to the OTS instance.
|
||||
*
|
||||
* This function adds an object to the OTS database. When the
|
||||
* object is being added, a callback obj_created() is called
|
||||
* to notify the user about a new object ID.
|
||||
*
|
||||
* @param ots OTS instance.
|
||||
* @param obj_init Meta data of the object.
|
||||
*
|
||||
* @return 0 in case of success or negative value in case of error.
|
||||
*/
|
||||
int bt_ots_obj_add(struct bt_ots *ots, struct bt_ots_obj_metadata *obj_init);
|
||||
|
||||
/** @brief Delete an object from the OTS instance.
|
||||
*
|
||||
* This function deletes an object from the OTS database. When the
|
||||
* object is deleted a callback obj_deleted() is called
|
||||
* to notify the user about this event. At this point, it is possible
|
||||
* to free allocated buffer for object data.
|
||||
*
|
||||
* @param ots OTS instance.
|
||||
* @param id ID of the object to be deleted (uint48).
|
||||
*
|
||||
* @return 0 in case of success or negative value in case of error.
|
||||
*/
|
||||
int bt_ots_obj_delete(struct bt_ots *ots, uint64_t id);
|
||||
|
||||
/** @brief Get the service declaration attribute.
|
||||
*
|
||||
* This function is enabled for CONFIG_BT_OTS_SECONDARY_SVC configuration.
|
||||
* The first service attribute can be included in any other GATT service.
|
||||
*
|
||||
* @param ots OTS instance.
|
||||
*
|
||||
* @return The first OTS attribute instance.
|
||||
*/
|
||||
void *bt_ots_svc_decl_get(struct bt_ots *ots);
|
||||
|
||||
/** @brief Initialize the OTS instance.
|
||||
*
|
||||
* @param ots OTS instance.
|
||||
* @param ots_init OTS initialization descriptor.
|
||||
*
|
||||
* @return 0 in case of success or negative value in case of error.
|
||||
*/
|
||||
int bt_ots_init(struct bt_ots *ots, struct bt_ots_init *ots_init);
|
||||
|
||||
/** @brief Get a free instance of OTS from the pool.
|
||||
*
|
||||
* @return OTS instance in case of success or NULL in case of error.
|
||||
*/
|
||||
struct bt_ots *bt_ots_free_instance_get(void);
|
||||
|
||||
/** @brief Converts binary OTS Object ID to string.
|
||||
*
|
||||
* @param obj_id Object ID.
|
||||
* @param str Address of user buffer with enough room to store
|
||||
* formatted string containing binary Object ID.
|
||||
* @param len Length of data to be copied to user string buffer.
|
||||
* Refer to BT_OTS_OBJ_ID_STR_LEN about
|
||||
* recommended value.
|
||||
*
|
||||
* @return Number of successfully formatted bytes from binary ID.
|
||||
*/
|
||||
static inline int bt_ots_obj_id_to_str(uint64_t obj_id, char *str, size_t len)
|
||||
{
|
||||
uint8_t id[6];
|
||||
|
||||
sys_put_le48(obj_id, id);
|
||||
|
||||
return snprintk(str, len, "0x%02X%02X%02X%02X%02X%02X",
|
||||
id[5], id[4], id[3], id[2], id[1], id[0]);
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @}
|
||||
*/
|
||||
|
||||
#endif /* ZEPHYR_INCLUDE_BLUETOOTH_SERVICES_OTS_H_ */
|
|
@ -6,3 +6,5 @@ zephyr_sources_ifdef(CONFIG_BT_DIS dis.c)
|
|||
zephyr_sources_ifdef(CONFIG_BT_BAS bas.c)
|
||||
|
||||
zephyr_sources_ifdef(CONFIG_BT_HRS hrs.c)
|
||||
|
||||
add_subdirectory_ifdef(CONFIG_BT_OTS ots)
|
||||
|
|
|
@ -12,4 +12,6 @@ source "subsys/bluetooth/services/Kconfig.bas"
|
|||
|
||||
source "subsys/bluetooth/services/Kconfig.hrs"
|
||||
|
||||
source "subsys/bluetooth/services/ots/Kconfig"
|
||||
|
||||
endmenu
|
||||
|
|
9
subsys/bluetooth/services/ots/CMakeLists.txt
Normal file
9
subsys/bluetooth/services/ots/CMakeLists.txt
Normal file
|
@ -0,0 +1,9 @@
|
|||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
zephyr_sources_ifdef(
|
||||
CONFIG_BT_OTS
|
||||
ots.c
|
||||
ots_l2cap.c
|
||||
ots_obj_manager.c
|
||||
ots_oacp.c
|
||||
ots_olcp.c)
|
47
subsys/bluetooth/services/ots/Kconfig
Normal file
47
subsys/bluetooth/services/ots/Kconfig
Normal file
|
@ -0,0 +1,47 @@
|
|||
# Bluetooth Object Transfer service
|
||||
|
||||
# Copyright (c) 2020 Nordic Semiconductor ASA
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
config BT_OTS
|
||||
bool "Object Transfer Service (OTS) [EXPERIMENTAL]"
|
||||
select BT_L2CAP_DYNAMIC_CHANNEL
|
||||
select BT_GATT_DYNAMIC_DB
|
||||
select BT_SMP
|
||||
help
|
||||
Enable Object Transfer Service.
|
||||
|
||||
if BT_OTS
|
||||
|
||||
config BT_OTS_MAX_INST_CNT
|
||||
int "Maximum number of available OTS instances"
|
||||
default 1
|
||||
range 1 1 if !BT_OTS_SECONDARY_SVC
|
||||
|
||||
config BT_OTS_MAX_OBJ_CNT
|
||||
int "Maximum number of objects that each service instance can store"
|
||||
default 5
|
||||
|
||||
config BT_OTS_SECONDARY_SVC
|
||||
bool "Register OTS as Secondary Service"
|
||||
|
||||
config BT_OTS_OACP_READ_SUPPORT
|
||||
bool "Support OACP Read Operation"
|
||||
default y
|
||||
|
||||
config BT_OTS_OLCP_GO_TO_SUPPORT
|
||||
bool "Support OLCP Go To Operation"
|
||||
default y
|
||||
|
||||
config BT_OTS_L2CAP_CHAN_RX_MTU
|
||||
int "Size of RX MTU for Object Transfer Channel"
|
||||
default BT_L2CAP_RX_MTU if BT_HCI_ACL_FLOW_CONTROL
|
||||
default 23
|
||||
range 23 BT_L2CAP_RX_MTU if BT_HCI_ACL_FLOW_CONTROL
|
||||
range 23 BT_RX_BUF_LEN
|
||||
|
||||
module = BT_OTS
|
||||
module-str = BT_OTS
|
||||
source "${ZEPHYR_BASE}/subsys/logging/Kconfig.template.log_config"
|
||||
|
||||
endif # BT_OTS
|
387
subsys/bluetooth/services/ots/ots.c
Normal file
387
subsys/bluetooth/services/ots/ots.c
Normal file
|
@ -0,0 +1,387 @@
|
|||
/*
|
||||
* 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 <init.h>
|
||||
#include <sys/printk.h>
|
||||
#include <sys/byteorder.h>
|
||||
#include <zephyr.h>
|
||||
|
||||
#include <bluetooth/bluetooth.h>
|
||||
#include <bluetooth/l2cap.h>
|
||||
#include <bluetooth/conn.h>
|
||||
#include <bluetooth/uuid.h>
|
||||
#include <bluetooth/gatt.h>
|
||||
|
||||
#include <bluetooth/services/ots.h>
|
||||
#include "ots_internal.h"
|
||||
#include "ots_obj_manager_internal.h"
|
||||
|
||||
#include <logging/log.h>
|
||||
|
||||
LOG_MODULE_REGISTER(bt_ots, CONFIG_BT_OTS_LOG_LEVEL);
|
||||
|
||||
#if defined(CONFIG_BT_OTS_OACP_READ_SUPPORT)
|
||||
#define OACP_FEAT_BIT_READ BIT(BT_OTS_OACP_FEAT_READ)
|
||||
#else
|
||||
#define OACP_FEAT_BIT_READ 0
|
||||
#endif
|
||||
|
||||
/* OACP features supported by Kconfig */
|
||||
#define OACP_FEAT OACP_FEAT_BIT_READ
|
||||
|
||||
#if defined(CONFIG_BT_OTS_OLCP_GO_TO_SUPPORT)
|
||||
#define OLCP_FEAT_BIT_GOTO BIT(BT_OTS_OLCP_FEAT_GO_TO)
|
||||
#else
|
||||
#define OLCP_FEAT_BIT_GOTO 0
|
||||
#endif
|
||||
|
||||
/* OLCP features supported by Kconfig */
|
||||
#define OLCP_FEAT OLCP_FEAT_BIT_GOTO
|
||||
|
||||
static ssize_t ots_feature_read(struct bt_conn *conn,
|
||||
const struct bt_gatt_attr *attr, void *buf,
|
||||
uint16_t len, uint16_t offset)
|
||||
{
|
||||
struct bt_ots *ots = (struct bt_ots *) attr->user_data;
|
||||
|
||||
LOG_DBG("OTS Feature GATT Read Operation");
|
||||
|
||||
return bt_gatt_attr_read(conn, attr, buf, len, offset, &ots->features,
|
||||
sizeof(ots->features));
|
||||
}
|
||||
|
||||
static ssize_t ots_obj_name_read(struct bt_conn *conn,
|
||||
const struct bt_gatt_attr *attr, void *buf,
|
||||
uint16_t len, uint16_t offset)
|
||||
{
|
||||
struct bt_ots *ots = (struct bt_ots *) attr->user_data;
|
||||
|
||||
LOG_DBG("OTS Object Name GATT Read Operation");
|
||||
|
||||
if (!ots->cur_obj) {
|
||||
LOG_DBG("No Current Object selected in OTS!");
|
||||
return BT_GATT_ERR(BT_GATT_OTS_OBJECT_NOT_SELECTED);
|
||||
}
|
||||
|
||||
return bt_gatt_attr_read(conn, attr, buf, len, offset,
|
||||
ots->cur_obj->metadata.name,
|
||||
strlen(ots->cur_obj->metadata.name));
|
||||
}
|
||||
|
||||
static ssize_t ots_obj_type_read(struct bt_conn *conn,
|
||||
const struct bt_gatt_attr *attr, void *buf,
|
||||
uint16_t len, uint16_t offset)
|
||||
{
|
||||
struct bt_ots *ots = (struct bt_ots *) attr->user_data;
|
||||
struct bt_ots_obj_metadata *obj_meta;
|
||||
|
||||
LOG_DBG("OTS Object Type GATT Read Operation");
|
||||
|
||||
if (!ots->cur_obj) {
|
||||
LOG_DBG("No Current Object selected in OTS!");
|
||||
return BT_GATT_ERR(BT_GATT_OTS_OBJECT_NOT_SELECTED);
|
||||
}
|
||||
|
||||
obj_meta = &ots->cur_obj->metadata;
|
||||
if (obj_meta->type.uuid.type == BT_UUID_TYPE_128) {
|
||||
return bt_gatt_attr_read(conn, attr, buf, len, offset,
|
||||
obj_meta->type.uuid_128.val,
|
||||
sizeof(obj_meta->type.uuid_128.val));
|
||||
} else {
|
||||
return bt_gatt_attr_read(conn, attr, buf, len, offset,
|
||||
&obj_meta->type.uuid_16.val,
|
||||
sizeof(obj_meta->type.uuid_16.val));
|
||||
}
|
||||
}
|
||||
|
||||
static ssize_t ots_obj_size_read(struct bt_conn *conn,
|
||||
const struct bt_gatt_attr *attr, void *buf,
|
||||
uint16_t len, uint16_t offset)
|
||||
{
|
||||
struct bt_ots *ots = (struct bt_ots *) attr->user_data;
|
||||
|
||||
LOG_DBG("OTS Object Size GATT Read Operation");
|
||||
|
||||
if (!ots->cur_obj) {
|
||||
LOG_DBG("No Current Object selected in OTS!");
|
||||
return BT_GATT_ERR(BT_GATT_OTS_OBJECT_NOT_SELECTED);
|
||||
}
|
||||
|
||||
return bt_gatt_attr_read(conn, attr, buf, len, offset,
|
||||
&ots->cur_obj->metadata.size,
|
||||
sizeof(ots->cur_obj->metadata.size));
|
||||
}
|
||||
|
||||
static ssize_t ots_obj_id_read(struct bt_conn *conn,
|
||||
const struct bt_gatt_attr *attr, void *buf,
|
||||
uint16_t len, uint16_t offset)
|
||||
{
|
||||
struct bt_ots *ots = (struct bt_ots *) attr->user_data;
|
||||
uint8_t id[BT_OTS_OBJ_ID_SIZE];
|
||||
char id_str[BT_OTS_OBJ_ID_STR_LEN];
|
||||
|
||||
LOG_DBG("OTS Object ID GATT Read Operation");
|
||||
|
||||
if (!ots->cur_obj) {
|
||||
LOG_DBG("No Current Object selected in OTS!");
|
||||
return BT_GATT_ERR(BT_GATT_OTS_OBJECT_NOT_SELECTED);
|
||||
}
|
||||
|
||||
sys_put_le48(ots->cur_obj->id, id);
|
||||
|
||||
bt_ots_obj_id_to_str(ots->cur_obj->id, id_str,
|
||||
sizeof(id_str));
|
||||
LOG_DBG("Current Object ID: %s", log_strdup(id_str));
|
||||
|
||||
return bt_gatt_attr_read(conn, attr, buf, len, offset, id, sizeof(id));
|
||||
}
|
||||
|
||||
static ssize_t ots_obj_prop_read(struct bt_conn *conn,
|
||||
const struct bt_gatt_attr *attr, void *buf,
|
||||
uint16_t len, uint16_t offset)
|
||||
{
|
||||
struct bt_ots *ots = (struct bt_ots *) attr->user_data;
|
||||
|
||||
LOG_DBG("OTS Object Properties GATT Read Operation");
|
||||
|
||||
if (!ots->cur_obj) {
|
||||
LOG_DBG("No Current Object selected in OTS!");
|
||||
return BT_GATT_ERR(BT_GATT_OTS_OBJECT_NOT_SELECTED);
|
||||
}
|
||||
|
||||
return bt_gatt_attr_read(conn, attr, buf, len, offset,
|
||||
&ots->cur_obj->metadata.props,
|
||||
sizeof(ots->cur_obj->metadata.props));
|
||||
}
|
||||
|
||||
int bt_ots_obj_add(struct bt_ots *ots,
|
||||
struct bt_ots_obj_metadata *obj_init)
|
||||
{
|
||||
int err;
|
||||
struct bt_gatt_ots_object *obj;
|
||||
|
||||
err = bt_gatt_ots_obj_manager_obj_add(ots->obj_manager, &obj);
|
||||
if (err) {
|
||||
LOG_ERR("No space available in the object manager");
|
||||
return err;
|
||||
}
|
||||
|
||||
/* Initialize object. */
|
||||
memcpy(&obj->metadata, obj_init, sizeof(obj->metadata));
|
||||
|
||||
/* Request object data. */
|
||||
if (ots->cb->obj_created) {
|
||||
err = ots->cb->obj_created(ots, NULL, obj->id, obj_init);
|
||||
if (err) {
|
||||
bt_gatt_ots_obj_manager_obj_delete(obj);
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
/* Make object the Current Object if this is the first one added. */
|
||||
if (!ots->cur_obj) {
|
||||
ots->cur_obj = obj;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int bt_ots_obj_delete(struct bt_ots *ots, uint64_t id)
|
||||
{
|
||||
int err;
|
||||
struct bt_gatt_ots_object *obj;
|
||||
|
||||
err = bt_gatt_ots_obj_manager_obj_get(ots->obj_manager, id, &obj);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
|
||||
if (ots->cur_obj == obj) {
|
||||
if (obj->state.type != BT_GATT_OTS_OBJECT_IDLE_STATE) {
|
||||
return -EBUSY;
|
||||
}
|
||||
ots->cur_obj = NULL;
|
||||
}
|
||||
|
||||
err = bt_gatt_ots_obj_manager_obj_delete(obj);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
|
||||
if (ots->cb->obj_deleted) {
|
||||
ots->cb->obj_deleted(ots, NULL, obj->id);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#if defined(CONFIG_BT_OTS_SECONDARY_SVC)
|
||||
void *bt_ots_svc_decl_get(struct bt_ots *ots)
|
||||
{
|
||||
return ots->service->attrs;
|
||||
}
|
||||
#endif
|
||||
|
||||
int bt_ots_init(struct bt_ots *ots,
|
||||
struct bt_ots_init *ots_init)
|
||||
{
|
||||
int err;
|
||||
|
||||
if (!ots || !ots_init || !ots_init->cb) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
__ASSERT(ots_init->cb->obj_created,
|
||||
"Callback for object creation is not set");
|
||||
__ASSERT(ots_init->cb->obj_read ||
|
||||
!BT_OTS_OACP_GET_FEAT_READ(ots_init->features.oacp),
|
||||
"Callback for object reading is not set");
|
||||
|
||||
/* Set callback structure. */
|
||||
ots->cb = ots_init->cb;
|
||||
|
||||
/* Check OACP supported features against Kconfig. */
|
||||
if (ots_init->features.oacp & (~((uint32_t) OACP_FEAT))) {
|
||||
return -ENOTSUP;
|
||||
}
|
||||
|
||||
ots->features.oacp = ots_init->features.oacp;
|
||||
LOG_DBG("OACP features: 0x%04X", ots->features.oacp);
|
||||
|
||||
/* Check OLCP supported features against Kconfig. */
|
||||
if (ots_init->features.olcp & (~((uint32_t) OLCP_FEAT))) {
|
||||
return -ENOTSUP;
|
||||
}
|
||||
ots->features.olcp = ots_init->features.olcp;
|
||||
LOG_DBG("OLCP features: 0x%04X", ots->features.olcp);
|
||||
|
||||
/* Register L2CAP context. */
|
||||
err = bt_gatt_ots_l2cap_register(&ots->l2cap);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
|
||||
err = bt_gatt_service_register(ots->service);
|
||||
if (err) {
|
||||
bt_gatt_ots_l2cap_unregister(&ots->l2cap);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
LOG_DBG("Initialized OTS");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#if defined(CONFIG_BT_OTS_SECONDARY_SVC)
|
||||
#define BT_GATT_OTS_SERVICE BT_GATT_SECONDARY_SERVICE
|
||||
#else
|
||||
#define BT_GATT_OTS_SERVICE BT_GATT_PRIMARY_SERVICE
|
||||
#endif
|
||||
|
||||
#define BT_GATT_OTS_ATTRS(_ots) { \
|
||||
BT_GATT_OTS_SERVICE(BT_UUID_OTS), \
|
||||
BT_GATT_CHARACTERISTIC(BT_UUID_OTS_FEATURE, \
|
||||
BT_GATT_CHRC_READ, BT_GATT_PERM_READ, \
|
||||
ots_feature_read, NULL, &_ots), \
|
||||
BT_GATT_CHARACTERISTIC(BT_UUID_OTS_NAME, \
|
||||
BT_GATT_CHRC_READ, BT_GATT_PERM_READ, \
|
||||
ots_obj_name_read, NULL, &_ots), \
|
||||
BT_GATT_CHARACTERISTIC(BT_UUID_OTS_TYPE, \
|
||||
BT_GATT_CHRC_READ, BT_GATT_PERM_READ, \
|
||||
ots_obj_type_read, NULL, &_ots), \
|
||||
BT_GATT_CHARACTERISTIC(BT_UUID_OTS_SIZE, \
|
||||
BT_GATT_CHRC_READ, BT_GATT_PERM_READ, \
|
||||
ots_obj_size_read, NULL, &_ots), \
|
||||
BT_GATT_CHARACTERISTIC(BT_UUID_OTS_ID, \
|
||||
BT_GATT_CHRC_READ, BT_GATT_PERM_READ, \
|
||||
ots_obj_id_read, NULL, &_ots), \
|
||||
BT_GATT_CHARACTERISTIC(BT_UUID_OTS_PROPERTIES, \
|
||||
BT_GATT_CHRC_READ, BT_GATT_PERM_READ, \
|
||||
ots_obj_prop_read, NULL, &_ots), \
|
||||
BT_GATT_CHARACTERISTIC(BT_UUID_OTS_ACTION_CP, \
|
||||
BT_GATT_CHRC_WRITE | BT_GATT_CHRC_INDICATE, \
|
||||
BT_GATT_PERM_WRITE, NULL, \
|
||||
bt_gatt_ots_oacp_write, &_ots), \
|
||||
BT_GATT_CCC_MANAGED(&_ots.oacp_ind.ccc, \
|
||||
BT_GATT_PERM_READ | BT_GATT_PERM_WRITE), \
|
||||
BT_GATT_CHARACTERISTIC(BT_UUID_OTS_LIST_CP, \
|
||||
BT_GATT_CHRC_WRITE | BT_GATT_CHRC_INDICATE, \
|
||||
BT_GATT_PERM_WRITE, NULL, \
|
||||
bt_gatt_ots_olcp_write, &_ots), \
|
||||
BT_GATT_CCC_MANAGED(&_ots.olcp_ind.ccc, \
|
||||
BT_GATT_PERM_READ | BT_GATT_PERM_WRITE) \
|
||||
}
|
||||
|
||||
#define BT_GATT_OTS_ATTRS_DEF(n, _ots_instances) \
|
||||
static struct bt_gatt_attr ots_attrs_##n[] = \
|
||||
BT_GATT_OTS_ATTRS(_ots_instances[n]);
|
||||
|
||||
#define BT_GATT_OTS_SVC_ITEM(n, _) BT_GATT_SERVICE(ots_attrs_##n),
|
||||
|
||||
#define BT_GATT_OTS_INSTANCE_LIST_DEF(_instance_num) \
|
||||
static struct bt_ots ots_instances[_instance_num]; \
|
||||
UTIL_EVAL(UTIL_REPEAT( \
|
||||
_instance_num, BT_GATT_OTS_ATTRS_DEF, ots_instances)) \
|
||||
static struct bt_gatt_service ots_service_list[] = { \
|
||||
UTIL_LISTIFY(_instance_num, BT_GATT_OTS_SVC_ITEM) \
|
||||
}
|
||||
|
||||
#define BT_GATT_OTS_INSTANCE_LIST_SIZE (ARRAY_SIZE(ots_instances))
|
||||
#define BT_GATT_OTS_INSTANCE_LIST_START ots_instances
|
||||
#define BT_GATT_OTS_INSTANCE_LIST_END \
|
||||
(&ots_instances[BT_GATT_OTS_INSTANCE_LIST_SIZE])
|
||||
|
||||
#define BT_GATT_OTS_SERVICE_LIST_START ots_service_list
|
||||
|
||||
BT_GATT_OTS_INSTANCE_LIST_DEF(CONFIG_BT_OTS_MAX_INST_CNT);
|
||||
static uint32_t instance_cnt;
|
||||
|
||||
struct bt_ots *bt_ots_free_instance_get(void)
|
||||
{
|
||||
if (instance_cnt >= BT_GATT_OTS_INSTANCE_LIST_SIZE) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return &BT_GATT_OTS_INSTANCE_LIST_START[instance_cnt++];
|
||||
}
|
||||
|
||||
static int bt_gatt_ots_instances_prepare(const struct device *dev)
|
||||
{
|
||||
uint32_t index;
|
||||
struct bt_ots *instance;
|
||||
|
||||
for (instance = BT_GATT_OTS_INSTANCE_LIST_START, index = 0;
|
||||
instance != BT_GATT_OTS_INSTANCE_LIST_END;
|
||||
instance++, index++) {
|
||||
/* Assign an object pool to the OTS instance. */
|
||||
instance->obj_manager = bt_gatt_ots_obj_manager_assign();
|
||||
if (!instance->obj_manager) {
|
||||
LOG_ERR("OTS Object manager instance not available");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
/* Assign pointer to the service descriptor. */
|
||||
instance->service = &BT_GATT_OTS_SERVICE_LIST_START[index];
|
||||
|
||||
/* Initialize CCC descriptors for characteristics with
|
||||
* indication properties.
|
||||
*/
|
||||
instance->oacp_ind.ccc.cfg_changed =
|
||||
bt_gatt_ots_oacp_cfg_changed;
|
||||
instance->olcp_ind.ccc.cfg_changed =
|
||||
bt_gatt_ots_olcp_cfg_changed;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
SYS_INIT(bt_gatt_ots_instances_prepare, APPLICATION,
|
||||
CONFIG_KERNEL_INIT_PRIORITY_DEFAULT);
|
84
subsys/bluetooth/services/ots/ots_internal.h
Normal file
84
subsys/bluetooth/services/ots/ots_internal.h
Normal file
|
@ -0,0 +1,84 @@
|
|||
/*
|
||||
* Copyright (c) 2020 Nordic Semiconductor ASA
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#ifndef BT_GATT_OTS_INTERNAL_H_
|
||||
#define BT_GATT_OTS_INTERNAL_H_
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <zephyr/types.h>
|
||||
#include "ots_l2cap_internal.h"
|
||||
#include "ots_oacp_internal.h"
|
||||
#include "ots_olcp_internal.h"
|
||||
|
||||
/**@brief OTS Attribute Protocol Application Error codes. */
|
||||
enum bt_gatt_ots_att_err_codes {
|
||||
/** An attempt was made to write a value that is invalid or
|
||||
* not supported by this Server for a reason other than
|
||||
* the attribute permissions.
|
||||
*/
|
||||
BT_GATT_OTS_WRITE_REQUEST_REJECTED = 0x80,
|
||||
/** An attempt was made to read or write to an Object Metadata
|
||||
* characteristic while the Current Object was an Invalid Object.
|
||||
*/
|
||||
BT_GATT_OTS_OBJECT_NOT_SELECTED = 0x81,
|
||||
/** The Server is unable to service the Read Request or Write Request
|
||||
* because it exceeds the concurrency limit of the service.
|
||||
*/
|
||||
BT_GATT_OTS_CONCURRENCY_LIMIT_EXCEEDED = 0x82,
|
||||
/** The requested object name was rejected because
|
||||
* the name was already in use by an existing object on the Server.
|
||||
*/
|
||||
BT_GATT_OTS_OBJECT_NAME_ALREADY_EXISTS = 0x83,
|
||||
};
|
||||
|
||||
enum bt_gatt_ots_object_state_type {
|
||||
BT_GATT_OTS_OBJECT_IDLE_STATE,
|
||||
|
||||
BT_GATT_OTS_OBJECT_READ_OP_STATE,
|
||||
};
|
||||
|
||||
struct bt_gatt_ots_object_state {
|
||||
enum bt_gatt_ots_object_state_type type;
|
||||
union {
|
||||
struct bt_gatt_ots_object_read_op {
|
||||
struct bt_gatt_ots_oacp_read_params oacp_params;
|
||||
uint32_t sent_len;
|
||||
} read_op;
|
||||
};
|
||||
};
|
||||
|
||||
struct bt_gatt_ots_object {
|
||||
uint64_t id;
|
||||
struct bt_ots_obj_metadata metadata;
|
||||
struct bt_gatt_ots_object_state state;
|
||||
};
|
||||
|
||||
struct bt_gatt_ots_indicate {
|
||||
struct bt_gatt_indicate_params params;
|
||||
struct bt_gatt_attr attr;
|
||||
struct _bt_gatt_ccc ccc;
|
||||
bool is_enabled;
|
||||
};
|
||||
|
||||
struct bt_ots {
|
||||
struct bt_ots_feat features;
|
||||
struct bt_gatt_ots_object *cur_obj;
|
||||
struct bt_gatt_service *service;
|
||||
struct bt_gatt_ots_indicate oacp_ind;
|
||||
struct bt_gatt_ots_indicate olcp_ind;
|
||||
struct bt_gatt_ots_l2cap l2cap;
|
||||
struct bt_ots_cb *cb;
|
||||
void *obj_manager;
|
||||
};
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* BT_GATT_OTS_INTERNAL_H_ */
|
215
subsys/bluetooth/services/ots/ots_l2cap.c
Normal file
215
subsys/bluetooth/services/ots/ots_l2cap.c
Normal file
|
@ -0,0 +1,215 @@
|
|||
/*
|
||||
* 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 <init.h>
|
||||
|
||||
#include <net/buf.h>
|
||||
|
||||
#include "ots_l2cap_internal.h"
|
||||
|
||||
#include <logging/log.h>
|
||||
|
||||
LOG_MODULE_DECLARE(bt_ots, CONFIG_BT_OTS_LOG_LEVEL);
|
||||
|
||||
/* According to BLE specification Assigned Numbers that are used in the
|
||||
* Logical Link Control for protocol/service multiplexers.
|
||||
*/
|
||||
#define BT_GATT_OTS_L2CAP_PSM 0x0025
|
||||
|
||||
/* Maximum size of TX buffer and its payload. */
|
||||
#define MAX_TX_BUF_SIZE 256
|
||||
#define MAX_TX_BUF_PAYLOAD_SIZE (MAX_TX_BUF_SIZE - BT_L2CAP_CHAN_SEND_RESERVE)
|
||||
|
||||
NET_BUF_POOL_FIXED_DEFINE(ot_chan_tx_pool, 1, MAX_TX_BUF_SIZE, NULL);
|
||||
|
||||
/* List of Object Transfer Channels. */
|
||||
static sys_slist_t channels;
|
||||
|
||||
static int ots_l2cap_send(struct bt_gatt_ots_l2cap *l2cap_ctx)
|
||||
{
|
||||
int ret;
|
||||
struct net_buf *buf;
|
||||
uint32_t len;
|
||||
|
||||
/* Calculate maximum length of data chunk. */
|
||||
len = MIN(l2cap_ctx->ot_chan.tx.mtu, MAX_TX_BUF_PAYLOAD_SIZE);
|
||||
len = MIN(len, l2cap_ctx->tx.len - l2cap_ctx->tx.len_sent);
|
||||
|
||||
/* Prepare buffer for sending. */
|
||||
buf = net_buf_alloc(&ot_chan_tx_pool, K_FOREVER);
|
||||
net_buf_reserve(buf, BT_L2CAP_CHAN_SEND_RESERVE);
|
||||
net_buf_add_mem(buf, &l2cap_ctx->tx.data[l2cap_ctx->tx.len_sent], len);
|
||||
|
||||
ret = bt_l2cap_chan_send(&l2cap_ctx->ot_chan.chan, buf);
|
||||
if (ret < 0) {
|
||||
LOG_ERR("Unable to send data over CoC: %d", ret);
|
||||
net_buf_unref(buf);
|
||||
|
||||
return -ENOEXEC;
|
||||
}
|
||||
|
||||
/* Mark that L2CAP TX was accepted. */
|
||||
l2cap_ctx->tx.len_sent += len;
|
||||
|
||||
LOG_DBG("Sending TX chunk with %d bytes on L2CAP CoC", len);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void l2cap_sent(struct bt_l2cap_chan *chan)
|
||||
{
|
||||
struct bt_gatt_ots_l2cap *l2cap_ctx;
|
||||
|
||||
LOG_DBG("Outgoing data channel %p transmitted", chan);
|
||||
|
||||
l2cap_ctx = CONTAINER_OF(chan, struct bt_gatt_ots_l2cap, ot_chan);
|
||||
|
||||
/* Ongoing TX - sending next chunk. */
|
||||
if (l2cap_ctx->tx.len != l2cap_ctx->tx.len_sent) {
|
||||
ots_l2cap_send(l2cap_ctx);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/* TX completed - notify upper layers and clean up. */
|
||||
memset(&l2cap_ctx->tx, 0, sizeof(l2cap_ctx->tx));
|
||||
|
||||
LOG_DBG("Scheduled TX on L2CAP CoC is complete");
|
||||
|
||||
if (l2cap_ctx->tx_done) {
|
||||
l2cap_ctx->tx_done(l2cap_ctx, chan->conn);
|
||||
}
|
||||
}
|
||||
|
||||
static void l2cap_status(struct bt_l2cap_chan *chan, atomic_t *status)
|
||||
{
|
||||
LOG_DBG("Channel %p status %u", chan, *status);
|
||||
}
|
||||
|
||||
static void l2cap_connected(struct bt_l2cap_chan *chan)
|
||||
{
|
||||
LOG_DBG("Channel %p connected", chan);
|
||||
}
|
||||
|
||||
static void l2cap_disconnected(struct bt_l2cap_chan *chan)
|
||||
{
|
||||
LOG_DBG("Channel %p disconnected", chan);
|
||||
}
|
||||
|
||||
static const struct bt_l2cap_chan_ops l2cap_ops = {
|
||||
.sent = l2cap_sent,
|
||||
.status = l2cap_status,
|
||||
.connected = l2cap_connected,
|
||||
.disconnected = l2cap_disconnected,
|
||||
};
|
||||
|
||||
static inline void l2cap_chan_init(struct bt_l2cap_le_chan *chan)
|
||||
{
|
||||
chan->rx.mtu = CONFIG_BT_OTS_L2CAP_CHAN_RX_MTU;
|
||||
chan->chan.ops = &l2cap_ops;
|
||||
|
||||
LOG_DBG("RX MTU set to %u", chan->rx.mtu);
|
||||
}
|
||||
|
||||
static int l2cap_accept(struct bt_conn *conn, struct bt_l2cap_chan **chan)
|
||||
{
|
||||
struct bt_gatt_ots_l2cap *l2cap_ctx;
|
||||
|
||||
LOG_DBG("Incoming conn %p", conn);
|
||||
|
||||
SYS_SLIST_FOR_EACH_CONTAINER(&channels, l2cap_ctx, node) {
|
||||
if (l2cap_ctx->ot_chan.chan.conn) {
|
||||
continue;
|
||||
}
|
||||
|
||||
l2cap_chan_init(&l2cap_ctx->ot_chan);
|
||||
memset(&l2cap_ctx->tx, 0, sizeof(l2cap_ctx->tx));
|
||||
|
||||
*chan = &l2cap_ctx->ot_chan.chan;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
static struct bt_l2cap_server l2cap_server = {
|
||||
.psm = BT_GATT_OTS_L2CAP_PSM,
|
||||
.accept = l2cap_accept,
|
||||
};
|
||||
|
||||
static int bt_gatt_ots_l2cap_init(const struct device *arg)
|
||||
{
|
||||
int err;
|
||||
|
||||
sys_slist_init(&channels);
|
||||
|
||||
err = bt_l2cap_server_register(&l2cap_server);
|
||||
if (err) {
|
||||
LOG_ERR("Unable to register OTS PSM");
|
||||
return err;
|
||||
}
|
||||
|
||||
LOG_DBG("Initialized OTS L2CAP");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool bt_gatt_ots_l2cap_is_open(struct bt_gatt_ots_l2cap *l2cap_ctx,
|
||||
struct bt_conn *conn)
|
||||
{
|
||||
return (l2cap_ctx->ot_chan.chan.conn == conn);
|
||||
}
|
||||
|
||||
int bt_gatt_ots_l2cap_send(struct bt_gatt_ots_l2cap *l2cap_ctx,
|
||||
uint8_t *data, uint32_t len)
|
||||
{
|
||||
int err;
|
||||
|
||||
if (l2cap_ctx->tx.len != 0) {
|
||||
LOG_ERR("L2CAP TX in progress");
|
||||
|
||||
return -EAGAIN;
|
||||
}
|
||||
|
||||
l2cap_ctx->tx.data = data;
|
||||
l2cap_ctx->tx.len = len;
|
||||
|
||||
LOG_DBG("Starting TX on L2CAP CoC with %d byte packet", len);
|
||||
|
||||
err = ots_l2cap_send(l2cap_ctx);
|
||||
if (err) {
|
||||
LOG_ERR("Unable to send data over CoC: %d", err);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int bt_gatt_ots_l2cap_register(struct bt_gatt_ots_l2cap *l2cap_ctx)
|
||||
{
|
||||
sys_slist_append(&channels, &l2cap_ctx->node);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int bt_gatt_ots_l2cap_unregister(struct bt_gatt_ots_l2cap *l2cap_ctx)
|
||||
{
|
||||
sys_slist_find_and_remove(&channels, &l2cap_ctx->node);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
SYS_INIT(bt_gatt_ots_l2cap_init, APPLICATION,
|
||||
CONFIG_APPLICATION_INIT_PRIORITY);
|
53
subsys/bluetooth/services/ots/ots_l2cap_internal.h
Normal file
53
subsys/bluetooth/services/ots/ots_l2cap_internal.h
Normal file
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
* Copyright (c) 2020 Nordic Semiconductor ASA
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#ifndef BT_GATT_OTS_L2CAP_H_
|
||||
#define BT_GATT_OTS_L2CAP_H_
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <zephyr/types.h>
|
||||
#include <sys/slist.h>
|
||||
|
||||
#include <bluetooth/l2cap.h>
|
||||
|
||||
struct bt_gatt_ots_l2cap_tx {
|
||||
uint8_t *data;
|
||||
uint32_t len;
|
||||
uint32_t len_sent;
|
||||
};
|
||||
|
||||
struct bt_gatt_ots_l2cap {
|
||||
sys_snode_t node;
|
||||
struct bt_l2cap_le_chan ot_chan;
|
||||
struct bt_gatt_ots_l2cap_tx tx;
|
||||
void (*tx_done)(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,
|
||||
struct bt_conn *conn);
|
||||
|
||||
int bt_gatt_ots_l2cap_send(struct bt_gatt_ots_l2cap *l2cap_ctx,
|
||||
uint8_t *data,
|
||||
uint32_t len);
|
||||
|
||||
int bt_gatt_ots_l2cap_register(struct bt_gatt_ots_l2cap *l2cap_ctx);
|
||||
|
||||
int bt_gatt_ots_l2cap_unregister(struct bt_gatt_ots_l2cap *l2cap_ctx);
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @}
|
||||
*/
|
||||
|
||||
#endif /* BT_GATT_OTS_L2CAP_H_ */
|
341
subsys/bluetooth/services/ots/ots_oacp.c
Normal file
341
subsys/bluetooth/services/ots/ots_oacp.c
Normal file
|
@ -0,0 +1,341 @@
|
|||
/*
|
||||
* 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 <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;
|
||||
ots->cb->obj_read(ots, conn, ots->cur_obj->id, NULL, 0,
|
||||
offset);
|
||||
return;
|
||||
}
|
||||
|
||||
len = read_op->oacp_params.len - read_op->sent_len;
|
||||
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 (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,
|
||||
const struct bt_gatt_attr *attr,
|
||||
uint8_t err)
|
||||
{
|
||||
struct bt_ots *ots = (struct bt_ots *) 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;
|
||||
}
|
||||
}
|
99
subsys/bluetooth/services/ots/ots_oacp_internal.h
Normal file
99
subsys/bluetooth/services/ots/ots_oacp_internal.h
Normal file
|
@ -0,0 +1,99 @@
|
|||
/*
|
||||
* Copyright (c) 2020 Nordic Semiconductor ASA
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#ifndef BT_GATT_OTS_OACP_H_
|
||||
#define BT_GATT_OTS_OACP_H_
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <zephyr/types.h>
|
||||
#include <bluetooth/gatt.h>
|
||||
#include <bluetooth/services/ots.h>
|
||||
|
||||
/* Types of Object Action Control Point Procedures. */
|
||||
enum bt_gatt_ots_oacp_proc_type {
|
||||
/** Create object.*/
|
||||
BT_GATT_OTS_OACP_PROC_CREATE = 0x01,
|
||||
/** Delete object.*/
|
||||
BT_GATT_OTS_OACP_PROC_DELETE = 0x02,
|
||||
/** Calculate Checksum.*/
|
||||
BT_GATT_OTS_OACP_PROC_CHECKSUM_CALC = 0x03,
|
||||
/** Execute Object.*/
|
||||
BT_GATT_OTS_OACP_PROC_EXECUTE = 0x04,
|
||||
/** Read object.*/
|
||||
BT_GATT_OTS_OACP_PROC_READ = 0x05,
|
||||
/** Write object.*/
|
||||
BT_GATT_OTS_OACP_PROC_WRITE = 0x06,
|
||||
/** Abort object.*/
|
||||
BT_GATT_OTS_OACP_PROC_ABORT = 0x07,
|
||||
/** Procedure response.*/
|
||||
BT_GATT_OTS_OACP_PROC_RESP = 0x60
|
||||
};
|
||||
|
||||
/* Object Action Control Point return codes. */
|
||||
enum bt_gatt_ots_oacp_res_code {
|
||||
/** Success.*/
|
||||
BT_GATT_OTS_OACP_RES_SUCCESS = 0x01,
|
||||
/** Not supported*/
|
||||
BT_GATT_OTS_OACP_RES_OPCODE_NOT_SUP = 0x02,
|
||||
/** Invalid parameter*/
|
||||
BT_GATT_OTS_OACP_RES_INV_PARAM = 0x03,
|
||||
/** Insufficient resources.*/
|
||||
BT_GATT_OTS_OACP_RES_INSUFF_RES = 0x04,
|
||||
/** Invalid object.*/
|
||||
BT_GATT_OTS_OACP_RES_INV_OBJ = 0x05,
|
||||
/** Channel unavailable.*/
|
||||
BT_GATT_OTS_OACP_RES_CHAN_UNAVAIL = 0x06,
|
||||
/** Unsupported procedure.*/
|
||||
BT_GATT_OTS_OACP_RES_UNSUP_TYPE = 0x07,
|
||||
/** Procedure not permitted.*/
|
||||
BT_GATT_OTS_OACP_RES_NOT_PERMITTED = 0x08,
|
||||
/** Object locked.*/
|
||||
BT_GATT_OTS_OACP_RES_OBJ_LOCKED = 0x09,
|
||||
/** Operation Failed.*/
|
||||
BT_GATT_OTS_OACP_RES_OPER_FAILED = 0x0A
|
||||
};
|
||||
|
||||
/* Object Action Control Point procedure definition. */
|
||||
struct bt_gatt_ots_oacp_proc {
|
||||
enum bt_gatt_ots_oacp_proc_type type;
|
||||
union {
|
||||
struct bt_gatt_ots_oacp_create_params {
|
||||
uint32_t size;
|
||||
struct bt_ots_obj_type type;
|
||||
} create_params;
|
||||
struct bt_gatt_ots_oacp_cs_calc_params {
|
||||
uint32_t offset;
|
||||
uint32_t len;
|
||||
} cs_calc_params;
|
||||
struct bt_gatt_ots_oacp_read_params {
|
||||
uint32_t offset;
|
||||
uint32_t len;
|
||||
} read_params;
|
||||
struct bt_gatt_ots_oacp_write_params {
|
||||
uint32_t offset;
|
||||
uint32_t len;
|
||||
uint8_t mode;
|
||||
} write_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);
|
||||
|
||||
void bt_gatt_ots_oacp_cfg_changed(const struct bt_gatt_attr *attr,
|
||||
uint16_t value);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* BT_GATT_OTS_OACP_H_ */
|
218
subsys/bluetooth/services/ots/ots_obj_manager.c
Normal file
218
subsys/bluetooth/services/ots/ots_obj_manager.c
Normal file
|
@ -0,0 +1,218 @@
|
|||
/*
|
||||
* 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/dlist.h>
|
||||
#include <sys/byteorder.h>
|
||||
|
||||
#include <bluetooth/services/ots.h>
|
||||
#include "ots_internal.h"
|
||||
|
||||
#include <logging/log.h>
|
||||
|
||||
LOG_MODULE_DECLARE(bt_ots, CONFIG_BT_OTS_LOG_LEVEL);
|
||||
|
||||
/**Start of the usable range of Object IDs (values 0 to 0x100 are reserved)*/
|
||||
#define OTS_OBJ_ID_START_RANGE 0x000000000100
|
||||
|
||||
#define OTS_OBJ_INDEX_TO_ID(_index) (OTS_OBJ_ID_START_RANGE + (_index))
|
||||
#define OTS_OBJ_ID_TO_INDEX(_id) ((_id) - OTS_OBJ_ID_START_RANGE)
|
||||
|
||||
struct bt_gatt_ots_pool_item {
|
||||
sys_dnode_t dnode;
|
||||
struct bt_gatt_ots_object val;
|
||||
bool is_allocated;
|
||||
};
|
||||
|
||||
struct bt_gatt_ots_obj_manager {
|
||||
sys_dlist_t list;
|
||||
struct bt_gatt_ots_pool_item pool[CONFIG_BT_OTS_MAX_OBJ_CNT];
|
||||
bool is_assigned;
|
||||
};
|
||||
|
||||
int bt_gatt_ots_obj_manager_first_obj_get(
|
||||
struct bt_gatt_ots_obj_manager *obj_manager,
|
||||
struct bt_gatt_ots_object **obj)
|
||||
{
|
||||
sys_dnode_t *obj_dnode;
|
||||
struct bt_gatt_ots_pool_item *first_item;
|
||||
|
||||
if (sys_dlist_is_empty(&obj_manager->list)) {
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
obj_dnode = sys_dlist_peek_head_not_empty(&obj_manager->list);
|
||||
first_item = CONTAINER_OF(obj_dnode, struct bt_gatt_ots_pool_item,
|
||||
dnode);
|
||||
*obj = &first_item->val;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int bt_gatt_ots_obj_manager_last_obj_get(
|
||||
struct bt_gatt_ots_obj_manager *obj_manager,
|
||||
struct bt_gatt_ots_object **obj)
|
||||
{
|
||||
sys_dnode_t *obj_dnode;
|
||||
struct bt_gatt_ots_pool_item *last_item;
|
||||
|
||||
if (sys_dlist_is_empty(&obj_manager->list)) {
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
obj_dnode = sys_dlist_peek_tail(&obj_manager->list);
|
||||
last_item = CONTAINER_OF(obj_dnode, struct bt_gatt_ots_pool_item,
|
||||
dnode);
|
||||
*obj = &last_item->val;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int bt_gatt_ots_obj_manager_prev_obj_get(
|
||||
struct bt_gatt_ots_obj_manager *obj_manager,
|
||||
const struct bt_gatt_ots_object *cur_obj,
|
||||
struct bt_gatt_ots_object **prev_obj)
|
||||
{
|
||||
sys_dnode_t *prev_obj_dnode;
|
||||
struct bt_gatt_ots_pool_item *cur_item, *prev_item;
|
||||
|
||||
if (sys_dlist_is_empty(&obj_manager->list)) {
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
cur_item = CONTAINER_OF(cur_obj, struct bt_gatt_ots_pool_item, val);
|
||||
prev_obj_dnode = sys_dlist_peek_prev_no_check(&obj_manager->list,
|
||||
&cur_item->dnode);
|
||||
if (!prev_obj_dnode) {
|
||||
return -ENFILE;
|
||||
}
|
||||
|
||||
prev_item = CONTAINER_OF(prev_obj_dnode,
|
||||
struct bt_gatt_ots_pool_item,
|
||||
dnode);
|
||||
*prev_obj = &prev_item->val;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int bt_gatt_ots_obj_manager_next_obj_get(
|
||||
struct bt_gatt_ots_obj_manager *obj_manager,
|
||||
const struct bt_gatt_ots_object *cur_obj,
|
||||
struct bt_gatt_ots_object **next_obj)
|
||||
{
|
||||
sys_dnode_t *next_obj_dnode;
|
||||
struct bt_gatt_ots_pool_item *cur_item, *next_item;
|
||||
|
||||
if (sys_dlist_is_empty(&obj_manager->list)) {
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
cur_item = CONTAINER_OF(cur_obj, struct bt_gatt_ots_pool_item, val);
|
||||
next_obj_dnode = sys_dlist_peek_next_no_check(&obj_manager->list,
|
||||
&cur_item->dnode);
|
||||
if (!next_obj_dnode) {
|
||||
return -ENFILE;
|
||||
}
|
||||
|
||||
next_item = CONTAINER_OF(next_obj_dnode,
|
||||
struct bt_gatt_ots_pool_item,
|
||||
dnode);
|
||||
*next_obj = &next_item->val;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int bt_gatt_ots_obj_manager_obj_get(
|
||||
struct bt_gatt_ots_obj_manager *obj_manager, uint64_t id,
|
||||
struct bt_gatt_ots_object **object)
|
||||
{
|
||||
uint64_t i = OTS_OBJ_ID_TO_INDEX(id);
|
||||
|
||||
if (sys_dlist_is_empty(&obj_manager->list)) {
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
if (id < OTS_OBJ_ID_START_RANGE) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (i >= ARRAY_SIZE(obj_manager->pool)) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (!obj_manager->pool[i].is_allocated) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
*object = &obj_manager->pool[i].val;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int bt_gatt_ots_obj_manager_obj_add(
|
||||
struct bt_gatt_ots_obj_manager *obj_manager,
|
||||
struct bt_gatt_ots_object **object)
|
||||
{
|
||||
for (uint64_t i = 0; i < ARRAY_SIZE(obj_manager->pool); i++) {
|
||||
struct bt_gatt_ots_pool_item *cur_obj =
|
||||
&obj_manager->pool[i];
|
||||
|
||||
if (!cur_obj->is_allocated) {
|
||||
cur_obj->is_allocated = true;
|
||||
cur_obj->val.id = OTS_OBJ_INDEX_TO_ID(i);
|
||||
sys_dlist_append(&obj_manager->list, &cur_obj->dnode);
|
||||
|
||||
*object = &cur_obj->val;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
int bt_gatt_ots_obj_manager_obj_delete(struct bt_gatt_ots_object *obj)
|
||||
{
|
||||
struct bt_gatt_ots_pool_item *item;
|
||||
|
||||
item = CONTAINER_OF(obj, struct bt_gatt_ots_pool_item, val);
|
||||
|
||||
if (!item->is_allocated) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
item->is_allocated = false;
|
||||
sys_dlist_remove(&item->dnode);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void *bt_gatt_ots_obj_manager_assign(void)
|
||||
{
|
||||
static struct bt_gatt_ots_obj_manager
|
||||
obj_manager[CONFIG_BT_OTS_MAX_INST_CNT];
|
||||
struct bt_gatt_ots_obj_manager *cur_manager;
|
||||
|
||||
|
||||
for (cur_manager = obj_manager;
|
||||
cur_manager != obj_manager + CONFIG_BT_OTS_MAX_INST_CNT;
|
||||
cur_manager++) {
|
||||
if (!cur_manager->is_assigned) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (cur_manager->is_assigned) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
cur_manager->is_assigned = true;
|
||||
sys_dlist_init(&cur_manager->list);
|
||||
|
||||
return cur_manager;
|
||||
}
|
47
subsys/bluetooth/services/ots/ots_obj_manager_internal.h
Normal file
47
subsys/bluetooth/services/ots/ots_obj_manager_internal.h
Normal file
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
* Copyright (c) 2020 Nordic Semiconductor ASA
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#ifndef BT_GATT_OTS_OBJ_MANAGER_H_
|
||||
#define BT_GATT_OTS_OBJ_MANAGER_H_
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <zephyr/types.h>
|
||||
|
||||
int bt_gatt_ots_obj_manager_first_obj_get(void *obj_manager,
|
||||
struct bt_gatt_ots_object **obj);
|
||||
|
||||
int bt_gatt_ots_obj_manager_last_obj_get(void *obj_manager,
|
||||
struct bt_gatt_ots_object **obj);
|
||||
|
||||
int bt_gatt_ots_obj_manager_prev_obj_get(
|
||||
void *obj_manager,
|
||||
const struct bt_gatt_ots_object *cur_obj,
|
||||
struct bt_gatt_ots_object **prev_obj);
|
||||
|
||||
int bt_gatt_ots_obj_manager_next_obj_get(
|
||||
void *obj_manager,
|
||||
const struct bt_gatt_ots_object *cur_obj,
|
||||
struct bt_gatt_ots_object **next_obj);
|
||||
|
||||
int bt_gatt_ots_obj_manager_obj_get(void *obj_manager,
|
||||
uint64_t id,
|
||||
struct bt_gatt_ots_object **obj);
|
||||
|
||||
int bt_gatt_ots_obj_manager_obj_add(void *obj_manager,
|
||||
struct bt_gatt_ots_object **obj);
|
||||
|
||||
int bt_gatt_ots_obj_manager_obj_delete(struct bt_gatt_ots_object *obj);
|
||||
|
||||
void *bt_gatt_ots_obj_manager_assign(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* BT_GATT_OTS_OBJ_MANAGER_H_ */
|
308
subsys/bluetooth/services/ots/ots_olcp.c
Normal file
308
subsys/bluetooth/services/ots/ots_olcp.c
Normal file
|
@ -0,0 +1,308 @@
|
|||
/*
|
||||
* 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 <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 enum bt_gatt_ots_olcp_res_code olcp_command_decode(
|
||||
const uint8_t *buf, struct bt_gatt_ots_olcp_proc *proc)
|
||||
{
|
||||
memset(proc, 0, sizeof(*proc));
|
||||
|
||||
proc->type = *buf++;
|
||||
|
||||
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:
|
||||
break;
|
||||
case BT_GATT_OTS_OLCP_PROC_GOTO:
|
||||
proc->goto_params.id = sys_get_le48(buf);
|
||||
break;
|
||||
default:
|
||||
LOG_WRN("OLCP unsupported procedure type");
|
||||
return BT_GATT_OTS_OLCP_RES_PROC_NOT_SUP;
|
||||
}
|
||||
|
||||
return BT_GATT_OTS_OLCP_RES_SUCCESS;
|
||||
}
|
||||
|
||||
static bool olcp_command_len_verify(enum bt_gatt_ots_olcp_proc_type type,
|
||||
uint16_t len)
|
||||
{
|
||||
uint16_t ref_len = OLCP_PROC_TYPE_SIZE;
|
||||
|
||||
switch (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:
|
||||
case BT_GATT_OTS_OLCP_PROC_REQ_NUM_OBJS:
|
||||
case BT_GATT_OTS_OLCP_PROC_CLEAR_MARKING:
|
||||
break;
|
||||
case BT_GATT_OTS_OLCP_PROC_GOTO:
|
||||
ref_len += BT_OTS_OBJ_ID_SIZE;
|
||||
break;
|
||||
case BT_GATT_OTS_OLCP_PROC_ORDER:
|
||||
ref_len += sizeof(uint8_t);
|
||||
break;
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
|
||||
return (len == ref_len);
|
||||
}
|
||||
|
||||
static void olcp_ind_cb(struct bt_conn *conn,
|
||||
const struct bt_gatt_attr *attr,
|
||||
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;
|
||||
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);
|
||||
}
|
||||
|
||||
olcp_status = olcp_command_decode(buf, &olcp_proc);
|
||||
|
||||
if (!olcp_command_len_verify(olcp_proc.type, len)) {
|
||||
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);
|
||||
}
|
||||
|
||||
old_obj = ots->cur_obj;
|
||||
if (olcp_status == BT_GATT_OTS_OLCP_RES_SUCCESS) {
|
||||
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 (ots->cb->obj_selected) {
|
||||
ots->cb->obj_selected(ots, conn, ots->cur_obj->id);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
95
subsys/bluetooth/services/ots/ots_olcp_internal.h
Normal file
95
subsys/bluetooth/services/ots/ots_olcp_internal.h
Normal file
|
@ -0,0 +1,95 @@
|
|||
/*
|
||||
* Copyright (c) 2020 Nordic Semiconductor ASA
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#ifndef BT_GATT_OTS_OLCP_H_
|
||||
#define BT_GATT_OTS_OLCP_H_
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <zephyr/types.h>
|
||||
#include <bluetooth/gatt.h>
|
||||
|
||||
/* The types of OLCP procedures. */
|
||||
enum bt_gatt_ots_olcp_proc_type {
|
||||
/* Select the first object.*/
|
||||
BT_GATT_OTS_OLCP_PROC_FIRST = 0x01,
|
||||
/* Select the last object.*/
|
||||
BT_GATT_OTS_OLCP_PROC_LAST = 0x02,
|
||||
/* Select the previous object.*/
|
||||
BT_GATT_OTS_OLCP_PROC_PREV = 0x03,
|
||||
/* Select the next object.*/
|
||||
BT_GATT_OTS_OLCP_PROC_NEXT = 0x04,
|
||||
/* Select the object with the given object ID.*/
|
||||
BT_GATT_OTS_OLCP_PROC_GOTO = 0x05,
|
||||
/* Order the objects.*/
|
||||
BT_GATT_OTS_OLCP_PROC_ORDER = 0x06,
|
||||
/* Request the number of objects.*/
|
||||
BT_GATT_OTS_OLCP_PROC_REQ_NUM_OBJS = 0x07,
|
||||
/* Clear Marking.*/
|
||||
BT_GATT_OTS_OLCP_PROC_CLEAR_MARKING = 0x08,
|
||||
/* Response.*/
|
||||
BT_GATT_OTS_OLCP_PROC_RESP = 0x70,
|
||||
};
|
||||
|
||||
/* Definition of a OLCP procedure. */
|
||||
struct bt_gatt_ots_olcp_proc {
|
||||
enum bt_gatt_ots_olcp_proc_type type;
|
||||
union {
|
||||
struct {
|
||||
uint64_t id;
|
||||
} goto_params;
|
||||
};
|
||||
};
|
||||
|
||||
/* The return codes obtained from doing OLCP procedures. */
|
||||
enum bt_gatt_ots_olcp_res_code {
|
||||
/* Response for successful operation. */
|
||||
BT_GATT_OTS_OLCP_RES_SUCCESS = 0x01,
|
||||
/* Response if unsupported Op Code is received.*/
|
||||
BT_GATT_OTS_OLCP_RES_PROC_NOT_SUP = 0x02,
|
||||
/* Response if Parameter received does not meet
|
||||
* the requirements of the service.
|
||||
*/
|
||||
BT_GATT_OTS_OLCP_RES_INVALID_PARAMETER = 0x03,
|
||||
/* Response if the requested procedure failed for a reason
|
||||
* other than those enumerated below.
|
||||
*/
|
||||
BT_GATT_OTS_OLCP_RES_OPERATION_FAILED = 0x04,
|
||||
/* Response if the requested procedure attempted to select an object
|
||||
* beyond the first object or
|
||||
* beyond the last object in the current list.
|
||||
*/
|
||||
BT_GATT_OTS_OLCP_RES_OUT_OF_BONDS = 0x05,
|
||||
/* Response if the requested procedure failed due
|
||||
* to too many objects in the current list.
|
||||
*/
|
||||
BT_GATT_OTS_OLCP_RES_TOO_MANY_OBJECTS = 0x06,
|
||||
/* Response if the requested procedure failed due
|
||||
* to there being zero objects in the current list.
|
||||
*/
|
||||
BT_GATT_OTS_OLCP_RES_NO_OBJECT = 0x07,
|
||||
/* Response if the requested procedure failed due
|
||||
* to there being no object with the requested Object ID.
|
||||
*/
|
||||
BT_GATT_OTS_OLCP_RES_OBJECT_ID_NOT_FOUND = 0x08,
|
||||
};
|
||||
|
||||
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);
|
||||
|
||||
void bt_gatt_ots_olcp_cfg_changed(const struct bt_gatt_attr *attr,
|
||||
uint16_t value);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* BT_GATT_OTS_OLCP_H_ */
|
Loading…
Add table
Add a link
Reference in a new issue