fix(ble/bluedroid): fix GATT client cache and service discovery

(cherry picked from commit b3ff15ed31)

Co-authored-by: zhiweijian <zhiweijian@espressif.com>
This commit is contained in:
Zhi Wei Jian
2026-06-10 19:53:47 +08:00
parent beca97bcae
commit 293b2d5398
11 changed files with 1130 additions and 369 deletions
@@ -390,6 +390,18 @@ tGATT_STATUS attp_send_msg_to_l2cap(tGATT_TCB *p_tcb, BT_HDR *p_toL2CAP)
if (p_tcb->att_lcid == L2CAP_ATT_CID) {
/* L2CA_SendFixedChnlData() silently drops (osi_free) the buffer when the
* ATT fixed channel is already in cong_sent state, yet still returns
* L2CAP_DW_CONGESTED. Without distinguishing this from the post-enqueue
* congestion case, the upper layer would treat a dropped PDU as "sent"
* and wait for a response that never arrives. Detect the drop path
* up-front, release the buffer here and surface it as GATT_BUSY so that
* callers go through their failure path instead. */
if (L2CA_CheckIsCongest(L2CAP_ATT_CID, p_tcb->peer_bda)) {
GATT_TRACE_WARNING("ATT fixed channel already congested, drop PDU");
osi_free(p_toL2CAP);
return GATT_BUSY;
}
l2cap_ret = L2CA_SendFixedChnlData (L2CAP_ATT_CID, p_tcb->peer_bda, p_toL2CAP);
} else {
#if (CLASSIC_BT_INCLUDED == TRUE)
@@ -403,6 +415,8 @@ tGATT_STATUS attp_send_msg_to_l2cap(tGATT_TCB *p_tcb, BT_HDR *p_toL2CAP)
GATT_TRACE_DEBUG("ATT failed to pass msg to L2CAP");
return GATT_INTERNAL_ERROR;
} else if (l2cap_ret == L2CAP_DW_CONGESTED) {
/* Buffer was enqueued by L2CAP before congestion was reported;
* L2CAP retains ownership of it. */
GATT_TRACE_DEBUG("ATT congested, message accepted");
return GATT_CONGESTED;
}
@@ -1750,9 +1750,22 @@ tGATT_STATUS GATTS_HandleMultiValueNotification (UINT16 conn_id, tGATT_HLV *tupl
return GATT_ILLEGAL_PARAMETER;
}
{
UINT32 new_len = (UINT32)notif.len + 4U + (UINT32)p_hlv->length;
if (new_len > (UINT32)GATT_MAX_ATTR_LEN) {
GATT_TRACE_ERROR("%s: len %u>MAX_ATTR_LEN", __func__, (unsigned)new_len);
return GATT_ILLEGAL_PARAMETER;
}
}
UINT16_TO_STREAM(p, p_hlv->handle); //handle
UINT16_TO_STREAM(p, p_hlv->length); //length
memcpy (p, p_hlv->value, p_hlv->length); //value
if (p_hlv->length > 0) {
if (p_hlv->value == NULL) {
return GATT_ILLEGAL_PARAMETER;
}
memcpy (p, p_hlv->value, p_hlv->length); //value
}
GATT_TRACE_DEBUG("%s handle %x, length %u", __func__, p_hlv->handle, p_hlv->length);
p += p_hlv->length;
notif.len += 4 + p_hlv->length;
@@ -1124,10 +1124,14 @@ BOOLEAN gatt_cl_send_next_cmd_inq(tGATT_TCB *p_tcb)
if (att_ret == GATT_SUCCESS || att_ret == GATT_CONGESTED) {
sent = TRUE;
p_cmd->to_send = FALSE;
if(p_cmd->p_cmd) {
osi_free(p_cmd->p_cmd);
p_cmd->p_cmd = NULL;
}
/* On GATT_SUCCESS / GATT_CONGESTED, L2CAP has taken ownership of
* p_cmd->p_cmd (it was either accepted normally, or enqueued just
* before the channel turned congested). The "already congested"
* drop path inside L2CA_SendFixedChnlData() is filtered out earlier
* by attp_send_msg_to_l2cap() and returned as GATT_BUSY, which
* falls into the error branch below. So we must not free the
* buffer here. */
p_cmd->p_cmd = NULL;
/* dequeue the request if is write command or sign write */
if (p_cmd->op_code != GATT_CMD_WRITE && p_cmd->op_code != GATT_SIGN_CMD_WRITE) {
@@ -1145,13 +1149,22 @@ BOOLEAN gatt_cl_send_next_cmd_inq(tGATT_TCB *p_tcb)
gatt_end_operation(p_clcb, att_ret, NULL);
}
} else {
GATT_TRACE_ERROR("gatt_cl_send_next_cmd_inq: L2CAP sent error");
GATT_TRACE_ERROR("gatt_cl_send_next_cmd_inq: L2CAP sent error, status=%d", att_ret);
/* attp_send_msg_to_l2cap() already freed p_cmd->p_cmd on failure */
p_cmd->p_cmd = NULL;
memset(p_cmd, 0, sizeof(tGATT_CMD_Q));
p_tcb->pending_cl_req ++;
p_tcb->pending_cl_req %= GATT_CL_MAX_LCB;
p_cmd->to_send = FALSE;
/* Dequeue the failing command so pending_cl_req is advanced and the
* associated p_clcb can be retrieved. */
p_clcb = gatt_cmd_dequeue(p_tcb, &rsp_code);
p_cmd = &p_tcb->cl_cmd_q[p_tcb->pending_cl_req];
/* Notify the upper layer about the failure. Without this the
* response timer is never armed (non-write ops) and the write
* completion callback is never fired, leaving the application
* stuck waiting for a callback that will never come. The p_clcb
* would also leak. */
if (p_clcb != NULL) {
gatt_end_operation(p_clcb, att_ret, NULL);
}
}
}