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:
Kamil Piszczek 2020-07-23 04:25:47 -04:00 committed by Carles Cufí
commit 0a7a8fd137
15 changed files with 2548 additions and 0 deletions

View 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_ */

View file

@ -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)

View file

@ -12,4 +12,6 @@ source "subsys/bluetooth/services/Kconfig.bas"
source "subsys/bluetooth/services/Kconfig.hrs"
source "subsys/bluetooth/services/ots/Kconfig"
endmenu

View 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)

View 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

View 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);

View 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_ */

View 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);

View 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_ */

View 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;
}
}

View 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_ */

View 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;
}

View 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_ */

View 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;
}
}

View 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_ */