Adds, removes and modifies includes in all LE audio files. Fixes any found spelling mistakes as well. Fixes a few places where incorrect types were used. Signed-off-by: Emil Gydesen <emil.gydesen@nordicsemi.no>
985 lines
26 KiB
C
985 lines
26 KiB
C
/* 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 <sys/types.h>
|
|
#include <errno.h>
|
|
#include <stdbool.h>
|
|
#include <stddef.h>
|
|
#include <stdint.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include <zephyr/autoconf.h>
|
|
#include <zephyr/bluetooth/addr.h>
|
|
#include <zephyr/bluetooth/att.h>
|
|
#include <zephyr/bluetooth/audio/csip.h>
|
|
#include <zephyr/bluetooth/bluetooth.h>
|
|
#include <zephyr/bluetooth/conn.h>
|
|
#include <zephyr/bluetooth/crypto.h>
|
|
#include <zephyr/bluetooth/gatt.h>
|
|
#include <zephyr/bluetooth/buf.h>
|
|
#include <zephyr/bluetooth/uuid.h>
|
|
#include <zephyr/device.h>
|
|
#include <zephyr/init.h>
|
|
#include <zephyr/kernel.h>
|
|
#include <zephyr/logging/log.h>
|
|
#include <zephyr/sys/__assert.h>
|
|
#include <zephyr/sys/atomic.h>
|
|
#include <zephyr/sys/util.h>
|
|
#include <zephyr/sys/util_macro.h>
|
|
#include <zephyr/sys_clock.h>
|
|
#include <zephyr/types.h>
|
|
#include <zephyr/sys/byteorder.h>
|
|
#include <zephyr/sys/check.h>
|
|
|
|
#include "../host/conn_internal.h"
|
|
#include "../host/hci_core.h"
|
|
#include "../host/keys.h"
|
|
|
|
#include "common/bt_str.h"
|
|
#include "audio_internal.h"
|
|
#include "csip_internal.h"
|
|
#include "csip_crypto.h"
|
|
|
|
#define CSIP_SET_LOCK_TIMER_VALUE K_SECONDS(60)
|
|
|
|
#define CSIS_CHAR_ATTR_COUNT 3 /* declaration + value + cccd */
|
|
#define CSIS_RANK_CHAR_ATTR_COUNT 2 /* declaration + value */
|
|
|
|
LOG_MODULE_REGISTER(bt_csip_set_member, CONFIG_BT_CSIP_SET_MEMBER_LOG_LEVEL);
|
|
|
|
enum csip_flag {
|
|
FLAG_ACTIVE,
|
|
FLAG_NOTIFY_LOCK,
|
|
FLAG_NOTIFY_SIRK,
|
|
FLAG_NUM,
|
|
};
|
|
|
|
struct csip_client {
|
|
bt_addr_le_t addr;
|
|
|
|
/* Pending notification flags */
|
|
ATOMIC_DEFINE(flags, FLAG_NUM);
|
|
};
|
|
|
|
struct bt_csip_set_member_svc_inst {
|
|
struct bt_csip_sirk 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_client clients[CONFIG_BT_MAX_PAIRED];
|
|
};
|
|
|
|
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 */
|
|
|
|
static void deferred_nfy_work_handler(struct k_work *work);
|
|
|
|
static K_WORK_DELAYABLE_DEFINE(deferred_nfy_work, deferred_nfy_work_handler);
|
|
|
|
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_work_reschedule(k_timeout_t delay)
|
|
{
|
|
int err;
|
|
|
|
/* If it is already scheduled, don't reschedule */
|
|
if (k_work_delayable_remaining_get(&deferred_nfy_work) > 0) {
|
|
return;
|
|
}
|
|
|
|
err = k_work_reschedule(&deferred_nfy_work, delay);
|
|
if (err < 0) {
|
|
LOG_ERR("Failed to reschedule notification work err %d", err);
|
|
}
|
|
}
|
|
|
|
static void notify_clients(struct bt_csip_set_member_svc_inst *svc_inst,
|
|
struct bt_conn *excluded_client, enum csip_flag flag)
|
|
{
|
|
bool submit_work = false;
|
|
|
|
/* Mark all bonded devices (except the excluded one) as pending notifications */
|
|
for (size_t i = 0U; i < ARRAY_SIZE(svc_inst->clients); i++) {
|
|
struct csip_client *client;
|
|
|
|
client = &svc_inst->clients[i];
|
|
|
|
if (atomic_test_bit(client->flags, FLAG_ACTIVE)) {
|
|
if (excluded_client != NULL &&
|
|
bt_addr_le_eq(bt_conn_get_dst(excluded_client), &client->addr)) {
|
|
continue;
|
|
}
|
|
|
|
atomic_set_bit(client->flags, flag);
|
|
submit_work = true;
|
|
}
|
|
}
|
|
|
|
/* Reschedule work for notifying */
|
|
if (submit_work) {
|
|
notify_work_reschedule(K_NO_WAIT);
|
|
}
|
|
}
|
|
|
|
static int sirk_encrypt(struct bt_conn *conn, const struct bt_csip_sirk *sirk,
|
|
struct bt_csip_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;
|
|
}
|
|
LOG_DBG("Encrypting test SIRK");
|
|
k = test_k;
|
|
} else {
|
|
if (conn == NULL) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
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(uint8_t dest[BT_CSIP_CRYPTO_PRAND_SIZE])
|
|
{
|
|
bool valid = false;
|
|
|
|
do {
|
|
int res;
|
|
uint32_t prand;
|
|
|
|
*dest = 0;
|
|
res = bt_rand(dest, BT_CSIP_CRYPTO_PRAND_SIZE);
|
|
if (res != 0) {
|
|
return res;
|
|
}
|
|
|
|
/* Validate Prand: Must contain both a 1 and a 0 */
|
|
prand = sys_get_le24(dest);
|
|
if (prand != 0 && prand != 0x3FFFFF) {
|
|
valid = true;
|
|
}
|
|
} while (!valid);
|
|
|
|
dest[BT_CSIP_CRYPTO_PRAND_SIZE - 1] &= 0x3F;
|
|
dest[BT_CSIP_CRYPTO_PRAND_SIZE - 1] |= BIT(6);
|
|
|
|
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;
|
|
uint8_t prand[BT_CSIP_CRYPTO_PRAND_SIZE];
|
|
uint8_t hash[BT_CSIP_CRYPTO_HASH_SIZE];
|
|
|
|
if (IS_ENABLED(CONFIG_BT_CSIP_SET_MEMBER_TEST_SAMPLE_DATA)) {
|
|
/* prand is from the sample data from A.2 in the CSIS spec */
|
|
sys_put_le24(0x69f563, prand);
|
|
} else {
|
|
res = generate_prand(prand);
|
|
|
|
if (res != 0) {
|
|
LOG_WRN("Could not generate new prand");
|
|
return res;
|
|
}
|
|
}
|
|
|
|
res = bt_csip_sih(svc_inst->sirk.value, prand, hash);
|
|
if (res != 0) {
|
|
LOG_WRN("Could not generate new RSI");
|
|
return res;
|
|
}
|
|
|
|
(void)memcpy(rsi, hash, BT_CSIP_CRYPTO_HASH_SIZE);
|
|
(void)memcpy(rsi + BT_CSIP_CRYPTO_HASH_SIZE, prand, BT_CSIP_CRYPTO_PRAND_SIZE);
|
|
|
|
return res;
|
|
}
|
|
|
|
static ssize_t read_sirk(struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf,
|
|
uint16_t len, uint16_t offset)
|
|
{
|
|
struct bt_csip_sirk enc_sirk;
|
|
struct bt_csip_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->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->sirk, &enc_sirk);
|
|
if (err != 0) {
|
|
LOG_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 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 {
|
|
LOG_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->sirk;
|
|
}
|
|
|
|
LOG_DBG("SIRK %sencrypted", sirk->type == BT_CSIP_SIRK_TYPE_PLAIN ? "not " : "");
|
|
LOG_HEXDUMP_DBG(svc_inst->sirk.value, sizeof(svc_inst->sirk.value), "SIRK");
|
|
return bt_gatt_attr_read(conn, attr, buf, len, offset,
|
|
sirk, sizeof(*sirk));
|
|
}
|
|
|
|
#if defined(CONFIG_BT_CSIP_SET_MEMBER_NOTIFIABLE)
|
|
static void sirk_cfg_changed(const struct bt_gatt_attr *attr, uint16_t value)
|
|
{
|
|
LOG_DBG("value 0x%04x", value);
|
|
}
|
|
#endif /* CONFIG_BT_CSIP_SET_MEMBER_NOTIFIABLE */
|
|
|
|
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);
|
|
|
|
LOG_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)
|
|
{
|
|
LOG_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);
|
|
|
|
LOG_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);
|
|
}
|
|
|
|
LOG_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, FLAG_NOTIFY_LOCK);
|
|
|
|
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)
|
|
{
|
|
LOG_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);
|
|
|
|
LOG_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 = k_work_delayable_from_work(work);
|
|
svc_inst = CONTAINER_OF(delayable, struct bt_csip_set_member_svc_inst,
|
|
set_lock_timer);
|
|
|
|
LOG_DBG("Lock timeout, releasing");
|
|
svc_inst->set_lock = BT_CSIP_RELEASE_VALUE;
|
|
notify_clients(svc_inst, NULL, FLAG_NOTIFY_LOCK);
|
|
|
|
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 (size_t i = 0U; i < ARRAY_SIZE(svc_insts); i++) {
|
|
struct bt_csip_set_member_svc_inst *svc_inst = &svc_insts[i];
|
|
|
|
for (size_t j = 0U; j < ARRAY_SIZE(svc_inst->clients); j++) {
|
|
struct csip_client *client;
|
|
|
|
client = &svc_inst->clients[i];
|
|
|
|
if (atomic_test_bit(client->flags, FLAG_NOTIFY_LOCK) &&
|
|
bt_addr_le_eq(bt_conn_get_dst(conn), &client->addr)) {
|
|
notify_work_reschedule(K_NO_WAIT);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void handle_csip_disconnect(struct bt_csip_set_member_svc_inst *svc_inst,
|
|
struct bt_conn *conn)
|
|
{
|
|
LOG_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, FLAG_NOTIFY_LOCK);
|
|
|
|
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 (size_t i = 0U; i < ARRAY_SIZE(svc_inst->clients); i++) {
|
|
struct csip_client *client;
|
|
|
|
client = &svc_inst->clients[i];
|
|
|
|
if (bt_addr_le_eq(bt_conn_get_dst(conn), &client->addr)) {
|
|
(void)memset(client, 0, sizeof(*client));
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void csip_disconnected(struct bt_conn *conn, uint8_t reason)
|
|
{
|
|
LOG_DBG("Disconnected: %s (reason %u)", bt_addr_le_str(bt_conn_get_dst(conn)), reason);
|
|
|
|
if (!bt_addr_le_is_bonded(conn->id, &conn->le.dst)) {
|
|
for (size_t i = 0U; 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 (size_t i = 0U; i < ARRAY_SIZE(svc_inst->clients); i++) {
|
|
struct csip_client *client;
|
|
|
|
client = &svc_inst->clients[i];
|
|
|
|
if (atomic_test_bit(client->flags, FLAG_ACTIVE) &&
|
|
bt_addr_le_eq(bt_conn_get_dst(conn), &client->addr)) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* Else add the device */
|
|
for (size_t i = 0U; i < ARRAY_SIZE(svc_inst->clients); i++) {
|
|
struct csip_client *client;
|
|
|
|
client = &svc_inst->clients[i];
|
|
|
|
if (!atomic_test_bit(client->flags, FLAG_ACTIVE)) {
|
|
atomic_set_bit(client->flags, FLAG_ACTIVE);
|
|
memcpy(&client->addr, bt_conn_get_dst(conn), sizeof(bt_addr_le_t));
|
|
|
|
/* Send out all pending notifications */
|
|
notify_work_reschedule(K_NO_WAIT);
|
|
return;
|
|
}
|
|
}
|
|
|
|
LOG_WRN("Could not add device to pending notification list");
|
|
}
|
|
|
|
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 `clients`, and if it is
|
|
* not, then we
|
|
* 3) Check if there's room for another device in the `clients`
|
|
* 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.
|
|
*/
|
|
|
|
LOG_DBG("%s paired (%sbonded)", bt_addr_le_str(bt_conn_get_dst(conn)),
|
|
bonded ? "" : "not ");
|
|
|
|
if (!bonded) {
|
|
return;
|
|
}
|
|
|
|
for (size_t i = 0U; 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 (size_t i = 0U; i < ARRAY_SIZE(svc_insts); i++) {
|
|
struct bt_csip_set_member_svc_inst *svc_inst = &svc_insts[i];
|
|
|
|
for (size_t j = 0U; j < ARRAY_SIZE(svc_inst->clients); j++) {
|
|
|
|
/* Check if match, and if active, if so, reset */
|
|
if (atomic_test_bit(svc_inst->clients[i].flags, FLAG_ACTIVE) &&
|
|
bt_addr_le_eq(peer, &svc_inst->clients[i].addr)) {
|
|
atomic_clear(svc_inst->clients[i].flags);
|
|
(void)memset(&svc_inst->clients[i].addr, 0, sizeof(bt_addr_le_t));
|
|
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
|
|
};
|
|
|
|
#if defined(CONFIG_BT_CSIP_SET_MEMBER_NOTIFIABLE)
|
|
#define BT_CSIS_CHR_SIRK(_csip) \
|
|
BT_AUDIO_CHRC(BT_UUID_CSIS_SIRK, BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY, \
|
|
BT_GATT_PERM_READ_ENCRYPT, read_sirk, NULL, &_csip), \
|
|
BT_AUDIO_CCC(sirk_cfg_changed)
|
|
#else
|
|
#define BT_CSIS_CHR_SIRK(_csip) \
|
|
BT_AUDIO_CHRC(BT_UUID_CSIS_SIRK, BT_GATT_CHRC_READ, BT_GATT_PERM_READ_ENCRYPT, read_sirk, \
|
|
NULL, &_csip)
|
|
#endif /* CONFIG_BT_CSIP_SET_MEMBER_NOTIFIABLE */
|
|
|
|
#define BT_CSIP_SERVICE_DEFINITION(_csip) {\
|
|
BT_GATT_PRIMARY_SERVICE(BT_UUID_CSIS), \
|
|
BT_CSIS_CHR_SIRK(_csip), \
|
|
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)
|
|
{
|
|
if (svc_inst == NULL || svc_inst->service_p == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
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) {
|
|
LOG_DBG("Rank cannot be 0 if service is lockable");
|
|
return false;
|
|
}
|
|
|
|
if (param->rank > 0 && param->set_size > 0 && param->rank > param->set_size) {
|
|
LOG_DBG("Invalid rank: %u (shall be less than or equal to set_size: %u)",
|
|
param->rank, param->set_size);
|
|
return false;
|
|
}
|
|
|
|
#if CONFIG_BT_CSIP_SET_MEMBER_MAX_INSTANCE_COUNT > 1
|
|
if (param->parent == NULL) {
|
|
LOG_DBG("Parent service not provided");
|
|
return false;
|
|
}
|
|
#endif /* CONFIG_BT_CSIP_SET_MEMBER_MAX_INSTANCE_COUNT > 1 */
|
|
|
|
return true;
|
|
}
|
|
|
|
static void remove_csis_char(const struct bt_uuid *uuid, struct bt_gatt_service *svc)
|
|
{
|
|
size_t attrs_to_rem;
|
|
|
|
/* Rank does not have any CCCD */
|
|
if (bt_uuid_cmp(uuid, BT_UUID_CSIS_RANK) == 0) {
|
|
attrs_to_rem = CSIS_RANK_CHAR_ATTR_COUNT;
|
|
} else {
|
|
attrs_to_rem = CSIS_CHAR_ATTR_COUNT;
|
|
}
|
|
|
|
/* Start at index 4 as the first 4 attributes are mandatory */
|
|
for (size_t i = 4U; i < svc->attr_count; i++) {
|
|
if (bt_uuid_cmp(svc->attrs[i].uuid, uuid) == 0) {
|
|
/* Remove the characteristic declaration, the characteristic value and
|
|
* potentially the CCCD. The value declaration will be a i - 1, the
|
|
* characteristic value at i and the CCCD is potentially at i + 1
|
|
*/
|
|
|
|
/* We use attrs_to_rem to determine whether there is a CCCD after the
|
|
* characteristic value or not, which then determines if this is the last
|
|
* characteristic or not
|
|
*/
|
|
if (i == (svc->attr_count - (attrs_to_rem - 1))) {
|
|
/* This is the last characteristic in the service: just decrement
|
|
* the attr_count by number of attributes to remove
|
|
* (CSIS_CHAR_ATTR_COUNT)
|
|
*/
|
|
} else {
|
|
/* Move all following attributes attrs_to_rem locations "up" */
|
|
for (size_t j = i - 1U; j < svc->attr_count - attrs_to_rem; j++) {
|
|
svc->attrs[j] = svc->attrs[j + attrs_to_rem];
|
|
}
|
|
}
|
|
|
|
svc->attr_count -= attrs_to_rem;
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
__ASSERT(false, "Failed to remove CSIS char %s", bt_uuid_str(uuid));
|
|
}
|
|
|
|
static void notify(struct bt_csip_set_member_svc_inst *svc_inst, struct bt_conn *conn,
|
|
const struct bt_uuid *uuid, const void *data, uint16_t len)
|
|
{
|
|
int err;
|
|
|
|
if (svc_inst->service_p == NULL) {
|
|
return;
|
|
}
|
|
|
|
err = bt_gatt_notify_uuid(conn, uuid, svc_inst->service_p->attrs, data, len);
|
|
if (err) {
|
|
if (err == -ENOTCONN) {
|
|
LOG_DBG("Notification error: ENOTCONN (%d)", err);
|
|
} else {
|
|
LOG_ERR("Notification error: %d", err);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void notify_cb(struct bt_conn *conn, void *data)
|
|
{
|
|
struct bt_conn_info info;
|
|
int err = 0;
|
|
|
|
err = bt_conn_get_info(conn, &info);
|
|
if (err != 0) {
|
|
return;
|
|
}
|
|
|
|
if (info.state != BT_CONN_STATE_CONNECTED) {
|
|
/* Not connected */
|
|
LOG_DBG("Not connected: %u", info.state);
|
|
return;
|
|
}
|
|
|
|
for (size_t i = 0U; i < ARRAY_SIZE(svc_insts); i++) {
|
|
struct bt_csip_set_member_svc_inst *svc_inst = &svc_insts[i];
|
|
struct csip_client *client = &svc_inst->clients[bt_conn_index(conn)];
|
|
|
|
if (atomic_test_and_clear_bit(client->flags, FLAG_NOTIFY_LOCK)) {
|
|
notify(svc_inst, conn, BT_UUID_CSIS_SET_LOCK, &svc_inst->set_lock,
|
|
sizeof(svc_inst->set_lock));
|
|
}
|
|
|
|
if (IS_ENABLED(CONFIG_BT_CSIP_SET_MEMBER_NOTIFIABLE) &&
|
|
atomic_test_and_clear_bit(client->flags, FLAG_NOTIFY_SIRK)) {
|
|
notify(svc_inst, conn, BT_UUID_CSIS_SIRK, &svc_inst->sirk,
|
|
sizeof(svc_inst->sirk));
|
|
}
|
|
}
|
|
}
|
|
|
|
static void deferred_nfy_work_handler(struct k_work *work)
|
|
{
|
|
bt_conn_foreach(BT_CONN_TYPE_LE, notify_cb, NULL);
|
|
}
|
|
|
|
static void add_bonded_addr_to_client_list(const struct bt_bond_info *info, void *data)
|
|
{
|
|
|
|
for (size_t i = 0U; i < ARRAY_SIZE(svc_insts); i++) {
|
|
struct bt_csip_set_member_svc_inst *svc_inst = &svc_insts[i];
|
|
|
|
for (size_t j = 1U; j < ARRAY_SIZE(svc_inst->clients); i++) {
|
|
/* Check if device is registered, it not, add it */
|
|
if (!atomic_test_bit(svc_inst->clients[j].flags, FLAG_ACTIVE)) {
|
|
char addr_str[BT_ADDR_LE_STR_LEN];
|
|
|
|
atomic_set_bit(svc_inst->clients[j].flags, FLAG_ACTIVE);
|
|
memcpy(&svc_inst->clients[j].addr, &info->addr,
|
|
sizeof(bt_addr_le_t));
|
|
bt_addr_le_to_str(&svc_inst->clients[j].addr, addr_str,
|
|
sizeof(addr_str));
|
|
LOG_DBG("Added %s to bonded list\n", addr_str);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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 bool first_register;
|
|
static uint8_t instance_cnt;
|
|
struct bt_csip_set_member_svc_inst *inst;
|
|
int err;
|
|
|
|
if (instance_cnt == ARRAY_SIZE(svc_insts)) {
|
|
LOG_DBG("Too many set member registrations");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
CHECKIF(param == NULL) {
|
|
LOG_DBG("NULL param");
|
|
return -EINVAL;
|
|
}
|
|
|
|
CHECKIF(!valid_register_param(param)) {
|
|
LOG_DBG("Invalid parameters");
|
|
return -EINVAL;
|
|
}
|
|
|
|
inst = &svc_insts[instance_cnt];
|
|
inst->service_p = &csip_set_member_service_list[instance_cnt];
|
|
instance_cnt++;
|
|
|
|
if (!first_register) {
|
|
bt_conn_cb_register(&conn_callbacks);
|
|
bt_conn_auth_info_cb_register(&auth_callbacks);
|
|
|
|
/* Restore bonding list */
|
|
bt_foreach_bond(BT_ID_DEFAULT, add_bonded_addr_to_client_list, NULL);
|
|
|
|
first_register = true;
|
|
}
|
|
|
|
/* The removal of the optional characteristics should be done in reverse order of the order
|
|
* in BT_CSIP_SERVICE_DEFINITION, as that improves the performance of remove_csis_char,
|
|
* since it's easier to remove the last characteristic
|
|
*/
|
|
if (param->rank == 0U) {
|
|
remove_csis_char(BT_UUID_CSIS_RANK, inst->service_p);
|
|
}
|
|
|
|
if (param->set_size == 0U) {
|
|
remove_csis_char(BT_UUID_CSIS_SET_SIZE, inst->service_p);
|
|
}
|
|
|
|
if (!param->lockable) {
|
|
remove_csis_char(BT_UUID_CSIS_SET_LOCK, inst->service_p);
|
|
}
|
|
|
|
err = bt_gatt_service_register(inst->service_p);
|
|
if (err != 0) {
|
|
LOG_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->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->sirk.value, test_sirk, sizeof(test_sirk));
|
|
LOG_DBG("CSIP SIRK was overwritten by sample data SIRK");
|
|
} else {
|
|
(void)memcpy(inst->sirk.value, param->sirk, sizeof(inst->sirk.value));
|
|
}
|
|
|
|
*svc_inst = inst;
|
|
return 0;
|
|
}
|
|
|
|
int bt_csip_set_member_unregister(struct bt_csip_set_member_svc_inst *svc_inst)
|
|
{
|
|
int err;
|
|
|
|
CHECKIF(svc_inst == NULL) {
|
|
LOG_DBG("NULL svc_inst");
|
|
return -EINVAL;
|
|
}
|
|
|
|
err = bt_gatt_service_unregister(svc_inst->service_p);
|
|
if (err != 0) {
|
|
LOG_DBG("CSIS service unregister failed: %d", err);
|
|
return err;
|
|
}
|
|
|
|
(void)k_work_cancel_delayable(&svc_inst->set_lock_timer);
|
|
memset(svc_inst, 0, sizeof(*svc_inst));
|
|
|
|
return 0;
|
|
}
|
|
|
|
int bt_csip_set_member_sirk(struct bt_csip_set_member_svc_inst *svc_inst,
|
|
const uint8_t sirk[BT_CSIP_SIRK_SIZE])
|
|
{
|
|
CHECKIF(svc_inst == NULL) {
|
|
LOG_DBG("NULL svc_inst");
|
|
return -EINVAL;
|
|
}
|
|
|
|
CHECKIF(sirk == NULL) {
|
|
LOG_DBG("NULL SIRK");
|
|
return -EINVAL;
|
|
}
|
|
|
|
memcpy(svc_inst->sirk.value, sirk, BT_CSIP_SIRK_SIZE);
|
|
|
|
notify_clients(svc_inst, NULL, FLAG_NOTIFY_SIRK);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int bt_csip_set_member_get_sirk(struct bt_csip_set_member_svc_inst *svc_inst,
|
|
uint8_t sirk[BT_CSIP_SIRK_SIZE])
|
|
{
|
|
CHECKIF(svc_inst == NULL) {
|
|
LOG_DBG("NULL svc_inst");
|
|
return -EINVAL;
|
|
}
|
|
|
|
CHECKIF(sirk == NULL) {
|
|
LOG_DBG("NULL SIRK");
|
|
return -EINVAL;
|
|
}
|
|
|
|
memcpy(sirk, svc_inst->sirk.value, BT_CSIP_SIRK_SIZE);
|
|
|
|
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, FLAG_NOTIFY_LOCK);
|
|
|
|
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;
|
|
}
|
|
}
|