feat(ble/bluedroid): Support bluedroid LE COC and EATT features

(cherry picked from commit 83f0831c53)

Co-authored-by: zhiweijian <zhiweijian@espressif.com>
This commit is contained in:
Zhi Wei Jian
2026-07-14 11:56:08 +08:00
parent 41582e1777
commit 7509dd6460
39 changed files with 7689 additions and 64 deletions
+6
View File
@@ -26,6 +26,9 @@
#include "btc_gap_ble.h"
#include "btc_iso_ble.h"
#include "btc_ble_cte.h"
#if (BLE_L2CAP_COC_INCLUDED == TRUE)
#include "btc_ble_l2cap.h"
#endif
#include "btc/btc_dm.h"
#include "bta/bta_gatt_api.h"
#if CLASSIC_BT_INCLUDED
@@ -279,6 +282,9 @@ static const btc_func_t profile_tab[BTC_PID_NUM] = {
#if (BLE_FEAT_CTE_EN == TRUE)
[BTC_PID_BLE_CTE] = {btc_ble_cte_call_handler, btc_ble_cte_cb_handler },
#endif // #if (BLE_FEAT_CTE_EN == TRUE)
#if (BLE_L2CAP_COC_INCLUDED == TRUE)
[BTC_PID_BLE_L2CAP] = {btc_ble_l2cap_call_handler, btc_ble_l2cap_cb_handler },
#endif // #if (BLE_L2CAP_COC_INCLUDED == TRUE)
};
/*****************************************************************************
@@ -120,6 +120,9 @@ typedef enum {
#if (BLE_FEAT_CTE_EN == TRUE)
BTC_PID_BLE_CTE,
#endif // #if (BLE_FEAT_CTE_EN == TRUE)
#if (BLE_L2CAP_COC_INCLUDED == TRUE)
BTC_PID_BLE_L2CAP,
#endif // #if (BLE_L2CAP_COC_INCLUDED == TRUE)
BTC_PID_NUM,
} btc_pid_t; //btc profile id
@@ -314,6 +314,26 @@ if(CONFIG_BT_BLE_FEAT_ISO_EN)
)
endif()
if(CONFIG_BT_BLE_L2CAP_COC_ENABLED)
list(APPEND bluedroid_host_srcs
"${CMAKE_CURRENT_LIST_DIR}/stack/l2cap/l2c_ble_le_coc.c"
"${CMAKE_CURRENT_LIST_DIR}/btc/profile/std/ble_l2cap/btc_ble_l2cap.c"
"${CMAKE_CURRENT_LIST_DIR}/api/esp_ble_l2cap_api.c"
)
endif()
if(CONFIG_BT_BLE_L2CAP_ENHANCED_COC)
list(APPEND bluedroid_host_srcs
"${CMAKE_CURRENT_LIST_DIR}/stack/l2cap/l2c_ble_ecfc.c"
)
endif()
if(CONFIG_BT_BLE_EATT_ENABLE)
list(APPEND bluedroid_host_srcs
"${CMAKE_CURRENT_LIST_DIR}/stack/gatt/gatt_eatt.c"
)
endif()
if(CONFIG_BT_BLE_FEAT_CTE_EN)
list(APPEND bluedroid_host_srcs
"${CMAKE_CURRENT_LIST_DIR}/stack/btm/btm_ble_cte.c"
+81
View File
@@ -1742,6 +1742,87 @@ config BT_BLE_HIGH_DUTY_ADV_INTERVAL
help
This enable BLE high duty advertising interval feature
menu "Bluedroid L2CAP CoC"
# LE CoC client code is compiled only with GATTC and server code only with
# GATTS (see BLE_L2CAP_COC_CLIENT/SERVER_INCLUDED in bt_target.h). Without
# either, enabling CoC would silently compile out entirely, so require at
# least one GATT role to be enabled.
depends on BT_BLE_ENABLED && (BT_GATTC_ENABLE || BT_GATTS_ENABLE)
config BT_BLE_L2CAP_COC_ENABLED
bool "Enable BLE L2CAP Connection Oriented Channels"
default n
help
Enable LE Credit Based Flow Control mode L2CAP CoC in Bluedroid stack.
Independent of Classic Bluetooth L2CAP; does not affect esp_bt_l2cap_* APIs.
config BT_BLE_L2CAP_COC_MAX_CHAN
int "Maximum LE CoC channels"
depends on BT_BLE_L2CAP_COC_ENABLED
range 1 15
default 5
config BT_BLE_L2CAP_COC_MPS
int "Default MPS (L2CAP fragment size)"
depends on BT_BLE_L2CAP_COC_ENABLED
range 23 65533 if !BT_BLE_L2CAP_ENHANCED_COC
range 64 65533 if BT_BLE_L2CAP_ENHANCED_COC
default 247
help
Default Maximum PDU Payload Size for LE CoC channels. Legacy LE Credit
Based Flow Control allows 23–65533 octets (section 4.22). Enhanced
Credit Based Flow Control (ECFC/EATT) requires 64–65533 (section 4.25).
When ECFC is enabled the minimum is raised to 64 automatically.
config BT_BLE_L2CAP_COC_INIT_CREDITS
int "Initial RX credit window (K-frames per channel)"
depends on BT_BLE_L2CAP_COC_ENABLED
range 1 64
default 24
help
Number of LE CoC RX credits granted to the peer when a channel opens.
One credit allows the peer to send one K-frame. A larger window can
raise sustained throughput but increases how many in-flight frames
the peer may send before waiting for more credits (higher RX memory
pressure). A smaller window reduces that pressure but can lower
throughput. Manual credit mode can stall if a single SDU needs more
K-frames than this window; prefer automatic credit mode or a larger
MPS when using large MTUs.
config BT_BLE_L2CAP_ENHANCED_COC
bool "Enable Enhanced Credit Based Flow Control (ECFC)"
depends on BT_BLE_L2CAP_COC_ENABLED
default n
help
Enable LE Enhanced CoC (L2CAP signaling 0x17/0x18) for multi-channel
establishment. Required for EATT multi-bearer support.
config BT_BLE_EATT_ENABLE
bool "Enable Enhanced ATT (EATT)"
depends on BT_BLE_L2CAP_COC_ENABLED && BT_BLE_L2CAP_ENHANCED_COC
default n
help
Enable EATT bearers over LE Enhanced CoC (PSM 0x0027).
GATT operations may use multiple parallel bearers after link encryption.
config BT_BLE_EATT_CHAN_NUM
int "Number of EATT bearers per connection"
depends on BT_BLE_EATT_ENABLE
range 1 BT_BLE_L2CAP_COC_MAX_CHAN
default 3
help
Number of parallel EATT bearers established per connection. Must not
exceed the maximum LE CoC channels, since EATT bearers are LE CoC
channels; the range is capped by BT_BLE_L2CAP_COC_MAX_CHAN.
config BT_BLE_EATT_MTU
int "EATT bearer MTU"
depends on BT_BLE_EATT_ENABLE
range 64 517
default 247
endmenu
config BT_ABORT_WHEN_ALLOCATION_FAILS
bool "Abort when memory allocation fails in BT/BLE stack"
depends on BT_BLUEDROID_ENABLED
@@ -0,0 +1,222 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <string.h>
#include "esp_bt_main.h"
#include "esp_bt_defs.h"
#include "esp_ble_l2cap_api.h"
#include "btc/btc_manage.h"
#include "btc/btc_task.h"
#if (BLE_L2CAP_COC_INCLUDED == TRUE)
#include "common/bt_target.h"
#include "btc_ble_l2cap.h"
/* LE PSM valid range per Core Spec: 0x0001..0x00FF */
#define ESP_BLE_L2CAP_IS_VALID_LE_PSM(psm) ((psm) > 0x0000 && (psm) < 0x0100)
/* Minimum MTU per Core Spec Vol 3 Part A: 23 for LE credit based (4.22),
* 64 for enhanced credit based / ECFC (4.25). */
#define ESP_BLE_L2CAP_LE_MIN_MTU 23
#define ESP_BLE_L2CAP_ECFC_MIN_MTU 64
/* Minimum MPS for enhanced credit based / ECFC channels (Core Spec Vol 3
* Part A 4.25). */
#define ESP_BLE_L2CAP_ECFC_MIN_MPS 64
/* Core Spec Vol 3 Part A 4.25/4.27: a single enhanced credit based connection
* or reconfiguration request may target at most five channels, regardless of
* the (pool-sized) BT_BLE_L2CAP_COC_MAX_CHAN Kconfig value. */
#define ESP_BLE_L2CAP_ECFC_MAX_REQ_CHANS 5
static esp_err_t btc_ble_l2cap_transfer(btc_ble_l2cap_act_t act, btc_ble_l2cap_args_t *arg)
{
btc_msg_t msg = {0};
msg.sig = BTC_SIG_API_CALL;
msg.pid = BTC_PID_BLE_L2CAP;
msg.act = act;
return (btc_transfer_context(&msg, arg, sizeof(btc_ble_l2cap_args_t),
btc_ble_l2cap_arg_deep_copy,
btc_ble_l2cap_arg_deep_free) == BT_STATUS_SUCCESS)
? ESP_OK : ESP_FAIL;
}
esp_err_t esp_ble_l2cap_register_callback(esp_ble_l2cap_cb_t callback)
{
if (callback == NULL) {
return ESP_ERR_INVALID_ARG;
}
ESP_BLUEDROID_STATUS_CHECK(ESP_BLUEDROID_STATUS_ENABLED);
return (btc_profile_cb_set(BTC_PID_BLE_L2CAP, callback) == 0) ? ESP_OK : ESP_FAIL;
}
esp_err_t esp_ble_l2cap_init(void)
{
btc_ble_l2cap_args_t arg = {0};
ESP_BLUEDROID_STATUS_CHECK(ESP_BLUEDROID_STATUS_ENABLED);
return btc_ble_l2cap_transfer(BTC_BLE_L2CAP_ACT_INIT, &arg);
}
esp_err_t esp_ble_l2cap_deinit(void)
{
btc_ble_l2cap_args_t arg = {0};
ESP_BLUEDROID_STATUS_CHECK(ESP_BLUEDROID_STATUS_ENABLED);
return btc_ble_l2cap_transfer(BTC_BLE_L2CAP_ACT_DEINIT, &arg);
}
#if (BLE_L2CAP_COC_SERVER_INCLUDED == TRUE)
esp_err_t esp_ble_l2cap_create_server(uint16_t psm, uint16_t mtu)
{
btc_ble_l2cap_args_t arg = {0};
ESP_BLUEDROID_STATUS_CHECK(ESP_BLUEDROID_STATUS_ENABLED);
if (psm == 0 || mtu < ESP_BLE_L2CAP_LE_MIN_MTU || !ESP_BLE_L2CAP_IS_VALID_LE_PSM(psm)) {
return ESP_ERR_INVALID_ARG;
}
arg.create_server.psm = psm;
arg.create_server.mtu = mtu;
return btc_ble_l2cap_transfer(BTC_BLE_L2CAP_ACT_CREATE_SERVER, &arg);
}
esp_err_t esp_ble_l2cap_delete_server(uint16_t psm)
{
btc_ble_l2cap_args_t arg = {0};
ESP_BLUEDROID_STATUS_CHECK(ESP_BLUEDROID_STATUS_ENABLED);
if (psm == 0 || !ESP_BLE_L2CAP_IS_VALID_LE_PSM(psm)) {
return ESP_ERR_INVALID_ARG;
}
arg.delete_server.psm = psm;
return btc_ble_l2cap_transfer(BTC_BLE_L2CAP_ACT_DELETE_SERVER, &arg);
}
esp_err_t esp_ble_l2cap_accept(uint16_t conn_id, uint8_t l2cap_id,
uint16_t chan_handle, bool accept, uint16_t mtu)
{
btc_ble_l2cap_args_t arg = {0};
ESP_BLUEDROID_STATUS_CHECK(ESP_BLUEDROID_STATUS_ENABLED);
if (chan_handle == 0 || (accept && mtu < ESP_BLE_L2CAP_LE_MIN_MTU)) {
return ESP_ERR_INVALID_ARG;
}
arg.accept.conn_id = conn_id;
arg.accept.l2cap_id = l2cap_id;
arg.accept.chan_handle = chan_handle;
arg.accept.accept = accept;
arg.accept.mtu = mtu;
return btc_ble_l2cap_transfer(BTC_BLE_L2CAP_ACT_ACCEPT, &arg);
}
#endif /* BLE_L2CAP_COC_SERVER_INCLUDED */
#if (BLE_L2CAP_COC_CLIENT_INCLUDED == TRUE)
esp_err_t esp_ble_l2cap_connect(uint16_t conn_id, uint16_t psm, uint16_t mtu)
{
btc_ble_l2cap_args_t arg = {0};
ESP_BLUEDROID_STATUS_CHECK(ESP_BLUEDROID_STATUS_ENABLED);
if (psm == 0 || mtu < ESP_BLE_L2CAP_LE_MIN_MTU || !ESP_BLE_L2CAP_IS_VALID_LE_PSM(psm)) {
return ESP_ERR_INVALID_ARG;
}
arg.connect.conn_id = conn_id;
arg.connect.psm = psm;
arg.connect.mtu = mtu;
return btc_ble_l2cap_transfer(BTC_BLE_L2CAP_ACT_CONNECT, &arg);
}
#endif /* BLE_L2CAP_COC_CLIENT_INCLUDED */
esp_err_t esp_ble_l2cap_disconnect(uint16_t chan_handle)
{
btc_ble_l2cap_args_t arg = {0};
ESP_BLUEDROID_STATUS_CHECK(ESP_BLUEDROID_STATUS_ENABLED);
if (chan_handle == 0) {
return ESP_ERR_INVALID_ARG;
}
arg.disconnect.chan_handle = chan_handle;
return btc_ble_l2cap_transfer(BTC_BLE_L2CAP_ACT_DISCONNECT, &arg);
}
esp_err_t esp_ble_l2cap_send(uint16_t chan_handle, uint8_t *data, uint16_t len)
{
btc_ble_l2cap_args_t arg = {0};
ESP_BLUEDROID_STATUS_CHECK(ESP_BLUEDROID_STATUS_ENABLED);
if (chan_handle == 0 || data == NULL || len == 0) {
return ESP_ERR_INVALID_ARG;
}
arg.send.chan_handle = chan_handle;
arg.send.len = len;
arg.send.data = data;
return btc_ble_l2cap_transfer(BTC_BLE_L2CAP_ACT_SEND, &arg);
}
esp_err_t esp_ble_l2cap_recv_ready(uint16_t chan_handle)
{
btc_ble_l2cap_args_t arg = {0};
ESP_BLUEDROID_STATUS_CHECK(ESP_BLUEDROID_STATUS_ENABLED);
if (chan_handle == 0) {
return ESP_ERR_INVALID_ARG;
}
arg.recv_ready.chan_handle = chan_handle;
return btc_ble_l2cap_transfer(BTC_BLE_L2CAP_ACT_RECV_READY, &arg);
}
esp_err_t esp_ble_l2cap_set_auto_credit(uint16_t chan_handle, bool enable)
{
btc_ble_l2cap_args_t arg = {0};
ESP_BLUEDROID_STATUS_CHECK(ESP_BLUEDROID_STATUS_ENABLED);
if (chan_handle == 0) {
return ESP_ERR_INVALID_ARG;
}
arg.set_auto_credit.chan_handle = chan_handle;
arg.set_auto_credit.enable = enable;
return btc_ble_l2cap_transfer(BTC_BLE_L2CAP_ACT_SET_AUTO_CREDIT, &arg);
}
#if (BLE_L2CAP_ENHANCED_COC_INCLUDED == TRUE)
#if (BLE_L2CAP_COC_CLIENT_INCLUDED == TRUE)
esp_err_t esp_ble_l2cap_connect_ecoc(uint16_t conn_id, uint16_t psm, uint16_t mtu, uint8_t num_chan)
{
btc_ble_l2cap_args_t arg = {0};
ESP_BLUEDROID_STATUS_CHECK(ESP_BLUEDROID_STATUS_ENABLED);
if (psm == 0 || mtu < ESP_BLE_L2CAP_ECFC_MIN_MTU || num_chan == 0 ||
num_chan > BLE_MAX_L2CAP_CLIENTS ||
num_chan > ESP_BLE_L2CAP_ECFC_MAX_REQ_CHANS || !ESP_BLE_L2CAP_IS_VALID_LE_PSM(psm)) {
return ESP_ERR_INVALID_ARG;
}
arg.connect_ecoc.conn_id = conn_id;
arg.connect_ecoc.psm = psm;
arg.connect_ecoc.mtu = mtu;
arg.connect_ecoc.num_chan = num_chan;
return btc_ble_l2cap_transfer(BTC_BLE_L2CAP_ACT_CONNECT_ECOC, &arg);
}
#endif /* BLE_L2CAP_COC_CLIENT_INCLUDED */
esp_err_t esp_ble_l2cap_reconfig(uint16_t *chan_handles, uint8_t num_chan, uint16_t mtu, uint16_t mps)
{
btc_ble_l2cap_args_t arg = {0};
ESP_BLUEDROID_STATUS_CHECK(ESP_BLUEDROID_STATUS_ENABLED);
if (chan_handles == NULL || num_chan == 0 || num_chan > BLE_MAX_L2CAP_CLIENTS ||
num_chan > ESP_BLE_L2CAP_ECFC_MAX_REQ_CHANS ||
mtu < ESP_BLE_L2CAP_ECFC_MIN_MTU || mps < ESP_BLE_L2CAP_ECFC_MIN_MPS) {
return ESP_ERR_INVALID_ARG;
}
arg.reconfig.num_chan = num_chan;
arg.reconfig.mtu = mtu;
arg.reconfig.mps = mps;
memcpy(arg.reconfig.chan_handles, chan_handles, num_chan * sizeof(uint16_t));
return btc_ble_l2cap_transfer(BTC_BLE_L2CAP_ACT_RECONFIG, &arg);
}
#endif /* BLE_L2CAP_ENHANCED_COC_INCLUDED */
#endif /* BLE_L2CAP_COC_INCLUDED == TRUE */
@@ -14,6 +14,10 @@
#include "btc_gap_ble.h"
#include "btc/btc_ble_storage.h"
#include "esp_random.h"
#include "common/bt_target.h"
#if (BLE_EATT_INCLUDED == TRUE)
#include "stack/gatt_api.h"
#endif
/* Hard upper bound to prevent excessive allocations in BTC/BTA layers. */
#define ESP_GAP_BLE_EXT_ADV_DATA_MAX_LEN 1650U
@@ -3226,3 +3230,36 @@ esp_err_t esp_ble_cs_procedure_enable(esp_ble_cs_procedure_enable_params *proced
}
#endif
#if (BLE_EATT_INCLUDED == TRUE)
/* Intentionally synchronous: updates the pre-connection EATT bearer count only.
* Must be called before the link is encrypted / bearers are established (see API
* doc). No btc_transfer_context dispatch — this is a setup-time config write, not
* an async stack procedure, and callers need immediate ESP_ERR_INVALID_ARG feedback. */
esp_err_t esp_ble_eatt_set_chan_num(uint8_t num_chan)
{
ESP_BLUEDROID_STATUS_CHECK(ESP_BLUEDROID_STATUS_ENABLED);
if (num_chan == 0 || num_chan > GATT_EATT_MAX_CHAN) {
return ESP_ERR_INVALID_ARG;
}
GATT_EattSetChanNum(num_chan);
return ESP_OK;
}
/* Intentionally synchronous: sets the preferred EATT bearer (ec->default_lcid) for
* subsequent GATT client TX routing on this connection. No btc_transfer_context
* dispatch — by design this is an immediate preference update with synchronous
* validation (invalid conn_id/cid returns ESP_ERR_INVALID_ARG at call time).
* Client-only: defined solely when the EATT client role is built in, so a build
* without it fails at link time rather than exposing a stub. */
#if (BLE_EATT_CLIENT_INCLUDED == TRUE)
esp_err_t esp_ble_eatt_set_default_bearer(uint16_t conn_id, uint16_t cid)
{
ESP_BLUEDROID_STATUS_CHECK(ESP_BLUEDROID_STATUS_ENABLED);
if (!GATT_EattSetDefaultBearer(conn_id, cid)) {
return ESP_ERR_INVALID_ARG;
}
return ESP_OK;
}
#endif /* BLE_EATT_CLIENT_INCLUDED */
#endif /* BLE_EATT_INCLUDED */
@@ -0,0 +1,382 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef __ESP_BLE_L2CAP_API_H__
#define __ESP_BLE_L2CAP_API_H__
#include <stdint.h>
#include <stdbool.h>
#include "esp_err.h"
#include "esp_bt_defs.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief LE L2CAP connection-oriented channel (CoC) callback events
*/
typedef enum {
ESP_BLE_L2CAP_COC_CONNECTED_EVT = 0, /*!< When an LE CoC channel is connected or the connection attempt fails, the event comes */
ESP_BLE_L2CAP_COC_DISCONNECTED_EVT, /*!< When an LE CoC channel is disconnected, the event comes */
ESP_BLE_L2CAP_COC_ACCEPT_EVT, /*!< When a remote device requests a new LE CoC connection to a local server, the event comes */
ESP_BLE_L2CAP_COC_DATA_RECEIVED_EVT, /*!< When a complete SDU is received on an LE CoC channel, the event comes */
ESP_BLE_L2CAP_COC_TX_UNSTALLED_EVT, /*!< When TX credits are restored and more data may be sent, the event comes */
ESP_BLE_L2CAP_COC_RECONFIG_COMPLETED_EVT, /*!< When a local channel reconfiguration request completes, the event comes */
ESP_BLE_L2CAP_COC_PEER_RECONFIGURED_EVT, /*!< When the peer completes a channel reconfiguration, the event comes */
ESP_BLE_L2CAP_COC_EVT_MAX,
} esp_ble_l2cap_evt_t;
/**
* @brief LE CoC channel information
*
* Delivered in `ESP_BLE_L2CAP_COC_CONNECTED_EVT` and reconfiguration events when the
* operation succeeds.
*/
typedef struct {
uint16_t scid; /*!< Local channel identifier (CID) */
uint16_t dcid; /*!< Remote channel identifier (CID) */
uint16_t psm; /*!< Protocol/Service Multiplexer */
uint16_t our_mtu; /*!< Local maximum SDU size (MTU) */
uint16_t peer_mtu; /*!< Peer maximum SDU size (MTU) */
uint16_t our_mps; /*!< Local maximum PDU payload size (MPS) */
uint16_t peer_mps; /*!< Peer maximum PDU payload size (MPS) */
} esp_ble_l2cap_chan_info_t;
/**
* @brief LE L2CAP CoC callback parameters union
*/
typedef union {
/**
* @brief ESP_BLE_L2CAP_COC_CONNECTED_EVT
*/
struct {
uint16_t conn_id; /*!< GATT connection id of the underlying ACL link. May be 0 if not yet bound */
uint16_t chan_handle; /*!< Local L2CAP channel identifier (CID) of the CoC */
uint16_t status; /*!< Connection result. 0 (`L2CAP_CONN_OK`) means success; other values are L2CAP connection result codes */
esp_ble_l2cap_chan_info_t chan_info; /*!< Channel information. Valid only when `status` is 0 (`L2CAP_CONN_OK`) */
} coc_connected; /*!< LE L2CAP callback param of ESP_BLE_L2CAP_COC_CONNECTED_EVT */
/**
* @brief ESP_BLE_L2CAP_COC_DISCONNECTED_EVT
*/
struct {
uint16_t conn_id; /*!< Reserved. Currently not populated by the stack (0) */
uint16_t chan_handle; /*!< Local L2CAP channel identifier (CID) of the disconnected CoC */
} coc_disconnected; /*!< LE L2CAP callback param of ESP_BLE_L2CAP_COC_DISCONNECTED_EVT */
/**
* @brief ESP_BLE_L2CAP_COC_ACCEPT_EVT
*/
struct {
uint16_t conn_id; /*!< GATT connection id of the underlying ACL link. May be 0 if not yet bound */
uint16_t chan_handle; /*!< Proposed local L2CAP channel identifier (CID) */
uint8_t l2cap_id; /*!< L2CAP signaling identifier of the connection request */
uint16_t psm; /*!< Protocol/Service Multiplexer requested by the peer */
} coc_accept; /*!< LE L2CAP callback param of ESP_BLE_L2CAP_COC_ACCEPT_EVT */
/**
* @brief ESP_BLE_L2CAP_COC_DATA_RECEIVED_EVT
*/
struct {
uint16_t chan_handle; /*!< Local L2CAP channel identifier (CID) that received the SDU */
uint16_t len; /*!< SDU length in bytes */
uint8_t *data; /*!< Pointer to the received SDU payload. Valid only during the callback */
} data_received; /*!< LE L2CAP callback param of ESP_BLE_L2CAP_COC_DATA_RECEIVED_EVT */
/**
* @brief ESP_BLE_L2CAP_COC_TX_UNSTALLED_EVT
*/
struct {
uint16_t chan_handle; /*!< Local L2CAP channel identifier (CID) whose TX path is no longer congested */
} tx_unstalled; /*!< LE L2CAP callback param of ESP_BLE_L2CAP_COC_TX_UNSTALLED_EVT */
/**
* @brief ESP_BLE_L2CAP_COC_RECONFIG_COMPLETED_EVT
*/
struct {
uint16_t chan_handle; /*!< Local L2CAP channel identifier (CID) that was reconfigured */
uint16_t status; /*!< Reconfiguration result. 0 (`L2CAP_LE_RECONFIG_OK`) means success */
esp_ble_l2cap_chan_info_t chan_info; /*!< Updated channel information. Valid only when `status` is 0 (`L2CAP_LE_RECONFIG_OK`) */
} reconfig_completed; /*!< LE L2CAP callback param of ESP_BLE_L2CAP_COC_RECONFIG_COMPLETED_EVT */
/**
* @brief ESP_BLE_L2CAP_COC_PEER_RECONFIGURED_EVT
*/
struct {
uint16_t chan_handle; /*!< Local L2CAP channel identifier (CID) reconfigured by the peer */
uint16_t status; /*!< Reconfiguration result. 0 (`L2CAP_LE_RECONFIG_OK`) means success */
esp_ble_l2cap_chan_info_t chan_info; /*!< Updated channel information. Valid only when `status` is 0 (`L2CAP_LE_RECONFIG_OK`) */
} peer_reconfigured; /*!< LE L2CAP callback param of ESP_BLE_L2CAP_COC_PEER_RECONFIGURED_EVT */
} esp_ble_l2cap_cb_param_t;
/**
* @brief LE L2CAP CoC callback function type
*
* @param[in] event: Event type
* @param[in] param: Pointer to callback parameter, currently is union type
*/
typedef void (*esp_ble_l2cap_cb_t)(esp_ble_l2cap_evt_t event, esp_ble_l2cap_cb_param_t *param);
/**
* @brief Register the LE L2CAP CoC callback function
*
* @param[in] callback: Pointer to the callback function
*
* @return
* - ESP_OK: success
* - ESP_ERR_INVALID_ARG: callback is NULL
* - ESP_FAIL: other error
*/
esp_err_t esp_ble_l2cap_register_callback(esp_ble_l2cap_cb_t callback);
/**
* @brief Initialize the LE L2CAP CoC module
*
* Requires `CONFIG_BT_BLE_L2CAP_COC_ENABLED`.
* This function should be called after `esp_bluedroid_enable()` completes successfully.
*
* @return
* - ESP_OK: success
* - ESP_FAIL: other error
*/
esp_err_t esp_ble_l2cap_init(void);
/**
* @brief Deinitialize the LE L2CAP CoC module
*
* Deregisters all local CoC servers created by this module.
* This function should be called after `esp_ble_l2cap_init()` completes successfully.
*
* @return
* - ESP_OK: success
* - ESP_FAIL: other error
*/
esp_err_t esp_ble_l2cap_deinit(void);
/**
* @brief Register a local LE CoC server on the given PSM
*
* When a remote device requests a connection to this PSM, the callback receives
* `ESP_BLE_L2CAP_COC_ACCEPT_EVT`.
*
* @param[in] psm: LE Protocol/Service Multiplexer. Valid range is 0x0001 to 0x00FF
* @param[in] mtu: Local maximum SDU size (MTU) for channels accepted on this PSM
*
* @return
* - ESP_OK: success
* - ESP_ERR_INVALID_ARG: invalid `psm` or `mtu`
* - ESP_FAIL: other error
*/
esp_err_t esp_ble_l2cap_create_server(uint16_t psm, uint16_t mtu);
/**
* @brief Deregister a local LE CoC server
*
* @param[in] psm: LE Protocol/Service Multiplexer previously registered with `esp_ble_l2cap_create_server()`
*
* @return
* - ESP_OK: success
* - ESP_ERR_INVALID_ARG: `psm` is 0
* - ESP_FAIL: other error
*/
esp_err_t esp_ble_l2cap_delete_server(uint16_t psm);
/**
* @brief Connect to a remote LE CoC server (client role)
*
* When the connection attempt completes, the callback receives
* `ESP_BLE_L2CAP_COC_CONNECTED_EVT`.
*
* @param[in] conn_id: GATT connection id of the underlying ACL link
* @param[in] psm: Remote LE Protocol/Service Multiplexer. Valid range is 0x0001 to 0x00FF
* @param[in] mtu: Local maximum SDU size (MTU) to propose for the channel
*
* @return
* - ESP_OK: success
* - ESP_ERR_INVALID_ARG: invalid `psm` or `mtu`
* - ESP_FAIL: other error
*/
esp_err_t esp_ble_l2cap_connect(uint16_t conn_id, uint16_t psm, uint16_t mtu);
/**
* @brief Accept or reject an inbound LE CoC connection request (server role)
*
* Call this function in response to `ESP_BLE_L2CAP_COC_ACCEPT_EVT`.
* When accepted, the callback receives `ESP_BLE_L2CAP_COC_CONNECTED_EVT`.
* When rejected, no `ESP_BLE_L2CAP_COC_CONNECTED_EVT` is reported to the local server.
*
* @param[in] conn_id: GATT connection id from `ESP_BLE_L2CAP_COC_ACCEPT_EVT`
* @param[in] l2cap_id: L2CAP signaling identifier from `ESP_BLE_L2CAP_COC_ACCEPT_EVT`
* @param[in] chan_handle: Proposed local channel identifier from `ESP_BLE_L2CAP_COC_ACCEPT_EVT`
* @param[in] accept: True to accept the connection; false to reject it
* @param[in] mtu: Local maximum SDU size (MTU) to use when accepting. Ignored when rejecting
*
* @return
* - ESP_OK: success
* - ESP_ERR_INVALID_ARG: `chan_handle` is 0
* - ESP_FAIL: other error
*/
esp_err_t esp_ble_l2cap_accept(uint16_t conn_id, uint8_t l2cap_id,
uint16_t chan_handle, bool accept, uint16_t mtu);
/**
* @brief Disconnect an LE CoC channel
*
* When the channel is closed, the callback receives `ESP_BLE_L2CAP_COC_DISCONNECTED_EVT`.
*
* @param[in] chan_handle: Local L2CAP channel identifier (CID) of the CoC to disconnect
*
* @return
* - ESP_OK: success
* - ESP_ERR_INVALID_ARG: `chan_handle` is 0
* - ESP_FAIL: other error
*/
esp_err_t esp_ble_l2cap_disconnect(uint16_t chan_handle);
/**
* @brief Send an SDU on an LE CoC channel
*
* Transmission is credit-based. The host accepts at most one SDU per
* channel in its TX queue; further calls return `ESP_OK` but the SDU
* may be dropped if the channel is busy. Retry on
* `ESP_BLE_L2CAP_COC_TX_UNSTALLED_EVT` or after the pipeline drains.
*
* This function returns `ESP_OK` when the send request is queued to the host stack.
* It does not indicate that the SDU has already been transmitted.
*
* @param[in] chan_handle: Local L2CAP channel identifier (CID)
* @param[in] data: Pointer to the SDU payload to send
* @param[in] len: SDU length in bytes
*
* @return
* - ESP_OK: success
* - ESP_ERR_INVALID_ARG: invalid argument
* - ESP_FAIL: other error
*/
esp_err_t esp_ble_l2cap_send(uint16_t chan_handle, uint8_t *data, uint16_t len);
/**
* @brief Return RX credits after processing a received SDU (manual credit mode)
*
* Call this function once after the application has finished handling the SDU delivered
* in `ESP_BLE_L2CAP_COC_DATA_RECEIVED_EVT`. The stack returns the exact number of RX
* credits that SDU consumed (a multi-frame SDU consumes more than one), so no credits
* are leaked regardless of how the SDU was fragmented.
*
* This call only has an effect when the channel is in manual credit mode
* (`esp_ble_l2cap_set_auto_credit(chan_handle, false)`). In the default automatic mode
* the stack returns credits itself and this call is a harmless no-op. See
* `esp_ble_l2cap_set_auto_credit()` for the trade-offs between the two modes.
*
* @param[in] chan_handle: Local L2CAP channel identifier (CID)
*
* @return
* - ESP_OK: success
* - ESP_ERR_INVALID_ARG: `chan_handle` is 0
* - ESP_FAIL: other error
*/
esp_err_t esp_ble_l2cap_recv_ready(uint16_t chan_handle);
/**
* @brief Connect multiple LE CoC channels in one Enhanced Credit Flow Control request (client role)
*
* Requires `CONFIG_BT_BLE_L2CAP_ENHANCED_COC`. The corresponding API symbols are
* available only when this option is enabled at build time.
* One `ESP_BLE_L2CAP_COC_CONNECTED_EVT` is reported per channel.
*
* @param[in] conn_id: GATT connection id of the underlying ACL link
* @param[in] psm: Remote LE Protocol/Service Multiplexer. Valid range is 0x0001 to 0x00FF
* @param[in] mtu: Local maximum SDU size (MTU) to propose for each channel
* @param[in] num_chan: Number of CoC channels to open in a single request
*
* @return
* - ESP_OK: success
* - ESP_ERR_INVALID_ARG: invalid argument
* - ESP_FAIL: other error
*/
esp_err_t esp_ble_l2cap_connect_ecoc(uint16_t conn_id, uint16_t psm, uint16_t mtu, uint8_t num_chan);
/**
* @brief Reconfigure MTU and/or MPS on one or more LE CoC channels
*
* Requires `CONFIG_BT_BLE_L2CAP_ENHANCED_COC`. The corresponding API symbols are
* available only when this option is enabled at build time.
* When the local request completes, the callback receives
* `ESP_BLE_L2CAP_COC_RECONFIG_COMPLETED_EVT` per channel.
*
* @param[in] chan_handles: Array of local L2CAP channel identifiers (CIDs) to reconfigure
* @param[in] num_chan: Number of entries in `chan_handles`
* @param[in] mtu: New local maximum SDU size (MTU)
* @param[in] mps: New local maximum PDU payload size (MPS)
*
* @return
* - ESP_OK: success
* - ESP_ERR_INVALID_ARG: invalid argument
* - ESP_FAIL: other error
*/
esp_err_t esp_ble_l2cap_reconfig(uint16_t *chan_handles, uint8_t num_chan, uint16_t mtu, uint16_t mps);
/**
* @brief Select the RX credit return policy for an LE CoC channel
*
* LE CoC flow control is credit based: one credit == one K-frame (an L2CAP PDU of
* up to MPS bytes). A single application SDU (up to MTU bytes) may be fragmented into
* several K-frames, so it consumes several RX credits. This function chooses how those
* consumed credits are returned to the peer.
*
* Automatic mode (enable = true, the default):
* - Behaviour: the stack returns credits itself as each K-frame is consumed (returns
* are batched for efficiency and flushed as the window drains). In this mode
* `esp_ble_l2cap_recv_ready()` is a no-op and does not need to be called.
* - Pros: highest sustained RX throughput (credits are replenished on the Bluetooth
* task with no application round trip); no per-SDU bookkeeping for the application;
* works for any MTU/MPS, including SDUs larger than the credit window (credits are
* returned mid-SDU so reassembly can always complete).
* - Cons: no application-level backpressure. The peer keeps sending as fast as the
* credit window allows, regardless of how quickly the application drains the data.
* Recommended for throughput-oriented use and as the general default.
*
* Manual mode (enable = false):
* - Behaviour: the stack withholds the consumed credits; the application returns them
* by calling `esp_ble_l2cap_recv_ready()` once after it has finished processing each
* SDU delivered in `ESP_BLE_L2CAP_COC_DATA_RECEIVED_EVT`. The stack tracks the exact
* number of K-frames each SDU consumed and returns that many credits per call, so a
* multi-frame SDU does not leak credits.
* - Pros: application-level backpressure. The peer's flow is gated by the application's
* processing pace (if `recv_ready()` is not called, the peer stalls once its credits
* run out), which is useful when the receiver has limited buffering.
* - Cons: lower sustained throughput than automatic mode, because each replenishment
* incurs an application-to-stack round trip.
*
* Possible problem in manual mode (large SDUs):
* - Because credits are returned only after a complete SDU is delivered, a single SDU
* whose K-frame count exceeds the whole RX credit window (roughly when
* ceil((MTU + 2) / MPS) > window) can stall: the peer exhausts its credits before the
* SDU is complete, so the application never receives the event and never calls
* `recv_ready()`. The stack contains a deadlock breaker that returns the withheld
* credits mid-SDU in this situation so the transfer still completes (at the cost of
* weaker backpressure for that oversized SDU), and it logs a warning when manual mode
* is enabled on a channel where this can happen. For large MTUs prefer automatic mode
* or negotiate a larger MPS so a single SDU fits within the credit window.
*
* @param[in] chan_handle: Local L2CAP channel identifier (CID)
* @param[in] enable: True to enable automatic credit return (default); false for manual
* return via `esp_ble_l2cap_recv_ready()`
*
* @return
* - ESP_OK: success
* - ESP_ERR_INVALID_ARG: `chan_handle` is 0
* - ESP_FAIL: other error
*/
esp_err_t esp_ble_l2cap_set_auto_credit(uint16_t chan_handle, bool enable);
#ifdef __cplusplus
}
#endif
#endif /* __ESP_BLE_L2CAP_API_H__ */
@@ -287,6 +287,7 @@ typedef enum {
ESP_GAP_BLE_UTP_RECEIVE_EVT, /*!< When UTP data is received, the event comes */
ESP_GAP_BLE_CS_SET_SECURITY_REQUIREMENTS_CMPL_EVT, /*!< When CS set security requirements complete, the event comes */
ESP_GAP_BLE_CS_SET_DEFAULT_SECURITY_REQUIREMENTS_CMPL_EVT, /*!< When CS set default security requirements complete, the event comes */
ESP_GAP_BLE_EATT_EVT, /*!< When an EATT bearer is connected or disconnected, the event comes. Requires `CONFIG_BT_BLE_EATT_ENABLE` */
ESP_GAP_BLE_EVT_MAX, /*!< when maximum advertising event complete, the event comes */
} esp_gap_ble_cb_event_t;
@@ -3228,6 +3229,18 @@ typedef union {
esp_ble_cs_step_info *step_info; /*!< steps information in the CS subevent */
} cs_subevt_result_continue; /*!< Event parameter of ESP_GAP_BLE_CS_SUBEVENT_RESULT_CONTINUE_EVT */
#endif // (BT_BLE_FEAT_CHANNEL_SOUNDING == TRUE)
/**
* @brief ESP_GAP_BLE_EATT_EVT
*
* Requires `CONFIG_BT_BLE_EATT_ENABLE`. EATT bearers are established automatically
* after the ACL link is encrypted.
*/
struct ble_eatt_evt {
uint16_t conn_id; /*!< GATT connection id of the underlying ACL link. 0xFFFF (GATT_INVALID_CONN_ID) if not yet available. Note: 0 is a valid conn_id (the first BLE connection) */
uint8_t status; /*!< EATT bearer status. 0: connected; 1: disconnected */
uint16_t cid; /*!< Local L2CAP channel identifier (CID) of the EATT bearer */
} eatt_evt; /*!< Event parameter of ESP_GAP_BLE_EATT_EVT */
} esp_ble_gap_cb_param_t;
/**
@@ -5167,6 +5180,53 @@ esp_err_t esp_ble_cs_set_procedure_params(esp_ble_cs_set_proc_params *procedure_
*/
esp_err_t esp_ble_cs_procedure_enable(esp_ble_cs_procedure_enable_params *procedure_enable_params);
/**
* @brief Set the number of EATT bearers to establish per connection
*
* Requires `CONFIG_BT_BLE_EATT_ENABLE`.
* EATT bearers are created automatically after the link is encrypted.
* Call this function before the bearers are established. The value must
* not exceed `CONFIG_BT_BLE_EATT_CHAN_NUM` (compile-time maximum).
*
* This API is intentionally synchronous (does not dispatch through the
* BTC task): it only stores the requested bearer count for future
* connections and returns validation errors immediately.
*
* @param[in] num_chan: Number of EATT bearers to establish per connection
*
* @return
* - ESP_OK: success
* - ESP_ERR_INVALID_ARG: `num_chan` is 0 or greater than
* `CONFIG_BT_BLE_EATT_CHAN_NUM`
*
* @note Defined only when `CONFIG_BT_BLE_EATT_ENABLE` is set; calling it
* in a build with EATT disabled fails at link time (no definition).
*/
esp_err_t esp_ble_eatt_set_chan_num(uint8_t num_chan);
/**
* @brief Set the preferred EATT bearer for GATT client operations on a connection
*
* Requires `CONFIG_BT_BLE_EATT_ENABLE`.
* By default the stack selects an available bearer automatically.
* Pass `cid` as 0 to restore automatic selection.
*
* This API is intentionally synchronous (does not dispatch through the
* BTC task): it updates the preferred bearer for GATT client TX routing
* and returns validation errors immediately.
*
* @param[in] conn_id: GATT connection id
* @param[in] cid: Local L2CAP channel identifier (CID) of the preferred EATT bearer
*
* @return
* - ESP_OK: success
* - ESP_ERR_INVALID_ARG: invalid `conn_id` or `cid`
*
* @note Defined only when `CONFIG_BT_BLE_EATT_ENABLE` is set; calling it
* in a build with EATT disabled fails at link time (no definition).
*/
esp_err_t esp_ble_eatt_set_default_bearer(uint16_t conn_id, uint16_t cid);
#ifdef __cplusplus
}
#endif
@@ -16,6 +16,9 @@
#include "bta_gattc_int.h"
#include "bta_gatts_int.h"
#include "bta_dm_int.h"
#if (BLE_EATT_INCLUDED == TRUE)
#include "gatt_eatt_int.h"
#endif
static future_t *main_future[BTC_MAIN_FUTURE_NUM];
static SemaphoreHandle_t s_init_done_sem = NULL;
@@ -48,6 +51,15 @@ static void btc_disable_bluetooth(void)
void btc_init_callback(bt_status_t status)
{
#if (BLE_EATT_INCLUDED == TRUE)
/* Only arm the EATT callback once BTE startup actually succeeded. On failure
* the partial-init cleanup path tears the stack down (and NULLs this cback
* in gatt_eatt_deinit), so registering it here would only briefly reference a
* non-running stack. Matches the deliberate NULL-on-teardown in deinit. */
if (status == BT_STATUS_SUCCESS) {
gatt_eatt_register_evt_cback(btc_ble_gap_eatt_evt_cback);
}
#endif
s_init_clean = (status == BT_STATUS_SUCCESS) ? false : true;
future_ready(*btc_main_get_future_p(BTC_MAIN_INIT_FUTURE),
(status == BT_STATUS_SUCCESS) ? FUTURE_SUCCESS : FUTURE_FAIL);
File diff suppressed because it is too large Load Diff
@@ -23,6 +23,9 @@
#include "btc/btc_util.h"
#include "osi/mutex.h"
#include "osi/thread.h"
#if (BLE_EATT_INCLUDED == TRUE)
#include "gatt_eatt_int.h"
#endif
#include "osi/pkt_queue.h"
#if (BT_CONTROLLER_INCLUDED == TRUE)
#include "esp_bt.h"
@@ -2286,6 +2289,31 @@ static void btc_ble_set_privacy_mode(uint8_t addr_type,
BTA_DmBleSetPrivacyMode(addr_type, addr, privacy_mode);
}
#if (BLE_EATT_INCLUDED == TRUE)
void btc_ble_gap_eatt_evt_cback(UINT16 conn_id, UINT8 status, UINT16 cid)
{
btc_msg_t msg = {0};
esp_ble_gap_cb_param_t param = {0};
bt_status_t ret;
param.eatt_evt.conn_id = conn_id;
param.eatt_evt.status = status;
param.eatt_evt.cid = cid;
msg.sig = BTC_SIG_API_CB;
msg.pid = BTC_PID_GAP_BLE;
msg.act = ESP_GAP_BLE_EATT_EVT;
/* eatt_evt holds only scalars, so no deep copy/free is needed (matches the
* convention used by the other scalar-only GAP cb events in this file). */
ret = btc_transfer_context(&msg, &param, sizeof(esp_ble_gap_cb_param_t),
NULL, NULL);
if (ret != BT_STATUS_SUCCESS) {
BTC_TRACE_ERROR("EATT evt transfer failed");
}
}
#endif /* BLE_EATT_INCLUDED == TRUE */
void btc_gap_ble_cb_handler(btc_msg_t *msg)
{
esp_ble_gap_cb_param_t *param = (esp_ble_gap_cb_param_t *)msg->arg;
@@ -3096,6 +3124,13 @@ void btc_gap_ble_cb_deep_free(btc_msg_t *msg)
}
break;
#endif // (BT_BLE_FEAT_CHANNEL_SOUNDING == TRUE)
#if (BLE_EATT_INCLUDED == TRUE)
case ESP_GAP_BLE_EATT_EVT:
/* Scalar-only event: nothing to free. Handled explicitly so the
* unconditional cb_deep_free call in btc_gap_ble_cb_handler does not
* emit a spurious "Unhandled deep free" debug log. */
break;
#endif // (BLE_EATT_INCLUDED == TRUE)
default:
BTC_TRACE_DEBUG("Unhandled deep free %d", msg->act);
break;
@@ -0,0 +1,89 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef __BTC_BLE_L2CAP_H__
#define __BTC_BLE_L2CAP_H__
#if (BLE_L2CAP_COC_INCLUDED == TRUE)
#include "btc/btc_manage.h"
#include "common/bt_target.h"
#include "esp_ble_l2cap_api.h"
typedef enum {
BTC_BLE_L2CAP_ACT_INIT = 0,
BTC_BLE_L2CAP_ACT_DEINIT,
BTC_BLE_L2CAP_ACT_CREATE_SERVER,
BTC_BLE_L2CAP_ACT_DELETE_SERVER,
BTC_BLE_L2CAP_ACT_CONNECT,
BTC_BLE_L2CAP_ACT_ACCEPT,
BTC_BLE_L2CAP_ACT_DISCONNECT,
BTC_BLE_L2CAP_ACT_SEND,
BTC_BLE_L2CAP_ACT_RECV_READY,
BTC_BLE_L2CAP_ACT_CONNECT_ECOC,
BTC_BLE_L2CAP_ACT_RECONFIG,
BTC_BLE_L2CAP_ACT_SET_AUTO_CREDIT,
} btc_ble_l2cap_act_t;
typedef union {
struct {
uint16_t psm;
uint16_t mtu;
} create_server;
struct {
uint16_t psm;
} delete_server;
struct {
uint16_t conn_id;
uint16_t psm;
uint16_t mtu;
} connect;
struct {
uint16_t conn_id;
uint8_t l2cap_id;
uint16_t chan_handle;
bool accept;
uint16_t mtu;
} accept;
struct {
uint16_t chan_handle;
} disconnect;
struct {
uint16_t chan_handle;
uint16_t len;
uint8_t *data;
} send;
struct {
uint16_t chan_handle;
} recv_ready;
struct {
uint16_t conn_id;
uint16_t psm;
uint16_t mtu;
uint8_t num_chan;
} connect_ecoc;
struct {
uint16_t num_chan;
uint16_t mtu;
uint16_t mps;
uint16_t chan_handles[BLE_MAX_L2CAP_CLIENTS];
} reconfig;
struct {
uint16_t chan_handle;
bool enable;
} set_auto_credit;
} btc_ble_l2cap_args_t;
void btc_ble_l2cap_call_handler(btc_msg_t *msg);
void btc_ble_l2cap_cb_handler(btc_msg_t *msg);
void btc_ble_l2cap_arg_deep_copy(btc_msg_t *msg, void *p_dest, void *p_src);
void btc_ble_l2cap_arg_deep_free(btc_msg_t *msg);
void btc_ble_l2cap_cb_deep_copy(btc_msg_t *msg, void *p_dest, void *p_src);
void btc_ble_l2cap_cb_deep_free(btc_msg_t *msg);
#endif /* BLE_L2CAP_COC_INCLUDED == TRUE */
#endif /* __BTC_BLE_L2CAP_H__ */
@@ -831,4 +831,8 @@ void btc_gap_ble_deinit(void);
void btc_adv_list_init(void);
void btc_adv_list_deinit(void);
#if (BLE_EATT_INCLUDED == TRUE)
void btc_ble_gap_eatt_evt_cback(UINT16 conn_id, UINT8 status, UINT16 cid);
#endif
#endif /* __BTC_GAP_BLE_H__ */
@@ -713,6 +713,54 @@
#define UC_BT_BLE_RPA_TIMEOUT 900
#endif
#ifdef CONFIG_BT_BLE_L2CAP_COC_ENABLED
#define UC_BT_BLE_L2CAP_COC_ENABLED CONFIG_BT_BLE_L2CAP_COC_ENABLED
#else
#define UC_BT_BLE_L2CAP_COC_ENABLED FALSE
#endif
#ifdef CONFIG_BT_BLE_L2CAP_COC_MAX_CHAN
#define UC_BT_BLE_L2CAP_COC_MAX_CHAN CONFIG_BT_BLE_L2CAP_COC_MAX_CHAN
#else
#define UC_BT_BLE_L2CAP_COC_MAX_CHAN 5
#endif
#ifdef CONFIG_BT_BLE_L2CAP_COC_MPS
#define UC_BT_BLE_L2CAP_COC_MPS CONFIG_BT_BLE_L2CAP_COC_MPS
#else
#define UC_BT_BLE_L2CAP_COC_MPS 247
#endif
#ifdef CONFIG_BT_BLE_L2CAP_COC_INIT_CREDITS
#define UC_BT_BLE_L2CAP_COC_INIT_CREDITS CONFIG_BT_BLE_L2CAP_COC_INIT_CREDITS
#else
#define UC_BT_BLE_L2CAP_COC_INIT_CREDITS 24
#endif
#ifdef CONFIG_BT_BLE_L2CAP_ENHANCED_COC
#define UC_BT_BLE_L2CAP_ENHANCED_COC CONFIG_BT_BLE_L2CAP_ENHANCED_COC
#else
#define UC_BT_BLE_L2CAP_ENHANCED_COC FALSE
#endif
#ifdef CONFIG_BT_BLE_EATT_ENABLE
#define UC_BT_BLE_EATT_ENABLE CONFIG_BT_BLE_EATT_ENABLE
#else
#define UC_BT_BLE_EATT_ENABLE FALSE
#endif
#ifdef CONFIG_BT_BLE_EATT_CHAN_NUM
#define UC_BT_BLE_EATT_CHAN_NUM CONFIG_BT_BLE_EATT_CHAN_NUM
#else
#define UC_BT_BLE_EATT_CHAN_NUM 3
#endif
#ifdef CONFIG_BT_BLE_EATT_MTU
#define UC_BT_BLE_EATT_MTU CONFIG_BT_BLE_EATT_MTU
#else
#define UC_BT_BLE_EATT_MTU 247
#endif
//SCO VOICE OVER HCI
#ifdef CONFIG_BT_HFP_AUDIO_DATA_PATH_HCI
#define UC_BT_HFP_AUDIO_DATA_PATH_HCI CONFIG_BT_HFP_AUDIO_DATA_PATH_HCI
@@ -1514,7 +1514,85 @@
/* Support status of L2CAP connection-oriented dynamic channels over LE transport with dynamic CID */
#ifndef BLE_L2CAP_COC_INCLUDED
#define BLE_L2CAP_COC_INCLUDED FALSE // LE COC not use by default
#if (UC_BT_BLE_L2CAP_COC_ENABLED == TRUE)
#define BLE_L2CAP_COC_INCLUDED TRUE
#else
#define BLE_L2CAP_COC_INCLUDED FALSE
#endif
#endif
#if (BLE_L2CAP_COC_INCLUDED == TRUE)
#undef BLE_MAX_L2CAP_CLIENTS
#define BLE_MAX_L2CAP_CLIENTS UC_BT_BLE_L2CAP_COC_MAX_CHAN
#endif
/* Initial LE CoC/ECFC RX credit window (K-frames) from
* CONFIG_BT_BLE_L2CAP_COC_INIT_CREDITS. Defined even when CoC is disabled so
* that internal headers that reference L2CAP_LE_INIT_CREDITS remain valid. */
#ifndef L2CAP_LE_INIT_CREDITS
#define L2CAP_LE_INIT_CREDITS UC_BT_BLE_L2CAP_COC_INIT_CREDITS
#endif
/* Default LE CoC/ECFC MPS from CONFIG_BT_BLE_L2CAP_COC_MPS. */
#ifndef L2CAP_LE_COC_MPS
#define L2CAP_LE_COC_MPS UC_BT_BLE_L2CAP_COC_MPS
#endif
#ifndef BLE_L2CAP_COC_CLIENT_INCLUDED
#if (BLE_L2CAP_COC_INCLUDED == TRUE) && (GATTC_INCLUDED == TRUE)
#define BLE_L2CAP_COC_CLIENT_INCLUDED TRUE
#else
#define BLE_L2CAP_COC_CLIENT_INCLUDED FALSE
#endif
#endif
#ifndef BLE_L2CAP_COC_SERVER_INCLUDED
#if (BLE_L2CAP_COC_INCLUDED == TRUE) && (GATTS_INCLUDED == TRUE)
#define BLE_L2CAP_COC_SERVER_INCLUDED TRUE
#else
#define BLE_L2CAP_COC_SERVER_INCLUDED FALSE
#endif
#endif
#ifndef BLE_L2CAP_ENHANCED_COC_INCLUDED
#if (UC_BT_BLE_L2CAP_ENHANCED_COC == TRUE) && (BLE_L2CAP_COC_INCLUDED == TRUE)
#define BLE_L2CAP_ENHANCED_COC_INCLUDED TRUE
#else
#define BLE_L2CAP_ENHANCED_COC_INCLUDED FALSE
#endif
#endif
#ifndef BLE_EATT_INCLUDED
#if (UC_BT_BLE_EATT_ENABLE == TRUE) && (BLE_L2CAP_ENHANCED_COC_INCLUDED == TRUE)
#define BLE_EATT_INCLUDED TRUE
#else
#define BLE_EATT_INCLUDED FALSE
#endif
#endif
#ifndef BLE_EATT_CLIENT_INCLUDED
#if (BLE_EATT_INCLUDED == TRUE) && (GATTC_INCLUDED == TRUE)
#define BLE_EATT_CLIENT_INCLUDED TRUE
#else
#define BLE_EATT_CLIENT_INCLUDED FALSE
#endif
#endif
#ifndef BLE_EATT_SERVER_INCLUDED
#if (BLE_EATT_INCLUDED == TRUE) && (GATTS_INCLUDED == TRUE)
#define BLE_EATT_SERVER_INCLUDED TRUE
#else
#define BLE_EATT_SERVER_INCLUDED FALSE
#endif
#endif
/* EATT bearer count and MTU from CONFIG_BT_BLE_EATT_CHAN_NUM / CONFIG_BT_BLE_EATT_MTU. */
#ifndef GATT_EATT_MAX_CHAN
#define GATT_EATT_MAX_CHAN UC_BT_BLE_EATT_CHAN_NUM
#endif
#ifndef GATT_EATT_MTU
#define GATT_EATT_MTU UC_BT_BLE_EATT_MTU
#endif
/* Support status of L2CAP connection-oriented dynamic channels over LE or BR/EDR transport with dynamic CID */
@@ -182,6 +182,10 @@ static void reassemble_and_dispatch(BT_HDR *packet)
}
STREAM_TO_UINT16(l2cap_length, stream);
/* A zero-length L2CAP information payload is valid per Core Spec v6.2
* Vol 3 Part A 3.1 (B-frame payload is 0..65535 octets); do not drop
* it. The downstream length math handles l2cap_length == 0 correctly
* (full_length == header-only == 8). */
/* Check for integer overflow in length calculation */
if (l2cap_length > (UINT16_MAX - L2CAP_HEADER_SIZE - HCI_ACL_PREAMBLE_SIZE)) {
HCI_TRACE_ERROR("L2CAP length too large: %u", l2cap_length);
@@ -971,4 +971,14 @@ static const char *mode_to_string(tBTM_PM_MODE mode)
}
#endif
#else /* CLASSIC_BT_INCLUDED != TRUE */
tBTM_STATUS BTM_SetPowerMode(UINT8 pm_id, BD_ADDR remote_bda, tBTM_PM_PWR_MD *p_mode)
{
UNUSED(pm_id);
UNUSED(remote_bda);
UNUSED(p_mode);
return BTM_SUCCESS;
}
#endif // #if (CLASSIC_BT_INCLUDED == TRUE)
@@ -39,6 +39,9 @@
#if (BLE_INCLUDED == TRUE)
#include "stack/gatt_api.h"
#include "gatt_int.h"
#if (BLE_EATT_INCLUDED == TRUE)
#include "gatt_eatt_int.h"
#endif
#if SMP_INCLUDED == TRUE
#include "smp_int.h"
#endif
@@ -120,6 +123,14 @@ void btu_init_core(void)
******************************************************************************/
void btu_free_core(void)
{
#if (BLE_INCLUDED == TRUE && defined(GATT_INCLUDED) && GATT_INCLUDED == true && BLE_EATT_INCLUDED == TRUE)
/* Tear down EATT before l2c_free(): gatt_eatt_deinit() deregisters the EATT
* LE CoC PSM (L2CA_DeregisterLECoc) and the EATT GATT interface
* (GATT_Deregister, which may disconnect open links). Both need live L2CAP
* state; running them after l2c_free() dereferences the freed l2c_cb_ptr. */
gatt_eatt_deinit();
#endif
// Free the mandatory core stack components
l2c_free();
@@ -29,6 +29,9 @@
#include "gatt_int.h"
#include "stack/l2c_api.h"
#if (BLE_EATT_INCLUDED == TRUE)
#include "gatt_eatt_int.h"
#endif
#define GATT_HDR_FIND_TYPE_VALUE_LEN 21
#define GATT_OP_CODE_SIZE 1
@@ -387,9 +390,26 @@ BT_HDR *attp_build_value_cmd(UINT16 payload_size, UINT8 op_code,
tGATT_STATUS attp_send_msg_to_l2cap(tGATT_TCB *p_tcb, BT_HDR *p_toL2CAP)
{
UINT16 l2cap_ret;
UINT16 lcid = p_tcb->att_lcid;
#if (BLE_EATT_INCLUDED == TRUE)
UINT8 op_code = *((UINT8 *)(p_toL2CAP + 1) + p_toL2CAP->offset);
if (p_tcb->att_lcid == L2CAP_ATT_CID) {
/* Exchange MTU is defined only on the legacy ATT bearer (Core Spec Vol 3
* Part G 5.3): keep it on att_lcid even if eatt_tx_bearer/eatt_rx_bearer is
* set. Without this, an MTU PDU flushed while an EATT response is still being
* processed (eatt_rx_bearer not yet cleared) would be sent on an EATT bearer
* and the peer would reject it with REQ_NOT_SUPPORTED. */
if (op_code != GATT_REQ_MTU && op_code != GATT_RSP_MTU) {
if (p_tcb->eatt_tx_bearer != 0) {
lcid = p_tcb->eatt_tx_bearer;
} else if (p_tcb->eatt_rx_bearer != 0) {
lcid = p_tcb->eatt_rx_bearer;
}
}
#endif
if (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
@@ -404,11 +424,25 @@ tGATT_STATUS attp_send_msg_to_l2cap(tGATT_TCB *p_tcb, BT_HDR *p_toL2CAP)
}
l2cap_ret = L2CA_SendFixedChnlData (L2CAP_ATT_CID, p_tcb->peer_bda, p_toL2CAP);
} else {
#if (BLE_L2CAP_COC_INCLUDED == TRUE) && (BLE_EATT_INCLUDED == TRUE)
if (gatt_eatt_is_bearer(lcid)) {
l2cap_ret = L2CA_LECocDataWrite(lcid, p_toL2CAP);
} else
#endif
#if (CLASSIC_BT_INCLUDED == TRUE)
l2cap_ret = (UINT16) L2CA_DataWrite (p_tcb->att_lcid, p_toL2CAP);
{
l2cap_ret = (UINT16) L2CA_DataWrite(lcid, p_toL2CAP);
}
#else
l2cap_ret = L2CAP_DW_FAILED;
#endif ///CLASSIC_BT_INCLUDED == TRUE
{
/* No L2CAP write consumed the buffer on this BLE-only path (e.g. an
* lcid that is neither the ATT fixed channel nor a known EATT
* bearer). Free it here so attp_send_msg_to_l2cap always consumes
* the buffer exactly once, matching every caller's assumption. */
osi_free(p_toL2CAP);
l2cap_ret = L2CAP_DW_FAILED;
}
#endif
}
if (l2cap_ret == L2CAP_DW_FAILED) {
@@ -470,7 +504,7 @@ BT_HDR *attp_build_sr_msg(tGATT_TCB *p_tcb, UINT8 op_code, tGATT_SR_MSG *p_msg)
case GATT_HANDLE_VALUE_NOTIF:
case GATT_HANDLE_VALUE_IND:
case GATT_HANDLE_MULTI_VALUE_NOTIF:
p_cmd = attp_build_value_cmd(p_tcb->payload_size,
p_cmd = attp_build_value_cmd(gatt_get_att_mtu(p_tcb),
op_code,
p_msg->attr_value.handle,
offset,
@@ -552,6 +586,37 @@ tGATT_STATUS attp_cl_send_cmd(tGATT_TCB *p_tcb, UINT16 clcb_idx, UINT8 cmd_code,
if (p_tcb != NULL) {
cmd_code &= ~GATT_AUTH_SIGN_MASK;
#if (BLE_EATT_CLIENT_INCLUDED == TRUE)
UINT16 eatt_bearer = L2CAP_ATT_CID;
if (cmd_code != GATT_HANDLE_VALUE_CONF && cmd_code != GATT_CMD_WRITE &&
cmd_code != GATT_REQ_MTU) {
eatt_bearer = gatt_eatt_get_available_bearer(p_tcb->peer_bda, cmd_code);
}
if (eatt_bearer != L2CAP_ATT_CID) {
p_tcb->eatt_tx_bearer = eatt_bearer;
att_ret = attp_send_msg_to_l2cap(p_tcb, p_cmd);
p_tcb->eatt_tx_bearer = 0;
if (att_ret == GATT_SUCCESS) {
gatt_eatt_mark_busy(p_tcb->peer_bda, eatt_bearer, cmd_code, clcb_idx);
gatt_start_rsp_timer(clcb_idx);
} else if (att_ret == GATT_CONGESTED) {
/* Buffer is queued at L2CAP; arm the response timer just like the
* legacy path so a lost response cannot hang the CLCB forever. */
gatt_eatt_mark_busy(p_tcb->peer_bda, eatt_bearer, cmd_code, clcb_idx);
gatt_start_rsp_timer(clcb_idx);
/* Normalize to success: the buffer was accepted (queued for
* credit) so the operation is in progress. Returning
* GATT_CONGESTED would make gatt_act_discovery/gatt_act_read
* treat it as failure and free the CLCB via gatt_end_operation
* without releasing this EATT bearer, leaving it stuck busy. */
att_ret = GATT_SUCCESS;
} else {
att_ret = GATT_INTERNAL_ERROR;
}
return att_ret;
}
#endif
/* no pending request or value confirmation */
if (p_tcb->pending_cl_req == p_tcb->next_slot_inq ||
cmd_code == GATT_HANDLE_VALUE_CONF) {
@@ -598,6 +663,16 @@ tGATT_STATUS attp_send_cl_msg (tGATT_TCB *p_tcb, UINT16 clcb_idx, UINT8 op_code,
UINT16 offset = 0, handle;
if (p_tcb != NULL) {
/* Use the legacy ATT payload_size as the fallback MTU. gatt_get_att_mtu()
* would return the EATT rx-bearer MTU when eatt_rx_bearer is transiently
* set (re-entrant response handling), which could size a PDU for EATT but
* send it on the legacy bearer and exceed its MTU. */
UINT16 att_mtu = p_tcb->payload_size;
#if (BLE_EATT_CLIENT_INCLUDED == TRUE)
att_mtu = gatt_eatt_mtu_for_client_op(p_tcb->peer_bda, op_code, att_mtu);
#endif
switch (op_code) {
case GATT_REQ_MTU:
if (p_msg->mtu <= GATT_MAX_MTU_SIZE) {
@@ -647,7 +722,7 @@ tGATT_STATUS attp_send_cl_msg (tGATT_TCB *p_tcb, UINT16 clcb_idx, UINT8 op_code,
case GATT_CMD_WRITE:
case GATT_SIGN_CMD_WRITE:
if (GATT_HANDLE_IS_VALID (p_msg->attr_value.handle)) {
p_cmd = attp_build_value_cmd (p_tcb->payload_size,
p_cmd = attp_build_value_cmd (att_mtu,
op_code, p_msg->attr_value.handle,
offset,
p_msg->attr_value.len,
@@ -662,12 +737,12 @@ tGATT_STATUS attp_send_cl_msg (tGATT_TCB *p_tcb, UINT16 clcb_idx, UINT8 op_code,
break;
case GATT_REQ_FIND_TYPE_VALUE:
p_cmd = attp_build_read_by_type_value_cmd(p_tcb->payload_size, &p_msg->find_type_value);
p_cmd = attp_build_read_by_type_value_cmd(att_mtu, &p_msg->find_type_value);
break;
case GATT_REQ_READ_MULTI:
case GATT_REQ_READ_MULTI_VAR:
p_cmd = attp_build_read_multi_cmd(op_code, p_tcb->payload_size,
p_cmd = attp_build_read_multi_cmd(op_code, att_mtu,
p_msg->read_multi.num_handles,
p_msg->read_multi.handles);
break;
@@ -31,6 +31,9 @@
#include "stack/gatt_api.h"
#include "gatt_int.h"
#include "stack/l2c_api.h"
#if (BLE_EATT_INCLUDED == TRUE)
#include "gatt_eatt_int.h"
#endif
#include "btm_int.h"
#include "stack/sdpdefs.h"
#include "stack/sdp_api.h"
@@ -636,6 +639,13 @@ tGATT_STATUS GATTS_HandleValueIndication (UINT16 conn_id, UINT16 attr_handle, U
return GATT_BUSY;
} else {
#if (BLE_EATT_SERVER_INCLUDED == TRUE)
/* Route the indication over an EATT bearer (if any) so it uses the EATT
* MTU instead of the legacy 23-byte ATT MTU. Set transiently and cleared
* after the send; all GATT TX runs on the single BTU task. */
UINT16 ind_bearer = gatt_eatt_get_server_tx_bearer(p_tcb->peer_bda);
p_tcb->eatt_tx_bearer = (ind_bearer != L2CAP_ATT_CID) ? ind_bearer : 0;
#endif
if ( (p_msg = attp_build_sr_msg (p_tcb, GATT_HANDLE_VALUE_IND, (tGATT_SR_MSG *)&indication)) != NULL) {
cmd_status = attp_send_sr_msg (p_tcb, p_msg);
@@ -644,6 +654,9 @@ tGATT_STATUS GATTS_HandleValueIndication (UINT16 conn_id, UINT16 attr_handle, U
gatt_start_conf_timer(p_tcb);
}
}
#if (BLE_EATT_SERVER_INCLUDED == TRUE)
p_tcb->eatt_tx_bearer = 0;
#endif
}
return cmd_status;
}
@@ -691,12 +704,22 @@ tGATT_STATUS GATTS_HandleValueNotification (UINT16 conn_id, UINT16 attr_handle,
memcpy (notif.value, p_val, val_len);
notif.auth_req = GATT_AUTH_REQ_NONE;
#if (BLE_EATT_SERVER_INCLUDED == TRUE)
/* Route the notification over an EATT bearer (if any) so it uses the EATT
* MTU instead of the legacy 23-byte ATT MTU. Set transiently and cleared
* after the send; all GATT TX runs on the single BTU task. */
UINT16 notif_bearer = gatt_eatt_get_server_tx_bearer(p_tcb->peer_bda);
p_tcb->eatt_tx_bearer = (notif_bearer != L2CAP_ATT_CID) ? notif_bearer : 0;
#endif
if ((p_buf = attp_build_sr_msg (p_tcb, GATT_HANDLE_VALUE_NOTIF, (tGATT_SR_MSG *)&notif))
!= NULL) {
cmd_sent = attp_send_sr_msg (p_tcb, p_buf);
} else {
cmd_sent = GATT_NO_RESOURCES;
}
#if (BLE_EATT_SERVER_INCLUDED == TRUE)
p_tcb->eatt_tx_bearer = 0;
#endif
}
return cmd_sent;
}
@@ -1208,7 +1231,17 @@ tGATT_STATUS GATTC_SendHandleValueConfirm (UINT16 conn_id, UINT16 handle)
GATT_TRACE_DEBUG ("notif_count=%d ", p_tcb->ind_count);
/* send confirmation now */
#if (BLE_EATT_INCLUDED == TRUE)
/* Route the confirmation back on the EATT bearer the indication came
* in on (0 == legacy ATT). eatt_rx_bearer was cleared after the
* indication was delivered, so use the saved eatt_ind_bearer. */
p_tcb->eatt_tx_bearer = p_tcb->eatt_ind_bearer;
ret = attp_send_cl_msg(p_tcb, 0, GATT_HANDLE_VALUE_CONF, (tGATT_CL_MSG *)&handle);
p_tcb->eatt_tx_bearer = 0;
p_tcb->eatt_ind_bearer = 0;
#else
ret = attp_send_cl_msg(p_tcb, 0, GATT_HANDLE_VALUE_CONF, (tGATT_CL_MSG *)&handle);
#endif
p_tcb->ind_count = 0;
@@ -1775,12 +1808,24 @@ tGATT_STATUS GATTS_HandleMultiValueNotification (UINT16 conn_id, tGATT_HLV *tupl
notif.auth_req = GATT_AUTH_REQ_NONE;
#if (BLE_EATT_SERVER_INCLUDED == TRUE)
/* Route the multi-value notification over an EATT bearer (if any) so it uses
* the EATT MTU instead of the legacy 23-byte ATT MTU, and so gatt_get_att_mtu()
* (used for buffer sizing in attp_build_sr_msg) matches the bearer it is sent
* on. Set transiently and cleared after the send; all GATT TX runs on the
* single BTU task. Mirrors GATTS_HandleValueNotification. */
UINT16 mv_bearer = gatt_eatt_get_server_tx_bearer(p_tcb->peer_bda);
p_tcb->eatt_tx_bearer = (mv_bearer != L2CAP_ATT_CID) ? mv_bearer : 0;
#endif
p_buf = attp_build_sr_msg (p_tcb, GATT_HANDLE_MULTI_VALUE_NOTIF, (tGATT_SR_MSG *)&notif);
if (p_buf != NULL) {
cmd_sent = attp_send_sr_msg (p_tcb, p_buf);
} else {
cmd_sent = GATT_NO_RESOURCES;
}
#if (BLE_EATT_SERVER_INCLUDED == TRUE)
p_tcb->eatt_tx_bearer = 0;
#endif
return cmd_sent;
}
@@ -1791,4 +1836,22 @@ tGATT_STATUS GATTS_ShowLocalDatabase(void)
return GATT_SUCCESS;
}
#if (BLE_EATT_INCLUDED == TRUE)
void GATT_EattSetChanNum(UINT8 num_chan)
{
gatt_eatt_set_chan_num(num_chan);
}
BOOLEAN GATT_EattSetDefaultBearer(UINT16 conn_id, UINT16 lcid)
{
#if (BLE_EATT_CLIENT_INCLUDED == TRUE)
return gatt_eatt_set_default_bearer(conn_id, lcid);
#else
UNUSED(conn_id);
UNUSED(lcid);
return FALSE;
#endif
}
#endif
#endif
@@ -28,6 +28,9 @@
#include <string.h>
#include "gatt_int.h"
#if (BLE_EATT_INCLUDED == TRUE)
#include "gatt_eatt_int.h"
#endif
#include "stack/gatt_api.h"
#include "btm_int.h"
@@ -251,6 +254,9 @@ void gatt_notify_enc_cmpl(BD_ADDR bd_addr)
}
}
}
#if (BLE_EATT_INCLUDED == TRUE)
gatt_eatt_on_encrypted(bd_addr);
#endif
} else {
GATT_TRACE_DEBUG("notify GATT for encryption completion of unknown device");
}
@@ -29,6 +29,9 @@
#include <string.h>
#include "osi/allocator.h"
#include "gatt_int.h"
#if (BLE_EATT_INCLUDED == TRUE)
#include "gatt_eatt_int.h"
#endif
#include "l2c_int.h"
#define GATT_WRITE_LONG_HDR_SIZE 5 /* 1 opcode + 2 handle + 2 offset */
@@ -244,7 +247,7 @@ void gatt_act_write (tGATT_CLCB *p_clcb, UINT8 sec_act)
break;
case GATT_WRITE:
if (p_attr->len <= (p_tcb->payload_size - GATT_HDR_SIZE)) {
if (p_attr->len <= (GATT_CL_ATT_MTU(p_tcb, GATT_REQ_WRITE) - GATT_HDR_SIZE)) {
p_clcb->s_handle = p_attr->handle;
rt = gatt_send_write_msg(p_tcb,
@@ -359,8 +362,8 @@ void gatt_send_prepare_write(tGATT_TCB *p_tcb, tGATT_CLCB *p_clcb)
GATT_TRACE_DEBUG("gatt_send_prepare_write type=0x%x", type );
to_send = p_attr->len - p_attr->offset;
if (to_send > (p_tcb->payload_size - GATT_WRITE_LONG_HDR_SIZE)) { /* 2 = UINT16 offset bytes */
to_send = p_tcb->payload_size - GATT_WRITE_LONG_HDR_SIZE;
if (to_send > (GATT_CL_ATT_MTU(p_tcb, GATT_REQ_PREPARE_WRITE) - GATT_WRITE_LONG_HDR_SIZE)) { /* 2 = UINT16 offset bytes */
to_send = GATT_CL_ATT_MTU(p_tcb, GATT_REQ_PREPARE_WRITE) - GATT_WRITE_LONG_HDR_SIZE;
}
p_clcb->s_handle = p_attr->handle;
@@ -722,6 +725,13 @@ void gatt_process_notification(tGATT_TCB *p_tcb, UINT8 op_code,
/* start a timer for app confirmation */
if (p_tcb->ind_count > 0) {
#if (BLE_EATT_INCLUDED == TRUE)
/* Remember the bearer this indication arrived on (0 == legacy ATT)
* so the app's deferred confirmation is routed back to it; by the
* time GATTC_SendHandleValueConfirm() runs, eatt_rx_bearer is
* already cleared. */
p_tcb->eatt_ind_bearer = p_tcb->eatt_rx_bearer;
#endif
gatt_start_ind_ack_timer(p_tcb);
} else { /* no app to indicate, or invalid handle */
attp_send_cl_msg(p_tcb, 0, GATT_HANDLE_VALUE_CONF, NULL);
@@ -797,11 +807,11 @@ void gatt_process_read_by_type_rsp (tGATT_TCB *p_tcb, tGATT_CLCB *p_clcb, UINT8
STREAM_TO_UINT8(value_len, p);
if ((value_len > (p_tcb->payload_size - 2)) || (value_len > (len - 1)) ) {
if ((value_len > (gatt_get_att_mtu(p_tcb) - 2)) || (value_len > (len - 1)) ) {
/* this is an error case that server's response containing a value length which is larger than MTU-2
or value_len > message total length -1 */
GATT_TRACE_ERROR("gatt_process_read_by_type_rsp: Discard response op_code=%d value_len=%d > (MTU-2=%d or msg_len-1=%d)",
op_code, value_len, (p_tcb->payload_size - 2), (len - 1));
op_code, value_len, (gatt_get_att_mtu(p_tcb) - 2), (len - 1));
gatt_end_operation(p_clcb, GATT_ERROR, NULL);
return;
}
@@ -891,7 +901,7 @@ void gatt_process_read_by_type_rsp (tGATT_TCB *p_tcb, tGATT_CLCB *p_clcb, UINT8
/* value_len is the length of current record's value; use it to avoid overread when multiple records present */
p_clcb->counter = value_len;
p_clcb->s_handle = handle;
UINT16 max_rbtype_val_len = (p_clcb->p_tcb->payload_size - 4);
UINT16 max_rbtype_val_len = (gatt_get_att_mtu(p_clcb->p_tcb) - 4);
if (max_rbtype_val_len > GATT_MAX_READ_BY_TYPE_VALUE_LEN) {
max_rbtype_val_len = GATT_MAX_READ_BY_TYPE_VALUE_LEN;
}
@@ -1000,7 +1010,7 @@ void gatt_process_read_rsp(tGATT_TCB *p_tcb, tGATT_CLCB *p_clcb, UINT8 op_code,
/* send next request if needed */
if (len == (p_tcb->payload_size - 1) && /* full packet for read or read blob rsp */
if (len == (gatt_get_att_mtu(p_tcb) - 1) && /* full packet for read or read blob rsp */
len + offset < GATT_MAX_ATTR_LEN) {
GATT_TRACE_DEBUG("full pkt issue read blob for remaining bytes old offset=%d len=%d new offset=%d",
offset, len, p_clcb->counter);
@@ -1068,6 +1078,14 @@ void gatt_process_mtu_rsp(tGATT_TCB *p_tcb, tGATT_CLCB *p_clcb, UINT16 len, UINT
UINT16 mtu;
tGATT_STATUS status = GATT_SUCCESS;
#if (BLE_EATT_CLIENT_INCLUDED == TRUE)
if (p_tcb->eatt_rx_bearer != 0 && gatt_eatt_is_bearer(p_tcb->eatt_rx_bearer)) {
GATT_TRACE_ERROR("ignore MTU response on EATT bearer");
gatt_end_operation(p_clcb, GATT_ERROR, NULL);
return;
}
#endif
if (len < GATT_MTU_RSP_MIN_LEN) {
GATT_TRACE_ERROR("invalid MTU response PDU received, discard.");
status = GATT_INVALID_PDU;
@@ -1187,21 +1205,51 @@ BOOLEAN gatt_cl_send_next_cmd_inq(tGATT_TCB *p_tcb)
**
*******************************************************************************/
void gatt_client_handle_server_rsp (tGATT_TCB *p_tcb, UINT8 op_code,
UINT16 len, UINT8 *p_data)
UINT16 len, UINT8 *p_data, UINT16 eatt_bearer_lcid)
{
tGATT_CLCB *p_clcb = NULL;
UINT8 rsp_code;
UINT8 rsp_code = 0;
#if (BLE_EATT_CLIENT_INCLUDED == TRUE)
UINT8 cmd_code = 0;
UINT16 clcb_idx = 0;
#else
UNUSED(eatt_bearer_lcid);
#endif
if (op_code != GATT_HANDLE_VALUE_IND && op_code != GATT_HANDLE_VALUE_NOTIF &&
op_code != GATT_HANDLE_MULTI_VALUE_NOTIF) {
p_clcb = gatt_cmd_dequeue(p_tcb, &rsp_code);
rsp_code = gatt_cmd_to_rsp_code(rsp_code);
p_clcb = NULL;
#if (BLE_EATT_CLIENT_INCLUDED == TRUE)
if (eatt_bearer_lcid != 0) {
if (gatt_eatt_release_bearer(p_tcb->peer_bda, eatt_bearer_lcid, &cmd_code, &clcb_idx)) {
p_clcb = gatt_clcb_find_by_idx(clcb_idx);
if (p_clcb != NULL) {
rsp_code = gatt_cmd_to_rsp_code(cmd_code);
}
}
}
#endif
if (p_clcb == NULL
#if (BLE_EATT_CLIENT_INCLUDED == TRUE)
&& eatt_bearer_lcid == 0
#endif
) {
p_clcb = gatt_cmd_dequeue(p_tcb, &rsp_code);
rsp_code = gatt_cmd_to_rsp_code(rsp_code);
}
if (p_clcb == NULL || (rsp_code != op_code && op_code != GATT_RSP_ERROR)) {
GATT_TRACE_WARNING ("ATT - Ignore wrong response. Receives (%02x) \
Request(%02x) Ignored", op_code, rsp_code);
#if (BLE_EATT_CLIENT_INCLUDED == TRUE)
/* On an EATT bearer the bearer was released above to locate the
* pending request. Since this response is wrong/unexpected, restore
* the bearer's busy state so the still-pending request keeps it and
* completes on the correct response or the response timer. */
if (p_clcb != NULL && eatt_bearer_lcid != 0) {
gatt_eatt_mark_busy(p_tcb->peer_bda, eatt_bearer_lcid, cmd_code, clcb_idx);
}
#endif
return;
} else {
btu_stop_timer (&p_clcb->rsp_timer_ent);
@@ -1210,8 +1258,8 @@ void gatt_client_handle_server_rsp (tGATT_TCB *p_tcb, UINT8 op_code,
}
/* the size of the message may not be bigger than the local max PDU size*/
/* The message has to be smaller than the agreed MTU, len does not count op_code */
if (len >= p_tcb->payload_size) {
GATT_TRACE_ERROR("invalid response/indicate pkt size: %d, PDU size: %d", len + 1, p_tcb->payload_size);
if (len >= gatt_get_att_mtu(p_tcb)) {
GATT_TRACE_ERROR("invalid response/indicate pkt size: %d, PDU size: %d", len + 1, gatt_get_att_mtu(p_tcb));
if (op_code != GATT_HANDLE_VALUE_NOTIF && op_code != GATT_HANDLE_VALUE_IND &&
op_code != GATT_HANDLE_MULTI_VALUE_NOTIF) {
gatt_end_operation(p_clcb, GATT_ERROR, NULL);
@@ -1272,7 +1320,12 @@ void gatt_client_handle_server_rsp (tGATT_TCB *p_tcb, UINT8 op_code,
if (op_code != GATT_HANDLE_VALUE_IND && op_code != GATT_HANDLE_VALUE_NOTIF &&
op_code != GATT_HANDLE_MULTI_VALUE_NOTIF) {
#if (BLE_EATT_CLIENT_INCLUDED == TRUE)
if (eatt_bearer_lcid == 0)
#endif
{
gatt_cl_send_next_cmd_inq(p_tcb);
}
}
}
File diff suppressed because it is too large Load Diff
@@ -28,6 +28,9 @@
#include "gatt_int.h"
#include "stack/l2c_api.h"
#if (BLE_EATT_INCLUDED == TRUE)
#include "gatt_eatt_int.h"
#endif
#include "btm_int.h"
#include "btm_ble_int.h"
#include "osi/allocator.h"
@@ -149,6 +152,10 @@ void gatt_init (void)
#endif ///GATTS_INCLUDED == TRUE
//init local MTU size
gatt_default.local_mtu = GATT_MAX_MTU_SIZE;
#if (BLE_EATT_INCLUDED == TRUE)
gatt_eatt_init();
#endif
}
@@ -172,6 +179,11 @@ void gatt_free(void)
gatt_cb.pending_new_srv_start_q = NULL;
#endif // (GATTS_INCLUDED == TRUE)
/* Note: gatt_eatt_deinit() is intentionally invoked from btu_free_core()
* BEFORE l2c_free(), because it deregisters L2CAP/GATT resources that
* require live L2CAP state. Calling it here (gatt_free runs after l2c_free)
* would dereference the already-freed l2c_cb_ptr. */
list_node_t *p_node = NULL;
tGATT_TCB *p_tcb = NULL;
for(p_node = list_begin(gatt_cb.p_tcb_list); p_node; p_node = list_next(p_node)) {
@@ -986,7 +998,7 @@ static void gatt_send_conn_cback(tGATT_TCB *p_tcb)
void gatt_data_process (tGATT_TCB *p_tcb, BT_HDR *p_buf)
{
UINT8 *p = (UINT8 *)(p_buf + 1) + p_buf->offset;
UINT8 op_code, pseudo_op_code;
UINT8 op_code;
#if (GATTS_INCLUDED == TRUE) || (GATTC_INCLUDED == TRUE)
UINT16 msg_len;
#endif ///(GATTS_INCLUDED == TRUE) || (GATTC_INCLUDED == TRUE)
@@ -998,10 +1010,7 @@ void gatt_data_process (tGATT_TCB *p_tcb, BT_HDR *p_buf)
#endif ///(GATTS_INCLUDED == TRUE) || (GATTC_INCLUDED == TRUE)
STREAM_TO_UINT8(op_code, p);
/* remove the two MSBs associated with sign write and write cmd */
pseudo_op_code = op_code & (~GATT_WRITE_CMD_MASK);
if (pseudo_op_code < GATT_OP_CODE_MAX) {
if (gatt_is_valid_att_opcode(op_code)) {
#if (GATTS_INCLUDED == TRUE) || (GATTC_INCLUDED == TRUE)
GATT_TRACE_DEBUG("%s opcode=%x msg_len=%u", __func__, op_code, msg_len);
#endif ///(GATTS_INCLUDED == TRUE) || (GATTC_INCLUDED == TRUE)
@@ -1017,7 +1026,7 @@ void gatt_data_process (tGATT_TCB *p_tcb, BT_HDR *p_buf)
#endif ///GATTS_INCLUDED == TRUE
} else {
#if (GATTC_INCLUDED == TRUE)
gatt_client_handle_server_rsp (p_tcb, op_code, msg_len, p);
gatt_client_handle_server_rsp (p_tcb, op_code, msg_len, p, 0);
#endif ///GATTC_INCLUDED == TRUE
}
}
@@ -30,6 +30,9 @@
#include "gatt_int.h"
#include "stack/l2c_api.h"
#include "l2c_int.h"
#if (BLE_EATT_INCLUDED == TRUE)
#include "gatt_eatt_int.h"
#endif
#define GATT_MTU_REQ_MIN_LEN 2
@@ -50,12 +53,13 @@ tGATT_STATUS gatt_send_packet (tGATT_TCB *p_tcb, UINT8 *p_data, UINT16 len)
UINT8 *p_m = NULL;
UINT16 buf_len;
tGATT_STATUS status;
UINT16 att_mtu = gatt_get_att_mtu(p_tcb);
if (len > p_tcb->payload_size){
if (len > att_mtu){
return GATT_ILLEGAL_PARAMETER;
}
buf_len = (UINT16)(sizeof(BT_HDR) + p_tcb->payload_size + L2CAP_MIN_OFFSET);
buf_len = (UINT16)(sizeof(BT_HDR) + att_mtu + L2CAP_MIN_OFFSET);
if ((p_msg = (BT_HDR *)osi_malloc(buf_len)) == NULL) {
return GATT_NO_RESOURCES;
}
@@ -442,13 +446,38 @@ tGATT_STATUS gatt_sr_process_app_rsp (tGATT_TCB *p_tcb, tGATT_IF gatt_if,
gatt_sr_update_cback_cnt(p_tcb, gatt_if, FALSE, FALSE);
#if (BLE_EATT_INCLUDED == TRUE)
/* If the request arrived on an EATT bearer and this response is deferred
* (GATT_PENDING) so eatt_rx_bearer was already cleared after synchronous
* handling, restore the TX bearer BEFORE the response is built. Otherwise
* gatt_get_att_mtu() below (and inside attp_build_sr_msg) would fall back to
* the legacy ATT MTU and truncate/mis-size the response. Cleared after send. */
BOOLEAN eatt_routed = FALSE;
if (gatt_sr_is_cback_cnt_zero(p_tcb) &&
p_tcb->eatt_tx_bearer == 0 && p_tcb->eatt_rx_bearer == 0 &&
p_tcb->sr_cmd.eatt_lcid != 0) {
p_tcb->eatt_tx_bearer = p_tcb->sr_cmd.eatt_lcid;
eatt_routed = TRUE;
}
#endif
if (op_code == GATT_REQ_READ_MULTI) {
/* If no error and still waiting, just return */
if (!process_read_multi_rsp (&p_tcb->sr_cmd, status, p_msg, p_tcb->payload_size)) {
if (!process_read_multi_rsp (&p_tcb->sr_cmd, status, p_msg, gatt_get_att_mtu(p_tcb))) {
#if (BLE_EATT_INCLUDED == TRUE)
if (eatt_routed) {
p_tcb->eatt_tx_bearer = 0;
}
#endif
return (GATT_SUCCESS);
}
} else if (op_code == GATT_REQ_READ_MULTI_VAR) {
if (!process_read_multi_var_rsp(&p_tcb->sr_cmd, status, p_msg, p_tcb->payload_size)) {
if (!process_read_multi_var_rsp(&p_tcb->sr_cmd, status, p_msg, gatt_get_att_mtu(p_tcb))) {
#if (BLE_EATT_INCLUDED == TRUE)
if (eatt_routed) {
p_tcb->eatt_tx_bearer = 0;
}
#endif
return (GATT_SUCCESS);
}
} else {
@@ -458,6 +487,19 @@ tGATT_STATUS gatt_sr_process_app_rsp (tGATT_TCB *p_tcb, tGATT_IF gatt_if,
if (op_code == GATT_REQ_EXEC_WRITE && status != GATT_SUCCESS) {
gatt_sr_reset_cback_cnt(p_tcb);
#if (BLE_EATT_INCLUDED == TRUE)
/* reset_cback_cnt() may have just forced the count to zero. If the
* EATT restore above was skipped because the count was still
* non-zero at that point (multi-app EXEC_WRITE), redo it now so the
* error response goes out on the originating EATT bearer instead of
* falling back to the legacy ATT fixed channel. */
if (!eatt_routed && gatt_sr_is_cback_cnt_zero(p_tcb) &&
p_tcb->eatt_tx_bearer == 0 && p_tcb->eatt_rx_bearer == 0 &&
p_tcb->sr_cmd.eatt_lcid != 0) {
p_tcb->eatt_tx_bearer = p_tcb->sr_cmd.eatt_lcid;
eatt_routed = TRUE;
}
#endif
}
p_tcb->sr_cmd.status = status;
@@ -472,6 +514,8 @@ tGATT_STATUS gatt_sr_process_app_rsp (tGATT_TCB *p_tcb, tGATT_IF gatt_if,
}
}
if (gatt_sr_is_cback_cnt_zero(p_tcb)) {
/* eatt_tx_bearer was already restored above (before the response was
* built) so gatt_get_att_mtu() used the correct EATT MTU. */
if ( (p_tcb->sr_cmd.status == GATT_SUCCESS) && (p_tcb->sr_cmd.p_rsp_msg) ) {
ret_code = attp_send_sr_msg (p_tcb, p_tcb->sr_cmd.p_rsp_msg);
p_tcb->sr_cmd.p_rsp_msg = NULL;
@@ -482,6 +526,11 @@ tGATT_STATUS gatt_sr_process_app_rsp (tGATT_TCB *p_tcb, tGATT_IF gatt_if,
ret_code = gatt_send_error_rsp (p_tcb, status, op_code, p_tcb->sr_cmd.handle, FALSE);
}
#if (BLE_EATT_INCLUDED == TRUE)
if (eatt_routed) {
p_tcb->eatt_tx_bearer = 0;
}
#endif
gatt_dequeue_sr_cmd(p_tcb);
}
@@ -875,7 +924,7 @@ static tGATT_STATUS gatt_build_primary_service_rsp (BT_HDR *p_msg, tGATT_TCB *p_
}
}
if (p_msg->len + p_msg->offset <= p_tcb->payload_size &&
if (p_msg->len + p_msg->offset <= gatt_get_att_mtu(p_tcb) &&
handle_len == p_msg->offset) {
if (op_code != GATT_REQ_FIND_TYPE_VALUE ||
gatt_uuid_compare(value, *p_uuid)) {
@@ -1053,7 +1102,7 @@ void gatts_process_primary_service_req(tGATT_TCB *p_tcb, UINT8 op_code, UINT16 l
UINT16 s_hdl = 0, e_hdl = 0;
tBT_UUID uuid, value, primary_service = {LEN_UUID_16, {GATT_UUID_PRI_SERVICE}};
BT_HDR *p_msg = NULL;
UINT16 msg_len = (UINT16)(sizeof(BT_HDR) + p_tcb->payload_size + L2CAP_MIN_OFFSET);
UINT16 msg_len = (UINT16)(sizeof(BT_HDR) + gatt_get_att_mtu(p_tcb) + L2CAP_MIN_OFFSET);
memset (&value, 0, sizeof(tBT_UUID));
reason = gatts_validate_packet_format(op_code, &len, &p_data, &uuid, &s_hdl, &e_hdl);
@@ -1119,7 +1168,7 @@ static void gatts_process_find_info(tGATT_TCB *p_tcb, UINT8 op_code, UINT16 len,
reason = gatts_validate_packet_format(op_code, &len, &p_data, NULL, &s_hdl, &e_hdl);
if (reason == GATT_SUCCESS) {
buf_len = (UINT16)(sizeof(BT_HDR) + p_tcb->payload_size + L2CAP_MIN_OFFSET);
buf_len = (UINT16)(sizeof(BT_HDR) + gatt_get_att_mtu(p_tcb) + L2CAP_MIN_OFFSET);
if ((p_msg = (BT_HDR *)osi_calloc(buf_len)) == NULL) {
reason = GATT_NO_RESOURCES;
@@ -1130,7 +1179,7 @@ static void gatts_process_find_info(tGATT_TCB *p_tcb, UINT8 op_code, UINT16 len,
*p ++ = op_code + 1;
p_msg->len = 2;
buf_len = p_tcb->payload_size - 2;
buf_len = gatt_get_att_mtu(p_tcb) - 2;
p_srv = p_list->p_first;
@@ -1182,6 +1231,14 @@ static void gatts_process_mtu_req (tGATT_TCB *p_tcb, UINT16 len, UINT8 *p_data)
BT_HDR *p_buf;
UINT16 conn_id;
#if (BLE_EATT_INCLUDED == TRUE)
/* Exchange MTU applies to Legacy ATT bearer only (Core Spec Vol 3 Part G 5.3). */
if (p_tcb->eatt_rx_bearer != 0 && gatt_eatt_is_bearer(p_tcb->eatt_rx_bearer)) {
gatt_send_error_rsp (p_tcb, GATT_REQ_NOT_SUPPORTED, GATT_REQ_MTU, 0, FALSE);
return;
}
#endif
/* BR/EDR connection, send error response */
if (p_tcb->att_lcid != L2CAP_ATT_CID) {
gatt_send_error_rsp (p_tcb, GATT_REQ_NOT_SUPPORTED, GATT_REQ_MTU, 0, FALSE);
@@ -1241,7 +1298,12 @@ void gatts_process_read_by_type_req(tGATT_TCB *p_tcb, UINT8 op_code, UINT16 len,
{
tBT_UUID uuid;
tGATT_SR_REG *p_rcb;
UINT16 msg_len = (UINT16)(sizeof(BT_HDR) + p_tcb->payload_size + L2CAP_MIN_OFFSET),
/* Cache the MTU once: gatt_get_att_mtu() reads dynamic EATT bearer state, so
* calling it separately for the allocation size and the write limit could
* (if it ever changed between calls) let buf_len exceed the allocated buffer.
* One read keeps both consistent, matching gatt_send_packet(). */
UINT16 att_mtu = gatt_get_att_mtu(p_tcb);
UINT16 msg_len = (UINT16)(sizeof(BT_HDR) + att_mtu + L2CAP_MIN_OFFSET),
buf_len,
s_hdl, e_hdl, err_hdl = 0;
BT_HDR *p_msg = NULL;
@@ -1274,7 +1336,7 @@ void gatts_process_read_by_type_req(tGATT_TCB *p_tcb, UINT8 op_code, UINT16 len,
*p ++ = op_code + 1;
/* reserve length byte */
p_msg->len = 2;
buf_len = p_tcb->payload_size - 2;
buf_len = att_mtu - 2;
reason = GATT_NOT_FOUND;
@@ -1614,7 +1676,7 @@ void gatt_attr_process_prepare_write (tGATT_TCB *p_tcb, UINT8 i_rcb, UINT16 hand
static void gatts_process_read_req(tGATT_TCB *p_tcb, tGATT_SR_REG *p_rcb, UINT8 op_code,
UINT16 handle, UINT16 len, UINT8 *p_data)
{
UINT16 buf_len = (UINT16)(sizeof(BT_HDR) + p_tcb->payload_size + L2CAP_MIN_OFFSET);
UINT16 buf_len = (UINT16)(sizeof(BT_HDR) + gatt_get_att_mtu(p_tcb) + L2CAP_MIN_OFFSET);
tGATT_STATUS reason;
BT_HDR *p_msg = NULL;
UINT8 sec_flag, key_size, *p;
@@ -1639,7 +1701,7 @@ static void gatts_process_read_req(tGATT_TCB *p_tcb, tGATT_SR_REG *p_rcb, UINT8
p = (UINT8 *)(p_msg + 1) + L2CAP_MIN_OFFSET;
*p ++ = op_code + 1;
p_msg->len = 1;
buf_len = p_tcb->payload_size - 1;
buf_len = gatt_get_att_mtu(p_tcb) - 1;
gatt_sr_get_sec_info(p_tcb->peer_bda,
p_tcb->transport,
@@ -1958,8 +2020,8 @@ void gatt_server_handle_client_req (tGATT_TCB *p_tcb, UINT8 op_code,
/* the size of the message may not be bigger than the local max PDU size*/
/* The message has to be smaller than the agreed MTU, len does not include op code */
if (len >= p_tcb->payload_size) {
GATT_TRACE_ERROR("server receive invalid PDU size:%d pdu size:%d", len + 1, p_tcb->payload_size );
if (len >= gatt_get_att_mtu(p_tcb)) {
GATT_TRACE_ERROR("server receive invalid PDU size:%d pdu size:%d", len + 1, gatt_get_att_mtu(p_tcb) );
/* for invalid request expecting response, send it now */
if (op_code != GATT_CMD_WRITE &&
op_code != GATT_SIGN_CMD_WRITE &&
@@ -34,6 +34,9 @@
#include "stack/gattdefs.h"
#include "stack/sdp_api.h"
#include "btm_int.h"
#if (BLE_EATT_INCLUDED == TRUE)
#include "gatt_eatt_int.h"
#endif
/* check if [x, y] and [a, b] have overlapping range */
#define GATT_VALIDATE_HANDLE_RANGE(x, y, a, b) (y >= a && x <= b)
@@ -1301,6 +1304,20 @@ void gatt_rsp_timeout(TIMER_LIST_ENT *p_tle)
p_clcb->retry_count < GATT_REQ_RETRY_LIMIT) {
UINT8 rsp_code;
GATT_TRACE_WARNING("gatt_rsp_timeout retry discovery primary service");
#if (BLE_EATT_INCLUDED == TRUE)
/* Operations sent over an EATT bearer are tracked in the EATT bearer
* table, not the legacy cl_cmd_q. Calling gatt_cmd_dequeue for them would
* consume an unrelated legacy command and report "out of sync". Release
* the EATT bearer and retry directly (gatt_act_discovery re-acquires a
* bearer via attp_cl_send_cmd). */
if (gatt_eatt_release_bearer_by_clcb(p_clcb->p_tcb->peer_bda, p_clcb->clcb_idx)) {
p_clcb->retry_count++;
#if (GATTC_INCLUDED == TRUE)
gatt_act_discovery(p_clcb);
#endif ///GATTC_INCLUDED == TRUE
return;
}
#endif ///BLE_EATT_INCLUDED == TRUE
if (p_clcb != gatt_cmd_dequeue(p_clcb->p_tcb, &rsp_code)) {
GATT_TRACE_ERROR("gatt_rsp_timeout command queue out of sync, disconnect");
} else {
@@ -1336,6 +1353,16 @@ void gatt_ind_ack_timeout(TIMER_LIST_ENT *p_tle)
p_tcb->ind_count = 0;
}
#if (BLE_EATT_INCLUDED == TRUE)
if (p_tcb != NULL) {
/* Auto-ack on the bearer the indication arrived on (0 == legacy ATT). */
p_tcb->eatt_tx_bearer = p_tcb->eatt_ind_bearer;
attp_send_cl_msg(p_tcb, 0, GATT_HANDLE_VALUE_CONF, NULL);
p_tcb->eatt_tx_bearer = 0;
p_tcb->eatt_ind_bearer = 0;
return;
}
#endif
attp_send_cl_msg(((tGATT_TCB *)p_tle->param), 0, GATT_HANDLE_VALUE_CONF, NULL);
}
#endif // (GATTC_INCLUDED == TRUE)
@@ -0,0 +1,48 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
/* EATT (Enhanced ATT) internal definitions. */
#ifndef GATT_EATT_INT_H
#define GATT_EATT_INT_H
#include "common/bt_target.h"
#if (BLE_EATT_INCLUDED == TRUE)
#include "stack/bt_types.h"
#include "gatt_int.h"
#define GATT_EATT_PSM 0x0027
typedef void (tGATT_EATT_EVT_CBACK)(UINT16 conn_id, UINT8 status, UINT16 cid);
void gatt_eatt_init(void);
void gatt_eatt_deinit(void);
void gatt_eatt_register_evt_cback(tGATT_EATT_EVT_CBACK *p_cback);
void gatt_eatt_set_chan_num(UINT8 num_chan);
void gatt_eatt_on_encrypted(BD_ADDR bd_addr);
BOOLEAN gatt_eatt_is_bearer(UINT16 lcid);
UINT16 gatt_eatt_get_available_bearer(BD_ADDR bd_addr, UINT8 op);
#if (BLE_EATT_SERVER_INCLUDED == TRUE)
/* Pick an EATT bearer for a server-initiated PDU (notification/indication),
* round-robin across the connection's bearers. Returns L2CAP_ATT_CID when no
* EATT bearer is available so the caller falls back to the legacy ATT channel. */
UINT16 gatt_eatt_get_server_tx_bearer(BD_ADDR bd_addr);
#endif
BOOLEAN gatt_eatt_set_default_bearer(UINT16 conn_id, UINT16 lcid);
BOOLEAN gatt_eatt_mark_busy(BD_ADDR bd_addr, UINT16 lcid, UINT8 op, UINT16 clcb_idx);
BOOLEAN gatt_eatt_release_bearer(BD_ADDR bd_addr, UINT16 lcid, UINT8 *p_op, UINT16 *p_clcb_idx);
BOOLEAN gatt_eatt_release_bearer_by_clcb(BD_ADDR bd_addr, UINT16 clcb_idx);
void gatt_eatt_data_ind(UINT16 lcid, BT_HDR *p_buf);
void gatt_eatt_on_chan_mtu_changed(BD_ADDR bd_addr, UINT16 lcid);
UINT16 gatt_eatt_mtu_for_client_op(BD_ADDR bd_addr, UINT8 op_code, UINT16 legacy_mtu);
#endif /* BLE_EATT_INCLUDED == TRUE */
#endif /* GATT_EATT_INT_H */
@@ -74,6 +74,20 @@ typedef UINT8 tGATT_SEC_ACTION;
#define GATT_AUTH_SIGN_MASK 0x80 /*0x1000-0000*/
#define GATT_AUTH_SIGN_LEN 12
/* Only Write Command (0x52) and Signed Write Command (0xD2) may set the
* command/signature bits in the top two MSBs; all other opcodes must be
* strictly below GATT_OP_CODE_MAX with those bits clear. */
static inline BOOLEAN gatt_is_valid_att_opcode(UINT8 op_code)
{
if (op_code == GATT_CMD_WRITE || op_code == GATT_SIGN_CMD_WRITE) {
return TRUE;
}
if (op_code & GATT_WRITE_CMD_MASK) {
return FALSE;
}
return op_code < GATT_OP_CODE_MAX;
}
#define GATT_HDR_SIZE 3 /* 1B opcode + 2B handle */
/* ATT Read By Type Response: Length field is 1 octet (max 255). */
@@ -301,6 +315,11 @@ typedef struct {
UINT8 op_code;
UINT8 status;
UINT8 cback_cnt[GATT_MAX_APPS];
#if (BLE_EATT_INCLUDED == TRUE)
UINT16 eatt_lcid; /* EATT bearer the request arrived on, so an
* async server response is routed back to it
* after eatt_rx_bearer has been cleared. */
#endif
} tGATT_SR_CMD;
#define GATT_CH_CLOSE 0
@@ -388,6 +407,13 @@ typedef struct {
UINT32 trans_id;
UINT16 att_lcid; /* L2CAP channel ID for ATT */
#if (BLE_EATT_INCLUDED == TRUE)
UINT16 eatt_rx_bearer; /* active EATT bearer for RX/response routing */
UINT16 eatt_tx_bearer; /* transient TX bearer override */
UINT16 eatt_ind_bearer; /* EATT bearer an indication arrived on, so a
* deferred app confirmation is sent back on it */
UINT16 eatt_att_mtu; /* negotiated L2CAP MTU for EATT bearers */
#endif
UINT16 payload_size;
tGATT_CH_STATE ch_state;
@@ -635,6 +661,19 @@ extern UINT16 gatt_profile_find_conn_id_by_bd_addr(BD_ADDR bda);
/* Functions provided by att_protocol.c */
#if (BLE_EATT_INCLUDED == TRUE)
extern UINT16 gatt_get_att_mtu(tGATT_TCB *p_tcb);
#if (BLE_EATT_CLIENT_INCLUDED == TRUE)
extern UINT16 gatt_eatt_mtu_for_client_op(BD_ADDR bd_addr, UINT8 op_code, UINT16 legacy_mtu);
#define GATT_CL_ATT_MTU(p_tcb, op) \
gatt_eatt_mtu_for_client_op((p_tcb)->peer_bda, (op), (p_tcb)->payload_size)
#else
#define GATT_CL_ATT_MTU(p_tcb, op) ((p_tcb)->payload_size)
#endif
#else
#define gatt_get_att_mtu(p_tcb) ((p_tcb)->payload_size)
#define GATT_CL_ATT_MTU(p_tcb, op) ((p_tcb)->payload_size)
#endif
extern tGATT_STATUS attp_send_cl_msg (tGATT_TCB *p_tcb, UINT16 clcb_idx, UINT8 op_code, tGATT_CL_MSG *p_msg);
extern BT_HDR *attp_build_sr_msg(tGATT_TCB *p_tcb, UINT8 op_code, tGATT_SR_MSG *p_msg);
extern tGATT_STATUS attp_send_sr_msg (tGATT_TCB *p_tcb, BT_HDR *p_msg);
@@ -753,7 +792,7 @@ extern UINT8 gatt_act_send_browse(tGATT_TCB *p_tcb, UINT16 index, UINT8 op, UINT
extern tGATT_CLCB *gatt_cmd_dequeue(tGATT_TCB *p_tcb, UINT8 *p_opcode);
extern BOOLEAN gatt_cmd_enq(tGATT_TCB *p_tcb, UINT16 clcb_idx, BOOLEAN to_send, UINT8 op_code, BT_HDR *p_buf);
extern void gatt_client_handle_server_rsp (tGATT_TCB *p_tcb, UINT8 op_code,
UINT16 len, UINT8 *p_data);
UINT16 len, UINT8 *p_data, UINT16 eatt_bearer_lcid);
extern void gatt_send_queue_write_cancel (tGATT_TCB *p_tcb, tGATT_CLCB *p_clcb, tGATT_EXEC_FLAG flag);
/* gatt_auth.c */
@@ -1278,6 +1278,11 @@ extern tGATT_STATUS GATTS_HandleMultiValueNotification (UINT16 conn_id, tGATT_HL
*******************************************************************************/
extern tGATT_STATUS GATTS_ShowLocalDatabase(void);
#if (BLE_EATT_INCLUDED == TRUE)
extern void GATT_EattSetChanNum(UINT8 num_chan);
extern BOOLEAN GATT_EattSetDefaultBearer(UINT16 conn_id, UINT16 lcid);
#endif
#ifdef __cplusplus
}
@@ -277,6 +277,15 @@ typedef void (tL2CA_ECHO_DATA_CB) (BD_ADDR, UINT16, UINT8 *);
*/
typedef void (tL2CA_CONGESTION_STATUS_CB) (UINT16, BOOLEAN);
#if (BLE_L2CAP_ENHANCED_COC_INCLUDED == TRUE)
/* LE CoC reconfiguration indication. Parameters are:
** Local CID
** Result (0 = L2CAP_LE_RECONFIG_OK)
** TRUE if peer initiated the reconfiguration
*/
typedef void (tL2CA_LE_RECONFIG_IND_CB) (UINT16, UINT16, BOOLEAN);
#endif
/* Callback prototype for number of packets completed events.
** This callback notifies the application when Number of Completed Packets
** event has been received.
@@ -312,6 +321,9 @@ typedef struct {
tL2CA_DATA_IND_CB *pL2CA_DataInd_Cb;
tL2CA_CONGESTION_STATUS_CB *pL2CA_CongestionStatus_Cb;
tL2CA_TX_COMPLETE_CB *pL2CA_TxComplete_Cb;
#if (BLE_L2CAP_ENHANCED_COC_INCLUDED == TRUE)
tL2CA_LE_RECONFIG_IND_CB *pL2CA_LeReconfigInd_Cb;
#endif
} tL2CAP_APPL_INFO;
@@ -558,6 +570,12 @@ extern UINT16 L2CA_ConnectLECocReq (UINT16 psm, BD_ADDR p_bd_addr, tL2CAP_LE_CFG
extern BOOLEAN L2CA_ConnectLECocRsp (BD_ADDR p_bd_addr, UINT8 id, UINT16 lcid, UINT16 result,
UINT16 status, tL2CAP_LE_CFG_INFO *p_cfg);
#if (BLE_L2CAP_ENHANCED_COC_INCLUDED == TRUE)
extern UINT8 L2CA_ConnectLEEcocReq(UINT16 psm, BD_ADDR p_bd_addr, tL2CAP_LE_CFG_INFO *p_cfg,
UINT8 num_chan, UINT16 *p_lcids);
extern BOOLEAN L2CA_LEEcocReconfig(UINT16 lcids[], UINT8 num, UINT16 new_mtu, UINT16 new_mps);
#endif
/*******************************************************************************
**
** Function L2CA_GetPeerLECocConfig
@@ -569,6 +587,12 @@ extern BOOLEAN L2CA_ConnectLECocRsp (BD_ADDR p_bd_addr, UINT8 id, UINT16 lcid, U
*******************************************************************************/
extern BOOLEAN L2CA_GetPeerLECocConfig (UINT16 lcid, tL2CAP_LE_CFG_INFO* peer_cfg);
extern UINT8 L2CA_LECocDataWrite (UINT16 lcid, BT_HDR *p_data);
extern BOOLEAN L2CA_LECocIsCongested (UINT16 lcid);
extern BOOLEAN L2CA_LECocGiveCredits (UINT16 lcid, UINT16 credits);
extern BOOLEAN L2CA_LECocSetAutoCredit (UINT16 lcid, BOOLEAN enable);
extern BOOLEAN L2CA_LECocDisconnect (UINT16 lcid);
#endif // (BLE_L2CAP_COC_INCLUDED == TRUE)
/*******************************************************************************
@@ -44,6 +44,10 @@
#define L2CAP_CMD_BLE_CREDIT_BASED_CONN_REQ 0x14
#define L2CAP_CMD_BLE_CREDIT_BASED_CONN_RES 0x15
#define L2CAP_CMD_BLE_FLOW_CTRL_CREDIT 0x16
#define L2CAP_CMD_BLE_ENHANCED_CONN_REQ 0x17
#define L2CAP_CMD_BLE_ENHANCED_CONN_RES 0x18
#define L2CAP_CMD_BLE_CREDIT_RECONFIG_REQ 0x19
#define L2CAP_CMD_BLE_CREDIT_RECONFIG_RSP 0x1A
@@ -77,6 +81,10 @@
#define L2CAP_CMD_BLE_CREDIT_BASED_CONN_REQ_LEN 10 /* LE_PSM, SCID, MTU, MPS, Init Credit */
#define L2CAP_CMD_BLE_CREDIT_BASED_CONN_RES_LEN 10 /* DCID, MTU, MPS, Init credit, Result */
#define L2CAP_CMD_BLE_FLOW_CTRL_CREDIT_LEN 4 /* CID, Credit */
#define L2CAP_CMD_BLE_ENHANCED_CONN_REQ_BASE_LEN 8 /* LE_PSM, MTU, MPS, Init Credit */
#define L2CAP_CMD_BLE_ENHANCED_CONN_RES_BASE_LEN 8 /* MTU, MPS, Init credit, Result */
#define L2CAP_CMD_BLE_CREDIT_RECONFIG_REQ_BASE_LEN 4 /* MTU, MPS */
#define L2CAP_CMD_BLE_CREDIT_RECONFIG_RSP_LEN 2 /* Result */
@@ -288,7 +296,7 @@
/* SAR bits in the control word
*/
#define L2CAP_FCR_UNSEG_SDU 0x0000 /* Control word to begin with for unsegmented PDU*/
#define L2CAP_FCR_START_SDU 0x4000 /* ...for Starting PDU of a semented SDU */
#define L2CAP_FCR_START_SDU 0x4000 /* ...for Starting PDU of a segmented SDU */
#define L2CAP_FCR_END_SDU 0x8000 /* ...for ending PDU of a segmented SDU */
#define L2CAP_FCR_CONT_SDU 0xc000 /* ...for continuation PDU of a segmented SDU */
@@ -333,4 +341,10 @@
#define L2CAP_LE_RESULT_UNACCEPTABLE_PARAMETERS 0x0B
#define L2CAP_LE_RESULT_INVALID_PARAMETERS 0x0C
#define L2CAP_LE_RECONFIG_OK 0
#define L2CAP_LE_RECONFIG_REDUCTION_MTU_NOT_ALLOWED 1
#define L2CAP_LE_RECONFIG_REDUCTION_MPS_NOT_ALLOWED 2
#define L2CAP_LE_RECONFIG_INVALID_DCID 3
#define L2CAP_LE_RECONFIG_UNACCEPTED_PARAM 4
#endif
@@ -38,6 +38,12 @@
#define L2CAP_LE_MIN_MTU 23
#define L2CAP_LE_MIN_MPS 23
#define L2CAP_LE_MAX_MPS 65533
#define L2CAP_LE_CLAMP_MPS(m) \
((UINT16)(((m) < L2CAP_LE_MIN_MPS) ? L2CAP_LE_MIN_MPS : \
(((m) > L2CAP_LE_MAX_MPS) ? L2CAP_LE_MAX_MPS : (m))))
/* Enhanced Credit Based Flow Control minimums (Core Spec Vol 3 Part A 4.25). */
#define L2CAP_LE_ECFC_MIN_MTU 64
#define L2CAP_LE_ECFC_MIN_MPS 64
#define L2CAP_LE_MIN_CREDIT 0
#define L2CAP_LE_MAX_CREDIT 65535
#define L2CAP_LE_DEFAULT_MTU 512
@@ -285,8 +291,10 @@ typedef struct
typedef struct t_l2c_ccb {
BOOLEAN in_use; /* TRUE when in use, FALSE when not */
tL2C_CHNL_STATE chnl_state; /* Channel state */
tL2CAP_LE_CFG_INFO local_conn_cfg; /* Our config for ble conn oriented channel */
tL2CAP_LE_CFG_INFO peer_conn_cfg; /* Peer device config ble conn oriented channel */
#if (BLE_INCLUDED == TRUE)
tL2CAP_LE_CFG_INFO local_conn_cfg; /* LE CoC local channel config */
tL2CAP_LE_CFG_INFO peer_conn_cfg; /* LE CoC peer channel config */
#endif
struct t_l2c_ccb *p_next_ccb; /* Next CCB in the chain */
struct t_l2c_ccb *p_prev_ccb; /* Previous CCB in the chain */
@@ -347,6 +355,23 @@ typedef struct t_l2c_ccb {
UINT16 fixed_chnl_idle_tout; /* Idle timeout to use for the fixed channel */
#endif
UINT16 tx_data_len;
#if (BLE_L2CAP_COC_INCLUDED == TRUE)
BOOLEAN le_coc_active;
BOOLEAN le_ecfc_channel;
BOOLEAN le_coc_no_auto_credit;
UINT16 le_coc_rx_avail;
UINT16 le_coc_rx_credits_pending;
UINT16 le_coc_rx_manual_owed; /* manual mode: K-frame credits consumed, awaiting recv_ready return */
BT_HDR *le_coc_rx_sdu;
UINT16 le_coc_rx_sdu_total;
UINT16 le_coc_rx_sdu_rcvd;
BOOLEAN le_coc_rx_have_len;
BT_HDR *le_coc_tx_sdu;
UINT16 le_coc_tx_offset;
BOOLEAN le_coc_tx_len_sent;
BOOLEAN le_coc_xmit_busy; /* try_xmit re-entrancy guard */
BOOLEAN le_coc_xmit_rerun; /* re-entered: outer loop must re-run */
#endif
} tL2C_CCB;
/***********************************************************************
@@ -449,7 +474,9 @@ typedef struct t_l2c_linkcb {
tBLE_ADDR_TYPE open_addr_type; /* be set by open API */
tBLE_ADDR_TYPE ble_addr_type;
UINT16 tx_data_len; /* tx data length used in data length extension */
#if (BLE_L2CAP_COC_INCLUDED == TRUE)
fixed_queue_t *le_sec_pending_q; /* LE coc channels waiting for security check completion */
#endif
UINT8 sec_act;
#define L2C_BLE_CONN_UPDATE_DISABLE 0x1 /* disable update connection parameters */
#define L2C_BLE_NEW_CONN_PARAM 0x2 /* new connection parameter to be set */
@@ -720,6 +747,7 @@ extern tL2C_RCB *l2cu_find_rcb_by_psm (UINT16 psm);
extern void l2cu_release_rcb (tL2C_RCB *p_rcb);
extern tL2C_RCB *l2cu_allocate_ble_rcb (UINT16 psm);
extern tL2C_RCB *l2cu_find_ble_rcb_by_psm (UINT16 psm);
extern tL2C_RCB *l2cu_find_ble_rcb_by_real_psm (UINT16 real_psm);
#if (L2CAP_COC_INCLUDED == TRUE)
extern UINT8 l2cu_process_peer_cfg_req (tL2C_CCB *p_ccb, tL2CAP_CFG_INFO *p_cfg);
@@ -828,7 +856,84 @@ extern void l2cble_credit_based_conn_req (tL2C_CCB *p_ccb);
extern void l2cble_credit_based_conn_res (tL2C_CCB *p_ccb, UINT16 result);
extern void l2cble_send_peer_disc_req(tL2C_CCB *p_ccb);
extern void l2cble_send_flow_control_credit(tL2C_CCB *p_ccb, UINT16 credit_value);
#if (BLE_L2CAP_COC_INCLUDED == TRUE)
#if (SMP_INCLUDED == TRUE)
/* Defined in l2c_ble.c under (SMP_INCLUDED && BLE_L2CAP_COC_INCLUDED); the LE
* CoC/ECFC security check has no meaning without SMP, so callers guard their
* use with #if (SMP_INCLUDED == TRUE) and fall back to an immediate success. */
extern BOOLEAN l2ble_sec_access_req(BD_ADDR bd_addr, UINT16 psm, BOOLEAN is_originator, tL2CAP_SEC_CBACK *p_callback, void *p_ref_data);
extern void l2ble_sec_flush_pending_req(tL2C_LCB *p_lcb, void *p_ref_data);
#endif
extern BOOLEAN l2c_ble_le_coc_is_chan(tL2C_CCB *p_ccb);
/* Map a BTM security failure (tBTM_STATUS) to the matching LE CoC/ECFC L2CAP
* result code (0x0005-0x0008) so the peer learns the real reason (authorization
* / encryption) instead of always seeing "insufficient authentication". */
extern UINT16 l2c_ble_coc_sec_status_to_result(BD_ADDR bd_addr, tBTM_STATUS status);
#if (BLE_L2CAP_COC_CLIENT_INCLUDED == TRUE)
extern void l2c_ble_le_coc_connect_req(tL2C_CCB *p_ccb);
extern void l2c_ble_le_coc_handle_credit_conn_res(tL2C_LCB *p_lcb, UINT8 *p, UINT8 id, UINT16 cmd_len);
/* Fail a pending base LE CoC (0x14) client request whose sig id was CMD_REJECTed.
* Returns TRUE if a matching pending CCB was found and torn down. */
extern BOOLEAN l2c_ble_le_coc_abort_conn_req(tL2C_LCB *p_lcb, UINT8 id, UINT16 result);
#endif
#if (BLE_L2CAP_COC_SERVER_INCLUDED == TRUE)
extern void l2c_ble_le_coc_connect_rsp(tL2C_CCB *p_ccb, UINT16 result);
extern void l2c_ble_le_coc_handle_credit_conn_req(tL2C_LCB *p_lcb, UINT8 *p, UINT8 id, UINT16 cmd_len);
#endif
extern void l2c_ble_le_coc_on_link_up(tL2C_LCB *p_lcb);
extern void l2c_ble_le_coc_open_channel(tL2C_CCB *p_ccb, UINT16 result);
extern void l2c_ble_le_coc_cleanup_ccb(tL2C_CCB *p_ccb);
extern void l2c_ble_le_coc_apply_reconfig(tL2C_CCB *p_ccb, UINT16 new_mtu, UINT16 new_mps);
extern void l2c_ble_le_coc_handle_flow_ctrl_credit(tL2C_LCB *p_lcb, UINT8 *p, UINT16 cmd_len);
extern void l2c_ble_le_coc_handle_disc_req(tL2C_CCB *p_ccb, tL2C_LCB *p_lcb, UINT8 id, UINT16 lcid, UINT16 rcid);
extern void l2c_ble_le_coc_handle_disc_rsp(tL2C_LCB *p_lcb, UINT8 *p, UINT8 id, UINT16 cmd_len);
extern void l2c_ble_le_coc_data_ind(tL2C_CCB *p_ccb, BT_HDR *p_msg);
extern UINT8 l2c_ble_le_coc_data_write(UINT16 lcid, BT_HDR *p_data);
extern BOOLEAN l2c_ble_le_coc_is_congested(UINT16 lcid);
extern BOOLEAN l2c_ble_le_coc_give_credits(UINT16 lcid, UINT16 credits);
extern BOOLEAN l2c_ble_le_coc_set_auto_credit(UINT16 lcid, BOOLEAN enable);
#if (BLE_L2CAP_ENHANCED_COC_INCLUDED == TRUE)
extern void l2c_ble_le_coc_notify_reconfig(tL2C_CCB *p_ccb, UINT16 status, BOOLEAN peer_initiated);
#endif
extern BOOLEAN l2c_ble_le_coc_disconnect(UINT16 lcid);
/* Per-CCB signalling response timeout (BTU_TTYPE_L2CAP_CHNL on p_ccb->timer_entry):
* fires when a peer never answers a pending connect/reconfigure request. */
extern void l2c_ble_le_coc_channel_timeout(tL2C_CCB *p_ccb);
extern void l2c_ble_le_coc_start_rsp_timer(tL2C_CCB *p_ccb, UINT16 timeout_sec);
extern void l2c_ble_le_coc_stop_rsp_timer(tL2C_CCB *p_ccb);
#if (BLE_L2CAP_ENHANCED_COC_INCLUDED == TRUE)
#if (BLE_L2CAP_COC_SERVER_INCLUDED == TRUE)
extern void l2c_ble_ecfc_connect_rsp(tL2C_CCB *p_ccb, UINT16 result);
extern void l2c_ble_ecfc_handle_conn_req(tL2C_LCB *p_lcb, UINT8 *p, UINT8 id, UINT16 cmd_len);
#endif
#if (BLE_L2CAP_COC_CLIENT_INCLUDED == TRUE)
extern void l2c_ble_ecfc_handle_conn_res(tL2C_LCB *p_lcb, UINT8 *p, UINT8 id, UINT16 cmd_len);
extern void l2c_ble_ecfc_abort_cl_txn(tL2C_LCB *p_lcb, UINT8 sig_id, UINT16 result);
/* Abort the ECFC client connect transaction that owns p_ccb (0x18 timed out). */
extern BOOLEAN l2c_ble_ecfc_on_conn_timeout(tL2C_CCB *p_ccb);
#endif
/* Reconfiguration is available regardless of the client/server flag. */
extern void l2c_ble_ecfc_abort_reconfig_txn(tL2C_LCB *p_lcb, UINT8 sig_id);
/* Abort the ECFC reconfigure transaction that owns p_ccb (0x1A timed out). */
extern BOOLEAN l2c_ble_ecfc_on_reconfig_timeout(tL2C_CCB *p_ccb);
extern void l2c_ble_ecfc_handle_reconfig_req(tL2C_LCB *p_lcb, UINT8 *p, UINT8 id, UINT16 cmd_len);
extern void l2c_ble_ecfc_handle_reconfig_res(tL2C_LCB *p_lcb, UINT8 *p, UINT8 id, UINT16 cmd_len);
extern void l2c_ble_ecfc_on_ccb_release(tL2C_CCB *p_ccb);
#if (BLE_L2CAP_COC_CLIENT_INCLUDED == TRUE)
extern void l2c_ble_ecfc_on_link_up(tL2C_LCB *p_lcb);
#endif
extern BOOLEAN l2cu_send_peer_ble_enhanced_credit_conn_req(tL2C_LCB *p_lcb, UINT8 sig_id, UINT16 psm,
UINT16 mtu, UINT16 mps, UINT16 credits, UINT8 num_chan, UINT16 *p_scids);
extern void l2cu_send_peer_ble_enhanced_credit_conn_res(tL2C_LCB *p_lcb, UINT8 rem_id,
UINT16 mtu, UINT16 mps, UINT16 credits, UINT16 result, UINT8 num_chan, UINT16 *p_dcids);
extern void l2cu_reject_ble_enhanced_connection(tL2C_LCB *p_lcb, UINT8 rem_id, UINT16 result, UINT8 num_scids);
extern BOOLEAN l2cu_send_peer_ble_credit_reconfig_req(tL2C_LCB *p_lcb, UINT8 sig_id,
UINT16 mtu, UINT16 mps, UINT8 num_chan, UINT16 *p_dcids);
extern void l2cu_send_peer_ble_credit_reconfig_rsp(tL2C_LCB *p_lcb, UINT8 rem_id, UINT16 result);
#endif /* BLE_L2CAP_ENHANCED_COC_INCLUDED == TRUE */
#endif /* BLE_L2CAP_COC_INCLUDED == TRUE */
#if (defined BLE_LLT_INCLUDED) && (BLE_LLT_INCLUDED == TRUE)
@@ -37,6 +37,7 @@
#include "stack/btm_api.h"
#include "osi/allocator.h"
#include "gatt_int.h"
#include "device/controller.h"
#if (CLASSIC_BT_INCLUDED == TRUE)
/*******************************************************************************
**
@@ -1439,6 +1440,12 @@ void L2CA_DeregisterLECoc(UINT16 psm)
*******************************************************************************/
UINT16 L2CA_ConnectLECocReq(UINT16 psm, BD_ADDR p_bd_addr, tL2CAP_LE_CFG_INFO *p_cfg)
{
#if (BLE_L2CAP_COC_CLIENT_INCLUDED != TRUE)
UNUSED(psm);
UNUSED(p_bd_addr);
UNUSED(p_cfg);
return 0;
#else
L2CAP_TRACE_API("%s PSM: 0x%04x BDA: %02x:%02x:%02x:%02x:%02x:%02x", __func__, psm,
p_bd_addr[0], p_bd_addr[1], p_bd_addr[2], p_bd_addr[3], p_bd_addr[4], p_bd_addr[5]);
@@ -1449,6 +1456,17 @@ UINT16 L2CA_ConnectLECocReq(UINT16 psm, BD_ADDR p_bd_addr, tL2CAP_LE_CFG_INFO *p
return 0;
}
/* Bail out before allocating an LCB if the controller has no BLE support:
* l2cu_create_conn()'s !supports_ble() path returns FALSE WITHOUT releasing
* the LCB (it must not change its ownership contract), so allocating here and
* relying on that path would leak the LCB. Pre-check at the API entry as the
* function header of l2cu_create_conn recommends. */
if (!controller_get_interface()->supports_ble())
{
L2CAP_TRACE_WARNING("%s controller has no BLE support", __func__);
return 0;
}
/* Fail if the PSM is not registered */
tL2C_RCB *p_rcb = l2cu_find_ble_rcb_by_psm(psm);
if (p_rcb == NULL)
@@ -1483,6 +1501,13 @@ UINT16 L2CA_ConnectLECocReq(UINT16 psm, BD_ADDR p_bd_addr, tL2CAP_LE_CFG_INFO *p
/* Save registration info */
p_ccb->p_rcb = p_rcb;
p_ccb->le_coc_active = TRUE;
/* A pooled CCB reused from a released non-CoC channel keeps its stale
* remote_cid (l2cu_allocate_ccb does not clear it, and l2cu_release_ccb only
* runs cleanup_ccb for le_coc_active CCBs). Clear it now so the DCID dedup
* check in l2c_ble_le_coc_handle_credit_conn_res cannot false-match this
* channel-in-setup before its real remote_cid is assigned. */
p_ccb->remote_cid = 0;
/* Save the configuration */
if (p_cfg) {
@@ -1495,7 +1520,7 @@ UINT16 L2CA_ConnectLECocReq(UINT16 psm, BD_ADDR p_bd_addr, tL2CAP_LE_CFG_INFO *p
if (p_ccb->p_lcb->transport == BT_TRANSPORT_LE)
{
L2CAP_TRACE_DEBUG("%s LE Link is up", __func__);
l2c_csm_execute(p_ccb, L2CEVT_L2CA_CONNECT_REQ, NULL);
l2c_ble_le_coc_connect_req(p_ccb);
}
}
@@ -1517,6 +1542,7 @@ UINT16 L2CA_ConnectLECocReq(UINT16 psm, BD_ADDR p_bd_addr, tL2CAP_LE_CFG_INFO *p
/* Return the local CID as our handle */
return p_ccb->local_cid;
#endif /* BLE_L2CAP_COC_CLIENT_INCLUDED */
}
/*******************************************************************************
@@ -1533,6 +1559,16 @@ UINT16 L2CA_ConnectLECocReq(UINT16 psm, BD_ADDR p_bd_addr, tL2CAP_LE_CFG_INFO *p
BOOLEAN L2CA_ConnectLECocRsp (BD_ADDR p_bd_addr, UINT8 id, UINT16 lcid, UINT16 result,
UINT16 status, tL2CAP_LE_CFG_INFO *p_cfg)
{
#if (BLE_L2CAP_COC_SERVER_INCLUDED != TRUE)
UNUSED(p_bd_addr);
UNUSED(id);
UNUSED(lcid);
UNUSED(result);
UNUSED(status);
UNUSED(p_cfg);
return FALSE;
#else
UNUSED(status);
L2CAP_TRACE_API("%s CID: 0x%04x Result: %d Status: %d BDA: %02x:%02x:%02x:%02x:%02x:%02x",
__func__, lcid, result, status,
p_bd_addr[0], p_bd_addr[1], p_bd_addr[2], p_bd_addr[3], p_bd_addr[4], p_bd_addr[5]);
@@ -1566,18 +1602,77 @@ BOOLEAN L2CA_ConnectLECocRsp (BD_ADDR p_bd_addr, UINT8 id, UINT16 lcid, UINT16 r
memcpy(&p_ccb->local_conn_cfg, p_cfg, sizeof(tL2CAP_LE_CFG_INFO));
}
if (result == L2CAP_CONN_OK)
l2c_csm_execute (p_ccb, L2CEVT_L2CA_CONNECT_RSP, NULL);
else
{
tL2C_CONN_INFO conn_info;
memcpy(conn_info.bd_addr, p_bd_addr, BD_ADDR_LEN);
conn_info.l2cap_result = result;
conn_info.l2cap_status = status;
l2c_csm_execute(p_ccb, L2CEVT_L2CA_CONNECT_RSP_NEG, &conn_info);
#if (BLE_L2CAP_ENHANCED_COC_INCLUDED == TRUE)
if (p_ccb->le_ecfc_channel) {
/* Forward the caller's specific result so a security reject
* (0x0005-0x0008) reaches the peer intact (Core Spec v6.2 Vol 3 Part A
* 10.2 mandates the exact "insufficient authentication/encryption" code).
* l2c_ble_ecfc_connect_rsp records it for the aggregate 0x18 response. */
l2c_ble_ecfc_connect_rsp(p_ccb, result);
return TRUE;
}
#endif
/* Legacy single-channel LE CoC: forward the caller's specific result so the
* peer sees the real reject reason (mapped to a valid LE result code). */
l2c_ble_le_coc_connect_rsp(p_ccb, result);
return TRUE;
#endif /* BLE_L2CAP_COC_SERVER_INCLUDED */
}
/*******************************************************************************
**
** Function L2CA_LECocDataWrite
**
** Description Write an SDU on an LE CoC channel.
**
** Returns L2CAP_DW_SUCCESS, L2CAP_DW_CONGESTED, or L2CAP_DW_FAILED
**
*******************************************************************************/
UINT8 L2CA_LECocDataWrite(UINT16 lcid, BT_HDR *p_data)
{
L2CAP_TRACE_API("L2CA_LECocDataWrite() CID: 0x%04x", lcid);
return l2c_ble_le_coc_data_write(lcid, p_data);
}
BOOLEAN L2CA_LECocIsCongested(UINT16 lcid)
{
return l2c_ble_le_coc_is_congested(lcid);
}
/*******************************************************************************
**
** Function L2CA_LECocGiveCredits
**
** Description Return RX credits to peer after processing an SDU.
**
** Returns TRUE if credits were sent
**
*******************************************************************************/
BOOLEAN L2CA_LECocGiveCredits(UINT16 lcid, UINT16 credits)
{
L2CAP_TRACE_API("L2CA_LECocGiveCredits() CID: 0x%04x credits: %u", lcid, credits);
return l2c_ble_le_coc_give_credits(lcid, credits);
}
BOOLEAN L2CA_LECocSetAutoCredit(UINT16 lcid, BOOLEAN enable)
{
L2CAP_TRACE_API("L2CA_LECocSetAutoCredit() CID: 0x%04x enable=%u", lcid, enable);
return l2c_ble_le_coc_set_auto_credit(lcid, enable);
}
/*******************************************************************************
**
** Description Disconnect an LE CoC channel.
**
** Returns TRUE if disconnect request was sent
**
*******************************************************************************/
BOOLEAN L2CA_LECocDisconnect(UINT16 lcid)
{
L2CAP_TRACE_API("L2CA_LECocDisconnect() CID: 0x%04x", lcid);
return l2c_ble_le_coc_disconnect(lcid);
}
/*******************************************************************************
@@ -312,6 +312,9 @@ void l2cble_notify_le_connection (BD_ADDR bda)
/* update l2cap link status and send callback */
p_lcb->link_state = LST_CONNECTED;
l2cu_process_fixed_chnl_resp (p_lcb);
#if (BLE_L2CAP_COC_INCLUDED == TRUE)
l2c_ble_le_coc_on_link_up(p_lcb);
#endif
}
}
@@ -493,6 +496,9 @@ void l2cble_advertiser_conn_comp (UINT16 handle, BD_ADDR bda, tBLE_ADDR_TYPE typ
if (!HCI_LE_SLAVE_INIT_FEAT_EXC_SUPPORTED(controller_get_interface()->get_features_ble()->as_array)) {
p_lcb->link_state = LST_CONNECTED;
l2cu_process_fixed_chnl_resp (p_lcb);
#if (BLE_L2CAP_COC_INCLUDED == TRUE)
l2c_ble_le_coc_on_link_up(p_lcb);
#endif
}
/* when adv and initiating are both active, cancel the direct connection */
@@ -738,8 +744,55 @@ void l2cble_process_sig_cmd (tL2C_LCB *p_lcb, UINT8 *p, UINT16 pkt_len)
return;
}
#if (BLE_L2CAP_ENHANCED_COC_INCLUDED == TRUE)
if (cmd_code >= L2CAP_CMD_BLE_ENHANCED_CONN_REQ &&
cmd_code <= L2CAP_CMD_BLE_CREDIT_RECONFIG_RSP) {
L2CAP_TRACE_DEBUG("LE_ECFC sig rx cmd=0x%02x id=%u len=%u link_st=%u role=%u",
cmd_code, id, cmd_len, p_lcb->link_state, p_lcb->link_role);
}
#endif
switch (cmd_code) {
#if (BLE_L2CAP_ENHANCED_COC_INCLUDED == TRUE)
case L2CAP_CMD_REJECT: {
UINT16 rej_reason = 0;
if (cmd_len < 2) {
L2CAP_TRACE_WARNING ("L2CAP - LE - short cmd: %d", cmd_len);
return;
}
STREAM_TO_UINT16(rej_reason, p);
L2CAP_TRACE_DEBUG("LE_ECFC rx CMD_REJECT sig_id=%u reason=%u", id, rej_reason);
#if (BLE_L2CAP_COC_CLIENT_INCLUDED == TRUE)
/* Peer explicitly rejected the request: "no/unsupported PSM" is the
* closest generic reason to report to the application. A CMD_REJECT may
* answer either an ECFC (0x18) or a base LE CoC (0x14) client request, so
* try both aborts; each only acts on its own matching pending state. */
l2c_ble_ecfc_abort_cl_txn(p_lcb, id, L2CAP_CONN_NO_PSM);
l2c_ble_le_coc_abort_conn_req(p_lcb, id, L2CAP_CONN_NO_PSM);
#endif
/* Reconfiguration is compiled in regardless of the client/server flag,
* so a CMD_REJECT may be answering a pending reconfigure request. Abort
* it here too, otherwise its txn slot leaks (never freed). */
l2c_ble_ecfc_abort_reconfig_txn(p_lcb, id);
break;
}
#endif
#if (BLE_L2CAP_ENHANCED_COC_INCLUDED != TRUE)
case L2CAP_CMD_REJECT:
if (cmd_len < 2) {
L2CAP_TRACE_WARNING ("L2CAP - LE - short cmd: %d", cmd_len);
return;
}
L2CAP_TRACE_DEBUG("LE rx CMD_REJECT sig_id=%u", id);
#if (BLE_L2CAP_COC_CLIENT_INCLUDED == TRUE)
/* A CMD_REJECT may be answering a pending base LE CoC (0x14) client
* request; fail it now instead of waiting out the connect RTX timer. */
l2c_ble_le_coc_abort_conn_req(p_lcb, id, L2CAP_CONN_NO_PSM);
#endif
p += 2;
break;
#endif
case L2CAP_CMD_ECHO_RSP:
case L2CAP_CMD_INFO_RSP:
if (cmd_len < 2) {
@@ -816,8 +869,12 @@ void l2cble_process_sig_cmd (tL2C_LCB *p_lcb, UINT8 *p, UINT16 pkt_len)
break;
}
case L2CAP_CMD_BLE_CREDIT_BASED_CONN_REQ: {
#if (BLE_L2CAP_COC_SERVER_INCLUDED == TRUE)
l2c_ble_le_coc_handle_credit_conn_req(p_lcb, p, id, cmd_len);
#elif (BLE_L2CAP_COC_INCLUDED != TRUE)
if (cmd_len < 10) {
L2CAP_TRACE_WARNING ("L2CAP - LE - short cmd: %d", cmd_len);
l2cu_reject_ble_connection(p_lcb, id, L2CAP_LE_RESULT_UNACCEPTABLE_PARAMETERS);
return;
}
tL2C_CCB *p_ccb = NULL;
@@ -863,9 +920,25 @@ void l2cble_process_sig_cmd (tL2C_LCB *p_lcb, UINT8 *p, UINT16 pkt_len)
p_ccb->peer_conn_cfg.credits = credits;
l2cu_send_peer_ble_credit_based_conn_res(p_ccb, L2CAP_LE_RESULT_CONN_OK);
#else
if (cmd_len < 10) {
L2CAP_TRACE_WARNING ("L2CAP - LE - short cmd: %d", cmd_len);
l2cu_reject_ble_connection(p_lcb, id, L2CAP_LE_RESULT_UNACCEPTABLE_PARAMETERS);
return;
}
l2cu_reject_ble_connection(p_lcb, id, L2CAP_LE_RESULT_NO_RESOURCES);
#endif
break;
}
#if (BLE_L2CAP_COC_CLIENT_INCLUDED == TRUE)
case L2CAP_CMD_BLE_CREDIT_BASED_CONN_RES:
l2c_ble_le_coc_handle_credit_conn_res(p_lcb, p, id, cmd_len);
break;
#endif
case L2CAP_CMD_BLE_FLOW_CTRL_CREDIT: {
#if (BLE_L2CAP_COC_INCLUDED == TRUE)
l2c_ble_le_coc_handle_flow_ctrl_credit(p_lcb, p, cmd_len);
#else
if (cmd_len < L2CAP_CMD_BLE_FLOW_CTRL_CREDIT_LEN) {
L2CAP_TRACE_WARNING ("L2CAP - LE - flow ctrl credit too short: %d", cmd_len);
return;
@@ -891,6 +964,7 @@ void l2cble_process_sig_cmd (tL2C_LCB *p_lcb, UINT8 *p, UINT16 pkt_len)
p_ccb->peer_conn_cfg.credits, lcid);
l2c_link_check_send_pkts(p_ccb->p_lcb, NULL, NULL);
}
#endif
break;
}
case L2CAP_CMD_DISC_REQ: {
@@ -905,6 +979,12 @@ void l2cble_process_sig_cmd (tL2C_LCB *p_lcb, UINT8 *p, UINT16 pkt_len)
STREAM_TO_UINT16(rcid, p);
p_ccb = l2cu_find_ccb_by_cid(p_lcb, lcid);
#if (BLE_L2CAP_COC_INCLUDED == TRUE)
if (p_ccb && p_ccb->le_coc_active) {
l2c_ble_le_coc_handle_disc_req(p_ccb, p_lcb, id, lcid, rcid);
break;
}
#endif
if (p_ccb) {
p_ccb->remote_id = id;
l2cu_send_peer_disc_rsp(p_lcb, id, lcid, rcid);
@@ -914,6 +994,57 @@ void l2cble_process_sig_cmd (tL2C_LCB *p_lcb, UINT8 *p, UINT16 pkt_len)
}
break;
}
#if (BLE_L2CAP_COC_INCLUDED == TRUE)
case L2CAP_CMD_DISC_RSP:
l2c_ble_le_coc_handle_disc_rsp(p_lcb, p, id, cmd_len);
break;
#endif
#if (BLE_L2CAP_ENHANCED_COC_INCLUDED == TRUE)
#if (BLE_L2CAP_COC_SERVER_INCLUDED == TRUE)
case L2CAP_CMD_BLE_ENHANCED_CONN_REQ:
l2c_ble_ecfc_handle_conn_req(p_lcb, p, id, cmd_len);
break;
#else
case L2CAP_CMD_BLE_ENHANCED_CONN_REQ: {
/* ECFC compiled without a server role (e.g. GATTS disabled): we still
* understand the ECFC command set (RECONFIG_REQ/RSP are handled below),
* so reply with a proper all-refused ECFC connection response instead of
* a CMD_REJECT "not understood". Mirrors the 0x14 #else path above. */
if (cmd_len >= L2CAP_CMD_BLE_ENHANCED_CONN_REQ_BASE_LEN + sizeof(UINT16)) {
UINT16 n_scids = (UINT16)((cmd_len - L2CAP_CMD_BLE_ENHANCED_CONN_REQ_BASE_LEN) / sizeof(UINT16));
/* The reject must carry one DCID per requested SCID (Core Spec v6.2
* Vol 3 Part A 4.26: 1:1 positional mapping); do NOT clamp to the
* local channel budget as that desyncs the DCID count. Cap at 255
* only to fit the UINT8 API argument. Mirror the server path
* (l2c_ble_ecfc_handle_conn_req): >5 SCIDs is malformed
* (INVALID_PARAMETERS), otherwise a plain resource refusal. */
UINT8 reject_scids = (n_scids > 255) ? 255 : (UINT8)n_scids;
UINT16 reason = (n_scids > 5) ? L2CAP_LE_RESULT_INVALID_PARAMETERS
: L2CAP_LE_RESULT_NO_RESOURCES;
l2cu_reject_ble_enhanced_connection(p_lcb, id, reason, reject_scids);
} else {
/* Too short to parse the SCID list, but the peer still expects a
* response; mirror the server path (l2c_ble_ecfc_handle_conn_req) and
* reject with n_scids=1 so the peer does not hang until its signalling
* timer expires. */
L2CAP_TRACE_WARNING("L2CAP - LE - short ECFC conn req: %d", cmd_len);
l2cu_reject_ble_enhanced_connection(p_lcb, id, L2CAP_LE_RESULT_INVALID_PARAMETERS, 1);
}
break;
}
#endif
#if (BLE_L2CAP_COC_CLIENT_INCLUDED == TRUE)
case L2CAP_CMD_BLE_ENHANCED_CONN_RES:
l2c_ble_ecfc_handle_conn_res(p_lcb, p, id, cmd_len);
break;
#endif
case L2CAP_CMD_BLE_CREDIT_RECONFIG_REQ:
l2c_ble_ecfc_handle_reconfig_req(p_lcb, p, id, cmd_len);
break;
case L2CAP_CMD_BLE_CREDIT_RECONFIG_RSP:
l2c_ble_ecfc_handle_reconfig_res(p_lcb, p, id, cmd_len);
break;
#endif
default:
L2CAP_TRACE_WARNING ("L2CAP - LE - unknown cmd code: %d", cmd_code);
l2cu_send_peer_cmd_reject (p_lcb, L2CAP_CMD_REJ_NOT_UNDERSTOOD, id, 0, 0);
@@ -952,6 +1083,10 @@ BOOLEAN l2cble_init_direct_conn (tL2C_LCB *p_lcb)
/* There can be only one BLE connection request outstanding at a time */
if (p_dev_rec == NULL) {
L2CAP_TRACE_WARNING ("unknown device, can not initiate connection");
/* The caller allocated this LCB and expects this function to release it
* on failure (as the other error paths do); free it to avoid leaking the
* LCB and its queues / num_ble_links_active count. */
l2cu_release_lcb (p_lcb);
return (FALSE);
}
@@ -1675,7 +1810,43 @@ void l2cble_send_peer_disc_req(tL2C_CCB *p_ccb)
return;
}
#if (SMP_INCLUDED == TRUE)
#if (BLE_L2CAP_COC_INCLUDED == TRUE)
/*******************************************************************************
**
** Function l2c_ble_coc_sec_status_to_result
**
** Description Translate a BTM security failure into the LE CoC/ECFC L2CAP
** result code that best matches it, so a rejected peer learns
** the real reason instead of always "insufficient
** authentication" (Core Spec v6.2 Vol 3 Part A 4.26/10.2 make
** 0x0005-0x0008 mandatory per failure type).
**
** Returns One of L2CAP_LE_RESULT_INSUFFICIENT_* (0x0005-0x0008)
**
*******************************************************************************/
UINT16 l2c_ble_coc_sec_status_to_result(BD_ADDR bd_addr, tBTM_STATUS status)
{
UINT8 sec_flags = 0;
if (status == BTM_NOT_AUTHORIZED) {
return L2CAP_LE_RESULT_INSUFFICIENT_AUTHORIZATION; /* 0x0006 */
}
/* If the link is not encrypted, tell the peer to encrypt (0x0008) rather
* than re-authenticate; only fall back to insufficient authentication
* (0x0005) when encryption is present but the required level was not met.
* Key-size (0x0007) needs the actual key length, which the flags API does
* not expose, so it is intentionally not distinguished here. */
if (BTM_GetSecurityFlagsByTransport(bd_addr, &sec_flags, BT_TRANSPORT_LE) &&
!(sec_flags & BTM_SEC_FLAG_ENCRYPTED)) {
return L2CAP_LE_RESULT_INSUFFICIENT_ENCRY; /* 0x0008 */
}
return L2CAP_LE_RESULT_INSUFFICIENT_AUTHENTICATION; /* 0x0005 */
}
#endif /* BLE_L2CAP_COC_INCLUDED == TRUE */
#if (SMP_INCLUDED == TRUE) && (BLE_L2CAP_COC_INCLUDED == TRUE)
/*******************************************************************************
**
** Function l2cble_sec_comp
@@ -1762,6 +1933,53 @@ void l2cble_sec_comp(BD_ADDR p_bda, tBT_TRANSPORT transport, void *p_ref_data,
}
}
/*******************************************************************************
**
** Function l2ble_sec_flush_pending_req
**
** Description Drop any queued LE security requests whose p_ref_data matches
** |p_ref_data| (typically a CCB being released). Without this,
** l2cble_sec_comp() would later invoke the stored callback with
** a dangling or reused pointer once SMP completes.
**
** Returns void
**
*******************************************************************************/
void l2ble_sec_flush_pending_req(tL2C_LCB *p_lcb, void *p_ref_data)
{
if (p_lcb == NULL || p_lcb->le_sec_pending_q == NULL || p_ref_data == NULL) {
return;
}
/* Removing mutates the underlying list, so re-scan from the head after each
* hit until no queued request references p_ref_data anymore. */
for (;;) {
list_t *list = fixed_queue_get_list(p_lcb->le_sec_pending_q);
tL2CAP_SEC_DATA *match = NULL;
list_node_t *node;
for (node = list_begin(list); node != list_end(list); node = list_next(node)) {
tL2CAP_SEC_DATA *p_buf = (tL2CAP_SEC_DATA *)list_node(node);
if (p_buf != NULL && p_buf->p_ref_data == p_ref_data) {
match = p_buf;
break;
}
}
if (match == NULL) {
break;
}
/* Only free once the node is actually detached. If removal fails (item
* gone / could not acquire the dequeue semaphore), freeing it here would
* leave a dangling node in the list, so the next scan would dereference
* freed memory (use-after-free) and could loop forever. Abort instead. */
if (fixed_queue_try_remove_from_queue(p_lcb->le_sec_pending_q, match) != NULL) {
osi_free(match);
} else {
break;
}
}
}
/*******************************************************************************
**
** Function l2ble_sec_access_req
@@ -1810,7 +2028,7 @@ BOOLEAN l2ble_sec_access_req(BD_ADDR bd_addr, UINT16 psm, BOOLEAN is_originator,
return status;
}
#endif /* #if (SMP_INCLUDED == TRUE) */
#endif /* (SMP_INCLUDED == TRUE) && (BLE_L2CAP_COC_INCLUDED == TRUE) */
#endif /* (BLE_INCLUDED == TRUE) */
/*******************************************************************************
**
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -478,6 +478,7 @@ BOOLEAN l2c_link_hci_disc_comp (UINT16 handle, UINT8 reason)
while (!list_is_empty(p_lcb->link_xmit_data_q)) {
p_buf = list_front(p_lcb->link_xmit_data_q);
list_remove(p_lcb->link_xmit_data_q, p_buf);
p_buf->event = 0;
osi_free(p_buf);
}
} else
@@ -1629,6 +1630,11 @@ void l2c_link_segments_xmitted (BT_HDR *p_msg)
/* Find the LCB based on the handle */
if ((p_lcb = l2cu_find_lcb_by_handle (handle)) == NULL) {
L2CAP_TRACE_WARNING ("L2CAP - rcvd segment complete, unknown handle: %d\n", handle);
/* The partial segment being bounced back here was already removed from
* link_xmit_data_q before it was handed to the controller, so it is not
* freed by l2cu_release_lcb()/disc_comp when the link goes away. This
* function is its sole owner, so it must be freed here to avoid a leak. */
p_msg->event = 0;
osi_free (p_msg);
return;
}
@@ -311,6 +311,13 @@ void l2c_rcv_acl_data (BT_HDR *p_msg)
if (p_ccb == NULL) {
osi_free (p_msg);
} else {
#if (BLE_L2CAP_COC_INCLUDED == TRUE)
/* LE CoC data plane only; BR/EDR dynamic channels use l2c_csm / l2c_fcr below */
if (p_lcb->transport == BT_TRANSPORT_LE && l2c_ble_le_coc_is_chan(p_ccb)) {
l2c_ble_le_coc_data_ind(p_ccb, p_msg);
return;
}
#endif
if (p_lcb->transport == BT_TRANSPORT_LE) {
l2c_link_check_send_pkts (p_ccb->p_lcb, NULL, NULL);
}
@@ -1147,11 +1154,41 @@ void l2c_process_timeout (TIMER_LIST_ENT *p_tle)
* re-issue the connection attempt now. */
l2c_link_create_conn_retry ((tL2C_LCB *)p_tle->param);
break;
#endif ///CLASSIC_BT_INCLUDED == TRUE
case BTU_TTYPE_L2CAP_CHNL:
l2c_csm_execute (((tL2C_CCB *)p_tle->param), L2CEVT_TIMEOUT, NULL);
case BTU_TTYPE_L2CAP_CHNL: {
#if (BLE_L2CAP_COC_INCLUDED == TRUE)
tL2C_CCB *p_ccb = (tL2C_CCB *)p_tle->param;
/* LE CoC/ECFC channels do not use the classic state machine; a per-CCB
* BTU_TTYPE_L2CAP_CHNL timer is their connect/reconfigure response
* timeout. Route it to the CoC handler. */
if (p_ccb != NULL && p_ccb->le_coc_active) {
l2c_ble_le_coc_channel_timeout(p_ccb);
break;
}
/* Keep the NULL handling consistent with the CoC check above: the classic
* state machine dereferences p_ccb unconditionally, so bail out here
* instead of passing a NULL CCB down to l2c_csm_execute. */
if (p_ccb == NULL) {
L2CAP_TRACE_WARNING("L2CAP channel timeout with NULL CCB");
break;
}
#if (CLASSIC_BT_INCLUDED == TRUE)
l2c_csm_execute (p_ccb, L2CEVT_TIMEOUT, NULL);
#else
/* p_ccb may be unused when BT_STACK_NO_LOG strips the trace macro. */
L2CAP_TRACE_WARNING("Unhandled L2CAP channel timeout for CCB %p", p_ccb);
UNUSED(p_ccb);
#endif
#elif (CLASSIC_BT_INCLUDED == TRUE)
l2c_csm_execute ((tL2C_CCB *)p_tle->param, L2CEVT_TIMEOUT, NULL);
#else
L2CAP_TRACE_WARNING("Unhandled L2CAP channel timeout");
#endif
break;
}
#if (CLASSIC_BT_INCLUDED == TRUE)
case BTU_TTYPE_L2CAP_FCR_ACK:
l2c_csm_execute (((tL2C_CCB *)p_tle->param), L2CEVT_ACK_TIMEOUT, NULL);
break;
@@ -108,7 +108,9 @@ tL2C_LCB *l2cu_allocate_lcb (BD_ADDR p_bd_addr, BOOLEAN is_bonding, tBT_TRANSPOR
#if (BLE_INCLUDED == TRUE)
p_lcb->transport = transport;
p_lcb->tx_data_len = controller_get_interface()->get_ble_default_data_packet_length();
#if (BLE_L2CAP_COC_INCLUDED == TRUE)
p_lcb->le_sec_pending_q = fixed_queue_new(QUEUE_SIZE_MAX);
#endif
if (transport == BT_TRANSPORT_LE) {
l2cb.num_ble_links_active++;
@@ -164,6 +166,16 @@ void l2cu_release_lcb (tL2C_LCB *p_lcb)
{
tL2C_CCB *p_ccb;
/* Make double-release harmless. Several failure paths (e.g.
* l2cble_init_direct_conn) release the LCB and return FALSE, after which the
* API-level caller (e.g. L2CA_ConnectFixedChnl) releases it again. Without
* this guard the second call would wrongly decrement num_ble_links_active
* and re-run l2cu_process_fixed_disc_cback on an already freed LCB. A valid
* LCB always has in_use == TRUE (set in l2cu_allocate_lcb). */
if (p_lcb == NULL || !p_lcb->in_use) {
return;
}
L2CAP_TRACE_DEBUG("%s handle=%u bda="MACSTR"",
__func__, p_lcb->handle, MAC2STR(p_lcb->remote_bd_addr));
@@ -253,6 +265,7 @@ void l2cu_release_lcb (tL2C_LCB *p_lcb)
while (!list_is_empty(p_lcb->link_xmit_data_q)) {
BT_HDR *p_buf = list_front(p_lcb->link_xmit_data_q);
list_remove(p_lcb->link_xmit_data_q, p_buf);
p_buf->event = 0;
osi_free(p_buf);
}
list_free(p_lcb->link_xmit_data_q);
@@ -294,7 +307,7 @@ void l2cu_release_lcb (tL2C_LCB *p_lcb)
(*p_cb) (L2CAP_PING_RESULT_NO_LINK);
}
#if (BLE_INCLUDED == TRUE)
#if (BLE_INCLUDED == TRUE) && (BLE_L2CAP_COC_INCLUDED == TRUE)
/* Check and release all the LE COC connections waiting for security */
if (p_lcb->le_sec_pending_q)
{
@@ -1721,8 +1734,18 @@ void l2cu_release_ccb (tL2C_CCB *p_ccb)
if (!p_ccb->in_use) {
return;
}
#if (BLE_L2CAP_ENHANCED_COC_INCLUDED == TRUE)
if (p_lcb != NULL && p_lcb->transport == BT_TRANSPORT_LE) {
l2c_ble_ecfc_on_ccb_release(p_ccb);
}
#endif
#if (BLE_L2CAP_COC_INCLUDED == TRUE)
if (p_lcb != NULL && p_lcb->transport == BT_TRANSPORT_LE && p_ccb->le_coc_active) {
l2c_ble_le_coc_cleanup_ccb(p_ccb);
}
#endif
#if BLE_INCLUDED == TRUE
if (p_lcb->transport == BT_TRANSPORT_LE) {
if (p_lcb != NULL && p_lcb->transport == BT_TRANSPORT_LE) {
/* Take samephore to avoid race condition */
l2ble_update_att_acl_pkt_num(L2CA_BUFF_FREE, NULL);
}
@@ -1995,6 +2018,32 @@ tL2C_RCB *l2cu_find_ble_rcb_by_psm (UINT16 psm)
/* If here, no match found */
return (NULL);
}
/*******************************************************************************
**
** Function l2cu_find_ble_rcb_by_real_psm
**
** Description Look through the BLE Registration Control Blocks to see if
** anyone registered to handle the application PSM in question
**
** Returns Pointer to the BLE RCB or NULL if not found
**
*******************************************************************************/
tL2C_RCB *l2cu_find_ble_rcb_by_real_psm (UINT16 real_psm)
{
tL2C_RCB *p_rcb = &l2cb.ble_rcb_pool[0];
UINT16 xx;
for (xx = 0; xx < BLE_MAX_L2CAP_CLIENTS; xx++, p_rcb++)
{
if ((p_rcb->in_use) && (p_rcb->real_psm == real_psm)) {
return (p_rcb);
}
}
/* If here, no match found */
return (NULL);
}
#endif ///BLE_INCLUDED == TRUE
#if (L2CAP_COC_INCLUDED == TRUE)
@@ -2306,6 +2355,32 @@ void l2cu_device_reset (void)
**
** Returns TRUE if successful, FALSE if gki get buffer fails.
**
** LCB OWNERSHIP ON FAILURE - READ BEFORE "FIXING" A LEAK HERE:
** The release contract of this function is deliberately NOT uniform, and the
** callers rely on the current behaviour. Do NOT add an unconditional
** l2cu_release_lcb(p_lcb) around the FALSE returns below - it causes a
** use-after-free + double free (see l2c_link_hci_disc_comp).
**
** Per-path behaviour on a FALSE return:
** - BLE connect path (l2cble_create_conn -> l2cble_init_direct_conn) and the
** classic l2cu_create_conn_after_switch RELEASE p_lcb internally on their
** own failures. Callers must therefore NOT release again on those paths.
** - The "!supports_ble()" and the trailing "return false" paths do NOT
** release p_lcb (kept as-is on purpose).
**
** Caller expectations (all currently satisfied by the above):
** - l2c_link_hci_disc_comp() keeps using p_lcb after a FALSE return and
** releases it itself at the end via lcb_is_free (see the explicit
** "must not release the LCB on failure" note there). Releasing internally
** would UAF/double-free this hot disconnect+reconnect path.
** - L2CA_ConnectFixedChnl() releases p_lcb itself on FALSE.
** - The LE CoC/ECFC callers (L2CA_ConnectLECocReq / L2CA_ConnectLEEcocReq)
** do NOT release on FALSE; they rely on the BLE path having released. The
** only genuine leak is the (practically unreachable) !supports_ble() path
** for those callers - if that must be closed, do it at the CoC API entry
** (pre-check supports_ble and release the freshly-allocated LCB there),
** not by changing the contract of this function.
**
*******************************************************************************/
BOOLEAN l2cu_create_conn (tL2C_LCB *p_lcb, tBT_TRANSPORT transport)
{
@@ -2327,6 +2402,9 @@ BOOLEAN l2cu_create_conn (tL2C_LCB *p_lcb, tBT_TRANSPORT transport)
if (transport == BT_TRANSPORT_LE) {
if (!controller_get_interface()->supports_ble()) {
/* Intentionally does NOT release p_lcb (see the ownership note in the
* function header). Practically unreachable for LE callers; close the
* CoC leak at the API entry, not here. */
return FALSE;
}
if(addr_type > BLE_ADDR_TYPE_MAX) {
@@ -2384,6 +2462,9 @@ BOOLEAN l2cu_create_conn (tL2C_LCB *p_lcb, tBT_TRANSPORT transport)
return (l2cu_create_conn_after_switch (p_lcb));
#endif // (CLASSIC_BT_INCLUDED == TRUE)
/* Fallthrough only in a BLE-only build reached with a non-LE transport
* (effectively dead). Intentionally does NOT release p_lcb - see the
* ownership note in the function header. */
return false;
}
@@ -3270,6 +3351,128 @@ void l2cu_send_peer_ble_credit_based_disconn_req(tL2C_CCB *p_ccb)
l2c_link_check_send_pkts (p_lcb, NULL, p_buf);
}
#if (BLE_L2CAP_ENHANCED_COC_INCLUDED == TRUE)
BOOLEAN l2cu_send_peer_ble_enhanced_credit_conn_req(tL2C_LCB *p_lcb, UINT8 sig_id, UINT16 psm,
UINT16 mtu, UINT16 mps, UINT16 credits, UINT8 num_chan, UINT16 *p_scids)
{
BT_HDR *p_buf;
UINT8 *p;
UINT16 len = L2CAP_CMD_BLE_ENHANCED_CONN_REQ_BASE_LEN + num_chan * sizeof(UINT16);
if (p_lcb == NULL || p_scids == NULL || num_chan == 0) {
return FALSE;
}
if ((p_buf = l2cu_build_header(p_lcb, len, L2CAP_CMD_BLE_ENHANCED_CONN_REQ, sig_id)) == NULL) {
L2CAP_TRACE_WARNING("LE_ECFC tx 0x17 build_header failed sig_id=%u", sig_id);
return FALSE;
}
p = (UINT8 *)(p_buf + 1) + L2CAP_SEND_CMD_OFFSET + HCI_DATA_PREAMBLE_SIZE +
L2CAP_PKT_OVERHEAD + L2CAP_CMD_OVERHEAD;
UINT16_TO_STREAM(p, psm);
UINT16_TO_STREAM(p, mtu);
UINT16_TO_STREAM(p, mps);
UINT16_TO_STREAM(p, credits);
for (UINT8 i = 0; i < num_chan; i++) {
UINT16_TO_STREAM(p, p_scids[i]);
}
l2c_link_check_send_pkts(p_lcb, NULL, p_buf);
return TRUE;
}
void l2cu_send_peer_ble_enhanced_credit_conn_res(tL2C_LCB *p_lcb, UINT8 rem_id,
UINT16 mtu, UINT16 mps, UINT16 credits, UINT16 result, UINT8 num_chan, UINT16 *p_dcids)
{
BT_HDR *p_buf;
UINT8 *p;
UINT16 len = L2CAP_CMD_BLE_ENHANCED_CONN_RES_BASE_LEN + num_chan * sizeof(UINT16);
if (p_lcb == NULL) {
return;
}
if ((p_buf = l2cu_build_header(p_lcb, len, L2CAP_CMD_BLE_ENHANCED_CONN_RES, rem_id)) == NULL) {
L2CAP_TRACE_WARNING("LE_ECFC tx 0x18 build_header failed rem_id=%u", rem_id);
return;
}
p = (UINT8 *)(p_buf + 1) + L2CAP_SEND_CMD_OFFSET + HCI_DATA_PREAMBLE_SIZE +
L2CAP_PKT_OVERHEAD + L2CAP_CMD_OVERHEAD;
UINT16_TO_STREAM(p, mtu);
UINT16_TO_STREAM(p, mps);
UINT16_TO_STREAM(p, credits);
UINT16_TO_STREAM(p, result);
for (UINT8 i = 0; i < num_chan; i++) {
UINT16 dcid = (p_dcids != NULL) ? p_dcids[i] : 0;
UINT16_TO_STREAM(p, dcid);
}
l2c_link_check_send_pkts(p_lcb, NULL, p_buf);
}
void l2cu_reject_ble_enhanced_connection(tL2C_LCB *p_lcb, UINT8 rem_id, UINT16 result, UINT8 num_scids)
{
if (num_scids == 0) {
num_scids = 1;
}
l2cu_send_peer_ble_enhanced_credit_conn_res(p_lcb, rem_id, 0, 0, 0, result, num_scids, NULL);
}
BOOLEAN l2cu_send_peer_ble_credit_reconfig_req(tL2C_LCB *p_lcb, UINT8 sig_id,
UINT16 mtu, UINT16 mps, UINT8 num_chan, UINT16 *p_dcids)
{
BT_HDR *p_buf;
UINT8 *p;
UINT16 len = L2CAP_CMD_BLE_CREDIT_RECONFIG_REQ_BASE_LEN + num_chan * sizeof(UINT16);
if (p_lcb == NULL || p_dcids == NULL || num_chan == 0) {
return FALSE;
}
if ((p_buf = l2cu_build_header(p_lcb, len, L2CAP_CMD_BLE_CREDIT_RECONFIG_REQ, sig_id)) == NULL) {
L2CAP_TRACE_WARNING("LE_ECFC tx 0x19 build_header failed sig_id=%u", sig_id);
return FALSE;
}
p = (UINT8 *)(p_buf + 1) + L2CAP_SEND_CMD_OFFSET + HCI_DATA_PREAMBLE_SIZE +
L2CAP_PKT_OVERHEAD + L2CAP_CMD_OVERHEAD;
UINT16_TO_STREAM(p, mtu);
UINT16_TO_STREAM(p, mps);
for (UINT8 i = 0; i < num_chan; i++) {
UINT16_TO_STREAM(p, p_dcids[i]);
}
l2c_link_check_send_pkts(p_lcb, NULL, p_buf);
return TRUE;
}
void l2cu_send_peer_ble_credit_reconfig_rsp(tL2C_LCB *p_lcb, UINT8 rem_id, UINT16 result)
{
BT_HDR *p_buf;
UINT8 *p;
if (p_lcb == NULL) {
return;
}
if ((p_buf = l2cu_build_header(p_lcb, L2CAP_CMD_BLE_CREDIT_RECONFIG_RSP_LEN,
L2CAP_CMD_BLE_CREDIT_RECONFIG_RSP, rem_id)) == NULL) {
return;
}
p = (UINT8 *)(p_buf + 1) + L2CAP_SEND_CMD_OFFSET + HCI_DATA_PREAMBLE_SIZE +
L2CAP_PKT_OVERHEAD + L2CAP_CMD_OVERHEAD;
UINT16_TO_STREAM(p, result);
l2c_link_check_send_pkts(p_lcb, NULL, p_buf);
}
#endif /* BLE_L2CAP_ENHANCED_COC_INCLUDED == TRUE */
#endif /* BLE_INCLUDED == TRUE */
/*******************************************************************************