fix(ble/bluedroid): BLE credit, reject when p_rcb NULL, timeout and leak fixes

- l2c_int: align struct/constant types with l2c_ble/l2c_main
- l2c_api: null/state checks in L2CA_SendFixedChnlData
- l2c_ble: reject when p_rcb==NULL, add L2CAP_CMD_BLE_FLOW_CTRL_CREDIT;
  l2cble_init_direct_conn int64_t timeout and link_timeout==0 fix
- l2c_link: null/state checks and cleanup in hci_disc_comp/timeout/send_to_lower
- l2c_main: free p_msg on FCR non-Basic and COC branches; fix LE credit handling;
  process_l2cap_cmd bounds
- l2c_utils: credit/queue cleanup and null checks in l2cu_disconnect_chnl


(cherry picked from commit 16d523e9bf)

Co-authored-by: zhiweijian <zhiweijian@espressif.com>
This commit is contained in:
Zhi Wei Jian
2026-03-25 13:58:27 +08:00
committed by zhiweijian
parent 5399361622
commit c89b01c3e5
6 changed files with 119 additions and 33 deletions
@@ -461,7 +461,7 @@ typedef struct t_l2c_linkcb {
*/
/* create connection retry count*/
UINT8 retry_create_con;
UINT32 start_time_s;
int64_t start_time_s;
#endif
#if (L2CAP_ROUND_ROBIN_CHANNEL_SERVICE == TRUE)
@@ -736,7 +736,7 @@ extern void l2c_link_timeout (tL2C_LCB *p_lcb);
extern void l2c_info_timeout (tL2C_LCB *p_lcb);
extern void l2c_link_check_send_pkts (tL2C_LCB *p_lcb, tL2C_CCB *p_ccb, BT_HDR *p_buf);
extern void l2c_link_adjust_allocation (void);
extern void l2c_link_process_num_completed_pkts (UINT8 *p);
extern void l2c_link_process_num_completed_pkts (UINT8 *p, UINT8 evt_len);
extern void l2c_link_process_num_completed_blocks (UINT8 controller_id, UINT8 *p, UINT16 evt_len);
extern void l2c_link_processs_num_bufs (UINT16 num_lm_acl_bufs);
extern UINT8 l2c_link_pkts_rcvd (UINT16 *num_pkts, UINT16 *handles);
@@ -1863,6 +1863,11 @@ UINT16 L2CA_SendFixedChnlData (UINT16 fixed_cid, BD_ADDR rem_bda, BT_HDR *p_buf)
BOOLEAN L2CA_CheckIsCongest(UINT16 fixed_cid, BD_ADDR addr)
{
if ((fixed_cid < L2CAP_FIRST_FIXED_CHNL) || (fixed_cid > L2CAP_LAST_FIXED_CHNL)) {
L2CAP_TRACE_ERROR ("L2CA_CheckIsCongest() Invalid CID: 0x%04x", fixed_cid);
return TRUE;
}
tL2C_LCB *p_lcb;
p_lcb = l2cu_find_lcb_by_bd_addr(addr, BT_TRANSPORT_LE);
@@ -828,7 +828,6 @@ void l2cble_process_sig_cmd (tL2C_LCB *p_lcb, UINT8 *p, UINT16 pkt_len)
STREAM_TO_UINT16(mps, p);
STREAM_TO_UINT16(credits, p);
L2CAP_TRACE_DEBUG("%s spsm %x, scid %x", __func__, spsm, scid);
UNUSED(spsm);
p_ccb = l2cu_find_ccb_by_remote_cid(p_lcb, scid);
if (p_ccb) {
@@ -836,12 +835,11 @@ void l2cble_process_sig_cmd (tL2C_LCB *p_lcb, UINT8 *p, UINT16 pkt_len)
break;
}
#if 0
p_rcb = l2cu_find_ble_rcb_by_psm(spsm);
if (p_rcb == NULL) {
l2cu_reject_ble_connection(p_lcb, id, L2CAP_LE_RESULT_NO_PSM);
break;
}
#endif
p_ccb = l2cu_allocate_ccb(p_lcb, 0);
if (p_ccb == NULL) {
@@ -862,6 +860,34 @@ void l2cble_process_sig_cmd (tL2C_LCB *p_lcb, UINT8 *p, UINT16 pkt_len)
l2cu_send_peer_ble_credit_based_conn_res(p_ccb, L2CAP_LE_RESULT_CONN_OK);
break;
}
case L2CAP_CMD_BLE_FLOW_CTRL_CREDIT: {
if (cmd_len < L2CAP_CMD_BLE_FLOW_CTRL_CREDIT_LEN) {
L2CAP_TRACE_WARNING ("L2CAP - LE - flow ctrl credit too short: %d", cmd_len);
return;
}
tL2C_CCB *p_ccb = NULL;
UINT16 lcid;
UINT16 credit;
STREAM_TO_UINT16(lcid, p);
STREAM_TO_UINT16(credit, p);
p_ccb = l2cu_find_ccb_by_cid(p_lcb, lcid);
if (p_ccb == NULL) {
L2CAP_TRACE_WARNING ("L2CAP - LE - flow ctrl credit for unknown CID: 0x%04x", lcid);
break;
}
if ((p_ccb->peer_conn_cfg.credits + credit) > L2CAP_LE_MAX_CREDIT) {
L2CAP_TRACE_WARNING ("L2CAP - LE - credit overflow, disconnecting CID: 0x%04x", lcid);
l2cble_send_peer_disc_req(p_ccb);
} else {
p_ccb->peer_conn_cfg.credits += credit;
L2CAP_TRACE_DEBUG ("L2CAP - LE - credits updated to %d for CID: 0x%04x",
p_ccb->peer_conn_cfg.credits, lcid);
l2c_link_check_send_pkts(p_ccb->p_lcb, NULL, NULL);
}
break;
}
case L2CAP_CMD_DISC_REQ: {
if (cmd_len < 4) {
L2CAP_TRACE_WARNING ("L2CAP - LE - short cmd: %d", cmd_len);
@@ -988,13 +1014,20 @@ BOOLEAN l2cble_init_direct_conn (tL2C_LCB *p_lcb)
uint32_t link_timeout = L2CAP_BLE_LINK_CONNECT_TOUT;
if(GATTC_CONNECT_RETRY_COUNT) {
if(!p_lcb->retry_create_con) {
p_lcb->start_time_s = (esp_system_get_time()/1000);
p_lcb->start_time_s = esp_system_get_time() / 1000;
}
uint32_t current_time = (esp_system_get_time()/1000);
link_timeout = (L2CAP_BLE_LINK_CONNECT_TOUT*1000 - (current_time - p_lcb->start_time_s))/1000;
int64_t current_time = esp_system_get_time() / 1000;
int64_t elapsed_ms = current_time - p_lcb->start_time_s;
int64_t timeout_ms = (int64_t)L2CAP_BLE_LINK_CONNECT_TOUT * 1000;
int64_t remaining_ms = timeout_ms - elapsed_ms;
if(link_timeout == 0 || link_timeout > L2CAP_BLE_LINK_CONNECT_TOUT) {
if (remaining_ms <= 0 || remaining_ms > timeout_ms) {
link_timeout = L2CAP_BLE_LINK_CONNECT_TOUT;
} else {
link_timeout = (uint32_t)(remaining_ms / 1000);
if (link_timeout == 0) {
link_timeout = 1;
}
}
}
@@ -379,6 +379,9 @@ BOOLEAN l2c_link_hci_disc_comp (UINT16 handle, UINT8 reason)
} else {
#if (BLE_INCLUDED == TRUE)
tL2C_LINK_STATE link_state_temp = p_lcb->link_state;
#if ((BLE_42_ADV_EN == TRUE) || (GATTC_CONNECT_RETRY_EN == TRUE) || (BLE_50_EXTEND_ADV_EN == TRUE))
UINT8 link_role_temp = p_lcb->link_role;
#endif
#endif // (BLE_INCLUDED == TRUE)
/* There can be a case when we rejected PIN code authentication */
/* otherwise save a new reason */
@@ -469,6 +472,7 @@ BOOLEAN l2c_link_hci_disc_comp (UINT16 handle, UINT8 reason)
}
#endif
}
/* l2cu_create_conn must not release the LCB on failure */
if (l2cu_create_conn(p_lcb, transport)) {
lcb_is_free = FALSE; /* still using this lcb */
}
@@ -476,13 +480,15 @@ BOOLEAN l2c_link_hci_disc_comp (UINT16 handle, UINT8 reason)
p_lcb->p_pending_ccb = NULL;
#if (BLE_INCLUDED == TRUE)
if(p_lcb->transport == BT_TRANSPORT_LE) {
/* Use p_lcb->transport so BLE reconnection (slave adv restart / master retry) runs even when
* there was no pending CCB; 'transport' is only set when (p_first_ccb || p_pending_ccb). */
if (p_lcb->transport == BT_TRANSPORT_LE) {
// for legacy adv, adv restart in gatt_le_connect_cback->gatt_cleanup_upon_disc->BTM_Recovery_Pre_State
if (reason == HCI_ERR_CONN_FAILED_ESTABLISHMENT) {
#if (BLE_42_FEATURE_SUPPORT == TRUE)
#if (BLE_42_ADV_EN == TRUE)
if(!btm_ble_inter_get() && p_lcb->link_role == HCI_ROLE_SLAVE) {
if(!btm_ble_inter_get() && link_role_temp == HCI_ROLE_SLAVE) {
L2CAP_TRACE_DEBUG("slave resatrt adv, retry count %d reason 0x%x\n", p_lcb->retry_create_con, reason);
tBTM_STATUS start_adv_status = btm_ble_start_adv();
if (start_adv_status != BTM_SUCCESS) {
@@ -496,7 +502,7 @@ BOOLEAN l2c_link_hci_disc_comp (UINT16 handle, UINT8 reason)
if ((reason == HCI_ERR_CONN_FAILED_ESTABLISHMENT) || (link_state_temp == LST_CONNECTING)) {
#if (GATTC_CONNECT_RETRY_EN == TRUE)
if(p_lcb->link_role == HCI_ROLE_MASTER && p_lcb->retry_create_con < GATTC_CONNECT_RETRY_COUNT) {
if(link_role_temp == HCI_ROLE_MASTER && p_lcb->retry_create_con < GATTC_CONNECT_RETRY_COUNT) {
L2CAP_TRACE_DEBUG("master retry connect, retry count %d reason 0x%x\n", p_lcb->retry_create_con, reason);
p_lcb->retry_create_con ++;
// create connection retry
@@ -513,7 +519,7 @@ BOOLEAN l2c_link_hci_disc_comp (UINT16 handle, UINT8 reason)
if ((reason == HCI_ERR_CONN_FAILED_ESTABLISHMENT) || (link_state_temp == LST_DISCONNECTED)) {
#if (BLE_50_FEATURE_SUPPORT == TRUE)
#if (BLE_50_EXTEND_ADV_EN == TRUE)
if(btm_ble_inter_get() && p_lcb->link_role == HCI_ROLE_SLAVE) {
if(btm_ble_inter_get() && link_role_temp == HCI_ROLE_SLAVE) {
L2CAP_TRACE_DEBUG("slave restart extend adv, retry count %d reason 0x%x\n", p_lcb->retry_create_con, reason);
tBTM_STATUS start_adv_status = BTM_BleStartExtAdvRestart(handle);
if (start_adv_status != BTM_SUCCESS) {
@@ -626,6 +632,7 @@ void l2c_link_timeout (tL2C_LCB *p_lcb)
#endif
/* Release the LCB */
l2cu_release_lcb (p_lcb);
return;
}
/* If link is connected, check for inactivity timeout */
@@ -1314,6 +1321,13 @@ static BOOLEAN l2c_link_send_to_lower (tL2C_LCB *p_lcb, BT_HDR *p_buf)
acl_data_size = controller->get_acl_data_size_classic();
xmit_window = l2cb.controller_xmit_window;
}
if (p_buf->len <= HCI_DATA_PREAMBLE_SIZE) {
L2CAP_TRACE_ERROR ("l2c_link_send_to_lower - bad buffer len: %u", p_buf->len);
osi_free(p_buf);
return FALSE;
}
num_segs = (p_buf->len - HCI_DATA_PREAMBLE_SIZE + acl_data_size - 1) / acl_data_size;
@@ -1395,15 +1409,26 @@ static BOOLEAN l2c_link_send_to_lower (tL2C_LCB *p_lcb, BT_HDR *p_buf)
** Returns void
**
*******************************************************************************/
void l2c_link_process_num_completed_pkts (UINT8 *p)
void l2c_link_process_num_completed_pkts (UINT8 *p, UINT8 evt_len)
{
UINT8 num_handles, xx;
UINT16 handle;
UINT16 num_sent;
tL2C_LCB *p_lcb;
if (evt_len < 1) {
L2CAP_TRACE_ERROR ("l2c_link_process_num_completed_pkts: evt too short (len=%u)", evt_len);
return;
}
STREAM_TO_UINT8 (num_handles, p);
if (num_handles > (evt_len - 1) / 4) {
L2CAP_TRACE_ERROR ("l2c_link_process_num_completed_pkts: num_handles %u exceeds evt_len %u, truncating",
num_handles, evt_len);
num_handles = (evt_len - 1) / 4;
}
for (xx = 0; xx < num_handles; xx++) {
STREAM_TO_UINT16 (handle, p);
STREAM_TO_UINT16 (num_sent, p);
@@ -150,7 +150,6 @@ void l2c_rcv_acl_data (BT_HDR *p_msg)
#if (!CONFIG_BT_STACK_NO_LOG)
UINT16 psm;
#endif
UINT16 credit;
/* Extract the handle */
STREAM_TO_UINT16 (handle, p);
@@ -292,6 +291,9 @@ void l2c_rcv_acl_data (BT_HDR *p_msg)
if (p_ccb->peer_cfg.fcr.mode != L2CAP_FCR_BASIC_MODE) {
#if (CLASSIC_BT_INCLUDED == TRUE)
l2c_fcr_proc_pdu (p_ccb, p_msg);
#else
/* Classic FCR not compiled (e.g. BLE-only); free p_msg to avoid leak */
osi_free (p_msg);
#endif ///CLASSIC_BT_INCLUDED == TRUE
} else {
(*l2cb.fixed_reg[rcv_cid - L2CAP_FIRST_FIXED_CHNL].pL2CA_FixedData_Cb)
@@ -310,31 +312,24 @@ void l2c_rcv_acl_data (BT_HDR *p_msg)
osi_free (p_msg);
} else {
if (p_lcb->transport == BT_TRANSPORT_LE) {
// Got a pkt, valid send out credits to the peer device
credit = L2CAP_LE_DEFAULT_CREDIT;
L2CAP_TRACE_DEBUG("%s Credits received %d",__func__, credit);
if((p_ccb->peer_conn_cfg.credits + credit) > L2CAP_LE_MAX_CREDIT) {
/* we have received credits more than max coc credits,
* so disconnecting the Le Coc Channel
*/
#if (BLE_INCLUDED == TRUE)
l2cble_send_peer_disc_req (p_ccb);
#endif ///BLE_INCLUDED == TRUE
} else {
p_ccb->peer_conn_cfg.credits += credit;
l2c_link_check_send_pkts (p_ccb->p_lcb, NULL, NULL);
}
l2c_link_check_send_pkts (p_ccb->p_lcb, NULL, NULL);
}
/* Basic mode packets go straight to the state machine */
if (p_ccb->peer_cfg.fcr.mode == L2CAP_FCR_BASIC_MODE) {
#if (CLASSIC_BT_INCLUDED == TRUE)
#if (L2CAP_COC_INCLUDED == TRUE)
l2c_csm_execute (p_ccb, L2CEVT_L2CAP_DATA, p_msg);
#endif ///CLASSIC_BT_INCLUDED == TRUE
#else
/* COC not included: state machine not compiled; free p_msg to avoid leak */
osi_free (p_msg);
#endif
} else {
/* eRTM or streaming mode, so we need to validate states first */
if ((p_ccb->chnl_state == CST_OPEN) || (p_ccb->chnl_state == CST_CONFIG)) {
#if (CLASSIC_BT_INCLUDED == TRUE)
l2c_fcr_proc_pdu (p_ccb, p_msg);
#else
/* Classic FCR not compiled (e.g. BLE-only); free p_msg to avoid leak */
osi_free (p_msg);
#endif ///CLASSIC_BT_INCLUDED == TRUE
} else {
osi_free (p_msg);
@@ -423,14 +418,26 @@ static void process_l2cap_cmd (tL2C_LCB *p_lcb, UINT8 *p, UINT16 pkt_len)
switch (cmd_code) {
case L2CAP_CMD_REJECT:
if (cmd_len < L2CAP_CMD_REJECT_LEN) {
L2CAP_TRACE_WARNING ("L2CAP - cmd reject too short, cmd_len: %d", cmd_len);
break;
}
STREAM_TO_UINT16 (rej_reason, p);
if (rej_reason == L2CAP_CMD_REJ_MTU_EXCEEDED) {
if (cmd_len < L2CAP_CMD_REJECT_LEN + 2) {
L2CAP_TRACE_WARNING ("L2CAP - MTU rej too short, cmd_len: %d", cmd_len);
break;
}
STREAM_TO_UINT16 (rej_mtu, p);
/* What to do with the MTU reject ? We have negotiated an MTU. For now */
/* we will ignore it and let a higher protocol timeout take care of it */
L2CAP_TRACE_WARNING ("L2CAP - MTU rej Handle: %d MTU: %d", p_lcb->handle, rej_mtu);
}
if (rej_reason == L2CAP_CMD_REJ_INVALID_CID) {
if (cmd_len < L2CAP_CMD_REJECT_LEN + 4) {
L2CAP_TRACE_WARNING ("L2CAP - CID rej too short, cmd_len: %d", cmd_len);
break;
}
STREAM_TO_UINT16 (rcid, p);
STREAM_TO_UINT16 (lcid, p);
@@ -1899,15 +1899,31 @@ void l2cu_disconnect_chnl (tL2C_CCB *p_ccb)
UINT16 local_cid = p_ccb->local_cid;
if (local_cid >= L2CAP_BASE_APPL_CID) {
tL2CA_DISCONNECT_IND_CB *p_disc_cb = p_ccb->p_rcb->api.pL2CA_DisconnectInd_Cb;
tL2CA_DISCONNECT_IND_CB *p_disc_cb = NULL;
if (p_ccb->p_rcb != NULL) {
p_disc_cb = p_ccb->p_rcb->api.pL2CA_DisconnectInd_Cb;
}
L2CAP_TRACE_WARNING ("L2CAP - disconnect_chnl CID: 0x%04x", local_cid);
l2cu_send_peer_disc_req (p_ccb);
/*
* l2cu_send_peer_disc_req drains xmit_hold_q in basic FCR mode.
* Free the queue and NULL it here to prevent l2cu_release_ccb from
* calling fixed_queue_free again on already-dequeued buffers.
*/
if (p_ccb->xmit_hold_q != NULL) {
fixed_queue_free(p_ccb->xmit_hold_q, osi_free_func);
p_ccb->xmit_hold_q = NULL;
}
l2cu_release_ccb (p_ccb);
(*p_disc_cb)(local_cid, FALSE);
if (p_disc_cb) {
(*p_disc_cb)(local_cid, FALSE);
}
} else {
/* failure on the AMP channel, probably need to disconnect ACL */
L2CAP_TRACE_ERROR ("L2CAP - disconnect_chnl CID: 0x%04x Ignored", local_cid);