Bluetooth: Audio: Add coordinated sets identification service server
Adds the coordinated set identification service (CSIS) server. This is still a work in progress and thus there are no public API for it yet, and some code changes will still be needed. Signed-off-by: Emil Gydesen <emil.gydesen@nordicsemi.no>
This commit is contained in:
parent
613ff42277
commit
d55aeb4a1e
11 changed files with 1698 additions and 0 deletions
|
@ -56,6 +56,7 @@ extern "C" {
|
||||||
#define BT_DATA_MESH_BEACON 0x2b /* Mesh Beacon */
|
#define BT_DATA_MESH_BEACON 0x2b /* Mesh Beacon */
|
||||||
#define BT_DATA_BIG_INFO 0x2c /* BIGInfo */
|
#define BT_DATA_BIG_INFO 0x2c /* BIGInfo */
|
||||||
#define BT_DATA_BROADCAST_CODE 0x2d /* Broadcast Code */
|
#define BT_DATA_BROADCAST_CODE 0x2d /* Broadcast Code */
|
||||||
|
#define BT_DATA_CSIS_RSI 0x2e /* CSIS Random Set ID type */
|
||||||
|
|
||||||
#define BT_DATA_MANUFACTURER_DATA 0xff /* Manufacturer Specific Data */
|
#define BT_DATA_MANUFACTURER_DATA 0xff /* Manufacturer Specific Data */
|
||||||
|
|
||||||
|
|
|
@ -446,6 +446,15 @@ struct bt_uuid_128 {
|
||||||
*/
|
*/
|
||||||
#define BT_UUID_VOCS \
|
#define BT_UUID_VOCS \
|
||||||
BT_UUID_DECLARE_16(BT_UUID_VOCS_VAL)
|
BT_UUID_DECLARE_16(BT_UUID_VOCS_VAL)
|
||||||
|
/** @def BT_UUID_CSIS_VAL
|
||||||
|
* @brief Coordinated Set Identification Service value
|
||||||
|
*/
|
||||||
|
#define BT_UUID_CSIS_VAL 0x1846
|
||||||
|
/** @def BT_UUID_CSIS
|
||||||
|
* @brief Coordinated Set Identification Service
|
||||||
|
*/
|
||||||
|
#define BT_UUID_CSIS \
|
||||||
|
BT_UUID_DECLARE_16(BT_UUID_CSIS_VAL)
|
||||||
/** @def BT_UUID_MICS_VAL
|
/** @def BT_UUID_MICS_VAL
|
||||||
* @brief Microphone Input Control Service value
|
* @brief Microphone Input Control Service value
|
||||||
*/
|
*/
|
||||||
|
@ -1484,6 +1493,42 @@ struct bt_uuid_128 {
|
||||||
*/
|
*/
|
||||||
#define BT_UUID_VOCS_DESCRIPTION \
|
#define BT_UUID_VOCS_DESCRIPTION \
|
||||||
BT_UUID_DECLARE_16(BT_UUID_VOCS_DESCRIPTION_VAL)
|
BT_UUID_DECLARE_16(BT_UUID_VOCS_DESCRIPTION_VAL)
|
||||||
|
/** @def BT_UUID_CSIS_SET_SIRK_VAL
|
||||||
|
* @brief Set Identity Resolving Key value
|
||||||
|
*/
|
||||||
|
#define BT_UUID_CSIS_SET_SIRK_VAL 0x2B84
|
||||||
|
/** @def BT_UUID_CSIS_SET_SIRK
|
||||||
|
* @brief Set Identity Resolving Key
|
||||||
|
*/
|
||||||
|
#define BT_UUID_CSIS_SET_SIRK \
|
||||||
|
BT_UUID_DECLARE_16(BT_UUID_CSIS_SET_SIRK_VAL)
|
||||||
|
/** @def BT_UUID_CSIS_SET_SIZE_VAL
|
||||||
|
* @brief Set size value
|
||||||
|
*/
|
||||||
|
#define BT_UUID_CSIS_SET_SIZE_VAL 0x2B85
|
||||||
|
/** @def BT_UUID_CSIS_SET_SIZE
|
||||||
|
* @brief Set size
|
||||||
|
*/
|
||||||
|
#define BT_UUID_CSIS_SET_SIZE \
|
||||||
|
BT_UUID_DECLARE_16(BT_UUID_CSIS_SET_SIZE_VAL)
|
||||||
|
/** @def BT_UUID_CSIS_SET_LOCK_VAL
|
||||||
|
* @brief Set lock value
|
||||||
|
*/
|
||||||
|
#define BT_UUID_CSIS_SET_LOCK_VAL 0x2B86
|
||||||
|
/** @def BT_UUID_CSIS_SET_LOCK
|
||||||
|
* @brief Set lock
|
||||||
|
*/
|
||||||
|
#define BT_UUID_CSIS_SET_LOCK \
|
||||||
|
BT_UUID_DECLARE_16(BT_UUID_CSIS_SET_LOCK_VAL)
|
||||||
|
/** @def BT_UUID_CSIS_RANK_VAL
|
||||||
|
* @brief Rank value
|
||||||
|
*/
|
||||||
|
#define BT_UUID_CSIS_RANK_VAL 0x2B87
|
||||||
|
/** @def BT_UUID_CSIS_RANK
|
||||||
|
* @brief Rank
|
||||||
|
*/
|
||||||
|
#define BT_UUID_CSIS_RANK \
|
||||||
|
BT_UUID_DECLARE_16(BT_UUID_CSIS_RANK_VAL)
|
||||||
/** @def BT_UUID_CCID_VAL
|
/** @def BT_UUID_CCID_VAL
|
||||||
* @brief Content Control ID value
|
* @brief Content Control ID value
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -25,3 +25,6 @@ zephyr_library_sources_ifdef(CONFIG_BT_MICS_CLIENT mics_client.c)
|
||||||
zephyr_library_sources_ifdef(CONFIG_BT_CCID ccid.c)
|
zephyr_library_sources_ifdef(CONFIG_BT_CCID ccid.c)
|
||||||
|
|
||||||
zephyr_library_link_libraries(subsys__bluetooth)
|
zephyr_library_link_libraries(subsys__bluetooth)
|
||||||
|
|
||||||
|
zephyr_library_sources_ifdef(CONFIG_BT_CSIS csis.c)
|
||||||
|
zephyr_library_sources_ifdef(CONFIG_BT_CSIS csis_crypto.c)
|
||||||
|
|
|
@ -61,5 +61,6 @@ rsource "Kconfig.vocs"
|
||||||
rsource "Kconfig.aics"
|
rsource "Kconfig.aics"
|
||||||
rsource "Kconfig.vcs"
|
rsource "Kconfig.vcs"
|
||||||
rsource "Kconfig.mics"
|
rsource "Kconfig.mics"
|
||||||
|
rsource "Kconfig.csis"
|
||||||
|
|
||||||
endif # BT_AUDIO
|
endif # BT_AUDIO
|
||||||
|
|
68
subsys/bluetooth/audio/Kconfig.csis
Normal file
68
subsys/bluetooth/audio/Kconfig.csis
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
# Bluetooth Audio - Broadcast Assistant configuration options
|
||||||
|
#
|
||||||
|
# Copyright (c) 2020 Bose Corporation
|
||||||
|
# Copyright (c) 2021 Nordic Semiconductor ASA
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
|
#
|
||||||
|
|
||||||
|
if BT_AUDIO
|
||||||
|
|
||||||
|
#################### Coordinated Set Identification Service ####################
|
||||||
|
|
||||||
|
config BT_CSIS
|
||||||
|
bool "Coordinated Set Identification Service Support [EXPERIMENTAL]"
|
||||||
|
imply BT_EXT_ADV if BT_PRIVACY
|
||||||
|
select EXPERIMENTAL
|
||||||
|
help
|
||||||
|
This option enables support for Coordinated Set Identification
|
||||||
|
Service.
|
||||||
|
|
||||||
|
if BT_CSIS
|
||||||
|
|
||||||
|
config BT_CSIS_TEST_SAMPLE_DATA
|
||||||
|
bool "Coordinated Set Identification Service Test Sample Data"
|
||||||
|
help
|
||||||
|
Enable the use of the sample data defined by the CSIS spec SIRK.
|
||||||
|
This will use the sample SIRK, prand and LTK.
|
||||||
|
|
||||||
|
WARNING: This option enables anyone to track and decrypt the SIRK
|
||||||
|
(if encrypted) using public sample data.
|
||||||
|
Should not be used for production builds.
|
||||||
|
|
||||||
|
config BT_CSIS_ENC_SIRK_SUPPORT
|
||||||
|
bool "Support for encrypted SIRK"
|
||||||
|
default y
|
||||||
|
select BT_SMP_SC_PAIR_ONLY
|
||||||
|
help
|
||||||
|
Enables support encrypting the SIRK.
|
||||||
|
|
||||||
|
config BT_CSIS_MAX_INSTANCE_COUNT
|
||||||
|
int "Coordinated Set Identification Service max instance count"
|
||||||
|
default 1
|
||||||
|
range 1 255
|
||||||
|
help
|
||||||
|
This option sets the maximum number of instances of Coordinated Set
|
||||||
|
Identification Services. If the service is declared as primary service
|
||||||
|
then only a single instance is possible.
|
||||||
|
|
||||||
|
############### DEBUG ###############
|
||||||
|
|
||||||
|
config BT_DEBUG_CSIS
|
||||||
|
bool "Coordinated Set Identification Service debug"
|
||||||
|
depends on BT_AUDIO_DEBUG
|
||||||
|
help
|
||||||
|
Use this option to enable Coordinated Set Identification Service debug
|
||||||
|
logs for the Bluetooth Audio functionality.
|
||||||
|
|
||||||
|
endif # BT_CSIS
|
||||||
|
|
||||||
|
config BT_DEBUG_CSIS_CRYPTO
|
||||||
|
bool "Coordinated Set Identification Service crypto functions debug"
|
||||||
|
depends on BT_AUDIO_DEBUG
|
||||||
|
depends on BT_CSIS
|
||||||
|
help
|
||||||
|
Use this option to enable Coordinated Set Identification Service
|
||||||
|
crypto functions debug logs for the Bluetooth Audio functionality.
|
||||||
|
|
||||||
|
endif # BT_AUDIO
|
996
subsys/bluetooth/audio/csis.c
Normal file
996
subsys/bluetooth/audio/csis.c
Normal file
|
@ -0,0 +1,996 @@
|
||||||
|
/* Bluetooth CSIS - Coordinated Set Identification Service */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2019 Bose Corporation
|
||||||
|
* Copyright (c) 2020-2021 Nordic Semiconductor ASA
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <zephyr.h>
|
||||||
|
#include <zephyr/types.h>
|
||||||
|
|
||||||
|
#include <device.h>
|
||||||
|
#include <init.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
#include <bluetooth/bluetooth.h>
|
||||||
|
#include <bluetooth/conn.h>
|
||||||
|
#include <bluetooth/gatt.h>
|
||||||
|
#include <bluetooth/buf.h>
|
||||||
|
#include <sys/byteorder.h>
|
||||||
|
#include <sys/check.h>
|
||||||
|
#include "csis_internal.h"
|
||||||
|
#include "csis_crypto.h"
|
||||||
|
#include "../host/conn_internal.h"
|
||||||
|
#include "../host/hci_core.h"
|
||||||
|
#include "../host/keys.h"
|
||||||
|
|
||||||
|
#define BT_CSIS_SIH_PRAND_SIZE 3
|
||||||
|
#define BT_CSIS_SIH_HASH_SIZE 3
|
||||||
|
#define CSIS_SET_LOCK_TIMER_VALUE K_SECONDS(60)
|
||||||
|
#if defined(CONFIG_BT_PRIVACY)
|
||||||
|
/* The ADV time (in tens of milliseconds). Shall be less than the RPA.
|
||||||
|
* Make it relatively smaller (90%) to handle all ranges. Maximum value is
|
||||||
|
* 2^16 - 1 (UINT16_MAX).
|
||||||
|
*/
|
||||||
|
#define CSIS_ADV_TIME (MIN((CONFIG_BT_RPA_TIMEOUT * 100 * 0.9), UINT16_MAX))
|
||||||
|
#else
|
||||||
|
/* Without privacy, connectable adv won't update the address when restarting,
|
||||||
|
* so we might as well continue advertising non-stop.
|
||||||
|
*/
|
||||||
|
#define CSIS_ADV_TIME 0
|
||||||
|
#endif /* CONFIG_BT_PRIVACY */
|
||||||
|
|
||||||
|
#define BT_DBG_ENABLED IS_ENABLED(CONFIG_BT_DEBUG_CSIS)
|
||||||
|
#define LOG_MODULE_NAME bt_csis
|
||||||
|
#include "common/log.h"
|
||||||
|
|
||||||
|
#if defined(CONFIG_BT_RPA) && !defined(CONFIG_BT_BONDABLE)
|
||||||
|
#define SIRK_READ_PERM (BT_GATT_PERM_READ_AUTHEN | BT_GATT_PERM_READ_ENCRYPT)
|
||||||
|
#else
|
||||||
|
#define SIRK_READ_PERM (BT_GATT_PERM_READ_ENCRYPT)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static struct bt_csis_cb *csis_cbs;
|
||||||
|
|
||||||
|
static struct bt_csis csis_insts[CONFIG_BT_CSIS_MAX_INSTANCE_COUNT];
|
||||||
|
static bt_addr_le_t server_dummy_addr; /* 0'ed address */
|
||||||
|
|
||||||
|
struct csis_notify_foreach {
|
||||||
|
struct bt_conn *excluded_client;
|
||||||
|
struct bt_csis *csis;
|
||||||
|
};
|
||||||
|
|
||||||
|
static bool is_last_client_to_write(const struct bt_csis *csis,
|
||||||
|
const struct bt_conn *conn)
|
||||||
|
{
|
||||||
|
if (conn != NULL) {
|
||||||
|
return !bt_addr_le_cmp(bt_conn_get_dst(conn),
|
||||||
|
&csis->srv.lock_client_addr);
|
||||||
|
} else {
|
||||||
|
return !bt_addr_le_cmp(&server_dummy_addr,
|
||||||
|
&csis->srv.lock_client_addr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void notify_lock_value(const struct bt_csis *csis, struct bt_conn *conn)
|
||||||
|
{
|
||||||
|
bt_gatt_notify_uuid(conn, BT_UUID_CSIS_SET_LOCK,
|
||||||
|
csis->srv.service_p->attrs,
|
||||||
|
&csis->srv.set_lock,
|
||||||
|
sizeof(csis->srv.set_lock));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void notify_client(struct bt_conn *conn, void *data)
|
||||||
|
{
|
||||||
|
struct csis_notify_foreach *csis_data = (struct csis_notify_foreach *)data;
|
||||||
|
struct bt_csis *csis = csis_data->csis;
|
||||||
|
struct bt_conn *excluded_conn = csis_data->excluded_client;
|
||||||
|
|
||||||
|
if (excluded_conn != NULL && conn == excluded_conn) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
notify_lock_value(csis, conn);
|
||||||
|
|
||||||
|
for (int i = 0; i < ARRAY_SIZE(csis->srv.pend_notify); i++) {
|
||||||
|
struct csis_pending_notifications *pend_notify;
|
||||||
|
|
||||||
|
pend_notify = &csis->srv.pend_notify[i];
|
||||||
|
|
||||||
|
if (pend_notify->pending &&
|
||||||
|
bt_addr_le_cmp(bt_conn_get_dst(conn),
|
||||||
|
&pend_notify->addr) == 0) {
|
||||||
|
pend_notify->pending = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void notify_clients(struct bt_csis *csis,
|
||||||
|
struct bt_conn *excluded_client)
|
||||||
|
{
|
||||||
|
struct csis_notify_foreach data = {
|
||||||
|
.excluded_client = excluded_client,
|
||||||
|
.csis = csis,
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Mark all bonded devices as pending notifications, and clear those
|
||||||
|
* that are notified in `notify_client`
|
||||||
|
*/
|
||||||
|
for (int i = 0; i < ARRAY_SIZE(csis->srv.pend_notify); i++) {
|
||||||
|
struct csis_pending_notifications *pend_notify;
|
||||||
|
|
||||||
|
pend_notify = &csis->srv.pend_notify[i];
|
||||||
|
|
||||||
|
if (pend_notify->active) {
|
||||||
|
if (excluded_client != NULL &&
|
||||||
|
bt_addr_le_cmp(bt_conn_get_dst(excluded_client),
|
||||||
|
&pend_notify->addr) == 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
pend_notify->pending = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bt_conn_foreach(BT_CONN_TYPE_ALL, notify_client, &data);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int sirk_encrypt(struct bt_conn *conn,
|
||||||
|
const struct bt_csis_set_sirk *sirk,
|
||||||
|
struct bt_csis_set_sirk *enc_sirk)
|
||||||
|
{
|
||||||
|
int err;
|
||||||
|
uint8_t *k;
|
||||||
|
|
||||||
|
if (IS_ENABLED(CONFIG_BT_CSIS_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 && (__BYTE_ORDER__ == __ORDER_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_csis_sef(k, sirk->value, enc_sirk->value);
|
||||||
|
|
||||||
|
if (err != 0) {
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
enc_sirk->type = BT_CSIS_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_CSIS_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;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int csis_update_psri(struct bt_csis *csis)
|
||||||
|
{
|
||||||
|
int res = 0;
|
||||||
|
uint32_t prand;
|
||||||
|
uint32_t hash;
|
||||||
|
|
||||||
|
if (IS_ENABLED(CONFIG_BT_CSIS_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_csis_sih(csis->srv.set_sirk.value, prand, &hash);
|
||||||
|
if (res != 0) {
|
||||||
|
BT_WARN("Could not generate new PSRI");
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
(void)memcpy(csis->srv.psri, &hash, BT_CSIS_SIH_HASH_SIZE);
|
||||||
|
(void)memcpy(csis->srv.psri + BT_CSIS_SIH_HASH_SIZE, &prand,
|
||||||
|
BT_CSIS_SIH_PRAND_SIZE);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
int csis_adv_resume(struct bt_csis *csis)
|
||||||
|
{
|
||||||
|
int err;
|
||||||
|
struct bt_data ad[2] = {
|
||||||
|
BT_DATA_BYTES(BT_DATA_FLAGS,
|
||||||
|
BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR)
|
||||||
|
};
|
||||||
|
|
||||||
|
BT_DBG("Restarting CSIS advertising");
|
||||||
|
|
||||||
|
if (csis_update_psri(csis) != 0) {
|
||||||
|
return -EAGAIN;
|
||||||
|
}
|
||||||
|
|
||||||
|
ad[1].type = BT_DATA_CSIS_RSI;
|
||||||
|
ad[1].data_len = sizeof(csis->srv.psri);
|
||||||
|
ad[1].data = csis->srv.psri;
|
||||||
|
|
||||||
|
#if defined(CONFIG_BT_EXT_ADV)
|
||||||
|
struct bt_le_ext_adv_start_param start_param;
|
||||||
|
|
||||||
|
if (csis->srv.adv == NULL) {
|
||||||
|
struct bt_le_adv_param param;
|
||||||
|
|
||||||
|
(void)memset(¶m, 0, sizeof(param));
|
||||||
|
param.options |= BT_LE_ADV_OPT_CONNECTABLE;
|
||||||
|
param.options |= BT_LE_ADV_OPT_SCANNABLE;
|
||||||
|
param.options |= BT_LE_ADV_OPT_USE_NAME;
|
||||||
|
|
||||||
|
param.id = BT_ID_DEFAULT;
|
||||||
|
param.sid = 0;
|
||||||
|
param.interval_min = BT_GAP_ADV_FAST_INT_MIN_2;
|
||||||
|
param.interval_max = BT_GAP_ADV_FAST_INT_MAX_2;
|
||||||
|
|
||||||
|
err = bt_le_ext_adv_create(¶m, &csis->srv.adv_cb,
|
||||||
|
&csis->srv.adv);
|
||||||
|
if (err != 0) {
|
||||||
|
BT_DBG("Could not create adv set: %d", err);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = bt_le_ext_adv_set_data(csis->srv.adv, ad, ARRAY_SIZE(ad), NULL,
|
||||||
|
0);
|
||||||
|
|
||||||
|
if (err != 0) {
|
||||||
|
BT_DBG("Could not set adv data: %d", err);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
(void)memset(&start_param, 0, sizeof(start_param));
|
||||||
|
start_param.timeout = CSIS_ADV_TIME;
|
||||||
|
err = bt_le_ext_adv_start(csis->srv.adv, &start_param);
|
||||||
|
#else
|
||||||
|
err = bt_le_adv_start(BT_LE_ADV_CONN_NAME, ad, ARRAY_SIZE(ad), NULL, 0);
|
||||||
|
#endif /* CONFIG_BT_EXT_ADV */
|
||||||
|
|
||||||
|
if (err != 0) {
|
||||||
|
BT_DBG("Could not start adv: %d", err);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
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_csis_set_sirk enc_sirk;
|
||||||
|
struct bt_csis_set_sirk *sirk;
|
||||||
|
struct bt_csis *csis = attr->user_data;
|
||||||
|
|
||||||
|
if (csis_cbs != NULL && csis_cbs->sirk_read_req != NULL) {
|
||||||
|
uint8_t cb_rsp;
|
||||||
|
|
||||||
|
/* Ask higher layer for what SIRK to return, if any */
|
||||||
|
cb_rsp = csis_cbs->sirk_read_req(conn, &csis_insts[0]);
|
||||||
|
|
||||||
|
if (cb_rsp == BT_CSIS_READ_SIRK_REQ_RSP_ACCEPT) {
|
||||||
|
sirk = &csis->srv.set_sirk;
|
||||||
|
} else if (IS_ENABLED(CONFIG_BT_CSIS_ENC_SIRK_SUPPORT) &&
|
||||||
|
cb_rsp == BT_CSIS_READ_SIRK_REQ_RSP_ACCEPT_ENC) {
|
||||||
|
int err;
|
||||||
|
|
||||||
|
err = sirk_encrypt(conn, &csis->srv.set_sirk,
|
||||||
|
&enc_sirk);
|
||||||
|
if (err != 0) {
|
||||||
|
BT_ERR("Could not encrypt SIRK: %d",
|
||||||
|
err);
|
||||||
|
return BT_GATT_ERR(BT_ATT_ERR_UNLIKELY);
|
||||||
|
}
|
||||||
|
|
||||||
|
sirk = &enc_sirk;
|
||||||
|
BT_HEXDUMP_DBG(enc_sirk.value, sizeof(enc_sirk.value),
|
||||||
|
"Encrypted Set SIRK");
|
||||||
|
} else if (cb_rsp == BT_CSIS_READ_SIRK_REQ_RSP_REJECT) {
|
||||||
|
return BT_GATT_ERR(BT_CSIS_ERROR_SIRK_ACCESS_REJECTED);
|
||||||
|
} else if (cb_rsp == BT_CSIS_READ_SIRK_REQ_RSP_OOB_ONLY) {
|
||||||
|
return BT_GATT_ERR(BT_CSIS_ERROR_SIRK_OOB_ONLY);
|
||||||
|
}
|
||||||
|
|
||||||
|
BT_ERR("Invalid callback response: %u", cb_rsp);
|
||||||
|
return BT_GATT_ERR(BT_ATT_ERR_UNLIKELY);
|
||||||
|
}
|
||||||
|
|
||||||
|
sirk = &csis->srv.set_sirk;
|
||||||
|
|
||||||
|
BT_DBG("Set sirk %sencrypted",
|
||||||
|
sirk->type == BT_CSIS_SIRK_TYPE_PLAIN ? "not " : "");
|
||||||
|
BT_HEXDUMP_DBG(csis->srv.set_sirk.value,
|
||||||
|
sizeof(csis->srv.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_csis *csis = attr->user_data;
|
||||||
|
|
||||||
|
BT_DBG("%u", csis->srv.set_size);
|
||||||
|
|
||||||
|
return bt_gatt_attr_read(conn, attr, buf, len, offset,
|
||||||
|
&csis->srv.set_size,
|
||||||
|
sizeof(csis->srv.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_csis *csis = attr->user_data;
|
||||||
|
|
||||||
|
BT_DBG("%u", csis->srv.set_lock);
|
||||||
|
|
||||||
|
return bt_gatt_attr_read(conn, attr, buf, len, offset,
|
||||||
|
&csis->srv.set_lock,
|
||||||
|
sizeof(csis->srv.set_lock));
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
uint8_t val;
|
||||||
|
bool notify;
|
||||||
|
struct bt_csis *csis = attr->user_data;
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
if (val != BT_CSIS_RELEASE_VALUE && val != BT_CSIS_LOCK_VALUE) {
|
||||||
|
return BT_GATT_ERR(BT_CSIS_ERROR_LOCK_INVAL_VALUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (csis->srv.set_lock == BT_CSIS_LOCK_VALUE) {
|
||||||
|
if (val == BT_CSIS_LOCK_VALUE) {
|
||||||
|
if (is_last_client_to_write(csis, conn)) {
|
||||||
|
return BT_GATT_ERR(
|
||||||
|
BT_CSIS_ERROR_LOCK_ALREADY_GRANTED);
|
||||||
|
} else {
|
||||||
|
return BT_GATT_ERR(BT_CSIS_ERROR_LOCK_DENIED);
|
||||||
|
}
|
||||||
|
} else if (!is_last_client_to_write(csis, conn)) {
|
||||||
|
return BT_GATT_ERR(BT_CSIS_ERROR_LOCK_RELEASE_DENIED);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
notify = csis->srv.set_lock != val;
|
||||||
|
|
||||||
|
csis->srv.set_lock = val;
|
||||||
|
if (csis->srv.set_lock == BT_CSIS_LOCK_VALUE) {
|
||||||
|
if (conn != NULL) {
|
||||||
|
bt_addr_le_copy(&csis->srv.lock_client_addr,
|
||||||
|
bt_conn_get_dst(conn));
|
||||||
|
}
|
||||||
|
(void)k_work_reschedule(&csis->srv.set_lock_timer,
|
||||||
|
CSIS_SET_LOCK_TIMER_VALUE);
|
||||||
|
} else {
|
||||||
|
(void)memset(&csis->srv.lock_client_addr, 0,
|
||||||
|
sizeof(csis->srv.lock_client_addr));
|
||||||
|
(void)k_work_cancel_delayable(&csis->srv.set_lock_timer);
|
||||||
|
}
|
||||||
|
|
||||||
|
BT_DBG("%u", csis->srv.set_lock);
|
||||||
|
|
||||||
|
if (notify) {
|
||||||
|
/*
|
||||||
|
* The Spec states that all clients, except for the
|
||||||
|
* client writing the value, shall be notified
|
||||||
|
* (if subscribed)
|
||||||
|
*/
|
||||||
|
notify_clients(csis, conn);
|
||||||
|
|
||||||
|
if (csis_cbs != NULL && csis_cbs->lock_changed != NULL) {
|
||||||
|
bool locked = csis->srv.set_lock == BT_CSIS_LOCK_VALUE;
|
||||||
|
|
||||||
|
csis_cbs->lock_changed(conn, csis, locked);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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_csis *csis = attr->user_data;
|
||||||
|
|
||||||
|
BT_DBG("%u", csis->srv.rank);
|
||||||
|
|
||||||
|
return bt_gatt_attr_read(conn, attr, buf, len, offset,
|
||||||
|
&csis->srv.rank,
|
||||||
|
sizeof(csis->srv.rank));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
static void set_lock_timer_handler(struct k_work *work)
|
||||||
|
{
|
||||||
|
struct k_work_delayable *delayable;
|
||||||
|
struct bt_csis_server *server;
|
||||||
|
struct bt_csis *csis;
|
||||||
|
|
||||||
|
delayable = CONTAINER_OF(work, struct k_work_delayable, work);
|
||||||
|
server = CONTAINER_OF(delayable, struct bt_csis_server, set_lock_timer);
|
||||||
|
csis = CONTAINER_OF(server, struct bt_csis, srv);
|
||||||
|
|
||||||
|
BT_DBG("Lock timeout, releasing");
|
||||||
|
csis->srv.set_lock = BT_CSIS_RELEASE_VALUE;
|
||||||
|
notify_clients(csis, NULL);
|
||||||
|
|
||||||
|
if (csis_cbs != NULL && csis_cbs->lock_changed != NULL) {
|
||||||
|
bool locked = csis->srv.set_lock == BT_CSIS_LOCK_VALUE;
|
||||||
|
|
||||||
|
csis_cbs->lock_changed(NULL, csis, locked);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void csis_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(csis_insts); i++) {
|
||||||
|
struct bt_csis *csis = &csis_insts[i];
|
||||||
|
|
||||||
|
for (int j = 0; j < ARRAY_SIZE(csis->srv.pend_notify); j++) {
|
||||||
|
struct csis_pending_notifications *pend_notify;
|
||||||
|
|
||||||
|
pend_notify = &csis->srv.pend_notify[j];
|
||||||
|
|
||||||
|
if (pend_notify->pending &&
|
||||||
|
bt_addr_le_cmp(bt_conn_get_dst(conn),
|
||||||
|
&pend_notify->addr) == 0) {
|
||||||
|
notify_lock_value(csis, conn);
|
||||||
|
pend_notify->pending = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#if defined(CONFIG_BT_EXT_ADV)
|
||||||
|
static void csis_connected(struct bt_conn *conn, uint8_t err)
|
||||||
|
{
|
||||||
|
if (err == BT_HCI_ERR_SUCCESS) {
|
||||||
|
for (int i = 0; i < ARRAY_SIZE(csis_insts); i++) {
|
||||||
|
struct bt_csis *csis = &csis_insts[i];
|
||||||
|
|
||||||
|
csis->srv.conn_cnt++;
|
||||||
|
|
||||||
|
__ASSERT(csis->srv.conn_cnt <= CONFIG_BT_MAX_CONN,
|
||||||
|
"Invalid csis->srv.conn_cnt value");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void disconnect_adv(struct k_work *work)
|
||||||
|
{
|
||||||
|
int err;
|
||||||
|
struct bt_csis_server *server = CONTAINER_OF(work, struct bt_csis_server, work);
|
||||||
|
struct bt_csis *csis = CONTAINER_OF(server, struct bt_csis, srv);
|
||||||
|
|
||||||
|
err = csis_adv_resume(csis);
|
||||||
|
|
||||||
|
if (err != 0) {
|
||||||
|
BT_ERR("Disconnect: Could not restart advertising: %d",
|
||||||
|
err);
|
||||||
|
csis->srv.adv_enabled = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif /* CONFIG_BT_EXT_ADV */
|
||||||
|
|
||||||
|
static void handle_csis_disconnect(struct bt_csis *csis, struct bt_conn *conn)
|
||||||
|
{
|
||||||
|
#if defined(CONFIG_BT_EXT_ADV)
|
||||||
|
__ASSERT(csis->srv.conn_cnt != 0, "Invalid csis->srv.conn_cnt value");
|
||||||
|
|
||||||
|
if (csis->srv.conn_cnt == CONFIG_BT_MAX_CONN &&
|
||||||
|
csis->srv.adv_enabled) {
|
||||||
|
/* A connection spot opened up */
|
||||||
|
k_work_submit(&csis->srv.work);
|
||||||
|
}
|
||||||
|
csis->srv.conn_cnt--;
|
||||||
|
#endif /* CONFIG_BT_EXT_ADV */
|
||||||
|
|
||||||
|
BT_DBG("Non-bonded device");
|
||||||
|
if (is_last_client_to_write(csis, conn)) {
|
||||||
|
(void)memset(&csis->srv.lock_client_addr, 0,
|
||||||
|
sizeof(csis->srv.lock_client_addr));
|
||||||
|
csis->srv.set_lock = BT_CSIS_RELEASE_VALUE;
|
||||||
|
notify_clients(csis, NULL);
|
||||||
|
|
||||||
|
if (csis_cbs != NULL && csis_cbs->lock_changed != NULL) {
|
||||||
|
bool locked = csis->srv.set_lock == BT_CSIS_LOCK_VALUE;
|
||||||
|
|
||||||
|
csis_cbs->lock_changed(conn, csis, locked);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Check if the disconnected device once was bonded and stored
|
||||||
|
* here as a bonded device
|
||||||
|
*/
|
||||||
|
for (int i = 0; i < ARRAY_SIZE(csis->srv.pend_notify); i++) {
|
||||||
|
struct csis_pending_notifications *pend_notify;
|
||||||
|
|
||||||
|
pend_notify = &csis->srv.pend_notify[i];
|
||||||
|
|
||||||
|
if (bt_addr_le_cmp(bt_conn_get_dst(conn),
|
||||||
|
&pend_notify->addr) == 0) {
|
||||||
|
(void)memset(pend_notify, 0, sizeof(*pend_notify));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void csis_disconnected(struct bt_conn *conn, uint8_t reason)
|
||||||
|
{
|
||||||
|
BT_DBG("Disconnected: %s (reason %u)",
|
||||||
|
bt_addr_le_str(bt_conn_get_dst(conn)), reason);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If lock was taken by non-bonded device, set lock to released value,
|
||||||
|
* and notify other connections.
|
||||||
|
*/
|
||||||
|
if (!bt_addr_le_is_bonded(conn->id, &conn->le.dst)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < ARRAY_SIZE(csis_insts); i++) {
|
||||||
|
handle_csis_disconnect(&csis_insts[i], conn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void handle_csis_auth_complete(struct bt_csis *csis,
|
||||||
|
struct bt_conn *conn)
|
||||||
|
{
|
||||||
|
/* Check if already in list, and do nothing if it is */
|
||||||
|
for (int i = 0; i < ARRAY_SIZE(csis->srv.pend_notify); i++) {
|
||||||
|
struct csis_pending_notifications *pend_notify;
|
||||||
|
|
||||||
|
pend_notify = &csis->srv.pend_notify[i];
|
||||||
|
|
||||||
|
if (pend_notify->active &&
|
||||||
|
bt_addr_le_cmp(bt_conn_get_dst(conn),
|
||||||
|
&pend_notify->addr) == 0) {
|
||||||
|
#if IS_ENABLED(CONFIG_BT_KEYS_OVERWRITE_OLDEST)
|
||||||
|
pend_notify->age = csis->srv.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(csis->srv.pend_notify); i++) {
|
||||||
|
struct csis_pending_notifications *pend_notify;
|
||||||
|
|
||||||
|
pend_notify = &csis->srv.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 = csis->srv.age_counter++;
|
||||||
|
#endif /* CONFIG_BT_KEYS_OVERWRITE_OLDEST */
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#if IS_ENABLED(CONFIG_BT_KEYS_OVERWRITE_OLDEST)
|
||||||
|
struct csis_pending_notifications *oldest;
|
||||||
|
|
||||||
|
oldest = &csis->srv.pend_notify[0];
|
||||||
|
|
||||||
|
for (int i = 1; i < ARRAY_SIZE(csis->srv.pend_notify); i++) {
|
||||||
|
struct csis_pending_notifications *pend_notify;
|
||||||
|
|
||||||
|
pend_notify = &csis->srv.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 = csis->srv.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(csis_insts); i++) {
|
||||||
|
handle_csis_auth_complete(&csis_insts[i], conn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void csis_bond_deleted(uint8_t id, const bt_addr_le_t *peer)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < ARRAY_SIZE(csis_insts); i++) {
|
||||||
|
struct bt_csis *csis = &csis_insts[i];
|
||||||
|
|
||||||
|
for (int j = 0; j < ARRAY_SIZE(csis->srv.pend_notify); j++) {
|
||||||
|
struct csis_pending_notifications *pend_notify;
|
||||||
|
|
||||||
|
pend_notify = &csis->srv.pend_notify[j];
|
||||||
|
|
||||||
|
if (pend_notify->active &&
|
||||||
|
bt_addr_le_cmp(peer, &pend_notify->addr) == 0) {
|
||||||
|
(void)memset(pend_notify, 0,
|
||||||
|
sizeof(*pend_notify));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct bt_conn_cb conn_callbacks = {
|
||||||
|
|
||||||
|
#if defined(CONFIG_BT_EXT_ADV)
|
||||||
|
.connected = csis_connected,
|
||||||
|
#endif /* CONFIG_BT_EXT_ADV */
|
||||||
|
.disconnected = csis_disconnected,
|
||||||
|
.security_changed = csis_security_changed,
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct bt_conn_auth_cb auth_callbacks = {
|
||||||
|
.pairing_complete = auth_pairing_complete,
|
||||||
|
.bond_deleted = csis_bond_deleted
|
||||||
|
};
|
||||||
|
|
||||||
|
#if defined(CONFIG_BT_EXT_ADV)
|
||||||
|
/* TODO: Temp fix due to bug in adv callbacks:
|
||||||
|
* https://github.com/zephyrproject-rtos/zephyr/issues/30699
|
||||||
|
*/
|
||||||
|
static bool conn_based_timeout;
|
||||||
|
static void adv_timeout(struct bt_le_ext_adv *adv,
|
||||||
|
struct bt_le_ext_adv_sent_info *info)
|
||||||
|
{
|
||||||
|
struct bt_csis *csis = NULL;
|
||||||
|
|
||||||
|
for (int i = 0; i < ARRAY_SIZE(csis_insts); i++) {
|
||||||
|
if (adv == csis_insts[i].srv.adv) {
|
||||||
|
csis = &csis_insts[i];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
__ASSERT(csis != NULL, "Could not find CSIS instance by ADV set %p",
|
||||||
|
adv);
|
||||||
|
|
||||||
|
if (conn_based_timeout) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
conn_based_timeout = false;
|
||||||
|
|
||||||
|
/* Restart to update RSI value with new private address */
|
||||||
|
if (csis->srv.adv_enabled) {
|
||||||
|
int err = csis_adv_resume(csis);
|
||||||
|
|
||||||
|
if (err != 0) {
|
||||||
|
BT_ERR("Timeout: Could not restart advertising: %d",
|
||||||
|
err);
|
||||||
|
csis->srv.adv_enabled = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void adv_connected(struct bt_le_ext_adv *adv,
|
||||||
|
struct bt_le_ext_adv_connected_info *info)
|
||||||
|
{
|
||||||
|
struct bt_csis *csis = NULL;
|
||||||
|
|
||||||
|
for (int i = 0; i < ARRAY_SIZE(csis_insts); i++) {
|
||||||
|
if (adv == csis_insts[i].srv.adv) {
|
||||||
|
csis = &csis_insts[i];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
__ASSERT(csis != NULL, "Could not find CSIS instance by ADV set %p",
|
||||||
|
adv);
|
||||||
|
|
||||||
|
if (csis->srv.conn_cnt < CONFIG_BT_MAX_CONN &&
|
||||||
|
csis->srv.adv_enabled) {
|
||||||
|
int err = csis_adv_resume(csis);
|
||||||
|
|
||||||
|
if (err != 0) {
|
||||||
|
BT_ERR("Connected: Could not restart advertising: %d",
|
||||||
|
err);
|
||||||
|
csis->srv.adv_enabled = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
conn_based_timeout = true;
|
||||||
|
}
|
||||||
|
#endif /* CONFIG_BT_EXT_ADV */
|
||||||
|
|
||||||
|
#define BT_CSIS_SERVICE_DEFINITION(_csis) {\
|
||||||
|
BT_GATT_PRIMARY_SERVICE(BT_UUID_CSIS), \
|
||||||
|
BT_GATT_CHARACTERISTIC(BT_UUID_CSIS_SET_SIRK, \
|
||||||
|
BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY, \
|
||||||
|
SIRK_READ_PERM, \
|
||||||
|
read_set_sirk, NULL, &_csis), \
|
||||||
|
BT_GATT_CCC(set_sirk_cfg_changed, \
|
||||||
|
BT_GATT_PERM_READ | BT_GATT_PERM_WRITE_ENCRYPT), \
|
||||||
|
BT_GATT_CHARACTERISTIC(BT_UUID_CSIS_SET_SIZE, \
|
||||||
|
BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY, \
|
||||||
|
BT_GATT_PERM_READ_ENCRYPT, \
|
||||||
|
read_set_size, NULL, &_csis), \
|
||||||
|
BT_GATT_CCC(set_size_cfg_changed, \
|
||||||
|
BT_GATT_PERM_READ | BT_GATT_PERM_WRITE_ENCRYPT), \
|
||||||
|
BT_GATT_CHARACTERISTIC(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, &_csis), \
|
||||||
|
BT_GATT_CCC(set_lock_cfg_changed, \
|
||||||
|
BT_GATT_PERM_READ | BT_GATT_PERM_WRITE_ENCRYPT), \
|
||||||
|
BT_GATT_CHARACTERISTIC(BT_UUID_CSIS_RANK, \
|
||||||
|
BT_GATT_CHRC_READ, \
|
||||||
|
BT_GATT_PERM_READ_ENCRYPT, \
|
||||||
|
read_rank, NULL, &_csis) \
|
||||||
|
}
|
||||||
|
|
||||||
|
BT_GATT_SERVICE_INSTANCE_DEFINE(csis_service_list, csis_insts,
|
||||||
|
CONFIG_BT_CSIS_MAX_INSTANCE_COUNT,
|
||||||
|
BT_CSIS_SERVICE_DEFINITION);
|
||||||
|
|
||||||
|
/****************************** Public API ******************************/
|
||||||
|
void *bt_csis_svc_decl_get(const struct bt_csis *csis)
|
||||||
|
{
|
||||||
|
return csis->srv.service_p->attrs;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool valid_register_param(const struct bt_csis_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 (param->set_size > 0 && param->set_size < BT_CSIS_MINIMUM_SET_SIZE) {
|
||||||
|
BT_DBG("Invalid set size: %u", param->set_size);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
int bt_csis_register(const struct bt_csis_register_param *param,
|
||||||
|
struct bt_csis **csis)
|
||||||
|
{
|
||||||
|
static uint8_t instance_cnt;
|
||||||
|
struct bt_csis *inst;
|
||||||
|
int err;
|
||||||
|
|
||||||
|
if (instance_cnt == ARRAY_SIZE(csis_insts)) {
|
||||||
|
return -ENOMEM;
|
||||||
|
}
|
||||||
|
|
||||||
|
CHECKIF(param == NULL) {
|
||||||
|
BT_DBG("NULL param");
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
CHECKIF(!valid_register_param(param)) {
|
||||||
|
BT_DBG("Invalid parameters");
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
inst = &csis_insts[instance_cnt];
|
||||||
|
inst->srv.service_p = &csis_service_list[instance_cnt];
|
||||||
|
instance_cnt++;
|
||||||
|
|
||||||
|
bt_conn_cb_register(&conn_callbacks);
|
||||||
|
bt_conn_auth_cb_register(&auth_callbacks);
|
||||||
|
|
||||||
|
err = bt_gatt_service_register(inst->srv.service_p);
|
||||||
|
if (err != 0) {
|
||||||
|
BT_DBG("CSIS service register failed: %d", err);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
k_work_init_delayable(&inst->srv.set_lock_timer,
|
||||||
|
set_lock_timer_handler);
|
||||||
|
inst->srv.rank = param->rank;
|
||||||
|
inst->srv.set_size = param->set_size;
|
||||||
|
inst->srv.set_lock = BT_CSIS_RELEASE_VALUE;
|
||||||
|
inst->srv.set_sirk.type = BT_CSIS_SIRK_TYPE_PLAIN;
|
||||||
|
|
||||||
|
if (IS_ENABLED(CONFIG_BT_CSIS_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->srv.set_sirk.value, test_sirk,
|
||||||
|
sizeof(test_sirk));
|
||||||
|
BT_DBG("CSIS SIRK was overwritten by sample data SIRK");
|
||||||
|
} else {
|
||||||
|
(void)memcpy(inst->srv.set_sirk.value, param->set_sirk,
|
||||||
|
sizeof(inst->srv.set_sirk.value));
|
||||||
|
}
|
||||||
|
|
||||||
|
#if defined(CONFIG_BT_EXT_ADV)
|
||||||
|
inst->srv.adv_cb.sent = adv_timeout;
|
||||||
|
inst->srv.adv_cb.connected = adv_connected;
|
||||||
|
k_work_init(&inst->srv.work, disconnect_adv);
|
||||||
|
#endif /* CONFIG_BT_EXT_ADV */
|
||||||
|
|
||||||
|
*csis = inst;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int bt_csis_advertise(struct bt_csis *csis, bool enable)
|
||||||
|
{
|
||||||
|
int err;
|
||||||
|
|
||||||
|
if (enable) {
|
||||||
|
if (csis->srv.adv_enabled) {
|
||||||
|
return -EALREADY;
|
||||||
|
}
|
||||||
|
|
||||||
|
err = csis_adv_resume(csis);
|
||||||
|
|
||||||
|
if (err != 0) {
|
||||||
|
BT_DBG("Could not start adv: %d", err);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
csis->srv.adv_enabled = true;
|
||||||
|
} else {
|
||||||
|
if (!csis->srv.adv_enabled) {
|
||||||
|
return -EALREADY;
|
||||||
|
}
|
||||||
|
#if defined(CONFIG_BT_EXT_ADV)
|
||||||
|
err = bt_le_ext_adv_stop(csis->srv.adv);
|
||||||
|
#else
|
||||||
|
err = bt_le_adv_stop();
|
||||||
|
#endif /* CONFIG_BT_EXT_ADV */
|
||||||
|
if (err != 0) {
|
||||||
|
BT_DBG("Could not stop start adv: %d", err);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
csis->srv.adv_enabled = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
int bt_csis_lock(struct bt_csis *csis, bool lock, bool force)
|
||||||
|
{
|
||||||
|
uint8_t lock_val;
|
||||||
|
int err = 0;
|
||||||
|
|
||||||
|
if (lock) {
|
||||||
|
lock_val = BT_CSIS_LOCK_VALUE;
|
||||||
|
} else {
|
||||||
|
lock_val = BT_CSIS_RELEASE_VALUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!lock && force) {
|
||||||
|
csis->srv.set_lock = BT_CSIS_RELEASE_VALUE;
|
||||||
|
notify_clients(csis, NULL);
|
||||||
|
|
||||||
|
if (csis_cbs != NULL && csis_cbs->lock_changed != NULL) {
|
||||||
|
csis_cbs->lock_changed(NULL, &csis_insts[0], false);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err = write_set_lock(NULL, NULL, &lock_val, sizeof(lock_val), 0,
|
||||||
|
0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (err < 0) {
|
||||||
|
return err;
|
||||||
|
} else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void bt_csis_print_sirk(const struct bt_csis *csis)
|
||||||
|
{
|
||||||
|
BT_HEXDUMP_DBG(&csis->srv.set_sirk, sizeof(csis->srv.set_sirk),
|
||||||
|
"Set SIRK");
|
||||||
|
}
|
183
subsys/bluetooth/audio/csis.h
Normal file
183
subsys/bluetooth/audio/csis.h
Normal file
|
@ -0,0 +1,183 @@
|
||||||
|
/**
|
||||||
|
* @file
|
||||||
|
* @brief APIs for Bluetooth CSIS
|
||||||
|
*
|
||||||
|
* Copyright (c) 2019 Bose Corporation
|
||||||
|
* Copyright (c) 2020-2021 Nordic Semiconductor ASA
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef ZEPHYR_SUBSYS_BLUETOOTH_AUDIO_CSIS_H_
|
||||||
|
#define ZEPHYR_SUBSYS_BLUETOOTH_AUDIO_CSIS_H_
|
||||||
|
|
||||||
|
#include <zephyr/types.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <bluetooth/conn.h>
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define BT_CSIS_MINIMUM_SET_SIZE 2
|
||||||
|
#define BT_CSIS_PSRI_SIZE 6
|
||||||
|
|
||||||
|
/** Accept the request to read the SIRK as plaintext */
|
||||||
|
#define BT_CSIS_READ_SIRK_REQ_RSP_ACCEPT 0x00
|
||||||
|
/** Accept the request to read the SIRK, but return encrypted SIRK */
|
||||||
|
#define BT_CSIS_READ_SIRK_REQ_RSP_ACCEPT_ENC 0x01
|
||||||
|
/** Reject the request to read the SIRK */
|
||||||
|
#define BT_CSIS_READ_SIRK_REQ_RSP_REJECT 0x02
|
||||||
|
/** SIRK is available only via an OOB procedure */
|
||||||
|
#define BT_CSIS_READ_SIRK_REQ_RSP_OOB_ONLY 0x03
|
||||||
|
|
||||||
|
#define BT_CSIS_SET_SIRK_SIZE 16
|
||||||
|
|
||||||
|
#define BT_CSIS_ERROR_LOCK_DENIED 0x80
|
||||||
|
#define BT_CSIS_ERROR_LOCK_RELEASE_DENIED 0x81
|
||||||
|
#define BT_CSIS_ERROR_LOCK_INVAL_VALUE 0x82
|
||||||
|
#define BT_CSIS_ERROR_SIRK_ACCESS_REJECTED 0x83
|
||||||
|
#define BT_CSIS_ERROR_SIRK_OOB_ONLY 0x84
|
||||||
|
#define BT_CSIS_ERROR_LOCK_ALREADY_GRANTED 0x85
|
||||||
|
|
||||||
|
#define BT_CSIS_RELEASE_VALUE 0x01
|
||||||
|
#define BT_CSIS_LOCK_VALUE 0x02
|
||||||
|
|
||||||
|
#define BT_CSIS_SIRK_TYPE_ENCRYPTED 0x00
|
||||||
|
#define BT_CSIS_SIRK_TYPE_PLAIN 0x01
|
||||||
|
|
||||||
|
/** @brief Opaque Coordinated Set Identification Service instance. */
|
||||||
|
struct bt_csis;
|
||||||
|
|
||||||
|
struct bt_csis_cb {
|
||||||
|
/**
|
||||||
|
* @brief Callback whenever the lock changes on the server.
|
||||||
|
*
|
||||||
|
* @param conn The connection to the client that changed the lock.
|
||||||
|
* NULL if server changed it, either by calling
|
||||||
|
* @ref csis_lock or by timeout.
|
||||||
|
* @param csis Pointer to the Coordinated Set Identification Service.
|
||||||
|
* @param locked Whether the lock was locked or released.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
void (*lock_changed)(struct bt_conn *conn, struct bt_csis *csis,
|
||||||
|
bool locked);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Request from a peer device to read the sirk.
|
||||||
|
*
|
||||||
|
* If this callback is not set, all clients will be allowed to read
|
||||||
|
* the SIRK unencrypted.
|
||||||
|
*
|
||||||
|
* @param conn The connection to the client that requested to read the
|
||||||
|
* SIRK.
|
||||||
|
* @param csis Pointer to the Coordinated Set Identification Service.
|
||||||
|
*
|
||||||
|
* @return A BT_CSIS_READ_SIRK_REQ_RSP_* response code.
|
||||||
|
*/
|
||||||
|
uint8_t (*sirk_read_req)(struct bt_conn *conn, struct bt_csis *csis);
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Register structure for Coordinated Set Identification Service */
|
||||||
|
struct bt_csis_register_param {
|
||||||
|
/**
|
||||||
|
* @brief Size of the set.
|
||||||
|
*
|
||||||
|
* If set to 0, the set size characteric won't be initialized.
|
||||||
|
* Otherwise shall be set to minimum 2.
|
||||||
|
*/
|
||||||
|
uint8_t set_size;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The unique Set Identity Resolving Key (SIRK)
|
||||||
|
*
|
||||||
|
* This shall be unique between different sets, and shall be the same
|
||||||
|
* for each set member for each set.
|
||||||
|
*/
|
||||||
|
uint8_t set_sirk[BT_CSIS_SET_SIRK_SIZE];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Boolean to set whether the set is lockable by clients
|
||||||
|
*
|
||||||
|
* Setting this to false will disable the lock characteristic.
|
||||||
|
*/
|
||||||
|
bool lockable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Rank of this device in this set.
|
||||||
|
*
|
||||||
|
* If the lockable parameter is set to true, this shall be > 0 and
|
||||||
|
* <= to the set_size. If the lockable parameter is set to false, this
|
||||||
|
* may be set to 0 to disable the rank characteristic.
|
||||||
|
*/
|
||||||
|
uint8_t rank;
|
||||||
|
|
||||||
|
/** Pointer to the callback structure. */
|
||||||
|
struct bt_csis_cb *cb;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the service declaration attribute.
|
||||||
|
*
|
||||||
|
* The first service attribute can be included in any other GATT service.
|
||||||
|
*
|
||||||
|
* @param csis Pointer to the Coordinated Set Identification Service.
|
||||||
|
*
|
||||||
|
* @return The first CSIS attribute instance.
|
||||||
|
*/
|
||||||
|
void *bt_csis_svc_decl_get(const struct bt_csis *csis);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Register the Coordinated Set Identification Service.
|
||||||
|
*
|
||||||
|
* This will register and enable the service and make it discoverable by
|
||||||
|
* clients.
|
||||||
|
*
|
||||||
|
* This shall only be done as a server.
|
||||||
|
*
|
||||||
|
* @param param Coordinated Set Identification Service register parameters.
|
||||||
|
* @param[out] csis Pointer to the registered Coordinated Set Identification
|
||||||
|
* Service.
|
||||||
|
*
|
||||||
|
* @return 0 if success, errno on failure.
|
||||||
|
*/
|
||||||
|
int bt_csis_register(const struct bt_csis_register_param *param,
|
||||||
|
struct bt_csis **csis);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Print the sirk to the debug output
|
||||||
|
*
|
||||||
|
* @param csis Pointer to the Coordinated Set Identification Service.
|
||||||
|
*/
|
||||||
|
void bt_csis_print_sirk(const struct bt_csis *csis);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Starts advertising the PRSI value.
|
||||||
|
*
|
||||||
|
* This cannot be used with other connectable advertising sets.
|
||||||
|
*
|
||||||
|
* @param csis Pointer to the Coordinated Set Identification Service.
|
||||||
|
* @param enable If true start advertising, if false stop advertising
|
||||||
|
*
|
||||||
|
* @return int 0 if on success, ERRNO on error.
|
||||||
|
*/
|
||||||
|
int bt_csis_advertise(struct bt_csis *csis, bool enable);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Locks the sets on the server.
|
||||||
|
*
|
||||||
|
* @param csis Pointer to the Coordinated Set Identification Service.
|
||||||
|
* @param lock If true lock the set, if false release the set.
|
||||||
|
* @param force This argument only have meaning when @p lock is false
|
||||||
|
* (release) and will force release the lock, regardless of who
|
||||||
|
* took the lock.
|
||||||
|
*
|
||||||
|
* @return 0 on success, GATT error on error.
|
||||||
|
*/
|
||||||
|
int bt_csis_lock(struct bt_csis *csis, bool lock, bool force);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* ZEPHYR_SUBSYS_BLUETOOTH_AUDIO_CSIS_H_ */
|
257
subsys/bluetooth/audio/csis_crypto.c
Normal file
257
subsys/bluetooth/audio/csis_crypto.c
Normal file
|
@ -0,0 +1,257 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2020 Bose Corporation
|
||||||
|
* Copyright (c) 2021 Nordic Semiconductor ASA
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*
|
||||||
|
* The static functions in this file operate on Big Endian (BE) as the
|
||||||
|
* underlying encryption library is BE as well. Furthermore, the sample data
|
||||||
|
* in the CSIS spec is also provided as BE, and logging values as BE will make
|
||||||
|
* it easier to compare.
|
||||||
|
*/
|
||||||
|
#include "csis_crypto.h"
|
||||||
|
#include <bluetooth/crypto.h>
|
||||||
|
#include <tinycrypt/constants.h>
|
||||||
|
#include <tinycrypt/utils.h>
|
||||||
|
#include <tinycrypt/aes.h>
|
||||||
|
#include <tinycrypt/cmac_mode.h>
|
||||||
|
#include <tinycrypt/ccm_mode.h>
|
||||||
|
#include <sys/byteorder.h>
|
||||||
|
|
||||||
|
#define BT_DBG_ENABLED IS_ENABLED(CONFIG_BT_DEBUG_CSIS_CRYPTO)
|
||||||
|
#define LOG_MODULE_NAME bt_csis_crypto
|
||||||
|
#include "common/log.h"
|
||||||
|
|
||||||
|
#define BT_CSIS_CRYPTO_PADDING_SIZE 13
|
||||||
|
#define BT_CSIS_R_SIZE 3 /* r is 24 bit / 3 octet */
|
||||||
|
#define BT_CSIS_R_MASK BIT_MASK(24) /* r is 24 bit / 3 octet */
|
||||||
|
|
||||||
|
static int aes_cmac(const uint8_t key[BT_CSIS_CRYPTO_KEY_SIZE],
|
||||||
|
const uint8_t *in, size_t in_len, uint8_t *out)
|
||||||
|
{
|
||||||
|
struct tc_aes_key_sched_struct sched;
|
||||||
|
struct tc_cmac_struct state;
|
||||||
|
|
||||||
|
/* TODO: Copy of the aes_cmac from smp.c: Can we merge them? */
|
||||||
|
|
||||||
|
if (tc_cmac_setup(&state, key, &sched) == TC_CRYPTO_FAIL) {
|
||||||
|
return -EIO;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tc_cmac_update(&state, in, in_len) == TC_CRYPTO_FAIL) {
|
||||||
|
return -EIO;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tc_cmac_final(out, &state) == TC_CRYPTO_FAIL) {
|
||||||
|
return -EIO;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void xor_128(const uint8_t a[16], const uint8_t b[16], uint8_t out[16])
|
||||||
|
{
|
||||||
|
size_t len = 16;
|
||||||
|
/* TODO: Identical to the xor_128 from smp.c: Move to util */
|
||||||
|
|
||||||
|
while (len--) {
|
||||||
|
*out++ = *a++ ^ *b++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int bt_csis_sih(const uint8_t sirk[BT_CSIS_SET_SIRK_SIZE], uint32_t r,
|
||||||
|
uint32_t *out)
|
||||||
|
{
|
||||||
|
uint8_t res[16]; /* need to store 128 bit */
|
||||||
|
int err;
|
||||||
|
uint8_t sirk_tmp[BT_CSIS_SET_SIRK_SIZE];
|
||||||
|
|
||||||
|
if ((r & BIT(23)) || ((r & BIT(22)) == 0)) {
|
||||||
|
BT_DBG("Invalid r %0x06x", (uint32_t)(r & BT_CSIS_R_MASK));
|
||||||
|
}
|
||||||
|
|
||||||
|
BT_DBG("SIRK %s", bt_hex(sirk, BT_CSIS_SET_SIRK_SIZE));
|
||||||
|
BT_DBG("r 0x%06x", r);
|
||||||
|
|
||||||
|
/* r' = padding || r */
|
||||||
|
(void)memset(res, 0, BT_CSIS_CRYPTO_PADDING_SIZE);
|
||||||
|
sys_put_be24(r, res + BT_CSIS_CRYPTO_PADDING_SIZE);
|
||||||
|
|
||||||
|
BT_DBG("BE: r' %s", bt_hex(res, sizeof(res)));
|
||||||
|
|
||||||
|
if (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) {
|
||||||
|
/* Swap to Big Endian (BE) */
|
||||||
|
sys_memcpy_swap(sirk_tmp, sirk, BT_CSIS_SET_SIRK_SIZE);
|
||||||
|
} else {
|
||||||
|
(void)memcpy(sirk_tmp, sirk, BT_CSIS_SET_SIRK_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
err = bt_encrypt_be(sirk_tmp, res, res);
|
||||||
|
|
||||||
|
if (err != 0) {
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The output of the function sih is:
|
||||||
|
* sih(k, r) = e(k, r') mod 2^24
|
||||||
|
* The output of the security function e is then truncated to 24 bits
|
||||||
|
* by taking the least significant 24 bits of the output of e as the
|
||||||
|
* result of sih.
|
||||||
|
*/
|
||||||
|
|
||||||
|
BT_DBG("BE: res %s", bt_hex(res, sizeof(res)));
|
||||||
|
|
||||||
|
/* Result is the lowest 3 bytes */
|
||||||
|
*out = sys_get_be24(res + 13);
|
||||||
|
|
||||||
|
BT_DBG("sih 0x%06x", *out);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief k1 derivation function
|
||||||
|
*
|
||||||
|
* The key derivation function k1 is used to derive a key. The derived key is
|
||||||
|
* used to encrypt and decrypt the value of the Set Identity Resolving Key
|
||||||
|
* characteristic.
|
||||||
|
*
|
||||||
|
* @param n n is 0 or more bytes.
|
||||||
|
* @param n_size Number of bytes in @p n.
|
||||||
|
* @param salt A 16-byte salt.
|
||||||
|
* @param p p is 0 or more bytes.
|
||||||
|
* @param p_size Number of bytes in @p p.
|
||||||
|
* @param out A 16-byte output buffer.
|
||||||
|
* @return int 0 on success, any other value indicates a failure.
|
||||||
|
*/
|
||||||
|
static int k1(const uint8_t *n, size_t n_size,
|
||||||
|
const uint8_t salt[BT_CSIS_CRYPTO_SALT_SIZE],
|
||||||
|
const uint8_t *p, size_t p_size, uint8_t out[16])
|
||||||
|
{
|
||||||
|
/* TODO: This is basically a duplicate of bt_mesh_k1 - Perhaps they can
|
||||||
|
* be merged
|
||||||
|
*/
|
||||||
|
uint8_t t[16];
|
||||||
|
int err;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* T = AES_CMAC_SALT(N)
|
||||||
|
*
|
||||||
|
* k1(N, SALT, P) = AES-CMAC_T(P)
|
||||||
|
*/
|
||||||
|
|
||||||
|
BT_DBG("BE: n %s", bt_hex(n, n_size));
|
||||||
|
BT_DBG("BE: salt %s", bt_hex(salt, BT_CSIS_CRYPTO_SALT_SIZE));
|
||||||
|
BT_DBG("BE: p %s", bt_hex(p, p_size));
|
||||||
|
|
||||||
|
err = aes_cmac(salt, n, n_size, t);
|
||||||
|
|
||||||
|
BT_DBG("BE: t %s", bt_hex(t, sizeof(t)));
|
||||||
|
|
||||||
|
if (err) {
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
err = aes_cmac(t, p, p_size, out);
|
||||||
|
|
||||||
|
BT_DBG("BE: out %s", bt_hex(out, 16));
|
||||||
|
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief s1 SALT generation function
|
||||||
|
*
|
||||||
|
* @param m A non-zero length octet array or ASCII encoded string
|
||||||
|
* @param m_size Size of @p m.
|
||||||
|
* @param out 16-byte output buffer.
|
||||||
|
* @return int 0 on success, any other value indicates a failure.
|
||||||
|
*/
|
||||||
|
static int s1(const uint8_t *m, size_t m_size,
|
||||||
|
uint8_t out[BT_CSIS_CRYPTO_SALT_SIZE])
|
||||||
|
{
|
||||||
|
uint8_t zero[16];
|
||||||
|
int err;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* s1(M) = AES-CMAC_zero(M)
|
||||||
|
*/
|
||||||
|
|
||||||
|
BT_DBG("BE: m %s", bt_hex(m, m_size));
|
||||||
|
|
||||||
|
memset(zero, 0, sizeof(zero));
|
||||||
|
|
||||||
|
err = aes_cmac(zero, m, m_size, out);
|
||||||
|
|
||||||
|
BT_DBG("BE: out %s", bt_hex(out, 16));
|
||||||
|
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
int bt_csis_sef(const uint8_t k[BT_CSIS_CRYPTO_KEY_SIZE],
|
||||||
|
const uint8_t sirk[BT_CSIS_SET_SIRK_SIZE],
|
||||||
|
uint8_t out_sirk[BT_CSIS_SET_SIRK_SIZE])
|
||||||
|
{
|
||||||
|
const uint8_t m[] = {'S', 'I', 'R', 'K', 'e', 'n', 'c'};
|
||||||
|
const uint8_t p[] = {'c', 's', 'i', 's'};
|
||||||
|
uint8_t s1_out[BT_CSIS_CRYPTO_SALT_SIZE];
|
||||||
|
uint8_t k1_out[BT_CSIS_CRYPTO_KEY_SIZE];
|
||||||
|
uint8_t k1_tmp[BT_CSIS_CRYPTO_KEY_SIZE];
|
||||||
|
int err;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* sef(K, SIRK) = k1(K, s1("SIRKenc"), "csis") ^ SIRK
|
||||||
|
*/
|
||||||
|
|
||||||
|
BT_DBG("SIRK %s", bt_hex(sirk, BT_CSIS_SET_SIRK_SIZE));
|
||||||
|
|
||||||
|
if (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) {
|
||||||
|
/* Swap because aes_cmac is big endian
|
||||||
|
* and we are little endian
|
||||||
|
*/
|
||||||
|
sys_memcpy_swap(k1_tmp, k, sizeof(k1_tmp));
|
||||||
|
} else {
|
||||||
|
(void)memcpy(k1_tmp, k, sizeof(k1_tmp));
|
||||||
|
}
|
||||||
|
BT_DBG("BE: k %s", bt_hex(k1_tmp, sizeof(k1_tmp)));
|
||||||
|
|
||||||
|
err = s1(m, sizeof(m), s1_out);
|
||||||
|
if (err) {
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
BT_DBG("BE: s1 result %s", bt_hex(s1_out, sizeof(s1_out)));
|
||||||
|
|
||||||
|
err = k1(k1_tmp, sizeof(k1_tmp), s1_out, p, sizeof(p), k1_out);
|
||||||
|
if (err) {
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
BT_DBG("BE: k1 result %s", bt_hex(k1_out, sizeof(k1_out)));
|
||||||
|
|
||||||
|
if (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) {
|
||||||
|
/* Swap result back to little endian */
|
||||||
|
sys_mem_swap(k1_out, sizeof(k1_out));
|
||||||
|
}
|
||||||
|
|
||||||
|
xor_128(k1_out, sirk, out_sirk);
|
||||||
|
BT_DBG("out %s", bt_hex(out_sirk, BT_CSIS_SET_SIRK_SIZE));
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int bt_csis_sdf(const uint8_t k[BT_CSIS_CRYPTO_KEY_SIZE],
|
||||||
|
const uint8_t enc_sirk[BT_CSIS_SET_SIRK_SIZE],
|
||||||
|
uint8_t out_sirk[BT_CSIS_SET_SIRK_SIZE])
|
||||||
|
{
|
||||||
|
/* SIRK encryption is currently symmetric, which means that we can
|
||||||
|
* simply apply the sef function to decrypt it.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* sdf(K, EncSIRK) = k1(K, s1("SIRKenc"), "csis") ^ EncSIRK
|
||||||
|
*/
|
||||||
|
|
||||||
|
BT_DBG("Running SDF as SEF");
|
||||||
|
return bt_csis_sef(k, enc_sirk, out_sirk);
|
||||||
|
}
|
83
subsys/bluetooth/audio/csis_crypto.h
Normal file
83
subsys/bluetooth/audio/csis_crypto.h
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2019 Bose Corporation
|
||||||
|
* Copyright (c) 2021 Nordic Semiconductor ASA
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <zephyr/types.h>
|
||||||
|
|
||||||
|
#include "csis.h"
|
||||||
|
|
||||||
|
#define BT_CSIS_CRYPTO_KEY_SIZE 16
|
||||||
|
#define BT_CSIS_CRYPTO_SALT_SIZE 16
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Private Set Unique identifier hash function sih.
|
||||||
|
*
|
||||||
|
* The PSRI hash function sih is used to generate a hash value that is
|
||||||
|
* used in PSRIs - Used by the Coordinated Set Identification service and
|
||||||
|
* profile.
|
||||||
|
*
|
||||||
|
* @param sirk The 16-byte SIRK
|
||||||
|
* @param r 3 byte random value
|
||||||
|
* @param out The 3 byte output buffer
|
||||||
|
* @return int 0 on success, any other value indicates a failure.
|
||||||
|
*/
|
||||||
|
int bt_csis_sih(const uint8_t sirk[BT_CSIS_SET_SIRK_SIZE], uint32_t r,
|
||||||
|
uint32_t *out);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief SIRK encryption function sef
|
||||||
|
*
|
||||||
|
* The SIRK encryption function sef is used by the server to encrypt the SIRK
|
||||||
|
* with a key K. The value of K depends on the transport on which the pairing
|
||||||
|
* between the client and the server was performed.
|
||||||
|
*
|
||||||
|
* If the pairing was performed on BR/EDR, K is equal to the Link Key shared by
|
||||||
|
* the server and the client.
|
||||||
|
* K = Link Key.
|
||||||
|
*
|
||||||
|
* If the pairing was performed on LE, the 64 LSBs of K correspond to the 64
|
||||||
|
* LSBs of the IRK that the server sent to the client during the Phase 3
|
||||||
|
* (Transport Specific Key Distribution) of the pairing procedure (see Volume 3,
|
||||||
|
* Part H, Section 2.1 in [2]), and the 64 MSBs of K correspond to the 64 MSBs
|
||||||
|
* of the LTK shared by the server and client. That is,
|
||||||
|
* K = LTK_64-127 || IRK_0-63
|
||||||
|
*
|
||||||
|
* @param k 16-byte key.
|
||||||
|
* @param sirk The unencrypted SIRK.
|
||||||
|
* @param out_sirk The encrypted SIRK.
|
||||||
|
* @return int 0 on success, any other value indicates a failure.
|
||||||
|
*/
|
||||||
|
int bt_csis_sef(const uint8_t k[BT_CSIS_CRYPTO_KEY_SIZE],
|
||||||
|
const uint8_t sirk[BT_CSIS_SET_SIRK_SIZE],
|
||||||
|
uint8_t out_sirk[BT_CSIS_SET_SIRK_SIZE]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief SIRK decryption function sdf
|
||||||
|
*
|
||||||
|
* The SIRK decryption function sdf is used by the client to decrypt the SIRK
|
||||||
|
* with a key K. The value of K depends on the transport on which the pairing
|
||||||
|
* between the client and the server was performed.
|
||||||
|
*
|
||||||
|
* If the pairing was performed on BR/EDR, K is equal to the Link Key shared by
|
||||||
|
* the server and the client.
|
||||||
|
* K = Link Key.
|
||||||
|
*
|
||||||
|
* If the pairing was performed on LE, the 64 LSBs of K correspond to the 64
|
||||||
|
* LSBs of the IRK that the server sent to the client during the Phase 3
|
||||||
|
* (Transport Specific Key Distribution) of the pairing procedure (see Volume 3,
|
||||||
|
* Part H, Section 2.1 in [2]), and the 64 MSBs of K correspond to the 64 MSBs
|
||||||
|
* of the LTK shared by the server and client. That is,
|
||||||
|
* K = LTK_64-127 || IRK_0-63
|
||||||
|
*
|
||||||
|
* @param k 16-byte key.
|
||||||
|
* @param sirk The encrypted SIRK.
|
||||||
|
* @param out_sirk The decrypted SIRK.
|
||||||
|
* @return int 0 on success, any other value indicates a failure.
|
||||||
|
*/
|
||||||
|
int bt_csis_sdf(const uint8_t k[BT_CSIS_CRYPTO_KEY_SIZE],
|
||||||
|
const uint8_t enc_sirk[BT_CSIS_SET_SIRK_SIZE],
|
||||||
|
uint8_t out_sirk[BT_CSIS_SET_SIRK_SIZE]);
|
58
subsys/bluetooth/audio/csis_internal.h
Normal file
58
subsys/bluetooth/audio/csis_internal.h
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
/**
|
||||||
|
* @file
|
||||||
|
* @brief Internal APIs for Bluetooth CSIS
|
||||||
|
*
|
||||||
|
* Copyright (c) 2021 Nordic Semiconductor ASA
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "csis.h"
|
||||||
|
|
||||||
|
struct bt_csis_set_sirk {
|
||||||
|
uint8_t type;
|
||||||
|
uint8_t value[BT_CSIS_SET_SIRK_SIZE];
|
||||||
|
} __packed;
|
||||||
|
|
||||||
|
struct csis_pending_notifications {
|
||||||
|
bt_addr_le_t addr;
|
||||||
|
bool pending;
|
||||||
|
bool active;
|
||||||
|
|
||||||
|
/* Since there's a 1-to-1 connection between bonded devices, and devices in
|
||||||
|
* the array containing this struct, if the security manager overwrites
|
||||||
|
* the oldest keys, we also overwrite the oldest entry
|
||||||
|
*/
|
||||||
|
#if IS_ENABLED(CONFIG_BT_KEYS_OVERWRITE_OLDEST)
|
||||||
|
uint32_t age;
|
||||||
|
#endif /* CONFIG_BT_KEYS_OVERWRITE_OLDEST */
|
||||||
|
};
|
||||||
|
|
||||||
|
struct bt_csis_server {
|
||||||
|
struct bt_csis_set_sirk set_sirk;
|
||||||
|
uint8_t psri[BT_CSIS_PSRI_SIZE];
|
||||||
|
uint8_t set_size;
|
||||||
|
uint8_t set_lock;
|
||||||
|
uint8_t rank;
|
||||||
|
bool adv_enabled;
|
||||||
|
struct k_work_delayable set_lock_timer;
|
||||||
|
bt_addr_le_t lock_client_addr;
|
||||||
|
struct bt_gatt_service *service_p;
|
||||||
|
struct csis_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 */
|
||||||
|
#if defined(CONFIG_BT_EXT_ADV)
|
||||||
|
uint8_t conn_cnt;
|
||||||
|
struct bt_le_ext_adv *adv;
|
||||||
|
struct bt_le_ext_adv_cb adv_cb;
|
||||||
|
struct k_work work;
|
||||||
|
#endif /* CONFIG_BT_EXT_ADV */
|
||||||
|
};
|
||||||
|
|
||||||
|
struct bt_csis {
|
||||||
|
union {
|
||||||
|
struct bt_csis_server srv;
|
||||||
|
/* TODO: Add cli */
|
||||||
|
};
|
||||||
|
};
|
|
@ -54,3 +54,6 @@ CONFIG_BT_MICS=y
|
||||||
CONFIG_BT_MICS_AICS_INSTANCE_COUNT=1
|
CONFIG_BT_MICS_AICS_INSTANCE_COUNT=1
|
||||||
CONFIG_BT_MICS_CLIENT=y
|
CONFIG_BT_MICS_CLIENT=y
|
||||||
CONFIG_BT_MICS_CLIENT_MAX_AICS_INST=1
|
CONFIG_BT_MICS_CLIENT_MAX_AICS_INST=1
|
||||||
|
|
||||||
|
# Coordinated Set Identification
|
||||||
|
CONFIG_BT_CSIS=y
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue