blob: f3b70b3f849788f8c4a879012e6f6fec106ef4cc [file] [log] [blame]
/* Bluetooth CSIP - Coordinated Set Identification Profile */
/*
* Copyright (c) 2019 Bose Corporation
* Copyright (c) 2020-2022 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/kernel.h>
#include <zephyr/types.h>
#include <zephyr/device.h>
#include <zephyr/init.h>
#include <stdlib.h>
#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/conn.h>
#include <zephyr/bluetooth/gatt.h>
#include <zephyr/bluetooth/buf.h>
#include <zephyr/sys/byteorder.h>
#include <zephyr/sys/check.h>
#include "audio_internal.h"
#include "csip_internal.h"
#include "csip_crypto.h"
#include "../host/conn_internal.h"
#include "../host/hci_core.h"
#include "../host/keys.h"
#define BT_CSIP_SIH_PRAND_SIZE 3
#define BT_CSIP_SIH_HASH_SIZE 3
#define CSIP_SET_LOCK_TIMER_VALUE K_SECONDS(60)
#define BT_DBG_ENABLED IS_ENABLED(CONFIG_BT_DEBUG_CSIP_SET_MEMBER)
#define LOG_MODULE_NAME bt_csip_set_member
#include "common/log.h"
#include "common/bt_str.h"
struct bt_csip_set_member_svc_inst {
struct bt_csip_set_sirk set_sirk;
uint8_t set_size;
uint8_t set_lock;
uint8_t rank;
struct bt_csip_set_member_cb *cb;
struct k_work_delayable set_lock_timer;
bt_addr_le_t lock_client_addr;
struct bt_gatt_service *service_p;
struct csip_pending_notifications pend_notify[CONFIG_BT_MAX_PAIRED];
#if IS_ENABLED(CONFIG_BT_KEYS_OVERWRITE_OLDEST)
uint32_t age_counter;
#endif /* CONFIG_BT_KEYS_OVERWRITE_OLDEST */
};
static struct bt_csip_set_member_svc_inst svc_insts[CONFIG_BT_CSIP_SET_MEMBER_MAX_INSTANCE_COUNT];
static bt_addr_le_t server_dummy_addr; /* 0'ed address */
struct csip_notify_foreach {
struct bt_conn *excluded_client;
struct bt_csip_set_member_svc_inst *svc_inst;
};
static bool is_last_client_to_write(const struct bt_csip_set_member_svc_inst *svc_inst,
const struct bt_conn *conn)
{
if (conn != NULL) {
return bt_addr_le_eq(bt_conn_get_dst(conn),
&svc_inst->lock_client_addr);
} else {
return bt_addr_le_eq(&server_dummy_addr,
&svc_inst->lock_client_addr);
}
}
static void notify_lock_value(const struct bt_csip_set_member_svc_inst *svc_inst,
struct bt_conn *conn)
{
bt_gatt_notify_uuid(conn, BT_UUID_CSIS_SET_LOCK,
svc_inst->service_p->attrs,
&svc_inst->set_lock,
sizeof(svc_inst->set_lock));
}
static void notify_client(struct bt_conn *conn, void *data)
{
struct csip_notify_foreach *csip_data = (struct csip_notify_foreach *)data;
struct bt_csip_set_member_svc_inst *svc_inst = csip_data->svc_inst;
struct bt_conn *excluded_conn = csip_data->excluded_client;
if (excluded_conn != NULL && conn == excluded_conn) {
return;
}
notify_lock_value(svc_inst, conn);
for (int i = 0; i < ARRAY_SIZE(svc_inst->pend_notify); i++) {
struct csip_pending_notifications *pend_notify;
pend_notify = &svc_inst->pend_notify[i];
if (pend_notify->pending &&
bt_addr_le_eq(bt_conn_get_dst(conn), &pend_notify->addr)) {
pend_notify->pending = false;
break;
}
}
}
static void notify_clients(struct bt_csip_set_member_svc_inst *svc_inst,
struct bt_conn *excluded_client)
{
struct csip_notify_foreach data = {
.excluded_client = excluded_client,
.svc_inst = svc_inst,
};
/* Mark all bonded devices as pending notifications, and clear those
* that are notified in `notify_client`
*/
for (int i = 0; i < ARRAY_SIZE(svc_inst->pend_notify); i++) {
struct csip_pending_notifications *pend_notify;
pend_notify = &svc_inst->pend_notify[i];
if (pend_notify->active) {
if (excluded_client != NULL &&
bt_addr_le_eq(bt_conn_get_dst(excluded_client), &pend_notify->addr)) {
continue;
}
pend_notify->pending = true;
}
}
bt_conn_foreach(BT_CONN_TYPE_LE, notify_client, &data);
}
static int sirk_encrypt(struct bt_conn *conn,
const struct bt_csip_set_sirk *sirk,
struct bt_csip_set_sirk *enc_sirk)
{
int err;
uint8_t *k;
if (IS_ENABLED(CONFIG_BT_CSIP_SET_MEMBER_TEST_SAMPLE_DATA)) {
/* test_k is from the sample data from A.2 in the CSIS spec */
static uint8_t test_k[] = {0x67, 0x6e, 0x1b, 0x9b,
0xd4, 0x48, 0x69, 0x6f,
0x06, 0x1e, 0xc6, 0x22,
0x3c, 0xe5, 0xce, 0xd9};
static bool swapped;
if (!swapped && IS_ENABLED(CONFIG_LITTLE_ENDIAN)) {
/* Swap test_k to little endian */
sys_mem_swap(test_k, 16);
swapped = true;
}
BT_DBG("Encrypting test SIRK");
k = test_k;
} else {
k = conn->le.keys->ltk.val;
}
err = bt_csip_sef(k, sirk->value, enc_sirk->value);
if (err != 0) {
return err;
}
enc_sirk->type = BT_CSIP_SIRK_TYPE_ENCRYPTED;
return 0;
}
static int generate_prand(uint32_t *dest)
{
bool valid = false;
do {
int res;
*dest = 0;
res = bt_rand(dest, BT_CSIP_SIH_PRAND_SIZE);
if (res != 0) {
return res;
}
/* Validate Prand: Must contain both a 1 and a 0 */
if (*dest != 0 && *dest != 0x3FFFFF) {
valid = true;
}
} while (!valid);
*dest &= 0x3FFFFF;
*dest |= BIT(22); /* bit 23 shall be 0, and bit 22 shall be 1 */
return 0;
}
int bt_csip_set_member_generate_rsi(const struct bt_csip_set_member_svc_inst *svc_inst,
uint8_t rsi[BT_CSIP_RSI_SIZE])
{
int res = 0;
uint32_t prand;
uint32_t hash;
if (IS_ENABLED(CONFIG_BT_CSIP_SET_MEMBER_TEST_SAMPLE_DATA)) {
/* prand is from the sample data from A.2 in the CSIS spec */
prand = 0x69f563;
} else {
res = generate_prand(&prand);
if (res != 0) {
BT_WARN("Could not generate new prand");
return res;
}
}
res = bt_csip_sih(svc_inst->set_sirk.value, prand, &hash);
if (res != 0) {
BT_WARN("Could not generate new RSI");
return res;
}
(void)memcpy(rsi, &hash, BT_CSIP_SIH_HASH_SIZE);
(void)memcpy(rsi + BT_CSIP_SIH_HASH_SIZE, &prand, BT_CSIP_SIH_PRAND_SIZE);
return res;
}
static ssize_t read_set_sirk(struct bt_conn *conn,
const struct bt_gatt_attr *attr,
void *buf, uint16_t len, uint16_t offset)
{
struct bt_csip_set_sirk enc_sirk;
struct bt_csip_set_sirk *sirk;
struct bt_csip_set_member_svc_inst *svc_inst = BT_AUDIO_CHRC_USER_DATA(attr);
if (svc_inst->cb != NULL && svc_inst->cb->sirk_read_req != NULL) {
ssize_t gatt_err = BT_GATT_ERR(BT_ATT_ERR_SUCCESS);
uint8_t cb_rsp;
/* Ask higher layer for what SIRK to return, if any */
cb_rsp = svc_inst->cb->sirk_read_req(conn, &svc_insts[0]);
if (cb_rsp == BT_CSIP_READ_SIRK_REQ_RSP_ACCEPT) {
sirk = &svc_inst->set_sirk;
} else if (IS_ENABLED(CONFIG_BT_CSIP_SET_MEMBER_ENC_SIRK_SUPPORT) &&
cb_rsp == BT_CSIP_READ_SIRK_REQ_RSP_ACCEPT_ENC) {
int err;
err = sirk_encrypt(conn, &svc_inst->set_sirk,
&enc_sirk);
if (err != 0) {
BT_ERR("Could not encrypt SIRK: %d",
err);
gatt_err = BT_GATT_ERR(BT_ATT_ERR_UNLIKELY);
} else {
sirk = &enc_sirk;
LOG_HEXDUMP_DBG(enc_sirk.value,
sizeof(enc_sirk.value),
"Encrypted Set SIRK");
}
} else if (cb_rsp == BT_CSIP_READ_SIRK_REQ_RSP_REJECT) {
gatt_err = BT_GATT_ERR(BT_ATT_ERR_AUTHORIZATION);
} else if (cb_rsp == BT_CSIP_READ_SIRK_REQ_RSP_OOB_ONLY) {
gatt_err = BT_GATT_ERR(BT_CSIP_ERROR_SIRK_OOB_ONLY);
} else {
BT_ERR("Invalid callback response: %u", cb_rsp);
gatt_err = BT_GATT_ERR(BT_ATT_ERR_UNLIKELY);
}
if (gatt_err != BT_GATT_ERR(BT_ATT_ERR_SUCCESS)) {
return gatt_err;
}
} else {
sirk = &svc_inst->set_sirk;
}
BT_DBG("Set sirk %sencrypted",
sirk->type == BT_CSIP_SIRK_TYPE_PLAIN ? "not " : "");
LOG_HEXDUMP_DBG(svc_inst->set_sirk.value,
sizeof(svc_inst->set_sirk.value), "Set SIRK");
return bt_gatt_attr_read(conn, attr, buf, len, offset,
sirk, sizeof(*sirk));
}
static void set_sirk_cfg_changed(const struct bt_gatt_attr *attr,
uint16_t value)
{
BT_DBG("value 0x%04x", value);
}
static ssize_t read_set_size(struct bt_conn *conn,
const struct bt_gatt_attr *attr,
void *buf, uint16_t len, uint16_t offset)
{
struct bt_csip_set_member_svc_inst *svc_inst = BT_AUDIO_CHRC_USER_DATA(attr);
BT_DBG("%u", svc_inst->set_size);
return bt_gatt_attr_read(conn, attr, buf, len, offset,
&svc_inst->set_size,
sizeof(svc_inst->set_size));
}
static void set_size_cfg_changed(const struct bt_gatt_attr *attr,
uint16_t value)
{
BT_DBG("value 0x%04x", value);
}
static ssize_t read_set_lock(struct bt_conn *conn,
const struct bt_gatt_attr *attr,
void *buf, uint16_t len, uint16_t offset)
{
struct bt_csip_set_member_svc_inst *svc_inst = BT_AUDIO_CHRC_USER_DATA(attr);
BT_DBG("%u", svc_inst->set_lock);
return bt_gatt_attr_read(conn, attr, buf, len, offset,
&svc_inst->set_lock,
sizeof(svc_inst->set_lock));
}
/**
* @brief Set the lock value of a CSIP instance.
*
* @param conn The connection locking the instance.
* Will be NULL if the server locally sets the lock.
* @param svc_inst The CSIP instance to change the lock value of
* @param val The lock value (BT_CSIP_LOCK_VALUE or BT_CSIP_RELEASE_VALUE)
*
* @return BT_CSIP_ERROR_* on failure or 0 if success
*/
static uint8_t set_lock(struct bt_conn *conn,
struct bt_csip_set_member_svc_inst *svc_inst,
uint8_t val)
{
bool notify;
if (val != BT_CSIP_RELEASE_VALUE && val != BT_CSIP_LOCK_VALUE) {
return BT_CSIP_ERROR_LOCK_INVAL_VALUE;
}
if (svc_inst->set_lock == BT_CSIP_LOCK_VALUE) {
if (val == BT_CSIP_LOCK_VALUE) {
if (is_last_client_to_write(svc_inst, conn)) {
return BT_CSIP_ERROR_LOCK_ALREADY_GRANTED;
} else {
return BT_CSIP_ERROR_LOCK_DENIED;
}
} else if (!is_last_client_to_write(svc_inst, conn)) {
return BT_CSIP_ERROR_LOCK_RELEASE_DENIED;
}
}
notify = svc_inst->set_lock != val;
svc_inst->set_lock = val;
if (svc_inst->set_lock == BT_CSIP_LOCK_VALUE) {
if (conn != NULL) {
bt_addr_le_copy(&svc_inst->lock_client_addr,
bt_conn_get_dst(conn));
}
(void)k_work_reschedule(&svc_inst->set_lock_timer,
CSIP_SET_LOCK_TIMER_VALUE);
} else {
(void)memset(&svc_inst->lock_client_addr, 0,
sizeof(svc_inst->lock_client_addr));
(void)k_work_cancel_delayable(&svc_inst->set_lock_timer);
}
BT_DBG("%u", svc_inst->set_lock);
if (notify) {
/*
* The Spec states that all clients, except for the
* client writing the value, shall be notified
* (if subscribed)
*/
notify_clients(svc_inst, conn);
if (svc_inst->cb != NULL && svc_inst->cb->lock_changed != NULL) {
bool locked = svc_inst->set_lock == BT_CSIP_LOCK_VALUE;
svc_inst->cb->lock_changed(conn, svc_inst, locked);
}
}
return 0;
}
static ssize_t write_set_lock(struct bt_conn *conn,
const struct bt_gatt_attr *attr,
const void *buf, uint16_t len,
uint16_t offset, uint8_t flags)
{
ssize_t res;
uint8_t val;
struct bt_csip_set_member_svc_inst *svc_inst = BT_AUDIO_CHRC_USER_DATA(attr);
if (offset != 0) {
return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET);
} else if (len != sizeof(val)) {
return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN);
}
(void)memcpy(&val, buf, len);
res = set_lock(conn, svc_inst, val);
if (res != BT_ATT_ERR_SUCCESS) {
return BT_GATT_ERR(res);
}
return len;
}
static void set_lock_cfg_changed(const struct bt_gatt_attr *attr,
uint16_t value)
{
BT_DBG("value 0x%04x", value);
}
static ssize_t read_rank(struct bt_conn *conn, const struct bt_gatt_attr *attr,
void *buf, uint16_t len, uint16_t offset)
{
struct bt_csip_set_member_svc_inst *svc_inst = BT_AUDIO_CHRC_USER_DATA(attr);
BT_DBG("%u", svc_inst->rank);
return bt_gatt_attr_read(conn, attr, buf, len, offset,
&svc_inst->rank,
sizeof(svc_inst->rank));
}
static void set_lock_timer_handler(struct k_work *work)
{
struct k_work_delayable *delayable;
struct bt_csip_set_member_svc_inst *svc_inst;
delayable = CONTAINER_OF(work, struct k_work_delayable, work);
svc_inst = CONTAINER_OF(delayable, struct bt_csip_set_member_svc_inst,
set_lock_timer);
BT_DBG("Lock timeout, releasing");
svc_inst->set_lock = BT_CSIP_RELEASE_VALUE;
notify_clients(svc_inst, NULL);
if (svc_inst->cb != NULL && svc_inst->cb->lock_changed != NULL) {
bool locked = svc_inst->set_lock == BT_CSIP_LOCK_VALUE;
svc_inst->cb->lock_changed(NULL, svc_inst, locked);
}
}
static void csip_security_changed(struct bt_conn *conn, bt_security_t level,
enum bt_security_err err)
{
if (err != 0 || conn->encrypt == 0) {
return;
}
if (!bt_addr_le_is_bonded(conn->id, &conn->le.dst)) {
return;
}
for (int i = 0; i < ARRAY_SIZE(svc_insts); i++) {
struct bt_csip_set_member_svc_inst *svc_inst = &svc_insts[i];
for (int j = 0; j < ARRAY_SIZE(svc_inst->pend_notify); j++) {
struct csip_pending_notifications *pend_notify;
pend_notify = &svc_inst->pend_notify[j];
if (pend_notify->pending &&
bt_addr_le_eq(bt_conn_get_dst(conn), &pend_notify->addr)) {
notify_lock_value(svc_inst, conn);
pend_notify->pending = false;
break;
}
}
}
}
static void handle_csip_disconnect(struct bt_csip_set_member_svc_inst *svc_inst,
struct bt_conn *conn)
{
BT_DBG("Non-bonded device");
if (is_last_client_to_write(svc_inst, conn)) {
(void)memset(&svc_inst->lock_client_addr, 0,
sizeof(svc_inst->lock_client_addr));
svc_inst->set_lock = BT_CSIP_RELEASE_VALUE;
notify_clients(svc_inst, NULL);
if (svc_inst->cb != NULL && svc_inst->cb->lock_changed != NULL) {
bool locked = svc_inst->set_lock == BT_CSIP_LOCK_VALUE;
svc_inst->cb->lock_changed(conn, svc_inst, locked);
}
}
/* Check if the disconnected device once was bonded and stored
* here as a bonded device
*/
for (int i = 0; i < ARRAY_SIZE(svc_inst->pend_notify); i++) {
struct csip_pending_notifications *pend_notify;
pend_notify = &svc_inst->pend_notify[i];
if (bt_addr_le_eq(bt_conn_get_dst(conn), &pend_notify->addr)) {
(void)memset(pend_notify, 0, sizeof(*pend_notify));
break;
}
}
}
static void csip_disconnected(struct bt_conn *conn, uint8_t reason)
{
BT_DBG("Disconnected: %s (reason %u)",
bt_addr_le_str(bt_conn_get_dst(conn)), reason);
for (int i = 0; i < ARRAY_SIZE(svc_insts); i++) {
handle_csip_disconnect(&svc_insts[i], conn);
}
}
static void handle_csip_auth_complete(struct bt_csip_set_member_svc_inst *svc_inst,
struct bt_conn *conn)
{
/* Check if already in list, and do nothing if it is */
for (int i = 0; i < ARRAY_SIZE(svc_inst->pend_notify); i++) {
struct csip_pending_notifications *pend_notify;
pend_notify = &svc_inst->pend_notify[i];
if (pend_notify->active &&
bt_addr_le_eq(bt_conn_get_dst(conn), &pend_notify->addr)) {
#if IS_ENABLED(CONFIG_BT_KEYS_OVERWRITE_OLDEST)
pend_notify->age = svc_inst->age_counter++;
#endif /* CONFIG_BT_KEYS_OVERWRITE_OLDEST */
return;
}
}
/* Copy addr to list over devices to save notifications for */
for (int i = 0; i < ARRAY_SIZE(svc_inst->pend_notify); i++) {
struct csip_pending_notifications *pend_notify;
pend_notify = &svc_inst->pend_notify[i];
if (!pend_notify->active) {
bt_addr_le_copy(&pend_notify->addr,
bt_conn_get_dst(conn));
pend_notify->active = true;
#if IS_ENABLED(CONFIG_BT_KEYS_OVERWRITE_OLDEST)
pend_notify->age = svc_inst->age_counter++;
#endif /* CONFIG_BT_KEYS_OVERWRITE_OLDEST */
return;
}
}
#if IS_ENABLED(CONFIG_BT_KEYS_OVERWRITE_OLDEST)
struct csip_pending_notifications *oldest;
oldest = &svc_inst->pend_notify[0];
for (int i = 1; i < ARRAY_SIZE(svc_inst->pend_notify); i++) {
struct csip_pending_notifications *pend_notify;
pend_notify = &svc_inst->pend_notify[i];
if (pend_notify->age < oldest->age) {
oldest = pend_notify;
}
}
(void)memset(oldest, 0, sizeof(*oldest));
bt_addr_le_copy(&oldest->addr, &conn->le.dst);
oldest->active = true;
oldest->age = svc_inst->age_counter++;
#else
BT_WARN("Could not add device to pending notification list");
#endif /* CONFIG_BT_KEYS_OVERWRITE_OLDEST */
}
static void auth_pairing_complete(struct bt_conn *conn, bool bonded)
{
/**
* If a pairing is complete for a bonded device, then we
* 1) Store the connection pointer to later validate SIRK encryption
* 2) Check if the device is already in the `pend_notify`, and if it is
* not, then we
* 3) Check if there's room for another device in the `pend_notify`
* array. If there are no more room for a new device, then
* 4) Either we ignore this new device (bad luck), or we overwrite
* the oldest entry, following the behavior of the key storage.
*/
BT_DBG("%s paired (%sbonded)",
bt_addr_le_str(bt_conn_get_dst(conn)), bonded ? "" : "not ");
if (!bonded) {
return;
}
for (int i = 0; i < ARRAY_SIZE(svc_insts); i++) {
handle_csip_auth_complete(&svc_insts[i], conn);
}
}
static void csip_bond_deleted(uint8_t id, const bt_addr_le_t *peer)
{
for (int i = 0; i < ARRAY_SIZE(svc_insts); i++) {
struct bt_csip_set_member_svc_inst *svc_inst = &svc_insts[i];
for (int j = 0; j < ARRAY_SIZE(svc_inst->pend_notify); j++) {
struct csip_pending_notifications *pend_notify;
pend_notify = &svc_inst->pend_notify[j];
if (pend_notify->active &&
bt_addr_le_eq(peer, &pend_notify->addr)) {
(void)memset(pend_notify, 0,
sizeof(*pend_notify));
break;
}
}
}
}
static struct bt_conn_cb conn_callbacks = {
.disconnected = csip_disconnected,
.security_changed = csip_security_changed,
};
static struct bt_conn_auth_info_cb auth_callbacks = {
.pairing_complete = auth_pairing_complete,
.bond_deleted = csip_bond_deleted
};
#define BT_CSIP_SERVICE_DEFINITION(_csip) {\
BT_GATT_PRIMARY_SERVICE(BT_UUID_CSIS), \
BT_AUDIO_CHRC(BT_UUID_CSIS_SET_SIRK, \
BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY, \
BT_GATT_PERM_READ_ENCRYPT, \
read_set_sirk, NULL, &_csip), \
BT_AUDIO_CCC(set_sirk_cfg_changed), \
BT_AUDIO_CHRC(BT_UUID_CSIS_SET_SIZE, \
BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY, \
BT_GATT_PERM_READ_ENCRYPT, \
read_set_size, NULL, &_csip), \
BT_AUDIO_CCC(set_size_cfg_changed), \
BT_AUDIO_CHRC(BT_UUID_CSIS_SET_LOCK, \
BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY | BT_GATT_CHRC_WRITE, \
BT_GATT_PERM_READ_ENCRYPT | BT_GATT_PERM_WRITE_ENCRYPT, \
read_set_lock, write_set_lock, &_csip), \
BT_AUDIO_CCC(set_lock_cfg_changed), \
BT_AUDIO_CHRC(BT_UUID_CSIS_RANK, \
BT_GATT_CHRC_READ, \
BT_GATT_PERM_READ_ENCRYPT, \
read_rank, NULL, &_csip) \
}
BT_GATT_SERVICE_INSTANCE_DEFINE(csip_set_member_service_list, svc_insts,
CONFIG_BT_CSIP_SET_MEMBER_MAX_INSTANCE_COUNT,
BT_CSIP_SERVICE_DEFINITION);
/****************************** Public API ******************************/
void *bt_csip_set_member_svc_decl_get(const struct bt_csip_set_member_svc_inst *svc_inst)
{
return svc_inst->service_p->attrs;
}
static bool valid_register_param(const struct bt_csip_set_member_register_param *param)
{
if (param->lockable && param->rank == 0) {
BT_DBG("Rank cannot be 0 if service is lockable");
return false;
}
if (param->rank > 0 && param->rank > param->set_size) {
BT_DBG("Invalid rank: %u (shall be less than set_size: %u)",
param->set_size, param->set_size);
return false;
}
#if CONFIG_BT_CSIP_SET_MEMBER_MAX_INSTANCE_COUNT > 1
if (param->parent == NULL) {
BT_DBG("Parent service not provided");
return false;
}
#endif /* CONFIG_BT_CSIP_SET_MEMBER_MAX_INSTANCE_COUNT > 1 */
return true;
}
int bt_csip_set_member_register(const struct bt_csip_set_member_register_param *param,
struct bt_csip_set_member_svc_inst **svc_inst)
{
static uint8_t instance_cnt;
struct bt_csip_set_member_svc_inst *inst;
int err;
if (instance_cnt == ARRAY_SIZE(svc_insts)) {
return -ENOMEM;
}
CHECKIF(param == NULL) {
BT_DBG("NULL param");
return -EINVAL;
}
CHECKIF(!valid_register_param(param)) {
BT_DBG("Invalid parameters");
return -EINVAL;
}
inst = &svc_insts[instance_cnt];
inst->service_p = &csip_set_member_service_list[instance_cnt];
instance_cnt++;
bt_conn_cb_register(&conn_callbacks);
bt_conn_auth_info_cb_register(&auth_callbacks);
err = bt_gatt_service_register(inst->service_p);
if (err != 0) {
BT_DBG("CSIS service register failed: %d", err);
return err;
}
k_work_init_delayable(&inst->set_lock_timer,
set_lock_timer_handler);
inst->rank = param->rank;
inst->set_size = param->set_size;
inst->set_lock = BT_CSIP_RELEASE_VALUE;
inst->set_sirk.type = BT_CSIP_SIRK_TYPE_PLAIN;
inst->cb = param->cb;
if (IS_ENABLED(CONFIG_BT_CSIP_SET_MEMBER_TEST_SAMPLE_DATA)) {
uint8_t test_sirk[] = {
0xcd, 0xcc, 0x72, 0xdd, 0x86, 0x8c, 0xcd, 0xce,
0x22, 0xfd, 0xa1, 0x21, 0x09, 0x7d, 0x7d, 0x45,
};
(void)memcpy(inst->set_sirk.value, test_sirk,
sizeof(test_sirk));
BT_DBG("CSIP SIRK was overwritten by sample data SIRK");
} else {
(void)memcpy(inst->set_sirk.value, param->set_sirk,
sizeof(inst->set_sirk.value));
}
*svc_inst = inst;
return 0;
}
int bt_csip_set_member_lock(struct bt_csip_set_member_svc_inst *svc_inst,
bool lock, bool force)
{
uint8_t lock_val;
int err = 0;
if (lock) {
lock_val = BT_CSIP_LOCK_VALUE;
} else {
lock_val = BT_CSIP_RELEASE_VALUE;
}
if (!lock && force) {
svc_inst->set_lock = BT_CSIP_RELEASE_VALUE;
notify_clients(svc_inst, NULL);
if (svc_inst->cb != NULL && svc_inst->cb->lock_changed != NULL) {
svc_inst->cb->lock_changed(NULL, &svc_insts[0], false);
}
} else {
err = set_lock(NULL, svc_inst, lock_val);
}
if (err < 0) {
return BT_GATT_ERR(err);
} else {
return 0;
}
}
void bt_csip_set_member_print_sirk(const struct bt_csip_set_member_svc_inst *svc_inst)
{
LOG_HEXDUMP_DBG(&svc_inst->set_sirk, sizeof(svc_inst->set_sirk),
"Set SIRK");
}