blob: 14b17d666d4b5ebf290059a4861b4c3cd3148d8a [file] [log] [blame]
/*
* Copyright (c) 2020 Demant
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/types.h>
#include <zephyr/bluetooth/hci.h>
#include <zephyr/sys/byteorder.h>
#include <zephyr/sys/slist.h>
#include <zephyr/sys/util.h>
#include "hal/ecb.h"
#include "hal/ccm.h"
#include "util/util.h"
#include "util/mem.h"
#include "util/memq.h"
#include "util/dbuf.h"
#include "pdu.h"
#include "ll.h"
#include "ll_feat.h"
#include "ll_settings.h"
#include "lll.h"
#include "lll/lll_df_types.h"
#include "lll_conn.h"
#include "lll_conn_iso.h"
#include "ull_tx_queue.h"
#include "isoal.h"
#include "ull_iso_types.h"
#include "ull_conn_iso_types.h"
#include "ull_conn_iso_internal.h"
#include "ull_conn_types.h"
#include "ull_internal.h"
#include "ull_llcp.h"
#include "ull_llcp_internal.h"
#include "ull_llcp_features.h"
#include "ull_conn_internal.h"
#define BT_DBG_ENABLED IS_ENABLED(CONFIG_BT_DEBUG_HCI_DRIVER)
#define LOG_MODULE_NAME bt_ctlr_ull_llcp_enc
#include "common/log.h"
#include <soc.h>
#include "hal/debug.h"
#if defined(CONFIG_BT_CENTRAL)
/* LLCP Local Procedure Encryption FSM states */
enum {
/* Start Procedure */
LP_ENC_STATE_UNENCRYPTED,
LP_ENC_STATE_WAIT_TX_ENC_REQ,
LP_ENC_STATE_WAIT_RX_ENC_RSP,
LP_ENC_STATE_WAIT_RX_START_ENC_REQ,
LP_ENC_STATE_WAIT_TX_START_ENC_RSP,
LP_ENC_STATE_WAIT_RX_START_ENC_RSP,
LP_ENC_STATE_WAIT_NTF,
/* Pause Procedure */
LP_ENC_STATE_ENCRYPTED,
LP_ENC_STATE_WAIT_TX_PAUSE_ENC_REQ,
LP_ENC_STATE_WAIT_RX_PAUSE_ENC_RSP,
LP_ENC_STATE_WAIT_TX_PAUSE_ENC_RSP,
};
/* LLCP Local Procedure Encryption FSM events */
enum {
/* Procedure prepared */
LP_ENC_EVT_RUN,
/* Response received */
LP_ENC_EVT_ENC_RSP,
/* Request received */
LP_ENC_EVT_START_ENC_REQ,
/* Response received */
LP_ENC_EVT_START_ENC_RSP,
/* Reject response received */
LP_ENC_EVT_REJECT,
/* Unknown response received */
LP_ENC_EVT_UNKNOWN,
/* Response received */
LP_ENC_EVT_PAUSE_ENC_RSP,
};
#endif /* CONFIG_BT_CENTRAL */
#if defined(CONFIG_BT_PERIPHERAL)
/* LLCP Remote Procedure Encryption FSM states */
enum {
/* Start Procedure */
RP_ENC_STATE_UNENCRYPTED,
RP_ENC_STATE_WAIT_RX_ENC_REQ,
RP_ENC_STATE_WAIT_TX_ENC_RSP,
RP_ENC_STATE_WAIT_NTF_LTK_REQ,
RP_ENC_STATE_WAIT_LTK_REPLY,
RP_ENC_STATE_WAIT_TX_START_ENC_REQ,
RP_ENC_STATE_WAIT_TX_REJECT_IND,
RP_ENC_STATE_WAIT_RX_START_ENC_RSP,
RP_ENC_STATE_WAIT_NTF,
RP_ENC_STATE_WAIT_TX_START_ENC_RSP,
/* Pause Procedure */
RP_ENC_STATE_ENCRYPTED,
RP_ENC_STATE_WAIT_RX_PAUSE_ENC_REQ,
RP_ENC_STATE_WAIT_TX_PAUSE_ENC_RSP,
RP_ENC_STATE_WAIT_RX_PAUSE_ENC_RSP,
};
/* LLCP Remote Procedure Encryption FSM events */
enum {
/* Procedure prepared */
RP_ENC_EVT_RUN,
/* Request received */
RP_ENC_EVT_ENC_REQ,
/* Response received */
RP_ENC_EVT_START_ENC_RSP,
/* LTK request reply */
RP_ENC_EVT_LTK_REQ_REPLY,
/* LTK request negative reply */
RP_ENC_EVT_LTK_REQ_NEG_REPLY,
/* Reject response received */
RP_ENC_EVT_REJECT,
/* Unknown response received */
RP_ENC_EVT_UNKNOWN,
/* Request received */
RP_ENC_EVT_PAUSE_ENC_REQ,
/* Response received */
RP_ENC_EVT_PAUSE_ENC_RSP,
};
#endif /* CONFIG_BT_PERIPHERAL */
#if defined(CONFIG_BT_CENTRAL)
/*
* LLCP Local Procedure Encryption FSM
*/
static struct node_tx *llcp_lp_enc_tx(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t opcode)
{
struct node_tx *tx;
struct pdu_data *pdu;
/* Allocate tx node */
tx = llcp_tx_alloc(conn, ctx);
LL_ASSERT(tx);
pdu = (struct pdu_data *)tx->pdu;
/* Encode LL Control PDU */
switch (opcode) {
case PDU_DATA_LLCTRL_TYPE_ENC_REQ:
llcp_pdu_encode_enc_req(ctx, pdu);
break;
case PDU_DATA_LLCTRL_TYPE_START_ENC_RSP:
llcp_pdu_encode_start_enc_rsp(pdu);
break;
case PDU_DATA_LLCTRL_TYPE_PAUSE_ENC_REQ:
llcp_pdu_encode_pause_enc_req(pdu);
break;
case PDU_DATA_LLCTRL_TYPE_PAUSE_ENC_RSP:
llcp_pdu_encode_pause_enc_rsp(pdu);
break;
default:
LL_ASSERT(0);
}
ctx->tx_opcode = pdu->llctrl.opcode;
/* Enqueue LL Control PDU towards LLL */
llcp_tx_enqueue(conn, tx);
/* Restart procedure response timeout timer */
llcp_lr_prt_restart(conn);
return tx;
}
static void lp_enc_ntf(struct ll_conn *conn, struct proc_ctx *ctx)
{
struct node_rx_pdu *ntf;
struct pdu_data *pdu;
/* Allocate ntf node */
ntf = llcp_ntf_alloc();
LL_ASSERT(ntf);
ntf->hdr.type = NODE_RX_TYPE_DC_PDU;
ntf->hdr.handle = conn->lll.handle;
pdu = (struct pdu_data *)ntf->pdu;
if (ctx->data.enc.error == BT_HCI_ERR_SUCCESS) {
if (ctx->proc == PROC_ENCRYPTION_START) {
/* Encryption Change Event */
/* TODO(thoh): is this correct? */
llcp_pdu_encode_start_enc_rsp(pdu);
} else if (ctx->proc == PROC_ENCRYPTION_PAUSE) {
/* Encryption Key Refresh Complete Event */
ntf->hdr.type = NODE_RX_TYPE_ENC_REFRESH;
} else {
/* Should never happen */
LL_ASSERT(0);
}
} else {
llcp_pdu_encode_reject_ind(pdu, ctx->data.enc.error);
}
/* Enqueue notification towards LL */
ll_rx_put(ntf->hdr.link, ntf);
ll_rx_sched();
}
static void lp_enc_complete(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, void *param)
{
if (!llcp_ntf_alloc_is_available()) {
ctx->state = LP_ENC_STATE_WAIT_NTF;
} else {
lp_enc_ntf(conn, ctx);
llcp_lr_complete(conn);
ctx->state = LP_ENC_STATE_UNENCRYPTED;
}
}
static void lp_enc_store_m(struct ll_conn *conn, struct proc_ctx *ctx, struct pdu_data *pdu)
{
/* Store SKDm */
memcpy(&ctx->data.enc.skd[0], pdu->llctrl.enc_req.skdm, sizeof(pdu->llctrl.enc_req.skdm));
/* Store IVm in the LLL CCM RX
* TODO(thoh): Should this be made into a ULL function, as it
* interacts with data outside of LLCP?
*/
memcpy(&conn->lll.ccm_rx.iv[0], pdu->llctrl.enc_req.ivm, sizeof(pdu->llctrl.enc_req.ivm));
}
static void lp_enc_send_enc_req(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt,
void *param)
{
struct node_tx *tx;
if (!llcp_tx_alloc_peek(conn, ctx)) {
ctx->state = LP_ENC_STATE_WAIT_TX_ENC_REQ;
} else {
tx = llcp_lp_enc_tx(conn, ctx, PDU_DATA_LLCTRL_TYPE_ENC_REQ);
lp_enc_store_m(conn, ctx, (struct pdu_data *)tx->pdu);
/* Wait for the LL_ENC_RSP */
ctx->rx_opcode = PDU_DATA_LLCTRL_TYPE_ENC_RSP;
ctx->state = LP_ENC_STATE_WAIT_RX_ENC_RSP;
/* Pause possibly ongoing remote procedure */
llcp_rr_pause(conn);
}
}
static void lp_enc_send_pause_enc_req(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt,
void *param)
{
if (!llcp_tx_alloc_peek(conn, ctx)) {
ctx->state = LP_ENC_STATE_WAIT_TX_PAUSE_ENC_REQ;
} else {
llcp_lp_enc_tx(conn, ctx, PDU_DATA_LLCTRL_TYPE_PAUSE_ENC_REQ);
/* Wait for the LL_PAUSE_ENC_RSP */
ctx->rx_opcode = PDU_DATA_LLCTRL_TYPE_PAUSE_ENC_RSP;
ctx->state = LP_ENC_STATE_WAIT_RX_PAUSE_ENC_RSP;
}
}
static void lp_enc_send_pause_enc_rsp(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt,
void *param)
{
if (!llcp_tx_alloc_peek(conn, ctx)) {
ctx->state = LP_ENC_STATE_WAIT_TX_PAUSE_ENC_RSP;
} else {
llcp_lp_enc_tx(conn, ctx, PDU_DATA_LLCTRL_TYPE_PAUSE_ENC_RSP);
ctx->rx_opcode = PDU_DATA_LLCTRL_TYPE_UNUSED;
/* Continue with an encapsulated Start Procedure */
ctx->state = LP_ENC_STATE_UNENCRYPTED;
/* Tx Encryption disabled */
conn->lll.enc_tx = 0U;
/* Rx Decryption disabled */
conn->lll.enc_rx = 0U;
}
}
static void lp_enc_setup_lll(struct ll_conn *conn, struct proc_ctx *ctx)
{
/* TODO(thoh): Move LLL/CCM manipulation to ULL? */
/* Calculate the Session Key */
ecb_encrypt(&ctx->data.enc.ltk[0], &ctx->data.enc.skd[0], NULL, &conn->lll.ccm_rx.key[0]);
/* Copy the Session Key */
memcpy(&conn->lll.ccm_tx.key[0], &conn->lll.ccm_rx.key[0], sizeof(conn->lll.ccm_tx.key));
/* Copy the IV */
memcpy(&conn->lll.ccm_tx.iv[0], &conn->lll.ccm_rx.iv[0], sizeof(conn->lll.ccm_tx.iv));
/* Reset CCM counter */
conn->lll.ccm_tx.counter = 0U;
conn->lll.ccm_rx.counter = 0U;
/* Set CCM direction:
* periph to central = 0,
* central to periph = 1
*/
conn->lll.ccm_tx.direction = 1U;
conn->lll.ccm_rx.direction = 0U;
}
static void lp_enc_send_start_enc_rsp(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt,
void *param)
{
if (!llcp_tx_alloc_peek(conn, ctx)) {
ctx->state = LP_ENC_STATE_WAIT_TX_START_ENC_RSP;
} else {
lp_enc_setup_lll(conn, ctx);
llcp_lp_enc_tx(conn, ctx, PDU_DATA_LLCTRL_TYPE_START_ENC_RSP);
/* Wait for LL_START_ENC_RSP */
ctx->rx_opcode = PDU_DATA_LLCTRL_TYPE_START_ENC_RSP;
ctx->state = LP_ENC_STATE_WAIT_RX_START_ENC_RSP;
/* Tx Encryption enabled */
conn->lll.enc_tx = 1U;
/* Rx Decryption enabled */
conn->lll.enc_rx = 1U;
}
}
static void lp_enc_st_unencrypted(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt,
void *param)
{
switch (evt) {
case LP_ENC_EVT_RUN:
/* Pause Tx data */
llcp_tx_pause_data(conn, LLCP_TX_QUEUE_PAUSE_DATA_ENCRYPTION);
lp_enc_send_enc_req(conn, ctx, evt, param);
break;
default:
/* Ignore other evts */
break;
}
}
static void lp_enc_st_wait_tx_enc_req(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt,
void *param)
{
switch (evt) {
case LP_ENC_EVT_RUN:
lp_enc_send_enc_req(conn, ctx, evt, param);
break;
default:
/* Ignore other evts */
break;
}
}
static void lp_enc_store_s(struct ll_conn *conn, struct proc_ctx *ctx, struct pdu_data *pdu)
{
/* Store SKDs */
memcpy(&ctx->data.enc.skd[8], pdu->llctrl.enc_rsp.skds, sizeof(pdu->llctrl.enc_rsp.skds));
/* Store IVs in the LLL CCM RX
* TODO(thoh): Should this be made into a ULL function, as it
* interacts with data outside of LLCP?
*/
memcpy(&conn->lll.ccm_rx.iv[4], pdu->llctrl.enc_rsp.ivs, sizeof(pdu->llctrl.enc_rsp.ivs));
}
static inline uint8_t reject_error_code(struct pdu_data *pdu)
{
if (pdu->llctrl.opcode == PDU_DATA_LLCTRL_TYPE_REJECT_IND) {
return pdu->llctrl.reject_ind.error_code;
#if defined(CONFIG_BT_CTLR_EXT_REJ_IND)
} else if (pdu->llctrl.opcode == PDU_DATA_LLCTRL_TYPE_REJECT_EXT_IND) {
return pdu->llctrl.reject_ext_ind.error_code;
#endif /* CONFIG_BT_CTLR_EXT_REJ_IND */
} else {
/* Called with an invalid PDU */
LL_ASSERT(0);
/* Keep compiler happy */
return BT_HCI_ERR_UNSPECIFIED;
}
}
static void lp_enc_st_wait_rx_enc_rsp(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt,
void *param)
{
struct pdu_data *pdu = (struct pdu_data *)param;
switch (evt) {
case LP_ENC_EVT_ENC_RSP:
/* Pause Rx data */
ull_conn_pause_rx_data(conn);
lp_enc_store_s(conn, ctx, pdu);
/* After the Central has received the LL_ENC_RSP PDU,
* only PDUs related to this procedure are valid, and invalids should
* result in disconnect.
* to achieve this enable the greedy RX behaviour, such that
* all PDU's end up in this FSM.
*/
ctx->rx_greedy = 1U;
/* Wait for LL_START_ENC_REQ */
ctx->rx_opcode = PDU_DATA_LLCTRL_TYPE_START_ENC_REQ;
ctx->state = LP_ENC_STATE_WAIT_RX_START_ENC_REQ;
break;
case LP_ENC_EVT_REJECT:
/* Encryption is not supported by the Link Layer of the Peripheral */
/* Resume Tx data */
llcp_tx_resume_data(conn, LLCP_TX_QUEUE_PAUSE_DATA_ENCRYPTION);
/* Store the error reason */
ctx->data.enc.error = reject_error_code(pdu);
/* Resume possibly paused remote procedure */
llcp_rr_resume(conn);
/* Complete the procedure */
lp_enc_complete(conn, ctx, evt, param);
break;
default:
/* Ignore other evts */
break;
}
}
static void lp_enc_st_wait_rx_start_enc_req(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt,
void *param)
{
struct pdu_data *pdu = (struct pdu_data *)param;
switch (evt) {
case LP_ENC_EVT_START_ENC_REQ:
lp_enc_send_start_enc_rsp(conn, ctx, evt, param);
break;
case LP_ENC_EVT_REJECT:
/* Resume Tx data */
llcp_tx_resume_data(conn, LLCP_TX_QUEUE_PAUSE_DATA_ENCRYPTION);
/* Resume Rx data */
ull_conn_resume_rx_data(conn);
/* Store the error reason */
ctx->data.enc.error = reject_error_code(pdu);
/* Resume possibly paused remote procedure */
llcp_rr_resume(conn);
/* Disable the greedy behaviour */
ctx->rx_greedy = 0U;
lp_enc_complete(conn, ctx, evt, param);
break;
default:
/* Ignore other evts */
break;
}
}
static void lp_enc_st_wait_tx_start_enc_rsp(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt,
void *param)
{
switch (evt) {
case LP_ENC_EVT_RUN:
lp_enc_send_start_enc_rsp(conn, ctx, evt, param);
break;
default:
/* Ignore other evts */
break;
}
}
static void lp_enc_st_wait_rx_start_enc_rsp(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt,
void *param)
{
switch (evt) {
case LP_ENC_EVT_START_ENC_RSP:
/* Resume Tx data */
llcp_tx_resume_data(conn, LLCP_TX_QUEUE_PAUSE_DATA_ENCRYPTION);
/* Resume Rx data */
ull_conn_resume_rx_data(conn);
ctx->data.enc.error = BT_HCI_ERR_SUCCESS;
/* Resume possibly paused remote procedure */
llcp_rr_resume(conn);
/* Disable the greedy behaviour */
ctx->rx_greedy = 0U;
lp_enc_complete(conn, ctx, evt, param);
break;
default:
/* Ignore other evts */
break;
}
}
static void lp_enc_st_wait_ntf(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, void *param)
{
switch (evt) {
case LP_ENC_EVT_RUN:
lp_enc_complete(conn, ctx, evt, param);
break;
default:
/* Ignore other evts */
break;
}
}
static void lp_enc_state_encrypted(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt,
void *param)
{
switch (evt) {
case LP_ENC_EVT_RUN:
/* Pause Tx data */
llcp_tx_pause_data(conn, LLCP_TX_QUEUE_PAUSE_DATA_ENCRYPTION);
lp_enc_send_pause_enc_req(conn, ctx, evt, param);
break;
default:
/* Ignore other evts */
break;
}
}
static void lp_enc_state_wait_tx_pause_enc_req(struct ll_conn *conn, struct proc_ctx *ctx,
uint8_t evt, void *param)
{
switch (evt) {
case LP_ENC_EVT_RUN:
lp_enc_send_pause_enc_req(conn, ctx, evt, param);
break;
default:
/* Ignore other evts */
break;
}
}
static void lp_enc_state_wait_rx_pause_enc_rsp(struct ll_conn *conn, struct proc_ctx *ctx,
uint8_t evt, void *param)
{
switch (evt) {
case LP_ENC_EVT_PAUSE_ENC_RSP:
/*
* Pause Rx data; will be resumed when the encapsulated
* Start Procedure is done.
*/
ull_conn_pause_rx_data(conn);
lp_enc_send_pause_enc_rsp(conn, ctx, evt, param);
break;
default:
/* Ignore other evts */
break;
}
}
static void lp_enc_state_wait_tx_pause_enc_rsp(struct ll_conn *conn, struct proc_ctx *ctx,
uint8_t evt, void *param)
{
switch (evt) {
case LP_ENC_EVT_RUN:
lp_enc_send_pause_enc_rsp(conn, ctx, evt, param);
break;
default:
/* Ignore other evts */
break;
}
}
static void lp_enc_execute_fsm(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, void *param)
{
switch (ctx->state) {
/* Start Procedure */
case LP_ENC_STATE_UNENCRYPTED:
lp_enc_st_unencrypted(conn, ctx, evt, param);
break;
case LP_ENC_STATE_WAIT_TX_ENC_REQ:
lp_enc_st_wait_tx_enc_req(conn, ctx, evt, param);
break;
case LP_ENC_STATE_WAIT_RX_ENC_RSP:
lp_enc_st_wait_rx_enc_rsp(conn, ctx, evt, param);
break;
case LP_ENC_STATE_WAIT_RX_START_ENC_REQ:
lp_enc_st_wait_rx_start_enc_req(conn, ctx, evt, param);
break;
case LP_ENC_STATE_WAIT_TX_START_ENC_RSP:
lp_enc_st_wait_tx_start_enc_rsp(conn, ctx, evt, param);
break;
case LP_ENC_STATE_WAIT_RX_START_ENC_RSP:
lp_enc_st_wait_rx_start_enc_rsp(conn, ctx, evt, param);
break;
case LP_ENC_STATE_WAIT_NTF:
lp_enc_st_wait_ntf(conn, ctx, evt, param);
break;
/* Pause Procedure */
case LP_ENC_STATE_ENCRYPTED:
lp_enc_state_encrypted(conn, ctx, evt, param);
break;
case LP_ENC_STATE_WAIT_TX_PAUSE_ENC_REQ:
lp_enc_state_wait_tx_pause_enc_req(conn, ctx, evt, param);
break;
case LP_ENC_STATE_WAIT_RX_PAUSE_ENC_RSP:
lp_enc_state_wait_rx_pause_enc_rsp(conn, ctx, evt, param);
break;
case LP_ENC_STATE_WAIT_TX_PAUSE_ENC_RSP:
lp_enc_state_wait_tx_pause_enc_rsp(conn, ctx, evt, param);
break;
default:
/* Unknown state */
LL_ASSERT(0);
}
}
void llcp_lp_enc_rx(struct ll_conn *conn, struct proc_ctx *ctx, struct node_rx_pdu *rx)
{
struct pdu_data *pdu = (struct pdu_data *)rx->pdu;
switch (pdu->llctrl.opcode) {
case PDU_DATA_LLCTRL_TYPE_ENC_RSP:
lp_enc_execute_fsm(conn, ctx, LP_ENC_EVT_ENC_RSP, pdu);
break;
case PDU_DATA_LLCTRL_TYPE_START_ENC_REQ:
lp_enc_execute_fsm(conn, ctx, LP_ENC_EVT_START_ENC_REQ, pdu);
break;
case PDU_DATA_LLCTRL_TYPE_START_ENC_RSP:
lp_enc_execute_fsm(conn, ctx, LP_ENC_EVT_START_ENC_RSP, pdu);
break;
case PDU_DATA_LLCTRL_TYPE_REJECT_IND:
case PDU_DATA_LLCTRL_TYPE_REJECT_EXT_IND:
lp_enc_execute_fsm(conn, ctx, LP_ENC_EVT_REJECT, pdu);
break;
case PDU_DATA_LLCTRL_TYPE_PAUSE_ENC_RSP:
lp_enc_execute_fsm(conn, ctx, LP_ENC_EVT_PAUSE_ENC_RSP, pdu);
break;
default:
/* Unknown opcode */
/*
* BLUETOOTH CORE SPECIFICATION Version 5.3
* Vol 6, Part B, 5.1.3.1 Encryption Start procedure
*
* [...]
*
* If, at any time during the encryption start procedure after the Peripheral has
* received the LL_ENC_REQ PDU or the Central has received the
* LL_ENC_RSP PDU, the Link Layer of the Central or the Peripheral receives an
* unexpected Data Physical Channel PDU from the peer Link Layer, it shall
* immediately exit the Connection state, and shall transition to the Standby state.
* The Host shall be notified that the link has been disconnected with the error
* code Connection Terminated Due to MIC Failure (0x3D).
*/
conn->llcp_terminate.reason_final = BT_HCI_ERR_TERM_DUE_TO_MIC_FAIL;
}
}
void llcp_lp_enc_init_proc(struct proc_ctx *ctx)
{
switch (ctx->proc) {
case PROC_ENCRYPTION_START:
ctx->state = LP_ENC_STATE_UNENCRYPTED;
break;
case PROC_ENCRYPTION_PAUSE:
ctx->state = LP_ENC_STATE_ENCRYPTED;
break;
default:
LL_ASSERT(0);
}
}
void llcp_lp_enc_run(struct ll_conn *conn, struct proc_ctx *ctx, void *param)
{
lp_enc_execute_fsm(conn, ctx, LP_ENC_EVT_RUN, param);
}
#endif /* CONFIG_BT_CENTRAL */
#if defined(CONFIG_BT_PERIPHERAL)
/*
* LLCP Remote Procedure Encryption FSM
*/
static struct node_tx *llcp_rp_enc_tx(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t opcode)
{
struct node_tx *tx;
struct pdu_data *pdu;
/* Allocate tx node */
tx = llcp_tx_alloc(conn, ctx);
LL_ASSERT(tx);
pdu = (struct pdu_data *)tx->pdu;
/* Encode LL Control PDU */
switch (opcode) {
case PDU_DATA_LLCTRL_TYPE_ENC_RSP:
llcp_pdu_encode_enc_rsp(pdu);
break;
case PDU_DATA_LLCTRL_TYPE_START_ENC_REQ:
llcp_pdu_encode_start_enc_req(pdu);
break;
case PDU_DATA_LLCTRL_TYPE_START_ENC_RSP:
llcp_pdu_encode_start_enc_rsp(pdu);
break;
case PDU_DATA_LLCTRL_TYPE_PAUSE_ENC_RSP:
llcp_pdu_encode_pause_enc_rsp(pdu);
break;
case PDU_DATA_LLCTRL_TYPE_REJECT_IND:
if (conn->llcp.fex.valid && feature_ext_rej_ind(conn)) {
llcp_pdu_encode_reject_ext_ind(pdu, PDU_DATA_LLCTRL_TYPE_ENC_REQ,
BT_HCI_ERR_PIN_OR_KEY_MISSING);
} else {
llcp_pdu_encode_reject_ind(pdu, BT_HCI_ERR_PIN_OR_KEY_MISSING);
}
break;
default:
LL_ASSERT(0);
}
ctx->tx_opcode = pdu->llctrl.opcode;
/* Enqueue LL Control PDU towards LLL */
llcp_tx_enqueue(conn, tx);
/* Restart procedure response timeout timer */
llcp_rr_prt_restart(conn);
return tx;
}
static void rp_enc_ntf_ltk(struct ll_conn *conn, struct proc_ctx *ctx)
{
struct node_rx_pdu *ntf;
struct pdu_data *pdu;
/* Allocate ntf node */
ntf = llcp_ntf_alloc();
LL_ASSERT(ntf);
ntf->hdr.type = NODE_RX_TYPE_DC_PDU;
ntf->hdr.handle = conn->lll.handle;
pdu = (struct pdu_data *)ntf->pdu;
llcp_ntf_encode_enc_req(ctx, pdu);
/* Enqueue notification towards LL */
ll_rx_put(ntf->hdr.link, ntf);
ll_rx_sched();
}
static void rp_enc_ntf(struct ll_conn *conn, struct proc_ctx *ctx)
{
struct node_rx_pdu *ntf;
struct pdu_data *pdu;
/* Allocate ntf node */
ntf = llcp_ntf_alloc();
LL_ASSERT(ntf);
ntf->hdr.type = NODE_RX_TYPE_DC_PDU;
ntf->hdr.handle = conn->lll.handle;
pdu = (struct pdu_data *)ntf->pdu;
if (ctx->proc == PROC_ENCRYPTION_START) {
/* Encryption Change Event */
/* TODO(thoh): is this correct? */
llcp_pdu_encode_start_enc_rsp(pdu);
} else if (ctx->proc == PROC_ENCRYPTION_PAUSE) {
/* Encryption Key Refresh Complete Event */
ntf->hdr.type = NODE_RX_TYPE_ENC_REFRESH;
} else {
/* Should never happen */
LL_ASSERT(0);
}
/* Enqueue notification towards LL */
ll_rx_put(ntf->hdr.link, ntf);
ll_rx_sched();
}
static void rp_enc_send_start_enc_rsp(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt,
void *param);
static void rp_enc_complete(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, void *param)
{
if (!llcp_ntf_alloc_is_available()) {
ctx->state = RP_ENC_STATE_WAIT_NTF;
} else {
rp_enc_ntf(conn, ctx);
rp_enc_send_start_enc_rsp(conn, ctx, evt, param);
}
}
static void rp_enc_send_ltk_ntf(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt,
void *param)
{
if (!llcp_ntf_alloc_is_available()) {
ctx->state = RP_ENC_STATE_WAIT_NTF_LTK_REQ;
} else {
rp_enc_ntf_ltk(conn, ctx);
ctx->state = RP_ENC_STATE_WAIT_LTK_REPLY;
}
}
static void rp_enc_store_s(struct ll_conn *conn, struct proc_ctx *ctx, struct pdu_data *pdu)
{
/* Store SKDs */
memcpy(&ctx->data.enc.skds, pdu->llctrl.enc_rsp.skds, sizeof(pdu->llctrl.enc_rsp.skds));
/* Store IVs in the LLL CCM RX
* TODO(thoh): Should this be made into a ULL function, as it
* interacts with data outside of LLCP?
*/
memcpy(&conn->lll.ccm_rx.iv[4], pdu->llctrl.enc_rsp.ivs, sizeof(pdu->llctrl.enc_rsp.ivs));
}
static void rp_enc_send_enc_rsp(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt,
void *param)
{
struct node_tx *tx;
if (!llcp_tx_alloc_peek(conn, ctx)) {
ctx->state = RP_ENC_STATE_WAIT_TX_ENC_RSP;
} else {
tx = llcp_rp_enc_tx(conn, ctx, PDU_DATA_LLCTRL_TYPE_ENC_RSP);
rp_enc_store_s(conn, ctx, (struct pdu_data *)tx->pdu);
rp_enc_send_ltk_ntf(conn, ctx, evt, param);
}
}
static void rp_enc_setup_lll(struct ll_conn *conn, struct proc_ctx *ctx)
{
/* TODO(thoh): Move LLL/CCM manipulation to ULL? */
/* Calculate the Session Key */
ecb_encrypt(&ctx->data.enc.ltk[0], &ctx->data.enc.skd[0], NULL, &conn->lll.ccm_rx.key[0]);
/* Copy the Session Key */
memcpy(&conn->lll.ccm_tx.key[0], &conn->lll.ccm_rx.key[0], sizeof(conn->lll.ccm_tx.key));
/* Copy the IV */
memcpy(&conn->lll.ccm_tx.iv[0], &conn->lll.ccm_rx.iv[0], sizeof(conn->lll.ccm_tx.iv));
/* Reset CCM counter */
conn->lll.ccm_tx.counter = 0U;
conn->lll.ccm_rx.counter = 0U;
/* Set CCM direction:
* periph to central = 0,
* central to periph = 1
*/
conn->lll.ccm_tx.direction = 0U;
conn->lll.ccm_rx.direction = 1U;
}
static void rp_enc_send_start_enc_req(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt,
void *param)
{
if (!llcp_tx_alloc_peek(conn, ctx)) {
ctx->state = RP_ENC_STATE_WAIT_TX_START_ENC_REQ;
} else {
rp_enc_setup_lll(conn, ctx);
llcp_rp_enc_tx(conn, ctx, PDU_DATA_LLCTRL_TYPE_START_ENC_REQ);
/* Wait for the LL_START_ENC_RSP */
ctx->rx_opcode = PDU_DATA_LLCTRL_TYPE_START_ENC_RSP;
ctx->state = RP_ENC_STATE_WAIT_RX_START_ENC_RSP;
/* Rx Decryption enabled */
conn->lll.enc_rx = 1U;
}
}
static void rp_enc_send_reject_ind(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt,
void *param)
{
if (!llcp_tx_alloc_peek(conn, ctx)) {
ctx->state = RP_ENC_STATE_WAIT_TX_REJECT_IND;
} else {
llcp_rp_enc_tx(conn, ctx, PDU_DATA_LLCTRL_TYPE_REJECT_IND);
llcp_rr_complete(conn);
ctx->state = RP_ENC_STATE_UNENCRYPTED;
/* Resume Tx data */
llcp_tx_resume_data(conn, LLCP_TX_QUEUE_PAUSE_DATA_ENCRYPTION);
/* Resume Rx data */
ull_conn_resume_rx_data(conn);
/* Resume possibly paused local procedure */
llcp_lr_resume(conn);
}
}
static void rp_enc_send_start_enc_rsp(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt,
void *param)
{
if (!llcp_tx_alloc_peek(conn, ctx)) {
ctx->state = RP_ENC_STATE_WAIT_TX_START_ENC_RSP;
} else {
llcp_rp_enc_tx(conn, ctx, PDU_DATA_LLCTRL_TYPE_START_ENC_RSP);
llcp_rr_complete(conn);
ctx->state = RP_ENC_STATE_UNENCRYPTED;
/* Resume Tx data */
llcp_tx_resume_data(conn, LLCP_TX_QUEUE_PAUSE_DATA_ENCRYPTION);
/* Resume Rx data */
ull_conn_resume_rx_data(conn);
/* Resume possibly paused local procedure */
llcp_lr_resume(conn);
/* Tx Encryption enabled */
conn->lll.enc_tx = 1U;
}
}
static void rp_enc_send_pause_enc_rsp(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt,
void *param)
{
if (!llcp_tx_alloc_peek(conn, ctx)) {
ctx->state = RP_ENC_STATE_WAIT_TX_PAUSE_ENC_RSP;
} else {
llcp_rp_enc_tx(conn, ctx, PDU_DATA_LLCTRL_TYPE_PAUSE_ENC_RSP);
/* Wait for the LL_PAUSE_ENC_RSP */
ctx->rx_opcode = PDU_DATA_LLCTRL_TYPE_PAUSE_ENC_RSP;
ctx->state = RP_ENC_STATE_WAIT_RX_PAUSE_ENC_RSP;
/* Rx Decryption disabled */
conn->lll.enc_rx = 0U;
}
}
static void rp_enc_state_unencrypted(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt,
void *param)
{
switch (evt) {
case RP_ENC_EVT_RUN:
ctx->state = RP_ENC_STATE_WAIT_RX_ENC_REQ;
break;
default:
/* Ignore other evts */
break;
}
}
static void rp_enc_store_m(struct ll_conn *conn, struct proc_ctx *ctx, struct pdu_data *pdu)
{
/* Store Rand */
memcpy(ctx->data.enc.rand, pdu->llctrl.enc_req.rand, sizeof(ctx->data.enc.rand));
/* Store EDIV */
ctx->data.enc.ediv[0] = pdu->llctrl.enc_req.ediv[0];
ctx->data.enc.ediv[1] = pdu->llctrl.enc_req.ediv[1];
/* Store SKDm */
memcpy(&ctx->data.enc.skdm, pdu->llctrl.enc_req.skdm, sizeof(ctx->data.enc.skdm));
/* Store IVm in the LLL CCM RX
* TODO(thoh): Should this be made into a ULL function, as it
* interacts with data outside of LLCP?
*/
memcpy(&conn->lll.ccm_rx.iv[0], pdu->llctrl.enc_req.ivm, sizeof(pdu->llctrl.enc_req.ivm));
}
static void rp_enc_state_wait_rx_enc_req(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt,
void *param)
{
switch (evt) {
case RP_ENC_EVT_ENC_REQ:
/* Pause Tx data */
llcp_tx_pause_data(conn, LLCP_TX_QUEUE_PAUSE_DATA_ENCRYPTION);
/* Pause Rx data */
ull_conn_pause_rx_data(conn);
/* Pause possibly paused local procedure */
llcp_lr_pause(conn);
rp_enc_store_m(conn, ctx, param);
rp_enc_send_enc_rsp(conn, ctx, evt, param);
break;
default:
/* Ignore other evts */
break;
}
}
static void rp_enc_state_wait_tx_enc_rsp(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt,
void *param)
{
switch (evt) {
case RP_ENC_EVT_RUN:
rp_enc_send_enc_rsp(conn, ctx, evt, param);
break;
default:
/* Ignore other evts */
break;
}
}
static void rp_enc_state_wait_ntf_ltk_req(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt,
void *param)
{
switch (evt) {
case RP_ENC_EVT_RUN:
rp_enc_send_ltk_ntf(conn, ctx, evt, param);
break;
default:
/* Ignore other evts */
break;
}
}
static void rp_enc_state_wait_ltk_reply(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt,
void *param)
{
switch (evt) {
case RP_ENC_EVT_LTK_REQ_REPLY:
rp_enc_send_start_enc_req(conn, ctx, evt, param);
break;
case RP_ENC_EVT_LTK_REQ_NEG_REPLY:
rp_enc_send_reject_ind(conn, ctx, evt, param);
break;
default:
/* Ignore other evts */
break;
}
}
static void rp_enc_state_wait_tx_start_enc_req(struct ll_conn *conn, struct proc_ctx *ctx,
uint8_t evt, void *param)
{
switch (evt) {
case RP_ENC_EVT_RUN:
rp_enc_send_start_enc_req(conn, ctx, evt, param);
break;
default:
/* Ignore other evts */
break;
}
}
static void rp_enc_state_wait_tx_reject_ind(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt,
void *param)
{
switch (evt) {
case RP_ENC_EVT_RUN:
rp_enc_send_reject_ind(conn, ctx, evt, param);
break;
default:
/* Ignore other evts */
break;
}
}
static void rp_enc_state_wait_rx_start_enc_rsp(struct ll_conn *conn, struct proc_ctx *ctx,
uint8_t evt, void *param)
{
switch (evt) {
case RP_ENC_EVT_START_ENC_RSP:
rp_enc_complete(conn, ctx, evt, param);
break;
default:
/* Ignore other evts */
break;
}
}
static void rp_enc_state_wait_ntf(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt,
void *param)
{
switch (evt) {
case RP_ENC_EVT_RUN:
rp_enc_complete(conn, ctx, evt, param);
break;
default:
/* Ignore other evts */
break;
}
}
static void rp_enc_state_wait_tx_start_enc_rsp(struct ll_conn *conn, struct proc_ctx *ctx,
uint8_t evt, void *param)
{
switch (evt) {
case RP_ENC_EVT_RUN:
rp_enc_send_start_enc_rsp(conn, ctx, evt, param);
break;
default:
/* Ignore other evts */
break;
}
}
static void rp_enc_state_encrypted(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt,
void *param)
{
switch (evt) {
case RP_ENC_EVT_RUN:
ctx->state = RP_ENC_STATE_WAIT_RX_PAUSE_ENC_REQ;
break;
default:
/* Ignore other evts */
break;
}
}
static void rp_enc_state_wait_rx_pause_enc_req(struct ll_conn *conn, struct proc_ctx *ctx,
uint8_t evt, void *param)
{
switch (evt) {
case RP_ENC_EVT_PAUSE_ENC_REQ:
/* Pause Tx data */
llcp_tx_pause_data(conn, LLCP_TX_QUEUE_PAUSE_DATA_ENCRYPTION);
/*
* Pause Rx data; will be resumed when the encapsulated
* Start Procedure is done.
*/
ull_conn_pause_rx_data(conn);
rp_enc_send_pause_enc_rsp(conn, ctx, evt, param);
break;
default:
/* Ignore other evts */
break;
}
}
static void rp_enc_state_wait_tx_pause_enc_rsp(struct ll_conn *conn, struct proc_ctx *ctx,
uint8_t evt, void *param)
{
switch (evt) {
case RP_ENC_EVT_RUN:
rp_enc_send_pause_enc_rsp(conn, ctx, evt, param);
break;
default:
/* Ignore other evts */
break;
}
}
static void rp_enc_state_wait_rx_pause_enc_rsp(struct ll_conn *conn, struct proc_ctx *ctx,
uint8_t evt, void *param)
{
switch (evt) {
case RP_ENC_EVT_PAUSE_ENC_RSP:
/* Continue with an encapsulated Start Procedure */
/* Wait for the LL_ENC_REQ */
ctx->rx_opcode = PDU_DATA_LLCTRL_TYPE_ENC_REQ;
ctx->state = RP_ENC_STATE_WAIT_RX_ENC_REQ;
/* Tx Encryption disabled */
conn->lll.enc_tx = 0U;
break;
default:
/* Ignore other evts */
break;
}
}
static void rp_enc_execute_fsm(struct ll_conn *conn, struct proc_ctx *ctx, uint8_t evt, void *param)
{
switch (ctx->state) {
/* Start Procedure */
case RP_ENC_STATE_UNENCRYPTED:
rp_enc_state_unencrypted(conn, ctx, evt, param);
break;
case RP_ENC_STATE_WAIT_RX_ENC_REQ:
rp_enc_state_wait_rx_enc_req(conn, ctx, evt, param);
break;
case RP_ENC_STATE_WAIT_TX_ENC_RSP:
rp_enc_state_wait_tx_enc_rsp(conn, ctx, evt, param);
break;
case RP_ENC_STATE_WAIT_NTF_LTK_REQ:
rp_enc_state_wait_ntf_ltk_req(conn, ctx, evt, param);
break;
case RP_ENC_STATE_WAIT_LTK_REPLY:
rp_enc_state_wait_ltk_reply(conn, ctx, evt, param);
break;
case RP_ENC_STATE_WAIT_TX_START_ENC_REQ:
rp_enc_state_wait_tx_start_enc_req(conn, ctx, evt, param);
break;
case RP_ENC_STATE_WAIT_TX_REJECT_IND:
rp_enc_state_wait_tx_reject_ind(conn, ctx, evt, param);
break;
case RP_ENC_STATE_WAIT_RX_START_ENC_RSP:
rp_enc_state_wait_rx_start_enc_rsp(conn, ctx, evt, param);
break;
case RP_ENC_STATE_WAIT_NTF:
rp_enc_state_wait_ntf(conn, ctx, evt, param);
break;
case RP_ENC_STATE_WAIT_TX_START_ENC_RSP:
rp_enc_state_wait_tx_start_enc_rsp(conn, ctx, evt, param);
break;
/* Pause Procedure */
case RP_ENC_STATE_ENCRYPTED:
rp_enc_state_encrypted(conn, ctx, evt, param);
break;
case RP_ENC_STATE_WAIT_RX_PAUSE_ENC_REQ:
rp_enc_state_wait_rx_pause_enc_req(conn, ctx, evt, param);
break;
case RP_ENC_STATE_WAIT_TX_PAUSE_ENC_RSP:
rp_enc_state_wait_tx_pause_enc_rsp(conn, ctx, evt, param);
break;
case RP_ENC_STATE_WAIT_RX_PAUSE_ENC_RSP:
rp_enc_state_wait_rx_pause_enc_rsp(conn, ctx, evt, param);
break;
default:
/* Unknown state */
LL_ASSERT(0);
}
}
void llcp_rp_enc_rx(struct ll_conn *conn, struct proc_ctx *ctx, struct node_rx_pdu *rx)
{
struct pdu_data *pdu = (struct pdu_data *)rx->pdu;
switch (pdu->llctrl.opcode) {
case PDU_DATA_LLCTRL_TYPE_ENC_REQ:
rp_enc_execute_fsm(conn, ctx, RP_ENC_EVT_ENC_REQ, pdu);
break;
case PDU_DATA_LLCTRL_TYPE_START_ENC_RSP:
rp_enc_execute_fsm(conn, ctx, RP_ENC_EVT_START_ENC_RSP, pdu);
break;
case PDU_DATA_LLCTRL_TYPE_PAUSE_ENC_REQ:
rp_enc_execute_fsm(conn, ctx, RP_ENC_EVT_PAUSE_ENC_REQ, pdu);
break;
case PDU_DATA_LLCTRL_TYPE_PAUSE_ENC_RSP:
rp_enc_execute_fsm(conn, ctx, RP_ENC_EVT_PAUSE_ENC_RSP, pdu);
break;
default:
/* Unknown opcode */
/*
* BLUETOOTH CORE SPECIFICATION Version 5.3
* Vol 6, Part B, 5.1.3.1 Encryption Start procedure
*
* [...]
*
* If, at any time during the encryption start procedure after the Peripheral has
* received the LL_ENC_REQ PDU or the Central has received the
* LL_ENC_RSP PDU, the Link Layer of the Central or the Peripheral receives an
* unexpected Data Physical Channel PDU from the peer Link Layer, it shall
* immediately exit the Connection state, and shall transition to the Standby state.
* The Host shall be notified that the link has been disconnected with the error
* code Connection Terminated Due to MIC Failure (0x3D).
*/
conn->llcp_terminate.reason_final = BT_HCI_ERR_TERM_DUE_TO_MIC_FAIL;
}
}
void llcp_rp_enc_init_proc(struct proc_ctx *ctx)
{
switch (ctx->proc) {
case PROC_ENCRYPTION_START:
ctx->state = RP_ENC_STATE_UNENCRYPTED;
break;
case PROC_ENCRYPTION_PAUSE:
ctx->state = RP_ENC_STATE_ENCRYPTED;
break;
default:
LL_ASSERT(0);
}
}
void llcp_rp_enc_ltk_req_reply(struct ll_conn *conn, struct proc_ctx *ctx)
{
rp_enc_execute_fsm(conn, ctx, RP_ENC_EVT_LTK_REQ_REPLY, NULL);
}
void llcp_rp_enc_ltk_req_neg_reply(struct ll_conn *conn, struct proc_ctx *ctx)
{
rp_enc_execute_fsm(conn, ctx, RP_ENC_EVT_LTK_REQ_NEG_REPLY, NULL);
}
bool llcp_rp_enc_ltk_req_reply_allowed(struct ll_conn *conn, struct proc_ctx *ctx)
{
return (ctx->state == RP_ENC_STATE_WAIT_LTK_REPLY);
}
void llcp_rp_enc_run(struct ll_conn *conn, struct proc_ctx *ctx, void *param)
{
rp_enc_execute_fsm(conn, ctx, RP_ENC_EVT_RUN, param);
}
#endif /* CONFIG_BT_PERIPHERAL */