Add a new callback structure for Bluetooth authentication This struct is meant to replace the information-only callbacks in bt_conn_auth_cb. The reason for this is that due to the nature of bt_conn_auth_cb, it can only be registered once. To allow mulitple users gain information about pairing and bond deletions, this new struct is needed. Samples, tests, etc. are updated to use the new struct. Signed-off-by: Emil Gydesen <emil.gydesen@nordicsemi.no>
997 lines
25 KiB
C
997 lines
25 KiB
C
/* 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_ATT_ERR_AUTHORIZATION);
|
|
} 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 struct bt_conn_auth_info_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_info_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");
|
|
}
|