diff --git a/include/bluetooth/l2cap.h b/include/bluetooth/l2cap.h index d508484cc33..5f57bc61513 100644 --- a/include/bluetooth/l2cap.h +++ b/include/bluetooth/l2cap.h @@ -74,6 +74,8 @@ struct bt_l2cap_chan { struct nano_delayed_work rtx_work; #if defined(CONFIG_BLUETOOTH_L2CAP_DYNAMIC_CHANNEL) bt_l2cap_chan_state_t state; + /** Remote PSM to be connected */ + uint16_t psm; /** Helps match request context during CoC */ uint8_t ident; bt_security_t required_sec_level; @@ -134,8 +136,6 @@ struct bt_l2cap_br_chan { struct bt_l2cap_br_endpoint tx; /* For internal use only */ atomic_t flags[1]; - /** Remote PSM to be connected */ - uint16_t psm; }; /** @brief L2CAP Channel operations structure. */ diff --git a/subsys/bluetooth/host/l2cap.c b/subsys/bluetooth/host/l2cap.c index 9677d715586..8b84f112158 100644 --- a/subsys/bluetooth/host/l2cap.c +++ b/subsys/bluetooth/host/l2cap.c @@ -220,6 +220,12 @@ void bt_l2cap_chan_del(struct bt_l2cap_chan *chan) chan->conn = NULL; destroy: +#if defined(CONFIG_BLUETOOTH_L2CAP_DYNAMIC_CHANNEL) + /* Reset internal members of common channel */ + chan->state = BT_L2CAP_DISCONNECTED; + chan->psm = 0; +#endif + if (chan->destroy) { chan->destroy(chan); } @@ -321,6 +327,77 @@ void bt_l2cap_disconnected(struct bt_conn *conn) conn->channels = NULL; } +#if defined(CONFIG_BLUETOOTH_L2CAP_DYNAMIC_CHANNEL) +static void l2cap_chan_send_req(struct bt_l2cap_le_chan *chan, + struct net_buf *buf, uint32_t ticks) +{ + /* BLUETOOTH SPECIFICATION Version 4.2 [Vol 3, Part A] page 126: + * + * The value of this timer is implementation-dependent but the minimum + * initial value is 1 second and the maximum initial value is 60 + * seconds. One RTX timer shall exist for each outstanding signaling + * request, including each Echo Request. The timer disappears on the + * final expiration, when the response is received, or the physical + * link is lost. + */ + if (ticks) { + nano_delayed_work_submit(&chan->chan.rtx_work, ticks); + } else { + nano_delayed_work_cancel(&chan->chan.rtx_work); + } + + bt_l2cap_send(chan->chan.conn, BT_L2CAP_CID_LE_SIG, buf); +} + +static int l2cap_le_conn_req(struct bt_l2cap_le_chan *ch) +{ + struct net_buf *buf; + struct bt_l2cap_sig_hdr *hdr; + struct bt_l2cap_le_conn_req *req; + + buf = bt_l2cap_create_pdu(&le_sig, 0); + if (!buf) { + BT_ERR("Unable to send L2CAP connection request"); + return -ENOMEM; + } + + ch->chan.ident = get_ident(); + + hdr = net_buf_add(buf, sizeof(*hdr)); + hdr->code = BT_L2CAP_LE_CONN_REQ; + hdr->ident = ch->chan.ident; + hdr->len = sys_cpu_to_le16(sizeof(*req)); + + req = net_buf_add(buf, sizeof(*req)); + req->psm = sys_cpu_to_le16(ch->chan.psm); + req->scid = sys_cpu_to_le16(ch->rx.cid); + req->mtu = sys_cpu_to_le16(ch->rx.mtu); + req->mps = sys_cpu_to_le16(ch->rx.mps); + req->credits = sys_cpu_to_le16(L2CAP_LE_MAX_CREDITS); + + l2cap_chan_send_req(ch, buf, L2CAP_CONN_TIMEOUT); + + return 0; +} + +static void l2cap_le_encrypt_change(struct bt_l2cap_chan *chan, uint8_t status) +{ + /* Skip channels already connected or with a pending request */ + if (chan->state != BT_L2CAP_CONNECT || chan->ident) { + return; + } + + if (status) { + l2cap_detach_chan(chan->conn, chan); + bt_l2cap_chan_del(chan); + return; + } + + /* Retry to connect */ + l2cap_le_conn_req(BT_L2CAP_LE_CHAN(chan)); +} +#endif /* CONFIG_BLUETOOTH_L2CAP_DYNAMIC_CHANNEL */ + void bt_l2cap_encrypt_change(struct bt_conn *conn, uint8_t hci_status) { struct bt_l2cap_chan *chan; @@ -333,6 +410,10 @@ void bt_l2cap_encrypt_change(struct bt_conn *conn, uint8_t hci_status) #endif /* CONFIG_BLUETOOTH_BREDR */ for (chan = conn->channels; chan; chan = chan->_next) { +#if defined(CONFIG_BLUETOOTH_L2CAP_DYNAMIC_CHANNEL) + l2cap_le_encrypt_change(chan, hci_status); +#endif + if (chan->ops->encrypt_change) { chan->ops->encrypt_change(chan, hci_status); } @@ -651,6 +732,9 @@ static void le_conn_req(struct bt_l2cap *l2cap, uint8_t ident, l2cap_chan_rx_init(ch); l2cap_chan_rx_give_credits(ch, L2CAP_LE_MAX_CREDITS); + /* Update state */ + chan->state = BT_L2CAP_CONNECTED; + if (chan->ops && chan->ops->connected) { chan->ops->connected(chan); } @@ -750,6 +834,33 @@ static void le_disconn_req(struct bt_l2cap *l2cap, uint8_t ident, bt_l2cap_send(conn, BT_L2CAP_CID_LE_SIG, buf); } +static int l2cap_change_security(struct bt_l2cap_le_chan *chan, uint16_t err) +{ + switch (err) { + case BT_L2CAP_ERR_ENCRYPTION: + if (chan->chan.required_sec_level >= BT_SECURITY_MEDIUM) { + return -EALREADY; + } + chan->chan.required_sec_level = BT_SECURITY_MEDIUM; + break; + case BT_L2CAP_ERR_AUTHENTICATION: + if (chan->chan.required_sec_level < BT_SECURITY_MEDIUM) { + chan->chan.required_sec_level = BT_SECURITY_MEDIUM; + } else if (chan->chan.required_sec_level < BT_SECURITY_HIGH) { + chan->chan.required_sec_level = BT_SECURITY_HIGH; + } else if (chan->chan.required_sec_level < BT_SECURITY_FIPS) { + chan->chan.required_sec_level = BT_SECURITY_FIPS; + } else { + return -EALREADY; + } + break; + default: + return -EINVAL; + } + + return bt_conn_security(chan->chan.conn, chan->chan.required_sec_level); +} + static void le_conn_rsp(struct bt_l2cap *l2cap, uint8_t ident, struct net_buf *buf) { @@ -772,7 +883,10 @@ static void le_conn_rsp(struct bt_l2cap *l2cap, uint8_t ident, BT_DBG("dcid 0x%04x mtu %u mps %u credits %u result 0x%04x", dcid, mtu, mps, credits, result); - if (result == BT_L2CAP_SUCCESS) { + /* Keep the channel in case of security errors */ + if (result == BT_L2CAP_SUCCESS || + result == BT_L2CAP_ERR_AUTHENTICATION || + result == BT_L2CAP_ERR_ENCRYPTION) { chan = l2cap_lookup_ident(conn, ident); } else { chan = l2cap_remove_ident(conn, ident); @@ -783,10 +897,14 @@ static void le_conn_rsp(struct bt_l2cap *l2cap, uint8_t ident, return; } + /* Cancel RTX work */ + nano_delayed_work_cancel(&chan->chan.rtx_work); + + /* Reset ident since it got a response */ + chan->chan.ident = 0; + switch (result) { case BT_L2CAP_SUCCESS: - /* Reset ident since it is no longer pending */ - chan->chan.ident = 0; chan->tx.cid = dcid; chan->tx.mtu = mtu; chan->tx.mps = mps; @@ -799,11 +917,14 @@ static void le_conn_rsp(struct bt_l2cap *l2cap, uint8_t ident, l2cap_chan_tx_give_credits(chan, credits); l2cap_chan_rx_give_credits(chan, L2CAP_LE_MAX_CREDITS); - /* Cancel RTX work */ - nano_delayed_work_cancel(&chan->chan.rtx_work); - break; - /* TODO: Retry on Authentication and Encryption errors */ + case BT_L2CAP_ERR_AUTHENTICATION: + case BT_L2CAP_ERR_ENCRYPTION: + /* If security needs changing wait it to be completed */ + if (l2cap_change_security(chan, result) == 0) { + return; + } + l2cap_detach_chan(conn, &chan->chan); default: bt_l2cap_chan_del(&chan->chan); } @@ -1278,34 +1399,9 @@ struct bt_l2cap_chan *bt_l2cap_le_lookup_rx_cid(struct bt_conn *conn, } #if defined(CONFIG_BLUETOOTH_L2CAP_DYNAMIC_CHANNEL) -static void l2cap_chan_send_req(struct bt_l2cap_le_chan *chan, - struct net_buf *buf, uint32_t ticks) -{ - /* BLUETOOTH SPECIFICATION Version 4.2 [Vol 3, Part A] page 126: - * - * The value of this timer is implementation-dependent but the minimum - * initial value is 1 second and the maximum initial value is 60 - * seconds. One RTX timer shall exist for each outstanding signaling - * request, including each Echo Request. The timer disappears on the - * final expiration, when the response is received, or the physical - * link is lost. - */ - if (ticks) { - nano_delayed_work_submit(&chan->chan.rtx_work, ticks); - } else { - nano_delayed_work_cancel(&chan->chan.rtx_work); - } - - bt_l2cap_send(chan->chan.conn, BT_L2CAP_CID_LE_SIG, buf); -} - static int l2cap_le_connect(struct bt_conn *conn, struct bt_l2cap_le_chan *ch, uint16_t psm) { - struct net_buf *buf; - struct bt_l2cap_sig_hdr *hdr; - struct bt_l2cap_le_conn_req *req; - if (psm < L2CAP_LE_PSM_START || psm > L2CAP_LE_PSM_END) { return -EINVAL; } @@ -1317,29 +1413,10 @@ static int l2cap_le_connect(struct bt_conn *conn, struct bt_l2cap_le_chan *ch, return -ENOMEM; } - buf = bt_l2cap_create_pdu(&le_sig, 0); - if (!buf) { - BT_ERR("Unable to send L2CAP connection request"); - return -ENOMEM; - } + ch->chan.psm = psm; + ch->chan.state = BT_L2CAP_CONNECT; - ch->chan.ident = get_ident(); - - hdr = net_buf_add(buf, sizeof(*hdr)); - hdr->code = BT_L2CAP_LE_CONN_REQ; - hdr->ident = ch->chan.ident; - hdr->len = sys_cpu_to_le16(sizeof(*req)); - - req = net_buf_add(buf, sizeof(*req)); - req->psm = sys_cpu_to_le16(psm); - req->scid = sys_cpu_to_le16(ch->rx.cid); - req->mtu = sys_cpu_to_le16(ch->rx.mtu); - req->mps = sys_cpu_to_le16(ch->rx.mps); - req->credits = sys_cpu_to_le16(L2CAP_LE_MAX_CREDITS); - - l2cap_chan_send_req(ch, buf, L2CAP_CONN_TIMEOUT); - - return 0; + return l2cap_le_conn_req(ch); } int bt_l2cap_chan_connect(struct bt_conn *conn, struct bt_l2cap_chan *chan, @@ -1411,6 +1488,7 @@ int bt_l2cap_chan_disconnect(struct bt_l2cap_chan *chan) req->scid = sys_cpu_to_le16(ch->rx.cid); l2cap_chan_send_req(ch, buf, L2CAP_DISC_TIMEOUT); + ch->chan.state = BT_L2CAP_DISCONNECT; return 0; } diff --git a/subsys/bluetooth/host/l2cap_br.c b/subsys/bluetooth/host/l2cap_br.c index 39a8cc6ce14..8c0fae1b971 100644 --- a/subsys/bluetooth/host/l2cap_br.c +++ b/subsys/bluetooth/host/l2cap_br.c @@ -293,10 +293,7 @@ static void l2cap_br_chan_destroy(struct bt_l2cap_chan *chan) /* Cancel ongoing work */ nano_delayed_work_cancel(&chan->rtx_work); - /* Reset _ONLY_ internal members of common channel */ - chan->state = BT_L2CAP_DISCONNECTED; - /* Reset _ONLY_ BR/EDR specific members on L2CAP app channel */ - BR_CHAN(chan)->psm = 0; + atomic_clear(BR_CHAN(chan)->flags); } @@ -1361,7 +1358,7 @@ int bt_l2cap_br_chan_connect(struct bt_conn *conn, struct bt_l2cap_chan *chan, return -EINVAL; } - if (BR_CHAN(chan)->psm) { + if (chan->psm) { return -EEXIST; } @@ -1395,7 +1392,7 @@ int bt_l2cap_br_chan_connect(struct bt_conn *conn, struct bt_l2cap_chan *chan, return -ENOMEM; } - BR_CHAN(chan)->psm = psm; + chan->psm = psm; l2cap_br_state_set(chan, BT_L2CAP_CONNECT); atomic_set_bit(BR_CHAN(chan)->flags, L2CAP_FLAG_CONN_PENDING); @@ -1619,7 +1616,7 @@ static void l2cap_br_conn_pend(struct bt_l2cap_chan *chan, uint8_t status) hdr->len = sys_cpu_to_le16(sizeof(*req)); req = net_buf_add(buf, sizeof(*req)); - req->psm = sys_cpu_to_le16(BR_CHAN(chan)->psm); + req->psm = sys_cpu_to_le16(chan->psm); req->scid = sys_cpu_to_le16(BR_CHAN(chan)->rx.cid); l2cap_br_chan_send_req(BR_CHAN(chan), buf,