Bluetooth: ISO: Central security request
If the required_sec_level is lower than the
conn->sec_level, the central will now initialize the
security procecure to ensure that the CIS is encrypted
properly.
The algorithm implemented is as follows:
1) Check security levels for each (acl, iso) pair
2) For those with insufficient security,
call bt_conn_set_security
3) For those with sufficient security, connect the CIS
4) Once the ISO from 2) has been encrypted, connect the
CIS for the specific ACL
The idea behind this was to implement similar support
for autonomous encryption as we have for L2CAP.
It is more complex for ISO as we are dealing with
an array of (acl, iso) pairs, meaning more can go
wrong.
Signed-off-by: Emil Gydesen <emil.gydesen@nordicsemi.no>
diff --git a/include/zephyr/bluetooth/iso.h b/include/zephyr/bluetooth/iso.h
index 01b2019..9138cef 100644
--- a/include/zephyr/bluetooth/iso.h
+++ b/include/zephyr/bluetooth/iso.h
@@ -94,6 +94,8 @@
enum bt_iso_state {
/** Channel disconnected */
BT_ISO_STATE_DISCONNECTED,
+ /** Channel is pending ACL encryption before connecting */
+ BT_ISO_STATE_ENCRYPT_PENDING,
/** Channel in connecting state */
BT_ISO_STATE_CONNECTING,
/** Channel ready for upper layer traffic on it */
@@ -119,6 +121,13 @@
/** Channel QoS reference */
struct bt_iso_chan_qos *qos;
enum bt_iso_state state;
+ /** @brief The required security level of the channel
+ *
+ * This value can be set as the central before connecting a CIS
+ * with bt_iso_chan_connect().
+ * The value is overwritten to @ref bt_iso_server::sec_level for the
+ * peripheral once a channel has been accepted.
+ */
bt_security_t required_sec_level;
/** Node used internally by the stack */
sys_snode_t node;
@@ -435,7 +444,7 @@
*
* If this callback is provided it will be called whenever the
* channel is disconnected, including when a connection gets
- * rejected.
+ * rejected or when setting security fails.
*
* @param chan The channel that has been Disconnected
* @param reason BT_HCI_ERR_* reason for the disconnection.
diff --git a/subsys/bluetooth/host/conn.c b/subsys/bluetooth/host/conn.c
index 093a329..39d7140 100644
--- a/subsys/bluetooth/host/conn.c
+++ b/subsys/bluetooth/host/conn.c
@@ -2008,6 +2008,9 @@
reset_pairing(conn);
bt_l2cap_security_changed(conn, hci_err);
+ if (IS_ENABLED(CONFIG_BT_ISO_CENTRAL)) {
+ bt_iso_security_changed(conn, hci_err);
+ }
for (cb = callback_list; cb; cb = cb->_next) {
if (cb->security_changed) {
diff --git a/subsys/bluetooth/host/iso.c b/subsys/bluetooth/host/iso.c
index ef17743..f90a465 100644
--- a/subsys/bluetooth/host/iso.c
+++ b/subsys/bluetooth/host/iso.c
@@ -52,7 +52,11 @@
static struct bt_iso_cig *get_cig(const struct bt_iso_chan *iso_chan);
static void bt_iso_remove_data_path(struct bt_conn *iso);
+static int hci_le_create_cis(const struct bt_iso_connect_param *param,
+ size_t count);
+
#endif /* CONFIG_BT_ISO_CENTRAL */
+
#if defined(CONFIG_BT_ISO_PERIPHERAL)
static struct bt_iso_server *iso_server;
@@ -460,6 +464,8 @@
return "disconnected";
case BT_ISO_STATE_CONNECTING:
return "connecting";
+ case BT_ISO_STATE_ENCRYPT_PENDING:
+ return "encryption pending";
case BT_ISO_STATE_CONNECTED:
return "connected";
case BT_ISO_STATE_DISCONNECTING:
@@ -482,6 +488,8 @@
case BT_ISO_STATE_DISCONNECTED:
/* regardless of old state always allows this states */
break;
+ case BT_ISO_STATE_ENCRYPT_PENDING:
+ __fallthrough;
case BT_ISO_STATE_CONNECTING:
if (chan->state != BT_ISO_STATE_DISCONNECTED) {
BT_WARN("%s()%d: invalid transition", func, line);
@@ -1673,6 +1681,94 @@
return 0;
}
+void bt_iso_security_changed(struct bt_conn *acl, uint8_t hci_status)
+{
+ struct bt_iso_connect_param param[CONFIG_BT_ISO_MAX_CHAN];
+ size_t param_count;
+ int err;
+
+ /* The peripheral does not accept any ISO requests if security is
+ * insufficient, so we only need to handle central here.
+ * BT_ISO_STATE_ENCRYPT_PENDING is only set by the central.
+ */
+ if (!IS_ENABLED(CONFIG_BT_CENTRAL) ||
+ acl->role != BT_CONN_ROLE_CENTRAL) {
+ return;
+ }
+
+ param_count = 0;
+ for (size_t i = 0; i < ARRAY_SIZE(iso_conns); i++) {
+ struct bt_conn *iso = &iso_conns[i];
+ struct bt_iso_chan *iso_chan;
+
+ if (iso == NULL || iso->iso.acl != acl) {
+ continue;
+ }
+
+ iso_chan = iso_chan(iso);
+ if (iso_chan->state != BT_ISO_STATE_ENCRYPT_PENDING) {
+ continue;
+ }
+
+ bt_iso_chan_set_state(iso_chan, BT_ISO_STATE_DISCONNECTED);
+
+ if (hci_status == BT_HCI_ERR_SUCCESS) {
+ param[param_count].acl = acl;
+ param[param_count].iso_chan = iso_chan;
+ param_count++;
+ } else {
+ BT_DBG("Failed to encrypt ACL %p for ISO %p: %u",
+ acl, iso, hci_status);
+
+ /* We utilize the disconnected callback to make the
+ * upper layers aware of the error
+ */
+ if (iso_chan->ops->disconnected) {
+ iso_chan->ops->disconnected(iso_chan,
+ hci_status);
+ }
+ }
+ }
+
+ if (param_count == 0) {
+ /* Nothing to do for ISO. This happens if security is changed,
+ * but no ISO channels were pending encryption.
+ */
+ return;
+ }
+
+ err = hci_le_create_cis(param, param_count);
+ if (err != 0) {
+ BT_ERR("Failed to connect CISes: %d", err);
+
+ for (size_t i = 0; i < param_count; i++) {
+ struct bt_iso_chan *iso_chan = param[i].iso_chan;
+
+ /* We utilize the disconnected callback to make the
+ * upper layers aware of the error
+ */
+ if (iso_chan->ops->disconnected) {
+ iso_chan->ops->disconnected(iso_chan,
+ hci_status);
+ }
+ }
+
+ return;
+ }
+
+ /* Set connection states */
+ for (size_t i = 0; i < param_count; i++) {
+ struct bt_iso_chan *iso_chan = param[i].iso_chan;
+ struct bt_iso_cig *cig = get_cig(iso_chan);
+
+ __ASSERT(cig != NULL, "CIG was NULL");
+ cig->state = BT_ISO_CIG_STATE_ACTIVE;
+
+ bt_conn_set_state(iso_chan->iso, BT_CONN_CONNECTING);
+ bt_iso_chan_set_state(iso_chan, BT_ISO_STATE_CONNECTING);
+ }
+}
+
static int hci_le_create_cis(const struct bt_iso_connect_param *param,
size_t count)
{
@@ -1690,21 +1786,84 @@
memset(req, 0, sizeof(*req));
- req->num_cis = count;
-
/* Program the cis parameters */
for (size_t i = 0; i < count; i++) {
+ struct bt_iso_chan *iso_chan = param[i].iso_chan;
+
+ if (iso_chan->state == BT_ISO_STATE_ENCRYPT_PENDING) {
+ continue;
+ }
+
cis = net_buf_add(buf, sizeof(*cis));
memset(cis, 0, sizeof(*cis));
cis->cis_handle = sys_cpu_to_le16(param[i].iso_chan->iso->handle);
cis->acl_handle = sys_cpu_to_le16(param[i].acl->handle);
+ req->num_cis++;
+ }
+
+ /* If all CIS are pending for security, do nothing,
+ * but return a recognizable return value
+ */
+ if (req->num_cis == 0) {
+ net_buf_unref(buf);
+
+ return -ECANCELED;
}
return bt_hci_cmd_send_sync(BT_HCI_OP_LE_CREATE_CIS, buf, NULL);
}
+static int iso_chan_connect_security(const struct bt_iso_connect_param *param,
+ size_t count)
+{
+ /* conn_idx_handled is an array of booleans for which conn indexes
+ * already have been used to call bt_conn_set_security.
+ * Using indexes avoids looping the array when checking if it has been
+ * handled.
+ */
+ bool conn_idx_handled[CONFIG_BT_MAX_CONN];
+
+ memset(conn_idx_handled, false, sizeof(conn_idx_handled));
+ for (size_t i = 0; i < count; i++) {
+ struct bt_iso_chan *iso_chan = param[i].iso_chan;
+ struct bt_conn *acl = param[i].acl;
+ uint8_t conn_idx = bt_conn_index(acl);
+
+ if (acl->sec_level < iso_chan->required_sec_level) {
+ if (!conn_idx_handled[conn_idx]) {
+ int err;
+
+ err = bt_conn_set_security(acl,
+ iso_chan->required_sec_level);
+ if (err != 0) {
+ BT_DBG("[%zu]: Failed to set security: %d",
+ i, err);
+
+ /* Restore states */
+ for (size_t j = 0; j < i; j++) {
+ iso_chan = param[j].iso_chan;
+
+ bt_iso_cleanup_acl(iso_chan->iso);
+ bt_iso_chan_set_state(iso_chan,
+ BT_ISO_STATE_DISCONNECTED);
+ }
+
+ return err;
+ }
+
+ conn_idx_handled[conn_idx] = true;
+ }
+
+ iso_chan->iso->iso.acl = bt_conn_ref(acl);
+ bt_iso_chan_set_state(iso_chan, BT_ISO_STATE_ENCRYPT_PENDING);
+ }
+ }
+
+ return 0;
+}
+
int bt_iso_chan_connect(const struct bt_iso_connect_param *param, size_t count)
{
int err;
@@ -1754,9 +1913,23 @@
}
}
+ /* Check for and initiate security for all channels that have
+ * requested encryption if the ACL link is not already secured
+ */
+ err = iso_chan_connect_security(param, count);
+ if (err != 0) {
+ BT_DBG("Failed to initate security for all CIS: %d", err);
+ return err;
+ }
+
err = hci_le_create_cis(param, count);
- if (err) {
+ if (err == -ECANCELED) {
+ BT_DBG("All channels are pending on security");
+
+ return 0;
+ } else if (err != 0) {
BT_DBG("Failed to connect CISes: %d", err);
+
return err;
}
@@ -1765,6 +1938,10 @@
struct bt_iso_chan *iso_chan = param[i].iso_chan;
struct bt_iso_cig *cig;
+ if (iso_chan->state == BT_ISO_STATE_ENCRYPT_PENDING) {
+ continue;
+ }
+
iso_chan->iso->iso.acl = bt_conn_ref(param[i].acl);
bt_conn_set_state(iso_chan->iso, BT_CONN_CONNECTING);
bt_iso_chan_set_state(iso_chan, BT_ISO_STATE_CONNECTING);
diff --git a/subsys/bluetooth/host/iso_internal.h b/subsys/bluetooth/host/iso_internal.h
index 6649262..545ed6a 100644
--- a/subsys/bluetooth/host/iso_internal.h
+++ b/subsys/bluetooth/host/iso_internal.h
@@ -106,6 +106,9 @@
/* Notify ISO channels of a disconnect event */
void bt_iso_disconnected(struct bt_conn *iso);
+/* Notify ISO connected channels of security changed */
+void bt_iso_security_changed(struct bt_conn *acl, uint8_t hci_status);
+
/* Allocate ISO PDU */
#if defined(CONFIG_NET_BUF_LOG)
struct net_buf *bt_iso_create_pdu_timeout_debug(struct net_buf_pool *pool,