diff --git a/components/bt/common/btc/core/btc_task.c b/components/bt/common/btc/core/btc_task.c index 45290df032a..097473a26e5 100644 --- a/components/bt/common/btc/core/btc_task.c +++ b/components/bt/common/btc/core/btc_task.c @@ -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 @@ -273,6 +276,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) }; /***************************************************************************** diff --git a/components/bt/common/btc/include/btc/btc_task.h b/components/bt/common/btc/include/btc/btc_task.h index 219b727d370..64989c2c8ea 100644 --- a/components/bt/common/btc/include/btc/btc_task.h +++ b/components/bt/common/btc/include/btc/btc_task.h @@ -117,6 +117,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 diff --git a/components/bt/host/bluedroid/CMakeLists.txt b/components/bt/host/bluedroid/CMakeLists.txt index f57f649b16c..a578bddb26c 100644 --- a/components/bt/host/bluedroid/CMakeLists.txt +++ b/components/bt/host/bluedroid/CMakeLists.txt @@ -317,6 +317,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" @@ -325,6 +345,12 @@ if(CONFIG_BT_BLE_FEAT_CTE_EN) ) endif() +if(CONFIG_BT_BLE_PERIPH_PSEUDO_ADDR_BOND) + list(APPEND bluedroid_host_srcs + "${CMAKE_CURRENT_LIST_DIR}/stack/btm/btm_ble_pseudo.c" + ) +endif() + # TODO: Added this file in the ble mesh cmake file if(CONFIG_BLE_MESH) list(APPEND bluedroid_host_srcs "${CMAKE_CURRENT_LIST_DIR}/../../esp_ble_mesh/core/bluedroid_host/adapter.c") diff --git a/components/bt/host/bluedroid/Kconfig.in b/components/bt/host/bluedroid/Kconfig.in index 176d238b92e..bd9dd3c2da4 100644 --- a/components/bt/host/bluedroid/Kconfig.in +++ b/components/bt/host/bluedroid/Kconfig.in @@ -347,6 +347,24 @@ config BT_BLE_SMP_BOND_NVS_FLASH help This select can save SMP bonding keys to nvs flash +config BT_BLE_PERIPH_PSEUDO_ADDR_BOND + bool "Peripheral dual local-identity bond isolation (pseudo address)" + depends on BT_BLE_SMP_ENABLE && BT_BLE_50_EXTEND_ADV_EN + default n + help + Enable Host-internal pseudo address derivation so that one peer phone + connecting through two distinct local identities (e.g. Public and a + fixed Static Random advertising set) is treated as two independent + peers. Each connection gets its own device record, LTK and NVS bond + section keyed by pseudo = f(local_identity, peer). The over-the-air + and SMP cryptography keep using the real peer and the real local + identity; the pseudo address never leaves the Host. + + This is intended for BLE 5.0 Extended Advertising peripherals that need + simultaneous dual-identity connections with isolated bonds. Requires + BT_BLE_50_EXTEND_ADV_EN. When disabled (default) the stack behaves + exactly as before. + config BT_BLE_RPA_SUPPORTED bool "Update RPA to Controller" depends on (BT_BLE_SMP_ENABLE && ((BT_CONTROLLER_ENABLED && !SOC_BLE_DEVICE_PRIVACY_SUPPORTED) || BT_CONTROLLER_DISABLED)) # NOERROR @@ -1572,6 +1590,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 diff --git a/components/bt/host/bluedroid/api/esp_ble_l2cap_api.c b/components/bt/host/bluedroid/api/esp_ble_l2cap_api.c new file mode 100644 index 00000000000..250b5ef5748 --- /dev/null +++ b/components/bt/host/bluedroid/api/esp_ble_l2cap_api.c @@ -0,0 +1,222 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#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 */ diff --git a/components/bt/host/bluedroid/api/esp_gap_ble_api.c b/components/bt/host/bluedroid/api/esp_gap_ble_api.c index 81a8069848b..762cf2cb02d 100644 --- a/components/bt/host/bluedroid/api/esp_gap_ble_api.c +++ b/components/bt/host/bluedroid/api/esp_gap_ble_api.c @@ -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 @@ -512,6 +516,59 @@ esp_err_t esp_ble_gap_get_local_used_addr(esp_bd_addr_t local_used_addr, uint8_t } return ESP_OK; } + +#if (CONFIG_BT_BLE_PERIPH_PSEUDO_ADDR_BOND) +esp_err_t esp_ble_gap_get_real_peer_addr(esp_bd_addr_t pseudo, esp_bd_addr_t real_peer) +{ + if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) { + LOG_ERROR("%s, bluedroid status error", __func__); + return ESP_FAIL; + } + if (pseudo == NULL || real_peer == NULL) { + return ESP_ERR_INVALID_ARG; + } + if (!BTM_BleGetRealPeerByPseudo(pseudo, real_peer)) { + return ESP_FAIL; + } + return ESP_OK; +} + +esp_err_t esp_ble_gap_get_conn_identity(esp_bd_addr_t pseudo, esp_ble_conn_identity_t *identity) +{ + if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) { + LOG_ERROR("%s, bluedroid status error", __func__); + return ESP_FAIL; + } + if (pseudo == NULL || identity == NULL) { + return ESP_ERR_INVALID_ARG; + } + UINT8 peer_type = 0, local_type = 0; + if (!BTM_BleGetConnIdentityByPseudo(pseudo, identity->peer_addr, identity->local_addr, + &peer_type, &local_type)) { + return ESP_FAIL; + } + identity->peer_addr_type = peer_type; + identity->local_addr_type = local_type; + return ESP_OK; +} + +esp_err_t esp_ble_gap_remove_bond_for_identity(esp_bd_addr_t local_addr, + esp_ble_addr_type_t local_addr_type, + esp_bd_addr_t peer_addr, + esp_ble_addr_type_t peer_addr_type) +{ + if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) { + LOG_ERROR("%s, bluedroid status error", __func__); + return ESP_FAIL; + } + if (local_addr == NULL || peer_addr == NULL) { + return ESP_ERR_INVALID_ARG; + } + esp_bd_addr_t pseudo; + BTM_BleComputePseudoForIdentity(local_addr, local_addr_type, peer_addr, peer_addr_type, pseudo); + return esp_ble_remove_bond_device(pseudo); +} +#endif // CONFIG_BT_BLE_PERIPH_PSEUDO_ADDR_BOND #if ((BLE_42_SCAN_EN == TRUE) || (BLE_50_EXTEND_SCAN_EN == TRUE)) uint8_t *esp_ble_resolve_adv_data_by_type( uint8_t *adv_data, uint16_t adv_data_len, esp_ble_adv_data_type type, uint8_t *length) { @@ -3226,3 +3283,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 */ diff --git a/components/bt/host/bluedroid/api/include/api/esp_ble_l2cap_api.h b/components/bt/host/bluedroid/api/include/api/esp_ble_l2cap_api.h new file mode 100644 index 00000000000..0a23f180987 --- /dev/null +++ b/components/bt/host/bluedroid/api/include/api/esp_ble_l2cap_api.h @@ -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 +#include + +#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__ */ diff --git a/components/bt/host/bluedroid/api/include/api/esp_gap_ble_api.h b/components/bt/host/bluedroid/api/include/api/esp_gap_ble_api.h index 2b93d1bf467..9b6ee4c6ab1 100644 --- a/components/bt/host/bluedroid/api/include/api/esp_gap_ble_api.h +++ b/components/bt/host/bluedroid/api/include/api/esp_gap_ble_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; /** @@ -3597,6 +3610,94 @@ esp_err_t esp_ble_gap_set_key_material(const uint8_t session_key[16], const uint */ esp_err_t esp_ble_gap_get_local_used_addr(esp_bd_addr_t local_used_addr, uint8_t * addr_type); +#if (CONFIG_BT_BLE_PERIPH_PSEUDO_ADDR_BOND) +/** + * @brief Reverse-map a Host pseudo address to the real peer identity. + * + * When CONFIG_BT_BLE_PERIPH_PSEUDO_ADDR_BOND is enabled, the + * remote_bda reported to the application for a dual local + * identity link is a Host-internal pseudo address (one peer + * phone connected through two local identities shows up as two + * different pseudo addresses). This helper returns the actual + * over-the-air peer identity for UI / diagnostics. + * + * **Must be called while the link is connected.** The mapping + * lives in a Host-side connection table that is cleared on + * disconnect. If there is no active link for `pseudo`, the call + * returns `ESP_FAIL` and `real_peer` is not modified. + * + * For offline bond information, use + * `esp_ble_get_bond_device_list()` and read + * `bond_key.pid_key.static_addr` for the real peer identity. + * + * @param[in] pseudo - the pseudo address as seen in remote_bda + * @param[out] real_peer - filled with the real peer identity on success + * + * @return - ESP_OK : success (link connected and pseudo known) + * - ESP_FAIL : Bluedroid not enabled, or pseudo not found / + * not connected + * - ESP_ERR_INVALID_ARG : NULL pointer argument + */ +esp_err_t esp_ble_gap_get_real_peer_addr(esp_bd_addr_t pseudo, esp_bd_addr_t real_peer); + +/** + * @brief Full identity of a dual local-identity connection. + */ +typedef struct { + esp_bd_addr_t peer_addr; /*!< real over-the-air peer identity */ + esp_bd_addr_t local_addr; /*!< local identity used for this link */ + esp_ble_addr_type_t peer_addr_type; /*!< peer identity address type */ + esp_ble_addr_type_t local_addr_type; /*!< local identity address type */ +} esp_ble_conn_identity_t; + +/** + * @brief Get the full (peer, local) identity of a dual local-identity + * link, keyed by the pseudo address the application sees as + * remote_bda. + * + * **Must be called while the link is connected.** The mapping + * is kept in a Host-side connection table that is registered at + * connection complete and cleared on disconnect. If there is no + * active link for `pseudo`, or the local identity is not yet + * finalized (`local_ready`), the call returns `ESP_FAIL` and + * `identity` is not modified. + * + * For offline bond information (no connection), use + * `esp_ble_get_bond_device_list()` and read + * `bond_key.pid_key.static_addr` for the real peer identity. + * The bond list key is the stored pseudo address; local identity + * is not exposed by this API offline. + * + * @param[in] pseudo - the pseudo address as seen in remote_bda + * @param[out] identity - filled with the peer/local identity on success + * + * @return - ESP_OK : success (link connected and pseudo known) + * - ESP_FAIL : Bluedroid not enabled, or pseudo not found / + * not connected / local identity not yet ready + * - ESP_ERR_INVALID_ARG : NULL pointer argument + */ +esp_err_t esp_ble_gap_get_conn_identity(esp_bd_addr_t pseudo, esp_ble_conn_identity_t *identity); + +/** + * @brief Remove the stored bond for one specific (local, peer) + * identity pair. The pseudo bond section is recomputed from the + * identity, so this only deletes that one local identity's bond + * and never affects the same phone's other local identity. + * + * @param[in] local_addr - local identity used when bonding + * @param[in] local_addr_type - local identity address type + * @param[in] peer_addr - real peer identity + * @param[in] peer_addr_type - peer identity address type + * + * @return - ESP_OK : request accepted + * - other : invalid arguments / not enabled + */ +esp_err_t esp_ble_gap_remove_bond_for_identity(esp_bd_addr_t local_addr, + esp_ble_addr_type_t local_addr_type, + esp_bd_addr_t peer_addr, + esp_ble_addr_type_t peer_addr_type); +#endif // CONFIG_BT_BLE_PERIPH_PSEUDO_ADDR_BOND + /** * @brief This function is called to get ADV data for a specific type. * @@ -5167,6 +5268,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 diff --git a/components/bt/host/bluedroid/bta/dm/bta_dm_act.c b/components/bt/host/bluedroid/bta/dm/bta_dm_act.c index 479fca05323..a426026e6f4 100644 --- a/components/bt/host/bluedroid/bta/dm/bta_dm_act.c +++ b/components/bt/host/bluedroid/bta/dm/bta_dm_act.c @@ -3798,17 +3798,55 @@ void bta_dm_acl_change(tBTA_DM_MSG *p_data) bta_dm_cb.p_sec_cback(BTA_DM_LINK_UP_EVT, (tBTA_DM_SEC *)&conn); } } else { - for (i = 0; i < bta_dm_cb.device_list.count; i++) { - if (bdcmp( bta_dm_cb.device_list.peer_device[i].peer_bdaddr, p_bda) -#if BLE_INCLUDED == TRUE - || bta_dm_cb.device_list.peer_device[i].transport != p_data->acl_change.transport +#if (BLE_INCLUDED == TRUE && SMP_INCLUDED == TRUE && BLE_PERIPH_PSEUDO_ADDR_BOND == TRUE) + BOOLEAN handle_only_match = FALSE; + BD_ADDR op_bda; + + bdcpy(op_bda, p_bda); #endif - ) { + for (i = 0; i < bta_dm_cb.device_list.count; i++) { + BOOLEAN entry_match = (bdcmp(bta_dm_cb.device_list.peer_device[i].peer_bdaddr, p_bda) == 0) +#if BLE_INCLUDED == TRUE + && (bta_dm_cb.device_list.peer_device[i].transport == p_data->acl_change.transport) +#endif + ; +#if (BLE_INCLUDED == TRUE && SMP_INCLUDED == TRUE && BLE_PERIPH_PSEUDO_ADDR_BOND == TRUE) + /* The peripheral pseudo-address bond feature may re-key an LE link's + * address (RPA -> pseudo) AFTER link-up was recorded, so the stored + * peer_bdaddr no longer matches the address reported at link-down. + * Match by the stable connection handle for LE to avoid leaking + * device_list entries (which would eventually exhaust the list). */ + if (!entry_match && + p_data->acl_change.transport == BT_TRANSPORT_LE && + bta_dm_cb.device_list.peer_device[i].transport == BT_TRANSPORT_LE && + bta_dm_cb.device_list.peer_device[i].conn_handle == p_data->acl_change.handle) { + entry_match = TRUE; + handle_only_match = TRUE; + } +#endif + if (!entry_match) { continue; } +#if (BLE_INCLUDED == TRUE && SMP_INCLUDED == TRUE && BLE_PERIPH_PSEUDO_ADDR_BOND == TRUE) + if (handle_only_match) { + tBTM_SEC_DEV_REC *p_rec = btm_find_dev_by_handle(p_data->acl_change.handle); + if (p_rec) { + bdcpy(op_bda, p_rec->bd_addr); + } else { + APPL_TRACE_WARNING("%s: handle-matched entry but no BTM record (handle=0x%x)," + " falling back to event addr", + __func__, p_data->acl_change.handle); + } + } +#endif + if ( bta_dm_cb.device_list.peer_device[i].conn_state == BTA_DM_UNPAIRING ) { +#if (BLE_INCLUDED == TRUE && SMP_INCLUDED == TRUE && BLE_PERIPH_PSEUDO_ADDR_BOND == TRUE) + if (BTM_SecDeleteDevice(op_bda, bta_dm_cb.device_list.peer_device[i].transport)) { +#else if (BTM_SecDeleteDevice(bta_dm_cb.device_list.peer_device[i].peer_bdaddr, bta_dm_cb.device_list.peer_device[i].transport)) { +#endif issue_unpair_cb = TRUE; } @@ -3863,10 +3901,18 @@ void bta_dm_acl_change(tBTA_DM_MSG *p_data) } } if (conn.link_down.is_removed) { +#if (BLE_INCLUDED == TRUE && SMP_INCLUDED == TRUE && BLE_PERIPH_PSEUDO_ADDR_BOND == TRUE) + BTM_SecDeleteDevice(op_bda, p_data->acl_change.transport); +#if (GATTC_INCLUDED == TRUE) + /* need to remove all pending background connection */ + BTA_GATTC_CancelOpen(0, op_bda, FALSE); +#endif +#else BTM_SecDeleteDevice(p_bda, p_data->acl_change.transport); #if (BLE_INCLUDED == TRUE && GATTC_INCLUDED == TRUE) /* need to remove all pending background connection */ BTA_GATTC_CancelOpen(0, p_bda, FALSE); +#endif #endif } @@ -3875,6 +3921,11 @@ void bta_dm_acl_change(tBTA_DM_MSG *p_data) if ( bta_dm_cb.p_sec_cback ) { bta_dm_cb.p_sec_cback(BTA_DM_LINK_DOWN_EVT, &conn); if ( issue_unpair_cb ) { +#if (BLE_INCLUDED == TRUE && SMP_INCLUDED == TRUE && BLE_PERIPH_PSEUDO_ADDR_BOND == TRUE) + if (handle_only_match) { + bdcpy(conn.link_down.bd_addr, op_bda); + } +#endif if (p_data->acl_change.transport == BT_TRANSPORT_LE) { bta_dm_cb.p_sec_cback(BTA_DM_BLE_DEV_UNPAIRED_EVT, &conn); } else { @@ -5110,7 +5161,19 @@ void bta_dm_add_ble_device (tBTA_DM_MSG *p_data) (p_data->add_ble_device.bd_addr[0] << 24) + (p_data->add_ble_device.bd_addr[1] << 16) + \ (p_data->add_ble_device.bd_addr[2] << 8) + p_data->add_ble_device.bd_addr[3], (p_data->add_ble_device.bd_addr[4] << 8) + p_data->add_ble_device.bd_addr[5]); + return; } + +#if (BLE_INCLUDED == TRUE && SMP_INCLUDED == TRUE && BLE_PERIPH_PSEUDO_ADDR_BOND == TRUE) + if (p_data->add_ble_device.is_pseudo_bond) { + if (!BTM_BleMarkPseudoBond(p_data->add_ble_device.bd_addr)) { + APPL_TRACE_WARNING("BTA_DM: failed to mark pseudo bond for device %08x%04x", + (p_data->add_ble_device.bd_addr[0] << 24) + (p_data->add_ble_device.bd_addr[1] << 16) + \ + (p_data->add_ble_device.bd_addr[2] << 8) + p_data->add_ble_device.bd_addr[3], + (p_data->add_ble_device.bd_addr[4] << 8) + p_data->add_ble_device.bd_addr[5]); + } + } +#endif } /******************************************************************************* diff --git a/components/bt/host/bluedroid/bta/dm/bta_dm_api.c b/components/bt/host/bluedroid/bta/dm/bta_dm_api.c index f7bdbdb23d6..a88edcfad40 100644 --- a/components/bt/host/bluedroid/bta/dm/bta_dm_api.c +++ b/components/bt/host/bluedroid/bta/dm/bta_dm_api.c @@ -1236,10 +1236,32 @@ void BTA_DmAddBleKey (BD_ADDR bd_addr, tBTA_LE_KEY_VALUE *p_le_key, tBTA_LE_KEY_ ** dev_type - Remote device's device type. ** auth_mode - auth mode ** addr_type - LE device address type. +** is_pseudo_bond - (pseudo bond only) TRUE when NVS section is +** keyed by a Host pseudo; tagged on BTU thread. ** ** Returns void ** *******************************************************************************/ +#if (BLE_INCLUDED == TRUE && SMP_INCLUDED == TRUE && BLE_PERIPH_PSEUDO_ADDR_BOND == TRUE) +void BTA_DmAddBleDevice(BD_ADDR bd_addr, tBLE_ADDR_TYPE addr_type, int auth_mode, + tBT_DEVICE_TYPE dev_type, BOOLEAN is_pseudo_bond) +{ + tBTA_DM_API_ADD_BLE_DEVICE *p_msg; + + if ((p_msg = (tBTA_DM_API_ADD_BLE_DEVICE *) osi_malloc(sizeof(tBTA_DM_API_ADD_BLE_DEVICE))) != NULL) { + memset (p_msg, 0, sizeof(tBTA_DM_API_ADD_BLE_DEVICE)); + + p_msg->hdr.event = BTA_DM_API_ADD_BLEDEVICE_EVT; + bdcpy(p_msg->bd_addr, bd_addr); + p_msg->addr_type = addr_type; + p_msg->auth_mode = auth_mode; + p_msg->dev_type = dev_type; + p_msg->is_pseudo_bond = is_pseudo_bond; + + bta_sys_sendmsg(p_msg); + } +} +#else void BTA_DmAddBleDevice(BD_ADDR bd_addr, tBLE_ADDR_TYPE addr_type, int auth_mode, tBT_DEVICE_TYPE dev_type) { tBTA_DM_API_ADD_BLE_DEVICE *p_msg; @@ -1256,6 +1278,7 @@ void BTA_DmAddBleDevice(BD_ADDR bd_addr, tBLE_ADDR_TYPE addr_type, int auth_mode bta_sys_sendmsg(p_msg); } } +#endif /******************************************************************************* ** ** Function BTA_DmBlePasskeyReply diff --git a/components/bt/host/bluedroid/bta/dm/include/bta_dm_int.h b/components/bt/host/bluedroid/bta/dm/include/bta_dm_int.h index c22c057013c..93bee549a61 100644 --- a/components/bt/host/bluedroid/bta/dm/include/bta_dm_int.h +++ b/components/bt/host/bluedroid/bta/dm/include/bta_dm_int.h @@ -795,6 +795,9 @@ typedef struct { tBT_DEVICE_TYPE dev_type ; UINT32 auth_mode; tBLE_ADDR_TYPE addr_type; +#if (BLE_INCLUDED == TRUE && SMP_INCLUDED == TRUE && BLE_PERIPH_PSEUDO_ADDR_BOND == TRUE) + BOOLEAN is_pseudo_bond; +#endif } tBTA_DM_API_ADD_BLE_DEVICE; diff --git a/components/bt/host/bluedroid/bta/include/bta/bta_api.h b/components/bt/host/bluedroid/bta/include/bta/bta_api.h index 32a8e3e59d8..1ed3f361b84 100644 --- a/components/bt/host/bluedroid/bta/include/bta/bta_api.h +++ b/components/bt/host/bluedroid/bta/include/bta/bta_api.h @@ -2513,12 +2513,19 @@ extern void BTA_DmBleConfirmReply(BD_ADDR bd_addr, BOOLEAN accept); ** dev_type - Remote device's device type. ** auth_mode - auth mode ** addr_type - LE device address type. +** is_pseudo_bond - (pseudo bond only) TRUE when NVS section is +** keyed by a Host pseudo; tagged on BTU thread. ** ** Returns void ** *******************************************************************************/ +#if (BLE_INCLUDED == TRUE && SMP_INCLUDED == TRUE && BLE_PERIPH_PSEUDO_ADDR_BOND == TRUE) +extern void BTA_DmAddBleDevice(BD_ADDR bd_addr, tBLE_ADDR_TYPE addr_type, int auth_mode, + tBT_DEVICE_TYPE dev_type, BOOLEAN is_pseudo_bond); +#else extern void BTA_DmAddBleDevice(BD_ADDR bd_addr, tBLE_ADDR_TYPE addr_type, int auth_mode, tBT_DEVICE_TYPE dev_type); +#endif /******************************************************************************* diff --git a/components/bt/host/bluedroid/btc/core/btc_ble_storage.c b/components/bt/host/bluedroid/btc/core/btc_ble_storage.c index a833a5ac91a..9b314c89478 100644 --- a/components/bt/host/bluedroid/btc/core/btc_ble_storage.c +++ b/components/bt/host/bluedroid/btc/core/btc_ble_storage.c @@ -12,6 +12,9 @@ #include "btc/btc_ble_storage.h" #include "bta/bta_gatts_co.h" #include "btc/btc_util.h" +#if (BLE_INCLUDED == TRUE && SMP_INCLUDED == TRUE && BLE_PERIPH_PSEUDO_ADDR_BOND == TRUE) +#include "stack/btm_ble_api.h" +#endif #if (SMP_INCLUDED == TRUE) @@ -134,6 +137,21 @@ static bt_status_t _btc_storage_add_ble_bonding_key(bt_bdaddr_t *remote_bd_addr, } int ret = btc_config_set_bin(bdstr, name, (const uint8_t *)key, key_length); + +#if (BLE_INCLUDED == TRUE && SMP_INCLUDED == TRUE && BLE_PERIPH_PSEUDO_ADDR_BOND == TRUE) + /* If this bond's section is keyed by a Host pseudo address (dual local + * identity link, still connected at save time), flag the section so the + * identity-based NVS de-dup never deletes it as a "duplicate" of the other + * local identity's bond (which shares the same peer Identity). Normal / + * RPA-keyed bonds are NOT flagged and keep the native cleanup behavior. */ + { + BD_ADDR real_peer; + if (BTM_BleGetRealPeerByPseudo(remote_bd_addr->address, real_peer)) { + btc_config_set_int(bdstr, BTC_BLE_STORAGE_PSEUDO_BOND_STR, 1); + } + } +#endif + _btc_storage_save(); return ret ? BT_STATUS_SUCCESS : BT_STATUS_FAIL; } @@ -256,6 +274,14 @@ static bt_status_t _btc_storage_remove_all_ble_keys(const char *name) if (btc_config_exist(name, BTC_BLE_STORAGE_LE_KEY_LID_STR)) { ret |= btc_config_remove(name, BTC_BLE_STORAGE_LE_KEY_LID_STR); } +#if (BLE_INCLUDED == TRUE && SMP_INCLUDED == TRUE && BLE_PERIPH_PSEUDO_ADDR_BOND == TRUE) + /* Clear the dual-identity pseudo-bond marker together with the LE keys so + * a removed bond does not leave a stale flag that would shield an empty + * section from cleanup. */ + if (btc_config_exist(name, BTC_BLE_STORAGE_PSEUDO_BOND_STR)) { + ret |= btc_config_remove(name, BTC_BLE_STORAGE_PSEUDO_BOND_STR); + } +#endif return ret; } @@ -273,6 +299,22 @@ void btc_storage_remove_unused_sections(uint8_t *cur_addr, tBTM_LE_PID_KEYS *del return; } +#if (BLE_INCLUDED == TRUE && SMP_INCLUDED == TRUE && BLE_PERIPH_PSEUDO_ADDR_BOND == TRUE) + /* Never use a pseudo-keyed bond as the de-dup baseline: it legitimately + * shares the peer Identity with a normal bond on another local identity. + * Symmetric with btc_storage_delete_duplicate_ble_devices() skipping pseudo + * baselines. The flag is only set when keys are saved, so use the live + * pseudo mapping rather than BTC_BLE_STORAGE_PSEUDO_BOND_STR on cur_addr. + * Orphan cleanup below still runs; only identity de-dup is skipped. */ + BOOLEAN skip_identity_dedup = FALSE; + { + BD_ADDR dummy; + if (BTM_BleGetRealPeerByPseudo(cur_addr, dummy)) { + skip_identity_dedup = TRUE; + } + } +#endif + btc_config_lock(); const btc_config_section_iter_t *iter = btc_config_section_begin(); @@ -303,6 +345,13 @@ void btc_storage_remove_unused_sections(uint8_t *cur_addr, tBTM_LE_PID_KEYS *del continue; } +#if (BLE_INCLUDED == TRUE && SMP_INCLUDED == TRUE && BLE_PERIPH_PSEUDO_ADDR_BOND == TRUE) + if (skip_identity_dedup) { + iter = btc_config_section_next(iter); + continue; + } +#endif + string_to_bdaddr(section, &bd_addr); char buffer[sizeof(tBTM_LE_KEY_VALUE)] = {0}; @@ -319,7 +368,14 @@ void btc_storage_remove_unused_sections(uint8_t *cur_addr, tBTM_LE_PID_KEYS *del if (del_pid_key->addr_type == pid_key->addr_type && !btc_storage_is_all_zeros(pid_key->static_addr, sizeof(pid_key->static_addr)) && memcmp(del_pid_key->static_addr, pid_key->static_addr, sizeof(pid_key->static_addr)) == 0 && - memcmp(cur_addr, bd_addr.address, sizeof(bd_addr.address)) != 0) { + memcmp(cur_addr, bd_addr.address, sizeof(bd_addr.address)) != 0 +#if (BLE_INCLUDED == TRUE && SMP_INCLUDED == TRUE && BLE_PERIPH_PSEUDO_ADDR_BOND == TRUE) + /* Dual local-identity bond isolation: a section keyed by a Host + * pseudo legitimately shares the peer Identity with another + * local identity's bond; never delete it as a "duplicate". */ + && !btc_config_exist(section, BTC_BLE_STORAGE_PSEUDO_BOND_STR) +#endif + ) { if (device_type == BT_DEVICE_TYPE_DUMO) { btc_config_set_int(section, BTC_BLE_STORAGE_DEV_TYPE_STR, BT_DEVICE_TYPE_BREDR); _btc_storage_remove_all_ble_keys(section); @@ -360,6 +416,19 @@ void btc_storage_delete_duplicate_ble_devices(void) continue; } +#if (BLE_INCLUDED == TRUE && SMP_INCLUDED == TRUE && BLE_PERIPH_PSEUDO_ADDR_BOND == TRUE) + /* Dual local-identity bond isolation: never use a pseudo-keyed section + * as the de-dup baseline. The inner check below only protects pseudo + * candidates, so without this an order-dependent case remains: if a + * pseudo bond is visited first and becomes the baseline, a normal bond + * that legitimately shares the same peer Identity (no PseudoBond flag) + * would match and be deleted. Skipping pseudo baselines makes the + * protection symmetric. */ + if (btc_config_exist(name, BTC_BLE_STORAGE_PSEUDO_BOND_STR)) { + continue; + } +#endif + string_to_bdaddr(name, &bd_addr); size_t pid_len = sizeof(tBTM_LE_PID_KEYS); bool pid_ok = btc_config_get_bin(name, BTC_BLE_STORAGE_LE_KEY_PID_STR, (uint8_t *)buffer, &pid_len); @@ -388,7 +457,13 @@ void btc_storage_delete_duplicate_ble_devices(void) temp_pid_key = (tBTM_LE_PID_KEYS *) temp_buffer; if (pid_key->addr_type == temp_pid_key->addr_type && !btc_storage_is_all_zeros(temp_pid_key->static_addr, sizeof(temp_pid_key->static_addr)) && - memcmp(pid_key->static_addr, temp_pid_key->static_addr, sizeof(pid_key->static_addr)) == 0) { + memcmp(pid_key->static_addr, temp_pid_key->static_addr, sizeof(pid_key->static_addr)) == 0 +#if (BLE_INCLUDED == TRUE && SMP_INCLUDED == TRUE && BLE_PERIPH_PSEUDO_ADDR_BOND == TRUE) + /* Skip pseudo-keyed sections: a dual local-identity bond + * shares the peer Identity with another bond on purpose. */ + && !btc_config_exist(temp_name, BTC_BLE_STORAGE_PSEUDO_BOND_STR) +#endif + ) { temp_iter = btc_config_section_next(temp_iter); if (temp_device_type == BT_DEVICE_TYPE_DUMO) { btc_config_set_int(temp_name, BTC_BLE_STORAGE_DEV_TYPE_STR, BT_DEVICE_TYPE_BREDR); @@ -915,8 +990,17 @@ bt_status_t btc_storage_get_remote_addr_type(bt_bdaddr_t *remote_bd_addr, } #if (BLE_INCLUDED == TRUE) +#if (SMP_INCLUDED == TRUE && BLE_PERIPH_PSEUDO_ADDR_BOND == TRUE) +#define BTC_BLE_FETCH_PSEUDO_BOND_PARAM , bool is_pseudo_bond +#define BTC_BLE_FETCH_PSEUDO_BOND_ARG , is_pseudo_bond +#else +#define BTC_BLE_FETCH_PSEUDO_BOND_PARAM +#define BTC_BLE_FETCH_PSEUDO_BOND_ARG +#endif + static void _btc_read_le_key(const uint8_t key_type, const size_t key_len, bt_bdaddr_t bd_addr, - const uint8_t addr_type, const bool add_key, bool *device_added, bool *key_found) + const uint8_t addr_type, const bool add_key BTC_BLE_FETCH_PSEUDO_BOND_PARAM, + bool *device_added, bool *key_found) { assert(device_added); assert(key_found); @@ -936,7 +1020,12 @@ static void _btc_read_le_key(const uint8_t key_type, const size_t key_len, bt_bd if(_btc_storage_get_ble_dev_auth_mode(&bd_addr, &auth_mode) != BT_STATUS_SUCCESS) { BTC_TRACE_WARNING("%s Failed to get auth mode from flash, please erase flash and download the firmware again", __func__); } +#if (SMP_INCLUDED == TRUE && BLE_PERIPH_PSEUDO_ADDR_BOND == TRUE) + BTA_DmAddBleDevice(bta_bd_addr, addr_type, auth_mode, BT_DEVICE_TYPE_BLE, + is_pseudo_bond ? TRUE : FALSE); +#else BTA_DmAddBleDevice(bta_bd_addr, addr_type, auth_mode, BT_DEVICE_TYPE_BLE); +#endif *device_added = true; } @@ -956,9 +1045,11 @@ bt_status_t _btc_storage_in_fetch_bonded_ble_device(const char *remote_bd_addr, uint32_t device_type = 0; int addr_type = BLE_ADDR_PUBLIC; bt_bdaddr_t bd_addr; - BD_ADDR bta_bd_addr; bool device_added = false; bool key_found = false; +#if (SMP_INCLUDED == TRUE && BLE_PERIPH_PSEUDO_ADDR_BOND == TRUE) + const bool is_pseudo_bond = add && btc_config_exist(remote_bd_addr, BTC_BLE_STORAGE_PSEUDO_BOND_STR); +#endif if (!btc_config_get_int(remote_bd_addr, BTC_BLE_STORAGE_DEV_TYPE_STR, (int *)&device_type)) { BTC_TRACE_ERROR("%s, device_type = %x", __func__, device_type); @@ -966,7 +1057,6 @@ bt_status_t _btc_storage_in_fetch_bonded_ble_device(const char *remote_bd_addr, } string_to_bdaddr(remote_bd_addr, &bd_addr); - bdcpy(bta_bd_addr, bd_addr.address); if (_btc_storage_get_remote_addr_type(&bd_addr, &addr_type) != BT_STATUS_SUCCESS) { addr_type = BLE_ADDR_PUBLIC; @@ -974,22 +1064,22 @@ bt_status_t _btc_storage_in_fetch_bonded_ble_device(const char *remote_bd_addr, } _btc_read_le_key(BTM_LE_KEY_PENC, sizeof(tBTM_LE_PENC_KEYS), - bd_addr, addr_type, add, &device_added, &key_found); + bd_addr, addr_type, add BTC_BLE_FETCH_PSEUDO_BOND_ARG, &device_added, &key_found); _btc_read_le_key(BTM_LE_KEY_PID, sizeof(tBTM_LE_PID_KEYS), - bd_addr, addr_type, add, &device_added, &key_found); + bd_addr, addr_type, add BTC_BLE_FETCH_PSEUDO_BOND_ARG, &device_added, &key_found); _btc_read_le_key(BTM_LE_KEY_LID, sizeof(tBTM_LE_PID_KEYS), - bd_addr, addr_type, add, &device_added, &key_found); + bd_addr, addr_type, add BTC_BLE_FETCH_PSEUDO_BOND_ARG, &device_added, &key_found); _btc_read_le_key(BTM_LE_KEY_PCSRK, sizeof(tBTM_LE_PCSRK_KEYS), - bd_addr, addr_type, add, &device_added, &key_found); + bd_addr, addr_type, add BTC_BLE_FETCH_PSEUDO_BOND_ARG, &device_added, &key_found); _btc_read_le_key(BTM_LE_KEY_LENC, sizeof(tBTM_LE_LENC_KEYS), - bd_addr, addr_type, add, &device_added, &key_found); + bd_addr, addr_type, add BTC_BLE_FETCH_PSEUDO_BOND_ARG, &device_added, &key_found); _btc_read_le_key(BTM_LE_KEY_LCSRK, sizeof(tBTM_LE_LCSRK_KEYS), - bd_addr, addr_type, add, &device_added, &key_found); + bd_addr, addr_type, add BTC_BLE_FETCH_PSEUDO_BOND_ARG, &device_added, &key_found); if (key_found) { return BT_STATUS_SUCCESS; diff --git a/components/bt/host/bluedroid/btc/core/btc_main.c b/components/bt/host/bluedroid/btc/core/btc_main.c index 279643bc628..e69306409e7 100644 --- a/components/bt/host/bluedroid/btc/core/btc_main.c +++ b/components/bt/host/bluedroid/btc/core/btc_main.c @@ -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); diff --git a/components/bt/host/bluedroid/btc/include/btc/btc_ble_storage.h b/components/bt/host/bluedroid/btc/include/btc/btc_ble_storage.h index 2cfb38b88be..24649a5088f 100644 --- a/components/bt/host/bluedroid/btc/include/btc/btc_ble_storage.h +++ b/components/bt/host/bluedroid/btc/include/btc/btc_ble_storage.h @@ -34,6 +34,10 @@ #define BTC_BLE_STORAGE_LE_KEY_LID_STR "LE_KEY_LID" #define BTC_BLE_STORAGE_LE_KEY_LCSRK_STR "LE_KEY_LCSRK" #define BTC_BLE_STORAGE_LE_AUTH_MODE_STR "AuthMode" +/* Marks a bond whose section is keyed by a Host pseudo address (dual local + * identity feature). Such sections legitimately share the peer Identity with + * another (local,peer) bond and must be exempt from identity-based de-dup. */ +#define BTC_BLE_STORAGE_PSEUDO_BOND_STR "PseudoBond" #define BTC_BLE_STORAGE_LOCAL_ADAPTER_STR "Adapter" #define BTC_BLE_STORAGE_LE_LOCAL_KEY_IR_STR "LE_LOCAL_KEY_IR" diff --git a/components/bt/host/bluedroid/btc/profile/std/ble_l2cap/btc_ble_l2cap.c b/components/bt/host/bluedroid/btc/profile/std/ble_l2cap/btc_ble_l2cap.c new file mode 100644 index 00000000000..cb897c2f70d --- /dev/null +++ b/components/bt/host/bluedroid/btc/profile/std/ble_l2cap/btc_ble_l2cap.c @@ -0,0 +1,1060 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#include "osi/allocator.h" +#include "btc/btc_task.h" +#include "btc/btc_manage.h" +#include "stack/l2c_api.h" +#include "l2c_int.h" +#include "stack/gatt_api.h" +#include "stack/btm_api.h" +#include "gatt_int.h" +#include "esp_err.h" +#include "btc_ble_l2cap.h" +#include "osi/list.h" + +#if (BLE_L2CAP_COC_INCLUDED == TRUE) + +#define BTC_BLE_L2CAP_TRACE_API(fmt, ...) BTC_TRACE_API("BLE_L2CAP: " fmt, ##__VA_ARGS__) +#define BTC_BLE_L2CAP_TRACE_DEBUG(fmt, ...) BTC_TRACE_DEBUG("BLE_L2CAP: " fmt, ##__VA_ARGS__) +#define BTC_BLE_L2CAP_TRACE_ERROR(fmt, ...) BTC_TRACE_ERROR("BLE_L2CAP: " fmt, ##__VA_ARGS__) + +typedef struct { + bool in_use; + uint16_t reg_psm; + uint16_t real_psm; + uint16_t mtu; +} btc_ble_l2cap_server_t; + +typedef struct { + bool initialized; +#if (BLE_L2CAP_COC_SERVER_INCLUDED == TRUE) + btc_ble_l2cap_server_t servers[BLE_MAX_L2CAP_CLIENTS]; +#endif +#if (BLE_L2CAP_COC_CLIENT_INCLUDED == TRUE) + /* reg_psm values this module created (via L2CA_RegisterLECoc) for outgoing + * client connections that have no server slot, so DEINIT can deregister them + * instead of leaking BLE RCB pool entries. */ + uint16_t client_reg_psms[BLE_MAX_L2CAP_CLIENTS]; +#endif +} btc_ble_l2cap_env_t; + +typedef struct { + bool in_use; + uint16_t conn_id; + BD_ADDR bda; +} btc_ble_l2cap_conn_bind_t; + +/* Per-channel lcid->bda shadow. The disconnect_ind callback only receives the + * lcid, and on some teardown paths (e.g. the classic L2CAP CSM link-loss path, + * l2cu_disconnect_chnl) the CCB is released BEFORE the callback runs, so + * l2cu_find_ccb_by_cid(lcid) returns NULL and the peer address can no longer be + * derived from the stack. Recording lcid->bda when the channel opens lets us + * still resolve the address at disconnect time and release the conn-bind slot. */ +typedef struct { + bool in_use; + uint16_t lcid; + BD_ADDR bda; +} btc_ble_l2cap_chan_bind_t; + +static btc_ble_l2cap_env_t s_l2cap_env; +static tL2CAP_APPL_INFO s_l2cap_appl; +static btc_ble_l2cap_conn_bind_t s_conn_bind[BLE_MAX_L2CAP_CLIENTS]; +static btc_ble_l2cap_chan_bind_t s_chan_bind[BLE_MAX_L2CAP_CLIENTS]; + +static void btc_ble_l2cap_post_event(esp_ble_l2cap_evt_t event, esp_ble_l2cap_cb_param_t *param); +static void btc_ble_l2cap_bind_conn(uint16_t conn_id, BD_ADDR bda); +static uint16_t btc_ble_l2cap_conn_id_from_bda(BD_ADDR bda); +static bool btc_ble_l2cap_bda_from_conn_id(uint16_t conn_id, BD_ADDR bda); +#if (BLE_L2CAP_COC_SERVER_INCLUDED == TRUE) +static btc_ble_l2cap_server_t *btc_ble_l2cap_find_server_by_real_psm(uint16_t psm); +#endif +static void btc_ble_l2cap_fill_chan_info_from_ccb(uint16_t chan_handle, + esp_ble_l2cap_chan_info_t *info); +#if (BLE_L2CAP_COC_CLIENT_INCLUDED == TRUE) +static uint16_t btc_ble_l2cap_get_or_register_psm(uint16_t real_psm, bool *newly_registered); +#endif + +/* ESP GATTC/GATTS APIs expose conn_id as tcb_idx (see BTC_GATT_GET_CONN_ID). */ +static uint16_t btc_ble_l2cap_app_conn_id_from_stack(UINT16 stack_conn_id) +{ + return (uint16_t)GATT_GET_TCB_IDX(stack_conn_id); +} + +static void btc_ble_l2cap_bind_conn(uint16_t conn_id, BD_ADDR bda) +{ + int i; + + for (i = 0; i < BLE_MAX_L2CAP_CLIENTS; i++) { + if (s_conn_bind[i].in_use && memcmp(s_conn_bind[i].bda, bda, BD_ADDR_LEN) == 0) { + s_conn_bind[i].conn_id = conn_id; + return; + } + } + for (i = 0; i < BLE_MAX_L2CAP_CLIENTS; i++) { + if (!s_conn_bind[i].in_use) { + s_conn_bind[i].in_use = true; + s_conn_bind[i].conn_id = conn_id; + memcpy(s_conn_bind[i].bda, bda, BD_ADDR_LEN); + return; + } + } + BTC_BLE_L2CAP_TRACE_ERROR("%s: conn bind table full (max=%d), conn_id=%u not tracked", + __func__, BLE_MAX_L2CAP_CLIENTS, conn_id); +} + +static void btc_ble_l2cap_unbind_bda(BD_ADDR bda) +{ + int i; + + for (i = 0; i < BLE_MAX_L2CAP_CLIENTS; i++) { + if (s_conn_bind[i].in_use && memcmp(s_conn_bind[i].bda, bda, BD_ADDR_LEN) == 0) { + memset(&s_conn_bind[i], 0, sizeof(s_conn_bind[i])); + return; + } + } +} + +/* Record (or refresh) the lcid->bda mapping for an opened CoC channel. */ +static void btc_ble_l2cap_track_chan(uint16_t lcid, BD_ADDR bda) +{ + int i, free_idx = -1; + + for (i = 0; i < BLE_MAX_L2CAP_CLIENTS; i++) { + if (s_chan_bind[i].in_use && s_chan_bind[i].lcid == lcid) { + memcpy(s_chan_bind[i].bda, bda, BD_ADDR_LEN); + return; + } + if (!s_chan_bind[i].in_use && free_idx < 0) { + free_idx = i; + } + } + if (free_idx >= 0) { + s_chan_bind[free_idx].in_use = true; + s_chan_bind[free_idx].lcid = lcid; + memcpy(s_chan_bind[free_idx].bda, bda, BD_ADDR_LEN); + } else { + /* Table full: without a mapping, disconnect_ind cannot recover the BDA + * once the CCB is gone, so the conn-bind slot for this peer would leak. + * Log it so the (normally unreachable) exhaustion is diagnosable. */ + BTC_BLE_L2CAP_TRACE_ERROR("%s: chan bind table full (max=%d), lcid=0x%04x not tracked", + __func__, BLE_MAX_L2CAP_CLIENTS, lcid); + } +} + +/* Look up the bda for an lcid and drop the entry. Returns true on success. */ +static bool btc_ble_l2cap_untrack_chan(uint16_t lcid, BD_ADDR out_bda) +{ + int i; + + for (i = 0; i < BLE_MAX_L2CAP_CLIENTS; i++) { + if (s_chan_bind[i].in_use && s_chan_bind[i].lcid == lcid) { + if (out_bda != NULL) { + memcpy(out_bda, s_chan_bind[i].bda, BD_ADDR_LEN); + } + memset(&s_chan_bind[i], 0, sizeof(s_chan_bind[i])); + return true; + } + } + return false; +} + +/* Whether the shadow table still holds another channel to this peer. */ +static bool btc_ble_l2cap_chan_has_other(BD_ADDR bda, uint16_t except_lcid) +{ + int i; + + for (i = 0; i < BLE_MAX_L2CAP_CLIENTS; i++) { + if (s_chan_bind[i].in_use && s_chan_bind[i].lcid != except_lcid && + memcmp(s_chan_bind[i].bda, bda, BD_ADDR_LEN) == 0) { + return true; + } + } + return false; +} + +static BOOLEAN btc_ble_l2cap_has_other_active_coc(BD_ADDR bda, UINT16 except_lcid) +{ + list_node_t *p_node; + tL2C_LCB *p_lcb; + tL2C_CCB *p_ccb; + + for (p_node = list_begin(l2cb.p_lcb_pool); p_node; p_node = list_next(p_node)) { + p_lcb = list_node(p_node); + /* The pool holds released LCBs too (in_use == FALSE, stale + * remote_bd_addr); skip them and non-LE links to match the established + * l2cu_find_lcb_by_bd_addr pattern. */ + if (!p_lcb->in_use || p_lcb->transport != BT_TRANSPORT_LE) { + continue; + } + if (memcmp(p_lcb->remote_bd_addr, bda, BD_ADDR_LEN) != 0) { + continue; + } + for (p_ccb = p_lcb->ccb_queue.p_first_ccb; p_ccb; p_ccb = p_ccb->p_next_ccb) { + if (p_ccb->le_coc_active && p_ccb->local_cid != except_lcid) { + return TRUE; + } + } + } + return FALSE; +} + +static uint16_t btc_ble_l2cap_conn_id_from_bda(BD_ADDR bda) +{ + int i; + UINT16 conn_id = 0; + + for (i = 0; i < BLE_MAX_L2CAP_CLIENTS; i++) { + if (s_conn_bind[i].in_use && memcmp(s_conn_bind[i].bda, bda, BD_ADDR_LEN) == 0) { + return s_conn_bind[i].conn_id; + } + } +#if (GATTC_INCLUDED == TRUE) + tGATT_REG *p_reg; + + for (i = 0, p_reg = gatt_cb.cl_rcb; i < GATT_MAX_APPS; i++, p_reg++) { + if (p_reg->in_use && + GATT_GetConnIdIfConnected(p_reg->gatt_if, bda, &conn_id, BT_TRANSPORT_LE)) { + return btc_ble_l2cap_app_conn_id_from_stack(conn_id); + } + } +#endif +#if (GATTS_INCLUDED == TRUE) + tGATT_SR_REG *p_sr_reg; + + for (i = 0, p_sr_reg = gatt_cb.sr_reg; i < GATT_MAX_SR_PROFILES; i++, p_sr_reg++) { + if (p_sr_reg->in_use && + GATT_GetConnIdIfConnected(p_sr_reg->gatt_if, bda, &conn_id, BT_TRANSPORT_LE)) { + return btc_ble_l2cap_app_conn_id_from_stack(conn_id); + } + } +#endif + return 0; +} + +static bool btc_ble_l2cap_bda_from_conn_id(uint16_t conn_id, BD_ADDR bda) +{ + int i; + tGATT_TCB *p_tcb; + + for (i = 0; i < BLE_MAX_L2CAP_CLIENTS; i++) { + if (s_conn_bind[i].in_use && s_conn_bind[i].conn_id == conn_id) { + memcpy(bda, s_conn_bind[i].bda, BD_ADDR_LEN); + return true; + } + } + + p_tcb = gatt_get_tcb_by_idx((UINT8)conn_id); + if (p_tcb != NULL && gatt_get_ch_state(p_tcb) >= GATT_CH_OPEN) { + memcpy(bda, p_tcb->peer_bda, BD_ADDR_LEN); + return true; + } + return false; +} + +static void btc_ble_l2cap_fill_chan_info_from_ccb(uint16_t chan_handle, + esp_ble_l2cap_chan_info_t *info) +{ + tL2C_CCB *p_ccb; + + if (info == NULL) { + return; + } + + memset(info, 0, sizeof(*info)); + p_ccb = l2cu_find_ccb_by_cid(NULL, chan_handle); + if (p_ccb == NULL || !p_ccb->le_coc_active) { + return; + } + + info->scid = p_ccb->local_cid; + info->dcid = p_ccb->remote_cid; + info->psm = p_ccb->p_rcb ? p_ccb->p_rcb->real_psm : 0; + info->our_mtu = p_ccb->local_conn_cfg.mtu; + info->peer_mtu = p_ccb->peer_conn_cfg.mtu; + info->our_mps = p_ccb->local_conn_cfg.mps; + info->peer_mps = p_ccb->peer_conn_cfg.mps; +} + +#if (BLE_L2CAP_COC_SERVER_INCLUDED == TRUE) +static void btc_ble_l2cap_connect_ind(BD_ADDR bd_addr, UINT16 lcid, UINT16 psm, UINT8 id) +{ + esp_ble_l2cap_cb_param_t param = {0}; + + param.coc_accept.conn_id = btc_ble_l2cap_conn_id_from_bda(bd_addr); + param.coc_accept.chan_handle = lcid; + param.coc_accept.l2cap_id = id; + param.coc_accept.psm = psm; + + BTC_BLE_L2CAP_TRACE_API("ConnectInd conn_id=%u lcid=0x%04x psm=0x%04x id=%u", + param.coc_accept.conn_id, lcid, psm, id); + btc_ble_l2cap_bind_conn(param.coc_accept.conn_id, bd_addr); + btc_ble_l2cap_post_event(ESP_BLE_L2CAP_COC_ACCEPT_EVT, ¶m); +} +#endif /* BLE_L2CAP_COC_SERVER_INCLUDED */ + +static void btc_ble_l2cap_connect_cfm(UINT16 lcid, UINT16 result) +{ + esp_ble_l2cap_cb_param_t param = {0}; + tL2C_CCB *p_ccb = l2cu_find_ccb_by_cid(NULL, lcid); + + param.coc_connected.chan_handle = lcid; + param.coc_connected.status = result; + if (p_ccb && p_ccb->p_lcb) { + param.coc_connected.conn_id = btc_ble_l2cap_conn_id_from_bda(p_ccb->p_lcb->remote_bd_addr); + } + + BTC_BLE_L2CAP_TRACE_API("ConnectCfm conn_id=%u lcid=0x%04x status=%u", + param.coc_connected.conn_id, lcid, result); + if (result == L2CAP_CONN_OK) { + btc_ble_l2cap_fill_chan_info_from_ccb(lcid, ¶m.coc_connected.chan_info); + /* Shadow the lcid->bda mapping so disconnect_ind can still resolve the + * peer address (and release the conn-bind slot) even if the CCB is freed + * before the DisconnectInd callback fires. */ + if (p_ccb && p_ccb->p_lcb) { + btc_ble_l2cap_track_chan(lcid, p_ccb->p_lcb->remote_bd_addr); + } + } else if (p_ccb && p_ccb->p_lcb && + !btc_ble_l2cap_has_other_active_coc(p_ccb->p_lcb->remote_bd_addr, lcid) && + !btc_ble_l2cap_chan_has_other(p_ccb->p_lcb->remote_bd_addr, lcid)) { + /* A failed connection is torn down via l2cu_release_ccb without a + * DisconnectInd callback, so the s_conn_bind entry created at connect + * time would leak. Release it here, mirroring disconnect_ind (which also + * consults the shadow table so we do not unbind while another channel to + * this peer is still tracked with a pending DisconnectInd). */ + btc_ble_l2cap_unbind_bda(p_ccb->p_lcb->remote_bd_addr); + } + btc_ble_l2cap_post_event(ESP_BLE_L2CAP_COC_CONNECTED_EVT, ¶m); +} + +#if (BLE_L2CAP_COC_CLIENT_INCLUDED == TRUE) +/* A connect/connect_ecoc request can be rejected synchronously by the stack + * (e.g. PSM registration or CCB allocation failure) with no CCB created, so + * neither ConnectCfm nor DisconnectInd will ever fire. Report the failure to the + * application (so it does not wait forever) and release the conn-bind slot + * created at request time. */ +static void btc_ble_l2cap_report_connect_fail(uint16_t conn_id, BD_ADDR bda, uint16_t status) +{ + esp_ble_l2cap_cb_param_t param = {0}; + + /* Consult both the live stack and the lcid->bda shadow before releasing the + * conn-bind slot, mirroring disconnect_ind. Another channel to this peer may + * have already released its CCB (so has_other_active_coc misses it) while its + * DisconnectInd is still pending in the shadow; unbinding on has_other_active_coc + * alone would drop the slot prematurely. */ + if (!btc_ble_l2cap_has_other_active_coc(bda, 0) && + !btc_ble_l2cap_chan_has_other(bda, 0)) { + btc_ble_l2cap_unbind_bda(bda); + } + param.coc_connected.conn_id = conn_id; + param.coc_connected.chan_handle = 0; + param.coc_connected.status = status; + BTC_BLE_L2CAP_TRACE_API("connect fail conn_id=%u status=%u", conn_id, status); + btc_ble_l2cap_post_event(ESP_BLE_L2CAP_COC_CONNECTED_EVT, ¶m); +} +#endif /* BLE_L2CAP_COC_CLIENT_INCLUDED */ + +static void btc_ble_l2cap_disconnect_ind(UINT16 lcid, BOOLEAN local_init) +{ + esp_ble_l2cap_cb_param_t param = {0}; + tL2C_CCB *p_ccb = l2cu_find_ccb_by_cid(NULL, lcid); + BD_ADDR bda; + bool have_bda = false; + + UNUSED(local_init); + + /* Prefer the live CCB's address; fall back to the lcid->bda shadow because + * some teardown paths release the CCB before invoking this callback, in + * which case l2cu_find_ccb_by_cid() returns NULL and the address would + * otherwise be lost, leaking the conn-bind slot. */ + if (p_ccb != NULL && p_ccb->p_lcb != NULL) { + memcpy(bda, p_ccb->p_lcb->remote_bd_addr, BD_ADDR_LEN); + have_bda = true; + btc_ble_l2cap_untrack_chan(lcid, NULL); + } else if (btc_ble_l2cap_untrack_chan(lcid, bda)) { + have_bda = true; + } + + /* Release the conn-bind slot once the last CoC channel to this peer is gone. + * Consult both the live stack (other CCBs) and the shadow table so we do not + * unbind while another channel to the same peer is still up. */ + if (have_bda && + !btc_ble_l2cap_has_other_active_coc(bda, lcid) && + !btc_ble_l2cap_chan_has_other(bda, lcid)) { + btc_ble_l2cap_unbind_bda(bda); + } + + param.coc_disconnected.chan_handle = lcid; + BTC_BLE_L2CAP_TRACE_API("DisconnectInd lcid=0x%04x", lcid); + btc_ble_l2cap_post_event(ESP_BLE_L2CAP_COC_DISCONNECTED_EVT, ¶m); +} + +static void btc_ble_l2cap_data_ind(UINT16 lcid, BT_HDR *p_buf) +{ + esp_ble_l2cap_cb_param_t param = {0}; + + if (p_buf == NULL) { + BTC_BLE_L2CAP_TRACE_ERROR("data_ind NULL buffer lcid=0x%04x", lcid); + return; + } + + param.data_received.chan_handle = lcid; + param.data_received.len = p_buf->len; + param.data_received.data = (UINT8 *)(p_buf + 1) + p_buf->offset; + + BTC_BLE_L2CAP_TRACE_DEBUG("DataInd lcid=0x%04x len=%u", lcid, p_buf->len); + /* p_buf ownership transfers to btc_ble_l2cap_cb_deep_copy() */ + btc_ble_l2cap_post_event(ESP_BLE_L2CAP_COC_DATA_RECEIVED_EVT, ¶m); +} + +static void btc_ble_l2cap_congestion(UINT16 lcid, BOOLEAN congested) +{ + esp_ble_l2cap_cb_param_t param = {0}; + + if (congested) { + return; + } + + param.tx_unstalled.chan_handle = lcid; + BTC_BLE_L2CAP_TRACE_DEBUG("TxUnstalled lcid=0x%04x", lcid); + btc_ble_l2cap_post_event(ESP_BLE_L2CAP_COC_TX_UNSTALLED_EVT, ¶m); +} + +#if (BLE_L2CAP_ENHANCED_COC_INCLUDED == TRUE) +static void btc_ble_l2cap_reconfig_ind(UINT16 lcid, UINT16 result, BOOLEAN peer_initiated) +{ + esp_ble_l2cap_cb_param_t param = {0}; + + if (peer_initiated) { + param.peer_reconfigured.chan_handle = lcid; + param.peer_reconfigured.status = result; + BTC_BLE_L2CAP_TRACE_API("PeerReconfig lcid=0x%04x status=%u", lcid, result); + if (result == L2CAP_LE_RECONFIG_OK) { + btc_ble_l2cap_fill_chan_info_from_ccb(lcid, ¶m.peer_reconfigured.chan_info); + BTC_BLE_L2CAP_TRACE_API("PeerReconfig mtu=%u mps=%u", + param.peer_reconfigured.chan_info.peer_mtu, + param.peer_reconfigured.chan_info.peer_mps); + } + btc_ble_l2cap_post_event(ESP_BLE_L2CAP_COC_PEER_RECONFIGURED_EVT, ¶m); + } else { + param.reconfig_completed.chan_handle = lcid; + param.reconfig_completed.status = result; + BTC_BLE_L2CAP_TRACE_API("ReconfigCompleted lcid=0x%04x status=%u", lcid, result); + if (result == L2CAP_LE_RECONFIG_OK) { + btc_ble_l2cap_fill_chan_info_from_ccb(lcid, ¶m.reconfig_completed.chan_info); + BTC_BLE_L2CAP_TRACE_API("ReconfigCompleted mtu=%u/%u mps=%u/%u", + param.reconfig_completed.chan_info.our_mtu, + param.reconfig_completed.chan_info.peer_mtu, + param.reconfig_completed.chan_info.our_mps, + param.reconfig_completed.chan_info.peer_mps); + } + btc_ble_l2cap_post_event(ESP_BLE_L2CAP_COC_RECONFIG_COMPLETED_EVT, ¶m); + } +} +#endif + +static void btc_ble_l2cap_post_event(esp_ble_l2cap_evt_t event, esp_ble_l2cap_cb_param_t *param) +{ + btc_msg_t msg = {0}; + bt_status_t ret; + + msg.sig = BTC_SIG_API_CB; + msg.pid = BTC_PID_BLE_L2CAP; + msg.act = event; + + ret = btc_transfer_context(&msg, param, sizeof(esp_ble_l2cap_cb_param_t), + btc_ble_l2cap_cb_deep_copy, btc_ble_l2cap_cb_deep_free); + if (ret != BT_STATUS_SUCCESS) { + BTC_BLE_L2CAP_TRACE_ERROR("btc_transfer_context failed evt=%d", event); + /* Free the original RX BT_HDR only if the deep-copy callback did not + * already free it. When btc_transfer_context fails after running the + * deep copy, that callback clears param->data_received.data to NULL, so + * the check below skips the free and avoids a double-free. If it failed + * before the deep copy (e.g. message alloc failure), the pointer is + * still valid and we free it here to avoid a leak (LE CoC delivers SDUs + * with offset 0, so the BT_HDR header sits right before data). */ + if (event == ESP_BLE_L2CAP_COC_DATA_RECEIVED_EVT && param->data_received.data != NULL) { + osi_free((UINT8 *)param->data_received.data - sizeof(BT_HDR)); + } + } +} + +#if (BLE_L2CAP_COC_SERVER_INCLUDED == TRUE) +static btc_ble_l2cap_server_t *btc_ble_l2cap_find_server_by_real_psm(uint16_t psm) +{ + for (int i = 0; i < BLE_MAX_L2CAP_CLIENTS; i++) { + if (s_l2cap_env.servers[i].in_use && s_l2cap_env.servers[i].real_psm == psm) { + return &s_l2cap_env.servers[i]; + } + } + return NULL; +} + +static btc_ble_l2cap_server_t *btc_ble_l2cap_alloc_server(uint16_t psm, uint16_t mtu) +{ + for (int i = 0; i < BLE_MAX_L2CAP_CLIENTS; i++) { + if (!s_l2cap_env.servers[i].in_use) { + s_l2cap_env.servers[i].in_use = true; + s_l2cap_env.servers[i].real_psm = psm; + s_l2cap_env.servers[i].mtu = mtu; + s_l2cap_env.servers[i].reg_psm = 0; + return &s_l2cap_env.servers[i]; + } + } + return NULL; +} +#endif /* BLE_L2CAP_COC_SERVER_INCLUDED */ + +static void btc_ble_l2cap_register_psm_security(uint16_t reg_psm) +{ + BTM_SetSecurityLevel(TRUE, "BLE_L2CAP_COC", BTM_SEC_SERVICE_GEN_NET, + BTM_SEC_NONE, reg_psm, BTM_SEC_PROTO_L2CAP, 0); + BTM_SetSecurityLevel(FALSE, "BLE_L2CAP_COC", BTM_SEC_SERVICE_GEN_NET, + BTM_SEC_NONE, reg_psm, BTM_SEC_PROTO_L2CAP, 0); +} + +#if (BLE_L2CAP_COC_CLIENT_INCLUDED == TRUE) +static bool btc_ble_l2cap_track_client_psm(uint16_t reg_psm) +{ + int free_slot = -1; + for (int i = 0; i < BLE_MAX_L2CAP_CLIENTS; i++) { + if (s_l2cap_env.client_reg_psms[i] == reg_psm) { + return true; /* already tracked */ + } + if (free_slot < 0 && s_l2cap_env.client_reg_psms[i] == 0) { + free_slot = i; + } + } + if (free_slot >= 0) { + s_l2cap_env.client_reg_psms[free_slot] = reg_psm; + return true; + } + /* Table full: report it (mirrors btc_ble_l2cap_track_chan/bind_conn) and let + * the caller deregister the PSM. A silently untracked PSM would never be + * deregistered at DEINIT, permanently leaking a BLE RCB pool entry. */ + BTC_BLE_L2CAP_TRACE_ERROR("%s client PSM track table full, psm=0x%04x", __func__, reg_psm); + return false; +} + +static void btc_ble_l2cap_untrack_client_psm(uint16_t reg_psm) +{ + for (int i = 0; i < BLE_MAX_L2CAP_CLIENTS; i++) { + if (s_l2cap_env.client_reg_psms[i] == reg_psm) { + s_l2cap_env.client_reg_psms[i] = 0; + return; + } + } +} + +/* newly_registered (optional out): set TRUE only when this call created a fresh + * L2CAP registration, so a failed connect attempt can deregister its own PSM + * without tearing down a registration shared with a server or a prior client + * connection to the same PSM. */ +static uint16_t btc_ble_l2cap_get_or_register_psm(uint16_t real_psm, bool *newly_registered) +{ + UINT16 reg_psm; + tL2C_RCB *p_rcb; + + if (newly_registered != NULL) { + *newly_registered = false; + } + +#if (BLE_L2CAP_COC_SERVER_INCLUDED == TRUE) + btc_ble_l2cap_server_t *srv = btc_ble_l2cap_find_server_by_real_psm(real_psm); + + if (srv != NULL && srv->reg_psm != 0) { + btc_ble_l2cap_register_psm_security(srv->reg_psm); + return srv->reg_psm; + } +#endif + + p_rcb = l2cu_find_ble_rcb_by_real_psm(real_psm); + if (p_rcb != NULL) { + btc_ble_l2cap_register_psm_security(p_rcb->psm); + return p_rcb->psm; + } + + reg_psm = L2CA_RegisterLECoc(real_psm, &s_l2cap_appl); + if (reg_psm != 0) { + /* Remember it so DEINIT can deregister this client-created PSM. If the + * tracking table is full, roll back the registration right away instead + * of leaving a PSM that DEINIT can never find and deregister (RCB leak). + * If the connect attempt later fails the caller deregisters + untracks. */ + if (!btc_ble_l2cap_track_client_psm(reg_psm)) { + L2CA_DeregisterLECoc(reg_psm); + return 0; + } + btc_ble_l2cap_register_psm_security(reg_psm); + if (newly_registered != NULL) { + *newly_registered = true; + } + } + return reg_psm; +} +#endif /* BLE_L2CAP_COC_CLIENT_INCLUDED */ + +static void btc_ble_l2cap_register_appl_cb(void) +{ + memset(&s_l2cap_appl, 0, sizeof(s_l2cap_appl)); +#if (BLE_L2CAP_COC_SERVER_INCLUDED == TRUE) + s_l2cap_appl.pL2CA_ConnectInd_Cb = btc_ble_l2cap_connect_ind; +#endif + s_l2cap_appl.pL2CA_ConnectCfm_Cb = btc_ble_l2cap_connect_cfm; + s_l2cap_appl.pL2CA_DisconnectInd_Cb = btc_ble_l2cap_disconnect_ind; + s_l2cap_appl.pL2CA_DataInd_Cb = btc_ble_l2cap_data_ind; + s_l2cap_appl.pL2CA_CongestionStatus_Cb = btc_ble_l2cap_congestion; +#if (BLE_L2CAP_ENHANCED_COC_INCLUDED == TRUE) + s_l2cap_appl.pL2CA_LeReconfigInd_Cb = btc_ble_l2cap_reconfig_ind; +#endif +} + +void btc_ble_l2cap_cb_deep_copy(btc_msg_t *msg, void *p_dest, void *p_src) +{ + esp_ble_l2cap_cb_param_t *dst = (esp_ble_l2cap_cb_param_t *)p_dest; + esp_ble_l2cap_cb_param_t *src = (esp_ble_l2cap_cb_param_t *)p_src; + + if (!dst || !src) { + return; + } + + memcpy(dst, src, sizeof(esp_ble_l2cap_cb_param_t)); + + /* Only the DATA_RECEIVED event carries a heap pointer that needs a deep copy. + * Do NOT clear dst->data_received.data for other events: the param is a union, + * so writing data_received.data would clobber overlapping scalar fields of + * other events (e.g. coc_accept.l2cap_id / psm). */ + if (msg->act == ESP_BLE_L2CAP_COC_DATA_RECEIVED_EVT && src->data_received.data != NULL) { + BT_HDR *p_rx; + + dst->data_received.data = NULL; + + /* LE CoC delivers SDUs with offset 0 (see l2c_ble_le_coc_data_ind). */ + p_rx = (BT_HDR *)((UINT8 *)src->data_received.data - sizeof(BT_HDR)); + + if (src->data_received.len > 0) { + dst->data_received.data = (uint8_t *)osi_malloc(src->data_received.len); + if (dst->data_received.data) { + memcpy(dst->data_received.data, src->data_received.data, src->data_received.len); + } else { + /* Deep-copy OOM: never hand the app a non-zero len with a NULL + * pointer, or it will dereference NULL. Report an empty SDU. */ + dst->data_received.len = 0; + BTC_BLE_L2CAP_TRACE_ERROR("rx deep_copy malloc failed len=%u", + src->data_received.len); + } + } + osi_free(p_rx); + /* The original RX BT_HDR is now freed. Clear the source pointer (safe: + * this is the DATA_RECEIVED union member) so the caller's error path in + * btc_ble_l2cap_post_event sees NULL and does not free it a second time + * when btc_task_post fails after this deep-copy already ran. */ + src->data_received.data = NULL; + } +} + +void btc_ble_l2cap_cb_deep_free(btc_msg_t *msg) +{ + esp_ble_l2cap_cb_param_t *param = (esp_ble_l2cap_cb_param_t *)msg->arg; + + if (param && msg->act == ESP_BLE_L2CAP_COC_DATA_RECEIVED_EVT && param->data_received.data) { + osi_free(param->data_received.data); + param->data_received.data = NULL; + } +} + +void btc_ble_l2cap_arg_deep_copy(btc_msg_t *msg, void *p_dest, void *p_src) +{ + btc_ble_l2cap_args_t *dst = (btc_ble_l2cap_args_t *)p_dest; + btc_ble_l2cap_args_t *src = (btc_ble_l2cap_args_t *)p_src; + + if (!dst || !src) { + return; + } + + memcpy(dst, src, sizeof(btc_ble_l2cap_args_t)); + + /* Only the SEND act carries a heap pointer that needs a deep copy. Do NOT + * clear dst->send.data for other acts: send/connect_ecoc/etc. share the same + * union, so writing send.data would clobber overlapping scalar fields (e.g. + * connect_ecoc.mtu / num_chan). */ + if (msg->act == BTC_BLE_L2CAP_ACT_SEND) { + dst->send.data = NULL; + if (src->send.len > 0 && src->send.data) { + BT_HDR *p_buf = (BT_HDR *)osi_malloc(sizeof(BT_HDR) + src->send.len); + if (p_buf) { + p_buf->offset = 0; + p_buf->len = src->send.len; + p_buf->event = 0; + p_buf->layer_specific = 0; + memcpy((UINT8 *)(p_buf + 1), src->send.data, src->send.len); + dst->send.data = (uint8_t *)p_buf; + } else { + BTC_BLE_L2CAP_TRACE_ERROR("deep_copy malloc failed act=%d len=%u", msg->act, src->send.len); + } + } + } +} + +void btc_ble_l2cap_arg_deep_free(btc_msg_t *msg) +{ + btc_ble_l2cap_args_t *arg = (btc_ble_l2cap_args_t *)msg->arg; + + if (arg && msg->act == BTC_BLE_L2CAP_ACT_SEND && arg->send.data) { + osi_free(arg->send.data); + arg->send.data = NULL; + } +} + +static void btc_ble_l2cap_cb_to_app(esp_ble_l2cap_evt_t event, esp_ble_l2cap_cb_param_t *param) +{ + esp_ble_l2cap_cb_t cb = (esp_ble_l2cap_cb_t)btc_profile_cb_get(BTC_PID_BLE_L2CAP); + if (cb) { + cb(event, param); + } +} + +void btc_ble_l2cap_cb_handler(btc_msg_t *msg) +{ + esp_ble_l2cap_cb_param_t *param = (esp_ble_l2cap_cb_param_t *)msg->arg; + + if (msg->act < ESP_BLE_L2CAP_COC_EVT_MAX) { + btc_ble_l2cap_cb_to_app((esp_ble_l2cap_evt_t)msg->act, param); + } else { + BTC_BLE_L2CAP_TRACE_ERROR("cb_handler invalid event act=%d", msg->act); + } + btc_ble_l2cap_cb_deep_free(msg); +} + +void btc_ble_l2cap_call_handler(btc_msg_t *msg) +{ + btc_ble_l2cap_args_t *arg = (btc_ble_l2cap_args_t *)msg->arg; + + BTC_BLE_L2CAP_TRACE_DEBUG("%s act=%d", __func__, msg->act); + + switch (msg->act) { + case BTC_BLE_L2CAP_ACT_INIT: + BTC_BLE_L2CAP_TRACE_DEBUG("INIT"); + /* Mirror DEINIT: on a re-init without a prior DEINIT, deregister any PSMs + * this module still tracks before wiping s_l2cap_env. Otherwise the + * corresponding tL2C_RCB entries leak from the limited BLE RCB pool and + * eventually make new L2CA_RegisterLECoc calls fail. */ + if (s_l2cap_env.initialized) { +#if (BLE_L2CAP_COC_SERVER_INCLUDED == TRUE) + for (int i = 0; i < BLE_MAX_L2CAP_CLIENTS; i++) { + if (s_l2cap_env.servers[i].in_use && s_l2cap_env.servers[i].reg_psm) { + L2CA_DeregisterLECoc(s_l2cap_env.servers[i].reg_psm); + } + } +#endif +#if (BLE_L2CAP_COC_CLIENT_INCLUDED == TRUE) + for (int i = 0; i < BLE_MAX_L2CAP_CLIENTS; i++) { + if (s_l2cap_env.client_reg_psms[i] != 0) { + L2CA_DeregisterLECoc(s_l2cap_env.client_reg_psms[i]); + } + } +#endif + } + memset(&s_l2cap_env, 0, sizeof(s_l2cap_env)); + memset(s_conn_bind, 0, sizeof(s_conn_bind)); + /* Mirror DEINIT: clear the channel-bind shadow table so a re-init without + * a prior DEINIT does not inherit stale entries, which would make + * btc_ble_l2cap_chan_has_other() wrongly hold conn-bind slots. */ + memset(s_chan_bind, 0, sizeof(s_chan_bind)); + s_l2cap_env.initialized = true; + btc_ble_l2cap_register_appl_cb(); + break; + + case BTC_BLE_L2CAP_ACT_DEINIT: + BTC_BLE_L2CAP_TRACE_DEBUG("DEINIT"); +#if (BLE_L2CAP_COC_SERVER_INCLUDED == TRUE) + for (int i = 0; i < BLE_MAX_L2CAP_CLIENTS; i++) { + if (s_l2cap_env.servers[i].in_use && s_l2cap_env.servers[i].reg_psm) { + BTC_BLE_L2CAP_TRACE_DEBUG("DEINIT deregister server reg_psm=0x%04x", + s_l2cap_env.servers[i].reg_psm); + L2CA_DeregisterLECoc(s_l2cap_env.servers[i].reg_psm); + } + } +#endif +#if (BLE_L2CAP_COC_CLIENT_INCLUDED == TRUE) + /* Deregister PSMs this module registered for outgoing connections that + * had no server slot, otherwise they leak BLE RCB pool entries across + * an init/deinit cycle. */ + for (int i = 0; i < BLE_MAX_L2CAP_CLIENTS; i++) { + if (s_l2cap_env.client_reg_psms[i] != 0) { + BTC_BLE_L2CAP_TRACE_DEBUG("DEINIT deregister client reg_psm=0x%04x", + s_l2cap_env.client_reg_psms[i]); + L2CA_DeregisterLECoc(s_l2cap_env.client_reg_psms[i]); + } + } +#endif + memset(&s_l2cap_env, 0, sizeof(s_l2cap_env)); + memset(s_conn_bind, 0, sizeof(s_conn_bind)); + memset(s_chan_bind, 0, sizeof(s_chan_bind)); + break; + +#if (BLE_L2CAP_COC_SERVER_INCLUDED == TRUE) + case BTC_BLE_L2CAP_ACT_CREATE_SERVER: { + btc_ble_l2cap_server_t *srv = btc_ble_l2cap_find_server_by_real_psm(arg->create_server.psm); + UINT16 reg_psm; + if (srv != NULL) { + /* Already registered for this PSM: refresh MTU instead of allocating + * another slot. A second L2CA_RegisterLECoc would overwrite the RCB + * and leak the previous slot. */ + srv->mtu = arg->create_server.mtu; + BTC_BLE_L2CAP_TRACE_API("create_server psm=0x%04x already registered", srv->real_psm); + break; + } + srv = btc_ble_l2cap_alloc_server(arg->create_server.psm, arg->create_server.mtu); + if (srv == NULL) { + BTC_BLE_L2CAP_TRACE_ERROR("no server slot psm=0x%04x", arg->create_server.psm); + break; + } + reg_psm = L2CA_RegisterLECoc(arg->create_server.psm, &s_l2cap_appl); + if (reg_psm == 0) { + srv->in_use = false; + BTC_BLE_L2CAP_TRACE_ERROR("RegisterLECoc failed psm=0x%04x", arg->create_server.psm); + break; + } + srv->reg_psm = reg_psm; + btc_ble_l2cap_register_psm_security(reg_psm); + BTC_BLE_L2CAP_TRACE_API("create_server real_psm=0x%04x reg_psm=0x%04x mtu=%u", + srv->real_psm, srv->reg_psm, srv->mtu); + break; + } + + case BTC_BLE_L2CAP_ACT_DELETE_SERVER: { + btc_ble_l2cap_server_t *srv = btc_ble_l2cap_find_server_by_real_psm(arg->delete_server.psm); + if (srv && srv->reg_psm) { + L2CA_DeregisterLECoc(srv->reg_psm); + memset(srv, 0, sizeof(*srv)); + BTC_BLE_L2CAP_TRACE_DEBUG("delete_server psm=0x%04x", arg->delete_server.psm); + } else { + BTC_BLE_L2CAP_TRACE_ERROR("delete_server psm=0x%04x not found", arg->delete_server.psm); + } + break; + } +#endif /* BLE_L2CAP_COC_SERVER_INCLUDED */ + +#if (BLE_L2CAP_COC_CLIENT_INCLUDED == TRUE) + case BTC_BLE_L2CAP_ACT_CONNECT: { + BD_ADDR bda; + tL2CAP_LE_CFG_INFO cfg = {0}; + UINT16 reg_psm; + UINT16 lcid; + + memset(bda, 0, BD_ADDR_LEN); + if (!btc_ble_l2cap_bda_from_conn_id(arg->connect.conn_id, bda)) { + BTC_BLE_L2CAP_TRACE_ERROR("invalid conn_id=%u", arg->connect.conn_id); + btc_ble_l2cap_report_connect_fail(arg->connect.conn_id, bda, L2CAP_CONN_NO_LINK); + break; + } + btc_ble_l2cap_bind_conn(arg->connect.conn_id, bda); + cfg.mtu = arg->connect.mtu; + bool newly_registered = false; + reg_psm = btc_ble_l2cap_get_or_register_psm(arg->connect.psm, &newly_registered); + if (reg_psm == 0) { + BTC_BLE_L2CAP_TRACE_ERROR("client RegisterLECoc failed psm=0x%04x", arg->connect.psm); + btc_ble_l2cap_report_connect_fail(arg->connect.conn_id, bda, L2CAP_CONN_NO_RESOURCES); + break; + } + lcid = L2CA_ConnectLECocReq(reg_psm, bda, &cfg); + if (lcid == 0) { + BTC_BLE_L2CAP_TRACE_ERROR("ConnectLECocReq failed conn_id=%u psm=0x%04x", + arg->connect.conn_id, arg->connect.psm); + /* Only deregister a PSM this attempt registered. A shared PSM (server + * or a prior client connection) must survive, or deregistering would + * tear down other active channels using the same RCB. */ + if (newly_registered && l2cu_find_ble_rcb_by_psm(reg_psm) != NULL) { + L2CA_DeregisterLECoc(reg_psm); + btc_ble_l2cap_untrack_client_psm(reg_psm); + } + /* No CCB was created, so ConnectCfm will never fire: report the + * failure and drop the conn-bind slot instead of leaking it. */ + btc_ble_l2cap_report_connect_fail(arg->connect.conn_id, bda, L2CAP_CONN_NO_RESOURCES); + break; + } + BTC_BLE_L2CAP_TRACE_API("connect conn_id=%u lcid=0x%04x", arg->connect.conn_id, lcid); + break; + } +#endif /* BLE_L2CAP_COC_CLIENT_INCLUDED */ + +#if (BLE_L2CAP_COC_SERVER_INCLUDED == TRUE) + case BTC_BLE_L2CAP_ACT_ACCEPT: { + BD_ADDR bda; + tL2CAP_LE_CFG_INFO cfg = {0}; + UINT16 result = arg->accept.accept ? L2CAP_CONN_OK : L2CAP_CONN_NO_PSM; + tL2C_CCB *p_ccb = l2cu_find_ccb_by_cid(NULL, arg->accept.chan_handle); + + /* Resolve the peer address from the (always unique) channel handle + * instead of conn_id. For GATT-less LE CoC peers conn_id is 0 for every + * such peer, so btc_ble_l2cap_bda_from_conn_id could return the wrong + * address and make L2CA_ConnectLECocRsp fail. Fall back to the conn_id + * lookup only if the CCB is no longer available. */ + if (p_ccb != NULL && p_ccb->p_lcb != NULL) { + memcpy(bda, p_ccb->p_lcb->remote_bd_addr, BD_ADDR_LEN); + } else if (!btc_ble_l2cap_bda_from_conn_id(arg->accept.conn_id, bda)) { + BTC_BLE_L2CAP_TRACE_ERROR("accept invalid conn_id=%u lcid=0x%04x", + arg->accept.conn_id, arg->accept.chan_handle); + /* Link torn down between ACCEPT_EVT and the app response: neither the + * CCB nor conn_id resolves, so L2CA_ConnectLECocRsp cannot run and no + * ConnectCfm will follow. For an accept, notify the app of the failure + * so its pending esp_ble_l2cap_accept() does not hang forever (mirrors + * the accept-fail path below and the CONNECT handler). No conn-bind was + * created yet, so nothing to unbind. Reject needs no event: the reject + * path never posts CONNECTED_EVT. */ + if (arg->accept.accept) { + esp_ble_l2cap_cb_param_t param = {0}; + param.coc_connected.conn_id = arg->accept.conn_id; + param.coc_connected.chan_handle = 0; + param.coc_connected.status = L2CAP_CONN_NO_LINK; + btc_ble_l2cap_post_event(ESP_BLE_L2CAP_COC_CONNECTED_EVT, ¶m); + } + break; + } + btc_ble_l2cap_bind_conn(arg->accept.conn_id, bda); + cfg.mtu = arg->accept.mtu; + if (!L2CA_ConnectLECocRsp(bda, arg->accept.l2cap_id, arg->accept.chan_handle, + result, 0, &cfg)) { + BTC_BLE_L2CAP_TRACE_ERROR("ConnectLECocRsp failed lcid=0x%04x", arg->accept.chan_handle); + if (arg->accept.accept) { + /* Accept failed at the stack API: no CCB and thus no ConnectCfm/ + * DisconnectInd will follow. Notify the app of the failure and drop + * the conn-bind slot created above, mirroring the CONNECT failure + * path (btc_ble_l2cap_report_connect_fail, which is client-only, so + * the equivalent is inlined here). Consult the shadow table as the + * reject path does so we do not unbind while another channel to this + * peer is still tracked with a pending DisconnectInd. */ + esp_ble_l2cap_cb_param_t param = {0}; + param.coc_connected.conn_id = arg->accept.conn_id; + param.coc_connected.chan_handle = 0; + param.coc_connected.status = L2CAP_CONN_NO_RESOURCES; + btc_ble_l2cap_post_event(ESP_BLE_L2CAP_COC_CONNECTED_EVT, ¶m); + if (!btc_ble_l2cap_has_other_active_coc(bda, arg->accept.chan_handle) && + !btc_ble_l2cap_chan_has_other(bda, arg->accept.chan_handle)) { + btc_ble_l2cap_unbind_bda(bda); + } + break; + } + } + /* On reject the stack releases the CCB directly (no DisconnectInd + * callback), so release the binding entry here to avoid leaking a slot. + * Consult the shadow table too (as disconnect_ind does) so we do not + * unbind while another channel to this peer is still tracked with a + * pending DisconnectInd. */ + if (!arg->accept.accept && + !btc_ble_l2cap_has_other_active_coc(bda, arg->accept.chan_handle) && + !btc_ble_l2cap_chan_has_other(bda, arg->accept.chan_handle)) { + btc_ble_l2cap_unbind_bda(bda); + } + break; + } +#endif /* BLE_L2CAP_COC_SERVER_INCLUDED */ + + case BTC_BLE_L2CAP_ACT_DISCONNECT: + if (!L2CA_LECocDisconnect(arg->disconnect.chan_handle)) { + BTC_BLE_L2CAP_TRACE_ERROR("disconnect failed lcid=0x%04x", arg->disconnect.chan_handle); + } + break; + + case BTC_BLE_L2CAP_ACT_SEND: { + BT_HDR *p_buf; + + if (arg->send.len == 0 || arg->send.data == NULL) { + BTC_BLE_L2CAP_TRACE_ERROR("send invalid lcid=0x%04x len=%u", + arg->send.chan_handle, arg->send.len); + break; + } + p_buf = (BT_HDR *)arg->send.data; + arg->send.data = NULL; + if (L2CA_LECocIsCongested(arg->send.chan_handle)) { + BTC_BLE_L2CAP_TRACE_DEBUG("send dropped, congested lcid=0x%04x", arg->send.chan_handle); + osi_free(p_buf); + break; + } + if (L2CA_LECocDataWrite(arg->send.chan_handle, p_buf) == L2CAP_DW_FAILED) { + /* L2CA_LECocDataWrite() already released p_buf on every DW_FAILED + * path; freeing it here would be a double free. */ + BTC_BLE_L2CAP_TRACE_ERROR("send failed lcid=0x%04x", arg->send.chan_handle); + } + break; + } + + case BTC_BLE_L2CAP_ACT_RECV_READY: + if (!L2CA_LECocGiveCredits(arg->recv_ready.chan_handle, 1)) { + BTC_BLE_L2CAP_TRACE_ERROR("recv_ready failed lcid=0x%04x", arg->recv_ready.chan_handle); + } + break; + + case BTC_BLE_L2CAP_ACT_SET_AUTO_CREDIT: + if (!L2CA_LECocSetAutoCredit(arg->set_auto_credit.chan_handle, + arg->set_auto_credit.enable ? TRUE : FALSE)) { + BTC_BLE_L2CAP_TRACE_ERROR("set_auto_credit failed lcid=0x%04x", + arg->set_auto_credit.chan_handle); + } + break; + +#if (BLE_L2CAP_ENHANCED_COC_INCLUDED == TRUE) +#if (BLE_L2CAP_COC_CLIENT_INCLUDED == TRUE) + case BTC_BLE_L2CAP_ACT_CONNECT_ECOC: { + BD_ADDR bda; + tL2CAP_LE_CFG_INFO cfg = {0}; + UINT16 lcids[BLE_MAX_L2CAP_CLIENTS]; + UINT16 reg_psm; + UINT8 num_started; + + memset(bda, 0, BD_ADDR_LEN); + if (!btc_ble_l2cap_bda_from_conn_id(arg->connect_ecoc.conn_id, bda)) { + BTC_BLE_L2CAP_TRACE_ERROR("ecoc connect invalid conn_id=%u", arg->connect_ecoc.conn_id); + btc_ble_l2cap_report_connect_fail(arg->connect_ecoc.conn_id, bda, L2CAP_CONN_NO_LINK); + break; + } + cfg.mtu = arg->connect_ecoc.mtu; + btc_ble_l2cap_bind_conn(arg->connect_ecoc.conn_id, bda); + bool ecoc_newly_registered = false; + reg_psm = btc_ble_l2cap_get_or_register_psm(arg->connect_ecoc.psm, &ecoc_newly_registered); + if (reg_psm == 0) { + BTC_BLE_L2CAP_TRACE_ERROR("client RegisterLECoc failed psm=0x%04x", arg->connect_ecoc.psm); + btc_ble_l2cap_report_connect_fail(arg->connect_ecoc.conn_id, bda, L2CAP_CONN_NO_RESOURCES); + break; + } + num_started = L2CA_ConnectLEEcocReq(reg_psm, bda, &cfg, + arg->connect_ecoc.num_chan, lcids); + if (num_started == 0) { + BTC_BLE_L2CAP_TRACE_ERROR("ConnectLEEcocReq failed"); + /* Only deregister a PSM this attempt registered; a shared PSM must + * survive so other active channels are not torn down. */ + if (ecoc_newly_registered && l2cu_find_ble_rcb_by_psm(reg_psm) != NULL) { + L2CA_DeregisterLECoc(reg_psm); + btc_ble_l2cap_untrack_client_psm(reg_psm); + } + /* No CCB created: report failure and release the conn-bind slot. */ + btc_ble_l2cap_report_connect_fail(arg->connect_ecoc.conn_id, bda, L2CAP_CONN_NO_RESOURCES); + } else { + BTC_BLE_L2CAP_TRACE_API("ecoc connect started n=%u", num_started); + } + break; + } +#endif /* BLE_L2CAP_COC_CLIENT_INCLUDED */ + + case BTC_BLE_L2CAP_ACT_RECONFIG: + if (!L2CA_LEEcocReconfig(arg->reconfig.chan_handles, arg->reconfig.num_chan, + arg->reconfig.mtu, arg->reconfig.mps)) { + BTC_BLE_L2CAP_TRACE_ERROR("LEEcocReconfig failed"); + } + break; +#endif + + default: + BTC_BLE_L2CAP_TRACE_ERROR("unknown act=%d", msg->act); + break; + } + + btc_ble_l2cap_arg_deep_free(msg); +} + +#endif /* BLE_L2CAP_COC_INCLUDED == TRUE */ diff --git a/components/bt/host/bluedroid/btc/profile/std/gap/btc_gap_ble.c b/components/bt/host/bluedroid/btc/profile/std/gap/btc_gap_ble.c index fcea1ecb070..bedbcb0d585 100644 --- a/components/bt/host/bluedroid/btc/profile/std/gap/btc_gap_ble.c +++ b/components/bt/host/bluedroid/btc/profile/std/gap/btc_gap_ble.c @@ -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" @@ -2284,6 +2287,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, ¶m, 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; @@ -3094,6 +3122,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; diff --git a/components/bt/host/bluedroid/btc/profile/std/include/btc_ble_l2cap.h b/components/bt/host/bluedroid/btc/profile/std/include/btc_ble_l2cap.h new file mode 100644 index 00000000000..3bd1bb5c9bd --- /dev/null +++ b/components/bt/host/bluedroid/btc/profile/std/include/btc_ble_l2cap.h @@ -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__ */ diff --git a/components/bt/host/bluedroid/btc/profile/std/include/btc_gap_ble.h b/components/bt/host/bluedroid/btc/profile/std/include/btc_gap_ble.h index 41fc24c7a74..b503e66e813 100644 --- a/components/bt/host/bluedroid/btc/profile/std/include/btc_gap_ble.h +++ b/components/bt/host/bluedroid/btc/profile/std/include/btc_gap_ble.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__ */ diff --git a/components/bt/host/bluedroid/common/include/common/bluedroid_user_config.h b/components/bt/host/bluedroid/common/include/common/bluedroid_user_config.h index 9b29cf13569..9b10f19d488 100644 --- a/components/bt/host/bluedroid/common/include/common/bluedroid_user_config.h +++ b/components/bt/host/bluedroid/common/include/common/bluedroid_user_config.h @@ -131,6 +131,12 @@ #define UC_BT_BLE_50_FEATURES_SUPPORTED FALSE #endif +#ifdef CONFIG_BT_BLE_PERIPH_PSEUDO_ADDR_BOND +#define UC_BT_BLE_PERIPH_PSEUDO_ADDR_BOND CONFIG_BT_BLE_PERIPH_PSEUDO_ADDR_BOND +#else +#define UC_BT_BLE_PERIPH_PSEUDO_ADDR_BOND FALSE +#endif + #ifdef CONFIG_BT_BLE_42_FEATURES_SUPPORTED #define UC_BT_BLE_42_FEATURES_SUPPORTED CONFIG_BT_BLE_42_FEATURES_SUPPORTED #else @@ -613,6 +619,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 diff --git a/components/bt/host/bluedroid/common/include/common/bt_target.h b/components/bt/host/bluedroid/common/include/common/bt_target.h index d0a9bc902bf..b91b2c62530 100644 --- a/components/bt/host/bluedroid/common/include/common/bt_target.h +++ b/components/bt/host/bluedroid/common/include/common/bt_target.h @@ -199,6 +199,14 @@ #define BLE_50_FEATURE_SUPPORT FALSE #endif +/* Peripheral dual local-identity bond isolation via Host-internal pseudo + * address. Guarded so default builds keep the legacy single-bond behavior. */ +#if (UC_BT_BLE_PERIPH_PSEUDO_ADDR_BOND == TRUE) +#define BLE_PERIPH_PSEUDO_ADDR_BOND TRUE +#else +#define BLE_PERIPH_PSEUDO_ADDR_BOND FALSE +#endif + #if (UC_BT_BLE_ENABLED ==TRUE) #if (UC_BT_BLE_42_FEATURES_SUPPORTED == TRUE || BLE_50_FEATURE_SUPPORT == FALSE) #define BLE_42_FEATURE_SUPPORT TRUE @@ -1406,7 +1414,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 */ diff --git a/components/bt/host/bluedroid/hci/packet_fragmenter.c b/components/bt/host/bluedroid/hci/packet_fragmenter.c index 26671bfe657..3d8bdbd1f77 100644 --- a/components/bt/host/bluedroid/hci/packet_fragmenter.c +++ b/components/bt/host/bluedroid/hci/packet_fragmenter.c @@ -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); diff --git a/components/bt/host/bluedroid/stack/btm/btm_acl.c b/components/bt/host/bluedroid/stack/btm/btm_acl.c index cc22fb5e4e8..5e40ee17ccc 100644 --- a/components/bt/host/bluedroid/stack/btm/btm_acl.c +++ b/components/bt/host/bluedroid/stack/btm/btm_acl.c @@ -43,6 +43,10 @@ #include "stack/btu.h" #include "stack/btm_api.h" #include "btm_int.h" +#if (BLE_INCLUDED == TRUE && SMP_INCLUDED == TRUE && BLE_PERIPH_PSEUDO_ADDR_BOND == TRUE) +#include "btm_ble_int.h" +#include "btm_ble_pseudo.h" +#endif #include "stack/acl_hci_link_interface.h" #include "l2c_int.h" #include "stack/l2cap_hci_link_interface.h" @@ -571,12 +575,6 @@ void btm_acl_removed (BD_ADDR bda, tBT_TRANSPORT transport) btm_cb.ble_ctr_cb.inq_var.connectable_mode, p->link_role); - if (p->transport == BT_TRANSPORT_LE) { -#if (BLE_50_FEATURE_SUPPORT == TRUE) && (BLE_50_EXTEND_ADV_EN == TRUE) - btm_ble_clear_ext_adv_ter_con_handle(p->hci_handle); -#endif - } - p_dev_rec = btm_find_dev(bda); if ( p_dev_rec) { BTM_TRACE_DEBUG("before update p_dev_rec->sec_flags=0x%x\n", p_dev_rec->sec_flags); @@ -603,6 +601,11 @@ void btm_acl_removed (BD_ADDR bda, tBT_TRANSPORT transport) #if (CLASSIC_BT_INCLUDED == TRUE) list_remove(btm_cb.p_pm_mode_db_list, p->p_pm_mode_db); #endif // #if (CLASSIC_BT_INCLUDED == TRUE) +#if (BLE_50_FEATURE_SUPPORT == TRUE) && (BLE_50_EXTEND_ADV_EN == TRUE) + if (p->transport == BT_TRANSPORT_LE) { + btm_ble_clear_ext_adv_ter_con_handle(p->hci_handle); + } +#endif /* Remove and free the ACL connection data */ list_remove(btm_cb.p_acl_db_list, p); p = NULL; @@ -2920,5 +2923,16 @@ BOOLEAN btm_acl_disconnected(UINT16 handle, UINT8 reason) #endif /* SMP_INCLUDED == TRUE */ +#if (BLE_INCLUDED == TRUE && SMP_INCLUDED == TRUE && BLE_PERIPH_PSEUDO_ADDR_BOND == TRUE) + /* Drop the per-connection pseudo identity mapping for this handle. */ + BLE_PSEUDO_DBG("disconnect: handle=0x%x reason=0x%x -> cleanup", handle, reason); + btm_ble_conn_identity_unregister(handle); +#endif + +#if (BLE_50_FEATURE_SUPPORT == TRUE) && (BLE_50_EXTEND_ADV_EN == TRUE) + /* Unbind ext-adv sets so a reused handle cannot leak into another inst. */ + btm_ble_clear_ext_adv_ter_con_handle(handle); +#endif + return status; } diff --git a/components/bt/host/bluedroid/stack/btm/btm_ble.c b/components/bt/host/bluedroid/stack/btm/btm_ble.c index 7bc86d38e70..51768b51fc6 100644 --- a/components/bt/host/bluedroid/stack/btm/btm_ble.c +++ b/components/bt/host/bluedroid/stack/btm/btm_ble.c @@ -36,12 +36,17 @@ #include "stack/gap_api.h" //#include "bt_utils.h" #include "device/controller.h" +#include "btm_ble_pseudo.h" +#if (BLE_INCLUDED == TRUE && SMP_INCLUDED == TRUE && BLE_PERIPH_PSEUDO_ADDR_BOND == TRUE) +#include "gatt_int.h" +#endif //#define LOG_TAG "bt_btm_ble" //#include "osi/include/log.h" #if BLE_INCLUDED == TRUE extern void BTM_UpdateAddrInfor(uint8_t addr_type, BD_ADDR bda); #if SMP_INCLUDED == TRUE +#include "smp_int.h" // The temp variable to pass parameter between functions when in the connected event callback. static BOOLEAN temp_enhanced = FALSE; extern BOOLEAN aes_cipher_msg_auth_code(BT_OCTET16 key, UINT8 *input, UINT16 length, @@ -50,6 +55,21 @@ extern void smp_link_encrypted(BD_ADDR bda, UINT8 encr_enable); extern BOOLEAN smp_proc_ltk_request(BD_ADDR bda); #endif extern void gatt_notify_enc_cmpl(BD_ADDR bd_addr); +#if (BLE_PERIPH_PSEUDO_ADDR_BOND == TRUE) +static BOOLEAN btm_ble_make_conn_pseudo(UINT16 handle, BD_ADDR real_peer, + tBLE_ADDR_TYPE peer_type, BD_ADDR pseudo_out); +static void btm_ble_pseudo_bringup_conn(UINT16 handle, UINT8 role, + const BD_ADDR hash_peer, UINT8 hash_peer_type, + const BD_ADDR fallback_bda, UINT8 bda_type, + UINT16 conn_interval, UINT16 conn_latency, + UINT16 conn_timeout, BOOLEAN match, + const UINT8 *air_peer, UINT8 air_peer_type, + const char *tag, BD_ADDR conn_index_bda_out); +static void btm_ble_pseudo_pick_peer_identity(tBTM_SEC_DEV_REC *p_rec, const BD_ADDR on_air, + UINT8 on_air_type, + BD_ADDR peer_out, UINT8 *p_peer_type); +extern tBTM_SEC_DEV_REC *btm_find_dev_by_identity_addr(BD_ADDR bd_addr, UINT8 addr_type); +#endif /*******************************************************************************/ /* External Function to be called by other modules */ /*******************************************************************************/ @@ -280,6 +300,90 @@ void BTM_GetDeviceDHK (BT_OCTET16 dhk) ** Returns void ** *******************************************************************************/ +#if (BLE_INCLUDED == TRUE && SMP_INCLUDED == TRUE && BLE_PERIPH_PSEUDO_ADDR_BOND == TRUE) +/******************************************************************************* +** Function BTM_BleGetRealPeerByPseudo +** +** Description Reverse map a Host pseudo address (as seen by the app in +** remote_bda for a dual-identity link) to the real peer +** identity. Returns TRUE if the pseudo is currently known. +*******************************************************************************/ +BOOLEAN BTM_BleGetRealPeerByPseudo(BD_ADDR pseudo, BD_ADDR real_peer) +{ + return btm_ble_pseudo_to_real_peer(pseudo, real_peer); +} + +/******************************************************************************* +** Function BTM_BleGetConnIdentityByPseudo +** +** Description Return the full identity (real peer + local identity and +** their address types) for a connected dual-identity link, +** keyed by the pseudo address the application sees. +** +** Returns TRUE if the pseudo belongs to a finalized link. +*******************************************************************************/ +BOOLEAN BTM_BleGetConnIdentityByPseudo(BD_ADDR pseudo, BD_ADDR peer, BD_ADDR local, + UINT8 *peer_type, UINT8 *local_type) +{ + tBTM_BLE_CONN_IDENTITY ent; + + if (!btm_ble_conn_identity_get_by_pseudo(pseudo, &ent) || !ent.local_ready) { + return FALSE; + } + if (peer) { + memcpy(peer, ent.id.peer, BD_ADDR_LEN); + } + if (local) { + memcpy(local, ent.id.local, BD_ADDR_LEN); + } + if (peer_type) { + *peer_type = ent.id.peer_type; + } + if (local_type) { + *local_type = ent.id.local_type; + } + return TRUE; +} + +/******************************************************************************* +** Function BTM_BleComputePseudoForIdentity +** +** Description Recompute the deterministic Host pseudo for a (local, peer) +** identity pair. Lets the app target a bond section by its +** identity even when the link is no longer connected. +*******************************************************************************/ +void BTM_BleComputePseudoForIdentity(BD_ADDR local, UINT8 local_type, + BD_ADDR peer, UINT8 peer_type, BD_ADDR pseudo) +{ + tBLE_CONN_IDENTITY id; + memset(&id, 0, sizeof(id)); + memcpy(id.local, local, BD_ADDR_LEN); + memcpy(id.peer, peer, BD_ADDR_LEN); + id.local_type = local_type; + id.peer_type = peer_type; + btm_ble_identity_to_pseudo(&id, pseudo); +} + +/******************************************************************************* +** Function BTM_BleMarkPseudoBond +** +** Description Tag the device record for bd_addr as a pseudo-address bond +** so the BTM_LE_KEY_PID handler keeps its pseudo bd_addr and +** skips consolidation while loading bonds from NVS. +*******************************************************************************/ +BOOLEAN BTM_BleMarkPseudoBond(BD_ADDR bd_addr) +{ + tBTM_SEC_DEV_REC *p_rec = btm_find_dev(bd_addr); + if (p_rec == NULL) { + BLE_PSEUDO_DBG("mark pseudo bond: no rec for " BLE_PSEUDO_BDA_FMT, BLE_PSEUDO_BDA(bd_addr)); + return FALSE; + } + p_rec->ble.is_pseudo_bond = TRUE; + BLE_PSEUDO_DBG("mark pseudo bond: " BLE_PSEUDO_BDA_FMT, BLE_PSEUDO_BDA(bd_addr)); + return TRUE; +} +#endif + void BTM_ReadConnectionAddr (BD_ADDR remote_bda, BD_ADDR local_conn_addr, tBLE_ADDR_TYPE *p_addr_type) { tACL_CONN *p_acl = btm_bda_to_acl(remote_bda, BT_TRANSPORT_LE); @@ -458,7 +562,11 @@ void BTM_BleConfirmReply (BD_ADDR bd_addr, UINT8 res) return; } - p_dev_rec->sec_flags |= BTM_SEC_LE_AUTHENTICATED; + /* Only mark the link as authenticated when the user accepts the comparison; + * a rejected/failed confirm must not raise the security level. */ + if (res_smp == SMP_SUCCESS) { + p_dev_rec->sec_flags |= BTM_SEC_LE_AUTHENTICATED; + } BTM_TRACE_DEBUG ("%s\n", __func__); SMP_ConfirmReply(bd_addr, res_smp); } @@ -488,6 +596,13 @@ void BTM_BleOobDataReply(BD_ADDR bd_addr, UINT8 res, UINT8 len, UINT8 *p_data) BTM_TRACE_ERROR("BTM_BleOobDataReply() to Unknown device"); return; } + + /* Ignore OOB data supplied for a device other than the one currently pairing. */ + if (memcmp(bd_addr, smp_cb.pairing_bda, BD_ADDR_LEN) != 0) { + BTM_TRACE_ERROR("BTM_BleOobDataReply() - Wrong BD Addr"); + return; + } + if (res_smp == SMP_SUCCESS) { p_dev_rec->sec_flags |= BTM_SEC_LE_AUTHENTICATED; } @@ -971,10 +1086,6 @@ tBTM_SEC_ACTION btm_ble_determine_security_act(BOOLEAN is_originator, BD_ADDR bd return BTM_SEC_ENC_PENDING; } - if (ble_sec_act == BTM_BLE_SEC_REQ_ACT_NONE) { - return BTM_SEC_OK; - } - UINT8 sec_flag = 0; BTM_GetSecurityFlagsByTransport(bdaddr, &sec_flag, BT_TRANSPORT_LE); @@ -1044,6 +1155,12 @@ BOOLEAN btm_ble_start_sec_check(BD_ADDR bd_addr, UINT16 psm, BOOLEAN is_originat return FALSE; } + if (btm_find_dev(bd_addr) == NULL) { + BTM_TRACE_ERROR ("%s no device record for bd_addr=" MACSTR, __func__, MAC2STR(bd_addr)); + (*p_callback) (bd_addr, BT_TRANSPORT_LE, p_ref_data, BTM_UNKNOWN_ADDR); + return FALSE; + } + tBTM_SEC_ACTION sec_act = btm_ble_determine_security_act(is_originator, bd_addr, p_serv_rec->security_flags); @@ -1281,10 +1398,36 @@ void btm_sec_save_le_key(BD_ADDR bd_addr, tBTM_LE_KEY_TYPE key_type, tBTM_LE_KEY p_rec->ble.static_addr_type = p_keys->pid_key.addr_type; p_rec->ble.key_type |= BTM_LE_KEY_PID; BTM_TRACE_DEBUG("BTM_LE_KEY_PID key_type=0x%x save peer IRK", p_rec->ble.key_type); - /* update device record address as static address */ - memcpy(p_rec->bd_addr, p_keys->pid_key.static_addr, BD_ADDR_LEN); - /* combine DUMO device security record if needed */ - btm_consolidate_dev(p_rec); +#if (BLE_INCLUDED == TRUE && SMP_INCLUDED == TRUE && BLE_PERIPH_PSEUDO_ADDR_BOND == TRUE) + /* A pseudo bond record must NEVER be consolidated onto the peer + * Identity, otherwise two local identities of the same phone (which + * share the peer IRK / static_addr) collapse into a single device + * record and overwrite each other's LTK. Detect it two ways: + * 1) an active dual-identity link: the side table has this handle; + * 2) an NVS-loaded bond marked as pseudo: BTA_DmAddBleDevice queued + * is_pseudo_bond=TRUE and bta_dm_add_ble_device called + * BTM_BleMarkPseudoBond() before this PID was added. This is the + * authoritative signal (no live connection exists at boot). */ + if (!btm_ble_conn_identity_exists_by_handle(p_rec->ble_hci_handle) && + !p_rec->ble.is_pseudo_bond) +#endif + { +#if (BLE_INCLUDED == TRUE && SMP_INCLUDED == TRUE && BLE_PERIPH_PSEUDO_ADDR_BOND == TRUE) + BLE_PSEUDO_DBG("PID: handle=0x%x NOT a pseudo bond -> overwrite bd_addr + consolidate (default)", + p_rec->ble_hci_handle); +#endif + /* update device record address as static address */ + memcpy(p_rec->bd_addr, p_keys->pid_key.static_addr, BD_ADDR_LEN); + /* combine DUMO device security record if needed */ + btm_consolidate_dev(p_rec); + } +#if (BLE_INCLUDED == TRUE && SMP_INCLUDED == TRUE && BLE_PERIPH_PSEUDO_ADDR_BOND == TRUE) + else { + BLE_PSEUDO_DBG("PID: handle=0x%x IS a pseudo bond -> keep bd_addr=" BLE_PSEUDO_BDA_FMT + ", skip consolidate (LTK isolated)", + p_rec->ble_hci_handle, BLE_PSEUDO_BDA(p_rec->bd_addr)); + } +#endif break; case BTM_LE_KEY_PCSRK: @@ -1862,12 +2005,13 @@ UINT8 btm_ble_br_keys_req(tBTM_SEC_DEV_REC *p_dev_rec, tBTM_LE_IO_REQ *p_data) ** air for THIS connection and causes SMP c1 / f5 / f6 ** to compute the wrong local address (pair fail 0x04). ** -** RPA paths (own_addr_type 0x02, or 0x03 with a valid +** RPA paths (own_addr_type 0x02 or 0x03 with a valid ** local RPA in the LE Enhanced Connection Complete event) -** are left untouched. For 0x03 when the controller falls -** back to per-set identity (zero local_rpa), replace the -** global private_addr written by -** btm_ble_refresh_local_resolvable_private_addr(). +** are left untouched. When the controller falls back to +** identity (zero local_rpa) the global private_addr written +** by btm_ble_refresh_local_resolvable_private_addr() is +** replaced: for 0x03 with the per-set static random, and for +** 0x02 with the public identity address. ** ** No-op when no ext-adv instance matches the handle ** (initiator role or legacy adv). @@ -1912,6 +2056,15 @@ void btm_ble_adjust_conn_addr_for_ext_adv(UINT16 handle) memcpy(p_acl->conn_addr, extend_adv_cb.inst[inst].rand_addr, BD_ADDR_LEN); + } else if (on_air_type == BLE_ADDR_PUBLIC_ID && + !BTM_BLE_IS_RESOLVE_BDA(p_acl->conn_addr)) { + /* Identity fallback: controller used the public identity, not an RPA. + * The RPA path (conn_addr already holds a valid local RPA) is left + * untouched by the IS_RESOLVE_BDA guard, mirroring the 0x03 case. */ + p_acl->conn_addr_type = BLE_ADDR_PUBLIC; + memcpy(p_acl->conn_addr, + controller_get_interface()->get_address()->address, + BD_ADDR_LEN); } BTM_TRACE_DEBUG("%s: handle=0x%04x inst=%u type=%u addr=%02x:%02x:%02x:%02x:%02x:%02x", @@ -1921,6 +2074,69 @@ void btm_ble_adjust_conn_addr_for_ext_adv(UINT16 handle) } #endif /* (BLE_50_FEATURE_SUPPORT == TRUE) && (BLE_50_EXTEND_ADV_EN == TRUE) && (CONTROLLER_RPA_LIST_ENABLE == TRUE) */ +#if (BLE_PERIPH_PSEUDO_ADDR_BOND == TRUE) +/******************************************************************************* +** Function btm_ble_pseudo_bringup_conn +** +** Description Shared peripheral connection-completion path for the +** pseudo-address bond feature, used by both the synchronous +** btm_ble_conn_complete() branch and the asynchronous RPA +** resolution callback. It derives the Host pseudo from the +** (local, peer-identity) pair and brings the link up on that +** pseudo, or keeps the real peer (fallback_bda) when the local +** identity is not yet resolvable (deferred to adv-terminate). +** +** When air_peer is non-NULL the ACL active_remote_addr is +** restored to the real on-air RPA so SC pairing f5/f6 stays +** valid after host RPA resolution rewrote bda to the pseudo. +** +** The address actually used to index the ACL / device record +** is written to conn_index_bda_out. tag only labels the trace. +*******************************************************************************/ +static void btm_ble_pseudo_bringup_conn(UINT16 handle, UINT8 role, + const BD_ADDR hash_peer, UINT8 hash_peer_type, + const BD_ADDR fallback_bda, UINT8 bda_type, + UINT16 conn_interval, UINT16 conn_latency, + UINT16 conn_timeout, BOOLEAN match, + const UINT8 *air_peer, UINT8 air_peer_type, + const char *tag, BD_ADDR conn_index_bda_out) +{ + BD_ADDR pseudo; + + if (role == HCI_ROLE_SLAVE && + btm_ble_make_conn_pseudo(handle, (UINT8 *)hash_peer, hash_peer_type, pseudo)) { + memcpy(conn_index_bda_out, pseudo, BD_ADDR_LEN); + BLE_PSEUDO_DBG("conn_complete[%s]: keyed handle=0x%x peer=" BLE_PSEUDO_BDA_FMT + " -> pseudo=" BLE_PSEUDO_BDA_FMT, + tag, handle, BLE_PSEUDO_BDA(hash_peer), BLE_PSEUDO_BDA(pseudo)); + } else { + memcpy(conn_index_bda_out, fallback_bda, BD_ADDR_LEN); + if (role == HCI_ROLE_SLAVE) { + BLE_PSEUDO_DBG("conn_complete[%s]: local NOT ready, defer to adv-terminate; handle=0x%x peer=" + BLE_PSEUDO_BDA_FMT, tag, handle, BLE_PSEUDO_BDA(fallback_bda)); + } + } + + btm_ble_connected(conn_index_bda_out, handle, HCI_ENCRYPT_MODE_DISABLED, role, bda_type, match); + l2cble_conn_comp(handle, role, conn_index_bda_out, bda_type, conn_interval, + conn_latency, conn_timeout); + + /* Host RPA resolution replaced the on-air RPA with a stored pseudo on the + * ACL. Restore the real on-air peer address so SC pairing f5/f6 uses what + * the peer actually put on air (otherwise the DHKey check fails on a + * resolved reconnect). The pseudo stays the dev_rec index. */ + if (air_peer != NULL && role == HCI_ROLE_SLAVE) { + tACL_CONN *p_air = btm_handle_to_acl(handle); + if (p_air != NULL && BTM_BLE_IS_RESOLVE_BDA(air_peer)) { + memcpy(p_air->active_remote_addr, air_peer, BD_ADDR_LEN); + p_air->active_remote_addr_type = air_peer_type; + BLE_PSEUDO_DBG("force air addr: handle=0x%x active_remote=" BLE_PSEUDO_BDA_FMT " type %u", + handle, BLE_PSEUDO_BDA(air_peer), air_peer_type); + } + } +} +#endif /* BLE_PERIPH_PSEUDO_ADDR_BOND */ + #if (BLE_PRIVACY_SPT == TRUE ) /******************************************************************************* ** @@ -1940,6 +2156,10 @@ static void btm_ble_resolve_random_addr_on_conn_cmpl(void *p_rec, void *p_data) BD_ADDR bda, local_rpa, peer_rpa; UINT16 conn_interval, conn_latency, conn_timeout; BOOLEAN match = FALSE; +#if (BLE_PERIPH_PSEUDO_ADDR_BOND == TRUE) + BD_ADDR air_peer; /* on-air peer address (RPA) before resolution rewrite */ + UINT8 air_peer_type; +#endif ++p; STREAM_TO_UINT16 (handle, p); @@ -1959,6 +2179,14 @@ static void btm_ble_resolve_random_addr_on_conn_cmpl(void *p_rec, void *p_data) handle = HCID_GET_HANDLE (handle); BTM_TRACE_EVENT ("%s\n", __func__); +#if (BLE_PERIPH_PSEUDO_ADDR_BOND == TRUE) + /* Snapshot the real on-air peer address (the RPA the controller reported) + * BEFORE host RPA resolution rewrites bda to a stored pseudo_addr. SC + * pairing f5/f6 must use this real on-air address, not the pseudo. */ + memcpy(air_peer, bda, BD_ADDR_LEN); + air_peer_type = bda_type; +#endif + if (match_rec) { BTM_TRACE_DEBUG("%s matched and resolved random address", __func__); match = TRUE; @@ -1974,10 +2202,38 @@ static void btm_ble_resolve_random_addr_on_conn_cmpl(void *p_rec, void *p_data) BTM_TRACE_DEBUG("%s unable to match and resolve random address", __func__); } +#if (BLE_PERIPH_PSEUDO_ADDR_BOND == TRUE) + { + BD_ADDR conn_bda; + BD_ADDR hash_peer; + UINT8 hash_peer_type = air_peer_type; + + /* Derive the pseudo from the PEER IDENTITY, never from a transient RPA + * or the stored pseudo_addr. bda may have been rewritten to the old + * pseudo above; use air_peer as the on-air fallback. */ + btm_ble_pseudo_pick_peer_identity(match_rec, air_peer, air_peer_type, hash_peer, &hash_peer_type); + + /* Bring the link up on the real on-air address (air_peer), NOT the + * possibly-rewritten bda. When the peer is already bonded under another + * local identity, btm_ble_init_pseudo_addr() above rewrites bda to that + * other identity's stored pseudo; using it as the deferred fallback + * would make this second link collide with the first link's LCB / GATT + * TCB (same remote_bd_addr) instead of getting its own, so the app would + * never receive a CONNECT event for the second identity. air_peer is the + * unique on-air address; finalize re-keys it to f(local, peer) at + * adv-terminate. air_peer is also passed (last two real args) so the ACL + * active_remote_addr is restored to the real on-air RPA for SC f5/f6. */ + btm_ble_pseudo_bringup_conn(handle, role, hash_peer, hash_peer_type, + air_peer, air_peer_type, conn_interval, conn_latency, + conn_timeout, match, air_peer, air_peer_type, + "rpa", conn_bda); + } +#else btm_ble_connected(bda, handle, HCI_ENCRYPT_MODE_DISABLED, role, bda_type, match); l2cble_conn_comp (handle, role, bda, bda_type, conn_interval, conn_latency, conn_timeout); +#endif #if (BLE_50_FEATURE_SUPPORT == TRUE) && (BLE_50_EXTEND_ADV_EN == TRUE) && (CONTROLLER_RPA_LIST_ENABLE == TRUE) /* Multi-ADV: fix up p_acl->conn_addr / conn_addr_type from per-set state. */ @@ -2024,11 +2280,44 @@ void btm_ble_connected (UINT8 *bda, UINT16 handle, UINT8 enc_mode, UINT8 role, } #endif +#if (BLE_PERIPH_PSEUDO_ADDR_BOND == TRUE) + /* Dual local-identity: a phone connecting through a SECOND local identity + * exposes the SAME peer IRK, so btm_find_dev(bda) resolves the on-air RPA to + * the FIRST identity's record, which belongs to a different, still-connected + * handle. Reusing it here would steal that live link's record (overwrite its + * ble_hci_handle and pseudo_addr) and break its encrypted session. Allocate a + * fresh record instead; this deferred link is re-keyed to its own + * f(local, peer) pseudo at adv-terminate finalize. */ + tBTM_SEC_DEV_REC *no_hijack_exclude = NULL; + if (role == HCI_ROLE_SLAVE && p_dev_rec && + p_dev_rec->ble_hci_handle != BTM_SEC_INVALID_HANDLE && + p_dev_rec->ble_hci_handle != handle && + btm_handle_to_acl(p_dev_rec->ble_hci_handle) != NULL) { + BLE_PSEUDO_DBG("connected: " BLE_PSEUDO_BDA_FMT " resolves to live handle 0x%x (rec %p);" + " alloc fresh rec for handle 0x%x (no hijack)", + BLE_PSEUDO_BDA(bda), p_dev_rec->ble_hci_handle, p_dev_rec, handle); + /* Keep the live record we just refused to hijack out of the recycle + * pool: when the device table is full btm_sec_alloc_dev() would call + * btm_find_oldest_dev_ex(NULL) and could pick this very record (it does + * not check for an active ACL), memset it and destroy the first + * identity's keys/handle. Exclude it explicitly, mirroring + * btm_ble_pseudo_finalize_local(). */ + no_hijack_exclude = p_dev_rec; + p_dev_rec = NULL; + } +#endif + if (!p_dev_rec) { /* There is no device record for new connection. Allocate one */ +#if (BLE_PERIPH_PSEUDO_ADDR_BOND == TRUE) + if ((p_dev_rec = btm_sec_alloc_dev_ex (bda, no_hijack_exclude)) == NULL) { + return; + } +#else if ((p_dev_rec = btm_sec_alloc_dev (bda)) == NULL) { return; } +#endif } else { /* Update the timestamp for this device */ p_dev_rec->timestamp = btm_cb.dev_rec_count++; } @@ -2060,6 +2349,356 @@ void btm_ble_connected (UINT8 *bda, UINT16 handle, UINT8 enc_mode, UINT8 role, return; } +#if (BLE_PERIPH_PSEUDO_ADDR_BOND == TRUE) +/******************************************************************************* +** Function btm_ble_resolve_conn_local +** +** Description Resolve the local identity (Public or fixed Static Random) +** that produced this peripheral connection from its ext-adv +** instance. Returns TRUE and fills id->local / local_type +** when the ext-adv instance is resolvable for the handle. +*******************************************************************************/ +static BOOLEAN btm_ble_resolve_conn_local(UINT16 handle, tBLE_CONN_IDENTITY *id) +{ +#if (BLE_50_FEATURE_SUPPORT == TRUE) && (BLE_50_EXTEND_ADV_EN == TRUE) + UINT8 inst = BTM_BleGetExtAdvInstByConHandle(handle); + if (inst < MAX_BLE_ADV_INSTANCE) { + tBLE_ADDR_TYPE own = extend_adv_cb.inst[inst].own_addr_type; + if (own == BLE_ADDR_PUBLIC || own == BLE_ADDR_PUBLIC_ID) { + memcpy(id->local, controller_get_interface()->get_address()->address, BD_ADDR_LEN); + id->local_type = BLE_ADDR_PUBLIC; + return TRUE; + } else if ((own == BLE_ADDR_RANDOM || own == BLE_ADDR_RANDOM_ID) && + extend_adv_cb.inst[inst].rand_addr_set) { + memcpy(id->local, extend_adv_cb.inst[inst].rand_addr, BD_ADDR_LEN); + id->local_type = BLE_ADDR_RANDOM; + return TRUE; + } + } +#else + UNUSED(handle); + UNUSED(id); +#endif + return FALSE; +} + +/******************************************************************************* +** Function btm_ble_make_conn_pseudo +** +** Description Resolve the local identity, derive the Host pseudo and +** register the (handle -> pseudo, identity) side table. +** Returns TRUE and fills pseudo_out when a usable local +** identity is known; FALSE if the ext-adv instance is not +** yet resolvable (the connection then keeps using the real +** peer until btm_ble_pseudo_finalize_local() at adv-terminate). +*******************************************************************************/ +static BOOLEAN btm_ble_make_conn_pseudo(UINT16 handle, BD_ADDR real_peer, + tBLE_ADDR_TYPE peer_type, BD_ADDR pseudo_out) +{ + tBLE_CONN_IDENTITY id; + + memset(&id, 0, sizeof(id)); + memcpy(id.peer, real_peer, BD_ADDR_LEN); + id.peer_type = peer_type; + + if (!btm_ble_resolve_conn_local(handle, &id)) { + return FALSE; + } + + btm_ble_identity_to_pseudo(&id, pseudo_out); + /* The pseudo is already traced by btm_ble_identity_to_pseudo() and + * btm_ble_conn_identity_register() via BLE_PSEUDO_DBG. */ + return btm_ble_conn_identity_register(handle, &id, pseudo_out, TRUE); +} + +/******************************************************************************* +** Function btm_ble_pseudo_rekey_link +** +** Description Re-key the whole per-link chain (GATT TCB, L2CAP LCB, ACL, +** device record) from its current index address to the given +** pseudo, consistently. ACL active_remote_addr (real on-air +** address) is left untouched so SMP cryptography stays valid. +*******************************************************************************/ +static void btm_ble_pseudo_rekey_link(UINT16 handle, tACL_CONN *p_acl, const BD_ADDR pseudo) +{ + if (p_acl == NULL || memcmp(p_acl->remote_addr, pseudo, BD_ADDR_LEN) == 0) { + return; + } + + BLE_PSEUDO_DBG("re-key: handle=0x%x " BLE_PSEUDO_BDA_FMT " -> " BLE_PSEUDO_BDA_FMT, + handle, BLE_PSEUDO_BDA(p_acl->remote_addr), BLE_PSEUDO_BDA(pseudo)); + + tGATT_TCB *p_tcb = gatt_find_tcb_by_addr(p_acl->remote_addr, BT_TRANSPORT_LE); + if (p_tcb) { + memcpy(p_tcb->peer_bda, pseudo, BD_ADDR_LEN); + } + + tL2C_LCB *p_lcb = l2cu_find_lcb_by_handle(handle); + if (p_lcb) { + memcpy(p_lcb->remote_bd_addr, pseudo, BD_ADDR_LEN); + } + + tBTM_SEC_DEV_REC *p_rec = btm_find_dev_by_handle(handle); + if (p_rec) { + memcpy(p_rec->bd_addr, pseudo, BD_ADDR_LEN); + memcpy(p_rec->ble.pseudo_addr, pseudo, BD_ADDR_LEN); + + /* Remove any OTHER device record that still carries this same pseudo + * (a stale duplicate left by an earlier pairing of the same + * (local,peer)). Keeping it would let btm_find_dev() return the stale + * record with an old LTK on a later encrypted reconnect -> MIC failure. + * Only exact-pseudo duplicates are removed, so other local identities + * (different pseudo) are never touched. */ + list_node_t *p_node = list_begin(btm_cb.p_sec_dev_rec_list); + while (p_node) { + tBTM_SEC_DEV_REC *p_dup = list_node(p_node); + p_node = list_next(p_node); + if (p_dup != p_rec && (p_dup->sec_flags & BTM_SEC_IN_USE) && + memcmp(p_dup->bd_addr, pseudo, BD_ADDR_LEN) == 0) { + BLE_PSEUDO_DBG("re-key: drop stale dup rec %p for pseudo " BLE_PSEUDO_BDA_FMT, + p_dup, BLE_PSEUDO_BDA(pseudo)); + /* Use the canonical free path so the stale LTK / BLE keys are + * zeroed before the record memory is released; it also removes + * the record from the list once BTM_SEC_IN_USE is cleared. */ + btm_sec_free_dev(p_dup, BT_TRANSPORT_LE); + } + } + } + + memcpy(p_acl->remote_addr, pseudo, BD_ADDR_LEN); + BLE_PSEUDO_DBG("re-key: done handle=0x%x tcb=%p lcb=%p rec=%p", handle, p_tcb, p_lcb, p_rec); +} + +/******************************************************************************* +** Function btm_ble_pseudo_pick_peer_identity +** +** Description Choose the STABLE peer identity to feed into the pseudo +** hash. A phone that connects with an RPA exposes a different +** address on every connection, so hashing the on-air address +** would make the pseudo (and therefore the bond key) drift on +** every reconnect. Prefer the resolved IRK Identity Address +** (ble.static_addr, learned from SMP Identity / PID) and only +** fall back to the on-air address before pairing. +*******************************************************************************/ +static void btm_ble_pseudo_pick_peer_identity(tBTM_SEC_DEV_REC *p_rec, const BD_ADDR on_air, + UINT8 on_air_type, + BD_ADDR peer_out, UINT8 *p_peer_type) +{ + const BD_ADDR zero = {0}; + if (p_rec && (p_rec->ble.key_type & BTM_LE_KEY_PID) && + memcmp(p_rec->ble.static_addr, zero, BD_ADDR_LEN) != 0) { + memcpy(peer_out, p_rec->ble.static_addr, BD_ADDR_LEN); + *p_peer_type = p_rec->ble.static_addr_type; + BLE_PSEUDO_DBG("pick_peer: use IDENTITY " BLE_PSEUDO_BDA_FMT " (type %u, key_type=0x%x)", + BLE_PSEUDO_BDA(peer_out), *p_peer_type, p_rec->ble.key_type); + } else { + memcpy(peer_out, on_air, BD_ADDR_LEN); + *p_peer_type = on_air_type; + BLE_PSEUDO_DBG("pick_peer: use ON-AIR " BLE_PSEUDO_BDA_FMT " (no PID yet; rec=%p key_type=0x%x)", + BLE_PSEUDO_BDA(peer_out), p_rec, p_rec ? p_rec->ble.key_type : 0); + } +} + +/******************************************************************************* +** Function btm_ble_pseudo_finalize_local +** +** Description Second-phase finalize, called from the LE Advertising Set +** Terminated handler. When the ext-adv instance was not yet +** resolvable at LE Connection Complete, the link was kept on +** the real peer address. Now that ter_con_handle is set the +** instance is known: derive the pseudo and re-key the link. +*******************************************************************************/ +void btm_ble_pseudo_finalize_local(UINT16 handle) +{ + tBTM_BLE_CONN_IDENTITY ent; + + if (btm_ble_conn_identity_get_by_handle(handle, &ent) && ent.local_ready) { + BLE_PSEUDO_DBG("finalize: handle=0x%x already keyed, skip", handle); + return; /* already keyed at connection complete */ + } + + tACL_CONN *p_acl = btm_handle_to_acl(handle); + if (p_acl == NULL || p_acl->transport != BT_TRANSPORT_LE || + p_acl->link_role != HCI_ROLE_SLAVE) { + BLE_PSEUDO_DBG("finalize: handle=0x%x no LE slave ACL, skip (p_acl=%p)", handle, p_acl); + return; + } + + tBTM_SEC_DEV_REC *p_cur = btm_find_dev_by_handle(handle); + + /* Pick the record that carries the peer Identity. Normally that is p_cur, + * but when btm_ble_connected() took the no-hijack path it allocated a FRESH, + * key-less record for this handle (because the on-air RPA IRK-resolved to + * ANOTHER live identity's bonded record). That fresh record has no PID, so + * feeding it to pick_peer would fall back to the transient on-air RPA and + * derive the WRONG pseudo - on an already-bonded reconnect there is no SMP + * pairing to re-key it, so the stored LTK is never found and the link fails. + * Recover the stable Identity by IRK-resolving the on-air RPA against the + * bonded records (all of this phone's local-identity bonds share one IRK / + * Identity). The local identity still comes from the adv set below, so any + * matching bond yields the correct (local, Identity) pseudo. */ + tBTM_SEC_DEV_REC *p_id_rec = p_cur; + if (p_id_rec == NULL || !(p_id_rec->ble.key_type & BTM_LE_KEY_PID)) { + list_node_t *p_node = list_begin(btm_cb.p_sec_dev_rec_list); + while (p_node) { + tBTM_SEC_DEV_REC *p_r = list_node(p_node); + p_node = list_next(p_node); + if (p_r != p_cur && (p_r->sec_flags & BTM_SEC_IN_USE) && + (p_r->ble.key_type & BTM_LE_KEY_PID) && + btm_ble_addr_resolvable(p_acl->active_remote_addr, p_r)) { + BLE_PSEUDO_DBG("finalize: handle=0x%x recover identity from bonded rec %p (cur %p has no PID)", + handle, p_r, p_cur); + p_id_rec = p_r; + break; + } + } + } + + tBLE_CONN_IDENTITY id; + memset(&id, 0, sizeof(id)); + /* Hash on the stable peer identity (static_addr) once known; this keeps the + * pseudo constant across the peer's RPA rotation. Before pairing (first + * ever connection) only the on-air RPA is known; PID will re-key later. */ + btm_ble_pseudo_pick_peer_identity(p_id_rec, p_acl->active_remote_addr, p_acl->active_remote_addr_type, + id.peer, &id.peer_type); + + if (!btm_ble_resolve_conn_local(handle, &id)) { + BLE_PSEUDO_DBG("finalize: handle=0x%x local STILL unknown, give up", handle); + return; /* instance still unknown; nothing we can do */ + } + + BD_ADDR pseudo; + btm_ble_identity_to_pseudo(&id, pseudo); + + if (!btm_ble_conn_identity_register(handle, &id, pseudo, TRUE)) { + BTM_TRACE_ERROR("%s: handle=0x%x side-table full, disconnect to avoid re-key without tracking", + __func__, handle); + btm_sec_disconnect(handle, HCI_ERR_HOST_REJECT_RESOURCES); + return; + } + + /* Bind this link to the device record that belongs to `pseudo` WITHOUT + * hijacking another local identity's bonded record (a phone connecting to + * a second local identity resolves to the first identity's record via its + * shared IRK, so btm_ble_connected() may have reused the wrong record). At + * this point pairing has not started, so no BLE keys can be lost. If the + * current record only holds a classic link key, allocate a separate entry + * so in-place re-key does not overwrite bd_addr and orphan the BR/EDR bond. + */ + tBTM_SEC_DEV_REC *p_tgt = btm_find_dev(pseudo); + if (p_tgt && p_tgt != p_cur) { + if (p_cur) { + p_cur->ble_hci_handle = BTM_SEC_INVALID_HANDLE; + p_tgt->ble.ble_addr_type = p_cur->ble.ble_addr_type; + /* p_cur was a fresh, key-less placeholder allocated by + * btm_ble_connected()'s no-hijack path. Now that the link is bound + * to the bonded target record, release the placeholder so it does + * not linger as an orphan (BTM_SEC_IN_USE with an invalid handle and + * a stale on-air RPA) consuming a device-record slot. Its + * ble_addr_type was copied to p_tgt above. Guard on "no bond" so a + * record that still holds BLE keys or a BR/EDR link key is never + * destroyed (that case is handled by the separate-alloc branch). */ + if (!p_cur->ble.key_type && + !(p_cur->sec_flags & BTM_SEC_LINK_KEY_KNOWN)) { + btm_sec_free_dev(p_cur, BT_TRANSPORT_LE); + p_cur = NULL; + } + } else { + p_tgt->ble.ble_addr_type = p_acl->active_remote_addr_type; + } + p_tgt->ble_hci_handle = handle; + p_tgt->device_type |= BT_DEVICE_TYPE_BLE; + BLE_PSEUDO_DBG("finalize: handle=0x%x bind to existing rec for pseudo", handle); + } else if (p_tgt == NULL && p_cur && + (p_cur->ble.key_type || (p_cur->sec_flags & BTM_SEC_LINK_KEY_KNOWN)) && + memcmp(p_cur->bd_addr, pseudo, BD_ADDR_LEN) != 0) { + UINT8 saved_addr_type = p_cur->ble.ble_addr_type; + tBTM_SEC_DEV_REC *p_new = btm_sec_alloc_dev_ex(pseudo, p_cur); + if (p_new) { + p_new->ble_hci_handle = handle; + p_new->device_type |= BT_DEVICE_TYPE_BLE; + p_new->ble.ble_addr_type = saved_addr_type; + memcpy(p_new->ble.pseudo_addr, pseudo, BD_ADDR_LEN); + if (p_new != p_cur) { + p_cur->ble_hci_handle = BTM_SEC_INVALID_HANDLE; + } + BLE_PSEUDO_DBG("finalize: handle=0x%x alloc separate rec for pseudo (no hijack)", handle); + } else { + BTM_TRACE_ERROR("%s: handle=0x%x alloc failed, disconnect to avoid cross-identity key corruption", + __func__, handle); + btm_sec_disconnect(handle, HCI_ERR_HOST_REJECT_RESOURCES); + return; + } + } else if (p_tgt == NULL && p_cur == NULL) { + /* A concurrent connection IRK-resolved to the same bonded record and + * overwrote ble_hci_handle, orphaning this link. Allocate a fresh entry. */ + tBTM_SEC_DEV_REC *p_new = btm_sec_alloc_dev(pseudo); + if (p_new) { + p_new->ble_hci_handle = handle; + p_new->device_type |= BT_DEVICE_TYPE_BLE; + p_new->ble.ble_addr_type = p_acl->active_remote_addr_type; + memcpy(p_new->ble.pseudo_addr, pseudo, BD_ADDR_LEN); + BLE_PSEUDO_DBG("finalize: handle=0x%x alloc rec for orphan link", handle); + } else { + BTM_TRACE_ERROR("%s: handle=0x%x alloc failed, disconnect to avoid missing sec record", + __func__, handle); + btm_sec_disconnect(handle, HCI_ERR_HOST_REJECT_RESOURCES); + return; + } + } else { + BLE_PSEUDO_DBG("finalize: handle=0x%x in-place key cur rec (tgt=%p cur=%p)", handle, p_tgt, p_cur); + } + + /* Re-key the address chain (GATT/LCB/ACL + the now-correct dev record). */ + btm_ble_pseudo_rekey_link(handle, p_acl, pseudo); +} + +/******************************************************************************* +** Function btm_ble_pseudo_apply_identity +** +** Description Called from SMP when the peer's Identity Address (PID) is +** received during pairing. If the link was keyed earlier from +** a transient RPA, re-derive the pseudo from the now known +** stable identity and re-key the link in place (the in-flight +** pairing keys stay in the same record). Returns TRUE and +** fills new_pseudo when the pseudo changed, so the SMP caller +** can update smp_cb.pairing_bda to keep pairing consistent. +*******************************************************************************/ +BOOLEAN btm_ble_pseudo_apply_identity(UINT16 handle, const BD_ADDR identity, + UINT8 id_type, BD_ADDR new_pseudo) +{ + tBTM_BLE_CONN_IDENTITY ent; + const BD_ADDR zero = {0}; + + if (!btm_ble_conn_identity_get_by_handle(handle, &ent) || + identity == NULL || memcmp(identity, zero, BD_ADDR_LEN) == 0) { + return FALSE; + } + + tBLE_CONN_IDENTITY id = ent.id; + memcpy(id.peer, identity, BD_ADDR_LEN); + id.peer_type = id_type; + + btm_ble_identity_to_pseudo(&id, new_pseudo); + if (memcmp(new_pseudo, ent.pseudo, BD_ADDR_LEN) == 0) { + BLE_PSEUDO_DBG("apply_identity: handle=0x%x pseudo unchanged (already on identity)", handle); + return FALSE; + } + + BLE_PSEUDO_DBG("apply_identity: handle=0x%x identity=" BLE_PSEUDO_BDA_FMT + " re-key " BLE_PSEUDO_BDA_FMT " -> " BLE_PSEUDO_BDA_FMT, + handle, BLE_PSEUDO_BDA(identity), + BLE_PSEUDO_BDA(ent.pseudo), BLE_PSEUDO_BDA(new_pseudo)); + + if (!btm_ble_conn_identity_register(handle, &id, new_pseudo, ent.local_ready)) { + BTM_TRACE_ERROR("%s: handle=0x%x side-table update failed, skip re-key", __func__, handle); + return FALSE; + } + btm_ble_pseudo_rekey_link(handle, btm_handle_to_acl(handle), new_pseudo); + return TRUE; +} +#endif /* BLE_PERIPH_PSEUDO_ADDR_BOND */ + /***************************************************************************** ** Function btm_ble_conn_complete ** @@ -2077,6 +2716,12 @@ void btm_ble_conn_complete(UINT8 *p, UINT16 evt_len, BOOLEAN enhanced) BD_ADDR local_rpa, peer_rpa; UINT16 conn_interval, conn_latency, conn_timeout; BOOLEAN match = FALSE; +#if (BLE_PERIPH_PSEUDO_ADDR_BOND == TRUE) + BD_ADDR pseudo_real_peer; /* controller-reported peer, before any pseudo_addr merge */ + UINT8 pseudo_peer_type; /* controller-reported peer type, before any rewrite */ + BOOLEAN pseudo_peer_valid = FALSE; + BD_ADDR conn_index_bda; /* address actually used to index ACL/dev_rec (pseudo or real) */ +#endif UNUSED(evt_len); STREAM_TO_UINT8 (status, p); STREAM_TO_UINT16 (handle, p); @@ -2089,6 +2734,12 @@ void btm_ble_conn_complete(UINT8 *p, UINT16 evt_len, BOOLEAN enhanced) if (enhanced) { STREAM_TO_BDADDR (local_rpa, p); STREAM_TO_BDADDR (peer_rpa, p); +#if (BLE_PERIPH_PSEUDO_ADDR_BOND == TRUE) + BLE_PSEUDO_DBG("conn_complete[enh]: handle=0x%x reported_peer=" BLE_PSEUDO_BDA_FMT + " local_rpa=" BLE_PSEUDO_BDA_FMT " peer_rpa(on-air)=" BLE_PSEUDO_BDA_FMT, + HCID_GET_HANDLE(handle), BLE_PSEUDO_BDA(bda), + BLE_PSEUDO_BDA(local_rpa), BLE_PSEUDO_BDA(peer_rpa)); +#endif #if (CONTROLLER_RPA_LIST_ENABLE == TRUE) BD_ADDR dummy_bda = {0}; /* For controller generates RPA, if resolving list contains no matching entry, it use identity address. @@ -2101,6 +2752,14 @@ void btm_ble_conn_complete(UINT8 *p, UINT16 evt_len, BOOLEAN enhanced) } #endif } +#if (BLE_PERIPH_PSEUDO_ADDR_BOND == TRUE) + /* Capture the controller-reported peer now: btm_identity_addr_to_random_pseudo() + * may rewrite bda/bda_type for a bonded peer, which would make the + * (local, peer) hash drift on reconnect. */ + pseudo_peer_type = bda_type; + memcpy(pseudo_real_peer, bda, BD_ADDR_LEN); + pseudo_peer_valid = TRUE; +#endif #if (BLE_PRIVACY_SPT == TRUE ) peer_addr_type = bda_type; match = btm_identity_addr_to_random_pseudo (bda, &bda_type, FALSE); @@ -2134,17 +2793,63 @@ void btm_ble_conn_complete(UINT8 *p, UINT16 evt_len, BOOLEAN enhanced) STREAM_TO_UINT16 (conn_timeout, p); handle = HCID_GET_HANDLE (handle); +#if (BLE_PERIPH_PSEUDO_ADDR_BOND == TRUE) + { + BD_ADDR hash_peer; + UINT8 hash_peer_type; + tBTM_SEC_DEV_REC *p_rec = NULL; + +#if (BLE_PRIVACY_SPT == TRUE) + if (match) { + p_rec = btm_find_dev_by_identity_addr(pseudo_real_peer, pseudo_peer_type); + } +#endif + /* Same stable-identity pick as the RPA async path and finalize. */ + btm_ble_pseudo_pick_peer_identity(p_rec, + pseudo_peer_valid ? pseudo_real_peer : bda, + pseudo_peer_type, + hash_peer, &hash_peer_type); + /* Bring the link up on the controller-reported peer captured + * BEFORE btm_identity_addr_to_random_pseudo() rewrote bda. For a + * peer already bonded under another local identity that rewrite + * turns bda into the other identity's stored pseudo; using it as + * the deferred fallback would collide this link's LCB / GATT TCB + * with the already-connected identity (no CONNECT event, no + * encryption). pseudo_real_peer is unique on-air; finalize re-keys + * it to f(local, peer). No air_peer restore here: this branch did + * not run host RPA resolution, so the index address already is the + * real on-air address. */ + btm_ble_pseudo_bringup_conn(handle, role, hash_peer, hash_peer_type, + pseudo_peer_valid ? pseudo_real_peer : bda, + pseudo_peer_valid ? pseudo_peer_type : bda_type, + conn_interval, conn_latency, + conn_timeout, match, NULL, 0, + "sync", conn_index_bda); + } +#else btm_ble_connected(bda, handle, HCI_ENCRYPT_MODE_DISABLED, role, bda_type, match); l2cble_conn_comp (handle, role, bda, bda_type, conn_interval, conn_latency, conn_timeout); +#endif #if (BLE_PRIVACY_SPT == TRUE) if (enhanced) { +#if (BLE_PERIPH_PSEUDO_ADDR_BOND == TRUE) + /* Use the connection index address (pseudo when keyed) so the + * ACL / device record lookups inside the refresh helpers hit + * the right entry. */ + btm_ble_refresh_local_resolvable_private_addr(conn_index_bda, local_rpa); + + if (peer_addr_type & BLE_ADDR_TYPE_ID_BIT) { + btm_ble_refresh_peer_resolvable_private_addr(conn_index_bda, peer_rpa, BLE_ADDR_RANDOM); + } +#else btm_ble_refresh_local_resolvable_private_addr(bda, local_rpa); if (peer_addr_type & BLE_ADDR_TYPE_ID_BIT) { btm_ble_refresh_peer_resolvable_private_addr(bda, peer_rpa, BLE_ADDR_RANDOM); } +#endif } #endif diff --git a/components/bt/host/bluedroid/stack/btm/btm_ble_5_gap.c b/components/bt/host/bluedroid/stack/btm/btm_ble_5_gap.c index f9fa9ebec74..d2d7fbf6e26 100644 --- a/components/bt/host/bluedroid/stack/btm/btm_ble_5_gap.c +++ b/components/bt/host/bluedroid/stack/btm/btm_ble_5_gap.c @@ -5,6 +5,9 @@ */ #include "btm_int.h" +#if (BLE_INCLUDED == TRUE && SMP_INCLUDED == TRUE && BLE_PERIPH_PSEUDO_ADDR_BOND == TRUE) +#include "btm_ble_pseudo.h" +#endif #include "stack/hcimsgs.h" #include "stack/hcidefs.h" #include "osi/allocator.h" @@ -625,7 +628,14 @@ tBTM_STATUS BTM_BleExtAdvSetRemove(UINT8 instance) extend_adv_cb.inst[instance].own_addr_type = BLE_ADDR_PUBLIC; extend_adv_cb.inst[instance].rand_addr_set = FALSE; memset(extend_adv_cb.inst[instance].rand_addr, 0, BD_ADDR_LEN); + /* Fully reset the per-set record, consistent with BTM_BleExtAdvSetClear(). */ adv_record[instance].ter_con_handle = INVALID_VALUE_16BIT; + adv_record[instance].invalid = false; + adv_record[instance].enabled = false; + adv_record[instance].instance = INVALID_VALUE_8BIT; + adv_record[instance].duration = INVALID_VALUE_32BIT; + adv_record[instance].max_events = INVALID_VALUE_32BIT; + adv_record[instance].retry_count = 0; } end: @@ -658,7 +668,18 @@ tBTM_STATUS BTM_BleExtAdvSetClear(void) extend_adv_cb.inst[i].own_addr_type = BLE_ADDR_PUBLIC; extend_adv_cb.inst[i].rand_addr_set = FALSE; memset(extend_adv_cb.inst[i].rand_addr, 0, BD_ADDR_LEN); + /* Fully reset the per-set record, consistent with + * btm_ble_advrecod_init() and the disable-all path. Resetting only + * ter_con_handle would leave 'enabled' (and the rest) stale, making + * btm_ble_ext_adv_active_count() report sets that the controller + * has already removed. */ adv_record[i].ter_con_handle = INVALID_VALUE_16BIT; + adv_record[i].invalid = false; + adv_record[i].enabled = false; + adv_record[i].instance = INVALID_VALUE_8BIT; + adv_record[i].duration = INVALID_VALUE_32BIT; + adv_record[i].max_events = INVALID_VALUE_32BIT; + adv_record[i].retry_count = 0; } } @@ -1272,6 +1293,14 @@ void btm_ble_adv_set_terminated_evt(tBTM_BLE_ADV_TERMINAT *params) * after LE (Enhanced) Connection Complete. */ #if (CONTROLLER_RPA_LIST_ENABLE == TRUE) btm_ble_adjust_conn_addr_for_ext_adv(adv_record[params->adv_handle].ter_con_handle); +#endif +#if (BLE_INCLUDED == TRUE && SMP_INCLUDED == TRUE && BLE_PERIPH_PSEUDO_ADDR_BOND == TRUE) + /* The ext-adv instance is now resolvable for this handle. If the link + * could not be pseudo-keyed at connection complete (instance not yet + * known), finalize it now so bond / LTK storage is isolated. */ + BLE_PSEUDO_DBG("adv_terminated: adv_handle=%u con_handle=0x%x -> finalize", + params->adv_handle, adv_record[params->adv_handle].ter_con_handle); + btm_ble_pseudo_finalize_local(adv_record[params->adv_handle].ter_con_handle); #endif } else { adv_record[params->adv_handle].ter_con_handle = INVALID_VALUE_16BIT; diff --git a/components/bt/host/bluedroid/stack/btm/btm_ble_bgconn.c b/components/bt/host/bluedroid/stack/btm/btm_ble_bgconn.c index 7a0e4dd7606..126ec6440d7 100644 --- a/components/bt/host/bluedroid/stack/btm/btm_ble_bgconn.c +++ b/components/bt/host/bluedroid/stack/btm/btm_ble_bgconn.c @@ -859,6 +859,35 @@ void btm_ble_enqueue_direct_conn_req(void *p_param) } /******************************************************************************* ** +** Function btm_ble_remove_direct_conn_req +** +** Description Remove a pending direct connection request for the given LCB. +** +** Returns None. +** +*******************************************************************************/ +void btm_ble_remove_direct_conn_req(void *p_param) +{ + fixed_queue_t *q = btm_cb.ble_ctr_cb.conn_pending_q; + + if (q == NULL || p_param == NULL) { + return; + } + + list_t *list = fixed_queue_get_list(q); + for (const list_node_t *node = list_begin(list); node != NULL; node = list_next(node)) { + tBTM_BLE_CONN_REQ *p = (tBTM_BLE_CONN_REQ *)list_node(node); + + if (p->p_param == p_param) { + if (fixed_queue_try_remove_from_queue(q, p) != NULL) { + osi_free(p); + } + break; + } + } +} +/******************************************************************************* +** ** Function btm_send_pending_direct_conn ** ** Description This function send the pending direct connection request in queue @@ -873,7 +902,17 @@ BOOLEAN btm_send_pending_direct_conn(void) p_req = (tBTM_BLE_CONN_REQ*)fixed_queue_dequeue(btm_cb.ble_ctr_cb.conn_pending_q, 0); if (p_req != NULL) { - rt = l2cble_init_direct_conn((tL2C_LCB *)(p_req->p_param)); + tL2C_LCB *p_lcb = (tL2C_LCB *)(p_req->p_param); + + if (p_lcb == NULL || !p_lcb->in_use) { + osi_free((void *)p_req); + return FALSE; + } + + rt = l2cble_init_direct_conn(p_lcb); + if (!rt) { + l2cu_release_lcb(p_lcb); + } osi_free((void *)p_req); } diff --git a/components/bt/host/bluedroid/stack/btm/btm_ble_gap.c b/components/bt/host/bluedroid/stack/btm/btm_ble_gap.c index 737b86c6ea1..43e713f19c2 100644 --- a/components/bt/host/bluedroid/stack/btm/btm_ble_gap.c +++ b/components/bt/host/bluedroid/stack/btm/btm_ble_gap.c @@ -30,6 +30,7 @@ //#include "bt_utils.h" #include "btm_int.h" #include "stack/btm_ble_api.h" +#include "btm_ble_pseudo.h" #include "stack/btu.h" #include "device/controller.h" #include "stack/hcimsgs.h" @@ -1841,7 +1842,7 @@ UINT8 *btm_ble_build_adv_data(tBTM_BLE_AD_MASK *p_data_mask, UINT8 **p_dst, if (len > MIN_ADV_LENGTH && data_mask & BTM_BLE_AD_BIT_SERVICE_DATA && p_data && p_data->p_service_data && p_data->p_service_data->len != 0 && p_data->p_service_data->p_val) { if (len > (p_data->p_service_data->service_uuid.len + MIN_ADV_LENGTH)) { - if (p_data->p_service_data->len > (len - MIN_ADV_LENGTH)) { + if (p_data->p_service_data->len > (len - MIN_ADV_LENGTH - p_data->p_service_data->service_uuid.len)) { cp_len = len - MIN_ADV_LENGTH - p_data->p_service_data->service_uuid.len; } else { cp_len = p_data->p_service_data->len; @@ -2895,6 +2896,9 @@ void btm_send_sel_conn_callback(BD_ADDR remote_bda, UINT8 evt_type, UINT8 *p_dat } if (p_dev_name) { + if (len > sizeof(remname) - 1) { + len = sizeof(remname) - 1; + } memcpy(remname, p_dev_name, len); } } @@ -3018,6 +3022,10 @@ void btm_ble_process_adv_pkt (UINT8 *p_data, UINT8 evt_len) #endif /* Validate data_len before any path (callee reads 1 + data_len + 1 = data_len+2 bytes from p) */ data_len = *p; /* read without advancing; p points to data_len byte */ + if (data_len > BTM_BLE_ADV_DATA_LEN_MAX) { + BTM_TRACE_ERROR("btm_ble_process_adv_pkt: legacy adv data_len %u exceeds max %u", data_len, BTM_BLE_ADV_DATA_LEN_MAX); + break; + } if (data_len + 2 > remaining - 8) { BTM_TRACE_ERROR("btm_ble_process_adv_pkt: data_len %u + data + rssi exceeds remaining %u", data_len, (UINT16)(remaining - 8)); break; @@ -3944,6 +3952,10 @@ void btm_ble_init (void) #if (BLE_VENDOR_HCI_EN == TRUE) BTM_RegisterForVSEvents(btm_ble_vs_evt_callback, TRUE); #endif // #if (BLE_VENDOR_HCI_EN == TRUE) + +#if (BLE_INCLUDED == TRUE && SMP_INCLUDED == TRUE && BLE_PERIPH_PSEUDO_ADDR_BOND == TRUE) + btm_ble_pseudo_init(); +#endif } /******************************************************************************* @@ -3969,6 +3981,10 @@ void btm_ble_free (void) osi_event_delete(p_cb->adv_rpt_ready); p_cb->adv_rpt_ready = NULL; #endif // #if (BLE_42_SCAN_EN == TRUE) + +#if (BLE_INCLUDED == TRUE && SMP_INCLUDED == TRUE && BLE_PERIPH_PSEUDO_ADDR_BOND == TRUE) + btm_ble_pseudo_deinit(); +#endif } static bool enable_topology_check_flag = true; diff --git a/components/bt/host/bluedroid/stack/btm/btm_ble_iso.c b/components/bt/host/bluedroid/stack/btm/btm_ble_iso.c index 947c920bffc..1f57f71f727 100644 --- a/components/bt/host/bluedroid/stack/btm/btm_ble_iso.c +++ b/components/bt/host/bluedroid/stack/btm/btm_ble_iso.c @@ -330,6 +330,8 @@ tBTM_STATUS BTM_BleBigCreate(uint8_t big_handle, uint8_t adv_handle, uint8_t num return BTM_ILLEGAL_VALUE; } + /* btsnd_hcic_ble_big_create() returns FALSE only when HCI_GET_CMD_BUF() + * fails (out of memory). That path is not surfaced to the caller by design. */ btsnd_hcic_ble_big_create(big_handle, adv_handle, num_bis, sdu_interval, max_sdu, max_transport_latency, rtn, phy, packing, framing, encryption, broadcast_code); @@ -348,6 +350,7 @@ tBTM_STATUS BTM_BleBigCreateTest(uint8_t big_handle, uint8_t adv_handle, uint8_t return BTM_ILLEGAL_VALUE; } + /* See BTM_BleBigCreate: HCI cmd buffer alloc failure is not checked. */ btsnd_hcic_ble_big_create_test(big_handle, adv_handle, num_bis, sdu_interval, iso_interval, nse, max_sdu, max_pdu, phy, packing, framing, bn, irc, pto, encryption, broadcast_code); @@ -357,6 +360,7 @@ tBTM_STATUS BTM_BleBigCreateTest(uint8_t big_handle, uint8_t adv_handle, uint8_t tBTM_STATUS BTM_BleBigTerminate(UINT8 big_handle, UINT8 reason) { // event will be triggered in command status and complete event + /* See BTM_BleBigCreate: HCI cmd buffer alloc failure is not checked. */ btsnd_hcic_ble_big_terminate(big_handle, reason); return BTM_SUCCESS; } @@ -373,6 +377,7 @@ tBTM_STATUS BTM_BleBigSyncCreate(uint8_t big_handle, uint16_t sync_handle, return BTM_ILLEGAL_VALUE; } + /* See BTM_BleBigCreate: HCI cmd buffer alloc failure is not checked. */ btsnd_hcic_ble_big_sync_create(big_handle, sync_handle, encryption, bc_code, mse, big_sync_timeout, num_bis, bis); return BTM_SUCCESS; diff --git a/components/bt/host/bluedroid/stack/btm/btm_ble_pseudo.c b/components/bt/host/bluedroid/stack/btm/btm_ble_pseudo.c new file mode 100644 index 00000000000..dc5a2f4a355 --- /dev/null +++ b/components/bt/host/bluedroid/stack/btm/btm_ble_pseudo.c @@ -0,0 +1,289 @@ +/****************************************************************************** + * + * Copyright (C) 2026 Espressif Systems (Shanghai) CO LTD + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ******************************************************************************/ + +#include +#include "common/bt_target.h" +#include "common/bt_trace.h" +#include "stack/bt_types.h" +#include "btm_int.h" +#include "btm_ble_pseudo.h" +#include "osi/mutex.h" + +#if (BLE_INCLUDED == TRUE && SMP_INCLUDED == TRUE && BLE_PERIPH_PSEUDO_ADDR_BOND == TRUE) + +/* The pseudo is derived with AES-CMAC, the same SMP crypto primitive used by + * c1/f5/f6. aes_cipher_msg_auth_code() is provided by stack/smp/smp_cmac.c for + * ALL three configurable crypto backends (mbedtls/PSA, tinycrypt, stack-native, + * selected by SMP_CRYPTO_MBEDTLS / SMP_CRYPTO_TINYCRYPT / SMP_CRYPTO_STACK_NATIVE), + * so the derivation automatically follows the configured crypto library and + * this module carries no library-specific code. */ +extern BOOLEAN aes_cipher_msg_auth_code(BT_OCTET16 key, UINT8 *input, UINT16 length, + UINT16 tlen, UINT8 *p_signature); + +/* Fixed 16-byte domain-separation key for the pseudo CMAC (not secret; the + * pseudo is a Host-internal index, never sent on air). Do not change after + * deployment without bumping BLE_PSEUDO_SCHEME_VER and migrating bonds. */ +static const UINT8 btm_ble_pseudo_cmac_key[16] = { + 'E', 'S', 'P', '_', 'B', 'L', 'E', '_', 'P', 'S', 'E', 'U', 'D', 'O', 'v', BLE_PSEUDO_SCHEME_VER +}; + +/* The side table holds one entry per concurrent LE link. BTU updates it from + * HCI/SMP paths; BTC and the public esp_ble_gap_* API read it. All accessors + * take btm_ble_pseudo_mutex and copy data out before returning. */ +#define BTM_BLE_PSEUDO_MAX_CONN MAX_ACL_CONNECTIONS + +static osi_mutex_t btm_ble_pseudo_mutex; +static tBTM_BLE_CONN_IDENTITY btm_ble_conn_id_tab[BTM_BLE_PSEUDO_MAX_CONN]; + +static tBTM_BLE_CONN_IDENTITY *conn_identity_by_handle_locked(UINT16 handle) +{ + for (int i = 0; i < BTM_BLE_PSEUDO_MAX_CONN; i++) { + if (btm_ble_conn_id_tab[i].in_use && btm_ble_conn_id_tab[i].handle == handle) { + return &btm_ble_conn_id_tab[i]; + } + } + return NULL; +} + +static tBTM_BLE_CONN_IDENTITY *conn_identity_by_pseudo_locked(const BD_ADDR pseudo) +{ + for (int i = 0; i < BTM_BLE_PSEUDO_MAX_CONN; i++) { + if (btm_ble_conn_id_tab[i].in_use && + memcmp(btm_ble_conn_id_tab[i].pseudo, pseudo, BD_ADDR_LEN) == 0) { + return &btm_ble_conn_id_tab[i]; + } + } + return NULL; +} + +/******************************************************************************* +** Function btm_ble_identity_to_pseudo +*******************************************************************************/ +void btm_ble_identity_to_pseudo(const tBLE_CONN_IDENTITY *p_id, BD_ADDR pseudo) +{ + uint8_t in[1 + BD_ADDR_LEN + BD_ADDR_LEN]; + uint8_t cmac[16]; + BT_OCTET16 key; + BOOLEAN hashed; + + if (p_id == NULL || pseudo == NULL) { + return; + } + + in[0] = BLE_PSEUDO_SCHEME_VER; + memcpy(&in[1], p_id->local, BD_ADDR_LEN); + memcpy(&in[1 + BD_ADDR_LEN], p_id->peer, BD_ADDR_LEN); + + /* AES-CMAC(key, version || local || peer); follows the configured SMP + * crypto backend (mbedtls/tinycrypt/native). */ + memcpy(key, btm_ble_pseudo_cmac_key, sizeof(key)); + hashed = aes_cipher_msg_auth_code(key, in, sizeof(in), sizeof(cmac), cmac); + + if (!hashed) { + /* Fall back to a deterministic non-crypto mix so we never emit a + * zero / unstable pseudo; bring-up only, should not happen. */ + for (int i = 0; i < BD_ADDR_LEN; i++) { + cmac[i] = in[1 + i] ^ in[1 + BD_ADDR_LEN + i] ^ BLE_PSEUDO_SCHEME_VER; + } + } + + memcpy(pseudo, cmac, BD_ADDR_LEN); + + /* Force Static-Random format (top two bits = 11). This is functionally + * required: it keeps the pseudo out of the resolvable-RPA space so that + * btm_find_dev()/btm_ble_addr_resolvable() can never misresolve it. */ + pseudo[0] |= 0xC0; + + /* Avoid the all-ones broadcast pattern. */ + if (pseudo[0] == 0xFF && pseudo[1] == 0xFF && pseudo[2] == 0xFF && + pseudo[3] == 0xFF && pseudo[4] == 0xFF && pseudo[5] == 0xFF) { + pseudo[0] = 0xC1; + } + + BLE_PSEUDO_DBG("derive: local(t%u) " BLE_PSEUDO_BDA_FMT " + peer(t%u) " BLE_PSEUDO_BDA_FMT + " -> pseudo " BLE_PSEUDO_BDA_FMT " (hashed=%d)", + p_id->local_type, BLE_PSEUDO_BDA(p_id->local), + p_id->peer_type, BLE_PSEUDO_BDA(p_id->peer), + BLE_PSEUDO_BDA(pseudo), hashed); +} + +/******************************************************************************* +** Function btm_ble_pseudo_init / deinit +*******************************************************************************/ +void btm_ble_pseudo_init(void) +{ + /* Bluedroid host init runs during stack bring-up where heap exhaustion is + * not a tolerated / recoverable condition: if this single fixed-size mutex + * cannot be created the whole host cannot come up, so there is nothing to + * gracefully fall back to. The return value is intentionally not checked + * here, matching the rest of the host init path (e.g. btm_ble_init()), and + * this is not a bug. */ + osi_mutex_new(&btm_ble_pseudo_mutex); + osi_mutex_lock(&btm_ble_pseudo_mutex, OSI_MUTEX_MAX_TIMEOUT); + memset(btm_ble_conn_id_tab, 0, sizeof(btm_ble_conn_id_tab)); + osi_mutex_unlock(&btm_ble_pseudo_mutex); +} + +void btm_ble_pseudo_deinit(void) +{ + if (btm_ble_pseudo_mutex == NULL) { + return; + } + osi_mutex_lock(&btm_ble_pseudo_mutex, OSI_MUTEX_MAX_TIMEOUT); + memset(btm_ble_conn_id_tab, 0, sizeof(btm_ble_conn_id_tab)); + osi_mutex_unlock(&btm_ble_pseudo_mutex); + osi_mutex_free(&btm_ble_pseudo_mutex); +} + +/******************************************************************************* +** Function btm_ble_conn_identity_get_by_handle +*******************************************************************************/ +BOOLEAN btm_ble_conn_identity_get_by_handle(UINT16 handle, tBTM_BLE_CONN_IDENTITY *p_out) +{ + BOOLEAN found = FALSE; + + if (p_out == NULL) { + return FALSE; + } + + osi_mutex_lock(&btm_ble_pseudo_mutex, OSI_MUTEX_MAX_TIMEOUT); + tBTM_BLE_CONN_IDENTITY *p_ent = conn_identity_by_handle_locked(handle); + if (p_ent) { + memcpy(p_out, p_ent, sizeof(tBTM_BLE_CONN_IDENTITY)); + found = TRUE; + } + osi_mutex_unlock(&btm_ble_pseudo_mutex); + return found; +} + +/******************************************************************************* +** Function btm_ble_conn_identity_get_by_pseudo +*******************************************************************************/ +BOOLEAN btm_ble_conn_identity_get_by_pseudo(const BD_ADDR pseudo, tBTM_BLE_CONN_IDENTITY *p_out) +{ + BOOLEAN found = FALSE; + + if (pseudo == NULL || p_out == NULL) { + return FALSE; + } + + osi_mutex_lock(&btm_ble_pseudo_mutex, OSI_MUTEX_MAX_TIMEOUT); + tBTM_BLE_CONN_IDENTITY *p_ent = conn_identity_by_pseudo_locked(pseudo); + if (p_ent) { + memcpy(p_out, p_ent, sizeof(tBTM_BLE_CONN_IDENTITY)); + found = TRUE; + } + osi_mutex_unlock(&btm_ble_pseudo_mutex); + return found; +} + +/******************************************************************************* +** Function btm_ble_conn_identity_exists_by_handle +*******************************************************************************/ +BOOLEAN btm_ble_conn_identity_exists_by_handle(UINT16 handle) +{ + BOOLEAN found = FALSE; + + osi_mutex_lock(&btm_ble_pseudo_mutex, OSI_MUTEX_MAX_TIMEOUT); + found = (conn_identity_by_handle_locked(handle) != NULL); + osi_mutex_unlock(&btm_ble_pseudo_mutex); + return found; +} + +/******************************************************************************* +** Function btm_ble_conn_identity_register +*******************************************************************************/ +BOOLEAN btm_ble_conn_identity_register(UINT16 handle, + const tBLE_CONN_IDENTITY *p_id, + const BD_ADDR pseudo, + BOOLEAN local_ready) +{ + BOOLEAN ok = FALSE; + + osi_mutex_lock(&btm_ble_pseudo_mutex, OSI_MUTEX_MAX_TIMEOUT); + + tBTM_BLE_CONN_IDENTITY *p_ent = conn_identity_by_handle_locked(handle); + if (p_ent == NULL) { + for (int i = 0; i < BTM_BLE_PSEUDO_MAX_CONN; i++) { + if (!btm_ble_conn_id_tab[i].in_use) { + p_ent = &btm_ble_conn_id_tab[i]; + break; + } + } + } + + if (p_ent == NULL) { + BTM_TRACE_ERROR("%s no free slot for handle 0x%x", __func__, handle); + BLE_PSEUDO_DBG("register FAIL: no free slot, handle=0x%x", handle); + } else { + p_ent->handle = handle; + p_ent->in_use = TRUE; + p_ent->local_ready = local_ready; + if (p_id) { + memcpy(&p_ent->id, p_id, sizeof(tBLE_CONN_IDENTITY)); + } + if (pseudo) { + memcpy(p_ent->pseudo, pseudo, BD_ADDR_LEN); + } + BLE_PSEUDO_DBG("register: handle=0x%x slot=%d pseudo=" BLE_PSEUDO_BDA_FMT " local_ready=%d", + handle, (int)(p_ent - btm_ble_conn_id_tab), + BLE_PSEUDO_BDA(p_ent->pseudo), local_ready); + ok = TRUE; + } + + osi_mutex_unlock(&btm_ble_pseudo_mutex); + return ok; +} + +/******************************************************************************* +** Function btm_ble_conn_identity_unregister +*******************************************************************************/ +void btm_ble_conn_identity_unregister(UINT16 handle) +{ + osi_mutex_lock(&btm_ble_pseudo_mutex, OSI_MUTEX_MAX_TIMEOUT); + tBTM_BLE_CONN_IDENTITY *p_ent = conn_identity_by_handle_locked(handle); + if (p_ent) { + BLE_PSEUDO_DBG("unregister: handle=0x%x pseudo=" BLE_PSEUDO_BDA_FMT, + handle, BLE_PSEUDO_BDA(p_ent->pseudo)); + memset(p_ent, 0, sizeof(tBTM_BLE_CONN_IDENTITY)); + } + osi_mutex_unlock(&btm_ble_pseudo_mutex); +} + +/******************************************************************************* +** Function btm_ble_pseudo_to_real_peer +*******************************************************************************/ +BOOLEAN btm_ble_pseudo_to_real_peer(const BD_ADDR pseudo, BD_ADDR real_peer) +{ + BOOLEAN found = FALSE; + + if (pseudo == NULL || real_peer == NULL) { + return FALSE; + } + + osi_mutex_lock(&btm_ble_pseudo_mutex, OSI_MUTEX_MAX_TIMEOUT); + tBTM_BLE_CONN_IDENTITY *p_ent = conn_identity_by_pseudo_locked(pseudo); + if (p_ent) { + memcpy(real_peer, p_ent->id.peer, BD_ADDR_LEN); + found = TRUE; + } + osi_mutex_unlock(&btm_ble_pseudo_mutex); + return found; +} + +#endif /* BLE_INCLUDED && SMP_INCLUDED && BLE_PERIPH_PSEUDO_ADDR_BOND */ diff --git a/components/bt/host/bluedroid/stack/btm/btm_dev.c b/components/bt/host/bluedroid/stack/btm/btm_dev.c index 47a5da7f2d1..6a8c05f6629 100644 --- a/components/bt/host/bluedroid/stack/btm/btm_dev.c +++ b/components/bt/host/bluedroid/stack/btm/btm_dev.c @@ -36,7 +36,7 @@ #include "stack/hcidefs.h" #include "stack/l2c_api.h" -static tBTM_SEC_DEV_REC *btm_find_oldest_dev (void); +static tBTM_SEC_DEV_REC *btm_find_oldest_dev_ex (tBTM_SEC_DEV_REC *exclude_rec); /******************************************************************************* ** @@ -330,6 +330,22 @@ BOOLEAN btm_find_sec_dev_in_list (void *p_node_data, void *context) ** *******************************************************************************/ tBTM_SEC_DEV_REC *btm_sec_alloc_dev (BD_ADDR bd_addr) +{ + return btm_sec_alloc_dev_ex(bd_addr, NULL); +} + +/******************************************************************************* +** +** Function btm_sec_alloc_dev_ex +** +** Description Same as btm_sec_alloc_dev(), but exclude_rec will never be +** recycled when the device table is full and an existing entry +** must be reused. +** +** Returns Pointer to the record or NULL +** +*******************************************************************************/ +tBTM_SEC_DEV_REC *btm_sec_alloc_dev_ex (BD_ADDR bd_addr, tBTM_SEC_DEV_REC *exclude_rec) { tBTM_SEC_DEV_REC *p_dev_rec = NULL; tBTM_SEC_DEV_REC *p_dev_new_rec = NULL; @@ -339,7 +355,7 @@ tBTM_SEC_DEV_REC *btm_sec_alloc_dev (BD_ADDR bd_addr) BOOLEAN new_entry_found = FALSE; BOOLEAN old_entry_found = FALSE; BOOLEAN malloc_new_entry = FALSE; - BTM_TRACE_EVENT ("btm_sec_alloc_dev - start alloc for device %02x:%02x:%02x:%02x:%02x:%02x", + BTM_TRACE_EVENT ("btm_sec_alloc_dev_ex - start alloc for device %02x:%02x:%02x:%02x:%02x:%02x", bd_addr[0], bd_addr[1], bd_addr[2], bd_addr[3], bd_addr[4], bd_addr[5]); for (p_node = list_begin(btm_cb.p_sec_dev_rec_list); p_node; p_node = list_next(p_node)) { p_dev_old_rec = list_node(p_node); @@ -374,13 +390,16 @@ tBTM_SEC_DEV_REC *btm_sec_alloc_dev (BD_ADDR bd_addr) } } if (!new_entry_found) { - p_dev_rec = btm_find_oldest_dev(); + p_dev_rec = btm_find_oldest_dev_ex(exclude_rec); #if (BLE_INCLUDED == TRUE) && (SMP_INCLUDED == TRUE) // If device record exists and contains identity key, remove it from resolving list if (p_dev_rec && (p_dev_rec->ble.key_type & SMP_SEC_KEY_TYPE_ID)) { btm_ble_resolving_list_remove_dev(p_dev_rec); } #endif // (BLE_INCLUDED == TRUE) && (SMP_INCLUDED == TRUE) + if (p_dev_rec == NULL) { + return NULL; + } } else { /* if the old device entry not present go with new entry */ if (old_entry_found) { @@ -654,16 +673,17 @@ tBTM_SEC_DEV_REC *btm_find_or_alloc_dev (BD_ADDR bd_addr) /******************************************************************************* ** -** Function btm_find_oldest_dev +** Function btm_find_oldest_dev_ex ** ** Description Locates the oldest device in use. It first looks for ** the oldest non-paired device. If all devices are paired it -** deletes the oldest paired device. +** deletes the oldest paired device. exclude_rec is never +** returned when non-NULL. ** ** Returns Pointer to the record or NULL ** *******************************************************************************/ -tBTM_SEC_DEV_REC *btm_find_oldest_dev (void) +static tBTM_SEC_DEV_REC *btm_find_oldest_dev_ex (tBTM_SEC_DEV_REC *exclude_rec) { tBTM_SEC_DEV_REC *p_dev_rec = NULL; tBTM_SEC_DEV_REC *p_oldest = NULL; @@ -673,6 +693,9 @@ tBTM_SEC_DEV_REC *btm_find_oldest_dev (void) /* First look for the non-paired devices for the oldest entry */ for (p_node = list_begin(btm_cb.p_sec_dev_rec_list); p_node; p_node = list_next(p_node)) { p_dev_rec = list_node(p_node); + if (p_dev_rec == exclude_rec) { + continue; + } if (((p_dev_rec->sec_flags & BTM_SEC_IN_USE) == 0) || ((p_dev_rec->sec_flags & (BTM_SEC_LINK_KEY_KNOWN | BTM_SEC_LE_LINK_KEY_KNOWN)) != 0)) { continue; /* Device is paired so skip it */ @@ -689,8 +712,12 @@ tBTM_SEC_DEV_REC *btm_find_oldest_dev (void) } /* All devices are paired; find the oldest */ + old_ts = 0xFFFFFFFF; for (p_node = list_begin(btm_cb.p_sec_dev_rec_list); p_node; p_node = list_next(p_node)) { p_dev_rec = list_node(p_node); + if (p_dev_rec == exclude_rec) { + continue; + } if ((p_dev_rec->sec_flags & BTM_SEC_IN_USE) == 0) { continue; } diff --git a/components/bt/host/bluedroid/stack/btm/btm_pm.c b/components/bt/host/bluedroid/stack/btm/btm_pm.c index 9391976a4e6..2b8b8d43703 100644 --- a/components/bt/host/bluedroid/stack/btm/btm_pm.c +++ b/components/bt/host/bluedroid/stack/btm/btm_pm.c @@ -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) diff --git a/components/bt/host/bluedroid/stack/btm/include/btm_ble_int.h b/components/bt/host/bluedroid/stack/btm/include/btm_ble_int.h index 6057f7951f5..56ae57a4460 100644 --- a/components/bt/host/bluedroid/stack/btm/include/btm_ble_int.h +++ b/components/bt/host/bluedroid/stack/btm/include/btm_ble_int.h @@ -480,6 +480,7 @@ void btm_ble_update_link_topology_mask(UINT8 role, BOOLEAN increase); /* direct connection utility */ BOOLEAN btm_send_pending_direct_conn(void); void btm_ble_enqueue_direct_conn_req(void *p_param); +void btm_ble_remove_direct_conn_req(void *p_param); /* BLE address management */ void btm_gen_resolvable_private_addr (void *p_cmd_cplt_cback); diff --git a/components/bt/host/bluedroid/stack/btm/include/btm_ble_pseudo.h b/components/bt/host/bluedroid/stack/btm/include/btm_ble_pseudo.h new file mode 100644 index 00000000000..78fd2aed914 --- /dev/null +++ b/components/bt/host/bluedroid/stack/btm/include/btm_ble_pseudo.h @@ -0,0 +1,161 @@ +/****************************************************************************** + * + * Copyright (C) 2026 Espressif Systems (Shanghai) CO LTD + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ******************************************************************************/ + +/****************************************************************************** + * + * Peripheral dual local-identity bond isolation: Host-internal pseudo + * address derivation and the per-connection identity side table. + * + * A pseudo address is a 6-byte Host-only key computed from + * (local_identity, peer). It lets one peer phone that connects through two + * distinct local identities (e.g. Public and a fixed Static Random adv set) + * appear as two independent peers inside the Host (separate device record, + * LTK and NVS bond section). The over-the-air and SMP cryptography keep + * using the real peer and the real local identity; the pseudo never leaves + * the Host. + * + ******************************************************************************/ +#ifndef BTM_BLE_PSEUDO_H +#define BTM_BLE_PSEUDO_H + +#include "common/bt_target.h" +#include "stack/bt_types.h" + +#if (BLE_INCLUDED == TRUE && SMP_INCLUDED == TRUE && BLE_PERIPH_PSEUDO_ADDR_BOND == TRUE) + +#include "common/bt_trace.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* Debug logging for the pseudo-address bond feature. Routed through the Host + * BTM trace macro so the "[PSEUDO]" lines follow the standard Bluetooth log + * level (BT_LOG_LEVEL_BTM) like the rest of the stack. */ +#define BLE_PSEUDO_DBG(fmt, ...) BTM_TRACE_DEBUG("[PSEUDO] " fmt, ##__VA_ARGS__) + +/* Helper to print a BD_ADDR without a MACSTR dependency. */ +#define BLE_PSEUDO_BDA(a) (a)[0], (a)[1], (a)[2], (a)[3], (a)[4], (a)[5] +#define BLE_PSEUDO_BDA_FMT "%02x:%02x:%02x:%02x:%02x:%02x" + +/* Bump when the pseudo derivation input layout or algorithm changes (e.g. v1 + * was SHA-256 truncated; v2 is AES-CMAC via the configured SMP crypto backend). + * Persisted in the input/key so a mismatch yields a different pseudo. */ +#define BLE_PSEUDO_SCHEME_VER 2 + +/* Identity that produced one connection: the resolved real peer plus the + * local identity (Public or fixed Static Random) of the adv set / link. */ +typedef struct { + BD_ADDR local; /* local identity (NOT a transient RPA) */ + BD_ADDR peer; /* resolved real peer identity */ + tBLE_ADDR_TYPE local_type; + tBLE_ADDR_TYPE peer_type; +} tBLE_CONN_IDENTITY; + +/* Per-connection side table entry, keyed by HCI handle. */ +typedef struct { + UINT16 handle; + BD_ADDR pseudo; + tBLE_CONN_IDENTITY id; + BOOLEAN local_ready; /* TRUE once local identity finalized */ + BOOLEAN in_use; +} tBTM_BLE_CONN_IDENTITY; + +/******************************************************************************* +** Function btm_ble_identity_to_pseudo +** +** Description Deterministically derive a 6-byte Host pseudo address from +** (local identity || peer). Same input always yields the same +** output. The result is forced to Static-Random format (top +** two bits = 11) so it can never be mistaken for a resolvable +** RPA by btm_find_dev()/btm_ble_addr_resolvable(). +*******************************************************************************/ +void btm_ble_identity_to_pseudo(const tBLE_CONN_IDENTITY *p_id, BD_ADDR pseudo); + +/******************************************************************************* +** Function btm_ble_pseudo_init / btm_ble_pseudo_deinit +*******************************************************************************/ +void btm_ble_pseudo_init(void); +void btm_ble_pseudo_deinit(void); + +/******************************************************************************* +** Function btm_ble_conn_identity_register +** +** Description Record (handle -> pseudo, identity). If an entry for the +** handle already exists it is updated. Returns FALSE when the +** table is full. +*******************************************************************************/ +BOOLEAN btm_ble_conn_identity_register(UINT16 handle, + const tBLE_CONN_IDENTITY *p_id, + const BD_ADDR pseudo, + BOOLEAN local_ready); + +/******************************************************************************* +** Function btm_ble_conn_identity_unregister +*******************************************************************************/ +void btm_ble_conn_identity_unregister(UINT16 handle); + +/******************************************************************************* +** Function btm_ble_conn_identity_get_by_handle / get_by_pseudo +** +** Description Copy-out snapshot of a side-table entry. Safe from any task. +*******************************************************************************/ +BOOLEAN btm_ble_conn_identity_get_by_handle(UINT16 handle, tBTM_BLE_CONN_IDENTITY *p_out); +BOOLEAN btm_ble_conn_identity_get_by_pseudo(const BD_ADDR pseudo, tBTM_BLE_CONN_IDENTITY *p_out); + +/******************************************************************************* +** Function btm_ble_conn_identity_exists_by_handle +*******************************************************************************/ +BOOLEAN btm_ble_conn_identity_exists_by_handle(UINT16 handle); + +/******************************************************************************* +** Function btm_ble_pseudo_to_real_peer +** +** Description Reverse map a pseudo back to the real peer. Returns TRUE if +** the pseudo is known. +*******************************************************************************/ +BOOLEAN btm_ble_pseudo_to_real_peer(const BD_ADDR pseudo, BD_ADDR real_peer); + +/******************************************************************************* +** Function btm_ble_pseudo_finalize_local +** +** Description Second-phase finalize, called from the LE Advertising Set +** Terminated handler. Re-keys a link to its pseudo when the +** ext-adv instance was not resolvable at connection complete. +** Defined in btm_ble.c. +*******************************************************************************/ +void btm_ble_pseudo_finalize_local(UINT16 handle); + +/******************************************************************************* +** Function btm_ble_pseudo_apply_identity +** +** Description Re-key a link from a transient-RPA-derived pseudo to the +** stable f(local, peer Identity) pseudo once the peer's +** Identity Address (PID) is learned during pairing. Returns +** TRUE and fills new_pseudo when the pseudo changed so the +** SMP caller can keep smp_cb.pairing_bda consistent. +*******************************************************************************/ +BOOLEAN btm_ble_pseudo_apply_identity(UINT16 handle, const BD_ADDR identity, + UINT8 id_type, BD_ADDR new_pseudo); + +#ifdef __cplusplus +} +#endif + +#endif /* BLE_INCLUDED && SMP_INCLUDED && BLE_PERIPH_PSEUDO_ADDR_BOND */ +#endif /* BTM_BLE_PSEUDO_H */ diff --git a/components/bt/host/bluedroid/stack/btm/include/btm_int.h b/components/bt/host/bluedroid/stack/btm/include/btm_int.h index 98ed8564357..5d4bd3cb846 100644 --- a/components/bt/host/bluedroid/stack/btm/include/btm_int.h +++ b/components/bt/host/bluedroid/stack/btm/include/btm_int.h @@ -622,6 +622,12 @@ typedef struct { BD_ADDR current_addr; /* current adv addr*/ bool current_addr_valid; /* current addr info is valid or not*/ #endif +#if (BLE_INCLUDED == TRUE && SMP_INCLUDED == TRUE && BLE_PERIPH_PSEUDO_ADDR_BOND == TRUE) + BOOLEAN is_pseudo_bond; /* record is keyed by a Host pseudo + * (dual local-identity bond); never + * consolidate it onto the peer + * Identity or its LTK is lost */ +#endif } tBTM_SEC_BLE; @@ -1277,6 +1283,7 @@ void btm_page_to_setup_timeout (void *p_tle); BOOLEAN btm_dev_support_switch (BD_ADDR bd_addr); tBTM_SEC_DEV_REC *btm_sec_alloc_dev (BD_ADDR bd_addr); +tBTM_SEC_DEV_REC *btm_sec_alloc_dev_ex (BD_ADDR bd_addr, tBTM_SEC_DEV_REC *exclude_rec); void btm_sec_free_dev (tBTM_SEC_DEV_REC *p_dev_rec, tBT_TRANSPORT transport); tBTM_SEC_DEV_REC *btm_find_dev (BD_ADDR bd_addr); tBTM_SEC_DEV_REC *btm_find_or_alloc_dev (BD_ADDR bd_addr); diff --git a/components/bt/host/bluedroid/stack/btu/btu_init.c b/components/bt/host/bluedroid/stack/btu/btu_init.c index 14747a03fbc..67b48effa50 100644 --- a/components/bt/host/bluedroid/stack/btu/btu_init.c +++ b/components/bt/host/bluedroid/stack/btu/btu_init.c @@ -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(); diff --git a/components/bt/host/bluedroid/stack/btu/btu_task.c b/components/bt/host/bluedroid/stack/btu/btu_task.c index 242ff8b2301..c38ecea33bc 100644 --- a/components/bt/host/bluedroid/stack/btu/btu_task.c +++ b/components/bt/host/bluedroid/stack/btu/btu_task.c @@ -317,6 +317,21 @@ static void btu_general_alarm_process(void *param) TIMER_LIST_ENT *p_tle = (TIMER_LIST_ENT *)param; assert(p_tle != NULL); + /* Skip stale alarms: btu_free_timer removes the entry before the owning + * structure may be freed, and btu_stop_timer clears in_use, but neither can + * retract a SIG_BTU_GENERAL_ALARM that was already queued to the BTU task. */ + osi_mutex_lock(&btu_general_alarm_lock, OSI_MUTEX_MAX_TIMEOUT); + bool active = hash_map_has_key(btu_general_alarm_hash_map, p_tle); + osi_mutex_unlock(&btu_general_alarm_lock); + if (!active) { + osi_mutex_lock(&btu_oneshot_alarm_lock, OSI_MUTEX_MAX_TIMEOUT); + active = hash_map_has_key(btu_oneshot_alarm_hash_map, p_tle); + osi_mutex_unlock(&btu_oneshot_alarm_lock); + } + if (!active || p_tle->in_use == FALSE) { + return; + } + switch (p_tle->event) { case BTU_TTYPE_BTM_DEV_CTL: #if (CLASSIC_BT_INCLUDED == TRUE) @@ -392,6 +407,12 @@ static void btu_general_alarm_process(void *param) #endif // (GATTC_INCLUDED == TRUE) break; + case BTU_TTYPE_ATT_WAIT_FOR_CONF: +#if (GATTS_INCLUDED == TRUE) + gatt_conf_timeout(p_tle); +#endif // (GATTS_INCLUDED == TRUE) + break; + #if (defined(SMP_INCLUDED) && SMP_INCLUDED == TRUE) case BTU_TTYPE_SMP_PAIRING_CMD: smp_rsp_timeout(p_tle); diff --git a/components/bt/host/bluedroid/stack/gatt/att_protocol.c b/components/bt/host/bluedroid/stack/gatt/att_protocol.c index 704e1e969f0..abcd8061645 100644 --- a/components/bt/host/bluedroid/stack/gatt/att_protocol.c +++ b/components/bt/host/bluedroid/stack/gatt/att_protocol.c @@ -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 @@ -171,7 +174,11 @@ BT_HDR *attp_build_read_by_type_value_cmd (UINT16 payload_size, tGATT_FIND_TYPE_ return NULL; } - if ((p_buf = (BT_HDR *)osi_malloc((UINT16)(sizeof(BT_HDR) + payload_size + L2CAP_MIN_OFFSET))) != NULL) { + if (payload_size > L2CAP_DEFAULT_MTU) { + return NULL; + } + + if ((p_buf = (BT_HDR *)osi_malloc(sizeof(BT_HDR) + payload_size + L2CAP_MIN_OFFSET)) != NULL) { p = (UINT8 *)(p_buf + 1) + L2CAP_MIN_OFFSET; p_buf->offset = L2CAP_MIN_OFFSET; @@ -208,7 +215,11 @@ BT_HDR *attp_build_read_multi_cmd(UINT8 op_code, UINT16 payload_size, UINT16 num UINT8 *p; UINT16 i = 0; - if ((p_buf = (BT_HDR *)osi_malloc((UINT16)(sizeof(BT_HDR) + num_handle * 2 + 1 + L2CAP_MIN_OFFSET))) != NULL) { + if (payload_size > L2CAP_DEFAULT_MTU) { + return NULL; + } + + if ((p_buf = (BT_HDR *)osi_malloc(sizeof(BT_HDR) + (size_t)num_handle * 2 + 1 + L2CAP_MIN_OFFSET)) != NULL) { p = (UINT8 *)(p_buf + 1) + L2CAP_MIN_OFFSET; p_buf->offset = L2CAP_MIN_OFFSET; @@ -321,6 +332,10 @@ BT_HDR *attp_build_value_cmd(UINT16 payload_size, UINT8 op_code, /* handle Read By Type response: reserve space for pair_len */ if (op_code == GATT_RSP_READ_BY_TYPE) { + if (len > GATT_MAX_READ_BY_TYPE_VALUE_LEN) { + GATT_TRACE_WARNING("ReadByType value truncated to %d", GATT_MAX_READ_BY_TYPE_VALUE_LEN); + len = GATT_MAX_READ_BY_TYPE_VALUE_LEN; + } p_pair_len = p++; pair_len = len + 2; /* handle(2 bytes) + value length */ size_now += 1; @@ -387,9 +402,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 +436,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 +516,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 +598,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 +675,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 +734,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 +749,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; diff --git a/components/bt/host/bluedroid/stack/gatt/gatt_api.c b/components/bt/host/bluedroid/stack/gatt/gatt_api.c index 79ae43c0afa..2531927c090 100644 --- a/components/bt/host/bluedroid/stack/gatt/gatt_api.c +++ b/components/bt/host/bluedroid/stack/gatt/gatt_api.c @@ -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" @@ -177,8 +180,8 @@ UINT16 GATTS_CreateService (tGATT_IF gatt_if, tBT_UUID *p_svc_uuid, p_app_uuid128 = &p_reg->app_uuid128; if ((p_list = gatt_find_hdl_buffer_by_app_id(p_app_uuid128, p_svc_uuid, svc_inst)) != NULL) { - s_hdl = p_list->asgn_range.s_handle; GATT_TRACE_DEBUG ("Service already been created!!\n"); + return p_list->asgn_range.s_handle; } else { if ( (p_svc_uuid->len == LEN_UUID_16) && (p_svc_uuid->uu.uuid16 == UUID_SERVCLASS_GATT_SERVER)) { s_hdl = gatt_cb.hdl_cfg.gatt_start_hdl; @@ -232,6 +235,7 @@ UINT16 GATTS_CreateService (tGATT_IF gatt_if, tBT_UUID *p_svc_uuid, if (p_list) { gatt_remove_an_item_from_list(p_list_info, p_list); + gatt_purge_prepare_write_before_free_db(&p_list->svc_db); gatt_free_attr_value_buffer(p_list); gatt_free_hdl_buffer(p_list); } @@ -246,6 +250,7 @@ UINT16 GATTS_CreateService (tGATT_IF gatt_if, tBT_UUID *p_svc_uuid, GATT_TRACE_ERROR ("GATTS_ReserveHandles: service DB initialization failed\n"); if (p_list) { gatt_remove_an_item_from_list(p_list_info, p_list); + gatt_purge_prepare_write_before_free_db(&p_list->svc_db); gatt_free_attr_value_buffer(p_list); gatt_free_hdl_buffer(p_list); } @@ -395,6 +400,7 @@ BOOLEAN GATTS_DeleteService (tGATT_IF gatt_if, tBT_UUID *p_svc_uuid, UINT16 svc_ tGATTS_PENDING_NEW_SRV_START *p_buf; tGATT_REG *p_reg = gatt_get_regcb(gatt_if); tBT_UUID *p_app_uuid128; + BOOLEAN notify_db_change = FALSE; GATT_TRACE_DEBUG ("GATTS_DeleteService"); @@ -415,12 +421,8 @@ BOOLEAN GATTS_DeleteService (tGATT_IF gatt_if, tBT_UUID *p_svc_uuid, UINT16 svc_ GATT_TRACE_DEBUG ("Delete a new service changed item - the service has not yet started"); osi_free(fixed_queue_try_remove_from_queue(gatt_cb.pending_new_srv_start_q, p_buf)); } else { -#if GATTS_ROBUST_CACHING_ENABLED - gatt_update_for_database_change(); -#endif /* GATTS_ROBUST_CACHING_ENABLED */ - if (gatt_cb.srv_chg_mode == GATTS_SEND_SERVICE_CHANGE_AUTO) { - gatt_proc_srv_chg(); - } + /* Service was started; notify clients after it is removed from sr_reg. */ + notify_db_change = TRUE; } if ((i_sreg = gatt_sr_find_i_rcb_by_app_id (p_app_uuid128, @@ -438,9 +440,19 @@ BOOLEAN GATTS_DeleteService (tGATT_IF gatt_if, tBT_UUID *p_svc_uuid, UINT16 svc_ } gatt_remove_an_item_from_list(p_list_info, p_list); + gatt_purge_prepare_write_before_free_db(&p_list->svc_db); gatt_free_attr_value_buffer(p_list); gatt_free_hdl_buffer(p_list); + if (notify_db_change) { +#if GATTS_ROBUST_CACHING_ENABLED + gatt_update_for_database_change(); +#endif /* GATTS_ROBUST_CACHING_ENABLED */ + if (gatt_cb.srv_chg_mode == GATTS_SEND_SERVICE_CHANGE_AUTO) { + gatt_proc_srv_chg(); + } + } + return (TRUE); } @@ -636,6 +648,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 +663,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 +713,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 *)¬if)) != 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; } @@ -1212,7 +1244,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; @@ -1365,6 +1407,15 @@ void GATT_Deregister (tGATT_IF gatt_if) GATTS_StopService(p_sreg->s_hdl); } } + if (gatt_if > 0 && gatt_if <= GATT_MAX_APPS) { + UINT8 prep_idx = (UINT8)(gatt_if - 1); + for (p_node = list_begin(gatt_cb.p_tcb_list); p_node; p_node = list_next(p_node)) { + p_tcb = list_node(p_node); + if (p_tcb->in_use) { + p_tcb->prep_cnt[prep_idx] = 0; + } + } + } /* free all services db buffers if owned by this application */ gatt_free_srvc_db_buffer_app_id(&p_reg->app_uuid128); #endif ///GATTS_INCLUDED == TRUE @@ -1779,12 +1830,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 *)¬if); 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; } @@ -1795,4 +1858,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 diff --git a/components/bt/host/bluedroid/stack/gatt/gatt_attr.c b/components/bt/host/bluedroid/stack/gatt/gatt_attr.c index 27e80d605d5..dd9ded17b7e 100644 --- a/components/bt/host/bluedroid/stack/gatt/gatt_attr.c +++ b/components/bt/host/bluedroid/stack/gatt/gatt_attr.c @@ -131,7 +131,7 @@ static tGATT_PROFILE_CLCB *gatt_profile_find_clcb_by_bd_addr(BD_ADDR bda, tBT_TR for (i_clcb = 0, p_clcb = gatt_cb.profile_clcb; i_clcb < GATT_MAX_APPS; i_clcb++, p_clcb++) { if (p_clcb->in_use && p_clcb->transport == transport && - p_clcb->connected && !memcmp(p_clcb->bda, bda, BD_ADDR_LEN)) { + !memcmp(p_clcb->bda, bda, BD_ADDR_LEN)) { return p_clcb; } } @@ -157,7 +157,7 @@ tGATT_PROFILE_CLCB *gatt_profile_clcb_alloc (UINT16 conn_id, BD_ADDR bda, tBT_TR if (!p_clcb->in_use) { p_clcb->in_use = TRUE; p_clcb->conn_id = conn_id; - p_clcb->connected = TRUE; + p_clcb->connected = (conn_id != 0); p_clcb->transport = transport; memcpy (p_clcb->bda, bda, BD_ADDR_LEN); break; @@ -184,6 +184,57 @@ void gatt_profile_clcb_dealloc (tGATT_PROFILE_CLCB *p_clcb) memset(p_clcb, 0, sizeof(tGATT_PROFILE_CLCB)); } +/******************************************************************************* +** +** Function gatt_copy_read_value +** +** Description Copy attribute value into read/read-blob response. +** +** Returns GATT_SUCCESS if successfully copied; otherwise error code. +** +*******************************************************************************/ +static tGATT_STATUS gatt_copy_read_value(UINT8 *value, UINT16 attr_len, + UINT16 offset, BOOLEAN is_long, + UINT16 mtu, tGATTS_RSP *p_rsp) +{ + UINT16 copy_len; + const UINT8 *src = value; + + if (is_long) { + if (offset > attr_len) { + return GATT_INVALID_OFFSET; + } + copy_len = attr_len - offset; + if (copy_len > 0) { + if (value == NULL) { + return GATT_UNKNOWN_ERROR; + } + src = value + offset; + } + } else { + if (offset > attr_len) { + return GATT_INVALID_OFFSET; + } + copy_len = attr_len; + if (copy_len > 0 && value == NULL) { + return GATT_UNKNOWN_ERROR; + } + } + + if (is_long && mtu > 0 && copy_len > mtu) { + copy_len = mtu; + } + if (copy_len > GATT_MAX_ATTR_LEN) { + copy_len = GATT_MAX_ATTR_LEN; + } + + p_rsp->attr_value.len = copy_len; + if (copy_len > 0) { + memcpy(p_rsp->attr_value.value, src, copy_len); + } + return GATT_SUCCESS; +} + /******************************************************************************* ** ** Function gatt_proc_read @@ -197,11 +248,19 @@ tGATT_STATUS gatt_proc_read (UINT16 conn_id, tGATTS_REQ_TYPE type, tGATT_READ_RE { tGATT_STATUS status = GATT_NO_RESOURCES; UINT16 len = 0; - UINT8 *value; + UINT16 mtu = GATT_MAX_ATTR_LEN; + UINT8 *value = NULL; + UINT8 tcb_idx = GATT_GET_TCB_IDX(conn_id); + tGATT_TCB *tcb = gatt_get_tcb_by_idx(tcb_idx); UNUSED(type); GATT_TRACE_DEBUG("%s handle %x", __func__, p_data->handle); + /* MTU limit applies to Read Blob only; conn_id 0 is used by internal getters. */ + if (p_data->is_long && tcb != NULL && tcb->payload_size > 1) { + mtu = tcb->payload_size - 1; + } + if (p_data->is_long) { p_rsp->attr_value.offset = p_data->offset; } @@ -209,42 +268,41 @@ tGATT_STATUS gatt_proc_read (UINT16 conn_id, tGATTS_REQ_TYPE type, tGATT_READ_RE p_rsp->attr_value.handle = p_data->handle; #if GATTS_ROBUST_CACHING_ENABLED - UINT8 tcb_idx = GATT_GET_TCB_IDX(conn_id); - tGATT_TCB *tcb = gatt_get_tcb_by_idx(tcb_idx); - /* handle request for reading client supported features */ if (p_data->handle == gatt_cb.handle_of_cl_supported_feat) { if (tcb == NULL) { return GATT_INSUF_RESOURCE; } - p_rsp->attr_value.len = 1; - memcpy(p_rsp->attr_value.value, &tcb->cl_supp_feat, 1); - return GATT_SUCCESS; + return gatt_copy_read_value(&tcb->cl_supp_feat, 1, p_data->offset, + p_data->is_long, mtu, p_rsp); } /* handle request for reading database hash */ if (p_data->handle == gatt_cb.handle_of_database_hash) { - p_rsp->attr_value.len = BT_OCTET16_LEN; - memcpy(p_rsp->attr_value.value, gatt_cb.database_hash, BT_OCTET16_LEN); - gatt_sr_update_cl_status(tcb, true); - return GATT_SUCCESS; + if (tcb == NULL) { + return GATT_INSUF_RESOURCE; + } + status = gatt_copy_read_value(gatt_cb.database_hash, BT_OCTET16_LEN, + p_data->offset, p_data->is_long, mtu, p_rsp); + if (status == GATT_SUCCESS) { + gatt_sr_update_cl_status(tcb, true); + } + return status; } /* handle request for reading server supported features */ if (p_data->handle == gatt_cb.handle_of_sr_supported_feat) { - p_rsp->attr_value.len = 1; - memcpy(p_rsp->attr_value.value, &gatt_cb.gatt_sr_supported_feat_mask, 1); - return GATT_SUCCESS; + return gatt_copy_read_value(&gatt_cb.gatt_sr_supported_feat_mask, 1, + p_data->offset, p_data->is_long, mtu, p_rsp); } #endif /* GATTS_ROBUST_CACHING_ENABLED */ /* handle request for reading service changed des and the others */ status = GATTS_GetAttributeValue(p_data->handle, &len, &value); - if(status == GATT_SUCCESS && len > 0 && value) { - if(len > GATT_MAX_ATTR_LEN) { + if (status == GATT_SUCCESS && (len == 0 || value != NULL)) { + if (len > GATT_MAX_ATTR_LEN) { len = GATT_MAX_ATTR_LEN; } - p_rsp->attr_value.len = len; - memcpy(p_rsp->attr_value.value, value, len); + status = gatt_copy_read_value(value, len, p_data->offset, p_data->is_long, mtu, p_rsp); } return status; } @@ -418,7 +476,9 @@ static void gatt_connect_cback (tGATT_IF gatt_if, BD_ADDR bda, UINT16 conn_id, if (!p_clcb->connected) { - /* wait for connection */ + if (!connected) { + gatt_profile_clcb_dealloc(p_clcb); + } return; } @@ -426,6 +486,10 @@ static void gatt_connect_cback (tGATT_IF gatt_if, BD_ADDR bda, UINT16 conn_id, p_clcb->conn_id = conn_id; p_clcb->connected = TRUE; + if (p_clcb->ccc_stage == GATT_SVC_CHANGED_CONNECTING) { + p_clcb->ccc_stage++; + gatt_cl_start_config_ccc(p_clcb); + } } else { gatt_profile_clcb_dealloc(p_clcb); } @@ -682,6 +746,8 @@ void GATT_ConfigServiceChangeCCC (BD_ADDR remote_bda, BOOLEAN enable, tBT_TRANSP if (GATT_GetConnIdIfConnected (gatt_cb.gatt_if, remote_bda, &p_clcb->conn_id, transport)) { p_clcb->connected = TRUE; + } else { + p_clcb->connected = FALSE; } /* hold the link here */ GATT_Connect(gatt_cb.gatt_if, remote_bda, BLE_ADDR_UNKNOWN_TYPE, TRUE, transport, FALSE, FALSE, 0xFF, 0xFF); diff --git a/components/bt/host/bluedroid/stack/gatt/gatt_auth.c b/components/bt/host/bluedroid/stack/gatt/gatt_auth.c index 2dbfa347c6c..78478702a57 100644 --- a/components/bt/host/bluedroid/stack/gatt/gatt_auth.c +++ b/components/bt/host/bluedroid/stack/gatt/gatt_auth.c @@ -28,6 +28,9 @@ #include #include "gatt_int.h" +#if (BLE_EATT_INCLUDED == TRUE) +#include "gatt_eatt_int.h" +#endif #include "stack/gatt_api.h" #include "btm_int.h" @@ -230,31 +233,40 @@ void gatt_notify_enc_cmpl(BD_ADDR bd_addr) tGATT_TCB *p_tcb; UINT8 i = 0; - if ((p_tcb = gatt_find_tcb_by_addr(bd_addr, BT_TRANSPORT_LE)) != NULL) { - for (i = 0; i < GATT_MAX_APPS; i++) { - if (gatt_cb.cl_rcb[i].in_use && gatt_cb.cl_rcb[i].app_cb.p_enc_cmpl_cb) { - (*gatt_cb.cl_rcb[i].app_cb.p_enc_cmpl_cb)(gatt_cb.cl_rcb[i].gatt_if, bd_addr); - } - } - - if (gatt_get_sec_act(p_tcb) == GATT_SEC_ENC_PENDING) { - gatt_set_sec_act(p_tcb, GATT_SEC_NONE); - - size_t count = fixed_queue_length(p_tcb->pending_enc_clcb); - for (; count > 0; count--) { - tGATT_PENDING_ENC_CLCB *p_buf = - (tGATT_PENDING_ENC_CLCB *)fixed_queue_dequeue(p_tcb->pending_enc_clcb, 0); - if (p_buf != NULL) { - gatt_security_check_start(p_buf->p_clcb); - osi_free(p_buf); - } else { - break; - } - } - } - } else { + if ((p_tcb = gatt_find_tcb_by_addr(bd_addr, BT_TRANSPORT_LE)) == NULL) { GATT_TRACE_DEBUG("notify GATT for encryption completion of unknown device"); + return; } + + for (i = 0; i < GATT_MAX_APPS; i++) { + if (gatt_cb.cl_rcb[i].in_use && gatt_cb.cl_rcb[i].app_cb.p_enc_cmpl_cb) { + (*gatt_cb.cl_rcb[i].app_cb.p_enc_cmpl_cb)(gatt_cb.cl_rcb[i].gatt_if, bd_addr); + } + } + + /* p_tcb may be removed in p_enc_cmpl_cb (e.g. disconnect); re-lookup before use */ + if ((p_tcb = gatt_find_tcb_by_addr(bd_addr, BT_TRANSPORT_LE)) == NULL) { + return; + } + + if (gatt_get_sec_act(p_tcb) == GATT_SEC_ENC_PENDING) { + gatt_set_sec_act(p_tcb, GATT_SEC_NONE); + + size_t count = fixed_queue_length(p_tcb->pending_enc_clcb); + for (; count > 0; count--) { + tGATT_PENDING_ENC_CLCB *p_buf = + (tGATT_PENDING_ENC_CLCB *)fixed_queue_dequeue(p_tcb->pending_enc_clcb, 0); + if (p_buf != NULL) { + gatt_security_check_start(p_buf->p_clcb); + osi_free(p_buf); + } else { + break; + } + } + } +#if (BLE_EATT_INCLUDED == TRUE) + gatt_eatt_on_encrypted(bd_addr); +#endif return; } diff --git a/components/bt/host/bluedroid/stack/gatt/gatt_cl.c b/components/bt/host/bluedroid/stack/gatt/gatt_cl.c index 1fc86945659..fcf21c1752f 100644 --- a/components/bt/host/bluedroid/stack/gatt/gatt_cl.c +++ b/components/bt/host/bluedroid/stack/gatt/gatt_cl.c @@ -29,6 +29,9 @@ #include #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, @@ -297,7 +300,7 @@ void gatt_send_queue_write_cancel (tGATT_TCB *p_tcb, tGATT_CLCB *p_clcb, tGATT_E rt = attp_send_cl_msg(p_tcb, p_clcb->clcb_idx, GATT_REQ_EXEC_WRITE, (tGATT_CL_MSG *)&flag); - if (rt != GATT_SUCCESS) { + if (rt != GATT_SUCCESS && rt != GATT_CMD_STARTED && rt != GATT_CONGESTED) { gatt_end_operation(p_clcb, rt, NULL); } } @@ -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; @@ -403,6 +406,7 @@ void gatt_process_find_type_value_rsp (tGATT_TCB *p_tcb, tGATT_CLCB *p_clcb, UIN { tGATT_DISC_RES result; UINT8 *p = p_data; + UINT16 req_s_handle, prev_e_handle; UNUSED(p_tcb); @@ -412,6 +416,9 @@ void gatt_process_find_type_value_rsp (tGATT_TCB *p_tcb, tGATT_CLCB *p_clcb, UIN return; } + req_s_handle = p_clcb->s_handle; + prev_e_handle = req_s_handle - 1; + memset (&result, 0, sizeof(tGATT_DISC_RES)); result.type.len = 2; result.type.uu.uuid16 = GATT_UUID_PRI_SERVICE; @@ -420,6 +427,25 @@ void gatt_process_find_type_value_rsp (tGATT_TCB *p_tcb, tGATT_CLCB *p_clcb, UIN while (len >= 4) { STREAM_TO_UINT16 (result.handle, p); STREAM_TO_UINT16 (result.value.group_value.e_handle, p); + + /* Reject handles that fall outside the requested range or are not + * strictly increasing; a malicious/buggy peer must not be able to make + * discovery loop forever or report overlapping services. */ + if (!GATT_HANDLE_IS_VALID(result.handle) || + !GATT_HANDLE_IS_VALID(result.value.group_value.e_handle) || + result.handle < req_s_handle || + result.handle > p_clcb->e_handle || + result.handle > result.value.group_value.e_handle || + result.handle <= prev_e_handle || + result.value.group_value.e_handle <= prev_e_handle) { + GATT_TRACE_ERROR("%s invalid handle range: s=%x e=%x req=[%x,%x]", + __func__, result.handle, result.value.group_value.e_handle, + req_s_handle, p_clcb->e_handle); + gatt_end_operation(p_clcb, GATT_INVALID_HANDLE, NULL); + return; + } + + prev_e_handle = result.value.group_value.e_handle; GATT_DISC_INFO("%s handle %x, end handle %x", __func__, result.handle, result.value.group_value.e_handle); memcpy (&result.value.group_value.service_type, &p_clcb->uuid, sizeof(tBT_UUID)); @@ -430,8 +456,8 @@ void gatt_process_find_type_value_rsp (tGATT_TCB *p_tcb, tGATT_CLCB *p_clcb, UIN } } - /* last handle + 1 */ - p_clcb->s_handle = (result.value.group_value.e_handle == 0) ? 0 : (result.value.group_value.e_handle + 1); + /* last handle + 1; empty response ends discovery */ + p_clcb->s_handle = (prev_e_handle < req_s_handle) ? 0 : (prev_e_handle + 1); /* initiate another request */ gatt_act_discovery(p_clcb) ; } @@ -451,6 +477,7 @@ void gatt_process_read_info_rsp(tGATT_TCB *p_tcb, tGATT_CLCB *p_clcb, UINT8 op_c { tGATT_DISC_RES result = {0}; UINT8 *p = p_data, uuid_len = 0, type; + UINT16 req_s_handle, prev_handle; UNUSED(p_tcb); UNUSED(op_code); @@ -465,6 +492,9 @@ void gatt_process_read_info_rsp(tGATT_TCB *p_tcb, tGATT_CLCB *p_clcb, UINT8 op_c return; } + req_s_handle = p_clcb->s_handle; + prev_handle = req_s_handle - 1; + STREAM_TO_UINT8(type, p); len -= 1; @@ -481,6 +511,16 @@ void gatt_process_read_info_rsp(tGATT_TCB *p_tcb, tGATT_CLCB *p_clcb, UINT8 op_c while (len >= uuid_len + 2) { STREAM_TO_UINT16 (result.handle, p); + if (!GATT_HANDLE_IS_VALID(result.handle) || + result.handle < req_s_handle || + result.handle > p_clcb->e_handle || + result.handle <= prev_handle) { + GATT_TRACE_ERROR("%s invalid handle %x req=[%x,%x]", + __func__, result.handle, req_s_handle, p_clcb->e_handle); + gatt_end_operation(p_clcb, GATT_INVALID_HANDLE, NULL); + return; + } + if (uuid_len > 0) { if (!gatt_parse_uuid_from_cmd(&result.type, uuid_len, &p)) { break; @@ -489,6 +529,7 @@ void gatt_process_read_info_rsp(tGATT_TCB *p_tcb, tGATT_CLCB *p_clcb, UINT8 op_c memcpy (&result.type, &p_clcb->uuid, sizeof(tBT_UUID)); } + prev_handle = result.handle; len -= (uuid_len + 2); GATT_DISC_INFO("%s handle %x, uuid %s", __func__, result.handle, gatt_uuid_to_str(&result.type)); @@ -498,7 +539,7 @@ void gatt_process_read_info_rsp(tGATT_TCB *p_tcb, tGATT_CLCB *p_clcb, UINT8 op_c } } - p_clcb->s_handle = (result.handle == 0) ? 0 : (result.handle + 1); + p_clcb->s_handle = (prev_handle < req_s_handle) ? 0 : (prev_handle + 1); /* initiate another request */ gatt_act_discovery(p_clcb) ; } @@ -694,6 +735,9 @@ void gatt_process_notification(tGATT_TCB *p_tcb, UINT8 op_code, if (value.len > GATT_MAX_ATTR_LEN) { GATT_TRACE_ERROR("value length larger than GATT_MAX_ATTR_LEN, discard"); + if (op_code == GATT_HANDLE_VALUE_IND) { + attp_send_cl_msg(p_tcb, 0, GATT_HANDLE_VALUE_CONF, NULL); + } return; } @@ -722,6 +766,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); @@ -752,6 +803,10 @@ void gatt_process_notification(tGATT_TCB *p_tcb, UINT8 op_code, STREAM_TO_UINT16(value.len, p); len -= 4; value.len = MIN(len, value.len); + if (value.len > GATT_MAX_ATTR_LEN) { + GATT_TRACE_ERROR("value length larger than GATT_MAX_ATTR_LEN, discard"); + return; + } memcpy(value.value, p, value.len); p += value.len; len -= value.len; @@ -783,12 +838,19 @@ void gatt_process_read_by_type_rsp (tGATT_TCB *p_tcb, tGATT_CLCB *p_clcb, UINT8 tGATT_DISC_VALUE record_value; UINT8 *p = p_data, value_len, handle_len = 2; UINT16 handle = 0; + UINT16 req_s_handle = 0, prev_disc_handle = 0; + BOOLEAN is_discovery = (p_clcb->operation == GATTC_OPTYPE_DISCOVERY); /* discovery procedure and no callback function registered */ - if (((!p_clcb->p_reg) || (!p_clcb->p_reg->app_cb.p_disc_res_cb)) && (p_clcb->operation == GATTC_OPTYPE_DISCOVERY)) { + if (((!p_clcb->p_reg) || (!p_clcb->p_reg->app_cb.p_disc_res_cb)) && is_discovery) { return; } + if (is_discovery) { + req_s_handle = p_clcb->s_handle; + prev_disc_handle = req_s_handle - 1; + } + if (len < GATT_READ_BY_TYPE_RSP_MIN_LEN) { GATT_TRACE_ERROR("Illegal ReadByType/ReadByGroupType Response length, discard"); gatt_end_operation(p_clcb, GATT_INVALID_PDU, NULL); @@ -797,11 +859,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; } @@ -830,6 +892,16 @@ void gatt_process_read_by_type_rsp (tGATT_TCB *p_tcb, tGATT_CLCB *p_clcb, UINT8 return; } + if (is_discovery && p_clcb->op_subtype != GATT_DISC_SRVC_ALL) { + if (handle < req_s_handle || handle > p_clcb->e_handle || handle <= prev_disc_handle) { + GATT_TRACE_ERROR("%s invalid handle %x req=[%x,%x]", + __func__, handle, req_s_handle, p_clcb->e_handle); + gatt_end_operation(p_clcb, GATT_INVALID_HANDLE, NULL); + return; + } + prev_disc_handle = handle; + } + memset(&result, 0, sizeof(tGATT_DISC_RES)); memset(&record_value, 0, sizeof(tGATT_DISC_VALUE)); @@ -848,6 +920,19 @@ void gatt_process_read_by_type_rsp (tGATT_TCB *p_tcb, tGATT_CLCB *p_clcb, UINT8 return; } else { record_value.group_value.e_handle = handle; + if (!GATT_HANDLE_IS_VALID(result.handle) || + result.handle < req_s_handle || + result.handle > p_clcb->e_handle || + result.handle > record_value.group_value.e_handle || + result.handle <= prev_disc_handle || + record_value.group_value.e_handle <= prev_disc_handle) { + GATT_TRACE_ERROR("%s invalid svc range: s=%x e=%x req=[%x,%x]", + __func__, result.handle, record_value.group_value.e_handle, + req_s_handle, p_clcb->e_handle); + gatt_end_operation(p_clcb, GATT_INVALID_HANDLE, NULL); + return; + } + prev_disc_handle = record_value.group_value.e_handle; if (!gatt_parse_uuid_from_cmd(&record_value.group_value.service_type, value_len, &p)) { GATT_TRACE_ERROR("discover all service response parsing failure"); break; @@ -857,6 +942,11 @@ void gatt_process_read_by_type_rsp (tGATT_TCB *p_tcb, tGATT_CLCB *p_clcb, UINT8 } /* discover included service */ else if (p_clcb->operation == GATTC_OPTYPE_DISCOVERY && p_clcb->op_subtype == GATT_DISC_INC_SRVC) { + if (value_len < 4) { + GATT_TRACE_ERROR("gatt_process_read_by_type_rsp INCL_SRVC: value_len(%d) too short", value_len); + gatt_end_operation(p_clcb, GATT_INVALID_PDU, NULL); + return; + } STREAM_TO_UINT16(record_value.incl_service.s_handle, p); STREAM_TO_UINT16(record_value.incl_service.e_handle, p); @@ -891,7 +981,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; } @@ -911,6 +1001,11 @@ void gatt_process_read_by_type_rsp (tGATT_TCB *p_tcb, tGATT_CLCB *p_clcb, UINT8 } return; } else { /* discover characteristic */ + if (value_len < 3) { + GATT_TRACE_ERROR("gatt_process_read_by_type_rsp CHAR: value_len(%d) too short", value_len); + gatt_end_operation(p_clcb, GATT_INVALID_PDU, NULL); + return; + } STREAM_TO_UINT8 (record_value.dclr_value.char_prop, p); STREAM_TO_UINT16(record_value.dclr_value.val_handle, p); if (!GATT_HANDLE_IS_VALID(record_value.dclr_value.val_handle)) { @@ -949,12 +1044,12 @@ void gatt_process_read_by_type_rsp (tGATT_TCB *p_tcb, tGATT_CLCB *p_clcb, UINT8 } } - p_clcb->s_handle = (handle == 0) ? 0 : (handle + 1); - - if (p_clcb->operation == GATTC_OPTYPE_DISCOVERY) { + if (is_discovery) { + p_clcb->s_handle = (prev_disc_handle < req_s_handle) ? 0 : (prev_disc_handle + 1); /* initiate another request */ gatt_act_discovery(p_clcb) ; - } else { /* read characteristic value */ + } else { + p_clcb->s_handle = (handle == 0) ? 0 : (handle + 1); gatt_act_read(p_clcb, 0); } } @@ -1000,7 +1095,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 +1163,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,31 +1290,84 @@ 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 req_op_code = 0; + 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); +#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) { + req_op_code = cmd_code; + rsp_code = gatt_cmd_to_rsp_code(cmd_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); + /* 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) { + gatt_eatt_mark_busy(p_tcb->peer_bda, eatt_bearer_lcid, cmd_code, clcb_idx); + } + return; + } - 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); + btu_stop_timer (&p_clcb->rsp_timer_ent); + p_clcb->retry_count = 0; + } else +#endif + { + if (p_tcb->pending_cl_req == p_tcb->next_slot_inq) { + GATT_TRACE_WARNING("ATT - Unexpected response (%02x), no pending command", op_code); + return; + } + + req_op_code = p_tcb->cl_cmd_q[p_tcb->pending_cl_req].op_code; + rsp_code = gatt_cmd_to_rsp_code(req_op_code); + + if (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); + + p_clcb = gatt_cmd_dequeue(p_tcb, &req_op_code); + if (p_clcb != NULL) { + btu_stop_timer(&p_clcb->rsp_timer_ent); + gatt_end_operation(p_clcb, GATT_ERROR, NULL); + } + gatt_cl_send_next_cmd_inq(p_tcb); + return; + } + + p_clcb = gatt_cmd_dequeue(p_tcb, &req_op_code); + if (p_clcb == NULL) { + GATT_TRACE_WARNING("ATT - Response (%02x) with no CLCB", op_code); + gatt_cl_send_next_cmd_inq(p_tcb); + return; + } - return; - } else { btu_stop_timer (&p_clcb->rsp_timer_ent); p_clcb->retry_count = 0; } } /* 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); @@ -1266,13 +1422,21 @@ void gatt_client_handle_server_rsp (tGATT_TCB *p_tcb, UINT8 op_code, default: GATT_TRACE_ERROR("Unknown opcode = %d", op_code); + if (p_clcb != NULL) { + gatt_end_operation(p_clcb, GATT_ERROR, NULL); + } break; } } 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); + } } } diff --git a/components/bt/host/bluedroid/stack/gatt/gatt_db.c b/components/bt/host/bluedroid/stack/gatt/gatt_db.c index 299eb80927d..ca27470999b 100644 --- a/components/bt/host/bluedroid/stack/gatt/gatt_db.c +++ b/components/bt/host/bluedroid/stack/gatt/gatt_db.c @@ -236,9 +236,9 @@ static tGATT_STATUS read_attr_value (void *p_attr, status = GATT_NO_RESOURCES; if (uuid16 == GATT_UUID_PRI_SERVICE || uuid16 == GATT_UUID_SEC_SERVICE) { - len = p_attr16->p_value->uuid.len; - if (mtu >= p_attr16->p_value->uuid.len) { - gatt_build_uuid_to_stream(&p, p_attr16->p_value->uuid); + len = gatt_get_uuid_stream_len(p_attr16->p_value->uuid); + if (mtu >= len) { + len = gatt_build_uuid_to_stream(&p, p_attr16->p_value->uuid); status = GATT_SUCCESS; } } else if (uuid16 == GATT_UUID_CHAR_DECLARE) { @@ -370,13 +370,19 @@ tGATT_STATUS gatts_db_read_attr_value_by_type (tGATT_TCB *p_tcb, UINT16_TO_STREAM (p, p_attr->handle); - { - UINT16 max_val_len = (UINT16)(*p_len - 2); - if (max_val_len > GATT_MAX_READ_BY_TYPE_VALUE_LEN) { - max_val_len = GATT_MAX_READ_BY_TYPE_VALUE_LEN; - } - status = read_attr_value ((void *)p_attr, 0, &p, FALSE, max_val_len, &len, sec_flag, key_size); + /* + * ATT Read By Type Response encodes each Handle-Value Pair length in 1 octet. + * Therefore a single record must be <= 255 bytes including the 2-byte handle, + * i.e. the value length must be <= 253 bytes. + * + * Limit the maximum value length here so that p_rsp->offset (pair_len) never + * exceeds 255 and cannot be truncated when written to the response PDU. + */ + UINT16 max_value_len = (UINT16)(*p_len - 2); + if (max_value_len > GATT_MAX_READ_BY_TYPE_VALUE_LEN) { + max_value_len = GATT_MAX_READ_BY_TYPE_VALUE_LEN; } + status = read_attr_value((void *)p_attr, 0, &p, FALSE, max_value_len, &len, sec_flag, key_size); if (status == GATT_PENDING) { @@ -1591,6 +1597,8 @@ static BOOLEAN gatts_db_add_service_declaration(tGATT_SVC_DB *p_db, tBT_UUID *p_ memcpy(p_attr->p_value->uuid.uu.uuid128, p_service->uu.uuid128, LEN_UUID_128); } rt = TRUE; + } else { + deallocate_attr_in_db(p_db, p_attr); } } diff --git a/components/bt/host/bluedroid/stack/gatt/gatt_eatt.c b/components/bt/host/bluedroid/stack/gatt/gatt_eatt.c new file mode 100644 index 00000000000..96bb2c61959 --- /dev/null +++ b/components/bt/host/bluedroid/stack/gatt/gatt_eatt.c @@ -0,0 +1,1369 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* EATT (Enhanced ATT) over LE Enhanced CoC (PSM 0x0027). */ + +#include +#include "common/bt_target.h" +#include "stack/bt_types.h" +#include "stack/l2c_api.h" +#include "stack/l2cdefs.h" +#include "stack/gatt_api.h" +#include "stack/gattdefs.h" +#include "stack/sdpdefs.h" +#include "stack/btm_ble_api.h" +#include "stack/btm_api.h" +#include "btm_int.h" +#include "l2c_int.h" +#include "gatt_int.h" +#include "gatt_eatt_int.h" +#include "osi/allocator.h" + +#if (BLE_EATT_INCLUDED == TRUE) + +#define GATT_EATT_TRACE_API(fmt, ...) GATT_TRACE_API("EATT: " fmt, ##__VA_ARGS__) +#define GATT_EATT_TRACE_DEBUG(fmt, ...) GATT_TRACE_DEBUG("EATT: " fmt, ##__VA_ARGS__) +#define GATT_EATT_TRACE_ERROR(fmt, ...) GATT_TRACE_ERROR("EATT: " fmt, ##__VA_ARGS__) + +#define BLE_GATT_SR_SUPP_FEAT_EATT_BITMASK 0x01 +#define BLE_GATT_CL_SUPP_FEAT_EATT_BITMASK 0x02 + +typedef enum { + GATT_EATT_ST_IDLE = 0, + GATT_EATT_ST_READ_SR_FEAT, + GATT_EATT_ST_READ_CL_FEAT, + GATT_EATT_ST_WRITE_CL_FEAT, + GATT_EATT_ST_CONNECTING, +} tGATT_EATT_SETUP_ST; + +typedef struct { + BOOLEAN in_use; + BOOLEAN reported; /* TRUE once a connect event (status=0) was delivered for + * this bearer, so free_bearer only reports a symmetric + * disconnect for bearers the app actually saw connect. */ + UINT16 lcid; + UINT8 client_op; + UINT16 clcb_idx; +} tGATT_EATT_BEARER; + +typedef struct { + BOOLEAN in_use; + BD_ADDR peer_bda; + UINT16 conn_id; + UINT8 bearer_count; + UINT8 setup_target; /* number of bearers requested in the active setup */ + UINT8 setup_done; /* number of setup responses (ok or fail) received */ + UINT16 default_lcid; + tGATT_EATT_SETUP_ST setup_st; + UINT16 peer_cl_feat_handle; + UINT8 peer_cl_feat_val; /* current Client Supported Features value read back */ + UINT8 tx_rr; /* round-robin cursor for server-initiated PDUs */ + tGATT_EATT_BEARER bearers[GATT_EATT_MAX_CHAN]; +} tGATT_EATT_CONN; + +static tGATT_EATT_CONN s_eatt_conn[MAX_L2CAP_LINKS]; +static tL2CAP_APPL_INFO s_eatt_l2cap_appl; +static UINT16 s_eatt_reg_psm; +static UINT8 s_eatt_chan_num = GATT_EATT_MAX_CHAN; +static tGATT_EATT_EVT_CBACK *s_eatt_evt_cback; +static tGATT_IF s_eatt_gatt_if; + +static void gatt_eatt_connect_cfm(UINT16 lcid, UINT16 result); +static void gatt_eatt_disconnect_ind(UINT16 lcid, BOOLEAN local_init); +static void gatt_eatt_data_ind_l2c(UINT16 lcid, BT_HDR *p_buf); +static void gatt_eatt_congestion(UINT16 lcid, BOOLEAN congested); + +static void gatt_eatt_gatt_conn_cback(tGATT_IF gatt_if, BD_ADDR bda, UINT16 conn_id, + BOOLEAN connected, tGATT_DISCONN_REASON reason, + tBT_TRANSPORT transport); +#if (BLE_EATT_CLIENT_INCLUDED == TRUE) +static void gatt_eatt_gatt_cmpl_cback(UINT16 conn_id, tGATTC_OPTYPE op, tGATT_STATUS status, + tGATT_CL_COMPLETE *p_data); +#endif +#if (BLE_EATT_SERVER_INCLUDED == TRUE) +static void gatt_eatt_connect_ind(BD_ADDR bd_addr, UINT16 lcid, UINT16 psm, UINT8 id); +#endif + +static tGATT_EATT_CONN *gatt_eatt_find_conn_by_bda(BD_ADDR bd_addr) +{ + for (int i = 0; i < MAX_L2CAP_LINKS; i++) { + if (s_eatt_conn[i].in_use && + memcmp(s_eatt_conn[i].peer_bda, bd_addr, BD_ADDR_LEN) == 0) { + return &s_eatt_conn[i]; + } + } + return NULL; +} + +#if (BLE_EATT_CLIENT_INCLUDED == TRUE) +static tGATT_EATT_CONN *gatt_eatt_find_conn_for_setup(UINT16 stack_conn_id) +{ + UINT8 tcb_idx = GATT_GET_TCB_IDX(stack_conn_id); + tGATT_TCB *p_tcb = gatt_get_tcb_by_idx(tcb_idx); + + if (p_tcb == NULL) { + return NULL; + } + return gatt_eatt_find_conn_by_bda(p_tcb->peer_bda); +} +#endif + +static tGATT_EATT_CONN *gatt_eatt_alloc_conn(BD_ADDR bd_addr) +{ + tGATT_EATT_CONN *ec; + + ec = gatt_eatt_find_conn_by_bda(bd_addr); + if (ec != NULL) { + return ec; + } + + for (int i = 0; i < MAX_L2CAP_LINKS; i++) { + if (!s_eatt_conn[i].in_use) { + memset(&s_eatt_conn[i], 0, sizeof(s_eatt_conn[i])); + s_eatt_conn[i].in_use = TRUE; + memcpy(s_eatt_conn[i].peer_bda, bd_addr, BD_ADDR_LEN); + return &s_eatt_conn[i]; + } + } + GATT_EATT_TRACE_ERROR("alloc_conn failed: conn table full"); + return NULL; +} + +static tGATT_EATT_BEARER *gatt_eatt_find_bearer(tGATT_EATT_CONN *ec, UINT16 lcid) +{ + if (ec == NULL) { + return NULL; + } + for (int i = 0; i < GATT_EATT_MAX_CHAN; i++) { + if (ec->bearers[i].in_use && ec->bearers[i].lcid == lcid) { + return &ec->bearers[i]; + } + } + return NULL; +} + +static tGATT_EATT_BEARER *gatt_eatt_alloc_bearer(tGATT_EATT_CONN *ec, UINT16 lcid) +{ + tGATT_EATT_BEARER *b; + + b = gatt_eatt_find_bearer(ec, lcid); + if (b != NULL) { + return b; + } + + for (int i = 0; i < GATT_EATT_MAX_CHAN; i++) { + if (!ec->bearers[i].in_use) { + memset(&ec->bearers[i], 0, sizeof(ec->bearers[i])); + ec->bearers[i].in_use = TRUE; + ec->bearers[i].lcid = lcid; + ec->bearer_count++; + return &ec->bearers[i]; + } + } + GATT_EATT_TRACE_ERROR("alloc_bearer failed: slots full lcid=0x%04x bearer_count=%u", + lcid, ec->bearer_count); + return NULL; +} + +static void gatt_eatt_free_bearer(tGATT_EATT_CONN *ec, tGATT_EATT_BEARER *b) +{ + BD_ADDR peer_bda; + UINT16 freed_lcid; + tGATT_TCB *p_tcb; + + if (ec == NULL || b == NULL || !b->in_use) { + return; + } + memcpy(peer_bda, ec->peer_bda, BD_ADDR_LEN); + freed_lcid = b->lcid; + if (ec->default_lcid == b->lcid) { + ec->default_lcid = 0; + } + /* Do not guard on ec->conn_id: an app conn_id of 0 is the valid tcb_idx 0 + * (the first BLE connection), not a "not found" sentinel. Guarding on it + * would suppress the disconnect event for tcb_idx 0 while its connect event + * was reported, causing an asymmetry. + * + * Only report the disconnect if a matching connect event was delivered. A + * bearer allocated in connect_ind but torn down before connect_cfm succeeds + * (e.g. L2CA_ConnectLECocRsp failed, or the link dropped mid-setup) was never + * reported as connected, so emitting a disconnect for it would be spurious. */ + if (s_eatt_evt_cback && b->reported) { + GATT_EATT_TRACE_DEBUG("free_bearer report disconnect conn_id=%u lcid=0x%04x", + ec->conn_id, b->lcid); + (*s_eatt_evt_cback)(ec->conn_id, 1, b->lcid); + } + memset(b, 0, sizeof(*b)); + if (ec->bearer_count > 0) { + ec->bearer_count--; + } + if (ec->bearer_count == 0) { + GATT_EATT_TRACE_DEBUG("free_bearer last bearer gone conn_id=%u", ec->conn_id); + ec->setup_st = GATT_EATT_ST_IDLE; + } + p_tcb = gatt_find_tcb_by_addr(peer_bda, BT_TRANSPORT_LE); + if (p_tcb != NULL) { + if (p_tcb->eatt_ind_bearer == freed_lcid) { + p_tcb->eatt_ind_bearer = 0; + } +#if (GATTS_INCLUDED == TRUE) + if (p_tcb->sr_cmd.eatt_lcid == freed_lcid) { + /* The freed bearer owns the pending server command. Fully clear the + * sr_cmd slot (op_code, p_rsp_msg, multi_rsp_q, eatt_lcid) via + * gatt_dequeue_sr_cmd. Clearing only eatt_lcid would leave op_code + * non-zero, so gatt_sr_cmd_empty stays FALSE and every later server + * request from this peer is silently discarded until the ACL drops; + * any pending response buffer would also leak. */ + gatt_dequeue_sr_cmd(p_tcb); + } +#endif + if (ec->bearer_count == 0) { + p_tcb->eatt_att_mtu = 0; + } + } +} + +static void gatt_eatt_free_conn(tGATT_EATT_CONN *ec) +{ + if (ec == NULL) { + return; + } + + GATT_EATT_TRACE_DEBUG("free_conn conn_id=%u bearer_count=%u", ec->conn_id, ec->bearer_count); + for (int i = 0; i < GATT_EATT_MAX_CHAN; i++) { + if (ec->bearers[i].in_use) { + gatt_eatt_free_bearer(ec, &ec->bearers[i]); + } + } + memset(ec, 0, sizeof(*ec)); +} + +static UINT16 gatt_eatt_chan_mtu(UINT16 lcid) +{ + tL2C_CCB *p_ccb = l2cu_find_ccb_by_cid(NULL, lcid); + + if (p_ccb == NULL) { + return 0; + } + return MIN(p_ccb->local_conn_cfg.mtu, p_ccb->peer_conn_cfg.mtu); +} + +static void gatt_eatt_update_tcb_mtu(BD_ADDR bd_addr, UINT16 lcid) +{ + tGATT_TCB *p_tcb = gatt_find_tcb_by_addr(bd_addr, BT_TRANSPORT_LE); + UINT16 mtu; + + if (p_tcb == NULL) { + return; + } + mtu = gatt_eatt_chan_mtu(lcid); + if (mtu >= GATT_DEF_BLE_MTU_SIZE) { + p_tcb->eatt_att_mtu = mtu; + GATT_EATT_TRACE_API("sync att mtu=%u lcid=0x%04x", mtu, lcid); + } +} + +void gatt_eatt_on_chan_mtu_changed(BD_ADDR bd_addr, UINT16 lcid) +{ + if (!gatt_eatt_is_bearer(lcid)) { + return; + } + gatt_eatt_update_tcb_mtu(bd_addr, lcid); +} + +UINT16 gatt_get_att_mtu(tGATT_TCB *p_tcb) +{ + UINT16 mtu; + + if (p_tcb == NULL) { + return GATT_DEF_BLE_MTU_SIZE; + } + if (p_tcb->eatt_tx_bearer != 0) { + mtu = gatt_eatt_chan_mtu(p_tcb->eatt_tx_bearer); + if (mtu >= GATT_DEF_BLE_MTU_SIZE) { + return mtu; + } + } + if (p_tcb->eatt_rx_bearer != 0) { + mtu = gatt_eatt_chan_mtu(p_tcb->eatt_rx_bearer); + if (mtu >= GATT_DEF_BLE_MTU_SIZE) { + return mtu; + } + } + return p_tcb->payload_size; +} + +UINT16 gatt_eatt_mtu_for_client_op(BD_ADDR bd_addr, UINT8 op_code, UINT16 legacy_mtu) +{ +#if (BLE_EATT_CLIENT_INCLUDED == TRUE) + UINT16 lcid; + UINT16 mtu; + + if (op_code == GATT_REQ_MTU || op_code == GATT_CMD_WRITE || + op_code == GATT_SIGN_CMD_WRITE || op_code == GATT_HANDLE_VALUE_CONF) { + return legacy_mtu; + } + + lcid = gatt_eatt_get_available_bearer(bd_addr, op_code); + if (lcid == L2CAP_ATT_CID) { + return legacy_mtu; + } + + mtu = gatt_eatt_chan_mtu(lcid); + if (mtu >= GATT_DEF_BLE_MTU_SIZE) { + return mtu; + } + return legacy_mtu; +#else + UNUSED(bd_addr); + UNUSED(op_code); + return legacy_mtu; +#endif +} + +static UINT16 gatt_eatt_app_conn_id_from_bda(BD_ADDR bd_addr) +{ + tGATT_TCB *p_tcb = gatt_find_tcb_by_addr(bd_addr, BT_TRANSPORT_LE); + + /* ESP exposes conn_id as tcb_idx (see BTC_GATT_GET_CONN_ID / btc_gattc.c), + * so report tcb_idx here to stay consistent with esp_ble_gattc_* events and + * with esp_ble_eatt_set_default_bearer(). */ + if (p_tcb != NULL && gatt_get_ch_state(p_tcb) == GATT_CH_OPEN) { + return p_tcb->tcb_idx; + } + /* tcb_idx is a UINT8 starting at 0, so the first BLE connection legitimately + * owns tcb_idx 0. Return the dedicated invalid sentinel (0xFFFF) for the + * not-found/not-open case so a raced setup does not report an EATT event with + * conn_id 0 that the app would misroute to the real first connection. */ + return GATT_INVALID_CONN_ID; +} + +static UINT16 gatt_eatt_stack_conn_id_from_bda(BD_ADDR bd_addr) +{ + tGATT_TCB *p_tcb = gatt_find_tcb_by_addr(bd_addr, BT_TRANSPORT_LE); + + if (p_tcb != NULL && gatt_get_ch_state(p_tcb) == GATT_CH_OPEN && s_eatt_gatt_if != 0) { + return GATT_CREATE_CONN_ID(p_tcb->tcb_idx, s_eatt_gatt_if); + } + if (p_tcb != NULL) { + GATT_EATT_TRACE_DEBUG("stack_conn_id: tcb ch_state=%u eatt_gatt_if=%u", + gatt_get_ch_state(p_tcb), s_eatt_gatt_if); + } + return 0; +} + +static void gatt_eatt_log_peer(const char *tag, BD_ADDR bd_addr) +{ + GATT_EATT_TRACE_API("%s peer %02x:%02x:%02x:%02x:%02x:%02x", + tag, + bd_addr[0], bd_addr[1], bd_addr[2], + bd_addr[3], bd_addr[4], bd_addr[5]); +} + +static void gatt_eatt_report_evt(UINT16 conn_id, UINT8 status, UINT16 cid) +{ + if (s_eatt_evt_cback) { + GATT_EATT_TRACE_DEBUG("report evt conn_id=%u status=%u cid=0x%04x", conn_id, status, cid); + (*s_eatt_evt_cback)(conn_id, status, cid); + } else { + GATT_EATT_TRACE_DEBUG("report evt dropped (no cback) conn_id=%u status=%u cid=0x%04x", + conn_id, status, cid); + } +} + +static BOOLEAN gatt_eatt_is_central(BD_ADDR bd_addr) +{ + tL2C_LCB *p_lcb = l2cu_find_lcb_by_bd_addr(bd_addr, BT_TRANSPORT_LE); + + if (p_lcb != NULL && p_lcb->link_state == LST_CONNECTED) { + return (p_lcb->link_role == HCI_ROLE_MASTER); + } + + tBTM_SEC_DEV_REC *p_dev = btm_find_dev(bd_addr); + return (p_dev != NULL && p_dev->role_master); +} + +static BOOLEAN gatt_eatt_is_encrypted(BD_ADDR bd_addr) +{ + UINT8 sec_flag = 0; + + if (!BTM_GetSecurityFlagsByTransport(bd_addr, &sec_flag, BT_TRANSPORT_LE)) { + return FALSE; + } + return (sec_flag & BTM_SEC_FLAG_ENCRYPTED) != 0; +} + +#if (BLE_EATT_CLIENT_INCLUDED == TRUE) +static void gatt_eatt_fallback_legacy(tGATT_EATT_CONN *ec) +{ + if (ec == NULL) { + return; + } + GATT_EATT_TRACE_DEBUG("EATT setup aborted, fallback to Legacy ATT (CID 4)"); + gatt_eatt_free_conn(ec); +} + +static void gatt_eatt_start_ecoc_connect(BD_ADDR bd_addr) +{ + tGATT_EATT_CONN *ec = gatt_eatt_alloc_conn(bd_addr); + tL2CAP_LE_CFG_INFO cfg = {0}; + UINT16 lcids[GATT_EATT_MAX_CHAN]; + + if (ec == NULL) { + return; + } + + ec->conn_id = gatt_eatt_app_conn_id_from_bda(bd_addr); + ec->setup_st = GATT_EATT_ST_CONNECTING; + ec->setup_target = s_eatt_chan_num; + ec->setup_done = 0; + + cfg.mtu = GATT_EATT_MTU; + cfg.mps = L2CAP_LE_COC_MPS; + + GATT_EATT_TRACE_API("ConnectLEEcocReq psm=0x%04x n=%u", GATT_EATT_PSM, s_eatt_chan_num); + { + UINT8 num_started = L2CA_ConnectLEEcocReq(GATT_EATT_PSM, bd_addr, &cfg, s_eatt_chan_num, lcids); + if (num_started == 0) { + GATT_EATT_TRACE_ERROR("ConnectLEEcocReq failed (no RCB or link not ready?)"); + /* Release the conn slot so a stale in-use ec does not linger and + * occupy a MAX_L2CAP_LINKS entry (fall back to Legacy ATT). */ + gatt_eatt_free_conn(ec); + } else { + GATT_EATT_TRACE_API("ConnectLEEcocReq started n=%u lcids=0x%04x 0x%04x 0x%04x", + num_started, lcids[0], + s_eatt_chan_num > 1 ? lcids[1] : 0, + s_eatt_chan_num > 2 ? lcids[2] : 0); + } + } +} + +static void gatt_eatt_central_write_cl_feat(UINT16 conn_id, tGATT_EATT_CONN *ec) +{ + tGATT_VALUE wr; + /* OR the EATT bit into the previously-read value instead of overwriting it, + * so bits the client already set are preserved (Core Spec v6.2 Vol 3 Part G + * 7.2 forbids clearing set bits). */ + UINT8 feat = ec->peer_cl_feat_val | BLE_GATT_CL_SUPP_FEAT_EATT_BITMASK; + + if (ec->peer_cl_feat_handle == 0) { + gatt_eatt_start_ecoc_connect(ec->peer_bda); + return; + } + + memset(&wr, 0, sizeof(wr)); + wr.handle = ec->peer_cl_feat_handle; + wr.len = 1; + wr.value[0] = feat; + ec->setup_st = GATT_EATT_ST_WRITE_CL_FEAT; + + if (GATTC_Write(conn_id, GATT_WRITE, &wr) != GATT_SUCCESS) { + GATT_EATT_TRACE_ERROR("write cl supp feat failed"); + /* Writing Client Supported Features is not a prerequisite for the ECOC + * bearers. Fall back to the connect phase (matching the read paths and + * the async WRITE_CL_FEAT error handler) instead of silently abandoning + * setup with no completion callback to advance the state machine. */ + gatt_eatt_start_ecoc_connect(ec->peer_bda); + } +} + +static void gatt_eatt_central_read_cl_feat(UINT16 conn_id, tGATT_EATT_CONN *ec) +{ + tGATT_READ_PARAM rd; + tBT_UUID uuid; + + memset(&rd, 0, sizeof(rd)); + uuid.len = LEN_UUID_16; + uuid.uu.uuid16 = GATT_UUID_CLIENT_SUP_FEAT; + rd.char_type.s_handle = 0x0001; + rd.char_type.e_handle = 0xFFFF; + rd.char_type.uuid = uuid; + rd.char_type.auth_req = GATT_AUTH_REQ_NONE; + ec->setup_st = GATT_EATT_ST_READ_CL_FEAT; + + if (GATTC_Read(conn_id, GATT_READ_BY_TYPE, &rd) != GATT_SUCCESS) { + GATT_EATT_TRACE_ERROR("read cl supp feat failed, try ecoc"); + gatt_eatt_start_ecoc_connect(ec->peer_bda); + } +} + +#if GATTS_ROBUST_CACHING_ENABLED +static void gatt_eatt_central_read_sr_feat(UINT16 conn_id, tGATT_EATT_CONN *ec) +{ + tGATT_READ_PARAM rd; + tBT_UUID uuid; + + memset(&rd, 0, sizeof(rd)); + uuid.len = LEN_UUID_16; + uuid.uu.uuid16 = GATT_UUID_SERVER_SUP_FEAT; + rd.char_type.s_handle = 0x0001; + rd.char_type.e_handle = 0xFFFF; + rd.char_type.uuid = uuid; + rd.char_type.auth_req = GATT_AUTH_REQ_NONE; + ec->setup_st = GATT_EATT_ST_READ_SR_FEAT; + + { + tGATT_STATUS st = GATTC_Read(conn_id, GATT_READ_BY_TYPE, &rd); + if (st != GATT_SUCCESS) { + GATT_EATT_TRACE_API("read sr feat queue failed status=0x%x, fallback legacy", st); + gatt_eatt_fallback_legacy(ec); + } else { + GATT_EATT_TRACE_API("read sr feat queued conn_id=0x%04x", conn_id); + } + } +} +#endif /* GATTS_ROBUST_CACHING_ENABLED */ + +static void gatt_eatt_gatt_cmpl_cback(UINT16 conn_id, tGATTC_OPTYPE op, tGATT_STATUS status, + tGATT_CL_COMPLETE *p_data) +{ + tGATT_EATT_CONN *ec; + UINT8 sr_feat = 0; + + if (op != GATTC_OPTYPE_READ && op != GATTC_OPTYPE_WRITE) { + return; + } + + ec = gatt_eatt_find_conn_for_setup(conn_id); + if (ec == NULL || ec->setup_st == GATT_EATT_ST_IDLE) { + GATT_EATT_TRACE_DEBUG("gatt_cmpl: no active setup for conn_id=0x%04x", conn_id); + return; + } + + GATT_EATT_TRACE_API("gatt_cmpl op=%u status=0x%x setup_st=%u conn_id=0x%04x", + op, status, ec->setup_st, conn_id); + + if (status != GATT_SUCCESS) { + if (ec->setup_st == GATT_EATT_ST_READ_SR_FEAT) { + GATT_EATT_TRACE_API("read sr feat failed status=0x%x, fallback legacy", status); + gatt_eatt_fallback_legacy(ec); + } else if (ec->setup_st != GATT_EATT_ST_CONNECTING) { + GATT_EATT_TRACE_DEBUG("setup GATT op=%u st=%u, try ecoc", op, ec->setup_st); + gatt_eatt_start_ecoc_connect(ec->peer_bda); + } + return; + } + + switch (ec->setup_st) { + case GATT_EATT_ST_READ_SR_FEAT: + if (p_data && p_data->att_value.len >= 1) { + sr_feat = p_data->att_value.value[0]; + } + if (sr_feat & BLE_GATT_SR_SUPP_FEAT_EATT_BITMASK) { + GATT_EATT_TRACE_API("peer supports EATT (sr_feat=0x%02x), read cl feat", sr_feat); + gatt_eatt_central_read_cl_feat(conn_id, ec); + } else { + GATT_EATT_TRACE_API("peer does not support EATT (sr_feat=0x%02x), legacy ATT", sr_feat); + gatt_eatt_free_conn(ec); + } + break; + + case GATT_EATT_ST_READ_CL_FEAT: + if (p_data) { + ec->peer_cl_feat_handle = p_data->att_value.handle; + /* Preserve any bits already set in the Client Supported Features + * value so the subsequent write does not clear them (Core Spec v6.2 + * Vol 3 Part G 7.2: a client shall not clear bits it has set, else + * the server rejects the write with Value Not Allowed 0x13). */ + if (p_data->att_value.len >= 1) { + ec->peer_cl_feat_val = p_data->att_value.value[0]; + } + } + gatt_eatt_central_write_cl_feat(conn_id, ec); + break; + + case GATT_EATT_ST_WRITE_CL_FEAT: + gatt_eatt_start_ecoc_connect(ec->peer_bda); + break; + + default: + break; + } +} +#endif /* BLE_EATT_CLIENT_INCLUDED */ + +/* The EATT module registers a GATT interface only to receive connection / + * operation-complete callbacks for its own bearer setup; it is not a GATT + * server application. Server request indications (e.g. the legacy ATT Exchange + * MTU) are fanned out to every registered interface, so provide a no-op + * request callback to absorb them instead of tripping the + * "Call back not found for application" warning in gatt_sr_send_req_callback(). + * Mirrors bta_gattc_req_cback(). */ +static void gatt_eatt_gatt_req_cback(UINT16 conn_id, UINT32 trans_id, + tGATTS_REQ_TYPE type, tGATTS_DATA *p_data) +{ + UNUSED(conn_id); + UNUSED(trans_id); + UNUSED(type); + UNUSED(p_data); +} + +static void gatt_eatt_gatt_conn_cback(tGATT_IF gatt_if, BD_ADDR bda, UINT16 conn_id, + BOOLEAN connected, tGATT_DISCONN_REASON reason, + tBT_TRANSPORT transport) +{ + UNUSED(gatt_if); + UNUSED(conn_id); + UNUSED(reason); + + /* EATT state is BLE-only. A BR/EDR GATT disconnect for the same BDA must not + * tear down the BLE EATT connection (find_conn_by_bda matches on BDA only). */ + if (transport != BT_TRANSPORT_LE) { + return; + } + + if (!connected) { + tGATT_EATT_CONN *ec = gatt_eatt_find_conn_by_bda(bda); + if (ec) { + GATT_EATT_TRACE_DEBUG("gatt disconnect, free_conn conn_id=%u reason=0x%x", conn_id, reason); + gatt_eatt_free_conn(ec); + } + } +} + +#if (BLE_EATT_SERVER_INCLUDED == TRUE) +static void gatt_eatt_release_empty_conn(tGATT_EATT_CONN *ec) +{ + if (ec != NULL && ec->bearer_count == 0) { + gatt_eatt_free_conn(ec); + } +} + +static void gatt_eatt_connect_ind(BD_ADDR bd_addr, UINT16 lcid, UINT16 psm, UINT8 id) +{ + tL2CAP_LE_CFG_INFO cfg = {0}; + tGATT_EATT_CONN *ec; + tGATT_EATT_BEARER *bearer; + + UNUSED(psm); + + if (!gatt_eatt_is_encrypted(bd_addr)) { + GATT_EATT_TRACE_API("connect_ind: reject unencrypted lcid=0x%04x", lcid); + /* Per Core Spec v6.2 10.1/10.2, reject due to missing security with the + * insufficient-encryption result, not a generic no-resources code, so + * the peer knows to encrypt/pair rather than retry. */ + L2CA_ConnectLECocRsp(bd_addr, id, lcid, L2CAP_LE_RESULT_INSUFFICIENT_ENCRY, 0, NULL); + return; + } + + gatt_eatt_log_peer("connect_ind accept", bd_addr); + + ec = gatt_eatt_alloc_conn(bd_addr); + if (ec == NULL) { + GATT_EATT_TRACE_ERROR("connect_ind: no conn slot, reject lcid=0x%04x", lcid); + L2CA_ConnectLECocRsp(bd_addr, id, lcid, L2CAP_LE_RESULT_NO_RESOURCES, 0, NULL); + return; + } + + ec->conn_id = gatt_eatt_app_conn_id_from_bda(bd_addr); + bearer = gatt_eatt_alloc_bearer(ec, lcid); + if (bearer == NULL) { + /* No free bearer slot: reject so the L2CAP channel is not left up while + * GATT does not track it (which would break TX routing and reporting). */ + GATT_EATT_TRACE_ERROR("connect_ind: no bearer slot lcid=0x%04x", lcid); + L2CA_ConnectLECocRsp(bd_addr, id, lcid, L2CAP_LE_RESULT_NO_RESOURCES, 0, NULL); + gatt_eatt_release_empty_conn(ec); + return; + } + + cfg.mtu = GATT_EATT_MTU; + cfg.mps = L2CAP_LE_COC_MPS; + /* Grant the full initial RX credit window (L2CAP_LE_INIT_CREDITS) instead of + * a single credit. A window of 1 forces the peer into a one-K-frame-at-a-time + * ping-pong (send frame -> wait for credit return), which throttles the + * central -> peripheral GATT request throughput on EATT bearers. Leaving + * credits at 0 would let l2c_ble_ecfc_apply_default_cfg() fill the same + * value; set it explicitly for clarity and symmetry with the central path. */ + cfg.credits = L2CAP_LE_INIT_CREDITS; + if (!L2CA_ConnectLECocRsp(bd_addr, id, lcid, L2CAP_CONN_OK, 0, &cfg)) { + GATT_EATT_TRACE_ERROR("connect_ind: ConnectLECocRsp failed lcid=0x%04x", lcid); + gatt_eatt_free_bearer(ec, bearer); + gatt_eatt_release_empty_conn(ec); + (void)L2CA_LECocDisconnect(lcid); + return; + } + GATT_EATT_TRACE_DEBUG("connect_ind accepted lcid=0x%04x conn_id=%u", lcid, ec->conn_id); + gatt_eatt_update_tcb_mtu(bd_addr, lcid); +} +#endif /* BLE_EATT_SERVER_INCLUDED */ + +/* Account for one completed EATT setup response (success or failure) on a + * client-initiated setup, and release the setup state once every requested + * bearer has been answered. Without this, a partial failure (peer accepts + * fewer bearers than requested) would wedge setup_st at CONNECTING and block + * any future EATT setup on this connection. Server-side (setup_st != CONNECTING) + * is unaffected. */ +static void gatt_eatt_setup_mark_done(tGATT_EATT_CONN *ec) +{ + if (ec == NULL || ec->setup_st != GATT_EATT_ST_CONNECTING) { + return; + } + if (ec->setup_done < 0xFF) { + ec->setup_done++; + } + if (ec->bearer_count >= s_eatt_chan_num || + (ec->setup_target != 0 && ec->setup_done >= ec->setup_target)) { + ec->setup_st = GATT_EATT_ST_IDLE; + } +} + +/* Invoked from l2c_ble_le_coc_open_channel() for both originator and acceptor + * paths; do not trim with BLE_EATT_CLIENT_INCLUDED. */ +static void gatt_eatt_connect_cfm(UINT16 lcid, UINT16 result) +{ + tL2C_CCB *p_ccb = l2cu_find_ccb_by_cid(NULL, lcid); + tGATT_EATT_CONN *ec; + tGATT_EATT_BEARER *b; + + if (p_ccb == NULL || p_ccb->p_lcb == NULL) { + GATT_EATT_TRACE_ERROR("connect_cfm: no ccb/lcb lcid=0x%04x result=%u", lcid, result); + return; + } + + if (result != L2CAP_LE_RESULT_CONN_OK) { + /* Failure path: only clean up an EXISTING conn. Do not allocate one here. + * If the original EATT conn was already freed (e.g. a GATT disconnect + * raced ahead of this CoC failure callback), gatt_eatt_alloc_conn would + * create a fresh empty conn (no bearers, setup_st IDLE) that nothing ever + * frees, leaking a MAX_L2CAP_LINKS slot. */ + ec = gatt_eatt_find_conn_by_bda(p_ccb->p_lcb->remote_bd_addr); + if (ec == NULL) { + GATT_EATT_TRACE_DEBUG("connect_cfm fail, conn gone lcid=0x%04x result=%u", lcid, result); + return; + } + GATT_EATT_TRACE_ERROR("bearer connect failed lcid=0x%04x result=%u", lcid, result); + b = gatt_eatt_find_bearer(ec, lcid); + if (b) { + gatt_eatt_free_bearer(ec, b); + } + gatt_eatt_setup_mark_done(ec); + return; + } + + /* Only look up the EXISTING conn; do not allocate here. In both the client + * (gatt_eatt_start_ecoc_connect) and server (gatt_eatt_connect_ind) flows the + * conn is allocated before connect_cfm arrives, so find_conn_by_bda succeeds. + * If it is NULL the original conn was already freed by a racing GATT + * disconnect; allocating a fresh one here would create an orphan conn (no + * GATT conn cback to ever free it) and leak a MAX_L2CAP_LINKS slot. Tear the + * L2CAP channel down instead, mirroring the failure path (696-712) and the + * no-bearer-slot path below. */ + ec = gatt_eatt_find_conn_by_bda(p_ccb->p_lcb->remote_bd_addr); + if (ec == NULL) { + GATT_EATT_TRACE_ERROR("connect_cfm ok but conn gone, disconnect lcid=0x%04x", lcid); + L2CA_LECocDisconnect(lcid); + return; + } + ec->conn_id = gatt_eatt_app_conn_id_from_bda(p_ccb->p_lcb->remote_bd_addr); + + b = gatt_eatt_alloc_bearer(ec, lcid); + if (b == NULL) { + /* No free bearer slot: tear down the L2CAP channel instead of leaving it + * up untracked by GATT. */ + GATT_EATT_TRACE_ERROR("connect_cfm: no bearer slot lcid=0x%04x, disconnecting", lcid); + gatt_eatt_setup_mark_done(ec); + L2CA_LECocDisconnect(lcid); + return; + } + + GATT_EATT_TRACE_API("bearer connected lcid=0x%04x conn_id=%u (%u/%u)", + lcid, ec->conn_id, ec->bearer_count, s_eatt_chan_num); + gatt_eatt_update_tcb_mtu(p_ccb->p_lcb->remote_bd_addr, lcid); + b->reported = TRUE; + gatt_eatt_report_evt(ec->conn_id, 0, lcid); + + gatt_eatt_setup_mark_done(ec); +} + +static void gatt_eatt_disconnect_ind(UINT16 lcid, BOOLEAN local_init) +{ + tL2C_CCB *p_ccb = l2cu_find_ccb_by_cid(NULL, lcid); + tGATT_EATT_CONN *ec; + tGATT_EATT_BEARER *b; + + UNUSED(local_init); + + GATT_EATT_TRACE_DEBUG("disconnect_ind lcid=0x%04x local_init=%u", lcid, local_init); + + if (p_ccb == NULL || p_ccb->p_lcb == NULL) { + GATT_EATT_TRACE_ERROR("disconnect_ind: no ccb/lcb lcid=0x%04x", lcid); + return; + } + + ec = gatt_eatt_find_conn_by_bda(p_ccb->p_lcb->remote_bd_addr); + if (ec == NULL) { + GATT_EATT_TRACE_DEBUG("disconnect_ind: no conn for lcid=0x%04x", lcid); + return; + } + + b = gatt_eatt_find_bearer(ec, lcid); + if (b == NULL) { + GATT_EATT_TRACE_DEBUG("disconnect_ind: no bearer for lcid=0x%04x", lcid); + } + if (b) { +#if (GATTC_INCLUDED == TRUE) + if (b->client_op != 0 && b->clcb_idx != 0) { + tGATT_CLCB *p_clcb = gatt_clcb_find_by_idx(b->clcb_idx); + + if (p_clcb != NULL) { + btu_stop_timer(&p_clcb->rsp_timer_ent); + gatt_end_operation(p_clcb, GATT_ERROR, NULL); + } + } +#endif + gatt_eatt_free_bearer(ec, b); + } +} + +static void gatt_eatt_congestion(UINT16 lcid, BOOLEAN congested) +{ + /* Deliberately a no-op on EATT decongestion. EATT client commands are never + * queued in cl_cmd_q: attp_cl_send_cmd() sends them straight to L2CAP, which + * owns their credit-based flow control. cl_cmd_q only holds legacy commands + * bound to the ATT fixed channel, and its resend is driven solely by the ATT + * fixed-channel congestion path (gatt_channel_congestion) and the response + * handler (gatt_client_handle_server_rsp). Calling gatt_cl_send_next_cmd_inq + * here would flush that legacy queue onto the ATT fixed channel with + * eatt_tx/rx_bearer == 0; if that channel were still congested the commands + * would be dropped as GATT_BUSY (permanently dequeued + failed). */ + UNUSED(lcid); + UNUSED(congested); +} + +static void gatt_eatt_data_ind_l2c(UINT16 lcid, BT_HDR *p_buf) +{ + gatt_eatt_data_ind(lcid, p_buf); +} + +void gatt_eatt_init(void) +{ + tBT_UUID app_uuid = {LEN_UUID_16, {UUID_SERVCLASS_GATT_SERVER}}; + static const tGATT_CBACK s_eatt_gatt_cback = { + gatt_eatt_gatt_conn_cback, +#if (BLE_EATT_CLIENT_INCLUDED == TRUE) + gatt_eatt_gatt_cmpl_cback, +#else + NULL, +#endif + NULL, /* p_disc_res_cb */ + NULL, /* p_disc_cmpl_cb */ + gatt_eatt_gatt_req_cback, /* p_req_cb: no-op, absorbs server req fan-out */ + NULL, /* p_enc_cmpl_cb */ + NULL, /* p_congestion_cb */ + }; + + memset(s_eatt_conn, 0, sizeof(s_eatt_conn)); + memset(&s_eatt_l2cap_appl, 0, sizeof(s_eatt_l2cap_appl)); + +#if (BLE_EATT_SERVER_INCLUDED == TRUE) + s_eatt_l2cap_appl.pL2CA_ConnectInd_Cb = gatt_eatt_connect_ind; +#endif + s_eatt_l2cap_appl.pL2CA_ConnectCfm_Cb = gatt_eatt_connect_cfm; + s_eatt_l2cap_appl.pL2CA_DisconnectInd_Cb = gatt_eatt_disconnect_ind; + s_eatt_l2cap_appl.pL2CA_DataInd_Cb = gatt_eatt_data_ind_l2c; + s_eatt_l2cap_appl.pL2CA_CongestionStatus_Cb = gatt_eatt_congestion; + + s_eatt_reg_psm = L2CA_RegisterLECoc(GATT_EATT_PSM, &s_eatt_l2cap_appl); + if (s_eatt_reg_psm == 0) { + /* Bail out before GATT_Register so a failed EATT init does not consume a + * scarce GATT_MAX_APPS slot. With no PSM, gatt_eatt_on_encrypted() also + * returns early, so nothing allocates an EATT conn we could not free. */ + GATT_EATT_TRACE_ERROR("RegisterLECoc PSM 0x%04x failed, EATT disabled", GATT_EATT_PSM); + return; + } + GATT_EATT_TRACE_DEBUG("RegisterLECoc success reg_psm=0x%04x", s_eatt_reg_psm); + + BTM_SetSecurityLevel(TRUE, "GATT_EATT", BTM_SEC_SERVICE_GEN_NET, + BTM_SEC_NONE, s_eatt_reg_psm, BTM_SEC_PROTO_L2CAP, 0); + BTM_SetSecurityLevel(FALSE, "GATT_EATT", BTM_SEC_SERVICE_GEN_NET, + BTM_SEC_NONE, s_eatt_reg_psm, BTM_SEC_PROTO_L2CAP, 0); + GATT_EATT_TRACE_API("init BTM_SetSecurityLevel for PSM 0x%04x", s_eatt_reg_psm); + + s_eatt_gatt_if = GATT_Register(&app_uuid, &s_eatt_gatt_cback); + if (s_eatt_gatt_if != 0) { + GATT_EATT_TRACE_DEBUG("GATT_Register success gatt_if=%u", s_eatt_gatt_if); + GATT_StartIf(s_eatt_gatt_if); + } else { + GATT_EATT_TRACE_ERROR("GATT_Register failed for EATT module"); + /* Roll back the L2CAP PSM registration. Without a GATT interface the + * gatt_eatt_gatt_conn_cback that frees tGATT_EATT_CONN on BLE disconnect + * is never registered, so leaving the PSM (and its still-live callbacks) + * up would let the server accept EATT connections it can never clean up, + * leaking an s_eatt_conn[] slot per peer. */ + L2CA_DeregisterLECoc(s_eatt_reg_psm); + s_eatt_reg_psm = 0; + } + +#if GATTS_ROBUST_CACHING_ENABLED + if (s_eatt_reg_psm != 0) { + gatt_cb.gatt_sr_supported_feat_mask |= BLE_GATT_SR_SUPP_FEAT_EATT_BITMASK; + } +#endif + + GATT_EATT_TRACE_API("init reg_psm=0x%04x eatt_gatt_if=%u chan_num=%u mtu=%u robust_caching=%d", + s_eatt_reg_psm, s_eatt_gatt_if, s_eatt_chan_num, GATT_EATT_MTU, + GATTS_ROBUST_CACHING_ENABLED); +} + +void gatt_eatt_deinit(void) +{ + tGATT_EATT_EVT_CBACK *saved_cback = s_eatt_evt_cback; + + GATT_EATT_TRACE_DEBUG("deinit start reg_psm=0x%04x gatt_if=%u", s_eatt_reg_psm, s_eatt_gatt_if); + + /* Suppress app callbacks while tearing down bearers during stack disable. */ + s_eatt_evt_cback = NULL; + + /* Explicitly disconnect every EATT L2CAP channel first. gatt_eatt_free_bearer + * only clears internal state, and the L2CA_DeregisterLECoc call below walks + * each link but disconnects only the first CCB in its queue. With multiple + * bearers per link the rest would be orphaned (left open until the ACL drops). + * Send the disconnect here, before free_conn wipes the bearer LCIDs. */ + for (int i = 0; i < MAX_L2CAP_LINKS; i++) { + if (!s_eatt_conn[i].in_use) { + continue; + } + GATT_EATT_TRACE_DEBUG("deinit disconnect bearers for conn_id=%u count=%u", + s_eatt_conn[i].conn_id, s_eatt_conn[i].bearer_count); + for (int j = 0; j < GATT_EATT_MAX_CHAN; j++) { + if (s_eatt_conn[i].bearers[j].in_use) { + L2CA_LECocDisconnect(s_eatt_conn[i].bearers[j].lcid); + } + } + } + + for (int i = 0; i < MAX_L2CAP_LINKS; i++) { + if (s_eatt_conn[i].in_use) { + gatt_eatt_free_conn(&s_eatt_conn[i]); + } + } + + if (s_eatt_reg_psm != 0) { + GATT_EATT_TRACE_DEBUG("deinit DeregisterLECoc reg_psm=0x%04x", s_eatt_reg_psm); + L2CA_DeregisterLECoc(s_eatt_reg_psm); + s_eatt_reg_psm = 0; + } + + /* Mirror the GATT_Register/GATT_StartIf done in init: GATT_Deregister stops + * CLCB response timers, frees CLCBs, updates link-use flags and releases the + * cl_rcb slot. Skipping it would leak the registration slot and could leave a + * timer referencing a CLCB later freed by gatt_free(). */ + if (s_eatt_gatt_if != 0) { + GATT_EATT_TRACE_DEBUG("deinit GATT_Deregister gatt_if=%u", s_eatt_gatt_if); + GATT_Deregister(s_eatt_gatt_if); + s_eatt_gatt_if = 0; + } + +#if GATTS_ROBUST_CACHING_ENABLED + gatt_cb.gatt_sr_supported_feat_mask &= ~BLE_GATT_SR_SUPP_FEAT_EATT_BITMASK; +#endif + + UNUSED(saved_cback); + memset(&s_eatt_l2cap_appl, 0, sizeof(s_eatt_l2cap_appl)); + GATT_EATT_TRACE_DEBUG("deinit done"); +} + +void gatt_eatt_register_evt_cback(tGATT_EATT_EVT_CBACK *p_cback) +{ + s_eatt_evt_cback = p_cback; +} + +void gatt_eatt_set_chan_num(UINT8 num_chan) +{ + if (num_chan > 0 && num_chan <= GATT_EATT_MAX_CHAN) { + s_eatt_chan_num = num_chan; + } +} + +void gatt_eatt_on_encrypted(BD_ADDR bd_addr) +{ + tGATT_EATT_CONN *ec; + UINT16 stack_conn_id; + tL2C_LCB *p_lcb; + tBTM_SEC_DEV_REC *p_dev; + BOOLEAN is_central; + + gatt_eatt_log_peer("on_encrypted", bd_addr); + + if (!gatt_eatt_is_encrypted(bd_addr)) { + GATT_EATT_TRACE_API("on_encrypted: link not encrypted yet, skip"); + return; + } + + /* EATT is not operational unless its L2CAP PSM was registered in + * gatt_eatt_init(). Without it no bearer can ever be set up, and the GATT + * conn cback that frees tGATT_EATT_CONN on disconnect may not be registered + * either, so allocating a conn here would only leak an s_eatt_conn[] slot. */ + if (s_eatt_reg_psm == 0) { + GATT_EATT_TRACE_API("on_encrypted: EATT PSM not registered, skip"); + return; + } + + ec = gatt_eatt_find_conn_by_bda(bd_addr); + if (ec != NULL && (ec->bearer_count > 0 || ec->setup_st != GATT_EATT_ST_IDLE)) { + /* Skip if bearers are already up OR a setup is in progress, so a repeated + * encryption-complete notification cannot start a duplicate EATT setup. */ + GATT_EATT_TRACE_API("on_encrypted: setup in progress or %u bearers up, skip", + ec->bearer_count); + return; + } + + ec = gatt_eatt_alloc_conn(bd_addr); + if (ec == NULL) { + GATT_EATT_TRACE_ERROR("on_encrypted: alloc ec failed"); + return; + } + + ec->conn_id = gatt_eatt_app_conn_id_from_bda(bd_addr); + if (ec->conn_id == GATT_INVALID_CONN_ID) { + GATT_EATT_TRACE_ERROR("on_encrypted: conn_id invalid conn_id=0x%04x", ec->conn_id); + } + stack_conn_id = gatt_eatt_stack_conn_id_from_bda(bd_addr); + is_central = gatt_eatt_is_central(bd_addr); + + p_lcb = l2cu_find_lcb_by_bd_addr(bd_addr, BT_TRANSPORT_LE); + p_dev = btm_find_dev(bd_addr); + GATT_EATT_TRACE_DEBUG("on_encrypted: central=%u app_conn_id=%u stack_conn_id=0x%04x " + "eatt_gatt_if=%u lcb=%p state=%u role=%u btm_role_master=%u", + is_central, ec->conn_id, stack_conn_id, s_eatt_gatt_if, + (void *)p_lcb, + p_lcb ? p_lcb->link_state : 0xFF, + p_lcb ? p_lcb->link_role : 0xFF, + (p_dev && p_dev->role_master) ? 1 : 0); + + if (is_central) { +#if (BLE_EATT_CLIENT_INCLUDED == TRUE) +#if GATTS_ROBUST_CACHING_ENABLED + if (stack_conn_id != 0) { + gatt_eatt_central_read_sr_feat(stack_conn_id, ec); + } else { + GATT_EATT_TRACE_ERROR("on_encrypted: no stack conn_id, fallback legacy"); + gatt_eatt_fallback_legacy(ec); + } +#else + GATT_EATT_TRACE_API("on_encrypted: robust caching off, start ecoc directly"); + gatt_eatt_start_ecoc_connect(bd_addr); +#endif +#else + GATT_EATT_TRACE_API("on_encrypted: central but GATTC disabled, skip EATT setup"); + gatt_eatt_free_conn(ec); +#endif + } else { +#if (BLE_EATT_SERVER_INCLUDED == TRUE) + GATT_EATT_TRACE_API("on_encrypted: peripheral, wait central ECFC on PSM 0x%04x", + GATT_EATT_PSM); +#else + /* No ConnectInd cb is registered when the EATT server is disabled, so this + * pre-allocated conn can never receive an incoming bearer; free it instead + * of holding an s_eatt_conn[] slot until GATT disconnect. */ + gatt_eatt_free_conn(ec); +#endif + } +} + +BOOLEAN gatt_eatt_is_bearer(UINT16 lcid) +{ + tL2C_CCB *p_ccb = l2cu_find_ccb_by_cid(NULL, lcid); + if (p_ccb == NULL || p_ccb->p_lcb == NULL || p_ccb->p_rcb == NULL) { + return FALSE; + } + return (p_ccb->p_rcb->real_psm == GATT_EATT_PSM && p_ccb->le_coc_active); +} + +UINT16 gatt_eatt_get_available_bearer(BD_ADDR bd_addr, UINT8 op) +{ +#if (BLE_EATT_CLIENT_INCLUDED == TRUE) + tGATT_EATT_CONN *ec = gatt_eatt_find_conn_by_bda(bd_addr); + tGATT_EATT_BEARER *b; + + UNUSED(op); + + if (ec == NULL || ec->bearer_count == 0) { + return L2CAP_ATT_CID; + } + + if (ec->default_lcid != 0) { + b = gatt_eatt_find_bearer(ec, ec->default_lcid); + if (b != NULL && b->client_op == 0) { + GATT_EATT_TRACE_DEBUG("get_available_bearer op=0x%02x -> default lcid=0x%04x", op, ec->default_lcid); + return ec->default_lcid; + } + } + + for (int i = 0; i < GATT_EATT_MAX_CHAN; i++) { + if (ec->bearers[i].in_use && ec->bearers[i].client_op == 0) { + GATT_EATT_TRACE_DEBUG("get_available_bearer op=0x%02x -> lcid=0x%04x", op, ec->bearers[i].lcid); + return ec->bearers[i].lcid; + } + } + GATT_EATT_TRACE_DEBUG("no free EATT bearer op=0x%02x (%u busy), use ATT CID", op, ec->bearer_count); +#endif + UNUSED(bd_addr); + UNUSED(op); + return L2CAP_ATT_CID; +} + +#if (BLE_EATT_SERVER_INCLUDED == TRUE) +UINT16 gatt_eatt_get_server_tx_bearer(BD_ADDR bd_addr) +{ + tGATT_EATT_CONN *ec = gatt_eatt_find_conn_by_bda(bd_addr); + + if (ec == NULL || ec->bearer_count == 0) { + return L2CAP_ATT_CID; + } + + /* Honor an app-selected default bearer when present. */ + if (ec->default_lcid != 0 && + gatt_eatt_find_bearer(ec, ec->default_lcid) != NULL) { + GATT_EATT_TRACE_DEBUG("server_tx_bearer -> default lcid=0x%04x", ec->default_lcid); + return ec->default_lcid; + } + + /* Round-robin across in-use bearers so server-initiated notifications and + * indications are spread over all EATT channels rather than serialized on + * a single one. */ + for (int n = 0; n < GATT_EATT_MAX_CHAN; n++) { + int i = (ec->tx_rr + n) % GATT_EATT_MAX_CHAN; + if (ec->bearers[i].in_use) { + ec->tx_rr = (UINT8)((i + 1) % GATT_EATT_MAX_CHAN); + GATT_EATT_TRACE_DEBUG("server_tx_bearer -> rr lcid=0x%04x", ec->bearers[i].lcid); + return ec->bearers[i].lcid; + } + } + + return L2CAP_ATT_CID; +} +#endif /* BLE_EATT_SERVER_INCLUDED */ + +BOOLEAN gatt_eatt_set_default_bearer(UINT16 conn_id, UINT16 lcid) +{ +#if (BLE_EATT_CLIENT_INCLUDED == TRUE) + /* conn_id here is the application-level id, which ESP exposes as the raw + * tcb_idx (see gatt_eatt_app_conn_id_from_bda / esp_ble_eatt_set_default_bearer). + * Do NOT apply GATT_GET_TCB_IDX (a >>8 for stack-layer conn_ids), which would + * evaluate to 0 for every application conn_id. */ + tGATT_TCB *p_tcb = gatt_get_tcb_by_idx((UINT8)conn_id); + tGATT_EATT_CONN *ec; + tGATT_EATT_BEARER *b; + + if (p_tcb == NULL) { + GATT_EATT_TRACE_ERROR("set_default_bearer: no tcb for conn_id=%u", conn_id); + return FALSE; + } + + ec = gatt_eatt_find_conn_by_bda(p_tcb->peer_bda); + if (ec == NULL) { + GATT_EATT_TRACE_ERROR("set_default_bearer: no EATT conn for conn_id=%u", conn_id); + return FALSE; + } + + if (lcid == 0 || lcid == L2CAP_ATT_CID) { + GATT_EATT_TRACE_DEBUG("set_default_bearer: clear default for conn_id=%u", conn_id); + ec->default_lcid = 0; + return TRUE; + } + + b = gatt_eatt_find_bearer(ec, lcid); + if (b == NULL) { + GATT_EATT_TRACE_ERROR("set_default_bearer: lcid=0x%04x not an EATT bearer of conn_id=%u", lcid, conn_id); + return FALSE; + } + + GATT_EATT_TRACE_DEBUG("set_default_bearer conn_id=%u lcid=0x%04x", conn_id, lcid); + ec->default_lcid = lcid; + return TRUE; +#else + UNUSED(conn_id); + UNUSED(lcid); + return FALSE; +#endif +} + +BOOLEAN gatt_eatt_mark_busy(BD_ADDR bd_addr, UINT16 lcid, UINT8 op, UINT16 clcb_idx) +{ +#if (BLE_EATT_CLIENT_INCLUDED == TRUE) + tGATT_EATT_BEARER *b; + tGATT_EATT_CONN *ec = gatt_eatt_find_conn_by_bda(bd_addr); + + if (ec == NULL) { + return FALSE; + } + b = gatt_eatt_find_bearer(ec, lcid); + if (b == NULL || b->client_op != 0) { + return FALSE; + } + b->client_op = op; + b->clcb_idx = clcb_idx; + return TRUE; +#else + UNUSED(bd_addr); + UNUSED(lcid); + UNUSED(op); + UNUSED(clcb_idx); + return FALSE; +#endif +} + +BOOLEAN gatt_eatt_release_bearer(BD_ADDR bd_addr, UINT16 lcid, UINT8 *p_op, UINT16 *p_clcb_idx) +{ +#if (BLE_EATT_CLIENT_INCLUDED == TRUE) + tGATT_EATT_BEARER *b; + tGATT_EATT_CONN *ec = gatt_eatt_find_conn_by_bda(bd_addr); + + if (ec == NULL) { + return FALSE; + } + b = gatt_eatt_find_bearer(ec, lcid); + if (b == NULL || b->client_op == 0) { + return FALSE; + } + if (p_op) { + *p_op = b->client_op; + } + if (p_clcb_idx) { + *p_clcb_idx = b->clcb_idx; + } + b->client_op = 0; + b->clcb_idx = 0; + return TRUE; +#else + UNUSED(bd_addr); + UNUSED(lcid); + UNUSED(p_op); + UNUSED(p_clcb_idx); + return FALSE; +#endif +} + +BOOLEAN gatt_eatt_release_bearer_by_clcb(BD_ADDR bd_addr, UINT16 clcb_idx) +{ +#if (BLE_EATT_CLIENT_INCLUDED == TRUE) + tGATT_EATT_CONN *ec = gatt_eatt_find_conn_by_bda(bd_addr); + int i; + + if (ec == NULL || clcb_idx == 0) { + return FALSE; + } + /* bearers[] is a sparse slot array: freeing a bearer only decrements + * bearer_count without compacting, so an in-use bearer may live at any + * index. Iterate the full slot range (matches every other loop here). */ + for (i = 0; i < GATT_EATT_MAX_CHAN; i++) { + tGATT_EATT_BEARER *b = &ec->bearers[i]; + if (b->in_use && b->client_op != 0 && b->clcb_idx == clcb_idx) { + b->client_op = 0; + b->clcb_idx = 0; + return TRUE; + } + } + return FALSE; +#else + UNUSED(bd_addr); + UNUSED(clcb_idx); + return FALSE; +#endif +} + +void gatt_eatt_data_ind(UINT16 lcid, BT_HDR *p_buf) +{ + tL2C_CCB *p_ccb; + tGATT_TCB *p_tcb; + UINT8 *p; + UINT8 op_code; + + if (p_buf == NULL) { + return; + } + + p_ccb = l2cu_find_ccb_by_cid(NULL, lcid); + if (p_ccb == NULL || p_ccb->p_lcb == NULL) { + osi_free(p_buf); + return; + } + + if (!gatt_eatt_is_encrypted(p_ccb->p_lcb->remote_bd_addr)) { + GATT_EATT_TRACE_ERROR("ATT PDU on EATT before encryption, disconnect"); + L2CA_LECocDisconnect(lcid); + osi_free(p_buf); + return; + } + + if (p_buf->len < 1) { + L2CA_LECocDisconnect(lcid); + osi_free(p_buf); + return; + } + + p = (UINT8 *)(p_buf + 1) + p_buf->offset; + STREAM_TO_UINT8(op_code, p); + if (!gatt_is_valid_att_opcode(op_code)) { + GATT_EATT_TRACE_ERROR("invalid ATT opcode 0x%02x on EATT, disconnect", op_code); + L2CA_LECocDisconnect(lcid); + osi_free(p_buf); + return; + } + + if (op_code == GATT_SIGN_CMD_WRITE) { + GATT_EATT_TRACE_ERROR("signed write on EATT bearer, disconnect lcid=0x%04x", lcid); + L2CA_LECocDisconnect(lcid); + osi_free(p_buf); + return; + } + + p_tcb = gatt_find_tcb_by_addr(p_ccb->p_lcb->remote_bd_addr, BT_TRANSPORT_LE); + if (p_tcb == NULL || gatt_get_ch_state(p_tcb) < GATT_CH_OPEN) { + osi_free(p_buf); + return; + } + + p_tcb->eatt_rx_bearer = lcid; + + if ((op_code % 2) != 0) { +#if (GATTC_INCLUDED == TRUE) + /* ATT response on client bearer */ + gatt_client_handle_server_rsp(p_tcb, op_code, p_buf->len - 1, p, lcid); + osi_free(p_buf); +#else + GATT_EATT_TRACE_ERROR("ATT response on EATT but GATTC disabled, disconnect"); + L2CA_LECocDisconnect(lcid); + osi_free(p_buf); + p_tcb->eatt_rx_bearer = 0; + return; +#endif + } else { + /* Client->server request on an EATT bearer. Record the bearer so a + * deferred (GATT_PENDING) app response is routed back to it once + * eatt_rx_bearer is cleared below. Cleared on gatt_dequeue_sr_cmd. */ +#if (GATTS_INCLUDED == TRUE) + /* Only record the routing hint when the sr_cmd slot is free. If a prior + * request is still pending (op_code != 0), gatt_server_handle_client_req + * discards this PDU without touching sr_cmd, so overwriting eatt_lcid here + * would misroute the already-pending response to this bearer. */ + BOOLEAN sr_cmd_was_empty = (p_tcb->sr_cmd.op_code == 0); + if (sr_cmd_was_empty) { + p_tcb->sr_cmd.eatt_lcid = lcid; + } + gatt_data_process(p_tcb, p_buf); + /* If the request was fully handled synchronously (or needed no response, + * e.g. a write command), no sr_cmd is pending (op_code == 0). Clear the + * routing hint so it cannot misroute a later, unrelated response. */ + if (sr_cmd_was_empty && p_tcb->sr_cmd.op_code == 0) { + p_tcb->sr_cmd.eatt_lcid = 0; + } +#else + gatt_data_process(p_tcb, p_buf); +#endif + } + + p_tcb->eatt_rx_bearer = 0; + /* EATT bearers run in auto-credit mode, where l2c_ble_le_coc_data_ind() has + * already returned the RX credit for this K-frame. This call is therefore a + * no-op today (l2c_ble_le_coc_give_credits() ignores it while auto-credit is + * on); it is kept only as a safety net should EATT ever switch to manual + * credit management. */ + L2CA_LECocGiveCredits(lcid, 1); +} + +#endif /* BLE_EATT_INCLUDED == TRUE */ diff --git a/components/bt/host/bluedroid/stack/gatt/gatt_main.c b/components/bt/host/bluedroid/stack/gatt/gatt_main.c index 5d57534f6ca..87df03a3cb8 100644 --- a/components/bt/host/bluedroid/stack/gatt/gatt_main.c +++ b/components/bt/host/bluedroid/stack/gatt/gatt_main.c @@ -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 } @@ -166,37 +173,54 @@ void gatt_free(void) { GATT_TRACE_DEBUG("gatt_free()"); #if (GATTS_INCLUDED == TRUE) - fixed_queue_free(gatt_cb.srv_chg_clt_q, NULL); + fixed_queue_free(gatt_cb.srv_chg_clt_q, osi_free_func); gatt_cb.srv_chg_clt_q = NULL; fixed_queue_free(gatt_cb.pending_new_srv_start_q, osi_free_func); 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; + list_node_t *p_next = NULL; tGATT_TCB *p_tcb = NULL; - for(p_node = list_begin(gatt_cb.p_tcb_list); p_node; p_node = list_next(p_node)) { - p_tcb = list_node(p_node); + tGATT_CLCB *p_clcb = NULL; + + for (p_node = list_begin(gatt_cb.p_tcb_list); p_node; p_node = list_next(p_node)) { + p_tcb = list_node(p_node); #if (SMP_INCLUDED == TRUE) - fixed_queue_free(p_tcb->pending_enc_clcb, NULL); - p_tcb->pending_enc_clcb = NULL; + gatt_free_pending_enc_queue(p_tcb); #endif // (SMP_INCLUDED == TRUE) #if (GATTS_INCLUDED == TRUE) + gatt_free_pending_prepare_write_queue(p_tcb); btu_free_timer(&p_tcb->conf_timer_ent); memset(&p_tcb->conf_timer_ent, 0, sizeof(TIMER_LIST_ENT)); + gatt_dequeue_sr_cmd(p_tcb); #endif // (GATTS_INCLUDED == TRUE) #if (GATTC_INCLUDED == TRUE) btu_free_timer(&p_tcb->ind_ack_timer_ent); memset(&p_tcb->ind_ack_timer_ent, 0, sizeof(TIMER_LIST_ENT)); -#endif // #if (GATTC_INCLUDED == TRUE) +#endif // (GATTC_INCLUDED == TRUE) -#if (GATTS_INCLUDED == TRUE) - fixed_queue_free(p_tcb->sr_cmd.multi_rsp_q, NULL); - p_tcb->sr_cmd.multi_rsp_q = NULL; -#endif /* #if (GATTS_INCLUDED == TRUE) */ UNUSED(p_tcb); } list_free(gatt_cb.p_tcb_list); + + for (p_node = list_begin(gatt_cb.p_clcb_list); p_node; p_node = p_next) { + p_clcb = list_node(p_node); + p_next = list_next(p_node); + if (p_clcb->p_attr_buf) { + osi_free(p_clcb->p_attr_buf); + p_clcb->p_attr_buf = NULL; + } + btu_free_timer(&p_clcb->rsp_timer_ent); + memset(&p_clcb->rsp_timer_ent, 0, sizeof(TIMER_LIST_ENT)); + list_remove(gatt_cb.p_clcb_list, p_clcb); + } list_free(gatt_cb.p_clcb_list); #if (GATTS_INCLUDED == TRUE) @@ -413,11 +437,9 @@ BOOLEAN gatt_act_connect (tGATT_REG *p_reg, BD_ADDR bd_addr, // p_tcb, p_tcb->pending_enc_clcb, and p_tcb->pending_ind_q have been freed in gatt_cleanup_upon_disc(), // but here p_tcb is get from gatt_allocate_tcb_by_bdaddr(), is too old, so we get p_tcb again p_tcb = gatt_find_tcb_by_addr(bd_addr, transport); - if(p_tcb != NULL) { + if (p_tcb != NULL) { #if (SMP_INCLUDED == TRUE) - if(p_tcb->pending_enc_clcb != NULL) { - fixed_queue_free(p_tcb->pending_enc_clcb, NULL); - } + gatt_free_pending_enc_queue(p_tcb); #endif // (SMP_INCLUDED == TRUE) gatt_tcb_free(p_tcb); } @@ -935,6 +957,7 @@ static void gatt_l2cif_congest_cback (UINT16 lcid, BOOLEAN congested) static void gatt_send_conn_cback(tGATT_TCB *p_tcb) { UINT8 i; + UINT8 tcb_idx = p_tcb->tcb_idx; tGATT_REG *p_reg; #if (GATT_BG_CONN_DEV == TRUE) tGATT_BG_CONN_DEV *p_bg_dev = NULL; @@ -947,6 +970,9 @@ static void gatt_send_conn_cback(tGATT_TCB *p_tcb) /* notifying all applications for the connection up event */ for (i = 0, p_reg = gatt_cb.cl_rcb ; i < GATT_MAX_APPS; i++, p_reg++) { + if (gatt_get_tcb_by_idx(tcb_idx) != p_tcb) { + return; + } if (p_reg->in_use) { #if (GATT_BG_CONN_DEV == TRUE) if (p_bg_dev && gatt_is_bg_dev_for_app(p_bg_dev, p_reg->gatt_if)) { @@ -954,14 +980,19 @@ static void gatt_send_conn_cback(tGATT_TCB *p_tcb) } #endif // #if (GATT_BG_CONN_DEV == TRUE) if (p_reg->app_cb.p_conn_cb) { - conn_id = GATT_CREATE_CONN_ID(p_tcb->tcb_idx, p_reg->gatt_if); + conn_id = GATT_CREATE_CONN_ID(tcb_idx, p_reg->gatt_if); (*p_reg->app_cb.p_conn_cb)(p_reg->gatt_if, p_tcb->peer_bda, conn_id, TRUE, 0, p_tcb->transport); + if (gatt_get_tcb_by_idx(tcb_idx) != p_tcb) { + return; + } } } } - + if (gatt_get_tcb_by_idx(tcb_idx) != p_tcb) { + return; + } if (gatt_num_apps_hold_link(p_tcb) && p_tcb->att_lcid == L2CAP_ATT_CID ) { /* disable idle timeout if one or more clients are holding the link disable the idle timer */ GATT_SetIdleTimeout(p_tcb->peer_bda, GATT_LINK_NO_IDLE_TIMEOUT, p_tcb->transport); @@ -986,7 +1017,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 +1029,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 +1045,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 } } @@ -1110,10 +1138,19 @@ tGATT_STATUS gatt_send_srv_chg_ind (BD_ADDR peer_bda) *******************************************************************************/ void gatt_chk_srv_chg(tGATTS_SRV_CHG *p_srv_chg_clt) { + tGATT_STATUS status; + GATT_TRACE_DEBUG("gatt_chk_srv_chg srv_changed=%d", p_srv_chg_clt->srv_changed ); - if (p_srv_chg_clt->srv_changed) { - gatt_send_srv_chg_ind(p_srv_chg_clt->bda); + if (!p_srv_chg_clt->srv_changed) { + return; + } + + status = gatt_send_srv_chg_ind(p_srv_chg_clt->bda); + if (status == GATT_BUSY || status == GATT_CONGESTED) { + GATT_TRACE_DEBUG("gatt_chk_srv_chg: defer srv chg ind (status=0x%02x)", status); + } else if (status != GATT_SUCCESS && status != GATT_PENDING) { + GATT_TRACE_WARNING("gatt_chk_srv_chg: send srv chg ind failed (status=0x%02x)", status); } } #endif ///GATTS_INCLUDED == TRUE @@ -1174,7 +1211,6 @@ void gatt_init_srv_chg (void) #if (GATTS_INCLUDED == TRUE) void gatt_proc_srv_chg (void) { - BOOLEAN srv_chg_ind_pending = FALSE; tGATT_TCB *p_tcb; list_node_t *p_node = NULL; @@ -1186,11 +1222,11 @@ void gatt_proc_srv_chg (void) for (p_node = list_begin(gatt_cb.p_tcb_list); p_node; p_node = list_next(p_node)) { p_tcb = list_node(p_node); if (p_tcb->in_use && p_tcb->ch_state == GATT_CH_OPEN) { - srv_chg_ind_pending = gatt_is_srv_chg_ind_pending(p_tcb); - if (!srv_chg_ind_pending) { - gatt_send_srv_chg_ind(p_tcb->peer_bda); + if (gatt_is_srv_chg_ind_pending(p_tcb) || + GATT_HANDLE_IS_VALID(p_tcb->indicate_handle)) { + GATT_TRACE_DEBUG ("defer srv chg - indication slot busy"); } else { - GATT_TRACE_DEBUG ("discard srv chg - already has one in the queue"); + gatt_send_srv_chg_ind(p_tcb->peer_bda); } } } diff --git a/components/bt/host/bluedroid/stack/gatt/gatt_sr.c b/components/bt/host/bluedroid/stack/gatt/gatt_sr.c index 41b6db2d184..b145e2daec7 100644 --- a/components/bt/host/bluedroid/stack/gatt/gatt_sr.c +++ b/components/bt/host/bluedroid/stack/gatt/gatt_sr.c @@ -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; } @@ -69,6 +73,48 @@ tGATT_STATUS gatt_send_packet (tGATT_TCB *p_tcb, UINT8 *p_data, UINT16 len) return status; } +/******************************************************************************* +** +** Function gatt_sr_next_trans_id +** +** Description Allocate the next transaction ID in [1, GATT_TRANS_ID_MAX - 1]. +** Zero is reserved for enqueue failure (sr_cmd busy). +** +*******************************************************************************/ +static UINT32 gatt_sr_next_trans_id(tGATT_TCB *p_tcb) +{ + UINT32 trans_id = p_tcb->trans_id % GATT_TRANS_ID_MAX; + + trans_id = (trans_id + 1) % GATT_TRANS_ID_MAX; + if (trans_id == 0) { + trans_id = 1; + } + p_tcb->trans_id = trans_id; + return trans_id; +} + +/******************************************************************************* +** +** Function gatt_sr_busy_error_code +** +** Description Pick an ATT error code for a request received while another +** server procedure is pending. Common profile codes (0xFE) are +** not valid for all request types per ATT Table 3.44. +** +*******************************************************************************/ +static UINT8 gatt_sr_busy_error_code(UINT8 op_code) +{ + switch (op_code) { + case GATT_REQ_FIND_TYPE_VALUE: + return GATT_REQ_NOT_SUPPORTED; + case GATT_REQ_FIND_INFO: + /* ATT Table 3.44: only Invalid Handle (0x01) and Not Found (0x0A) are valid. */ + return GATT_NOT_FOUND; + default: + return GATT_PRC_IN_PROGRESS; + } +} + /******************************************************************************* ** ** Function gatt_sr_enqueue_cmd @@ -84,21 +130,20 @@ UINT32 gatt_sr_enqueue_cmd (tGATT_TCB *p_tcb, UINT8 op_code, UINT16 handle) tGATT_SR_CMD *p_cmd = &p_tcb->sr_cmd; UINT32 trans_id = 0; - if ( (p_cmd->op_code == 0) || - (op_code == GATT_HANDLE_VALUE_CONF)) { /* no pending request */ - if (op_code == GATT_CMD_WRITE || - op_code == GATT_SIGN_CMD_WRITE || - op_code == GATT_REQ_MTU || - op_code == GATT_HANDLE_VALUE_CONF) { - trans_id = ++p_tcb->trans_id; - } else { - p_cmd->trans_id = ++p_tcb->trans_id; - p_cmd->op_code = op_code; - p_cmd->handle = handle; - p_cmd->status = GATT_NOT_FOUND; - p_tcb->trans_id %= GATT_TRANS_ID_MAX; - trans_id = p_cmd->trans_id; - } + /* No-tracking ops do not occupy sr_cmd and may arrive while a request is pending. */ + if (op_code == GATT_CMD_WRITE || + op_code == GATT_SIGN_CMD_WRITE || + op_code == GATT_REQ_MTU || + op_code == GATT_HANDLE_VALUE_CONF) { + return gatt_sr_next_trans_id(p_tcb); + } + + if (p_cmd->op_code == 0) { + p_cmd->trans_id = gatt_sr_next_trans_id(p_tcb); + p_cmd->op_code = op_code; + p_cmd->handle = handle; + p_cmd->status = GATT_NOT_FOUND; + trans_id = p_cmd->trans_id; } return trans_id; @@ -442,13 +487,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 +528,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 +555,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 +567,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); } @@ -515,6 +605,7 @@ void gatt_process_exec_write_req (tGATT_TCB *p_tcb, UINT8 op_code, UINT16 len, U BOOLEAN sr_cmd_already_dequeued = FALSE; tGATT_PREPARE_WRITE_RECORD *prepare_record = NULL; tGATT_PREPARE_WRITE_QUEUE_DATA * queue_data = NULL; + tGATTS_DATA sr_data = {0}; /* Fix: Validate minimum length (flags: 1 byte) */ if (len < 1) { @@ -538,6 +629,7 @@ void gatt_process_exec_write_req (tGATT_TCB *p_tcb, UINT8 op_code, UINT16 len, U /* mask the flag */ flag &= GATT_PREP_WRITE_EXEC; + sr_data.exec_write = flag; prepare_record = &(p_tcb->prepare_write_record); queue_num = fixed_queue_length(prepare_record->queue); @@ -614,7 +706,7 @@ void gatt_process_exec_write_req (tGATT_TCB *p_tcb, UINT8 op_code, UINT16 len, U gatt_sr_send_req_callback(conn_id, trans_id, GATTS_REQ_TYPE_WRITE_EXEC, - (tGATTS_DATA *)&flag); + &sr_data); p_tcb->prep_cnt[i] = 0; } } @@ -696,7 +788,7 @@ void gatt_process_exec_write_req (tGATT_TCB *p_tcb, UINT8 op_code, UINT16 len, U gatt_sr_send_req_callback(conn_id, trans_id, GATTS_REQ_TYPE_WRITE_EXEC, - (tGATTS_DATA *)&flag); + &sr_data); p_tcb->prep_cnt[i] = 0; } } @@ -724,6 +816,7 @@ void gatt_process_read_multi_req (tGATT_TCB *p_tcb, UINT8 op_code, UINT16 len, U tGATT_STATUS err = GATT_SUCCESS; UINT8 sec_flag, key_size; tGATTS_RSP *p_msg; + BOOLEAN sr_cmd_enqueued = FALSE; GATT_TRACE_DEBUG("gatt_process_read_multi_req" ); p_tcb->sr_cmd.multi_req.num_handles = 0; @@ -782,6 +875,7 @@ void gatt_process_read_multi_req (tGATT_TCB *p_tcb, UINT8 op_code, UINT16 len, U if (err == GATT_SUCCESS) { if ((trans_id = gatt_sr_enqueue_cmd (p_tcb, op_code, p_tcb->sr_cmd.multi_req.handles[0])) != 0) { + sr_cmd_enqueued = TRUE; gatt_sr_reset_cback_cnt(p_tcb); /* read multiple use multi_rsp_q's count*/ for (ll = 0; ll < p_tcb->sr_cmd.multi_req.num_handles; ll ++) { @@ -805,12 +899,16 @@ void gatt_process_read_multi_req (tGATT_TCB *p_tcb, UINT8 op_code, UINT16 len, U if (err == GATT_SUCCESS || err == GATT_STACK_RSP) { gatt_sr_process_app_rsp(p_tcb, gatt_cb.sr_reg[i_rcb].gatt_if , trans_id, op_code, GATT_SUCCESS, p_msg); + } else if (err != GATT_PENDING && err != GATT_BUSY) { + osi_free(p_msg); + break; } /* either not using or done using the buffer, release it now */ osi_free(p_msg); } else { err = GATT_NO_RESOURCES; gatt_dequeue_sr_cmd(p_tcb); + sr_cmd_enqueued = FALSE; break; } } @@ -820,7 +918,7 @@ void gatt_process_read_multi_req (tGATT_TCB *p_tcb, UINT8 op_code, UINT16 len, U } /* in theroy BUSY is not possible(should already been checked), protected check */ if (err != GATT_SUCCESS && err != GATT_STACK_RSP && err != GATT_PENDING && err != GATT_BUSY) { - gatt_send_error_rsp(p_tcb, err, op_code, handle, FALSE); + gatt_send_error_rsp(p_tcb, err, op_code, handle, sr_cmd_enqueued); } } @@ -860,7 +958,7 @@ static tGATT_STATUS gatt_build_primary_service_rsp (BT_HDR *p_msg, tGATT_TCB *p_ p_rcb->type == GATT_UUID_PRI_SERVICE) { if ((p_uuid = gatts_get_service_uuid (p_rcb->p_db)) != NULL) { if (op_code == GATT_REQ_READ_BY_GRP_TYPE) { - handle_len = 4 + p_uuid->len; + handle_len = 4 + gatt_get_uuid_stream_len(*p_uuid); } /* get the length byte in the response */ @@ -875,7 +973,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 +1151,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 +1217,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 +1228,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; @@ -1140,11 +1238,15 @@ static void gatts_process_find_info(tGATT_TCB *p_tcb, UINT8 op_code, UINT16 len, if (p_rcb->in_use && !(p_rcb->s_hdl > e_hdl || p_rcb->e_hdl < s_hdl)) { - reason = gatt_build_find_info_rsp(p_rcb, p_msg, &buf_len, s_hdl, e_hdl); - if (reason == GATT_NO_RESOURCES) { + tGATT_STATUS build_status = gatt_build_find_info_rsp(p_rcb, p_msg, &buf_len, s_hdl, e_hdl); + if (build_status == GATT_SUCCESS) { + reason = GATT_SUCCESS; + } else if (build_status == GATT_NO_RESOURCES) { reason = GATT_SUCCESS; break; } + /* GATT_NOT_FOUND for this service: keep reason (do not discard + * attributes already added from other services). */ } p_srv = p_srv->p_next; } @@ -1181,6 +1283,15 @@ static void gatts_process_mtu_req (tGATT_TCB *p_tcb, UINT16 len, UINT8 *p_data) UINT8 *p = p_data, i; BT_HDR *p_buf; UINT16 conn_id; + tGATTS_DATA sr_data = {0}; + +#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) { @@ -1210,11 +1321,12 @@ static void gatts_process_mtu_req (tGATT_TCB *p_tcb, UINT16 len, UINT8 *p_data) /* Notify all registered application with new MTU size. Us a transaction ID */ /* of 0, as no response is allowed from applications */ + sr_data.mtu = p_tcb->payload_size; for (i = 0; i < GATT_MAX_APPS; i ++) { if (gatt_cb.cl_rcb[i].in_use ) { conn_id = GATT_CREATE_CONN_ID(p_tcb->tcb_idx, gatt_cb.cl_rcb[i].gatt_if); gatt_sr_send_req_callback(conn_id, 0, GATTS_REQ_TYPE_MTU, - (tGATTS_DATA *)&p_tcb->payload_size); + &sr_data); } } @@ -1241,7 +1353,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 +1391,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; @@ -1317,7 +1434,13 @@ void gatts_process_read_by_type_req(tGATT_TCB *p_tcb, UINT8 op_code, UINT16 len, } p_srv = p_srv->p_next; } - *p = (UINT8)p_msg->offset; + /* Defensive: Read By Type response record length is a 1-octet field (<= 255). */ + if (p_msg->offset > UINT8_MAX) { + GATT_TRACE_ERROR("%s: invalid ReadByType pair_len=%u (>255)", __func__, p_msg->offset); + reason = GATT_INVALID_PDU; + } else { + *p = (UINT8)p_msg->offset; + } p_msg->offset = L2CAP_MIN_OFFSET; } } @@ -1614,7 +1737,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 +1762,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, @@ -1769,7 +1892,7 @@ void gatts_process_attribute_req (tGATT_TCB *p_tcb, UINT8 op_code, ** Returns void ** *******************************************************************************/ -static void gatts_proc_srv_chg_ind_ack(tGATT_TCB *p_tcb ) +void gatts_proc_srv_chg_ind_ack(tGATT_TCB *p_tcb ) { tGATTS_SRV_CHG_REQ req; tGATTS_SRV_CHG *p_buf = NULL; @@ -1842,6 +1965,10 @@ void gatts_process_value_conf(tGATT_TCB *p_tcb, UINT8 op_code) for (i = 0; i < GATT_MAX_SR_PROFILES; i ++, p_rcb ++) { if (p_rcb->in_use && p_rcb->s_hdl <= handle && p_rcb->e_hdl >= handle) { trans_id = gatt_sr_enqueue_cmd(p_tcb, op_code, handle); + if (trans_id == 0) { + GATT_TRACE_ERROR("%s: no trans_id for handle conf 0x%04x", __func__, handle); + continue; + } conn_id = GATT_CREATE_CONN_ID(p_tcb->tcb_idx, p_rcb->gatt_if); tGATTS_DATA p_data = {0}; p_data.handle = handle; @@ -1850,6 +1977,15 @@ void gatts_process_value_conf(tGATT_TCB *p_tcb, UINT8 op_code) } } } + + /* Retry Service Changed if a previous attempt was deferred (GATT_BUSY). */ + { + tGATTS_SRV_CHG *p_srv_chg_clt; + + if ((p_srv_chg_clt = gatt_is_bda_in_the_srv_chg_clt_list(p_tcb->peer_bda)) != NULL) { + gatt_chk_srv_chg(p_srv_chg_clt); + } + } } else { GATT_TRACE_ERROR("unexpected handle value confirmation"); } @@ -1950,16 +2086,20 @@ static BOOLEAN gatts_handle_db_out_of_sync(tGATT_TCB *p_tcb, UINT8 op_code, void gatt_server_handle_client_req (tGATT_TCB *p_tcb, UINT8 op_code, UINT16 len, UINT8 *p_data) { - /* there is pending command, discard this one */ - if (!gatt_sr_cmd_empty(p_tcb) && op_code != GATT_HANDLE_VALUE_CONF) { - GATT_TRACE_WARNING("%s discard command opcode=%02x", __func__, op_code); - return; + if (!gatt_sr_cmd_empty(p_tcb)) { + if (op_code == GATT_CMD_WRITE || op_code == GATT_SIGN_CMD_WRITE) { + /* ATT commands have no flow control and may arrive while a request is pending. */ + } else if (op_code != GATT_HANDLE_VALUE_CONF && op_code != GATT_REQ_MTU) { + GATT_TRACE_WARNING("%s reject opcode=%02x, procedure in progress", __func__, op_code); + gatt_send_error_rsp(p_tcb, gatt_sr_busy_error_code(op_code), op_code, 0, FALSE); + return; + } } /* 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 && diff --git a/components/bt/host/bluedroid/stack/gatt/gatt_sr_hash.c b/components/bt/host/bluedroid/stack/gatt/gatt_sr_hash.c index 869610b3705..8651b0910cf 100644 --- a/components/bt/host/bluedroid/stack/gatt/gatt_sr_hash.c +++ b/components/bt/host/bluedroid/stack/gatt/gatt_sr_hash.c @@ -38,6 +38,14 @@ static const char *gatt_get_attr_name(UINT16 uuid) return "Unknown Attribute"; } +/* GATT declaration attribute types (Primary Service, Characteristic, etc.) are + * always stored with 16-bit attribute UUID. Do not compare p_attr->uuid unless + * uuid_type is 16, or a 128/32-bit characteristic UUID may be misread. */ +static BOOLEAN gatt_attr_is_uuid16(const tGATT_ATTR16 *p_attr, UINT16 uuid16) +{ + return (p_attr->uuid_type == GATT_ATTR_UUID_TYPE_16 && p_attr->uuid == uuid16); +} + static void attr_uuid_to_bt_uuid(void *p_attr, tBT_UUID *p_uuid) { tGATT_ATTR16 *p_attr16 = (tGATT_ATTR16 *)p_attr; @@ -56,15 +64,6 @@ static void attr_uuid_to_bt_uuid(void *p_attr, tBT_UUID *p_uuid) } } -static UINT8 get_uuid_stream_len(tBT_UUID uuid) -{ - // gatt_build_uuid_to_stream always converts 32-bit UUID to 128-bit UUID - if (uuid.len == LEN_UUID_32) { - return LEN_UUID_128; - } - return uuid.len; -} - static size_t calculate_database_info_size(void) { UINT8 i; @@ -77,31 +76,45 @@ static size_t calculate_database_info_size(void) if (p_db && p_db->p_attr_list) { p_attr = (tGATT_ATTR16 *)p_db->p_attr_list; while (p_attr) { - if (p_attr->uuid == GATT_UUID_PRI_SERVICE || - p_attr->uuid == GATT_UUID_SEC_SERVICE) { + if (gatt_attr_is_uuid16(p_attr, GATT_UUID_PRI_SERVICE) || + gatt_attr_is_uuid16(p_attr, GATT_UUID_SEC_SERVICE)) { // Service declaration - len += 4 + get_uuid_stream_len(p_attr->p_value->uuid); - } else if (p_attr->uuid == GATT_UUID_INCLUDE_SERVICE) { + if (p_attr->p_value == NULL) { + GATT_TRACE_WARNING("%s: service decl at handle %u missing p_value", + __func__, p_attr->handle); + } else { + len += 4 + gatt_get_uuid_stream_len(p_attr->p_value->uuid); + } + } else if (gatt_attr_is_uuid16(p_attr, GATT_UUID_INCLUDE_SERVICE)) { // Included service declaration - len += 8 + get_uuid_stream_len(p_attr->p_value->incl_handle.service_type); - } else if (p_attr->uuid == GATT_UUID_CHAR_DECLARE) { + if (p_attr->p_value == NULL) { + GATT_TRACE_WARNING("%s: include service at handle %u missing p_value", + __func__, p_attr->handle); + } else { + len += 8 + gatt_get_uuid_stream_len(p_attr->p_value->incl_handle.service_type); + } + } else if (gatt_attr_is_uuid16(p_attr, GATT_UUID_CHAR_DECLARE)) { tBT_UUID char_uuid = {0}; - if (p_attr->p_next == NULL) { + if (p_attr->p_value == NULL) { + GATT_TRACE_WARNING("%s: char decl at handle %u missing p_value", + __func__, p_attr->handle); + } else if (p_attr->p_next == NULL) { GATT_TRACE_ERROR("%s: malformed DB, char decl at handle %u has no value attr", __func__, p_attr->handle); break; + } else { + p_attr = (tGATT_ATTR16 *)p_attr->p_next; + attr_uuid_to_bt_uuid((void *)p_attr, &char_uuid); + len += 7 + gatt_get_uuid_stream_len(char_uuid); } - p_attr = (tGATT_ATTR16 *)p_attr->p_next; - attr_uuid_to_bt_uuid((void *)p_attr, &char_uuid); - len += 7 + get_uuid_stream_len(char_uuid); - } else if (p_attr->uuid == GATT_UUID_CHAR_DESCRIPTION || - p_attr->uuid == GATT_UUID_CHAR_CLIENT_CONFIG || - p_attr->uuid == GATT_UUID_CHAR_SRVR_CONFIG || - p_attr->uuid == GATT_UUID_CHAR_PRESENT_FORMAT || - p_attr->uuid == GATT_UUID_CHAR_AGG_FORMAT) { + } else if (gatt_attr_is_uuid16(p_attr, GATT_UUID_CHAR_DESCRIPTION) || + gatt_attr_is_uuid16(p_attr, GATT_UUID_CHAR_CLIENT_CONFIG) || + gatt_attr_is_uuid16(p_attr, GATT_UUID_CHAR_SRVR_CONFIG) || + gatt_attr_is_uuid16(p_attr, GATT_UUID_CHAR_PRESENT_FORMAT) || + gatt_attr_is_uuid16(p_attr, GATT_UUID_CHAR_AGG_FORMAT)) { // Descriptor len += 4; - } else if (p_attr->uuid == GATT_UUID_CHAR_EXT_PROP) { + } else if (gatt_attr_is_uuid16(p_attr, GATT_UUID_CHAR_EXT_PROP)) { // Descriptor len += 6; } @@ -124,47 +137,55 @@ static void fill_database_info(UINT8 *p_data) if (p_db && p_db->p_attr_list) { p_attr = (tGATT_ATTR16 *)p_db->p_attr_list; while (p_attr) { - if (p_attr->uuid == GATT_UUID_PRI_SERVICE || - p_attr->uuid == GATT_UUID_SEC_SERVICE) { + if (gatt_attr_is_uuid16(p_attr, GATT_UUID_PRI_SERVICE) || + gatt_attr_is_uuid16(p_attr, GATT_UUID_SEC_SERVICE)) { // Service declaration - UINT16_TO_STREAM(p_data, p_attr->handle); - UINT16_TO_STREAM(p_data, p_attr->uuid); - gatt_build_uuid_to_stream(&p_data, p_attr->p_value->uuid); - } else if (p_attr->uuid == GATT_UUID_INCLUDE_SERVICE) { + if (p_attr->p_value != NULL) { + UINT16_TO_STREAM(p_data, p_attr->handle); + UINT16_TO_STREAM(p_data, p_attr->uuid); + gatt_build_uuid_to_stream(&p_data, p_attr->p_value->uuid); + } + } else if (gatt_attr_is_uuid16(p_attr, GATT_UUID_INCLUDE_SERVICE)) { // Included service declaration - UINT16_TO_STREAM(p_data, p_attr->handle); - UINT16_TO_STREAM(p_data, GATT_UUID_INCLUDE_SERVICE); - UINT16_TO_STREAM(p_data, p_attr->p_value->incl_handle.s_handle); - UINT16_TO_STREAM(p_data, p_attr->p_value->incl_handle.e_handle); - gatt_build_uuid_to_stream(&p_data, p_attr->p_value->incl_handle.service_type); - } else if (p_attr->uuid == GATT_UUID_CHAR_DECLARE) { + if (p_attr->p_value != NULL) { + UINT16_TO_STREAM(p_data, p_attr->handle); + UINT16_TO_STREAM(p_data, GATT_UUID_INCLUDE_SERVICE); + UINT16_TO_STREAM(p_data, p_attr->p_value->incl_handle.s_handle); + UINT16_TO_STREAM(p_data, p_attr->p_value->incl_handle.e_handle); + gatt_build_uuid_to_stream(&p_data, p_attr->p_value->incl_handle.service_type); + } + } else if (gatt_attr_is_uuid16(p_attr, GATT_UUID_CHAR_DECLARE)) { tBT_UUID char_uuid = {0}; - if (p_attr->p_next == NULL) { + if (p_attr->p_value == NULL) { + GATT_TRACE_WARNING("%s: char decl at handle %u missing p_value", + __func__, p_attr->handle); + } else if (p_attr->p_next == NULL) { GATT_TRACE_ERROR("%s: malformed DB, char decl at handle %u has no value attr", __func__, p_attr->handle); break; + } else { + UINT16_TO_STREAM(p_data, p_attr->handle); + UINT16_TO_STREAM(p_data, GATT_UUID_CHAR_DECLARE); + UINT8_TO_STREAM(p_data, p_attr->p_value->char_decl.property); + UINT16_TO_STREAM(p_data, p_attr->p_value->char_decl.char_val_handle); + p_attr = (tGATT_ATTR16 *)p_attr->p_next; + attr_uuid_to_bt_uuid((void *)p_attr, &char_uuid); + gatt_build_uuid_to_stream(&p_data, char_uuid); } - UINT16_TO_STREAM(p_data, p_attr->handle); - UINT16_TO_STREAM(p_data, GATT_UUID_CHAR_DECLARE); - UINT8_TO_STREAM(p_data, p_attr->p_value->char_decl.property); - UINT16_TO_STREAM(p_data, p_attr->p_value->char_decl.char_val_handle); - p_attr = (tGATT_ATTR16 *)p_attr->p_next; - attr_uuid_to_bt_uuid((void *)p_attr, &char_uuid); - gatt_build_uuid_to_stream(&p_data, char_uuid); - } else if (p_attr->uuid == GATT_UUID_CHAR_DESCRIPTION || - p_attr->uuid == GATT_UUID_CHAR_CLIENT_CONFIG || - p_attr->uuid == GATT_UUID_CHAR_SRVR_CONFIG || - p_attr->uuid == GATT_UUID_CHAR_PRESENT_FORMAT || - p_attr->uuid == GATT_UUID_CHAR_AGG_FORMAT) { + } else if (gatt_attr_is_uuid16(p_attr, GATT_UUID_CHAR_DESCRIPTION) || + gatt_attr_is_uuid16(p_attr, GATT_UUID_CHAR_CLIENT_CONFIG) || + gatt_attr_is_uuid16(p_attr, GATT_UUID_CHAR_SRVR_CONFIG) || + gatt_attr_is_uuid16(p_attr, GATT_UUID_CHAR_PRESENT_FORMAT) || + gatt_attr_is_uuid16(p_attr, GATT_UUID_CHAR_AGG_FORMAT)) { // Descriptor UINT16_TO_STREAM(p_data, p_attr->handle); UINT16_TO_STREAM(p_data, p_attr->uuid); - } else if (p_attr->uuid == GATT_UUID_CHAR_EXT_PROP) { + } else if (gatt_attr_is_uuid16(p_attr, GATT_UUID_CHAR_EXT_PROP)) { // Descriptor UINT16_TO_STREAM(p_data, p_attr->handle); UINT16_TO_STREAM(p_data, p_attr->uuid); - // TODO: process extended properties descriptor - if (p_attr->p_value->attr_val.attr_len == 2) { + if (p_attr->p_value != NULL && p_attr->p_value->attr_val.attr_val != NULL + && p_attr->p_value->attr_val.attr_len == 2) { memcpy(p_data, p_attr->p_value->attr_val.attr_val, 2); p_data += 2; } else { @@ -180,8 +201,8 @@ static void fill_database_info(UINT8 *p_data) tGATT_STATUS gatts_calculate_datebase_hash(BT_OCTET16 hash) { UINT8 tmp; - UINT16 i; - UINT16 j; + size_t i; + size_t j; size_t len; UINT8 *data_buf = NULL; @@ -194,23 +215,31 @@ tGATT_STATUS gatts_calculate_datebase_hash(BT_OCTET16 hash) data_buf = (UINT8 *)osi_malloc(len); if (data_buf == NULL) { - GATT_TRACE_ERROR ("%s failed to allocate buffer (%u)\n", __func__, len); + GATT_TRACE_ERROR ("%s failed to allocate buffer (%u)\n", __func__, (unsigned)len); return GATT_NO_RESOURCES; } fill_database_info(data_buf); // reverse database info - for (i = 0, j = len-1; i < j; i++, j--) { + for (i = 0, j = len - 1; i < j; i++, j--) { tmp = data_buf[i]; data_buf[i] = data_buf[j]; data_buf[j] = tmp; } #if SMP_INCLUDED == TRUE + if (len > UINT16_MAX) { + GATT_TRACE_ERROR("%s: database info too large (%u)", __func__, (unsigned)len); + osi_free(data_buf); + return GATT_NO_RESOURCES; + } + BT_OCTET16 key = {0}; - aes_cipher_msg_auth_code(key, data_buf, len, 16, hash); - //ESP_LOG_BUFFER_HEX("db hash", hash, BT_OCTET16_LEN); + if (!aes_cipher_msg_auth_code(key, data_buf, (UINT16)len, 16, hash)) { + osi_free(data_buf); + return GATT_ERROR; + } #endif osi_free(data_buf); @@ -229,25 +258,38 @@ void gatts_show_local_database(void) if (p_db && p_db->p_attr_list) { p_attr = (tGATT_ATTR16 *)p_db->p_attr_list; while (p_attr) { + if (p_attr->uuid_type != GATT_ATTR_UUID_TYPE_16) { + p_attr = (tGATT_ATTR16 *)p_attr->p_next; + continue; + } + switch (p_attr->uuid) { case GATT_UUID_PRI_SERVICE: case GATT_UUID_SEC_SERVICE: // Service declaration printf("%s\n", gatt_get_attr_name(p_attr->uuid)); - printf("\tuuid %s\n", gatt_uuid_to_str(&p_attr->p_value->uuid)); + if (p_attr->p_value != NULL) { + printf("\tuuid %s\n", gatt_uuid_to_str(&p_attr->p_value->uuid)); + } printf("\thandle %d\n", p_attr->handle); - printf("\tend_handle %d\n",p_db->end_handle-1); + printf("\tend_handle %d\n", p_db->end_handle - 1); break; case GATT_UUID_INCLUDE_SERVICE: // Included service declaration printf("%s\n", gatt_get_attr_name(p_attr->uuid)); - printf("\tuuid %s\t", gatt_uuid_to_str(&p_attr->p_value->incl_handle.service_type)); - printf("\thandle %d\n", p_attr->p_value->incl_handle.s_handle); - printf("\tend_handle %d\n", p_attr->p_value->incl_handle.e_handle); + if (p_attr->p_value != NULL) { + printf("\tuuid %s\t", gatt_uuid_to_str(&p_attr->p_value->incl_handle.service_type)); + printf("\thandle %d\n", p_attr->p_value->incl_handle.s_handle); + printf("\tend_handle %d\n", p_attr->p_value->incl_handle.e_handle); + } break; case GATT_UUID_CHAR_DECLARE: { tBT_UUID char_uuid = {0}; tGATT_ATTR16 *p_char_val; + if (p_attr->p_value == NULL) { + printf("characteristic (malformed - no decl value)\n"); + break; + } p_char_val = (tGATT_ATTR16 *)p_attr->p_next; if (p_char_val == NULL) { printf("characteristic (malformed - no value attr)\n"); @@ -259,7 +301,8 @@ void gatts_show_local_database(void) printf("\tuuid %s\n", gatt_uuid_to_str(&char_uuid)); printf("\tdef_handle %d\n", p_attr->handle); printf("\tval_handle %d\n", p_attr->p_value->char_decl.char_val_handle); - printf("\tperm 0x%04x, prop 0x%02x\n", p_char_val->permission, p_attr->p_value->char_decl.property); + printf("\tperm 0x%04x, prop 0x%02x\n", p_char_val->permission, + p_attr->p_value->char_decl.property); break; } case GATT_UUID_CHAR_EXT_PROP: @@ -271,6 +314,8 @@ void gatts_show_local_database(void) printf("%s\n", gatt_get_attr_name(p_attr->uuid)); printf("\thandle %d\n", p_attr->handle); break; + default: + break; } p_attr = (tGATT_ATTR16 *) p_attr->p_next; } diff --git a/components/bt/host/bluedroid/stack/gatt/gatt_utils.c b/components/bt/host/bluedroid/stack/gatt/gatt_utils.c index 05d896ebe0d..dcd04530e15 100644 --- a/components/bt/host/bluedroid/stack/gatt/gatt_utils.c +++ b/components/bt/host/bluedroid/stack/gatt/gatt_utils.c @@ -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) @@ -131,6 +134,119 @@ void gatt_free_pending_prepare_write_queue(tGATT_TCB *p_tcb) p_tcb->prepare_write_record.total_num = 0; p_tcb->prepare_write_record.error_code_app = GATT_SUCCESS; } + +/******************************************************************************* +** +** Function gatt_attr_in_svc_db +** +** Description Return TRUE if p_attr belongs to the given service database. +** +*******************************************************************************/ +static BOOLEAN gatt_attr_in_svc_db(tGATT_SVC_DB *p_db, tGATT_ATTR16 *p_attr) +{ + tGATT_ATTR16 *p; + + if (p_db == NULL || p_attr == NULL || p_db->p_attr_list == NULL) { + return FALSE; + } + + for (p = (tGATT_ATTR16 *)p_db->p_attr_list; p != NULL; p = p->p_next) { + if (p == p_attr) { + return TRUE; + } + } + return FALSE; +} + +/******************************************************************************* +** +** Function gatt_purge_prepare_write_for_svc_db +** +** Description Remove queued prepare-write entries that reference attributes +** in p_db. Must be called before freeing that service database. +** +*******************************************************************************/ +static void gatt_purge_prepare_write_for_svc_db(tGATT_TCB *p_tcb, tGATT_SVC_DB *p_db) +{ + tGATT_PREPARE_WRITE_QUEUE_DATA *queue_data; + tGATT_PREPARE_WRITE_RECORD *prepare_record; + fixed_queue_t *old_queue; + fixed_queue_t *new_queue; + UINT16 purged = 0; + + if (p_tcb == NULL || p_db == NULL || !p_tcb->in_use) { + return; + } + + prepare_record = &p_tcb->prepare_write_record; + old_queue = prepare_record->queue; + if (old_queue == NULL || fixed_queue_is_empty(old_queue)) { + return; + } + + new_queue = fixed_queue_new(QUEUE_SIZE_MAX); + if (new_queue == NULL) { + GATT_TRACE_ERROR("%s: failed to allocate queue, dropping all prepare writes", __func__); + gatt_free_pending_prepare_write_queue(p_tcb); + return; + } + + while (!fixed_queue_is_empty(old_queue)) { + queue_data = fixed_queue_dequeue(old_queue, FIXED_QUEUE_MAX_TIMEOUT); + if (gatt_attr_in_svc_db(p_db, queue_data->p_attr)) { + osi_free(queue_data); + purged++; + } else { + if (!fixed_queue_enqueue(new_queue, queue_data, FIXED_QUEUE_MAX_TIMEOUT)) { + GATT_TRACE_ERROR("%s: failed to re-queue prepare write entry", __func__); + osi_free(queue_data); + purged++; + } + } + } + + fixed_queue_free(old_queue, NULL); + if (fixed_queue_is_empty(new_queue)) { + fixed_queue_free(new_queue, NULL); + prepare_record->queue = NULL; + } else { + prepare_record->queue = new_queue; + } + + if (purged > 0) { + if (prepare_record->total_num >= purged) { + prepare_record->total_num -= purged; + } else { + prepare_record->total_num = 0; + } + if (prepare_record->queue == NULL && prepare_record->total_num == 0) { + prepare_record->error_code_app = GATT_SUCCESS; + } + } +} + +/******************************************************************************* +** +** Function gatt_purge_prepare_write_before_free_db +** +** Description Purge prepare-write queue entries for p_db on all active TCBs. +** Call before freeing a service database. +** +*******************************************************************************/ +void gatt_purge_prepare_write_before_free_db(tGATT_SVC_DB *p_db) +{ + list_node_t *p_node; + list_node_t *p_next; + tGATT_TCB *p_tcb; + + for (p_node = list_begin(gatt_cb.p_tcb_list); p_node; p_node = p_next) { + p_tcb = list_node(p_node); + p_next = list_next(p_node); + if (p_tcb->in_use) { + gatt_purge_prepare_write_for_svc_db(p_tcb, p_db); + } + } +} #endif // (GATTS_INCLUDED == TRUE) #if (GATTS_INCLUDED == TRUE) @@ -410,6 +526,7 @@ void gatt_free_attr_value_buffer(tGATT_HDL_LIST_ELEM *p) p_value = p_attr->p_value; if ((p_value != NULL) && (p_value->attr_val.attr_val != NULL)){ osi_free(p_value->attr_val.attr_val); + p_value->attr_val.attr_val = NULL; } } p_attr = p_attr->p_next; @@ -457,6 +574,8 @@ void gatt_free_srvc_db_buffer_app_id(tBT_UUID *p_app_id) if (memcmp(p_app_id, &p_elem->asgn_range.app_uuid128, sizeof(tBT_UUID)) == 0) { /* Remove from linked list first */ gatt_remove_an_item_from_list(p_list_info, p_elem); + /* Drop prepare-write queue entries pointing into this DB before free */ + gatt_purge_prepare_write_before_free_db(&p_elem->svc_db); /* Free attribute value buffers */ gatt_free_attr_value_buffer(p_elem); /* Free the handle buffer completely (including svc_buffer and setting in_use = FALSE) */ @@ -487,7 +606,7 @@ BOOLEAN gatt_is_last_attribute(tGATT_SRV_LIST_INFO *p_list, tGATT_SRV_LIST_ELEM p_svc_uuid = gatts_get_service_uuid (p_rcb->p_db); - if (gatt_uuid_compare(value, *p_svc_uuid)) { + if (p_svc_uuid && gatt_uuid_compare(value, *p_svc_uuid)) { is_last_attribute = FALSE; break; @@ -1126,6 +1245,23 @@ BOOLEAN gatt_uuid_compare (tBT_UUID src, tBT_UUID tar) return (memcmp(ps, pt, LEN_UUID_128) == 0); } +/******************************************************************************* +** +** Function gatt_get_uuid_stream_len +** +** Description Get the number of bytes gatt_build_uuid_to_stream writes. +** +** Returns UUID stream length. +** +*******************************************************************************/ +UINT8 gatt_get_uuid_stream_len(tBT_UUID uuid) +{ + if (uuid.len == LEN_UUID_32) { + return LEN_UUID_128; + } + return uuid.len; +} + /******************************************************************************* ** ** Function gatt_build_uuid_to_stream @@ -1256,9 +1392,40 @@ void gatt_start_rsp_timer(UINT16 clcb_idx) void gatt_start_conf_timer(tGATT_TCB *p_tcb) { p_tcb->conf_timer_ent.param = (TIMER_PARAM_TYPE)p_tcb; - btu_start_timer (&p_tcb->conf_timer_ent, BTU_TTYPE_ATT_WAIT_FOR_RSP, + btu_start_timer (&p_tcb->conf_timer_ent, BTU_TTYPE_ATT_WAIT_FOR_CONF, GATT_WAIT_FOR_RSP_TOUT); } + +/******************************************************************************* +** +** Function gatt_conf_timeout +** +** Description Called when GATT wait for indication confirmation timer expires +** +** Returns void +** +*******************************************************************************/ +void gatt_conf_timeout(TIMER_LIST_ENT *p_tle) +{ + tGATT_TCB *p_tcb = (tGATT_TCB *)p_tle->param; + + if (p_tcb == NULL || gatt_get_tcb_by_idx(p_tcb->tcb_idx) != p_tcb) { + GATT_TRACE_WARNING("gatt_conf_timeout tcb is already deleted"); + return; + } + + if (p_tcb->indicate_handle == gatt_cb.handle_of_h_r) { + /* Server-only remotes may ignore Service Changed indication; do not disconnect. */ + GATT_TRACE_WARNING("gatt_conf_timeout Service Changed indication timed out, not disconnecting"); + p_tcb->indicate_handle = 0; + gatts_proc_srv_chg_ind_ack(p_tcb); + return; + } + + GATT_TRACE_WARNING("gatt_conf_timeout handle=%u disconnecting...", p_tcb->indicate_handle); + p_tcb->indicate_handle = 0; + gatt_disconnect(p_tcb); +} #endif // (GATTS_INCLUDED == TRUE) #if (GATTC_INCLUDED == TRUE) @@ -1301,6 +1468,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 { @@ -1330,13 +1511,24 @@ void gatt_ind_ack_timeout(TIMER_LIST_ENT *p_tle) { tGATT_TCB *p_tcb = (tGATT_TCB *)p_tle->param; - GATT_TRACE_WARNING("gatt_ind_ack_timeout send ack now"); - - if (p_tcb != NULL) { - p_tcb->ind_count = 0; + if (p_tcb == NULL || gatt_get_tcb_by_idx(p_tcb->tcb_idx) != p_tcb) { + GATT_TRACE_WARNING("gatt_ind_ack_timeout tcb is already deleted"); + return; } - attp_send_cl_msg(((tGATT_TCB *)p_tle->param), 0, GATT_HANDLE_VALUE_CONF, NULL); + GATT_TRACE_WARNING("gatt_ind_ack_timeout send ack now"); + + p_tcb->ind_count = 0; + +#if (BLE_EATT_INCLUDED == TRUE) + /* 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(p_tcb, 0, GATT_HANDLE_VALUE_CONF, NULL); } #endif // (GATTC_INCLUDED == TRUE) @@ -2677,6 +2869,7 @@ BOOLEAN gatt_remove_bg_dev_from_list(tGATT_REG *p_reg, BD_ADDR bd_addr, BOOLEAN for (j = i + 1; j < GATT_MAX_APPS; j ++) { p_dev->gatt_if[j - 1] = p_dev->gatt_if[j]; } + p_dev->gatt_if[GATT_MAX_APPS - 1] = 0; if (p_dev->gatt_if[0] == 0) { ret = BTM_BleUpdateBgConnDev(FALSE, p_dev->remote_bda); @@ -2694,6 +2887,7 @@ BOOLEAN gatt_remove_bg_dev_from_list(tGATT_REG *p_reg, BD_ADDR bd_addr, BOOLEAN for (j = i + 1; j < GATT_MAX_APPS; j ++) { p_dev->listen_gif[j - 1] = p_dev->listen_gif[j]; } + p_dev->listen_gif[GATT_MAX_APPS - 1] = 0; if (p_dev->listen_gif[0] == 0) { // To check, we do not support background connection, code will not be called here @@ -2739,6 +2933,7 @@ void gatt_deregister_bgdev_list(tGATT_IF gatt_if) for (k = j + 1; k < GATT_MAX_APPS; k ++) { p_dev_list->gatt_if[k - 1] = p_dev_list->gatt_if[k]; } + p_dev_list->gatt_if[GATT_MAX_APPS - 1] = 0; if (p_dev_list->gatt_if[0] == 0) { BTM_BleUpdateBgConnDev(FALSE, p_dev_list->remote_bda); @@ -2756,6 +2951,7 @@ void gatt_deregister_bgdev_list(tGATT_IF gatt_if) for (k = j + 1; k < GATT_MAX_APPS; k ++) { p_dev_list->listen_gif[k - 1] = p_dev_list->listen_gif[k]; } + p_dev_list->listen_gif[GATT_MAX_APPS - 1] = 0; if (p_dev_list->listen_gif[0] == 0) { // To check, we do not support background connection, code will not be called here @@ -2763,6 +2959,10 @@ void gatt_deregister_bgdev_list(tGATT_IF gatt_if) } } } + + if (p_dev_list->gatt_if[0] == 0 && p_dev_list->listen_gif[0] == 0) { + memset(p_dev_list, 0, sizeof(tGATT_BG_CONN_DEV)); + } } } } diff --git a/components/bt/host/bluedroid/stack/gatt/include/gatt_eatt_int.h b/components/bt/host/bluedroid/stack/gatt/include/gatt_eatt_int.h new file mode 100644 index 00000000000..3c438de0d37 --- /dev/null +++ b/components/bt/host/bluedroid/stack/gatt/include/gatt_eatt_int.h @@ -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 */ diff --git a/components/bt/host/bluedroid/stack/gatt/include/gatt_int.h b/components/bt/host/bluedroid/stack/gatt/include/gatt_int.h index 06ec1b5cb05..bc61b08d882 100644 --- a/components/bt/host/bluedroid/stack/gatt/include/gatt_int.h +++ b/components/bt/host/bluedroid/stack/gatt/include/gatt_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 @@ -390,6 +409,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; @@ -637,6 +663,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); @@ -648,6 +687,7 @@ extern UINT8 *gatt_dbg_op_name(UINT8 op_code); extern UINT32 gatt_add_sdp_record (tBT_UUID *p_uuid, UINT16 start_hdl, UINT16 end_hdl); #endif ///SDP_INCLUDED == TRUE && CLASSIC_BT_GATT_INCLUDED == TRUE extern BOOLEAN gatt_parse_uuid_from_cmd(tBT_UUID *p_uuid, UINT16 len, UINT8 **p_data); +extern UINT8 gatt_get_uuid_stream_len(tBT_UUID uuid); extern UINT8 gatt_build_uuid_to_stream(UINT8 **p_dst, tBT_UUID uuid); extern BOOLEAN gatt_uuid_compare(tBT_UUID src, tBT_UUID tar); extern void gatt_convert_uuid32_to_uuid128(UINT8 uuid_128[LEN_UUID_128], UINT32 uuid_32); @@ -655,6 +695,8 @@ extern char *gatt_uuid_to_str(const tBT_UUID *uuid); extern void gatt_sr_get_sec_info(BD_ADDR rem_bda, tBT_TRANSPORT transport, UINT8 *p_sec_flag, UINT8 *p_key_size); extern void gatt_start_rsp_timer(UINT16 clcb_idx); extern void gatt_start_conf_timer(tGATT_TCB *p_tcb); +extern void gatt_conf_timeout(TIMER_LIST_ENT *p_tle); +extern void gatts_proc_srv_chg_ind_ack(tGATT_TCB *p_tcb); extern void gatt_rsp_timeout(TIMER_LIST_ENT *p_tle); extern void gatt_ind_ack_timeout(TIMER_LIST_ENT *p_tle); extern void gatt_start_ind_ack_timer(tGATT_TCB *p_tcb); @@ -680,6 +722,7 @@ extern tGATT_HDL_LIST_ELEM *gatt_find_hdl_buffer_by_attr_handle(UINT16 attr_hand extern tGATT_HDL_LIST_ELEM *gatt_alloc_hdl_buffer(void); extern void gatt_free_hdl_buffer(tGATT_HDL_LIST_ELEM *p); extern void gatt_free_attr_value_buffer(tGATT_HDL_LIST_ELEM *p); +extern void gatt_purge_prepare_write_before_free_db(tGATT_SVC_DB *p_db); extern BOOLEAN gatt_is_last_attribute(tGATT_SRV_LIST_INFO *p_list, tGATT_SRV_LIST_ELEM *p_start, tBT_UUID value); extern void gatt_update_last_pri_srv_info(tGATT_SRV_LIST_INFO *p_list); extern BOOLEAN gatt_add_a_srv_to_list(tGATT_SRV_LIST_INFO *p_list, tGATT_SRV_LIST_ELEM *p_new); @@ -745,6 +788,12 @@ extern void gatt_dequeue_sr_cmd (tGATT_TCB *p_tcb); extern UINT8 gatt_send_write_msg(tGATT_TCB *p_tcb, UINT16 clcb_idx, UINT8 op_code, UINT16 handle, UINT16 len, UINT16 offset, UINT8 *p_data); extern void gatt_cleanup_upon_disc(BD_ADDR bda, UINT16 reason, tBT_TRANSPORT transport); +#if (SMP_INCLUDED == TRUE) +extern void gatt_free_pending_enc_queue(tGATT_TCB *p_tcb); +#endif // (SMP_INCLUDED == TRUE) +#if (GATTS_INCLUDED == TRUE) +extern void gatt_free_pending_prepare_write_queue(tGATT_TCB *p_tcb); +#endif // (GATTS_INCLUDED == TRUE) extern void gatt_end_operation(tGATT_CLCB *p_clcb, tGATT_STATUS status, void *p_data); extern void gatt_act_discovery(tGATT_CLCB *p_clcb); @@ -755,7 +804,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 */ diff --git a/components/bt/host/bluedroid/stack/hcic/hciblecmds.c b/components/bt/host/bluedroid/stack/hcic/hciblecmds.c index 1dbe71b2886..34d611a3ea5 100644 --- a/components/bt/host/bluedroid/stack/hcic/hciblecmds.c +++ b/components/bt/host/bluedroid/stack/hcic/hciblecmds.c @@ -1169,7 +1169,7 @@ BOOLEAN btsnd_hcic_ble_set_phy(UINT16 conn_handle, } #if (BLE_50_DTM_TEST_EN == TRUE) -UINT8 btsnd_hcic_ble_enhand_rx_test(UINT8 rx_channel, UINT8 phy, +BOOLEAN btsnd_hcic_ble_enhand_rx_test(UINT8 rx_channel, UINT8 phy, UINT8 modulation_idx) { BT_HDR *p; @@ -1190,7 +1190,7 @@ UINT8 btsnd_hcic_ble_enhand_rx_test(UINT8 rx_channel, UINT8 phy, return TRUE; } -UINT8 btsnd_hcic_ble_enhand_tx_test(UINT8 tx_channel, UINT8 len, +BOOLEAN btsnd_hcic_ble_enhand_tx_test(UINT8 tx_channel, UINT8 len, UINT8 packect, UINT8 phy) { @@ -2160,7 +2160,7 @@ BOOLEAN btsnd_hcic_ble_set_vendor_evt_mask (UINT32 evt_mask) #if (BLE_FEAT_ISO_EN == TRUE) #if (BLE_FEAT_ISO_BIG_BROADCASTER_EN == TRUE) -UINT8 btsnd_hcic_ble_big_create(uint8_t big_handle, uint8_t adv_handle, uint8_t num_bis, +BOOLEAN btsnd_hcic_ble_big_create(uint8_t big_handle, uint8_t adv_handle, uint8_t num_bis, uint32_t sdu_interval, uint16_t max_sdu, uint16_t max_transport_latency, uint8_t rtn, uint8_t phy, uint8_t packing, uint8_t framing, uint8_t encryption, uint8_t *broadcast_code) @@ -2196,7 +2196,7 @@ UINT8 btsnd_hcic_ble_big_create(uint8_t big_handle, uint8_t adv_handle, uint8_t return TRUE; } -UINT8 btsnd_hcic_ble_big_create_test(uint8_t big_handle, uint8_t adv_handle, uint8_t num_bis, +BOOLEAN btsnd_hcic_ble_big_create_test(uint8_t big_handle, uint8_t adv_handle, uint8_t num_bis, uint32_t sdu_interval, uint16_t iso_interval, uint8_t nse, uint16_t max_sdu, uint16_t max_pdu, uint8_t phy, uint8_t packing, uint8_t framing, uint8_t bn, uint8_t irc, @@ -2237,7 +2237,7 @@ UINT8 btsnd_hcic_ble_big_create_test(uint8_t big_handle, uint8_t adv_handle, uin return TRUE; } -UINT8 btsnd_hcic_ble_big_terminate(uint8_t big_handle, uint8_t reason) +BOOLEAN btsnd_hcic_ble_big_terminate(uint8_t big_handle, uint8_t reason) { BT_HDR *p; UINT8 *pp; @@ -2259,7 +2259,7 @@ UINT8 btsnd_hcic_ble_big_terminate(uint8_t big_handle, uint8_t reason) } #endif // #if (BLE_FEAT_ISO_BIG_BROADCASTER_EN == TRUE) #if (BLE_FEAT_ISO_BIG_SYNCER_EN == TRUE) -UINT8 btsnd_hcic_ble_big_sync_create(uint8_t big_handle, uint16_t sync_handle, +BOOLEAN btsnd_hcic_ble_big_sync_create(uint8_t big_handle, uint16_t sync_handle, uint8_t encryption, uint8_t *bc_code, uint8_t mse, uint16_t big_sync_timeout, uint8_t num_bis, uint8_t *bis) @@ -2471,7 +2471,7 @@ UINT8 btsnd_hcic_ble_iso_set_cig_params_test(uint8_t cig_id, uint32_t sdu_int_c_ return btu_hcif_send_cmd_sync(LOCAL_BR_EDR_CONTROLLER_ID, p); } -UINT8 btsnd_hcic_ble_iso_create_cis(uint8_t cis_count, struct ble_hci_cis_hdls *cis_hdls) +BOOLEAN btsnd_hcic_ble_iso_create_cis(uint8_t cis_count, struct ble_hci_cis_hdls *cis_hdls) { BT_HDR *p; UINT8 *pp; @@ -2524,7 +2524,7 @@ UINT8 btsnd_hcic_ble_iso_remove_cig(uint8_t cig_id) #endif // #if (BLE_FEAT_ISO_CIG_CENTRAL_EN == TRUE) #if (BLE_FEAT_ISO_CIG_PERIPHERAL_EN == TRUE) -UINT8 btsnd_hcic_ble_iso_accept_cis_req(uint16_t cis_handle) +BOOLEAN btsnd_hcic_ble_iso_accept_cis_req(uint16_t cis_handle) { BT_HDR *p; UINT8 *pp; @@ -2802,7 +2802,7 @@ UINT8 btsnd_hcic_ble_enh_read_trans_power_level(uint16_t conn_handle, uint8_t ph return btu_hcif_send_cmd_sync(LOCAL_BR_EDR_CONTROLLER_ID, p); } -UINT8 btsnd_hcic_ble_read_remote_trans_power_level(uint16_t conn_handle, uint8_t phy) +BOOLEAN btsnd_hcic_ble_read_remote_trans_power_level(uint16_t conn_handle, uint8_t phy) { BT_HDR *p; UINT8 *pp; @@ -2916,7 +2916,7 @@ UINT8 btsnd_hcic_ble_set_default_subrate(UINT16 subrate_min, UINT16 subrate_max, return btu_hcif_send_cmd_sync(LOCAL_BR_EDR_CONTROLLER_ID, p); } -UINT8 btsnd_hcic_ble_subrate_request(UINT16 conn_handle, UINT16 subrate_min, UINT16 subrate_max, UINT16 max_latency, +BOOLEAN btsnd_hcic_ble_subrate_request(UINT16 conn_handle, UINT16 subrate_min, UINT16 subrate_max, UINT16 max_latency, UINT16 continuation_number, UINT16 supervision_timeout) { BT_HDR *p; @@ -3451,7 +3451,7 @@ UINT8 btsnd_hcic_ble_set_periodic_sync_subevt(UINT16 sync_handle, UINT16 periodi #endif // #if (BT_BLE_FEAT_PAWR_EN == TRUE) #if (BT_BLE_FEAT_CHANNEL_SOUNDING == TRUE) -UINT8 btsnd_hcic_ble_cs_read_local_supported_caps(void) +BOOLEAN btsnd_hcic_ble_cs_read_local_supported_caps(void) { BT_HDR *p; UINT8 *pp; @@ -3469,7 +3469,7 @@ UINT8 btsnd_hcic_ble_cs_read_local_supported_caps(void) return (TRUE); } -UINT8 btsnd_hcic_ble_cs_read_remote_supported_capabilities(UINT16 conn_handle) +BOOLEAN btsnd_hcic_ble_cs_read_remote_supported_capabilities(UINT16 conn_handle) { BT_HDR *p; UINT8 *pp; @@ -3541,7 +3541,7 @@ UINT8 btsnd_hcic_ble_cs_write_cached_remote_supported_capabilities(UINT16 conn_h } -UINT8 btsnd_hcic_ble_cs_security_enable(UINT16 conn_handle) +BOOLEAN btsnd_hcic_ble_cs_security_enable(UINT16 conn_handle) { BT_HDR *p; UINT8 *pp; @@ -3581,7 +3581,7 @@ UINT8 btsnd_hcic_ble_cs_set_default_settings(UINT16 conn_handle, UINT8 role_enab return btu_hcif_send_cmd_sync(LOCAL_BR_EDR_CONTROLLER_ID, p); } -UINT8 btsnd_hcic_ble_cs_read_remote_fae_table(UINT16 conn_handle) +BOOLEAN btsnd_hcic_ble_cs_read_remote_fae_table(UINT16 conn_handle) { BT_HDR *p; UINT8 *pp; @@ -3620,7 +3620,7 @@ UINT8 btsnd_hcic_ble_cs_write_cached_remote_fae_table(UINT16 conn_handle, UINT8 return btu_hcif_send_cmd_sync(LOCAL_BR_EDR_CONTROLLER_ID, p); } -UINT8 btsnd_hcic_ble_cs_create_config(UINT16 conn_handle, UINT8 config_id, UINT8 create_context, +BOOLEAN btsnd_hcic_ble_cs_create_config(UINT16 conn_handle, UINT8 config_id, UINT8 create_context, UINT8 main_mode_type, UINT8 sub_mode_type, UINT8 min_main_mode_steps, UINT8 max_main_mode_steps, UINT8 main_mode_repetition, UINT8 mode_0_steps, UINT8 role, UINT8 rtt_type, UINT8 cs_sync_phy, UINT8 *channel_map, @@ -3667,7 +3667,7 @@ UINT8 btsnd_hcic_ble_cs_create_config(UINT16 conn_handle, UINT8 config_id, UINT8 return (TRUE); } -UINT8 btsnd_hcic_ble_cs_remove_config(UINT16 conn_handle, UINT8 config_id) +BOOLEAN btsnd_hcic_ble_cs_remove_config(UINT16 conn_handle, UINT8 config_id) { BT_HDR *p; UINT8 *pp; @@ -3748,7 +3748,7 @@ UINT8 btsnd_hcic_ble_cs_set_procedure_params(UINT16 conn_handle, UINT8 config_id return btu_hcif_send_cmd_sync(LOCAL_BR_EDR_CONTROLLER_ID, p); } -UINT8 btsnd_hcic_ble_cs_procedure_enable(UINT16 conn_handle, UINT8 config_id, UINT8 enable) +BOOLEAN btsnd_hcic_ble_cs_procedure_enable(UINT16 conn_handle, UINT8 config_id, UINT8 enable) { BT_HDR *p; UINT8 *pp; diff --git a/components/bt/host/bluedroid/stack/include/stack/btm_ble_api.h b/components/bt/host/bluedroid/stack/include/stack/btm_ble_api.h index eae0a71b2ee..e3a408bd6cf 100644 --- a/components/bt/host/bluedroid/stack/include/stack/btm_ble_api.h +++ b/components/bt/host/bluedroid/stack/include/stack/btm_ble_api.h @@ -2635,6 +2635,54 @@ bool BTM_GetLocalIRK(uint8_t *irk); *******************************************************************************/ BOOLEAN BTM_BleGetCurrentAddress(BD_ADDR addr, uint8_t *addr_type); +#if (BLE_INCLUDED == TRUE && SMP_INCLUDED == TRUE && BLE_PERIPH_PSEUDO_ADDR_BOND == TRUE) +/******************************************************************************* +** Function BTM_BleGetRealPeerByPseudo +** +** Description Reverse map a Host pseudo address to the real peer identity +** for a dual-identity (pseudo-address bond) link. +** +** Returns TRUE if the pseudo is known, FALSE otherwise. +*******************************************************************************/ +BOOLEAN BTM_BleGetRealPeerByPseudo(BD_ADDR pseudo, BD_ADDR real_peer); + +/******************************************************************************* +** Function BTM_BleGetConnIdentityByPseudo +** +** Description Return the real peer + local identity (and address types) +** for a connected dual-identity link keyed by its pseudo. +** +** Returns TRUE if the pseudo belongs to a finalized link. +*******************************************************************************/ +BOOLEAN BTM_BleGetConnIdentityByPseudo(BD_ADDR pseudo, BD_ADDR peer, BD_ADDR local, + UINT8 *peer_type, UINT8 *local_type); + +/******************************************************************************* +** Function BTM_BleComputePseudoForIdentity +** +** Description Recompute the deterministic Host pseudo for a (local, peer) +** identity pair (e.g. to remove a stored bond by identity). +*******************************************************************************/ +void BTM_BleComputePseudoForIdentity(BD_ADDR local, UINT8 local_type, + BD_ADDR peer, UINT8 peer_type, BD_ADDR pseudo); + +/******************************************************************************* +** Function BTM_BleMarkPseudoBond +** +** Description Mark the device record for bd_addr as a pseudo-address bond +** (dual local-identity). Normally invoked from bta_dm_add_ble_device +** on the BTU thread when BTA_DmAddBleDevice is called with +** is_pseudo_bond=TRUE while loading bonds from NVS. There is no live +** connection at boot, so the side table cannot be consulted. The mark +** prevents the BTM_LE_KEY_PID handler from consolidating two pseudo +** bonds (which share the peer IRK / Identity) into one record and +** losing one LTK after reboot. +** +** Returns TRUE if a record was found and marked. +*******************************************************************************/ +BOOLEAN BTM_BleMarkPseudoBond(BD_ADDR bd_addr); +#endif + /******************************************************************************* ** ** Function BTM__BLEReadDiscoverability diff --git a/components/bt/host/bluedroid/stack/include/stack/btu.h b/components/bt/host/bluedroid/stack/include/stack/btu.h index 5d2a821d8f5..f973cccb68c 100644 --- a/components/bt/host/bluedroid/stack/include/stack/btu.h +++ b/components/bt/host/bluedroid/stack/include/stack/btu.h @@ -174,6 +174,7 @@ typedef void (*tBTU_EVENT_CALLBACK)(BT_HDR *p_hdr); /* L2CAP host-driven Create_Connection retry back-off timer */ #define BTU_TTYPE_L2CAP_LINK_RETRY 113 +#define BTU_TTYPE_ATT_WAIT_FOR_CONF 114 /* BTU Task Signal */ typedef enum { diff --git a/components/bt/host/bluedroid/stack/include/stack/gatt_api.h b/components/bt/host/bluedroid/stack/include/stack/gatt_api.h index 2e1d2d54c2b..2eaab687601 100644 --- a/components/bt/host/bluedroid/stack/include/stack/gatt_api.h +++ b/components/bt/host/bluedroid/stack/include/stack/gatt_api.h @@ -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 } diff --git a/components/bt/host/bluedroid/stack/include/stack/hcimsgs.h b/components/bt/host/bluedroid/stack/include/stack/hcimsgs.h index a126492cf3a..ae2155a5865 100644 --- a/components/bt/host/bluedroid/stack/include/stack/hcimsgs.h +++ b/components/bt/host/bluedroid/stack/include/stack/hcimsgs.h @@ -1052,10 +1052,10 @@ BOOLEAN btsnd_hcic_ble_set_phy(UINT16 conn_handle, UINT8 rx_phys, UINT16 phy_options); #endif // #if (BLE_50_FEATURE_SUPPORT == TRUE) #if (BLE_50_DTM_TEST_EN == TRUE) -UINT8 btsnd_hcic_ble_enhand_rx_test(UINT8 rx_channel, UINT8 phy, +BOOLEAN btsnd_hcic_ble_enhand_rx_test(UINT8 rx_channel, UINT8 phy, UINT8 modulation_idx); -UINT8 btsnd_hcic_ble_enhand_tx_test(UINT8 tx_channel, UINT8 len, +BOOLEAN btsnd_hcic_ble_enhand_tx_test(UINT8 tx_channel, UINT8 len, UINT8 packect, UINT8 phy); #endif // #if (BLE_50_DTM_TEST_EN == TRUE) @@ -1223,32 +1223,32 @@ UINT8 btsnd_hcic_ble_iso_set_cig_params(uint8_t cig_id, uint32_t sdu_int_c_to_p, UINT8 btsnd_hcic_ble_iso_set_cig_params_test(uint8_t cig_id, uint32_t sdu_int_c_to_p, uint32_t sdu_int_p_to_c, uint8_t ft_c_to_p, uint8_t ft_p_to_c, uint16_t iso_interval, uint8_t worse_case_SCA, uint8_t packing, uint8_t framing, uint8_t cis_cnt, struct ble_hci_le_cis_params_test *cis_params_test); -UINT8 btsnd_hcic_ble_iso_create_cis(uint8_t cis_count, struct ble_hci_cis_hdls *cis_hdls); +BOOLEAN btsnd_hcic_ble_iso_create_cis(uint8_t cis_count, struct ble_hci_cis_hdls *cis_hdls); UINT8 btsnd_hcic_ble_iso_remove_cig(uint8_t cig_id); #endif // (BLE_FEAT_ISO_CIG_CENTRAL_EN == TRUE) #if (BLE_FEAT_ISO_CIG_PERIPHERAL_EN == TRUE) -UINT8 btsnd_hcic_ble_iso_accept_cis_req(uint16_t cis_handle); +BOOLEAN btsnd_hcic_ble_iso_accept_cis_req(uint16_t cis_handle); UINT8 btsnd_hcic_ble_iso_reject_cis_req(uint16_t cis_handle, uint8_t reason); #endif // #if (BLE_FEAT_ISO_CIG_PERIPHERAL_EN == TRUE) #if (BLE_FEAT_ISO_BIG_BROADCASTER_EN == TRUE) -UINT8 btsnd_hcic_ble_big_create(uint8_t big_handle, uint8_t adv_handle, uint8_t num_bis, +BOOLEAN btsnd_hcic_ble_big_create(uint8_t big_handle, uint8_t adv_handle, uint8_t num_bis, uint32_t sdu_interval, uint16_t max_sdu, uint16_t max_transport_latency, uint8_t rtn, uint8_t phy, uint8_t packing, uint8_t framing, uint8_t encryption, uint8_t *broadcast_code); -UINT8 btsnd_hcic_ble_big_create_test(uint8_t big_handle, uint8_t adv_handle, uint8_t num_bis, +BOOLEAN btsnd_hcic_ble_big_create_test(uint8_t big_handle, uint8_t adv_handle, uint8_t num_bis, uint32_t sdu_interval, uint16_t iso_interval, uint8_t nse, uint16_t max_sdu, uint16_t max_pdu, uint8_t phy, uint8_t packing, uint8_t framing, uint8_t bn, uint8_t irc, uint8_t pto, uint8_t encryption, uint8_t *broadcast_code); -UINT8 btsnd_hcic_ble_big_terminate(uint8_t big_handle, uint8_t reason); +BOOLEAN btsnd_hcic_ble_big_terminate(uint8_t big_handle, uint8_t reason); #endif // #if (BLE_FEAT_ISO_BIG_BROADCASTER_EN == TRUE) #if (BLE_FEAT_ISO_BIG_SYNCER_EN == TRUE) -UINT8 btsnd_hcic_ble_big_sync_create(uint8_t big_handle, uint16_t sync_handle, +BOOLEAN btsnd_hcic_ble_big_sync_create(uint8_t big_handle, uint16_t sync_handle, uint8_t encryption, uint8_t *bc_code, uint8_t mse, uint16_t big_sync_timeout, uint8_t num_bis, uint8_t *bis); @@ -1305,7 +1305,7 @@ UINT8 btsnd_hcic_ble_read_antenna_info(void); #define HCIC_PARAM_SIZE_SET_TRANS_PWR_REPORTING_ENABLE 4 UINT8 btsnd_hcic_ble_enh_read_trans_power_level(uint16_t conn_handle, uint8_t phy); -UINT8 btsnd_hcic_ble_read_remote_trans_power_level(uint16_t conn_handle, uint8_t phy); +BOOLEAN btsnd_hcic_ble_read_remote_trans_power_level(uint16_t conn_handle, uint8_t phy); UINT8 btsnd_hcic_ble_set_path_loss_rpt_params(uint16_t conn_handle, uint8_t high_threshold, uint8_t high_hysteresis, uint8_t low_threshold, uint8_t low_hysteresis, uint16_t min_time_spent); UINT8 btsnd_hcic_ble_set_path_loss_rpt_enable(uint16_t conn_handle, uint8_t enable); @@ -1318,7 +1318,7 @@ UINT8 btsnd_hcic_ble_set_trans_pwr_rpt_enable(uint16_t conn_handle, uint8_t loca UINT8 btsnd_hcic_ble_set_default_subrate(UINT16 subrate_min, UINT16 subrate_max, UINT16 max_latency, UINT16 continuation_number, UINT16 supervision_timeout); -UINT8 btsnd_hcic_ble_subrate_request(UINT16 conn_handle, UINT16 subrate_min, UINT16 subrate_max, UINT16 max_latency, +BOOLEAN btsnd_hcic_ble_subrate_request(UINT16 conn_handle, UINT16 subrate_min, UINT16 subrate_max, UINT16 max_latency, UINT16 continuation_number, UINT16 supervision_timeout); #endif // #if (BLE_FEAT_CONN_SUBRATING == TRUE) @@ -1380,8 +1380,8 @@ UINT8 btsnd_hcic_ble_set_ext_adv_params_v2(UINT8 adv_handle, UINT16 properties, #define HCIC_PARAM_SIZE_SET_PROCEDURE_PARAMS_LEN 23 #define HCIC_PARAM_SIZE_SET_PROCEDURE_ENABLE_PARAMS_LEN 4 -UINT8 btsnd_hcic_ble_cs_read_local_supported_caps(void); -UINT8 btsnd_hcic_ble_cs_read_remote_supported_capabilities(UINT16 conn_handle); +BOOLEAN btsnd_hcic_ble_cs_read_local_supported_caps(void); +BOOLEAN btsnd_hcic_ble_cs_read_remote_supported_capabilities(UINT16 conn_handle); UINT8 btsnd_hcic_ble_cs_write_cached_remote_supported_capabilities(UINT16 conn_handle, UINT8 num_config_supported, UINT16 max_consecutive_proc_supported, UINT8 num_ant_supported, UINT8 max_ant_paths_supported, UINT8 roles_supported, UINT8 modes_supported, UINT8 rtt_capability, UINT8 rtt_aa_only_n, @@ -1390,17 +1390,17 @@ UINT8 btsnd_hcic_ble_cs_write_cached_remote_supported_capabilities(UINT16 conn_h UINT16 T_IP1_times_supported, UINT16 T_IP2_times_supported, UINT16 T_FCS_times_supported, UINT16 T_PM_times_supported, UINT8 T_SW_times_supported, UINT8 TX_SNR_capability); -UINT8 btsnd_hcic_ble_cs_security_enable(UINT16 conn_handle); +BOOLEAN btsnd_hcic_ble_cs_security_enable(UINT16 conn_handle); UINT8 btsnd_hcic_ble_cs_set_default_settings(UINT16 conn_handle, UINT8 role_enable, UINT8 cs_sync_ant_selection, INT8 max_tx_power); -UINT8 btsnd_hcic_ble_cs_read_remote_fae_table(UINT16 conn_handle); +BOOLEAN btsnd_hcic_ble_cs_read_remote_fae_table(UINT16 conn_handle); UINT8 btsnd_hcic_ble_cs_write_cached_remote_fae_table(UINT16 conn_handle, UINT8 *remote_fae_table); -UINT8 btsnd_hcic_ble_cs_create_config(UINT16 conn_handle, UINT8 config_id, UINT8 create_context, +BOOLEAN btsnd_hcic_ble_cs_create_config(UINT16 conn_handle, UINT8 config_id, UINT8 create_context, UINT8 main_mode_type, UINT8 sub_mode_type, UINT8 min_main_mode_steps, UINT8 max_main_mode_steps, UINT8 main_mode_repetition, UINT8 mode_0_steps, UINT8 role, UINT8 rtt_type, UINT8 cs_sync_phy, UINT8 *channel_map, UINT8 channel_map_repetition, UINT8 channel_selection_type, UINT8 ch3c_shape, UINT8 ch3c_jump,UINT8 reserved); -UINT8 btsnd_hcic_ble_cs_remove_config(UINT16 conn_handle, UINT8 config_id); +BOOLEAN btsnd_hcic_ble_cs_remove_config(UINT16 conn_handle, UINT8 config_id); UINT8 btsnd_hcic_ble_cs_set_channel_classification(UINT8 *channel_class); UINT8 btsnd_hcic_ble_cs_set_procedure_params(UINT16 conn_handle, UINT8 config_id, UINT16 max_procedure_len, UINT16 min_procedure_interval, UINT16 max_procedure_interval, @@ -1408,7 +1408,7 @@ UINT8 btsnd_hcic_ble_cs_set_procedure_params(UINT16 conn_handle, UINT8 config_id UINT32 max_subevent_len, UINT8 tone_ant_config_selection, UINT8 phy, UINT8 tx_power_delta, UINT8 preferred_peer_antenna, UINT8 SNR_control_initiator, UINT8 SNR_control_reflector); -UINT8 btsnd_hcic_ble_cs_procedure_enable(UINT16 conn_handle, UINT8 config_id, UINT8 enable); +BOOLEAN btsnd_hcic_ble_cs_procedure_enable(UINT16 conn_handle, UINT8 config_id, UINT8 enable); #endif // (BT_BLE_FEAT_CHANNEL_SOUNDING == TRUE) #if (BT_BLE_FEAT_CS_SECURITY_REQUIREMENTS == TRUE) diff --git a/components/bt/host/bluedroid/stack/include/stack/l2c_api.h b/components/bt/host/bluedroid/stack/include/stack/l2c_api.h index 45538ff5347..e4e2c6e30c6 100644 --- a/components/bt/host/bluedroid/stack/include/stack/l2c_api.h +++ b/components/bt/host/bluedroid/stack/include/stack/l2c_api.h @@ -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) /******************************************************************************* diff --git a/components/bt/host/bluedroid/stack/include/stack/l2cdefs.h b/components/bt/host/bluedroid/stack/include/stack/l2cdefs.h index 1572a37d6d7..b64573e0d82 100644 --- a/components/bt/host/bluedroid/stack/include/stack/l2cdefs.h +++ b/components/bt/host/bluedroid/stack/include/stack/l2cdefs.h @@ -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 diff --git a/components/bt/host/bluedroid/stack/l2cap/include/l2c_int.h b/components/bt/host/bluedroid/stack/l2cap/include/l2c_int.h index 705b11048bd..ce15c167ac9 100644 --- a/components/bt/host/bluedroid/stack/l2cap/include/l2c_int.h +++ b/components/bt/host/bluedroid/stack/l2cap/include/l2c_int.h @@ -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); @@ -814,6 +842,8 @@ extern void l2c_fcr_free_timer (tL2C_CCB *p_ccb); */ #if (BLE_INCLUDED == TRUE) extern BOOLEAN l2cble_create_conn (tL2C_LCB *p_lcb); +extern void l2cble_remove_pending_direct_conn (tL2C_LCB *p_lcb); +extern void l2cble_cleanup_alloc_ccb_failed_conn (tL2C_LCB *p_lcb); extern void l2cble_process_sig_cmd (tL2C_LCB *p_lcb, UINT8 *p, UINT16 pkt_len); extern void l2cble_conn_comp (UINT16 handle, UINT8 role, BD_ADDR bda, tBLE_ADDR_TYPE type, UINT16 conn_interval, UINT16 conn_latency, UINT16 conn_timeout); @@ -828,7 +858,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) diff --git a/components/bt/host/bluedroid/stack/l2cap/l2c_api.c b/components/bt/host/bluedroid/stack/l2cap/l2c_api.c index c0487f16a5c..35953afcd11 100644 --- a/components/bt/host/bluedroid/stack/l2cap/l2c_api.c +++ b/components/bt/host/bluedroid/stack/l2cap/l2c_api.c @@ -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) @@ -1458,17 +1476,22 @@ UINT16 L2CA_ConnectLECocReq(UINT16 psm, BD_ADDR p_bd_addr, tL2CAP_LE_CFG_INFO *p } /* First, see if we already have a le link to the remote */ + BOOLEAN lcb_allocated = FALSE; tL2C_LCB *p_lcb = l2cu_find_lcb_by_bd_addr(p_bd_addr, BT_TRANSPORT_LE); if (p_lcb == NULL) { /* No link. Get an LCB and start link establishment */ p_lcb = l2cu_allocate_lcb(p_bd_addr, FALSE, BT_TRANSPORT_LE); - if ((p_lcb == NULL) - /* currently use BR/EDR for ERTM mode l2cap connection */ - || (l2cu_create_conn(p_lcb, BT_TRANSPORT_LE) == FALSE) ) - { - L2CAP_TRACE_WARNING("%s conn not started for PSM: 0x%04x p_lcb: 0x%p", + if (p_lcb == NULL) { + L2CAP_TRACE_WARNING("%s conn not started for PSM: 0x%04x p_lcb: NULL", + __func__, psm); + return 0; + } + lcb_allocated = TRUE; + if (l2cu_create_conn(p_lcb, BT_TRANSPORT_LE) == FALSE) { + L2CAP_TRACE_WARNING("%s conn not started for PSM: 0x%04x p_lcb: %p", __func__, psm, p_lcb); + l2cu_release_lcb(p_lcb); return 0; } } @@ -1478,11 +1501,21 @@ UINT16 L2CA_ConnectLECocReq(UINT16 psm, BD_ADDR p_bd_addr, tL2CAP_LE_CFG_INFO *p if (p_ccb == NULL) { L2CAP_TRACE_WARNING("%s no CCB, PSM: 0x%04x", __func__, psm); + if (lcb_allocated) { + l2cble_cleanup_alloc_ccb_failed_conn(p_lcb); + } return 0; } /* 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 +1528,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 +1550,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 +1567,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 +1610,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); } /******************************************************************************* diff --git a/components/bt/host/bluedroid/stack/l2cap/l2c_ble.c b/components/bt/host/bluedroid/stack/l2cap/l2c_ble.c index 3dfd210cc24..2cb1ac8928f 100644 --- a/components/bt/host/bluedroid/stack/l2cap/l2c_ble.c +++ b/components/bt/host/bluedroid/stack/l2cap/l2c_ble.c @@ -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; @@ -834,6 +891,11 @@ void l2cble_process_sig_cmd (tL2C_LCB *p_lcb, UINT8 *p, UINT16 pkt_len) STREAM_TO_UINT16(credits, p); L2CAP_TRACE_DEBUG("%s spsm %x, scid %x", __func__, spsm, scid); + if (mtu < L2CAP_LE_MIN_MTU || mps < L2CAP_LE_MIN_MPS || mps > L2CAP_LE_MAX_MPS) { + l2cu_reject_ble_connection(p_lcb, id, L2CAP_LE_RESULT_INVALID_PARAMETERS); + break; + } + p_ccb = l2cu_find_ccb_by_remote_cid(p_lcb, scid); if (p_ccb) { l2cu_reject_ble_connection(p_lcb, id, L2CAP_LE_RESULT_SOURCE_CID_ALREADY_ALLOCATED); @@ -855,17 +917,35 @@ void l2cble_process_sig_cmd (tL2C_LCB *p_lcb, UINT8 *p, UINT16 pkt_len) p_ccb->remote_id = id; p_ccb->p_rcb = p_rcb; p_ccb->remote_cid = scid; - p_ccb->local_conn_cfg.mtu = mtu; - p_ccb->local_conn_cfg.mps = controller_get_interface()->get_acl_data_size_ble(); - p_ccb->local_conn_cfg.credits = credits; + /* Peer request fields describe peer receive capability */ p_ccb->peer_conn_cfg.mtu = mtu; p_ccb->peer_conn_cfg.mps = mps; p_ccb->peer_conn_cfg.credits = credits; + /* Response must advertise our receive capability, not peer's */ + p_ccb->local_conn_cfg.mtu = L2CAP_LE_DEFAULT_MTU; + p_ccb->local_conn_cfg.mps = controller_get_interface()->get_acl_data_size_ble(); + p_ccb->local_conn_cfg.credits = L2CAP_LE_DEFAULT_CREDIT; 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 +971,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 +986,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 +1001,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); @@ -921,6 +1059,17 @@ void l2cble_process_sig_cmd (tL2C_LCB *p_lcb, UINT8 *p, UINT16 pkt_len) } } +#if (BLE_50_FEATURE_SUPPORT == TRUE) +static void l2cble_abort_direct_conn_init(tL2C_LCB *p_lcb) +{ + btu_stop_timer(&p_lcb->timer_entry); + l2cb.is_ble_connecting = FALSE; + memset(l2cb.ble_connecting_bda, 0, BD_ADDR_LEN); + btm_ble_set_conn_st(BLE_CONN_IDLE); + p_lcb->link_state = LST_DISCONNECTED; +} +#endif // (BLE_50_FEATURE_SUPPORT == TRUE) + /******************************************************************************* ** ** Function l2cble_init_direct_conn @@ -929,6 +1078,9 @@ void l2cble_process_sig_cmd (tL2C_LCB *p_lcb, UINT8 *p, UINT16 pkt_len) ** ** Returns TRUE connection initiated, FALSE otherwise. ** +** Note On failure the LCB is not released; the caller must call +** l2cu_release_lcb (see l2cu_create_conn contract in l2c_link.c). +** *******************************************************************************/ BOOLEAN l2cble_init_direct_conn (tL2C_LCB *p_lcb) { @@ -952,6 +1104,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); } @@ -1012,7 +1168,6 @@ BOOLEAN l2cble_init_direct_conn (tL2C_LCB *p_lcb) #if (BLE_TOPOLOGY_CHECK == TRUE) if (!btm_ble_topology_check(BTM_BLE_STATE_INIT)) { - l2cu_release_lcb (p_lcb); L2CAP_TRACE_ERROR("initiate direct connection fail, topology limitation"); return FALSE; } @@ -1077,7 +1232,6 @@ BOOLEAN l2cble_init_direct_conn (tL2C_LCB *p_lcb) p_dev_rec->conn_params.min_ce_len : BLE_CE_LEN_MIN), /* UINT16 min_ce_len */ (UINT16) ((p_dev_rec->conn_params.max_ce_len != BTM_BLE_CONN_PARAM_UNDEF) ? p_dev_rec->conn_params.max_ce_len : BLE_CE_LEN_MIN) /* UINT16 max_ce_len */)) { - l2cu_release_lcb (p_lcb); L2CAP_TRACE_ERROR("initiate direct connection fail, no resources"); return (FALSE); } else { @@ -1135,10 +1289,7 @@ BOOLEAN l2cble_init_direct_conn (tL2C_LCB *p_lcb) #if (BT_BLE_FEAT_PAWR_EN == TRUE) if (p_lcb->is_pawr_synced) { if(!btsnd_hcic_ble_create_ext_conn_v2(&aux_conn)) { - l2cb.is_ble_connecting = FALSE; - memset(l2cb.ble_connecting_bda, 0, BD_ADDR_LEN); - btm_ble_set_conn_st (BLE_CONN_IDLE); - l2cu_release_lcb (p_lcb); + l2cble_abort_direct_conn_init(p_lcb); L2CAP_TRACE_ERROR("initiate pawr sync connection failed, no resources"); return (FALSE); } @@ -1146,16 +1297,12 @@ BOOLEAN l2cble_init_direct_conn (tL2C_LCB *p_lcb) #endif // (BT_BLE_FEAT_PAWR_EN == TRUE) { if(!btsnd_hcic_ble_create_ext_conn(&aux_conn)) { - l2cb.is_ble_connecting = FALSE; - memset(l2cb.ble_connecting_bda, 0, BD_ADDR_LEN); - btm_ble_set_conn_st (BLE_CONN_IDLE); - l2cu_release_lcb (p_lcb); + l2cble_abort_direct_conn_init(p_lcb); L2CAP_TRACE_ERROR("initiate Aux connection failed, no resources"); return (FALSE); } } #else - l2cu_release_lcb (p_lcb); L2CAP_TRACE_ERROR("BLE 5.0 not support!\n"); return (FALSE); #endif // #if (BLE_50_FEATURE_SUPPORT == TRUE) @@ -1194,6 +1341,50 @@ BOOLEAN l2cble_create_conn (tL2C_LCB *p_lcb) return rt; } +/******************************************************************************* +** +** Function l2cble_cleanup_alloc_ccb_failed_conn +** +** Description Clean up after LE CoC setup fails to allocate a CCB. If a +** direct HCI connection is in progress, cancel it and update +** BTM state; otherwise drop a queued direct-connect request. +** +** Returns void +** +*******************************************************************************/ +void l2cble_cleanup_alloc_ccb_failed_conn (tL2C_LCB *p_lcb) +{ + if (p_lcb == NULL) { + return; + } + + if (p_lcb->link_state == LST_CONNECTING) { + if (!L2CA_CancelBleConnectReq(p_lcb->remote_bd_addr)) { + L2CAP_TRACE_ERROR("%s: cancel direct connect failed", __func__); + l2cu_release_lcb(p_lcb); + memset(l2cb.ble_connecting_bda, 0, BD_ADDR_LEN); + btm_ble_set_conn_st(BLE_CONN_IDLE); + } + } else { + l2cble_remove_pending_direct_conn(p_lcb); + l2cu_release_lcb(p_lcb); + } +} + +/******************************************************************************* +** +** Function l2cble_remove_pending_direct_conn +** +** Description Drop a queued direct-connection attempt for this LCB. +** +** Returns void +** +*******************************************************************************/ +void l2cble_remove_pending_direct_conn (tL2C_LCB *p_lcb) +{ + btm_ble_remove_direct_conn_req(p_lcb); +} + /******************************************************************************* ** ** Function l2c_link_processs_ble_num_bufs @@ -1675,7 +1866,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 +1989,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 +2084,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) */ /******************************************************************************* ** diff --git a/components/bt/host/bluedroid/stack/l2cap/l2c_ble_ecfc.c b/components/bt/host/bluedroid/stack/l2cap/l2c_ble_ecfc.c new file mode 100644 index 00000000000..17b432d8682 --- /dev/null +++ b/components/bt/host/bluedroid/stack/l2cap/l2c_ble_ecfc.c @@ -0,0 +1,1547 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* LE L2CAP Enhanced Credit Based Flow Control (ECFC) - signaling 0x17/0x18/0x19/0x1A. */ + +#include +#include "device/controller.h" +#include "stack/bt_types.h" +#include "stack/l2cdefs.h" +#include "l2c_int.h" +#include "stack/l2c_api.h" +#include "stack/btu.h" +#if (BLE_EATT_INCLUDED == TRUE) +#include "gatt_eatt_int.h" +#endif + +#if (BLE_L2CAP_ENHANCED_COC_INCLUDED == TRUE) + +#define L2C_BLE_ECFC_TRACE_API(fmt, ...) L2CAP_TRACE_API("LE_ECFC: " fmt, ##__VA_ARGS__) +#define L2C_BLE_ECFC_TRACE_DEBUG(fmt, ...) L2CAP_TRACE_DEBUG("LE_ECFC: " fmt, ##__VA_ARGS__) +#define L2C_BLE_ECFC_TRACE_WARN(fmt, ...) L2CAP_TRACE_WARNING("LE_ECFC: " fmt, ##__VA_ARGS__) +#define L2C_BLE_ECFC_TRACE_ERROR(fmt, ...) L2CAP_TRACE_ERROR("LE_ECFC: " fmt, ##__VA_ARGS__) + +/* Core Spec v6.2 Vol 3 Part A 4.25/4.27: a single enhanced credit based + * connection or reconfiguration request may target at most five channels. This + * is independent of BLE_MAX_L2CAP_CLIENTS, which sizes the local channel + * pool and can be configured up to 15. */ +#define L2C_BLE_ECFC_MAX_REQ_CHANS 5 + +typedef struct { + BOOLEAN in_use; + BOOLEAN pending_link; + UINT8 sig_id; + UINT8 num_chan; + tL2C_LCB *p_lcb; + tL2C_CCB *ccbs[BLE_MAX_L2CAP_CLIENTS]; +} tL2C_BLE_ECFC_CL_TXN; + +typedef struct { + BOOLEAN in_use; + UINT8 rem_id; + tL2C_LCB *p_lcb; + UINT8 num_total; + UINT8 num_done; + BOOLEAN rsp_recorded; /* canonical rsp_* captured from first accepted chan */ + UINT16 reject_result; /* app-supplied reject reason (security codes prioritized) */ + UINT16 rsp_mtu; + UINT16 rsp_mps; + UINT16 rsp_credits; + tL2C_CCB *ccbs[BLE_MAX_L2CAP_CLIENTS]; + BOOLEAN accepted[BLE_MAX_L2CAP_CLIENTS]; +} tL2C_BLE_ECFC_SRV_TXN; + +typedef struct { + BOOLEAN in_use; + UINT8 sig_id; + UINT8 num_chan; + UINT16 new_mtu; + UINT16 new_mps; + tL2C_LCB *p_lcb; + tL2C_CCB *ccbs[BLE_MAX_L2CAP_CLIENTS]; +} tL2C_BLE_ECFC_RECONFIG_TXN; + +#if (BLE_L2CAP_COC_CLIENT_INCLUDED == TRUE) +static tL2C_BLE_ECFC_CL_TXN s_ecfc_cl_txn[MAX_L2CAP_LINKS]; +#endif +#if (BLE_L2CAP_COC_SERVER_INCLUDED == TRUE) +static tL2C_BLE_ECFC_SRV_TXN s_ecfc_srv_txn[MAX_L2CAP_LINKS]; +#endif +static tL2C_BLE_ECFC_RECONFIG_TXN s_ecfc_reconfig_txn[MAX_L2CAP_LINKS]; + +static void l2c_ble_ecfc_apply_default_cfg(tL2CAP_LE_CFG_INFO *cfg); +static void l2c_ble_ecfc_open_ccb(tL2C_CCB *p_ccb, UINT16 result); +#if (BLE_L2CAP_COC_CLIENT_INCLUDED == TRUE) +static tL2C_BLE_ECFC_CL_TXN *l2c_ble_ecfc_find_cl_txn(tL2C_LCB *p_lcb, UINT8 sig_id); +static void l2c_ble_ecfc_cl_txn_free(tL2C_BLE_ECFC_CL_TXN *txn); +static BOOLEAN l2c_ble_ecfc_cl_txn_has_ccb(tL2C_BLE_ECFC_CL_TXN *txn); +static BOOLEAN l2c_ble_ecfc_cl_send_connect(tL2C_BLE_ECFC_CL_TXN *txn); +#endif +static tL2C_BLE_ECFC_RECONFIG_TXN *l2c_ble_ecfc_find_reconfig_txn(tL2C_LCB *p_lcb, UINT8 sig_id); +#if (BLE_L2CAP_COC_SERVER_INCLUDED == TRUE) +static void l2c_ble_ecfc_srv_txn_free(tL2C_BLE_ECFC_SRV_TXN *txn); +static void l2c_ble_ecfc_srv_txn_try_complete(tL2C_BLE_ECFC_SRV_TXN *txn); +static void l2c_ble_ecfc_sec_cback(BD_ADDR bd_addr, tBT_TRANSPORT transport, + void *p_ref_data, tBTM_STATUS result); +static void l2c_ble_ecfc_fire_connect_ind(tL2C_BLE_ECFC_SRV_TXN *txn); +static BOOLEAN l2c_ble_ecfc_srv_txn_has_ccb(tL2C_BLE_ECFC_SRV_TXN *txn); +#endif +static BOOLEAN l2c_ble_ecfc_reconfig_txn_has_ccb(tL2C_BLE_ECFC_RECONFIG_TXN *txn); + +static void l2c_ble_ecfc_apply_default_cfg(tL2CAP_LE_CFG_INFO *cfg) +{ + if (cfg->mtu == 0) { + cfg->mtu = L2CAP_LE_DEFAULT_MTU; + } + if (cfg->mps == 0) { + cfg->mps = L2CAP_LE_COC_MPS; + } + cfg->mps = L2CAP_LE_CLAMP_MPS(cfg->mps); + if (cfg->mps < L2CAP_LE_ECFC_MIN_MPS) { + cfg->mps = L2CAP_LE_ECFC_MIN_MPS; + } + if (cfg->mtu < L2CAP_LE_ECFC_MIN_MTU) { + cfg->mtu = L2CAP_LE_ECFC_MIN_MTU; + } + if (cfg->credits == 0) { + cfg->credits = L2CAP_LE_INIT_CREDITS; + } +} + +#if (BLE_L2CAP_COC_CLIENT_INCLUDED == TRUE) +static tL2C_BLE_ECFC_CL_TXN *l2c_ble_ecfc_alloc_cl_txn(tL2C_LCB *p_lcb, UINT8 sig_id) +{ + for (int i = 0; i < MAX_L2CAP_LINKS; i++) { + if (!s_ecfc_cl_txn[i].in_use) { + memset(&s_ecfc_cl_txn[i], 0, sizeof(s_ecfc_cl_txn[i])); + s_ecfc_cl_txn[i].in_use = TRUE; + s_ecfc_cl_txn[i].p_lcb = p_lcb; + s_ecfc_cl_txn[i].sig_id = sig_id; + return &s_ecfc_cl_txn[i]; + } + } + L2C_BLE_ECFC_TRACE_WARN("alloc cl txn failed sig_id=%u", sig_id); + return NULL; +} + +static tL2C_BLE_ECFC_CL_TXN *l2c_ble_ecfc_find_cl_txn(tL2C_LCB *p_lcb, UINT8 sig_id) +{ + for (int i = 0; i < MAX_L2CAP_LINKS; i++) { + if (s_ecfc_cl_txn[i].in_use && s_ecfc_cl_txn[i].p_lcb == p_lcb && + s_ecfc_cl_txn[i].sig_id == sig_id) { + return &s_ecfc_cl_txn[i]; + } + } + return NULL; +} + +static void l2c_ble_ecfc_cl_txn_free(tL2C_BLE_ECFC_CL_TXN *txn) +{ + if (txn) { + memset(txn, 0, sizeof(*txn)); + } +} + +static BOOLEAN l2c_ble_ecfc_cl_txn_has_ccb(tL2C_BLE_ECFC_CL_TXN *txn) +{ + for (int i = 0; i < txn->num_chan; i++) { + if (txn->ccbs[i] != NULL) { + return TRUE; + } + } + return FALSE; +} + +static BOOLEAN l2c_ble_ecfc_cl_send_connect(tL2C_BLE_ECFC_CL_TXN *txn) +{ + tL2C_LCB *p_lcb; + UINT16 scids[BLE_MAX_L2CAP_CLIENTS]; + int i; + + if (txn == NULL || txn->num_chan == 0) { + return FALSE; + } + + p_lcb = txn->p_lcb; + if (p_lcb == NULL || p_lcb->link_state != LST_CONNECTED) { + return FALSE; + } + + /* Validate every CCB before mutating any state: a NULL entry mid-array (a CCB + * released via on_ccb_release while the txn waited for the link) must not + * leave earlier CCBs stuck in CST_W4_L2CAP_CONNECT_RSP with no rollback. */ + for (i = 0; i < txn->num_chan; i++) { + if (txn->ccbs[i] == NULL || txn->ccbs[i]->p_rcb == NULL) { + return FALSE; + } + } + for (i = 0; i < txn->num_chan; i++) { + scids[i] = txn->ccbs[i]->local_cid; + txn->ccbs[i]->local_id = txn->sig_id; + txn->ccbs[i]->chnl_state = CST_W4_L2CAP_CONNECT_RSP; + } + + /* If the request could not even be built/queued (e.g. buffer OOM), the peer + * will never respond, so the CCBs would stay stuck in + * CST_W4_L2CAP_CONNECT_RSP forever. Roll the state back and report failure so + * the caller can clean up. */ + if (!l2cu_send_peer_ble_enhanced_credit_conn_req(p_lcb, txn->sig_id, + txn->ccbs[0]->p_rcb->real_psm, + txn->ccbs[0]->local_conn_cfg.mtu, + txn->ccbs[0]->local_conn_cfg.mps, + txn->ccbs[0]->local_conn_cfg.credits, + txn->num_chan, scids)) { + L2C_BLE_ECFC_TRACE_ERROR("0x17 send failed sig_id=%u", txn->sig_id); + for (i = 0; i < txn->num_chan; i++) { + if (txn->ccbs[i]) { + txn->ccbs[i]->chnl_state = CST_CLOSED; + } + } + return FALSE; + } + + /* Guard against a peer that never sends the 0x18 response: one timer per + * transaction (on ccbs[0]) aborts the whole batch on expiry. */ + l2c_ble_le_coc_start_rsp_timer(txn->ccbs[0], L2CAP_CHNL_CONNECT_TOUT); + return TRUE; +} + +void l2c_ble_ecfc_on_link_up(tL2C_LCB *p_lcb) +{ + if (p_lcb == NULL) { + return; + } + + for (int i = 0; i < MAX_L2CAP_LINKS; i++) { + tL2C_BLE_ECFC_CL_TXN *txn = &s_ecfc_cl_txn[i]; + + if (!txn->in_use || !txn->pending_link || txn->p_lcb != p_lcb) { + continue; + } + + txn->pending_link = FALSE; + if (!l2c_ble_ecfc_cl_send_connect(txn)) { + L2C_BLE_ECFC_TRACE_ERROR("deferred ConnectLEEcocReq send failed"); + /* Fail every channel so the app is notified and the CCBs are + * released (open_ccb releases on non-OK), then free the txn to + * avoid leaking it and leaving CCBs stuck in W4_CONNECT_RSP. + * Snapshot then free before opening (see l2c_ble_ecfc_abort_cl_txn): + * a non-OK open re-enters on_ccb_release and may free/realloc this + * slot during the callback chain. */ + tL2C_CCB *ccbs[BLE_MAX_L2CAP_CLIENTS]; + UINT8 num_chan = txn->num_chan; + if (num_chan > BLE_MAX_L2CAP_CLIENTS) { + num_chan = BLE_MAX_L2CAP_CLIENTS; + } + memcpy(ccbs, txn->ccbs, num_chan * sizeof(tL2C_CCB *)); + l2c_ble_ecfc_cl_txn_free(txn); + + for (int j = 0; j < num_chan; j++) { + if (ccbs[j]) { + l2c_ble_ecfc_open_ccb(ccbs[j], L2CAP_CONN_NO_PSM); + } + } + } + } +} +#endif /* BLE_L2CAP_COC_CLIENT_INCLUDED */ + +#if (BLE_L2CAP_COC_SERVER_INCLUDED == TRUE) +static tL2C_BLE_ECFC_SRV_TXN *l2c_ble_ecfc_alloc_srv_txn(tL2C_LCB *p_lcb, UINT8 rem_id) +{ + for (int i = 0; i < MAX_L2CAP_LINKS; i++) { + if (!s_ecfc_srv_txn[i].in_use) { + memset(&s_ecfc_srv_txn[i], 0, sizeof(s_ecfc_srv_txn[i])); + s_ecfc_srv_txn[i].in_use = TRUE; + s_ecfc_srv_txn[i].p_lcb = p_lcb; + s_ecfc_srv_txn[i].rem_id = rem_id; + return &s_ecfc_srv_txn[i]; + } + } + L2C_BLE_ECFC_TRACE_WARN("alloc srv txn failed rem_id=%u", rem_id); + return NULL; +} + +static tL2C_BLE_ECFC_SRV_TXN *l2c_ble_ecfc_find_srv_txn_by_ccb(tL2C_CCB *p_ccb) +{ + for (int i = 0; i < MAX_L2CAP_LINKS; i++) { + if (!s_ecfc_srv_txn[i].in_use) { + continue; + } + for (int j = 0; j < s_ecfc_srv_txn[i].num_total; j++) { + if (s_ecfc_srv_txn[i].ccbs[j] == p_ccb) { + return &s_ecfc_srv_txn[i]; + } + } + } + return NULL; +} + +static void l2c_ble_ecfc_srv_txn_free(tL2C_BLE_ECFC_SRV_TXN *txn) +{ + if (txn) { +#if (SMP_INCLUDED == TRUE) + /* The server security check (l2ble_sec_access_req in + * l2c_ble_ecfc_handle_conn_req) is queued with p_ref_data == txn, not a + * CCB, so the CCB-based flush in l2c_ble_le_coc_cleanup_ccb never matches + * it. If the txn is torn down (e.g. link loss) while that check is still + * outstanding, drop the pending request here so the deferred SMP callback + * cannot later fire l2c_ble_ecfc_sec_cback on a reused txn slot. Must run + * before the memset since p_lcb is needed for the queue lookup. */ + l2ble_sec_flush_pending_req(txn->p_lcb, txn); +#endif + memset(txn, 0, sizeof(*txn)); + } +} + +static BOOLEAN l2c_ble_ecfc_srv_txn_has_ccb(tL2C_BLE_ECFC_SRV_TXN *txn) +{ + for (int i = 0; i < txn->num_total; i++) { + if (txn->ccbs[i] != NULL) { + return TRUE; + } + } + return FALSE; +} +#endif /* BLE_L2CAP_COC_SERVER_INCLUDED */ + +static tL2C_BLE_ECFC_RECONFIG_TXN *l2c_ble_ecfc_alloc_reconfig_txn(tL2C_LCB *p_lcb, UINT8 sig_id) +{ + for (int i = 0; i < MAX_L2CAP_LINKS; i++) { + if (!s_ecfc_reconfig_txn[i].in_use) { + memset(&s_ecfc_reconfig_txn[i], 0, sizeof(s_ecfc_reconfig_txn[i])); + s_ecfc_reconfig_txn[i].in_use = TRUE; + s_ecfc_reconfig_txn[i].p_lcb = p_lcb; + s_ecfc_reconfig_txn[i].sig_id = sig_id; + return &s_ecfc_reconfig_txn[i]; + } + } + L2C_BLE_ECFC_TRACE_WARN("alloc reconfig txn failed sig_id=%u", sig_id); + return NULL; +} + +static tL2C_BLE_ECFC_RECONFIG_TXN *l2c_ble_ecfc_find_reconfig_txn(tL2C_LCB *p_lcb, UINT8 sig_id) +{ + for (int i = 0; i < MAX_L2CAP_LINKS; i++) { + if (s_ecfc_reconfig_txn[i].in_use && s_ecfc_reconfig_txn[i].p_lcb == p_lcb && + s_ecfc_reconfig_txn[i].sig_id == sig_id) { + return &s_ecfc_reconfig_txn[i]; + } + } + return NULL; +} + +static BOOLEAN l2c_ble_ecfc_reconfig_txn_has_ccb(tL2C_BLE_ECFC_RECONFIG_TXN *txn) +{ + for (int i = 0; i < txn->num_chan; i++) { + if (txn->ccbs[i] != NULL) { + return TRUE; + } + } + return FALSE; +} + +void l2c_ble_ecfc_on_ccb_release(tL2C_CCB *p_ccb) +{ + int t, i; + + if (p_ccb == NULL || p_ccb->p_lcb == NULL || + p_ccb->p_lcb->transport != BT_TRANSPORT_LE) { + return; + } + +#if (BLE_L2CAP_COC_CLIENT_INCLUDED == TRUE) + for (t = 0; t < MAX_L2CAP_LINKS; t++) { + tL2C_BLE_ECFC_CL_TXN *cl = &s_ecfc_cl_txn[t]; + + if (!cl->in_use) { + continue; + } + for (i = 0; i < cl->num_chan; i++) { + if (cl->ccbs[i] == p_ccb) { + cl->ccbs[i] = NULL; + } + } + if (!l2c_ble_ecfc_cl_txn_has_ccb(cl)) { + l2c_ble_ecfc_cl_txn_free(cl); + } + } +#endif + +#if (BLE_L2CAP_COC_SERVER_INCLUDED == TRUE) + for (t = 0; t < MAX_L2CAP_LINKS; t++) { + tL2C_BLE_ECFC_SRV_TXN *srv = &s_ecfc_srv_txn[t]; + + if (!srv->in_use) { + continue; + } + for (i = 0; i < srv->num_total; i++) { + if (srv->ccbs[i] == p_ccb) { + srv->ccbs[i] = NULL; + if (!srv->accepted[i]) { + srv->num_done++; + } + } + } + if (!srv->in_use) { + continue; + } + if (srv->num_done >= srv->num_total) { + l2c_ble_ecfc_srv_txn_try_complete(srv); + } else if (!l2c_ble_ecfc_srv_txn_has_ccb(srv) && srv->num_done == 0) { + /* Abandon a txn whose CCBs were torn down before any connect_rsp + * (e.g. link loss). Do not free when num_done > 0: connect_rsp may + * still be aggregating rejects and must send the 0x18 response. */ + l2c_ble_ecfc_srv_txn_free(srv); + } + } +#endif + + for (t = 0; t < MAX_L2CAP_LINKS; t++) { + tL2C_BLE_ECFC_RECONFIG_TXN *rc = &s_ecfc_reconfig_txn[t]; + + if (!rc->in_use) { + continue; + } + for (i = 0; i < rc->num_chan; i++) { + if (rc->ccbs[i] == p_ccb) { + rc->ccbs[i] = NULL; + } + } + if (!l2c_ble_ecfc_reconfig_txn_has_ccb(rc)) { + memset(rc, 0, sizeof(*rc)); + } + } +} + +static void l2c_ble_ecfc_open_ccb(tL2C_CCB *p_ccb, UINT16 result) +{ + l2c_ble_le_coc_open_channel(p_ccb, result); +} + +#if (BLE_L2CAP_COC_SERVER_INCLUDED == TRUE) +static void l2c_ble_ecfc_srv_txn_try_complete(tL2C_BLE_ECFC_SRV_TXN *txn) +{ + UINT16 dcids[BLE_MAX_L2CAP_CLIENTS]; + UINT16 result = L2CAP_LE_RESULT_CONN_OK; + UINT8 created = 0; + int i; + + if (txn == NULL || !txn->in_use || txn->num_done < txn->num_total) { + return; + } + + /* Once num_done == num_total, every channel was either accepted (accepted[i] + * set with the CCB opened) or already released (ccbs[i] set NULL at the point + * num_done was incremented). So a not-accepted, still-attached CCB cannot + * exist here: reporting the accepted ones and zeroing the rest is enough, + * and we must not call l2cu_release_ccb() from this path (it would re-enter + * l2c_ble_ecfc_on_ccb_release() -> try_complete() and send a duplicate res). */ + for (i = 0; i < txn->num_total; i++) { + if (txn->accepted[i] && txn->ccbs[i] && txn->ccbs[i]->chnl_state == CST_OPEN) { + dcids[i] = txn->ccbs[i]->local_cid; + created++; + } else { + dcids[i] = 0; + } + } + + /* Application/resource level rejection (all DCIDs 0). Prefer the specific + * app-supplied reason (e.g. a security code 0x0005-0x0008, mandated by Core + * Spec v6.2 Vol 3 Part A 10.2) so the peer learns to encrypt/authenticate + * instead of treating it as a transient resource shortage. Fall back to + * "insufficient resources" (0x0004) when no specific reason was recorded. */ + if (created == 0) { + result = (txn->reject_result != L2CAP_LE_RESULT_CONN_OK) ? + txn->reject_result : L2CAP_LE_RESULT_NO_RESOURCES; + } else if (created < txn->num_total) { + result = L2CAP_LE_RESULT_NO_RESOURCES; + } + + l2cu_send_peer_ble_enhanced_credit_conn_res(txn->p_lcb, txn->rem_id, + txn->rsp_mtu, txn->rsp_mps, txn->rsp_credits, result, + txn->num_total, dcids); + l2c_ble_ecfc_srv_txn_free(txn); +} + +static void l2c_ble_ecfc_fire_connect_ind(tL2C_BLE_ECFC_SRV_TXN *txn) +{ + tL2CA_CONNECT_IND_CB *ind_cb; + int i; + + for (i = 0; i < txn->num_total; i++) { + tL2C_CCB *p_ccb = txn->ccbs[i]; + if (p_ccb == NULL || p_ccb->p_rcb == NULL) { + continue; + } + /* Skip a channel already accepted by a connect_rsp re-entered from an + * earlier iteration's ind_cb (batch accept sharing rem_id); firing ind_cb + * again would deliver a duplicate ConnectInd for an already-open channel. + * Mirrors the accepted[] duplicate guard in l2c_ble_ecfc_connect_rsp. */ + if (txn->accepted[i]) { + continue; + } + ind_cb = p_ccb->p_rcb->api.pL2CA_ConnectInd_Cb; + if (ind_cb) { + L2C_BLE_ECFC_TRACE_API("ConnectInd lcid=0x%04x psm=0x%04x id=%u", + p_ccb->local_cid, p_ccb->p_rcb->real_psm, txn->rem_id); + if (txn->in_use) { + (*ind_cb)(p_ccb->p_lcb->remote_bd_addr, p_ccb->local_cid, + p_ccb->p_rcb->real_psm, txn->rem_id); + } + } else { + /* Increment num_done BEFORE open_ccb: open_ccb fires ConnectCfm, + * whose handler may synchronously release this CCB and re-enter + * l2c_ble_ecfc_on_ccb_release(). If num_done were still 0 there, the + * abandon guard (!has_ccb && num_done == 0) would free the txn out + * from under us and the 0x18 response would never be sent. */ + txn->accepted[i] = TRUE; + txn->num_done++; + l2c_ble_ecfc_open_ccb(p_ccb, L2CAP_CONN_OK); + } + } + if (txn->in_use) { + l2c_ble_ecfc_srv_txn_try_complete(txn); + } +} + +static void l2c_ble_ecfc_sec_cback(BD_ADDR bd_addr, tBT_TRANSPORT transport, + void *p_ref_data, tBTM_STATUS result) +{ + tL2C_BLE_ECFC_SRV_TXN *txn = (tL2C_BLE_ECFC_SRV_TXN *)p_ref_data; + UNUSED(transport); + + L2C_BLE_ECFC_TRACE_DEBUG("sec_cback bda=%02x:%02x:%02x:%02x:%02x:%02x result=%u rem_id=%u num_total=%u", + bd_addr[0], bd_addr[1], bd_addr[2], bd_addr[3], bd_addr[4], bd_addr[5], + result, txn ? txn->rem_id : 0, txn ? txn->num_total : 0); + + if (txn == NULL || !txn->in_use) { + L2C_BLE_ECFC_TRACE_WARN("sec_cback txn invalid"); + return; + } + + if (result != BTM_SUCCESS) { + L2C_BLE_ECFC_TRACE_WARN("sec_cback security failed result=%u rem_id=%u", result, txn->rem_id); + l2cu_reject_ble_enhanced_connection(txn->p_lcb, txn->rem_id, + l2c_ble_coc_sec_status_to_result(bd_addr, result), + txn->num_total); + for (int i = 0; i < txn->num_total; i++) { + if (txn->ccbs[i]) { + tL2C_CCB *p_rel = txn->ccbs[i]; + txn->ccbs[i] = NULL; + l2cu_release_ccb(p_rel); + } + } + /* Releasing the last CCB above can reach l2c_ble_ecfc_on_ccb_release, + * whose abandon guard (all ccbs[] NULL && num_done == 0) may already have + * freed this txn. Only free again if it is still in use, mirroring the + * guard in l2c_ble_ecfc_fire_connect_ind, to avoid a double free. */ + if (txn->in_use) { + l2c_ble_ecfc_srv_txn_free(txn); + } + return; + } + + L2C_BLE_ECFC_TRACE_DEBUG("sec_cback security ok rem_id=%u, fire connect ind", txn->rem_id); + l2c_ble_ecfc_fire_connect_ind(txn); +} +#endif /* BLE_L2CAP_COC_SERVER_INCLUDED */ + +#if (BLE_L2CAP_COC_SERVER_INCLUDED == TRUE) +void l2c_ble_ecfc_connect_rsp(tL2C_CCB *p_ccb, UINT16 result) +{ + tL2C_BLE_ECFC_SRV_TXN *txn; + int idx; + + L2C_BLE_ECFC_TRACE_DEBUG("connect_rsp local_cid=0x%04x result=%u", + p_ccb ? p_ccb->local_cid : 0, result); + + if (p_ccb == NULL || !p_ccb->le_ecfc_channel) { + L2C_BLE_ECFC_TRACE_WARN("connect_rsp not an ECFC channel"); + return; + } + + txn = l2c_ble_ecfc_find_srv_txn_by_ccb(p_ccb); + if (txn == NULL) { + L2C_BLE_ECFC_TRACE_WARN("connect_rsp srv txn not found lcid=0x%04x", p_ccb->local_cid); + return; + } + + for (idx = 0; idx < txn->num_total; idx++) { + if (txn->ccbs[idx] == p_ccb) { + break; + } + } + if (idx >= txn->num_total) { + L2C_BLE_ECFC_TRACE_WARN("connect_rsp ccb not in txn lcid=0x%04x", p_ccb->local_cid); + return; + } + + /* Guard against a duplicate response for the same channel (e.g. the app + * calls accept twice for one chan_handle). Re-processing would inflate + * num_done, fire a second ConnectCfm and re-seed the RX window. */ + if (txn->accepted[idx]) { + L2C_BLE_ECFC_TRACE_WARN("connect_rsp duplicate response lcid=0x%04x", p_ccb->local_cid); + return; + } + + if (result == L2CAP_CONN_OK) { + l2c_ble_ecfc_apply_default_cfg(&p_ccb->local_conn_cfg); + /* The 0x18 response carries a single MTU/MPS/credits set that the peer + * applies uniformly to every accepted channel (Core Spec v6.2 Vol 3 + * Part A 4.26). Record the canonical values from the first accepted + * channel and force every subsequent channel's local config to match, so + * the RX window (le_coc_rx_avail) seeded in l2c_ble_le_coc_open_channel + * stays consistent with what the peer is told. */ + if (!txn->rsp_recorded) { + txn->rsp_mtu = p_ccb->local_conn_cfg.mtu; + txn->rsp_mps = p_ccb->local_conn_cfg.mps; + txn->rsp_credits = p_ccb->local_conn_cfg.credits; + txn->rsp_recorded = TRUE; + } else { + p_ccb->local_conn_cfg.mtu = txn->rsp_mtu; + p_ccb->local_conn_cfg.mps = txn->rsp_mps; + p_ccb->local_conn_cfg.credits = txn->rsp_credits; + } + /* Increment num_done BEFORE open_ccb so a synchronous CCB release from + * within the ConnectCfm callback (re-entering on_ccb_release) cannot hit + * the abandon guard (!has_ccb && num_done == 0) and free the txn before + * the 0x18 response is sent. */ + txn->accepted[idx] = TRUE; + txn->num_done++; + l2c_ble_ecfc_open_ccb(p_ccb, L2CAP_CONN_OK); + /* open_ccb fires ConnectCfm synchronously; its handler may release this + * CCB and re-enter on_ccb_release -> try_complete, which frees the txn. + * Skip the second try_complete on a freed txn (mirrors the in_use guard + * in l2c_ble_ecfc_fire_connect_ind and l2c_ble_ecfc_sec_cback). */ + if (txn->in_use) { + l2c_ble_ecfc_srv_txn_try_complete(txn); + } + return; + } + + /* Record the app-supplied reject reason. A security-related code + * (0x0005-0x0008) takes precedence over a previously recorded generic code + * so the aggregate response conveys the strongest security requirement. */ + if (result != L2CAP_CONN_OK) { + BOOLEAN is_sec = (result >= L2CAP_LE_RESULT_INSUFFICIENT_AUTHENTICATION && + result <= L2CAP_LE_RESULT_INSUFFICIENT_ENCRY); + if (txn->reject_result == L2CAP_LE_RESULT_CONN_OK || is_sec) { + txn->reject_result = result; + } + } + + /* Reject: aggregate the 0x18 response before releasing the CCB. Pre-nulling + * ccbs[idx] and calling l2cu_release_ccb() first lets on_ccb_release() free the + * txn while num_done is still short, so try_complete() never reaches the peer. */ + txn->num_done++; + if (txn->num_done >= txn->num_total) { + l2c_ble_ecfc_srv_txn_try_complete(txn); + l2cu_release_ccb(p_ccb); + return; + } + + txn->ccbs[idx] = NULL; + l2cu_release_ccb(p_ccb); +} +#endif /* BLE_L2CAP_COC_SERVER_INCLUDED */ + +#if (BLE_L2CAP_COC_CLIENT_INCLUDED == TRUE) +UINT8 L2CA_ConnectLEEcocReq(UINT16 psm, BD_ADDR p_bd_addr, tL2CAP_LE_CFG_INFO *p_cfg, + UINT8 num_chan, UINT16 *p_lcids) +{ + tL2C_RCB *p_rcb; + tL2C_LCB *p_lcb; + tL2C_CCB *p_ccb; + tL2C_BLE_ECFC_CL_TXN *txn; + UINT16 scids[BLE_MAX_L2CAP_CLIENTS]; + UINT8 sig_id; + int i; + + if (num_chan == 0 || num_chan > BLE_MAX_L2CAP_CLIENTS || + num_chan > L2C_BLE_ECFC_MAX_REQ_CHANS) { + L2C_BLE_ECFC_TRACE_WARN("ConnectLEEcocReq invalid num_chan=%u", num_chan); + return 0; + } + + L2C_BLE_ECFC_TRACE_API("ConnectLEEcocReq psm=0x%04x num=%u", psm, num_chan); + + if (!BTM_IsDeviceUp()) { + L2C_BLE_ECFC_TRACE_WARN("ConnectLEEcocReq BTM not up psm=0x%04x", psm); + return 0; + } + + /* Bail out before allocating an LCB if BLE is unsupported: l2cu_create_conn()'s + * !supports_ble() path returns FALSE without releasing the LCB, so relying on + * it below would leak the freshly-allocated LCB (mirrors L2CA_ConnectLECocReq). */ + if (!controller_get_interface()->supports_ble()) { + L2C_BLE_ECFC_TRACE_WARN("ConnectLEEcocReq BLE not supported"); + return 0; + } + + p_rcb = l2cu_find_ble_rcb_by_psm(psm); + if (p_rcb == NULL) { + L2C_BLE_ECFC_TRACE_WARN("ConnectLEEcocReq no RCB for psm=0x%04x", psm); + return 0; + } + + p_lcb = l2cu_find_lcb_by_bd_addr(p_bd_addr, BT_TRANSPORT_LE); + if (p_lcb == NULL) { + p_lcb = l2cu_allocate_lcb(p_bd_addr, FALSE, BT_TRANSPORT_LE); + if (p_lcb == NULL || !l2cu_create_conn(p_lcb, BT_TRANSPORT_LE)) { + L2C_BLE_ECFC_TRACE_ERROR("ConnectLEEcocReq LCB alloc/create_conn failed"); + return 0; + } + } + + for (i = 0; i < num_chan; i++) { + p_ccb = l2cu_allocate_ccb(p_lcb, 0); + if (p_ccb == NULL) { + L2C_BLE_ECFC_TRACE_ERROR("ConnectLEEcocReq CCB alloc failed at chan %d", i); + while (--i >= 0) { + p_ccb = l2cu_find_ccb_by_cid(p_lcb, scids[i]); + if (p_ccb) { + l2cu_release_ccb(p_ccb); + } + } + return 0; + } + p_ccb->p_rcb = p_rcb; + p_ccb->le_coc_active = TRUE; + p_ccb->le_ecfc_channel = TRUE; + /* A pooled CCB reused from a released non-CoC channel keeps its stale + * remote_cid; clear it so the DCID/SCID dedup checks in + * l2c_ble_ecfc_handle_conn_res/handle_conn_req cannot false-match this + * channel-in-setup before its real remote_cid is assigned (mirrors + * L2CA_ConnectLECocReq). */ + p_ccb->remote_cid = 0; + if (p_cfg) { + memcpy(&p_ccb->local_conn_cfg, p_cfg, sizeof(tL2CAP_LE_CFG_INFO)); + } + l2c_ble_ecfc_apply_default_cfg(&p_ccb->local_conn_cfg); + scids[i] = p_ccb->local_cid; + if (p_lcids) { + p_lcids[i] = p_ccb->local_cid; + } + } + + p_lcb->id++; + l2cu_adj_id(p_lcb, L2CAP_ADJ_ID); + sig_id = p_lcb->id; + + txn = l2c_ble_ecfc_alloc_cl_txn(p_lcb, sig_id); + if (txn == NULL) { + L2C_BLE_ECFC_TRACE_WARN("ConnectLEEcocReq cl txn alloc failed sig_id=%u", sig_id); + for (i = 0; i < num_chan; i++) { + p_ccb = l2cu_find_ccb_by_cid(p_lcb, scids[i]); + if (p_ccb) { + l2cu_release_ccb(p_ccb); + } + } + return 0; + } + + txn->num_chan = num_chan; + for (i = 0; i < num_chan; i++) { + txn->ccbs[i] = l2cu_find_ccb_by_cid(p_lcb, scids[i]); + } + + if (p_lcb->link_state != LST_CONNECTED) { + if (p_lcb->link_state == LST_DISCONNECTING) { + L2C_BLE_ECFC_TRACE_WARN("ConnectLEEcocReq link disconnecting, abort sig_id=%u", sig_id); + l2c_ble_ecfc_cl_txn_free(txn); + for (i = 0; i < num_chan; i++) { + p_ccb = l2cu_find_ccb_by_cid(p_lcb, scids[i]); + if (p_ccb) { + l2cu_release_ccb(p_ccb); + } + } + return 0; + } + + txn->pending_link = TRUE; + L2C_BLE_ECFC_TRACE_DEBUG("ConnectLEEcocReq deferred until link up"); + return num_chan; + } + + if (!l2c_ble_ecfc_cl_send_connect(txn)) { + L2C_BLE_ECFC_TRACE_ERROR("ConnectLEEcocReq send_connect failed sig_id=%u", sig_id); + l2c_ble_ecfc_cl_txn_free(txn); + for (i = 0; i < num_chan; i++) { + p_ccb = l2cu_find_ccb_by_cid(p_lcb, scids[i]); + if (p_ccb) { + l2cu_release_ccb(p_ccb); + } + } + return 0; + } + + return num_chan; +} +#endif /* BLE_L2CAP_COC_CLIENT_INCLUDED */ + +BOOLEAN L2CA_LEEcocReconfig(UINT16 lcids[], UINT8 num, UINT16 new_mtu, UINT16 new_mps) +{ + tL2C_CCB *p_ccb; + tL2C_LCB *p_lcb = NULL; + tL2C_BLE_ECFC_RECONFIG_TXN *txn; + UINT16 dcids[BLE_MAX_L2CAP_CLIENTS]; + UINT8 sig_id; + int i; + + L2C_BLE_ECFC_TRACE_DEBUG("LEEcocReconfig num=%u new_mtu=%u new_mps=%u", num, new_mtu, new_mps); + + if (lcids == NULL || num == 0 || num > BLE_MAX_L2CAP_CLIENTS || + num > L2C_BLE_ECFC_MAX_REQ_CHANS || + new_mtu < L2CAP_LE_ECFC_MIN_MTU || new_mps < L2CAP_LE_ECFC_MIN_MPS || + new_mps > L2CAP_LE_MAX_MPS) { + L2C_BLE_ECFC_TRACE_WARN("LEEcocReconfig invalid params num=%u new_mtu=%u new_mps=%u", + num, new_mtu, new_mps); + return FALSE; + } + + for (i = 0; i < num; i++) { + /* Reject a request that lists the same LCID more than once: a duplicate + * resolves to the same CCB, so txn->ccbs[] would store it twice and the + * 0x1A response would apply/notify the reconfigure on one channel twice. + * Mirrors the seen_dcids[] dedup in l2c_ble_ecfc_handle_reconfig_req. + * num <= 5, so the O(n^2) scan is cheap. */ + for (int j = 0; j < i; j++) { + if (lcids[j] == lcids[i]) { + L2C_BLE_ECFC_TRACE_WARN("LEEcocReconfig duplicate lcid=0x%04x", lcids[i]); + return FALSE; + } + } + p_ccb = l2cu_find_ccb_by_cid(NULL, lcids[i]); + if (p_ccb == NULL || !p_ccb->le_coc_active || p_ccb->chnl_state != CST_OPEN) { + L2C_BLE_ECFC_TRACE_WARN("LEEcocReconfig invalid ccb/not open lcid=0x%04x", lcids[i]); + return FALSE; + } + /* Reconfiguration (0x19) is only defined for ECFC channels. Reject an + * attempt to reconfigure a base LE CoC channel, which cannot carry the + * enhanced reconfig request. */ + if (!p_ccb->le_ecfc_channel) { + L2C_BLE_ECFC_TRACE_WARN("LEEcocReconfig not an ECFC channel lcid=0x%04x", lcids[i]); + return FALSE; + } + if (p_lcb == NULL) { + p_lcb = p_ccb->p_lcb; + } else if (p_ccb->p_lcb != p_lcb) { + L2C_BLE_ECFC_TRACE_WARN("LEEcocReconfig lcid=0x%04x on different link", lcids[i]); + return FALSE; + } + if (p_ccb->local_conn_cfg.mtu > new_mtu) { + L2C_BLE_ECFC_TRACE_WARN("LEEcocReconfig MTU reduction not allowed lcid=0x%04x cur=%u new=%u", + lcids[i], p_ccb->local_conn_cfg.mtu, new_mtu); + return FALSE; + } + /* Core Spec v6.2 Vol 3 Part A 4.27: when more than one channel is + * reconfigured, the new MPS must be >= the current MPS of each channel. + * Reject up front instead of sending a request the peer will refuse + * (result 0x0002) while we wrongly report success. */ + if (num > 1 && p_ccb->local_conn_cfg.mps > new_mps) { + L2C_BLE_ECFC_TRACE_WARN("LEEcocReconfig multi-chan MPS reduction not allowed lcid=0x%04x cur=%u new=%u", + lcids[i], p_ccb->local_conn_cfg.mps, new_mps); + return FALSE; + } + dcids[i] = p_ccb->local_cid; + } + + p_lcb->id++; + l2cu_adj_id(p_lcb, L2CAP_ADJ_ID); + sig_id = p_lcb->id; + + txn = l2c_ble_ecfc_alloc_reconfig_txn(p_lcb, sig_id); + if (txn == NULL) { + L2C_BLE_ECFC_TRACE_WARN("LEEcocReconfig reconfig txn alloc failed sig_id=%u", sig_id); + return FALSE; + } + + txn->num_chan = num; + txn->new_mtu = new_mtu; + txn->new_mps = new_mps; + for (i = 0; i < num; i++) { + txn->ccbs[i] = l2cu_find_ccb_by_cid(p_lcb, lcids[i]); + } + + /* If the request cannot be sent (e.g. buffer OOM) the peer never answers, so + * release the txn slot instead of leaking it and reporting success. */ + if (!l2cu_send_peer_ble_credit_reconfig_req(p_lcb, sig_id, new_mtu, new_mps, num, dcids)) { + L2C_BLE_ECFC_TRACE_ERROR("LEEcocReconfig 0x19 send failed sig_id=%u", sig_id); + memset(txn, 0, sizeof(*txn)); + return FALSE; + } + + /* Guard against a peer that never sends the 0x1A response: one timer per + * transaction (on ccbs[0]) aborts the reconfigure on expiry. Channels stay + * OPEN, so this timer is stopped explicitly on completion/abort. */ + if (txn->ccbs[0]) { + l2c_ble_le_coc_start_rsp_timer(txn->ccbs[0], L2CAP_CHNL_CONNECT_TOUT); + } + L2C_BLE_ECFC_TRACE_DEBUG("LEEcocReconfig 0x19 sent sig_id=%u num=%u", sig_id, num); + return TRUE; +} + +#if (BLE_L2CAP_COC_SERVER_INCLUDED == TRUE) +void l2c_ble_ecfc_handle_conn_req(tL2C_LCB *p_lcb, UINT8 *p, UINT8 id, UINT16 cmd_len) +{ + tL2C_RCB *p_rcb; + tL2C_CCB *p_ccb; + tL2C_BLE_ECFC_SRV_TXN *txn; + UINT16 spsm, mtu, mps, credits, scid; + UINT16 n_scids; + UINT8 num_scids; + int i; + + if (cmd_len < L2CAP_CMD_BLE_ENHANCED_CONN_REQ_BASE_LEN + sizeof(UINT16)) { + L2C_BLE_ECFC_TRACE_WARN("reject 0x17 short cmd len=%u id=%u", cmd_len, id); + l2cu_reject_ble_enhanced_connection(p_lcb, id, L2CAP_LE_RESULT_INVALID_PARAMETERS, 1); + return; + } + + STREAM_TO_UINT16(spsm, p); + STREAM_TO_UINT16(mtu, p); + STREAM_TO_UINT16(mps, p); + STREAM_TO_UINT16(credits, p); + + /* Compute the SCID count in a wide type first: (cmd_len - base)/2 can exceed + * 255 for an oversized packet and would truncate if assigned to a UINT8 + * before the upper-bound check, letting a malformed request slip through. */ + n_scids = (UINT16)((cmd_len - L2CAP_CMD_BLE_ENHANCED_CONN_REQ_BASE_LEN) / sizeof(UINT16)); + /* Core Spec v6.2 Vol 3 Part A 4.25 caps a single request at 5 source CIDs. + * BLE_MAX_L2CAP_CLIENTS may be configured up to 15, so enforce the 5 + * spec limit here too (matches handle_reconfig_req / L2CA_ConnectLEEcocReq). */ + /* The reject response must echo one DCID per requested SCID (Core Spec v6.2 + * Vol 3 Part A 4.26: 1:1 positional mapping), so pass the actual n_scids as + * the count rather than clamping it to the local channel budget. Cap the + * count at 255 only to fit the UINT8 API argument: n_scids = (cmd_len-8)/2 + * can exceed 255 for an oversized/malformed packet, and a bare (UINT8) cast + * would truncate it (e.g. 256 -> 0, then promoted to 1). */ + if (n_scids == 0) { + L2C_BLE_ECFC_TRACE_WARN("reject 0x17 n_scids=0 id=%u", id); + l2cu_reject_ble_enhanced_connection(p_lcb, id, L2CAP_LE_RESULT_INVALID_PARAMETERS, 1); + return; + } + UINT8 reject_scids = (n_scids > 255) ? 255 : (UINT8)n_scids; + if (n_scids > L2C_BLE_ECFC_MAX_REQ_CHANS) { + /* More SCIDs than the spec's per-request maximum of 5: malformed. */ + L2C_BLE_ECFC_TRACE_WARN("reject 0x17 n_scids=%u > max id=%u", n_scids, id); + l2cu_reject_ble_enhanced_connection(p_lcb, id, L2CAP_LE_RESULT_INVALID_PARAMETERS, + reject_scids); + return; + } + if (n_scids > BLE_MAX_L2CAP_CLIENTS) { + /* Spec-valid count but exceeds our local channel budget: resource limit. */ + L2C_BLE_ECFC_TRACE_WARN("reject 0x17 n_scids=%u over budget id=%u", n_scids, id); + l2cu_reject_ble_enhanced_connection(p_lcb, id, L2CAP_LE_RESULT_NO_RESOURCES, + reject_scids); + return; + } + num_scids = (UINT8)n_scids; + + /* Defensive: reject an initial credit value of 0 for the enhanced + * credit-based (0x17) request. NOTE: confirm against Core Spec v6.2 4.25 + * whether 0 is strictly illegal for 0x17; regardless, seeding a channel + * with 0 TX credits leaves it unusable until the peer later grants credit, + * so rejecting up front is the safer behaviour. */ + if (mtu < L2CAP_LE_ECFC_MIN_MTU || mps < L2CAP_LE_ECFC_MIN_MPS || + mps > L2CAP_LE_MAX_MPS || credits == 0) { + L2C_BLE_ECFC_TRACE_WARN("reject 0x17 invalid mtu=%u mps=%u cred=%u id=%u", mtu, mps, credits, id); + l2cu_reject_ble_enhanced_connection(p_lcb, id, L2CAP_LE_RESULT_INVALID_PARAMETERS, num_scids); + return; + } + + L2C_BLE_ECFC_TRACE_API("rx 0x17 spsm=0x%04x mtu=%u mps=%u cred=%u n=%u id=%u", + spsm, mtu, mps, credits, num_scids, id); + + p_rcb = l2cu_find_ble_rcb_by_psm(spsm); + if (p_rcb == NULL) { + L2C_BLE_ECFC_TRACE_WARN("reject 0x17 no PSM spsm=0x%04x id=%u", spsm, id); + l2cu_reject_ble_enhanced_connection(p_lcb, id, L2CAP_LE_RESULT_NO_PSM, num_scids); + return; + } + + txn = l2c_ble_ecfc_alloc_srv_txn(p_lcb, id); + if (txn == NULL) { + L2C_BLE_ECFC_TRACE_ERROR("reject 0x17 srv txn alloc failed id=%u", id); + l2cu_reject_ble_enhanced_connection(p_lcb, id, L2CAP_LE_RESULT_NO_RESOURCES, num_scids); + return; + } + + txn->num_total = num_scids; + + for (i = 0; i < num_scids; i++) { + STREAM_TO_UINT16(scid, p); + /* Each SCID must be a peer dynamic LE-U CID (0x0040-0x007F). Reject a + * fixed/invalid CID (e.g. 0x0004 ATT) so we never target it with + * K-frames (Core Spec v6.2 Vol 3 Part A 4.25 / result 0x0009). */ + if (scid < L2CAP_BASE_APPL_CID || scid > L2CAP_BLE_CONN_MAX_CID) { + L2C_BLE_ECFC_TRACE_WARN("reject 0x17 invalid scid=0x%04x id=%u", scid, id); + l2cu_reject_ble_enhanced_connection(p_lcb, id, L2CAP_LE_RESULT_INVALID_SOURCE_CID, num_scids); + for (int j = 0; j < i; j++) { + if (txn->ccbs[j]) { + tL2C_CCB *p_rel = txn->ccbs[j]; + txn->ccbs[j] = NULL; + l2cu_release_ccb(p_rel); + } + } + if (txn->in_use) { + l2c_ble_ecfc_srv_txn_free(txn); + } + return; + } + if (l2cu_find_ccb_by_remote_cid(p_lcb, scid)) { + L2C_BLE_ECFC_TRACE_WARN("reject 0x17 duplicate scid=0x%04x id=%u", scid, id); + l2cu_reject_ble_enhanced_connection(p_lcb, id, L2CAP_LE_RESULT_SOURCE_CID_ALREADY_ALLOCATED, num_scids); + for (int j = 0; j < i; j++) { + if (txn->ccbs[j]) { + tL2C_CCB *p_rel = txn->ccbs[j]; + txn->ccbs[j] = NULL; + l2cu_release_ccb(p_rel); + } + } + if (txn->in_use) { + l2c_ble_ecfc_srv_txn_free(txn); + } + return; + } + + p_ccb = l2cu_allocate_ccb(p_lcb, 0); + if (p_ccb == NULL) { + L2C_BLE_ECFC_TRACE_ERROR("reject 0x17 CCB alloc failed id=%u chan=%d", id, i); + l2cu_reject_ble_enhanced_connection(p_lcb, id, L2CAP_LE_RESULT_NO_RESOURCES, num_scids); + for (int j = 0; j < i; j++) { + if (txn->ccbs[j]) { + tL2C_CCB *p_rel = txn->ccbs[j]; + txn->ccbs[j] = NULL; + l2cu_release_ccb(p_rel); + } + } + if (txn->in_use) { + l2c_ble_ecfc_srv_txn_free(txn); + } + return; + } + + p_ccb->le_coc_active = TRUE; + p_ccb->le_ecfc_channel = TRUE; + p_ccb->remote_id = id; + p_ccb->p_rcb = p_rcb; + p_ccb->remote_cid = scid; + p_ccb->peer_conn_cfg.mtu = mtu; + p_ccb->peer_conn_cfg.mps = mps; + p_ccb->peer_conn_cfg.credits = credits; + l2c_ble_ecfc_apply_default_cfg(&p_ccb->local_conn_cfg); + txn->ccbs[i] = p_ccb; + } + + txn->rsp_credits = txn->ccbs[0]->local_conn_cfg.credits; + txn->rsp_mtu = txn->ccbs[0]->local_conn_cfg.mtu; + txn->rsp_mps = txn->ccbs[0]->local_conn_cfg.mps; + + /* Move every channel in the batch (not just ccbs[0]) into the security-wait + * state so link/CSM lookups and cleanup treat them consistently while the + * single security check for this connection request is in flight. */ + for (i = 0; i < num_scids; i++) { + txn->ccbs[i]->chnl_state = CST_TERM_W4_SEC_COMP; + } + + p_ccb = txn->ccbs[0]; + (void)p_ccb; +#if (SMP_INCLUDED == TRUE) + l2ble_sec_access_req(p_lcb->remote_bd_addr, p_rcb->real_psm, FALSE, + l2c_ble_ecfc_sec_cback, txn); +#else + /* SMP disabled: no security procedure to run, so complete the access check + * immediately (l2ble_sec_access_req is only compiled with SMP). */ + l2c_ble_ecfc_sec_cback(p_lcb->remote_bd_addr, BT_TRANSPORT_LE, txn, BTM_SUCCESS); +#endif +} +#endif /* BLE_L2CAP_COC_SERVER_INCLUDED */ + +#if (BLE_L2CAP_COC_CLIENT_INCLUDED == TRUE) +void l2c_ble_ecfc_abort_cl_txn(tL2C_LCB *p_lcb, UINT8 sig_id, UINT16 result) +{ + tL2C_BLE_ECFC_CL_TXN *txn = l2c_ble_ecfc_find_cl_txn(p_lcb, sig_id); + + if (txn == NULL) { + /* Common no-op: a CMD_REJECT that does not target a pending ECFC client + * transaction (it may be for a base CoC or reconfigure request). */ + return; + } + + L2C_BLE_ECFC_TRACE_WARN("abort cl txn sig_id=%u result=%u", sig_id, result); + + /* Snapshot the CCBs and free the txn slot BEFORE opening any CCB. A non-OK + * open releases the CCB, which re-enters l2c_ble_ecfc_on_ccb_release() and may + * free this txn once no CCBs remain. Working from a local copy means neither + * the loop condition nor the trailing free can read or corrupt a slot that + * was reallocated during the callback chain. */ + tL2C_CCB *ccbs[BLE_MAX_L2CAP_CLIENTS]; + UINT8 num_chan = txn->num_chan; + if (num_chan > BLE_MAX_L2CAP_CLIENTS) { + num_chan = BLE_MAX_L2CAP_CLIENTS; + } + memcpy(ccbs, txn->ccbs, num_chan * sizeof(tL2C_CCB *)); + l2c_ble_ecfc_cl_txn_free(txn); + + for (int i = 0; i < num_chan; i++) { + if (ccbs[i]) { + l2c_ble_ecfc_open_ccb(ccbs[i], result); + } + } +} + +void l2c_ble_ecfc_handle_conn_res(tL2C_LCB *p_lcb, UINT8 *p, UINT8 id, UINT16 cmd_len) +{ + tL2C_BLE_ECFC_CL_TXN *txn; + UINT16 mtu, mps, credits, result, dcid; + UINT16 n_dcids; + int i; + + txn = l2c_ble_ecfc_find_cl_txn(p_lcb, id); + if (txn == NULL) { + L2C_BLE_ECFC_TRACE_WARN("0x18 unknown id=%u", id); + return; + } + + /* Response received: cancel the connect-response timeout (armed on ccbs[0]). */ + if (txn->ccbs[0]) { + l2c_ble_le_coc_stop_rsp_timer(txn->ccbs[0]); + } + + /* Snapshot the CCBs and free the txn BEFORE opening any channel. A non-OK + * open releases the CCB and re-enters l2c_ble_ecfc_on_ccb_release(), which + * frees this txn once its last CCB is gone; a synchronous ConnectCfm callback + * (e.g. EATT) could then reallocate the same slot. Working from a local copy + * keeps the loop bound, the stream parsing and the field writes from touching + * a reused txn, matching l2c_ble_ecfc_abort_cl_txn / l2c_ble_ecfc_on_link_up. */ + tL2C_CCB *ccbs[BLE_MAX_L2CAP_CLIENTS]; + UINT8 num_chan = txn->num_chan; + if (num_chan > BLE_MAX_L2CAP_CLIENTS) { + num_chan = BLE_MAX_L2CAP_CLIENTS; + } + memcpy(ccbs, txn->ccbs, num_chan * sizeof(tL2C_CCB *)); + + if (cmd_len < L2CAP_CMD_BLE_ENHANCED_CONN_RES_BASE_LEN) { + L2C_BLE_ECFC_TRACE_WARN("0x18 short cmd len=%u", cmd_len); + l2c_ble_ecfc_cl_txn_free(txn); + for (i = 0; i < num_chan; i++) { + if (ccbs[i]) { + l2c_ble_ecfc_open_ccb(ccbs[i], L2CAP_CONN_NO_PSM); + } + } + return; + } + + STREAM_TO_UINT16(mtu, p); + STREAM_TO_UINT16(mps, p); + STREAM_TO_UINT16(credits, p); + STREAM_TO_UINT16(result, p); + + /* Compute the DCID count in a wide type first: (cmd_len - base)/2 can exceed + * 255 for an oversized packet and would truncate if assigned to a UINT8 + * before the count check, letting a malformed response slip through. Mirrors + * the request handler (l2c_ble_ecfc_handle_conn_req). */ + n_dcids = (UINT16)((cmd_len - L2CAP_CMD_BLE_ENHANCED_CONN_RES_BASE_LEN) / sizeof(UINT16)); + L2C_BLE_ECFC_TRACE_API("rx 0x18 id=%u mtu=%u mps=%u cred=%u result=%u n=%u", + id, mtu, mps, credits, result, n_dcids); + + if (n_dcids != num_chan) { + L2C_BLE_ECFC_TRACE_WARN("0x18 dcid count mismatch n=%u txn=%u", n_dcids, num_chan); + l2c_ble_ecfc_cl_txn_free(txn); + for (i = 0; i < num_chan; i++) { + if (ccbs[i]) { + l2c_ble_ecfc_open_ccb(ccbs[i], L2CAP_CONN_NO_PSM); + } + } + return; + } + + /* Validate the peer's common MTU/MPS/credits once. An mps of 0 would later + * be used as a divisor when fragmenting SDUs (divide-by-zero); mtu/credits of + * 0 leave the channel unusable while being reported as opened (Core Spec v6.2 + * Vol 3 Part A 4.26). The check is applied per accepted channel (dcid != 0) + * inside the loop below rather than up front, so that on an "all connections + * refused" result — where MTU/MPS/Credits shall be ignored and every DCID is + * zero — the loop still forwards the real reject reason to the app instead of + * masking every channel with L2CAP_CONN_NO_PSM. */ + BOOLEAN params_valid = !(mtu < L2CAP_LE_ECFC_MIN_MTU || mps < L2CAP_LE_ECFC_MIN_MPS || + mps > L2CAP_LE_MAX_MPS || credits == 0); + + /* Everything needed from txn has been read; free it now so the open_ccb calls + * below cannot trigger a re-entrant free of a slot we still read. */ + l2c_ble_ecfc_cl_txn_free(txn); + + for (i = 0; i < num_chan; i++) { + tL2C_CCB *p_ccb; + + /* Each DCID in the response maps 1:1 to the SCID at the same index in + * the request (Core Spec 4.26). Consume the stream entry before checking + * ccbs[i]: a NULL slot (CCB released while the txn was pending) must + * still advance p past its DCID. */ + STREAM_TO_UINT16(dcid, p); + + p_ccb = ccbs[i]; + if (p_ccb == NULL) { + continue; + } + + /* Per Core Spec v6.2 Vol 3 Part A 4.26, on a "some connections refused" + * result each channel is accepted iff its DCID is non-zero; a zero DCID + * marks that individual channel as refused. Do not tear down channels + * with a valid DCID just because the aggregate result is not CONN_OK. */ + if (dcid == 0) { + UINT16 fail_result = (result != L2CAP_LE_RESULT_CONN_OK) ? result : L2CAP_CONN_NO_PSM; + l2c_ble_ecfc_open_ccb(p_ccb, fail_result); + continue; + } + + /* A non-zero DCID must be a peer dynamic LE-U CID (0x0040-0x007F) and + * unique on this link; otherwise our outgoing K-frames would target an + * invalid/duplicate peer CID (matches the 0x15 DCID check). Validate it + * (and record remote_cid) BEFORE the params check so a channel the peer + * accepted can be torn down on the air. An invalid/duplicate DCID cannot + * be cleanly disconnected (no valid target, and a duplicate would drop + * the wrong channel), so that case just drops our CCB. */ + if (dcid < L2CAP_BASE_APPL_CID || dcid > L2CAP_BLE_CONN_MAX_CID || + l2cu_find_ccb_by_remote_cid(p_lcb, dcid)) { + L2C_BLE_ECFC_TRACE_WARN("0x18 invalid/duplicate dcid=0x%04x lcid=0x%04x", + dcid, p_ccb->local_cid); + l2c_ble_ecfc_open_ccb(p_ccb, L2CAP_CONN_NO_PSM); + continue; + } + p_ccb->remote_cid = dcid; + + /* A channel the peer accepted (valid non-zero DCID) must carry usable + * common parameters; if the shared MTU/MPS/credits are invalid the peer + * still has an open channel, so tear it down on the air (best-effort + * DISC_REQ) before dropping our CCB rather than leaving it orphaned. */ + if (!params_valid) { + L2C_BLE_ECFC_TRACE_WARN("0x18 invalid peer params mtu=%u mps=%u cred=%u", mtu, mps, credits); + l2cble_send_peer_disc_req(p_ccb); + l2c_ble_ecfc_open_ccb(p_ccb, L2CAP_CONN_NO_PSM); + continue; + } + + p_ccb->peer_conn_cfg.mtu = mtu; + p_ccb->peer_conn_cfg.mps = mps; + p_ccb->peer_conn_cfg.credits = credits; + l2c_ble_ecfc_open_ccb(p_ccb, L2CAP_CONN_OK); + } +} +#endif /* BLE_L2CAP_COC_CLIENT_INCLUDED */ + +void l2c_ble_ecfc_handle_reconfig_req(tL2C_LCB *p_lcb, UINT8 *p, UINT8 id, UINT16 cmd_len) +{ + UINT8 *p_start = p; + UINT16 mtu, mps, dcid; + UINT16 n_dcids; + UINT8 num_dcids; + tL2C_CCB *p_ccb; + UINT8 reduction_mps = 0; + UINT16 seen_dcids[BLE_MAX_L2CAP_CLIENTS]; + int i; + + L2C_BLE_ECFC_TRACE_DEBUG("rx 0x19 id=%u cmd_len=%u", id, cmd_len); + + if (cmd_len < L2CAP_CMD_BLE_CREDIT_RECONFIG_REQ_BASE_LEN + sizeof(UINT16)) { + L2C_BLE_ECFC_TRACE_WARN("reject 0x19 short cmd len=%u id=%u", cmd_len, id); + l2cu_send_peer_ble_credit_reconfig_rsp(p_lcb, id, L2CAP_LE_RECONFIG_UNACCEPTED_PARAM); + return; + } + + STREAM_TO_UINT16(mtu, p); + STREAM_TO_UINT16(mps, p); + /* Wide-type count first to avoid UINT8 truncation on an oversized packet, + * then enforce the spec upper bound of 5 DCIDs (Core Spec v6.2 Vol 3 + * Part A 4.27). */ + n_dcids = (UINT16)((cmd_len - L2CAP_CMD_BLE_CREDIT_RECONFIG_REQ_BASE_LEN) / sizeof(UINT16)); + + if (mtu < L2CAP_LE_ECFC_MIN_MTU || mps < L2CAP_LE_ECFC_MIN_MPS || + mps > L2CAP_LE_MAX_MPS || n_dcids == 0 || + n_dcids > BLE_MAX_L2CAP_CLIENTS || n_dcids > L2C_BLE_ECFC_MAX_REQ_CHANS) { + L2C_BLE_ECFC_TRACE_WARN("reject 0x19 invalid mtu=%u mps=%u n_dcids=%u id=%u", + mtu, mps, n_dcids, id); + l2cu_send_peer_ble_credit_reconfig_rsp(p_lcb, id, L2CAP_LE_RECONFIG_UNACCEPTED_PARAM); + return; + } + num_dcids = (UINT8)n_dcids; + + for (i = 0; i < num_dcids; i++) { + STREAM_TO_UINT16(dcid, p); + /* Reject a request that lists the same DCID more than once. Each DCID + * "shall be non-zero and represent channel endpoints" (Core Spec v6.2 + * Vol 3 Part A 4.27); a duplicate is malformed and, if let through, would + * update the same CCB and fire notify_reconfig twice. n <= 5, so an O(n^2) + * scan of the DCIDs already parsed is cheap. */ + for (int j = 0; j < i; j++) { + if (seen_dcids[j] == dcid) { + L2C_BLE_ECFC_TRACE_WARN("reject 0x19 duplicate dcid=0x%04x id=%u", dcid, id); + l2cu_send_peer_ble_credit_reconfig_rsp(p_lcb, id, L2CAP_LE_RECONFIG_INVALID_DCID); + return; + } + } + seen_dcids[i] = dcid; + /* The Destination CID array in a reconfigure request holds the peer's + * local CIDs (Core Spec v6.2 Vol 3 Part A 4.27), which map to our + * remote_cid, not our local_cid. */ + p_ccb = l2cu_find_ccb_by_remote_cid(p_lcb, dcid); + /* Reconfiguration (0x19) is only defined for an established ECFC channel + * (Core Spec v6.2 Vol 3 Part A 4.27). Reject a request that targets a + * base LE CoC channel (le_coc_active but !le_ecfc_channel) or a channel + * not yet fully open, mirroring the local L2CA_LEEcocReconfig checks. */ + if (p_ccb == NULL || !p_ccb->le_coc_active || !p_ccb->le_ecfc_channel || + p_ccb->chnl_state != CST_OPEN) { + L2C_BLE_ECFC_TRACE_WARN("reject 0x19 invalid dcid=0x%04x id=%u", dcid, id); + l2cu_send_peer_ble_credit_reconfig_rsp(p_lcb, id, L2CAP_LE_RECONFIG_INVALID_DCID); + return; + } + /* A reconfigure request changes the requester's (peer's) receive + * MTU/MPS, i.e. our peer_conn_cfg. Per spec the new MTU must not be + * smaller than the peer's previously agreed MTU. Compare against + * peer_conn_cfg.mtu (matches the peer_conn_cfg.mps check below), not + * our own local RX MTU. */ + if (p_ccb->peer_conn_cfg.mtu > mtu) { + L2C_BLE_ECFC_TRACE_WARN("reject 0x19 MTU reduction not allowed dcid=0x%04x cur=%u new=%u id=%u", + dcid, p_ccb->peer_conn_cfg.mtu, mtu, id); + l2cu_send_peer_ble_credit_reconfig_rsp(p_lcb, id, L2CAP_LE_RECONFIG_REDUCTION_MTU_NOT_ALLOWED); + return; + } + if (p_ccb->peer_conn_cfg.mps > mps) { + reduction_mps++; + } + } + + if (reduction_mps > 0 && num_dcids > 1) { + L2C_BLE_ECFC_TRACE_WARN("reject 0x19 multi-chan MPS reduction not allowed n=%u id=%u", + num_dcids, id); + l2cu_send_peer_ble_credit_reconfig_rsp(p_lcb, id, L2CAP_LE_RECONFIG_REDUCTION_MPS_NOT_ALLOWED); + return; + } + + /* Core Spec v6.2 Vol 3 Part A 7.11 mandates the ordering: finish sending any + * existing PDUs that need the old (larger) MPS, THEN send the + * L2CAP_CREDIT_BASED_RECONFIGURE_RSP, and only afterwards adopt the new + * MTU/MPS for subsequent SDUs. Send the response before updating the local + * peer_conn_cfg / notifying the upper layer, otherwise a data write driven + * synchronously from the notify callback would fragment SDUs with the new + * (possibly smaller) MPS before the peer has seen the confirming response. */ + l2cu_send_peer_ble_credit_reconfig_rsp(p_lcb, id, L2CAP_LE_RECONFIG_OK); + L2C_BLE_ECFC_TRACE_DEBUG("0x1A OK id=%u mtu=%u mps=%u num_dcids=%u", id, mtu, mps, num_dcids); + + p = p_start + sizeof(UINT16) + sizeof(UINT16); + for (i = 0; i < num_dcids; i++) { + STREAM_TO_UINT16(dcid, p); + p_ccb = l2cu_find_ccb_by_remote_cid(p_lcb, dcid); + if (p_ccb) { + p_ccb->peer_conn_cfg.mtu = mtu; + p_ccb->peer_conn_cfg.mps = mps; + l2c_ble_le_coc_notify_reconfig(p_ccb, L2CAP_LE_RECONFIG_OK, TRUE); +#if (BLE_EATT_INCLUDED == TRUE) + gatt_eatt_on_chan_mtu_changed(p_lcb->remote_bd_addr, p_ccb->local_cid); +#endif + } + } +} + +void l2c_ble_ecfc_handle_reconfig_res(tL2C_LCB *p_lcb, UINT8 *p, UINT8 id, UINT16 cmd_len) +{ + tL2C_BLE_ECFC_RECONFIG_TXN *txn; + UINT16 result; + int i; + + tL2C_CCB *ccbs[BLE_MAX_L2CAP_CLIENTS]; + UINT8 num_chan; + UINT16 new_mtu, new_mps; + + txn = l2c_ble_ecfc_find_reconfig_txn(p_lcb, id); + if (txn == NULL) { + L2C_BLE_ECFC_TRACE_WARN("0x1A unknown id=%u", id); + return; + } + + /* Response received: cancel the reconfigure-response timeout (armed on + * ccbs[0]); channels remain OPEN so the timer is not freed by release. */ + if (txn->ccbs[0]) { + l2c_ble_le_coc_stop_rsp_timer(txn->ccbs[0]); + } + + /* Snapshot the CCBs and reconfigure params, then free the txn slot BEFORE + * invoking any user callback. l2c_ble_le_coc_notify_reconfig() calls + * pL2CA_LeReconfigInd_Cb, whose handler may synchronously disconnect a + * channel; that re-enters l2c_ble_ecfc_on_ccb_release(), which memset-frees + * this reconfig txn once its last CCB is gone, and a re-entrant reconfigure + * could then reallocate the slot. Working from a local copy keeps the loop + * and the trailing writes from reading/corrupting a reused slot (matches + * l2c_ble_ecfc_abort_cl_txn / l2c_ble_ecfc_handle_conn_res). + * + * Residual (theoretical only): the snapshot could still hold a CCB that a + * callback releases mid-loop, so a later apply/notify would touch freed + * memory. This is the same edge already accepted for l2c_ble_ecfc_handle_conn_res + * and is not reachable today: the only pL2CA_LeReconfigInd_Cb registrant is + * btc_ble_l2cap_reconfig_ind(), which merely posts an async event (no + * synchronous l2cu_release_ccb), and EATT registers no reconfig callback. */ + num_chan = txn->num_chan; + if (num_chan > BLE_MAX_L2CAP_CLIENTS) { + num_chan = BLE_MAX_L2CAP_CLIENTS; + } + memcpy(ccbs, txn->ccbs, num_chan * sizeof(tL2C_CCB *)); + new_mtu = txn->new_mtu; + new_mps = txn->new_mps; + + /* A malformed/short response still terminates this pending reconfigure, so + * the txn must be released here (it was already looked up) or it leaks. */ + if (cmd_len < L2CAP_CMD_BLE_CREDIT_RECONFIG_RSP_LEN) { + L2C_BLE_ECFC_TRACE_WARN("0x1A short cmd len=%u id=%u, notify unaccepted", cmd_len, id); + memset(txn, 0, sizeof(*txn)); + for (i = 0; i < num_chan; i++) { + if (ccbs[i]) { + l2c_ble_le_coc_notify_reconfig(ccbs[i], L2CAP_LE_RECONFIG_UNACCEPTED_PARAM, FALSE); + } + } + return; + } + + STREAM_TO_UINT16(result, p); + + memset(txn, 0, sizeof(*txn)); + + if (result == L2CAP_LE_RECONFIG_OK) { + L2C_BLE_ECFC_TRACE_DEBUG("0x1A reconfig ok id=%u num_chan=%u new_mtu=%u new_mps=%u", + id, num_chan, new_mtu, new_mps); + for (i = 0; i < num_chan; i++) { + if (ccbs[i]) { + l2c_ble_le_coc_apply_reconfig(ccbs[i], new_mtu, new_mps); + l2c_ble_le_coc_notify_reconfig(ccbs[i], L2CAP_LE_RECONFIG_OK, FALSE); + } + } + } else { + L2C_BLE_ECFC_TRACE_WARN("0x1A reconfig failed id=%u result=%u", id, result); + for (i = 0; i < num_chan; i++) { + if (ccbs[i]) { + l2c_ble_le_coc_notify_reconfig(ccbs[i], result, FALSE); + } + } + } +} + +void l2c_ble_ecfc_abort_reconfig_txn(tL2C_LCB *p_lcb, UINT8 sig_id) +{ + tL2C_BLE_ECFC_RECONFIG_TXN *txn = l2c_ble_ecfc_find_reconfig_txn(p_lcb, sig_id); + tL2C_CCB *ccbs[BLE_MAX_L2CAP_CLIENTS]; + UINT8 num_chan; + int i; + + if (txn == NULL) { + /* Common no-op: a CMD_REJECT that does not target a pending reconfigure. */ + return; + } + + L2C_BLE_ECFC_TRACE_WARN("abort reconfig txn sig_id=%u", sig_id); + + /* Peer rejected the reconfigure request with L2CAP_CMD_REJECT instead of a + * 0x1A response, so no reconfig response will ever arrive. Snapshot the + * CCBs and free the txn slot BEFORE notifying: l2c_ble_le_coc_notify_reconfig + * calls pL2CA_LeReconfigInd_Cb, whose handler may synchronously disconnect a + * channel and re-enter l2c_ble_ecfc_on_ccb_release() (which memset-frees this + * txn once its last CCB is gone), after which a re-entrant reconfigure could + * reallocate the slot. Working from a local copy avoids corrupting a reused + * slot (matches l2c_ble_ecfc_abort_cl_txn / l2c_ble_ecfc_handle_reconfig_res). + * + * Residual (theoretical only): the snapshot could still hold a CCB that a + * notify callback releases mid-loop, so a later stop_timer/notify would touch + * freed memory. Same edge already accepted for l2c_ble_ecfc_handle_conn_res, + * and not reachable today: the only pL2CA_LeReconfigInd_Cb registrant + * (btc_ble_l2cap_reconfig_ind) posts an async event without a synchronous + * l2cu_release_ccb, and EATT registers no reconfig callback. */ + num_chan = txn->num_chan; + if (num_chan > BLE_MAX_L2CAP_CLIENTS) { + num_chan = BLE_MAX_L2CAP_CLIENTS; + } + memcpy(ccbs, txn->ccbs, num_chan * sizeof(tL2C_CCB *)); + memset(txn, 0, sizeof(*txn)); + + /* The reconfigure-response timer is armed only on ccbs[0] (see + * L2CA_LEEcocReconfig), so stop it there and nowhere else. Do NOT stop it + * per-CCB: another channel in this txn may have independently armed a + * disconnect RTX timer on the same timer_entry (l2c_ble_le_coc_initiate_disc); + * cancelling that would strand the channel in CST_W4_L2CAP_DISCONNECT_RSP + * with no timeout. Channels stay OPEN through a reconfigure, so release does + * not run here. Mirrors l2c_ble_ecfc_handle_reconfig_res. */ + if (ccbs[0]) { + l2c_ble_le_coc_stop_rsp_timer(ccbs[0]); + } + + for (i = 0; i < num_chan; i++) { + if (ccbs[i]) { + l2c_ble_le_coc_notify_reconfig(ccbs[i], L2CAP_LE_RECONFIG_UNACCEPTED_PARAM, FALSE); + } + } +} + +#if (BLE_L2CAP_COC_CLIENT_INCLUDED == TRUE) +BOOLEAN l2c_ble_ecfc_on_conn_timeout(tL2C_CCB *p_ccb) +{ + int i, j; + + for (i = 0; i < MAX_L2CAP_LINKS; i++) { + tL2C_BLE_ECFC_CL_TXN *txn = &s_ecfc_cl_txn[i]; + if (!txn->in_use) { + continue; + } + for (j = 0; j < txn->num_chan; j++) { + if (txn->ccbs[j] == p_ccb) { + L2C_BLE_ECFC_TRACE_WARN("0x18 timeout sig_id=%u", txn->sig_id); + /* Fails every channel (open_ccb releases them, freeing timers) + * and releases the txn slot. Report L2CAP_CONN_TIMEOUT (not the + * hardcoded NO_PSM) so the app sees a retryable timeout, matching + * the base LE CoC timeout path (l2c_ble_le_coc_channel_timeout). */ + l2c_ble_ecfc_abort_cl_txn(txn->p_lcb, txn->sig_id, L2CAP_CONN_TIMEOUT); + return TRUE; + } + } + } + return FALSE; +} +#endif /* BLE_L2CAP_COC_CLIENT_INCLUDED */ + +BOOLEAN l2c_ble_ecfc_on_reconfig_timeout(tL2C_CCB *p_ccb) +{ + int i, j; + + for (i = 0; i < MAX_L2CAP_LINKS; i++) { + tL2C_BLE_ECFC_RECONFIG_TXN *txn = &s_ecfc_reconfig_txn[i]; + if (!txn->in_use) { + continue; + } + for (j = 0; j < txn->num_chan; j++) { + if (txn->ccbs[j] == p_ccb) { + L2C_BLE_ECFC_TRACE_WARN("0x1A timeout sig_id=%u", txn->sig_id); + /* Notifies each channel of the failed reconfigure and frees the + * txn (also stops the response timers). */ + l2c_ble_ecfc_abort_reconfig_txn(txn->p_lcb, txn->sig_id); + return TRUE; + } + } + } + return FALSE; +} + +#if (BLE_L2CAP_COC_CLIENT_INCLUDED != TRUE) +UINT8 L2CA_ConnectLEEcocReq(UINT16 psm, BD_ADDR p_bd_addr, tL2CAP_LE_CFG_INFO *p_cfg, + UINT8 num_chan, UINT16 *p_lcids) +{ + UNUSED(psm); + UNUSED(p_bd_addr); + UNUSED(p_cfg); + UNUSED(num_chan); + UNUSED(p_lcids); + return 0; +} +#endif + +#endif /* BLE_L2CAP_ENHANCED_COC_INCLUDED == TRUE */ diff --git a/components/bt/host/bluedroid/stack/l2cap/l2c_ble_le_coc.c b/components/bt/host/bluedroid/stack/l2cap/l2c_ble_le_coc.c new file mode 100644 index 00000000000..f96af4172fc --- /dev/null +++ b/components/bt/host/bluedroid/stack/l2cap/l2c_ble_le_coc.c @@ -0,0 +1,1458 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* LE L2CAP Connection-Oriented Channel (Credit Based Flow Control Mode). */ +/* Independent from BR/EDR l2c_csm.c. */ + +#include +#include "device/controller.h" +#include "stack/bt_types.h" +#include "stack/l2cdefs.h" +#include "l2c_int.h" +#include "stack/l2c_api.h" +#include "stack/btu.h" +#if (BLE_EATT_INCLUDED == TRUE) +#include "gatt_eatt_int.h" +#endif + +#if (BLE_L2CAP_COC_INCLUDED == TRUE) + +#define L2C_BLE_COC_DEFAULT_MTU 512 +#define L2C_BLE_COC_DEFAULT_CREDITS 10 +#define L2C_BLE_COC_SDU_LEN_SIZE 2 +/* LE Credit Based Flow Control minimums (Core Spec Vol 3 Part A 4.22/4.23). */ +#define L2C_BLE_COC_MIN_MTU 23 +#define L2C_BLE_COC_MIN_MPS 23 + +/* RX credit window used for throughput. This is the number of K-frame credits we + * keep granted to the peer. It must stay large enough to keep the sender's pipeline + * fed (otherwise it ping-pongs at 1 credit), and is decoupled from the minimal + * ceil(MTU/MPS) needed to reassemble a single SDU. Kept in sync with the ECFC + * initial credits so the window is consistent before and after a reconfig. */ +#define L2C_BLE_COC_RX_CREDIT_WINDOW L2CAP_LE_INIT_CREDITS + +#define L2C_BLE_COC_TRACE_DEBUG(fmt, ...) L2CAP_TRACE_DEBUG("LE_COC: " fmt, ##__VA_ARGS__) +#define L2C_BLE_COC_TRACE_WARN(fmt, ...) L2CAP_TRACE_WARNING("LE_COC: " fmt, ##__VA_ARGS__) +#define L2C_BLE_COC_TRACE_ERROR(fmt, ...) L2CAP_TRACE_ERROR("LE_COC: " fmt, ##__VA_ARGS__) + +static void l2c_ble_le_coc_sec_cback(BD_ADDR bd_addr, tBT_TRANSPORT transport, + void *p_ref_data, tBTM_STATUS result); +static void l2c_ble_le_coc_notify_disconnect(tL2C_CCB *p_ccb, BOOLEAN local_init); +void l2c_ble_le_coc_open_channel(tL2C_CCB *p_ccb, UINT16 result); +static void l2c_ble_le_coc_try_xmit(tL2C_CCB *p_ccb); +static BOOLEAN l2c_ble_le_coc_send_frame(tL2C_CCB *p_ccb, const UINT8 *data, UINT16 len); +static void l2c_ble_le_coc_apply_default_cfg(tL2CAP_LE_CFG_INFO *cfg); +static UINT16 l2c_ble_le_coc_effective_mps(tL2C_CCB *p_ccb); +static UINT16 l2c_ble_le_coc_calc_rx_credits(UINT16 mtu, UINT16 mps); + +#if (BLE_L2CAP_COC_SERVER_INCLUDED == TRUE) +/* Normalize an application/internal result code to a valid LE Credit Based + * Connection Response result. L2CAP_CONN_{OK,NO_PSM,NO_RESOURCES} share values + * with their L2CAP_LE_RESULT_* counterparts, and callers may also pass an + * LE result code directly, so any valid LE result code is forwarded as-is. + * Internal-only L2CAP_CONN_* codes with no LE encoding (e.g. NO_LINK, TIMEOUT) + * fall back to UNACCEPTABLE_PARAMETERS. */ +static UINT16 l2c_ble_le_coc_wire_result(UINT16 result) +{ + switch (result) { + case L2CAP_LE_RESULT_CONN_OK: + case L2CAP_LE_RESULT_NO_PSM: + case L2CAP_LE_RESULT_NO_RESOURCES: + case L2CAP_LE_RESULT_INSUFFICIENT_AUTHENTICATION: + case L2CAP_LE_RESULT_INSUFFICIENT_AUTHORIZATION: + case L2CAP_LE_RESULT_INSUFFICIENT_ENCRY_KEY_SIZE: + case L2CAP_LE_RESULT_INSUFFICIENT_ENCRY: + case L2CAP_LE_RESULT_INVALID_SOURCE_CID: + case L2CAP_LE_RESULT_SOURCE_CID_ALREADY_ALLOCATED: + case L2CAP_LE_RESULT_UNACCEPTABLE_PARAMETERS: + return result; + /* 0x0C (INVALID_PARAMETERS) is "Reserved for future use" for the LE Credit + * Based Connection Response (code 0x15) per Core Spec v6.2 Vol 3 Part A + * Table 4.16; fall through to the default so it maps to 0x0B, the last + * valid result code for this packet type. */ + default: + return L2CAP_LE_RESULT_UNACCEPTABLE_PARAMETERS; + } +} +#endif /* BLE_L2CAP_COC_SERVER_INCLUDED */ + +BOOLEAN l2c_ble_le_coc_is_chan(tL2C_CCB *p_ccb) +{ + return (p_ccb != NULL) && p_ccb->le_coc_active && p_ccb->p_lcb != NULL && + p_ccb->p_lcb->transport == BT_TRANSPORT_LE; +} + +static void l2c_ble_le_coc_apply_default_cfg(tL2CAP_LE_CFG_INFO *cfg) +{ + if (cfg->mtu == 0) { + cfg->mtu = L2C_BLE_COC_DEFAULT_MTU; + } + /* Enforce the spec minimum MTU (Core Spec v6.2 Vol 3 Part A 4.22/4.23: + * "shall support a minimum MTU size of 23 octets"). Mirrors the MPS clamp + * below so a caller-supplied non-zero MTU < 23 is not sent on the wire. */ + if (cfg->mtu < L2C_BLE_COC_MIN_MTU) { + cfg->mtu = L2C_BLE_COC_MIN_MTU; + } + if (cfg->mps == 0) { + cfg->mps = L2CAP_LE_COC_MPS; + } + cfg->mps = L2CAP_LE_CLAMP_MPS(cfg->mps); + if (cfg->credits == 0) { + cfg->credits = L2C_BLE_COC_RX_CREDIT_WINDOW; + } +} + +static UINT16 l2c_ble_le_coc_effective_mps(tL2C_CCB *p_ccb) +{ + UINT16 mps = p_ccb->peer_conn_cfg.mps; + UINT16 acl = controller_get_interface()->get_acl_data_size_ble(); + + if (mps == 0) { + mps = L2CAP_LE_COC_MPS; + } + if (acl > L2CAP_PKT_OVERHEAD && mps > (acl - L2CAP_PKT_OVERHEAD)) { + mps = acl - L2CAP_PKT_OVERHEAD; + } + return mps; +} + +static UINT16 l2c_ble_le_coc_calc_rx_credits(UINT16 mtu, UINT16 mps) +{ + UINT16 credits; + UINT32 total; + + if (mps == 0) { + mps = L2CAP_LE_COC_MPS; + } + /* The first K-frame of an SDU carries a 2-byte SDU Length header, so a full + * SDU of `mtu` bytes spans ceil((mtu + 2) / mps) K-frames. Ignoring the + * header under-counts by one credit whenever mtu is a multiple of mps. */ + total = (UINT32)mtu + L2C_BLE_COC_SDU_LEN_SIZE; + credits = (UINT16)(total / mps); + if (total % mps) { + credits++; + } + return (credits > 0) ? credits : 1; +} + +void l2c_ble_le_coc_apply_reconfig(tL2C_CCB *p_ccb, UINT16 new_mtu, UINT16 new_mps) +{ + UINT16 credits; + + if (p_ccb == NULL || !p_ccb->le_coc_active) { + return; + } + + p_ccb->local_conn_cfg.mtu = new_mtu; + if (new_mps != 0) { + p_ccb->local_conn_cfg.mps = L2CAP_LE_CLAMP_MPS(new_mps); + } + credits = l2c_ble_le_coc_calc_rx_credits(new_mtu, p_ccb->local_conn_cfg.mps); + if (credits < L2C_BLE_COC_RX_CREDIT_WINDOW) { + credits = L2C_BLE_COC_RX_CREDIT_WINDOW; + } + p_ccb->local_conn_cfg.credits = credits; + + /* Grant the peer the extra RX credits the enlarged window now allows, + * otherwise the widened window never takes effect: the auto-credit path only + * returns consumed credits and can never raise le_coc_rx_avail above the + * previously outstanding count. */ + { + /* Count every credit the peer will eventually hold again: what it still + * holds (le_coc_rx_avail) plus credits already consumed but not yet + * returned - batched in auto mode (le_coc_rx_credits_pending) or owed in + * manual mode (le_coc_rx_manual_owed). Omitting manual_owed under-counts + * the window, so the deficit sent here plus the same owed credits later + * returned by recv_ready would double-grant and inflate the peer's TX + * window beyond the configured window. Use a UINT32 sum and clamp to the + * max credit window, matching give_credits / set_auto_credit. */ + UINT32 outstanding = (UINT32)p_ccb->le_coc_rx_avail + + p_ccb->le_coc_rx_credits_pending + + p_ccb->le_coc_rx_manual_owed; + if (credits > outstanding && p_ccb->p_lcb != NULL) { + UINT16 deficit = (UINT16)(credits - outstanding); + if ((UINT32)p_ccb->le_coc_rx_avail + deficit > L2CAP_LE_MAX_CREDIT) { + deficit = L2CAP_LE_MAX_CREDIT - p_ccb->le_coc_rx_avail; + } + if (deficit > 0) { + p_ccb->le_coc_rx_avail += deficit; + l2cble_send_flow_control_credit(p_ccb, deficit); + } + } + } + + L2C_BLE_COC_TRACE_DEBUG("reconfig lcid=0x%04x mtu=%u mps=%u rx_cred=%u", + p_ccb->local_cid, new_mtu, p_ccb->local_conn_cfg.mps, credits); +#if (BLE_EATT_INCLUDED == TRUE) + if (p_ccb->p_lcb != NULL) { + gatt_eatt_on_chan_mtu_changed(p_ccb->p_lcb->remote_bd_addr, p_ccb->local_cid); + } +#endif +} + +#if (BLE_L2CAP_ENHANCED_COC_INCLUDED == TRUE) +void l2c_ble_le_coc_notify_reconfig(tL2C_CCB *p_ccb, UINT16 status, BOOLEAN peer_initiated) +{ + tL2CA_LE_RECONFIG_IND_CB *cb; + + if (p_ccb == NULL || p_ccb->p_rcb == NULL) { + return; + } + + cb = p_ccb->p_rcb->api.pL2CA_LeReconfigInd_Cb; + if (cb) { + (*cb)(p_ccb->local_cid, status, peer_initiated); + } +} +#endif /* BLE_L2CAP_ENHANCED_COC_INCLUDED == TRUE */ + +BOOLEAN l2c_ble_le_coc_set_auto_credit(UINT16 lcid, BOOLEAN enable) +{ + tL2C_CCB *p_ccb; + + p_ccb = l2cu_find_ccb_by_cid(NULL, lcid); + if (p_ccb == NULL || !p_ccb->le_coc_active) { + return FALSE; + } + + /* When leaving auto-credit mode, flush any RX credits batched by the + * auto-credit path. Otherwise they are stranded (the auto path stops running + * and give_credits only adds new credits), permanently shrinking the peer's + * TX window. */ + if (!enable && !p_ccb->le_coc_no_auto_credit && p_ccb->le_coc_rx_credits_pending > 0 && + p_ccb->p_lcb != NULL) { + UINT16 give = p_ccb->le_coc_rx_credits_pending; + p_ccb->le_coc_rx_credits_pending = 0; + p_ccb->le_coc_rx_avail += give; + l2cble_send_flow_control_credit(p_ccb, give); + } + + /* Symmetrically, when re-enabling auto-credit, flush any credits the manual + * path consumed but has not returned yet (le_coc_rx_manual_owed). The auto + * path only returns credits for frames it consumes from now on, so leftover + * owed credits would otherwise be stranded and shrink the peer's TX window. */ + if (enable && p_ccb->le_coc_no_auto_credit && p_ccb->le_coc_rx_manual_owed > 0 && + p_ccb->p_lcb != NULL) { + UINT16 give = p_ccb->le_coc_rx_manual_owed; + if ((UINT32)p_ccb->le_coc_rx_avail + give > L2CAP_LE_MAX_CREDIT) { + give = L2CAP_LE_MAX_CREDIT - p_ccb->le_coc_rx_avail; + } + if (give > 0) { + p_ccb->le_coc_rx_manual_owed -= give; + p_ccb->le_coc_rx_avail += give; + l2cble_send_flow_control_credit(p_ccb, give); + } + } + + /* Manual mode returns RX credits only after a complete SDU is delivered (via + * recv_ready). If a single SDU can span more K-frames than the whole RX + * window, the peer runs out of TX credit mid-SDU and the SDU never completes + * -> the app never calls recv_ready -> stall. Warn when entering manual mode + * so the misconfiguration is visible; the data_ind deadlock breaker keeps it + * working, but auto mode (or a larger MPS) is the proper fix. */ + if (!enable) { + UINT16 need = l2c_ble_le_coc_calc_rx_credits(p_ccb->local_conn_cfg.mtu, + p_ccb->local_conn_cfg.mps); + if (need > p_ccb->local_conn_cfg.credits) { + L2C_BLE_COC_TRACE_WARN("manual mode: SDU up to %u K-frames > RX window %u lcid=0x%04x, prefer auto mode", + need, p_ccb->local_conn_cfg.credits, lcid); + } + } + + p_ccb->le_coc_no_auto_credit = !enable; + return TRUE; +} + +/* Arm/disarm the per-CCB signalling response timer. Reuses p_ccb->timer_entry + * (unused by LE CoC otherwise) with the classic per-channel BTU timer type; the + * dispatcher in l2c_process_timeout() routes it to l2c_ble_le_coc_channel_timeout() + * for LE CoC channels. l2cu_release_ccb() frees the timer, so no explicit stop is + * needed on the teardown path. */ +void l2c_ble_le_coc_start_rsp_timer(tL2C_CCB *p_ccb, UINT16 timeout_sec) +{ + if (p_ccb != NULL) { + btu_start_timer(&p_ccb->timer_entry, BTU_TTYPE_L2CAP_CHNL, timeout_sec); + } +} + +void l2c_ble_le_coc_stop_rsp_timer(tL2C_CCB *p_ccb) +{ + if (p_ccb != NULL) { + btu_stop_timer(&p_ccb->timer_entry); + } +} + +/* Send an LE Credit Based DISC_REQ, move the channel to + * CST_W4_L2CAP_DISCONNECT_RSP and arm the RTX timer so an unresponsive peer + * cannot leave the CCB (and its CID) leaked (Core Spec v6.2 Vol 3 Part A + * 6.2.1). l2c_ble_le_coc_channel_timeout() releases the CCB on expiry. */ +static void l2c_ble_le_coc_initiate_disc(tL2C_CCB *p_ccb) +{ + /* Already awaiting a DISC_RSP: a second DISC_REQ would bump p_lcb->id and + * overwrite p_ccb->local_id (see l2cu_send_peer_ble_credit_based_disconn_req), + * so the peer's DISC_RSP to the first request would be rejected on the id + * mismatch in l2c_ble_le_coc_handle_disc_rsp and the channel would linger + * (with the RTX timer restarted, up to 20s) instead of closing. Guard here so + * every caller is safe, including l2c_ble_le_coc_handle_flow_ctrl_credit + * which does not check chnl_state. Mirrors l2c_ble_le_coc_disconnect. */ + if (p_ccb->chnl_state == CST_W4_L2CAP_DISCONNECT_RSP) { + L2C_BLE_COC_TRACE_DEBUG("DISC pending, skip dup DISC_REQ lcid=0x%04x", p_ccb->local_cid); + return; + } + + /* No NULL p_lcb guard is needed here even though the send path dereferences + * p_ccb->p_lcb: p_lcb is only ever set to NULL in l2cu_release_ccb(), which + * clears in_use in the same synchronous call immediately afterwards, and the + * BT stack is single-threaded. l2cu_find_ccb_by_cid() (used by the callers) + * only returns CCBs with in_use == TRUE, so a CCB reaching here always has a + * live p_lcb; there is no window where p_lcb == NULL while in_use == TRUE. */ + L2C_BLE_COC_TRACE_DEBUG("send DISC_REQ lcid=0x%04x rcid=0x%04x", p_ccb->local_cid, p_ccb->remote_cid); + l2cble_send_peer_disc_req(p_ccb); + p_ccb->chnl_state = CST_W4_L2CAP_DISCONNECT_RSP; + /* This is the disconnect RTX (waiting for DISC_RSP), not a connect timeout, + * so use the shorter 10s disconnect timeout rather than the 60s connect one + * (matches the classic BR/EDR disconnect path). */ + l2c_ble_le_coc_start_rsp_timer(p_ccb, L2CAP_CHNL_DISCONNECT_TOUT); +} + +void l2c_ble_le_coc_channel_timeout(tL2C_CCB *p_ccb) +{ + if (p_ccb == NULL || !p_ccb->le_coc_active) { + return; + } + + L2C_BLE_COC_TRACE_WARN("rsp timeout lcid=0x%04x state=%d", p_ccb->local_cid, p_ccb->chnl_state); + +#if (BLE_L2CAP_ENHANCED_COC_INCLUDED == TRUE) + /* Reconfigure (0x19) whose 0x1A never arrived: channel stays OPEN. */ + if (p_ccb->chnl_state == CST_OPEN) { + l2c_ble_ecfc_on_reconfig_timeout(p_ccb); + return; + } +#endif + + if (p_ccb->chnl_state == CST_W4_L2CAP_DISCONNECT_RSP) { + /* Peer never answered our DISC_REQ (RTX timeout, Core Spec v6.2 Vol 3 + * Part A 6.2.1): notify the upper layer of a local disconnect and free + * the CCB instead of leaking it and its CID. The DisconnectInd callback + * runs synchronously and may re-enter L2CAP and release/reuse this CCB + * slot; re-fetch by the saved lcid and only release if it is still the + * same, in-use CoC CCB (mirrors l2c_ble_le_coc_open_channel). */ + UINT16 saved_lcid = p_ccb->local_cid; + l2c_ble_le_coc_notify_disconnect(p_ccb, TRUE); + tL2C_CCB *p_now = l2cu_find_ccb_by_cid(NULL, saved_lcid); + if (p_now == p_ccb && p_ccb->in_use && p_ccb->le_coc_active) { + l2cu_release_ccb(p_ccb); + } else { + L2C_BLE_COC_TRACE_DEBUG("disc timeout: lcid=0x%04x freed in cb, skip release", saved_lcid); + } + return; + } + + if (p_ccb->chnl_state == CST_W4_L2CAP_CONNECT_RSP) { +#if (BLE_L2CAP_ENHANCED_COC_INCLUDED == TRUE) + if (p_ccb->le_ecfc_channel) { +#if (BLE_L2CAP_COC_CLIENT_INCLUDED == TRUE) + if (l2c_ble_ecfc_on_conn_timeout(p_ccb)) { + return; + } +#endif + /* No owning txn found: release the orphaned CCB directly. */ + l2cu_release_ccb(p_ccb); + return; + } +#endif + /* Basic LE CoC (0x14) with no 0x15: fail + release via open_channel. */ + l2c_ble_le_coc_open_channel(p_ccb, L2CAP_CONN_TIMEOUT); + } +} + +void l2c_ble_le_coc_cleanup_ccb(tL2C_CCB *p_ccb) +{ + if (p_ccb == NULL) { + return; + } + + L2C_BLE_COC_TRACE_DEBUG("cleanup lcid=0x%04x", p_ccb->local_cid); + +#if (SMP_INCLUDED == TRUE) + /* A CCB torn down while its LE security check is still outstanding would + * leave a queued request pointing at this (soon reused) memory. Drop it so + * the deferred SMP callback never lands on a stale/reused CCB. */ + l2ble_sec_flush_pending_req(p_ccb->p_lcb, p_ccb); +#endif + + if (p_ccb->le_coc_rx_sdu) { + osi_free(p_ccb->le_coc_rx_sdu); + p_ccb->le_coc_rx_sdu = NULL; + } + if (p_ccb->le_coc_tx_sdu) { + osi_free(p_ccb->le_coc_tx_sdu); + p_ccb->le_coc_tx_sdu = NULL; + } + + p_ccb->le_coc_active = FALSE; +#if (BLE_L2CAP_ENHANCED_COC_INCLUDED == TRUE) + p_ccb->le_ecfc_channel = FALSE; +#endif + p_ccb->le_coc_no_auto_credit = FALSE; + p_ccb->le_coc_rx_avail = 0; + p_ccb->le_coc_rx_credits_pending = 0; + p_ccb->le_coc_rx_manual_owed = 0; + p_ccb->le_coc_rx_sdu_total = 0; + p_ccb->le_coc_rx_sdu_rcvd = 0; + p_ccb->le_coc_rx_have_len = FALSE; + p_ccb->le_coc_tx_offset = 0; + p_ccb->le_coc_tx_len_sent = FALSE; + /* Reset the TX re-entrancy guard/flag too. l2cu_allocate_ccb does not memset + * a CCB reused from the pool, so a stale le_coc_xmit_busy == TRUE (left by a + * release that raced a try_xmit re-entrancy) would permanently deadlock TX on + * the recycled channel. */ + p_ccb->le_coc_xmit_busy = FALSE; + p_ccb->le_coc_xmit_rerun = FALSE; + /* Clear the negotiated config so a recycled CCB never inherits the previous + * channel's MTU/MPS/credits (apply_default_cfg only fills zero fields). */ + memset(&p_ccb->local_conn_cfg, 0, sizeof(p_ccb->local_conn_cfg)); + memset(&p_ccb->peer_conn_cfg, 0, sizeof(p_ccb->peer_conn_cfg)); + /* Clear the peer CID too. l2cu_allocate_ccb does not memset a CCB reused from + * the pool, so a stale remote_cid would let the duplicate-DCID check in + * l2c_ble_le_coc_handle_credit_conn_res (l2cu_find_ccb_by_remote_cid) match + * this very CCB and wrongly reject a valid new outgoing connection. */ + p_ccb->remote_cid = 0; +} + +static void l2c_ble_le_coc_notify_disconnect(tL2C_CCB *p_ccb, BOOLEAN local_init) +{ + tL2CA_DISCONNECT_IND_CB *cb; + + if (p_ccb == NULL || p_ccb->p_rcb == NULL) { + return; + } + + cb = p_ccb->p_rcb->api.pL2CA_DisconnectInd_Cb; + if (cb) { + L2C_BLE_COC_TRACE_DEBUG("DisconnectInd lcid=0x%04x local_init=%d", + p_ccb->local_cid, local_init); + (*cb)(p_ccb->local_cid, local_init); + } +} + +void l2c_ble_le_coc_open_channel(tL2C_CCB *p_ccb, UINT16 result) +{ + tL2CA_CONNECT_CFM_CB *cb; + + if (p_ccb == NULL) { + return; + } + + if (result == L2CAP_CONN_OK) { + p_ccb->chnl_state = CST_OPEN; + p_ccb->le_coc_rx_avail = p_ccb->local_conn_cfg.credits; + p_ccb->le_coc_rx_credits_pending = 0; + p_ccb->le_coc_rx_manual_owed = 0; + L2C_BLE_COC_TRACE_DEBUG("OPEN lcid=0x%04x rcid=0x%04x mtu=%u mps=%u tx_cred=%u rx_cred=%u", + p_ccb->local_cid, p_ccb->remote_cid, + p_ccb->peer_conn_cfg.mtu, p_ccb->peer_conn_cfg.mps, + p_ccb->peer_conn_cfg.credits, p_ccb->le_coc_rx_avail); + } else { + L2C_BLE_COC_TRACE_WARN("connect failed lcid=0x%04x result=%u", p_ccb->local_cid, result); + } + + UINT16 saved_lcid = p_ccb->local_cid; + + if (p_ccb->p_rcb && p_ccb->p_rcb->api.pL2CA_ConnectCfm_Cb) { + cb = p_ccb->p_rcb->api.pL2CA_ConnectCfm_Cb; + (*cb)(p_ccb->local_cid, result); + } + + if (result != L2CAP_CONN_OK) { + /* The ConnectCfm callback runs synchronously and may re-enter L2CAP (e.g. + * disconnect this channel and start a new connect), which could release + * this CCB and reallocate the same pool slot for another channel. In that + * case l2cu_release_ccb(p_ccb) would tear down the wrong (reused) CCB. + * Re-fetch by the saved lcid and only release if it is still the same, + * in-use, active CoC CCB. No registered ConnectCfm handler does this + * synchronously today; this just closes the dangling-pointer window. */ + tL2C_CCB *p_now = l2cu_find_ccb_by_cid(NULL, saved_lcid); + if (p_now == p_ccb && p_ccb->in_use && p_ccb->le_coc_active) { + l2cu_release_ccb(p_ccb); + } + } +} + +static void l2c_ble_le_coc_sec_cback(BD_ADDR bd_addr, tBT_TRANSPORT transport, + void *p_ref_data, tBTM_STATUS result) +{ + tL2C_CCB *p_ccb = (tL2C_CCB *)p_ref_data; + UNUSED(transport); + + if (p_ccb == NULL || !p_ccb->le_coc_active) { + return; + } + + L2C_BLE_COC_TRACE_DEBUG("sec_cback lcid=0x%04x status=%d state=%d", + p_ccb->local_cid, result, p_ccb->chnl_state); + + if (result != BTM_SUCCESS) { +#if (BLE_L2CAP_COC_SERVER_INCLUDED == TRUE) + if (p_ccb->chnl_state == CST_TERM_W4_SEC_COMP && p_ccb->p_lcb != NULL) { + l2cu_reject_ble_connection(p_ccb->p_lcb, p_ccb->remote_id, + l2c_ble_coc_sec_status_to_result(bd_addr, result)); + l2cu_release_ccb(p_ccb); + return; + } +#endif + l2c_ble_le_coc_open_channel(p_ccb, L2CAP_CONN_NO_LINK); + return; + } + + if (p_ccb->chnl_state == CST_ORIG_W4_SEC_COMP) { +#if (BLE_L2CAP_COC_CLIENT_INCLUDED == TRUE) + l2cble_credit_based_conn_req(p_ccb); + p_ccb->chnl_state = CST_W4_L2CAP_CONNECT_RSP; + l2c_ble_le_coc_start_rsp_timer(p_ccb, L2CAP_CHNL_CONNECT_TOUT); + L2C_BLE_COC_TRACE_DEBUG("sent 0x14 lcid=0x%04x", p_ccb->local_cid); +#else + L2C_BLE_COC_TRACE_WARN("orig sec complete but client path disabled lcid=0x%04x", p_ccb->local_cid); + l2c_ble_le_coc_open_channel(p_ccb, L2CAP_CONN_NO_LINK); +#endif + } else if (p_ccb->chnl_state == CST_TERM_W4_SEC_COMP) { +#if (BLE_L2CAP_COC_SERVER_INCLUDED == TRUE) + tL2CA_CONNECT_IND_CB *ind_cb; + + p_ccb->chnl_state = CST_W4_L2CA_CONNECT_RSP; + if (p_ccb->p_rcb && p_ccb->p_rcb->api.pL2CA_ConnectInd_Cb) { + ind_cb = p_ccb->p_rcb->api.pL2CA_ConnectInd_Cb; + L2C_BLE_COC_TRACE_DEBUG("ConnectInd lcid=0x%04x psm=0x%04x id=%u", + p_ccb->local_cid, p_ccb->p_rcb->real_psm, p_ccb->remote_id); + /* Use the bd_addr parameter instead of dereferencing p_ccb->p_lcb: + * it carries the same address (both callers pass p_lcb->remote_bd_addr, + * and the BTM security callback delivers the peer address), and this + * matches the defensive p_lcb-free failure path above. */ + (*ind_cb)(bd_addr, p_ccb->local_cid, + p_ccb->p_rcb->real_psm, p_ccb->remote_id); + } else { + L2C_BLE_COC_TRACE_WARN("no ConnectInd_Cb, auto-accept lcid=0x%04x", p_ccb->local_cid); + l2c_ble_le_coc_connect_rsp(p_ccb, L2CAP_CONN_OK); + } +#else + L2C_BLE_COC_TRACE_WARN("term sec complete but server path disabled lcid=0x%04x", p_ccb->local_cid); + l2c_ble_le_coc_open_channel(p_ccb, L2CAP_CONN_NO_LINK); +#endif + } +} + +#if (BLE_L2CAP_COC_CLIENT_INCLUDED == TRUE) +void l2c_ble_le_coc_connect_req(tL2C_CCB *p_ccb) +{ + if (p_ccb == NULL || p_ccb->p_lcb == NULL || p_ccb->p_rcb == NULL) { + return; + } + + l2c_ble_le_coc_apply_default_cfg(&p_ccb->local_conn_cfg); + + L2C_BLE_COC_TRACE_DEBUG("connect_req lcid=0x%04x psm=0x%04x mtu=%u mps=%u cred=%u", + p_ccb->local_cid, p_ccb->p_rcb->real_psm, + p_ccb->local_conn_cfg.mtu, p_ccb->local_conn_cfg.mps, + p_ccb->local_conn_cfg.credits); + + p_ccb->chnl_state = CST_ORIG_W4_SEC_COMP; +#if (SMP_INCLUDED == TRUE) + l2ble_sec_access_req(p_ccb->p_lcb->remote_bd_addr, p_ccb->p_rcb->real_psm, + TRUE, l2c_ble_le_coc_sec_cback, p_ccb); +#else + /* SMP disabled: there is no LE security procedure to run, so proceed as if + * the access check passed (l2ble_sec_access_req is only compiled with SMP). */ + l2c_ble_le_coc_sec_cback(p_ccb->p_lcb->remote_bd_addr, BT_TRANSPORT_LE, p_ccb, BTM_SUCCESS); +#endif +} +#endif /* BLE_L2CAP_COC_CLIENT_INCLUDED */ + +#if (BLE_L2CAP_COC_SERVER_INCLUDED == TRUE) +void l2c_ble_le_coc_connect_rsp(tL2C_CCB *p_ccb, UINT16 result) +{ + if (p_ccb == NULL) { + return; + } + + result = l2c_ble_le_coc_wire_result(result); + L2C_BLE_COC_TRACE_DEBUG("connect_rsp lcid=0x%04x result=%u", p_ccb->local_cid, result); + + if (result == L2CAP_LE_RESULT_CONN_OK) { + l2c_ble_le_coc_apply_default_cfg(&p_ccb->local_conn_cfg); + l2cble_credit_based_conn_res(p_ccb, L2CAP_LE_RESULT_CONN_OK); + l2c_ble_le_coc_open_channel(p_ccb, L2CAP_CONN_OK); + } else { + /* Forward the specific reject reason to the peer instead of collapsing + * every failure to UNACCEPTABLE_PARAMETERS. */ + l2cble_credit_based_conn_res(p_ccb, result); + l2cu_release_ccb(p_ccb); + } +} +#endif /* BLE_L2CAP_COC_SERVER_INCLUDED */ + +void l2c_ble_le_coc_on_link_up(tL2C_LCB *p_lcb) +{ + tL2C_CCB *p_ccb; + + if (p_lcb == NULL) { + return; + } + + for (p_ccb = p_lcb->ccb_queue.p_first_ccb; p_ccb; ) { + tL2C_CCB *p_next = p_ccb->p_next_ccb; + + if (p_ccb->le_coc_active && p_ccb->chnl_state == CST_CLOSED) { +#if (BLE_L2CAP_ENHANCED_COC_INCLUDED == TRUE) + if (!p_ccb->le_ecfc_channel) +#endif + { + L2C_BLE_COC_TRACE_DEBUG("link up, start pending lcid=0x%04x", p_ccb->local_cid); +#if (BLE_L2CAP_COC_CLIENT_INCLUDED == TRUE) + l2c_ble_le_coc_connect_req(p_ccb); +#endif + } + } + p_ccb = p_next; + } + +#if (BLE_L2CAP_ENHANCED_COC_INCLUDED == TRUE) && (BLE_L2CAP_COC_CLIENT_INCLUDED == TRUE) + l2c_ble_ecfc_on_link_up(p_lcb); +#endif +} + +#if (BLE_L2CAP_COC_SERVER_INCLUDED == TRUE) +void l2c_ble_le_coc_handle_credit_conn_req(tL2C_LCB *p_lcb, UINT8 *p, UINT8 id, UINT16 cmd_len) +{ + tL2C_CCB *p_ccb = NULL; + tL2C_RCB *p_rcb = NULL; + UINT16 spsm, scid, mtu, mps, credits; + + if (cmd_len < L2CAP_CMD_BLE_CREDIT_BASED_CONN_REQ_LEN) { + /* Owe the peer a response even for a malformed request, otherwise it + * waits out its RTX timer (Core Spec v6.2 Vol 3 Part A signalling + * rules). ECFC 0x17 already rejects short packets the same way. */ + L2C_BLE_COC_TRACE_WARN("short 0x14 len=%u", cmd_len); + l2cu_reject_ble_connection(p_lcb, id, L2CAP_LE_RESULT_UNACCEPTABLE_PARAMETERS); + return; + } + + STREAM_TO_UINT16(spsm, p); + STREAM_TO_UINT16(scid, p); + STREAM_TO_UINT16(mtu, p); + STREAM_TO_UINT16(mps, p); + STREAM_TO_UINT16(credits, p); + + L2C_BLE_COC_TRACE_DEBUG("rx 0x14 spsm=0x%04x scid=0x%04x mtu=%u mps=%u cred=%u id=%u", + spsm, scid, mtu, mps, credits, id); + + if (mtu < L2C_BLE_COC_MIN_MTU || mps < L2C_BLE_COC_MIN_MPS || + mps > L2CAP_LE_MAX_MPS) { + L2C_BLE_COC_TRACE_WARN("bad params mtu=%u mps=%u", mtu, mps); + l2cu_reject_ble_connection(p_lcb, id, L2CAP_LE_RESULT_UNACCEPTABLE_PARAMETERS); + return; + } + + /* The source CID must lie in the LE-U dynamically allocated range + * (0x0040-0x007F, Core Spec v6.2 Vol 3 Part A Table 2.3). Reject values + * such as 0x0000 or the fixed ATT CID 0x0004. */ + if (scid < L2CAP_BASE_APPL_CID || scid > L2CAP_BLE_CONN_MAX_CID) { + L2C_BLE_COC_TRACE_WARN("0x14 invalid scid=0x%04x", scid); + l2cu_reject_ble_connection(p_lcb, id, L2CAP_LE_RESULT_INVALID_SOURCE_CID); + return; + } + + if (l2cu_find_ccb_by_remote_cid(p_lcb, scid)) { + l2cu_reject_ble_connection(p_lcb, id, L2CAP_LE_RESULT_SOURCE_CID_ALREADY_ALLOCATED); + return; + } + + p_rcb = l2cu_find_ble_rcb_by_psm(spsm); + if (p_rcb == NULL) { + L2C_BLE_COC_TRACE_WARN("no RCB for psm=0x%04x", spsm); + l2cu_reject_ble_connection(p_lcb, id, L2CAP_LE_RESULT_NO_PSM); + return; + } + + p_ccb = l2cu_allocate_ccb(p_lcb, 0); + if (p_ccb == NULL) { + l2cu_reject_ble_connection(p_lcb, id, L2CAP_LE_RESULT_NO_RESOURCES); + return; + } + + p_ccb->le_coc_active = TRUE; + p_ccb->remote_id = id; + p_ccb->p_rcb = p_rcb; + p_ccb->remote_cid = scid; + p_ccb->peer_conn_cfg.mtu = mtu; + p_ccb->peer_conn_cfg.mps = mps; + p_ccb->peer_conn_cfg.credits = credits; + l2c_ble_le_coc_apply_default_cfg(&p_ccb->local_conn_cfg); + + p_ccb->chnl_state = CST_TERM_W4_SEC_COMP; +#if (SMP_INCLUDED == TRUE) + l2ble_sec_access_req(p_lcb->remote_bd_addr, p_rcb->real_psm, FALSE, + l2c_ble_le_coc_sec_cback, p_ccb); +#else + /* SMP disabled: no security procedure, treat the access check as passed. */ + l2c_ble_le_coc_sec_cback(p_lcb->remote_bd_addr, BT_TRANSPORT_LE, p_ccb, BTM_SUCCESS); +#endif +} +#endif /* BLE_L2CAP_COC_SERVER_INCLUDED */ + +#if (BLE_L2CAP_COC_CLIENT_INCLUDED == TRUE) +static tL2C_CCB *l2c_ble_le_coc_find_ccb_by_sig_id(tL2C_LCB *p_lcb, UINT8 id) +{ + tL2C_CCB *p_ccb; + + if (p_lcb == NULL) { + return NULL; + } + + for (p_ccb = p_lcb->ccb_queue.p_first_ccb; p_ccb; p_ccb = p_ccb->p_next_ccb) { + if (p_ccb->in_use && p_ccb->le_coc_active && +#if (BLE_L2CAP_ENHANCED_COC_INCLUDED == TRUE) + /* ECFC client CCBs also set le_coc_active and sit in + * CST_W4_L2CAP_CONNECT_RSP with local_id == txn sig_id. Exclude them + * so a base-CoC 0x15 response can never be applied to an ECFC channel + * on a signalling-id collision (8-bit wrap or a malformed peer). */ + !p_ccb->le_ecfc_channel && +#endif + p_ccb->local_id == id && + p_ccb->chnl_state == CST_W4_L2CAP_CONNECT_RSP) { + return p_ccb; + } + } + return NULL; +} + +void l2c_ble_le_coc_handle_credit_conn_res(tL2C_LCB *p_lcb, UINT8 *p, UINT8 id, UINT16 cmd_len) +{ + tL2C_CCB *p_ccb; + UINT16 dcid, mtu, mps, credits, result; + + /* Find and de-arm the pending CCB before validating length, mirroring the + * ECFC handler (l2c_ble_ecfc_handle_conn_res). The response id is consumed + * either way; returning on a short packet without cleanup would leave the + * CCB stuck in CST_W4_L2CAP_CONNECT_RSP until its response timer fires, + * needlessly delaying error recovery. */ + p_ccb = l2c_ble_le_coc_find_ccb_by_sig_id(p_lcb, id); + if (p_ccb == NULL || !p_ccb->le_coc_active) { + L2C_BLE_COC_TRACE_WARN("0x15 unknown id=%u", id); + return; + } + + /* Response received: cancel the connect-response timeout. */ + l2c_ble_le_coc_stop_rsp_timer(p_ccb); + + if (cmd_len < L2CAP_CMD_BLE_CREDIT_BASED_CONN_RES_LEN) { + L2C_BLE_COC_TRACE_WARN("short 0x15 len=%u", cmd_len); + l2c_ble_le_coc_open_channel(p_ccb, L2CAP_CONN_NO_PSM); + return; + } + + STREAM_TO_UINT16(dcid, p); + STREAM_TO_UINT16(mtu, p); + STREAM_TO_UINT16(mps, p); + STREAM_TO_UINT16(credits, p); + STREAM_TO_UINT16(result, p); + + L2C_BLE_COC_TRACE_DEBUG("rx 0x15 dcid=0x%04x mtu=%u mps=%u cred=%u result=%u id=%u", + dcid, mtu, mps, credits, result, id); + + if (result != L2CAP_LE_RESULT_CONN_OK) { + /* Forward the peer's specific reject reason (e.g. insufficient + * authentication/encryption, no resources) instead of a hardcoded + * L2CAP_CONN_NO_PSM, so the application can recover appropriately. + * L2CAP_LE_RESULT_CONN_OK (0) == L2CAP_CONN_OK (0), so the success + * check in l2c_ble_le_coc_open_channel still holds. Mirrors the ECFC + * handler (l2c_ble_ecfc_handle_conn_res). */ + l2c_ble_le_coc_open_channel(p_ccb, result); + return; + } + + /* Destination CID must be from the LE-U dynamic range (Core Spec v6.2 Vol 3 + * Part A 4.23) and not already assigned on this link; otherwise outgoing + * frames would target an invalid peer CID. Validate the DCID (and record + * remote_cid) BEFORE the MTU/MPS check so a subsequent teardown can address + * the peer's channel. An invalid/duplicate DCID cannot be cleanly torn down + * (no valid target, and a duplicate would disconnect the wrong channel), so + * that case still just drops our CCB. */ + if (dcid < L2CAP_BASE_APPL_CID || dcid > L2CAP_BLE_CONN_MAX_CID || + l2cu_find_ccb_by_remote_cid(p_lcb, dcid) != NULL) { + L2C_BLE_COC_TRACE_WARN("0x15 invalid/duplicate dcid=0x%04x", dcid); + l2c_ble_le_coc_open_channel(p_ccb, L2CAP_CONN_NO_PSM); + return; + } + p_ccb->remote_cid = dcid; + + if (mtu < L2C_BLE_COC_MIN_MTU || mps < L2CAP_LE_MIN_MPS || mps > L2CAP_LE_MAX_MPS) { + /* result==OK means the peer established the channel on its side; a bad + * MTU/MPS makes it unusable for us, but merely dropping our CCB would + * leave the peer's half orphaned until the ACL drops. Send a best-effort + * DISC_REQ (remote_cid is set) to tear it down on the air, then report + * the connect failure to the app (Core Spec v6.2 Vol 3 Part A 4.23 + * mandates a 23-byte minimum MTU/MPS). */ + L2C_BLE_COC_TRACE_WARN("0x15 bad mtu=%u mps=%u", mtu, mps); + l2cble_send_peer_disc_req(p_ccb); + l2c_ble_le_coc_open_channel(p_ccb, L2CAP_CONN_NO_PSM); + return; + } + + p_ccb->peer_conn_cfg.mtu = mtu; + p_ccb->peer_conn_cfg.mps = mps; + p_ccb->peer_conn_cfg.credits = credits; + l2c_ble_le_coc_open_channel(p_ccb, L2CAP_CONN_OK); +} + +BOOLEAN l2c_ble_le_coc_abort_conn_req(tL2C_LCB *p_lcb, UINT8 id, UINT16 result) +{ + /* A base LE CoC (0x14) client request whose signalling id was answered by a + * CMD_REJECT (rather than a 0x15 response): fail it now so the app is told + * immediately instead of waiting out the connect RTX timer. find_ccb_by_sig_id + * excludes ECFC channels, so this never collides with the ECFC abort path + * handling the same CMD_REJECT. */ + tL2C_CCB *p_ccb = l2c_ble_le_coc_find_ccb_by_sig_id(p_lcb, id); + + if (p_ccb == NULL) { + return FALSE; + } + + L2C_BLE_COC_TRACE_DEBUG("CMD_REJECT abort base CoC lcid=0x%04x id=%u result=%u", p_ccb->local_cid, id, result); + l2c_ble_le_coc_stop_rsp_timer(p_ccb); + l2c_ble_le_coc_open_channel(p_ccb, result); /* failure path releases the CCB */ + return TRUE; +} +#endif /* BLE_L2CAP_COC_CLIENT_INCLUDED */ + +void l2c_ble_le_coc_handle_flow_ctrl_credit(tL2C_LCB *p_lcb, UINT8 *p, UINT16 cmd_len) +{ + tL2C_CCB *p_ccb; + UINT16 lcid, credit; + + if (cmd_len < L2CAP_CMD_BLE_FLOW_CTRL_CREDIT_LEN) { + L2C_BLE_COC_TRACE_WARN("short 0x16 len=%u", cmd_len); + return; + } + + STREAM_TO_UINT16(lcid, p); + STREAM_TO_UINT16(credit, p); + + /* Per Core Spec v6.2 Vol 3 Part A 4.24 the CID in L2CAP_FLOW_CONTROL_CREDIT_IND + * is the sender's local (source) CID, i.e. our remote CID. Look up by remote + * CID so credits are routed to the correct channel when several CoC channels + * share the ACL link with non-matching CID values. */ + p_ccb = l2cu_find_ccb_by_remote_cid(p_lcb, lcid); + if (p_ccb == NULL || !p_ccb->le_coc_active) { + L2C_BLE_COC_TRACE_WARN("0x16 unknown lcid=0x%04x", lcid); + return; + } + + if (credit == 0) { + /* Core Spec v6.2 Vol 3 Part A 10.1: a device receiving a + * L2CAP_FLOW_CONTROL_CREDIT_IND with a credit value of zero shall ignore + * the packet. Handling it would falsely toggle the congestion state. */ + L2C_BLE_COC_TRACE_WARN("0x16 zero credits lcid=0x%04x, ignoring", lcid); + return; + } + + if ((p_ccb->peer_conn_cfg.credits + credit) > L2CAP_LE_MAX_CREDIT) { + L2C_BLE_COC_TRACE_ERROR("credit overflow lcid=0x%04x", lcid); + l2c_ble_le_coc_initiate_disc(p_ccb); + return; + } + + p_ccb->peer_conn_cfg.credits += credit; + L2C_BLE_COC_TRACE_DEBUG("0x16 lcid=0x%04x +%u tx_cred=%u", lcid, credit, p_ccb->peer_conn_cfg.credits); + + /* Only signal decongestion for a channel that is actually OPEN. Credits can + * still arrive after l2c_ble_le_coc_initiate_disc moved the channel to + * CST_W4_L2CAP_DISCONNECT_RSP; firing the callback then would tell the upper + * layer the channel is ready to send while it is being torn down. Mirrors the + * CST_OPEN guard in l2c_ble_le_coc_try_xmit. */ + if (p_ccb->chnl_state == CST_OPEN && p_ccb->p_rcb && + p_ccb->p_rcb->api.pL2CA_CongestionStatus_Cb && p_ccb->cong_sent) { + UINT16 saved_lcid = p_ccb->local_cid; + p_ccb->cong_sent = FALSE; + (*p_ccb->p_rcb->api.pL2CA_CongestionStatus_Cb)(p_ccb->local_cid, FALSE); + /* The callback runs synchronously and may re-enter L2CAP and release this + * CCB. l2cu_release_ccb() clears in_use/p_lcb but leaves chnl_state intact, + * so try_xmit's CST_OPEN check alone would not detect a freed slot; re-fetch + * by the saved lcid and bail if it is gone, mirroring handle_disc_req / + * handle_disc_rsp / open_channel / channel_timeout. */ + tL2C_CCB *p_now = l2cu_find_ccb_by_cid(NULL, saved_lcid); + if (p_now != p_ccb || !p_ccb->in_use || !p_ccb->le_coc_active) { + L2C_BLE_COC_TRACE_DEBUG("0x16: lcid=0x%04x freed in cong cb, skip xmit", saved_lcid); + return; + } + } + + l2c_ble_le_coc_try_xmit(p_ccb); + l2c_link_check_send_pkts(p_ccb->p_lcb, NULL, NULL); +} + +void l2c_ble_le_coc_handle_disc_req(tL2C_CCB *p_ccb, tL2C_LCB *p_lcb, UINT8 id, UINT16 lcid, UINT16 rcid) +{ + if (p_ccb == NULL || p_lcb == NULL || !p_ccb->le_coc_active) { + return; + } + + /* Core Spec v6.2 Vol 3 Part A 4.6: if the DCID matches but the SCID does not, + * silently discard the request. */ + if (p_ccb->remote_cid != 0 && rcid != p_ccb->remote_cid) { + L2C_BLE_COC_TRACE_WARN("rx DISC_REQ scid mismatch lcid=0x%04x rcid=0x%04x expect=0x%04x", + lcid, rcid, p_ccb->remote_cid); + return; + } + + L2C_BLE_COC_TRACE_DEBUG("rx DISC_REQ lcid=0x%04x", lcid); + p_ccb->remote_id = id; + l2cu_send_peer_disc_rsp(p_lcb, id, lcid, rcid); + /* The DisconnectInd callback runs synchronously and may re-enter L2CAP and + * release/reuse this CCB slot; re-fetch by the saved lcid and only release if + * it is still the same, in-use CoC CCB (mirrors l2c_ble_le_coc_open_channel). */ + UINT16 saved_lcid = p_ccb->local_cid; + l2c_ble_le_coc_notify_disconnect(p_ccb, FALSE); + tL2C_CCB *p_now = l2cu_find_ccb_by_cid(NULL, saved_lcid); + if (p_now == p_ccb && p_ccb->in_use && p_ccb->le_coc_active) { + l2cu_release_ccb(p_ccb); + } else { + L2C_BLE_COC_TRACE_DEBUG("DISC_REQ: lcid=0x%04x freed in cb, skip release", saved_lcid); + } +} + +void l2c_ble_le_coc_handle_disc_rsp(tL2C_LCB *p_lcb, UINT8 *p, UINT8 id, UINT16 cmd_len) +{ + tL2C_CCB *p_ccb; + UINT16 lcid, rcid; + + if (cmd_len < L2CAP_DISC_REQ_LEN) { + return; + } + + STREAM_TO_UINT16(rcid, p); + STREAM_TO_UINT16(lcid, p); + + p_ccb = l2cu_find_ccb_by_cid(p_lcb, lcid); + if (p_ccb == NULL || !p_ccb->le_coc_active) { + return; + } + + /* Only accept a DISC_RSP that actually answers a DISC_REQ we sent: the + * channel must be awaiting the response and the signalling Identifier must + * match the one used for the request (Core Spec v6.2 Vol 3 Part A 4.7). + * Otherwise an unsolicited/stale/cross DISC_RSP could tear down an OPEN + * channel that reused the same CID. */ + if (p_ccb->chnl_state != CST_W4_L2CAP_DISCONNECT_RSP || p_ccb->local_id != id) { + L2C_BLE_COC_TRACE_WARN("rx unexpected DISC_RSP lcid=0x%04x state=%d id=%u expect_id=%u", + lcid, p_ccb->chnl_state, id, p_ccb->local_id); + return; + } + + if (p_ccb->remote_cid != 0 && rcid != p_ccb->remote_cid) { + /* The signalling Identifier already confirmed this DISC_RSP answers our + * DISC_REQ, so no further response will arrive for this transaction. + * Complete the teardown below even though the echoed DCID is malformed; + * returning here would leave the CCB in CST_W4_L2CAP_DISCONNECT_RSP until + * the 10s RTX timer fires. l2cu_release_ccb() below stops that timer. */ + L2C_BLE_COC_TRACE_WARN("rx DISC_RSP rcid mismatch lcid=0x%04x rcid=0x%04x expect=0x%04x", + lcid, rcid, p_ccb->remote_cid); + } + + L2C_BLE_COC_TRACE_DEBUG("rx DISC_RSP lcid=0x%04x rcid=0x%04x", lcid, rcid); + /* The DisconnectInd callback runs synchronously and may re-enter L2CAP and + * release/reuse this CCB slot; re-fetch by the saved lcid and only release if + * it is still the same, in-use CoC CCB (mirrors l2c_ble_le_coc_open_channel). */ + UINT16 saved_lcid = p_ccb->local_cid; + l2c_ble_le_coc_notify_disconnect(p_ccb, TRUE); + tL2C_CCB *p_now = l2cu_find_ccb_by_cid(NULL, saved_lcid); + if (p_now == p_ccb && p_ccb->in_use && p_ccb->le_coc_active) { + l2cu_release_ccb(p_ccb); + } else { + L2C_BLE_COC_TRACE_DEBUG("DISC_RSP: lcid=0x%04x freed in cb, skip release", saved_lcid); + } +} + +static BOOLEAN l2c_ble_le_coc_send_frame(tL2C_CCB *p_ccb, const UINT8 *data, UINT16 len) +{ + BT_HDR *p_buf; + UINT8 *pkt; + + if (p_ccb->peer_conn_cfg.credits == 0) { + return FALSE; + } + + p_buf = (BT_HDR *)osi_malloc(sizeof(BT_HDR) + HCI_DATA_PREAMBLE_SIZE + + L2CAP_PKT_OVERHEAD + len); + if (p_buf == NULL) { + return FALSE; + } + + p_buf->offset = HCI_DATA_PREAMBLE_SIZE; + p_buf->len = L2CAP_PKT_OVERHEAD + len; + p_buf->event = 0; + p_buf->layer_specific = 0; + pkt = (UINT8 *)(p_buf + 1) + p_buf->offset; + UINT16_TO_STREAM(pkt, len); + UINT16_TO_STREAM(pkt, p_ccb->remote_cid); + memcpy(pkt, data, len); + + l2cu_set_acl_hci_header(p_buf, p_ccb); + l2c_link_check_send_pkts(p_ccb->p_lcb, p_ccb, p_buf); + p_ccb->peer_conn_cfg.credits--; + + L2C_BLE_COC_TRACE_DEBUG("tx frame lcid=0x%04x len=%u tx_cred=%u", + p_ccb->local_cid, len, p_ccb->peer_conn_cfg.credits); + return TRUE; +} + +static void l2c_ble_le_coc_try_xmit(tL2C_CCB *p_ccb) +{ + UINT16 mps, chunk, sdu_len, hdr_len; + const UINT8 *src; + BT_HDR *p_sdu; + BOOLEAN completed_sdu = FALSE; + + if (p_ccb == NULL || p_ccb->chnl_state != CST_OPEN) { + return; + } + + /* Re-entrancy guard. The uncongested CongestionStatus_Cb(FALSE) below can be + * invoked synchronously (e.g. the EATT congestion callback -> + * gatt_cl_send_next_cmd_inq -> data_write -> try_xmit), which would otherwise + * recurse once per credit and overflow the BTU task stack. Instead, a + * re-entrant call just flags a rerun and returns; the outermost invocation + * loops to drain the newly queued data. This preserves the self-clocked TX + * (UNSTALLED-after-each-SDU) behaviour without unbounded recursion. */ + if (p_ccb->le_coc_xmit_busy) { + p_ccb->le_coc_xmit_rerun = TRUE; + return; + } + p_ccb->le_coc_xmit_busy = TRUE; + +again: + /* Re-validate the state on every (re)entry, not just at function entry. The + * uncongested callback below can synchronously disconnect the channel + * (chnl_state -> CST_W4_L2CAP_DISCONNECT_RSP after DISC_REQ is sent) and set + * le_coc_xmit_rerun, so a plain "goto again" would otherwise resume the TX + * loop on a non-OPEN channel and emit K-frames after the DISC_REQ. Reset the + * busy flag so a recycled CCB is not left permanently blocked for TX. */ + if (p_ccb->chnl_state != CST_OPEN) { + p_ccb->le_coc_xmit_busy = FALSE; + return; + } + completed_sdu = FALSE; + while (p_ccb->peer_conn_cfg.credits > 0) { + if (p_ccb->le_coc_tx_sdu == NULL) { + if (fixed_queue_is_empty(p_ccb->xmit_hold_q)) { + break; + } + p_ccb->le_coc_tx_sdu = (BT_HDR *)fixed_queue_dequeue(p_ccb->xmit_hold_q, 0); + p_ccb->le_coc_tx_offset = 0; + p_ccb->le_coc_tx_len_sent = FALSE; + } + + p_sdu = p_ccb->le_coc_tx_sdu; + sdu_len = p_sdu->len; + mps = l2c_ble_le_coc_effective_mps(p_ccb); + src = (UINT8 *)(p_sdu + 1) + p_sdu->offset; + + if (!p_ccb->le_coc_tx_len_sent) { + UINT8 *frame_buf; + hdr_len = L2C_BLE_COC_SDU_LEN_SIZE; + if (mps < hdr_len) { + break; + } + frame_buf = (UINT8 *)osi_malloc(mps); + if (frame_buf == NULL) { + break; + } + frame_buf[0] = (UINT8)(sdu_len & 0xFF); + frame_buf[1] = (UINT8)((sdu_len >> 8) & 0xFF); + chunk = mps - hdr_len; + if (chunk > sdu_len) { + chunk = sdu_len; + } + memcpy(frame_buf + hdr_len, src, chunk); + if (!l2c_ble_le_coc_send_frame(p_ccb, frame_buf, hdr_len + chunk)) { + osi_free(frame_buf); + break; + } + osi_free(frame_buf); + p_ccb->le_coc_tx_offset += chunk; + if (p_ccb->le_coc_tx_offset >= sdu_len) { + osi_free(p_ccb->le_coc_tx_sdu); + p_ccb->le_coc_tx_sdu = NULL; + completed_sdu = TRUE; + } else { + p_ccb->le_coc_tx_len_sent = TRUE; + } + } else { + chunk = sdu_len - p_ccb->le_coc_tx_offset; + if (chunk > mps) { + chunk = mps; + } + if (!l2c_ble_le_coc_send_frame(p_ccb, src + p_ccb->le_coc_tx_offset, chunk)) { + break; + } + p_ccb->le_coc_tx_offset += chunk; + if (p_ccb->le_coc_tx_offset >= sdu_len) { + osi_free(p_ccb->le_coc_tx_sdu); + p_ccb->le_coc_tx_sdu = NULL; + completed_sdu = TRUE; + } + } + } + + if (p_ccb->peer_conn_cfg.credits == 0 && p_ccb->p_rcb && + p_ccb->p_rcb->api.pL2CA_CongestionStatus_Cb && !p_ccb->cong_sent) { + p_ccb->cong_sent = TRUE; + (*p_ccb->p_rcb->api.pL2CA_CongestionStatus_Cb)(p_ccb->local_cid, TRUE); + L2C_BLE_COC_TRACE_DEBUG("tx congested lcid=0x%04x", p_ccb->local_cid); + } else if (completed_sdu && p_ccb->peer_conn_cfg.credits > 0 && + p_ccb->le_coc_tx_sdu == NULL && + fixed_queue_is_empty(p_ccb->xmit_hold_q) && + p_ccb->p_rcb && p_ccb->p_rcb->api.pL2CA_CongestionStatus_Cb) { + /* SDU fully sent and pipeline drained with credits to spare: signal the + * app it may send the next SDU. This self-clocks TX without polling. The + * re-entrancy guard above turns any synchronous re-entry from this + * callback into an iterative rerun instead of deep recursion. */ + p_ccb->cong_sent = FALSE; + (*p_ccb->p_rcb->api.pL2CA_CongestionStatus_Cb)(p_ccb->local_cid, FALSE); + } + + /* A re-entrant call (typically triggered by the callback above) queued more + * data; drain it here in the outer frame rather than on a nested stack. */ + if (p_ccb->le_coc_xmit_rerun) { + p_ccb->le_coc_xmit_rerun = FALSE; + goto again; + } + p_ccb->le_coc_xmit_busy = FALSE; +} + +UINT8 l2c_ble_le_coc_data_write(UINT16 lcid, BT_HDR *p_data) +{ + tL2C_CCB *p_ccb; + + if (p_data == NULL) { + return L2CAP_DW_FAILED; + } + + p_ccb = l2cu_find_ccb_by_cid(NULL, lcid); + if (p_ccb == NULL || !p_ccb->le_coc_active || p_ccb->chnl_state != CST_OPEN) { + osi_free(p_data); + return L2CAP_DW_FAILED; + } + + if (p_data->len > p_ccb->peer_conn_cfg.mtu) { + L2C_BLE_COC_TRACE_WARN("SDU too large lcid=0x%04x len=%u mtu=%u", + lcid, p_data->len, p_ccb->peer_conn_cfg.mtu); + osi_free(p_data); + return L2CAP_DW_FAILED; + } + + L2C_BLE_COC_TRACE_DEBUG("data_write lcid=0x%04x len=%u", lcid, p_data->len); + /* fixed_queue_enqueue returns FALSE on OOM (list node alloc) without taking + * ownership of p_data; free it and report failure instead of leaking + losing + * the SDU while wrongly reporting success. */ + if (!fixed_queue_enqueue(p_ccb->xmit_hold_q, p_data, FIXED_QUEUE_MAX_TIMEOUT)) { + L2C_BLE_COC_TRACE_ERROR("xmit enqueue failed lcid=0x%04x", lcid); + osi_free(p_data); + return L2CAP_DW_FAILED; + } + l2c_ble_le_coc_try_xmit(p_ccb); + + if (p_ccb->peer_conn_cfg.credits == 0 && + (p_ccb->le_coc_tx_sdu != NULL || !fixed_queue_is_empty(p_ccb->xmit_hold_q))) { + return L2CAP_DW_CONGESTED; + } + return L2CAP_DW_SUCCESS; +} + +BOOLEAN l2c_ble_le_coc_is_congested(UINT16 lcid) +{ + tL2C_CCB *p_ccb; + + p_ccb = l2cu_find_ccb_by_cid(NULL, lcid); + if (p_ccb == NULL || !p_ccb->le_coc_active || p_ccb->chnl_state != CST_OPEN) { + return TRUE; + } + if (p_ccb->cong_sent) { + return TRUE; + } + if (p_ccb->le_coc_tx_sdu != NULL || !fixed_queue_is_empty(p_ccb->xmit_hold_q)) { + return TRUE; + } + return FALSE; +} + +BOOLEAN l2c_ble_le_coc_give_credits(UINT16 lcid, UINT16 credits) +{ + tL2C_CCB *p_ccb; + + if (credits == 0) { + return FALSE; + } + + p_ccb = l2cu_find_ccb_by_cid(NULL, lcid); + if (p_ccb == NULL || !p_ccb->le_coc_active || p_ccb->chnl_state != CST_OPEN) { + return FALSE; + } + + /* In auto-credit mode the stack returns credits per consumed frame, so a + * manual return (app recv_ready) would double-count and over-grant. Ignore. */ + if (!p_ccb->le_coc_no_auto_credit) { + return TRUE; + } + + if (p_ccb->p_lcb == NULL) { + return FALSE; + } + + /* Manual mode: return the credits actually consumed by the delivered SDU(s), + * not the caller's nominal count. data_ind() tracked one credit per consumed + * K-frame in le_coc_rx_manual_owed, so a multi-frame SDU returns >1 credit + * even though the app calls recv_ready once. The caller's `credits` argument + * is intentionally ignored here (recv_ready always means "return what the + * processed SDU consumed"). Nothing owed (e.g. a pre-auth recv_ready before + * any data) is a harmless no-op: the initial window was granted at open. */ + UINT16 give = p_ccb->le_coc_rx_manual_owed; + if (give == 0) { + return TRUE; + } + if ((UINT32)p_ccb->le_coc_rx_avail + give > L2CAP_LE_MAX_CREDIT) { + /* Clamp so we never advertise more than the peer's max window; keep the + * remainder owed to return on the next recv_ready. */ + give = L2CAP_LE_MAX_CREDIT - p_ccb->le_coc_rx_avail; + if (give == 0) { + L2C_BLE_COC_TRACE_ERROR("give_credits window full lcid=0x%04x", lcid); + return FALSE; + } + } + + p_ccb->le_coc_rx_manual_owed -= give; + p_ccb->le_coc_rx_avail += give; + L2C_BLE_COC_TRACE_DEBUG("give_credits lcid=0x%04x +%u owed=%u", lcid, give, p_ccb->le_coc_rx_manual_owed); + l2cble_send_flow_control_credit(p_ccb, give); + return TRUE; +} + +BOOLEAN l2c_ble_le_coc_disconnect(UINT16 lcid) +{ + tL2C_CCB *p_ccb; + + p_ccb = l2cu_find_ccb_by_cid(NULL, lcid); + if (p_ccb == NULL || !p_ccb->le_coc_active) { + return FALSE; + } + + /* Already tearing down: don't emit a duplicate disconnect request. */ + if (p_ccb->chnl_state == CST_W4_L2CAP_DISCONNECT_RSP) { + return TRUE; + } + +#if (BLE_L2CAP_COC_SERVER_INCLUDED == TRUE) + /* Incoming connection still awaiting the app's accept/reject decision: the + * peer sent an LE credit based connection request that we owe a response to. + * Reject it (sends 0x15 with a failure result) instead of silently dropping + * the CCB, which would leave the peer waiting for a response. */ + if (p_ccb->chnl_state == CST_W4_L2CA_CONNECT_RSP) { + L2C_BLE_COC_TRACE_DEBUG("reject pending incoming coc lcid=0x%04x", lcid); + l2c_ble_le_coc_connect_rsp(p_ccb, L2CAP_LE_RESULT_NO_RESOURCES); + return TRUE; + } +#endif + + /* A channel still connecting has remote_cid == 0. Emitting a disconnect + * request with a 0x0000 destination CID violates the spec (Core Spec v6.2 + * Vol 3 Part A 2.1) and the peer will not answer, leaving the CCB stuck. + * Cancel such a pending connection locally instead. */ + if (p_ccb->chnl_state != CST_OPEN || p_ccb->remote_cid == 0) { + L2C_BLE_COC_TRACE_DEBUG("cancel pending coc lcid=0x%04x state=%d", lcid, p_ccb->chnl_state); + l2cu_release_ccb(p_ccb); + return TRUE; + } + + L2C_BLE_COC_TRACE_DEBUG("disconnect lcid=0x%04x", lcid); + l2c_ble_le_coc_initiate_disc(p_ccb); + return TRUE; +} + +void l2c_ble_le_coc_data_ind(tL2C_CCB *p_ccb, BT_HDR *p_msg) +{ + UINT8 *p; + UINT16 frame_len, sdu_len, copy_len, hdr_need; + tL2CA_DATA_IND_CB *cb; + + if (p_ccb == NULL || p_msg == NULL || !p_ccb->le_coc_active) { + osi_free(p_msg); + return; + } + + if (p_ccb->chnl_state != CST_OPEN) { + L2C_BLE_COC_TRACE_WARN("data on non-open lcid=0x%04x state=%d", + p_ccb->local_cid, p_ccb->chnl_state); + osi_free(p_msg); + return; + } + + if (p_ccb->le_coc_rx_avail == 0) { + L2C_BLE_COC_TRACE_ERROR("rx credit exhausted lcid=0x%04x", p_ccb->local_cid); + l2c_ble_le_coc_initiate_disc(p_ccb); + osi_free(p_msg); + return; + } + + frame_len = p_msg->len; + p = (UINT8 *)(p_msg + 1) + p_msg->offset; + p_ccb->le_coc_rx_avail--; + + /* Core Spec v6.2 Vol 3 Part A 3.4.3: "If the payload size of any K-frame + * exceeds the receiver's MPS, the receiver shall disconnect the channel." + * frame_len is the K-frame information payload (basic L2CAP header already + * stripped), so it must not exceed the MPS we advertised for this channel. */ + if (frame_len > p_ccb->local_conn_cfg.mps) { + L2C_BLE_COC_TRACE_ERROR("K-frame %u > mps %u lcid=0x%04x", + frame_len, p_ccb->local_conn_cfg.mps, p_ccb->local_cid); + l2c_ble_le_coc_initiate_disc(p_ccb); + osi_free(p_msg); + return; + } + + if (!p_ccb->le_coc_rx_have_len) { + if (frame_len < L2C_BLE_COC_SDU_LEN_SIZE) { + L2C_BLE_COC_TRACE_ERROR("short first frame lcid=0x%04x", p_ccb->local_cid); + l2c_ble_le_coc_initiate_disc(p_ccb); + osi_free(p_msg); + return; + } + sdu_len = p[0] | (p[1] << 8); + if (sdu_len > p_ccb->local_conn_cfg.mtu) { + L2C_BLE_COC_TRACE_ERROR("SDU len %u > mtu %u", sdu_len, p_ccb->local_conn_cfg.mtu); + l2c_ble_le_coc_initiate_disc(p_ccb); + osi_free(p_msg); + return; + } + + p_ccb->le_coc_rx_sdu = (BT_HDR *)osi_malloc(sizeof(BT_HDR) + sdu_len); + if (p_ccb->le_coc_rx_sdu == NULL) { + /* Reassembly buffer OOM: disconnect instead of silently dropping. + * le_coc_rx_have_len is still FALSE here, so keeping the channel up + * would misparse the peer's subsequent continuation K-frames as new + * first frames and desync reassembly. */ + L2C_BLE_COC_TRACE_ERROR("rx SDU alloc failed lcid=0x%04x len=%u", p_ccb->local_cid, sdu_len); + l2c_ble_le_coc_initiate_disc(p_ccb); + osi_free(p_msg); + return; + } + p_ccb->le_coc_rx_sdu->offset = 0; + p_ccb->le_coc_rx_sdu->len = 0; + p_ccb->le_coc_rx_sdu_total = sdu_len; + p_ccb->le_coc_rx_sdu_rcvd = 0; + p_ccb->le_coc_rx_have_len = TRUE; + + copy_len = frame_len - L2C_BLE_COC_SDU_LEN_SIZE; + /* Core Spec v6.2 Vol 3 Part A 3.4.3: "If the sum of the payload sizes + * ... exceeds the specified SDU length, the receiver shall disconnect + * the channel." A first frame carrying more data than the declared SDU + * is malformed; disconnect instead of silently truncating (which would + * misalign reassembly against the peer's intended SDU boundaries). */ + if (copy_len > sdu_len) { + L2C_BLE_COC_TRACE_ERROR("first frame data %u > sdu %u lcid=0x%04x", + copy_len, sdu_len, p_ccb->local_cid); + l2c_ble_le_coc_initiate_disc(p_ccb); + osi_free(p_msg); + return; + } + memcpy((UINT8 *)(p_ccb->le_coc_rx_sdu + 1), p + L2C_BLE_COC_SDU_LEN_SIZE, copy_len); + p_ccb->le_coc_rx_sdu->len = copy_len; + p_ccb->le_coc_rx_sdu_rcvd = copy_len; + } else { + copy_len = frame_len; + hdr_need = p_ccb->le_coc_rx_sdu_total - p_ccb->le_coc_rx_sdu_rcvd; + /* Same 3.4.3 "shall disconnect" rule: a continuation frame that pushes + * the running total past the declared SDU length is malformed. */ + if (copy_len > hdr_need) { + L2C_BLE_COC_TRACE_ERROR("cont frame %u > remaining %u lcid=0x%04x", + copy_len, hdr_need, p_ccb->local_cid); + l2c_ble_le_coc_initiate_disc(p_ccb); + osi_free(p_msg); + return; + } + memcpy((UINT8 *)(p_ccb->le_coc_rx_sdu + 1) + p_ccb->le_coc_rx_sdu_rcvd, p, copy_len); + p_ccb->le_coc_rx_sdu->len += copy_len; + p_ccb->le_coc_rx_sdu_rcvd += copy_len; + } + + osi_free(p_msg); + + /* Return one RX credit for the K-frame just consumed. In auto-credit mode we + * batch the returns and flush when half the window has been used, or + * immediately if the window is empty, so the peer keeps a healthy credit + * pipeline and never ping-pongs at a single credit. */ + if (!p_ccb->le_coc_no_auto_credit) { + UINT16 window = p_ccb->local_conn_cfg.credits; + UINT16 flush_at = window ? ((window + 1) / 2) : 1; + + p_ccb->le_coc_rx_credits_pending++; + if (p_ccb->le_coc_rx_credits_pending >= flush_at || p_ccb->le_coc_rx_avail == 0) { + UINT16 give = p_ccb->le_coc_rx_credits_pending; + p_ccb->le_coc_rx_credits_pending = 0; + p_ccb->le_coc_rx_avail += give; + l2cble_send_flow_control_credit(p_ccb, give); + } + } else { + /* Manual mode: the stack does not return the credit now. Track each + * consumed K-frame so recv_ready (l2c_ble_le_coc_give_credits) can return + * the exact number of credits this SDU used. A multi-frame SDU consumes + * >1 credit while the app calls recv_ready once per SDU; returning a fixed + * 1 would leak (credit consumed per K-frame, returned per SDU). */ + p_ccb->le_coc_rx_manual_owed++; + + /* Deadlock breaker: if the peer's credit is now exhausted (avail == 0) + * while the current SDU is still incomplete, the app can never receive + * DATA_RECEIVED and thus never call recv_ready to replenish -> permanent + * stall. Return the owed credits now so the peer can finish this SDU. + * This only triggers for an SDU larger than the whole RX window; an SDU + * that fits the window never reaches avail == 0 mid-reassembly (it hits 0 + * only on its final frame, when it is already complete), so normal + * per-SDU backpressure is fully preserved for the supported range. */ + if (p_ccb->le_coc_rx_avail == 0 && + p_ccb->le_coc_rx_have_len && + p_ccb->le_coc_rx_sdu_rcvd < p_ccb->le_coc_rx_sdu_total) { + UINT16 give = p_ccb->le_coc_rx_manual_owed; + p_ccb->le_coc_rx_manual_owed = 0; + p_ccb->le_coc_rx_avail += give; + L2C_BLE_COC_TRACE_DEBUG("manual deadlock breaker lcid=0x%04x return %u", p_ccb->local_cid, give); + l2cble_send_flow_control_credit(p_ccb, give); + } + } + + if (p_ccb->le_coc_rx_sdu_rcvd < p_ccb->le_coc_rx_sdu_total) { + return; + } + + L2C_BLE_COC_TRACE_DEBUG("SDU complete lcid=0x%04x len=%u", p_ccb->local_cid, p_ccb->le_coc_rx_sdu_total); + + p_ccb->le_coc_rx_have_len = FALSE; + p_ccb->le_coc_rx_sdu_total = 0; + p_ccb->le_coc_rx_sdu_rcvd = 0; + + /* Transfer ownership of the completed SDU before invoking the callback: + * clear le_coc_rx_sdu first so that if the app synchronously tears the + * channel down from within DataInd_Cb, cleanup_ccb() cannot free the same + * buffer again (double free) and we never touch p_ccb after it may be gone. */ + BT_HDR *rx_sdu = p_ccb->le_coc_rx_sdu; + p_ccb->le_coc_rx_sdu = NULL; + if (p_ccb->p_rcb && p_ccb->p_rcb->api.pL2CA_DataInd_Cb) { + cb = p_ccb->p_rcb->api.pL2CA_DataInd_Cb; + (*cb)(p_ccb->local_cid, rx_sdu); + } else { + osi_free(rx_sdu); + } +} + +#endif /* BLE_L2CAP_COC_INCLUDED == TRUE */ diff --git a/components/bt/host/bluedroid/stack/l2cap/l2c_link.c b/components/bt/host/bluedroid/stack/l2cap/l2c_link.c index 1a74d33dcec..b1f06652963 100644 --- a/components/bt/host/bluedroid/stack/l2cap/l2c_link.c +++ b/components/bt/host/bluedroid/stack/l2cap/l2c_link.c @@ -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; } diff --git a/components/bt/host/bluedroid/stack/l2cap/l2c_main.c b/components/bt/host/bluedroid/stack/l2cap/l2c_main.c index 221109a1b66..f9b28cbc805 100644 --- a/components/bt/host/bluedroid/stack/l2cap/l2c_main.c +++ b/components/bt/host/bluedroid/stack/l2cap/l2c_main.c @@ -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; diff --git a/components/bt/host/bluedroid/stack/l2cap/l2c_utils.c b/components/bt/host/bluedroid/stack/l2cap/l2c_utils.c index 2cf6443dccb..978b4c37a7e 100644 --- a/components/bt/host/bluedroid/stack/l2cap/l2c_utils.c +++ b/components/bt/host/bluedroid/stack/l2cap/l2c_utils.c @@ -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 */ /******************************************************************************* diff --git a/components/bt/host/bluedroid/stack/smp/smp_act.c b/components/bt/host/bluedroid/stack/smp/smp_act.c index ba20aaeda30..cf485db8344 100644 --- a/components/bt/host/bluedroid/stack/smp/smp_act.c +++ b/components/bt/host/bluedroid/stack/smp/smp_act.c @@ -20,6 +20,9 @@ #include "device/interop.h" #include "common/bt_target.h" #include "btm_int.h" +#if (BLE_INCLUDED == TRUE && SMP_INCLUDED == TRUE && BLE_PERIPH_PSEUDO_ADDR_BOND == TRUE) +#include "btm_ble_pseudo.h" +#endif #include "stack/l2c_api.h" #include "smp_int.h" #if (SMP_CRYPTO_MBEDTLS == TRUE) @@ -522,8 +525,17 @@ void smp_proc_sec_grant(tSMP_CB *p_cb, tSMP_INT_DATA *p_data) *******************************************************************************/ void smp_proc_pair_fail(tSMP_CB *p_cb, tSMP_INT_DATA *p_data) { - SMP_TRACE_DEBUG("%s", __func__); - p_cb->status = *(UINT8 *)p_data; + UINT8 reason = *(UINT8 *)p_data; + + SMP_TRACE_DEBUG("%s reason=0x%02x", __func__, reason); + /* A peer may send a reserved or out-of-range reason code; normalize it so + * upper layers always receive a defined pairing failure status. */ + if (reason == SMP_SUCCESS || reason > SMP_MAX_FAIL_RSN_PER_SPEC) { + SMP_TRACE_WARNING("%s invalid pairing fail reason 0x%02x", __func__, reason); + reason = SMP_PAIR_FAIL_UNKNOWN; + } + p_cb->status = reason; + p_cb->failure = reason; } /******************************************************************************* @@ -1206,6 +1218,28 @@ void smp_proc_id_addr(tSMP_CB *p_cb, tSMP_INT_DATA *p_data) } #endif ///BLE_INCLUDED == TRUE +#if (BLE_INCLUDED == TRUE && SMP_INCLUDED == TRUE && BLE_PERIPH_PSEUDO_ADDR_BOND == TRUE) + /* Dual-identity bond isolation: the link may have been keyed earlier from a + * transient RPA. Now that the peer's stable Identity Address is known, + * re-derive the pseudo from (local, Identity) and re-key the link so the + * stored bond is reproducible across the peer's future RPA rotations. Keep + * smp_cb.pairing_bda consistent so the in-flight pairing continues. */ + { + tACL_CONN *p_acl = btm_bda_to_acl(p_cb->pairing_bda, BT_TRANSPORT_LE); + BLE_PSEUDO_DBG("smp PID: pairing_bda=" BLE_PSEUDO_BDA_FMT " acl=%p id_addr=" BLE_PSEUDO_BDA_FMT, + BLE_PSEUDO_BDA(p_cb->pairing_bda), p_acl, BLE_PSEUDO_BDA(pid_key.static_addr)); + if (p_acl != NULL) { + BD_ADDR new_pseudo; + if (btm_ble_pseudo_apply_identity(p_acl->hci_handle, pid_key.static_addr, + pid_key.addr_type, new_pseudo)) { + memcpy(p_cb->pairing_bda, new_pseudo, BD_ADDR_LEN); + BLE_PSEUDO_DBG("smp PID: pairing_bda updated -> " BLE_PSEUDO_BDA_FMT, + BLE_PSEUDO_BDA(p_cb->pairing_bda)); + } + } + } +#endif + smp_key_distribution_by_transport(p_cb, NULL); } diff --git a/components/bt/host/bluedroid/stack/smp/smp_api.c b/components/bt/host/bluedroid/stack/smp/smp_api.c index 7a592b775d6..e91f4fe619b 100644 --- a/components/bt/host/bluedroid/stack/smp/smp_api.c +++ b/components/bt/host/bluedroid/stack/smp/smp_api.c @@ -443,6 +443,17 @@ void SMP_OobDataReply(BD_ADDR bd_addr, tSMP_STATUS res, UINT8 len, UINT8 *p_data return; } + /* Reject an OOB reply that does not match the device currently pairing. */ + if (memcmp(bd_addr, p_cb->pairing_bda, BD_ADDR_LEN) != 0) { + SMP_TRACE_ERROR("%s() - Wrong BD Addr", __func__); + return; + } + + if (btm_find_dev(bd_addr) == NULL) { + SMP_TRACE_ERROR("%s() - no dev CB", __func__); + return; + } + if (res != SMP_SUCCESS || len == 0 || !p_data) { SMP_TRACE_ERROR("%s pairing failed, res=0x%x len=%u p_data=%p", __func__, res, len, p_data); diff --git a/components/bt/host/bluedroid/stack/smp/smp_utils.c b/components/bt/host/bluedroid/stack/smp/smp_utils.c index 09b62119a3d..5d2823c1699 100644 --- a/components/bt/host/bluedroid/stack/smp/smp_utils.c +++ b/components/bt/host/bluedroid/stack/smp/smp_utils.c @@ -36,6 +36,9 @@ #include "smp_int.h" #include "device/controller.h" #include "btm_int.h" +#if (BLE_INCLUDED == TRUE && SMP_INCLUDED == TRUE && BLE_PERIPH_PSEUDO_ADDR_BOND == TRUE) +#include "btm_ble_pseudo.h" +#endif #include "common/bte_appl.h" #define SMP_PAIRING_REQ_SIZE 7 @@ -1530,6 +1533,10 @@ void smp_collect_local_ble_address(UINT8 *le_addr, tSMP_CB *p_cb) BTM_ReadConnectionAddr( p_cb->pairing_bda, bda, &addr_type); BDADDR_TO_STREAM(p, bda); UINT8_TO_STREAM(p, addr_type); +#if (BLE_INCLUDED == TRUE && SMP_INCLUDED == TRUE && BLE_PERIPH_PSEUDO_ADDR_BOND == TRUE) + BLE_PSEUDO_DBG("smp local addr for f5/f6 = " BLE_PSEUDO_BDA_FMT " type %u (pairing_bda " BLE_PSEUDO_BDA_FMT ")", + BLE_PSEUDO_BDA(bda), addr_type, BLE_PSEUDO_BDA(p_cb->pairing_bda)); +#endif } /******************************************************************************* @@ -1557,6 +1564,10 @@ void smp_collect_peer_ble_address(UINT8 *le_addr, tSMP_CB *p_cb) BDADDR_TO_STREAM(p, bda); UINT8_TO_STREAM(p, addr_type); +#if (BLE_INCLUDED == TRUE && SMP_INCLUDED == TRUE && BLE_PERIPH_PSEUDO_ADDR_BOND == TRUE) + BLE_PSEUDO_DBG("smp peer addr for f5/f6 = " BLE_PSEUDO_BDA_FMT " type %u (pairing_bda " BLE_PSEUDO_BDA_FMT ")", + BLE_PSEUDO_BDA(bda), addr_type, BLE_PSEUDO_BDA(p_cb->pairing_bda)); +#endif } /******************************************************************************* diff --git a/docs/conf_common.py b/docs/conf_common.py index 3ccc17e4473..b1b3133875c 100644 --- a/docs/conf_common.py +++ b/docs/conf_common.py @@ -43,6 +43,10 @@ BLE_DOCS = ['api-guides/ble/index.rst', 'api-reference/bluetooth/nimble/index.rst', 'migration-guides/release-5.x/5.0/bluetooth-low-energy.rst'] +BLE_DUAL_IDENTITY_DOCS = [ + 'api-guides/ble/bluedroid-dual-identity-host-dev.rst', +] + BLE_MESH_DOCS = ['api-guides/esp-ble-mesh/ble-mesh-index.rst', 'api-guides/esp-ble-mesh/ble-mesh-feature-list.rst', 'api-guides/esp-ble-mesh/ble-mesh-terminology.rst', @@ -205,6 +209,7 @@ ESP32P4_DOCS = ['api-reference/system/ipc.rst', # format: {tag needed to include: documents to included}, tags are parsed from sdkconfig and peripheral_caps.h headers conditional_include_dict = {'SOC_BT_SUPPORTED':BT_DOCS, 'SOC_BLE_SUPPORTED':BLE_DOCS, + 'SOC_BLE_50_SUPPORTED':BLE_DUAL_IDENTITY_DOCS, 'SOC_BLE_MESH_SUPPORTED':BLE_MESH_DOCS, 'SOC_BLUFI_SUPPORTED':BLUFI_DOCS, 'SOC_WIFI_SUPPORTED':WIFI_DOCS, diff --git a/docs/en/api-guides/ble/bluedroid-dual-identity-host-dev.rst b/docs/en/api-guides/ble/bluedroid-dual-identity-host-dev.rst new file mode 100644 index 00000000000..cae025296a1 --- /dev/null +++ b/docs/en/api-guides/ble/bluedroid-dual-identity-host-dev.rst @@ -0,0 +1,25 @@ +Bluedroid Host Support for Dual Local Identities +================================================ + +:link_to_translation:`zh_CN:[中文]` + +Introduction +------------ + +When a single peer phone connects to an ESP32 peripheral through two different **local identities** (for example, a Public address and a fixed Static Random address from two extended advertising sets), the default Bluedroid Host treats both links as the same peer. Bonds, LTK, and NVS sections can overwrite each other. + +Enable :ref:`BT_BLE_PERIPH_PSEUDO_ADDR_BOND ` to derive a Host-internal **pseudo address** ``f(local_identity, peer)`` per link. The application sees two different ``remote_bda`` values for the same phone, while SMP and the controller still use the real peer identity on air. + +Example +------- + +See :example:`ble50_dual_identity_server ` for a Bluetooth LE 5.0 peripheral that advertises two identities concurrently, pairs with both, and keeps **isolated bonds per (local, peer) pair**. + +Application Notes +----------------- + +- Use **conn_id** as the link key in GATTS calls; do not use ``remote_bda`` to tell links apart. +- ``remote_bda`` in GAP/GATTS events is the **pseudo address** when this feature is enabled. +- Call ``esp_ble_gap_get_conn_identity()`` while connected to recover the real peer and local identity. +- Use ``esp_ble_gap_remove_bond_for_identity()`` to delete one identity's bond without affecting the other. +- For controller operations (whitelist, directed advertising), use the **real peer** address, never the pseudo. diff --git a/docs/en/api-guides/ble/index.rst b/docs/en/api-guides/ble/index.rst index 584571c555b..ffaf7d5f517 100644 --- a/docs/en/api-guides/ble/index.rst +++ b/docs/en/api-guides/ble/index.rst @@ -15,6 +15,7 @@ Overview ble-qualification Low Power Mode Introduction ble-multiconnection-guide + :SOC_BLE_50_SUPPORTED: bluedroid-dual-identity-host-dev *************** Get Started diff --git a/docs/zh_CN/api-guides/ble/bluedroid-dual-identity-host-dev.rst b/docs/zh_CN/api-guides/ble/bluedroid-dual-identity-host-dev.rst new file mode 100644 index 00000000000..27c12a2b76c --- /dev/null +++ b/docs/zh_CN/api-guides/ble/bluedroid-dual-identity-host-dev.rst @@ -0,0 +1,25 @@ +Bluedroid 双本地身份 Host 开发说明 +===================================== + +:link_to_translation:`en:[English]` + +简介 +---- + +当同一部手机通过两个不同的**本地身份**(例如来自两个扩展广播集的 Public 地址与固定的 Static Random 地址)连接到 ESP32 外围设备时,默认 Bluedroid Host 会将两条链路视为同一对端。Bond、LTK 与 NVS 区段可能相互覆盖。 + +启用 :ref:`BT_BLE_PERIPH_PSEUDO_ADDR_BOND `\ 后,Host 会为每条链路派生内部 **伪地址 (pseudo address)** ``f(local_identity, peer)``。应用层对同一手机会看到两个不同的 ``remote_bda``,而 SMP 与控制器仍使用空口真实对端身份。 + +示例 +---- + +请参阅 :example:`ble50_dual_identity_server `\ :该 Bluetooth LE 5.0 外围设备同时广播两个身份、分别配对,并为每个 **(local, peer)** 对保留**独立的 bond**。 + +应用要点 +-------- + +- 在 GATTS 调用中以 **conn_id** 作为链路键;不要用 ``remote_bda`` 区分链路。 +- 启用本特性后,GAP/GATTS 事件中的 ``remote_bda`` 为 **pseudo 地址**。 +- 连接态下调用 ``esp_ble_gap_get_conn_identity()`` 可恢复真实对端与本地身份。 +- 使用 ``esp_ble_gap_remove_bond_for_identity()`` 只删除一路身份的 bond,不影响另一路。 +- 控制器相关操作(白名单、定向广播)须使用**真实对端**地址,切勿使用伪地址。 diff --git a/docs/zh_CN/api-guides/ble/index.rst b/docs/zh_CN/api-guides/ble/index.rst index 16895d38cce..858c1d427d5 100644 --- a/docs/zh_CN/api-guides/ble/index.rst +++ b/docs/zh_CN/api-guides/ble/index.rst @@ -15,6 +15,7 @@ ble-qualification 低功耗模式介绍 ble-multiconnection-guide + :SOC_BLE_50_SUPPORTED: bluedroid-dual-identity-host-dev ********** 快速入门 diff --git a/examples/bluetooth/bluedroid/ble_50/ble50_dual_identity_server/CMakeLists.txt b/examples/bluetooth/bluedroid/ble_50/ble50_dual_identity_server/CMakeLists.txt new file mode 100644 index 00000000000..f30129c3669 --- /dev/null +++ b/examples/bluetooth/bluedroid/ble_50/ble50_dual_identity_server/CMakeLists.txt @@ -0,0 +1,8 @@ +# The following lines of boilerplate have to be in your project's CMakeLists +# in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.22) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +# "Trim" the build. Include the minimal set of components, main, and anything it depends on. +idf_build_set_property(MINIMAL_BUILD ON) +project(ble50_dual_identity_server) diff --git a/examples/bluetooth/bluedroid/ble_50/ble50_dual_identity_server/README.md b/examples/bluetooth/bluedroid/ble_50/ble50_dual_identity_server/README.md new file mode 100644 index 00000000000..e8b31350f3d --- /dev/null +++ b/examples/bluetooth/bluedroid/ble_50/ble50_dual_identity_server/README.md @@ -0,0 +1,77 @@ +| Supported Targets | ESP32-C2 | ESP32-C3 | ESP32-C6 | ESP32-H2 | ESP32-S3 | +| ----------------- | -------- | -------- | -------- | -------- | -------- | + +# BLE 5.0 Dual Local-Identity Security Server + +This example demonstrates **two local identities advertising and being connected/encrypted at the same time** on a BLE 5.0 peripheral, with **isolated bonds per identity**. + +It relies on the Host feature `CONFIG_BT_BLE_PERIPH_PSEUDO_ADDR_BOND`: the Bluedroid Host derives a per-link **pseudo address** from `f(local_identity, peer)`, so that one phone connecting through two different local identities is treated as two independent peers (separate device record, LTK and NVS bond section). + +## What it does + +* Advertises **two connectable extended advertising sets** simultaneously: + * Adv set 0 — **Public** address (identity A, name `ESP_DUAL_PUBLIC_A`) + * Adv set 1 — **fixed Static Random** address (identity B, name `ESP_DUAL_RANDOM_B`) +* Runs a minimal GATT server with one characteristic that requires an **encrypted** link to read/write. +* On each connection it starts pairing (`SC + MITM + BOND`, static passkey `123456`). +* On `AUTH_CMPL` it prints the link's **real peer** and **local identity** via `esp_ble_gap_get_conn_identity()`, and lists bonded devices. + +## Key points for the application + +* The `remote_bda` reported in `ESP_GATTS_CONNECT_EVT` is a **Host pseudo address**, not the phone's real MAC. The **same phone shows up as two different addresses**, one per identity. +* **Always use `conn_id` as the link key.** Do not use `remote_bda` to tell links apart. +* Use `esp_ble_gap_get_conn_identity(pseudo, &id)` to recover the real peer MAC and the local identity. +* Use `esp_ble_gap_remove_bond_for_identity(local, local_type, peer, peer_type)` to delete a single identity's bond without affecting the other. +* The Static Random address used for identity B is **hard-coded and fixed** (`s_identity_b_addr`) so the per-identity bond bucket survives reboots. If you randomize it per boot, reconnection cannot match the stored bond. + +### Which address to pass to GAP/GATTS APIs + +* **Link/bond operations** (`esp_ble_set_encryption`, `esp_ble_gap_disconnect`, `esp_ble_gap_security_rsp`, `esp_ble_confirm_reply`, `esp_ble_passkey_reply`, `esp_ble_gap_update_conn_params`, `esp_ble_gap_read_rssi`, `esp_ble_remove_bond_device`): pass back **exactly the `remote_bda` the event gave you** (the pseudo). Never build the real MAC yourself. +* **Telling links apart / GATTS data** (`esp_ble_gatts_send_indicate`, `send_response`, `close`, …): use **`conn_id`**, not the address. +* **Controller-level operations** (`esp_ble_gap_update_whitelist`, resolving list, directed advertising, `esp_ble_gatts_open`): use the **real peer Identity/RPA**, never the pseudo (the pseudo never goes on air). +* **Need the real MAC**: call `esp_ble_gap_get_real_peer_addr()` / `esp_ble_gap_get_conn_identity()`. + +See the Pseudo design guide **§8.2.2** for the full per-API table. + +## How to test + +1. `idf.py set-target esp32c3` (or s3/c6/h2), then `idf.py flash monitor`. +2. On a phone, scan: you will see **two devices** — `ESP_DUAL_PUBLIC_A` and `ESP_DUAL_RANDOM_B`. +3. Connect and pair to `..._A` (passkey `123456`). Read/write the characteristic — succeeds because the link is encrypted. +4. While still connected to A, connect and pair to `..._B` from the **same phone**. +5. Observe in the log that both links have the **same real peer** but **different pseudo** addresses and **different local identities**, and that `Bonded devices` shows **two** independent entries. +6. Disconnect/reconnect either identity — it re-encrypts from its own stored LTK independently. +7. Remove one bond (e.g. forget `..._B` on the phone, or call `esp_ble_gap_remove_bond_for_identity` for B) — the other identity's bond and encrypted reconnect are unaffected. + +## Example log (abridged) + +``` +I (xxx) DUAL_ID: Pseudo-address dual-identity bond isolation ENABLED +I (xxx) DUAL_ID: Advertising as two identities: Public (A) + Static Random c1:22:33:44:55:66 (B) +[PSEUDO] conn_complete[sync]: keyed handle=0x0 real= -> pseudo= +I (xxx) DUAL_ID: CONNECT conn_id 0, remote(pseudo) c4:..:a +I (xxx) DUAL_ID: AUTH_CMPL identity: real peer , local +[PSEUDO] conn_complete[sync]: keyed handle=0x1 real= -> pseudo= +I (xxx) DUAL_ID: CONNECT conn_id 1, remote(pseudo) fb:..:b +I (xxx) DUAL_ID: AUTH_CMPL identity: real peer , local c1:22:33:44:55:66 +I (xxx) DUAL_ID: Bonded devices: 2 (each entry is a (local,peer) identity = one pseudo) +``` + +The `[PSEUDO] ...` lines come from the Host feature's debug logging, emitted through the standard BTM trace at debug level. They make it easy to follow the dual-identity flow during bring-up; raise the Bluetooth log level (or lower the BTM trace level) to suppress them. + +## Notes + +* Requires a BLE 5.0 capable chip (Extended Advertising). ESP32 (original) does not support it. +* If `CONFIG_BT_BLE_PERIPH_PSEUDO_ADDR_BOND` is disabled, the example still builds and runs but the two identities of the same phone will **share one bond** and overwrite each other — a startup warning is printed. + +## Verified scenarios (ESP32-C3, Android phone using RPA) + +* Connect Public_A and Static-Random_B from the same phone, pair/bond each → `Bonded devices: 2` (two isolated pseudo bonds). +* Delete both bonds on the phone, reconnect A and B → fresh re-pairing succeeds for both, still two isolated bonds. +* Disconnect all, reconnect A → **re-encrypts directly from the stored LTK (no re-pairing)**; the two identities' bonds never interfere. + +The Host-side feature internals and the fixes behind these scenarios (peer-RPA Identity derivation, SC `active_remote_addr` using the real on-air RPA, disabling same-identity NVS de-dup, duplicate device-record cleanup, BTA `device_list` removal by handle) are documented in the Pseudo design guide, section **§24 (implementation notes)**: `docs/zh_CN/api-guides/ble/bluedroid-periph-pseudo-addr-dev-guide-v0.10.md`. + +## Known limitation + +The two bonds share the same peer IRK, so adding that IRK to the controller resolving list a second time is rejected (`Add resolving list error 18`). This does **not** affect encrypted reconnection here (RPA resolution is done in the Host), but it matters if you rely on the controller resolving list, white list, or directed advertising. diff --git a/examples/bluetooth/bluedroid/ble_50/ble50_dual_identity_server/main/CMakeLists.txt b/examples/bluetooth/bluedroid/ble_50/ble50_dual_identity_server/main/CMakeLists.txt new file mode 100644 index 00000000000..4ed4bbf0cbb --- /dev/null +++ b/examples/bluetooth/bluedroid/ble_50/ble50_dual_identity_server/main/CMakeLists.txt @@ -0,0 +1,3 @@ +idf_component_register(SRCS "ble50_dual_identity_server.c" + PRIV_REQUIRES bt nvs_flash + INCLUDE_DIRS ".") diff --git a/examples/bluetooth/bluedroid/ble_50/ble50_dual_identity_server/main/ble50_dual_identity_server.c b/examples/bluetooth/bluedroid/ble_50/ble50_dual_identity_server/main/ble50_dual_identity_server.c new file mode 100644 index 00000000000..7a8b6d590a8 --- /dev/null +++ b/examples/bluetooth/bluedroid/ble_50/ble50_dual_identity_server/main/ble50_dual_identity_server.c @@ -0,0 +1,488 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ + +/* + * BLE 5.0 dual local-identity GATT server. + * + * The device advertises TWO connectable extended advertising sets at the same + * time, each using a different local identity: + * + * - Adv set 0 : Public address -> identity A + * - Adv set 1 : fixed Static Random addr -> identity B + * + * A single phone can connect to BOTH identities simultaneously. With + * CONFIG_BT_BLE_PERIPH_PSEUDO_ADDR_BOND enabled, the Host derives a distinct + * pseudo address per (local_identity, peer) pair, so the two links get + * independent device records, LTKs and NVS bond sections. Deleting one + * identity's bond never affects the other. + * + * IMPORTANT for the application: + * - remote_bda reported here is the Host PSEUDO (the same phone shows up as + * two different addresses, one per identity). Use conn_id as the link key. + * - Call esp_ble_gap_get_conn_identity(pseudo, &id) to recover the real peer + * MAC and the local identity that the link was established with. + */ + +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_system.h" +#include "esp_log.h" +#include "nvs_flash.h" +#include "esp_bt.h" + +#include "esp_gap_ble_api.h" +#include "esp_gatts_api.h" +#include "esp_bt_defs.h" +#include "esp_bt_main.h" +#include "ble50_dual_identity_server.h" + +#define TAG "DUAL_ID" + +#define ESP_APP_ID 0x55 +#define SVC_INST_ID 0 +#define GATTS_DEMO_CHAR_VAL_LEN_MAX 0x40 +#define MAX_CONN 3 +#define NOTIFY_ENABLE 0x0001 + +/* Two simultaneously-advertising connectable extended advertising sets. */ +#define ADV_HANDLE_PUBLIC 0 /* identity A : Public */ +#define ADV_HANDLE_RANDOM 1 /* identity B : Static Random */ +#define NUM_ADV_SET 2 + +#ifndef MIN +#define MIN(a, b) (((a) < (b)) ? (a) : (b)) +#endif + +/* A FIXED static random address for identity B. It MUST be persistent across + * reboots (top two bits = 0b11) so the per-identity bond bucket matches on + * reconnect. Do NOT generate it randomly at every boot. */ +static esp_bd_addr_t s_identity_b_addr = {0xC1, 0x22, 0x33, 0x44, 0x55, 0x66}; + +static uint16_t s_handle_table[HRS_IDX_NB]; + +/* Track which advertising sets are currently on-air so that on a disconnect we + * only restart the set(s) that had been consumed by a connection. */ +static bool s_adv_on_air[NUM_ADV_SET]; + +/* ---- advertising data: one human-readable name per identity ---- */ +static uint8_t adv_data_public[] = { + 0x02, ESP_BLE_AD_TYPE_FLAG, 0x06, + 0x12, ESP_BLE_AD_TYPE_NAME_CMPL, + 'E', 'S', 'P', '_', 'D', 'U', 'A', 'L', '_', 'P', 'U', 'B', 'L', 'I', 'C', '_', 'A', +}; + +static uint8_t adv_data_random[] = { + 0x02, ESP_BLE_AD_TYPE_FLAG, 0x06, + 0x12, ESP_BLE_AD_TYPE_NAME_CMPL, + 'E', 'S', 'P', '_', 'D', 'U', 'A', 'L', '_', 'R', 'A', 'N', 'D', 'O', 'M', '_', 'B', +}; + +static esp_ble_gap_ext_adv_t s_ext_adv[NUM_ADV_SET] = { + [0] = {ADV_HANDLE_PUBLIC, 0, 0}, + [1] = {ADV_HANDLE_RANDOM, 0, 0}, +}; + +static esp_ble_gap_ext_adv_params_t s_adv_params_public = { + .type = ESP_BLE_GAP_SET_EXT_ADV_PROP_CONNECTABLE, + .interval_min = ESP_BLE_GAP_ADV_ITVL_MS(40), + .interval_max = ESP_BLE_GAP_ADV_ITVL_MS(40), + .channel_map = ADV_CHNL_ALL, + .filter_policy = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY, + .primary_phy = ESP_BLE_GAP_PHY_1M, + .max_skip = 0, + .secondary_phy = ESP_BLE_GAP_PHY_2M, + .sid = 0, + .scan_req_notif = false, + .own_addr_type = BLE_ADDR_TYPE_PUBLIC, + .tx_power = EXT_ADV_TX_PWR_NO_PREFERENCE, +}; + +static esp_ble_gap_ext_adv_params_t s_adv_params_random = { + .type = ESP_BLE_GAP_SET_EXT_ADV_PROP_CONNECTABLE, + .interval_min = ESP_BLE_GAP_ADV_ITVL_MS(40), + .interval_max = ESP_BLE_GAP_ADV_ITVL_MS(40), + .channel_map = ADV_CHNL_ALL, + .filter_policy = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY, + .primary_phy = ESP_BLE_GAP_PHY_1M, + .max_skip = 0, + .secondary_phy = ESP_BLE_GAP_PHY_2M, + .sid = 1, + .scan_req_notif = false, + .own_addr_type = BLE_ADDR_TYPE_RANDOM, + .tx_power = EXT_ADV_TX_PWR_NO_PREFERENCE, +}; + +/* ---- minimal GATT database (one read/write/notify characteristic) ---- */ +static const uint16_t primary_service_uuid = ESP_GATT_UUID_PRI_SERVICE; +static const uint16_t character_declaration_uuid = ESP_GATT_UUID_CHAR_DECLARE; +static const uint16_t character_client_config_uuid = ESP_GATT_UUID_CHAR_CLIENT_CONFIG; +static const uint16_t GATTS_SERVICE_UUID_TEST = 0x00FF; +static const uint16_t GATTS_CHAR_UUID_TEST_A = 0xFF01; +static const uint8_t char_prop_read_write_notify = ESP_GATT_CHAR_PROP_BIT_WRITE | ESP_GATT_CHAR_PROP_BIT_READ | ESP_GATT_CHAR_PROP_BIT_NOTIFY; +static const uint8_t ccc[2] = {0x00, 0x00}; +static const uint8_t char_value[4] = {0x11, 0x22, 0x33, 0x44}; +#define CHAR_DECLARATION_SIZE (sizeof(uint8_t)) + +static const esp_gatts_attr_db_t gatt_db[HRS_IDX_NB] = { + [IDX_SVC] = + {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&primary_service_uuid, ESP_GATT_PERM_READ, + sizeof(uint16_t), sizeof(GATTS_SERVICE_UUID_TEST), (uint8_t *)&GATTS_SERVICE_UUID_TEST}}, + + [IDX_CHAR_A] = + {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&character_declaration_uuid, ESP_GATT_PERM_READ, + CHAR_DECLARATION_SIZE, CHAR_DECLARATION_SIZE, (uint8_t *)&char_prop_read_write_notify}}, + + /* Require encryption to read/write so the link must be paired/encrypted. */ + [IDX_CHAR_VAL_A] = + {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&GATTS_CHAR_UUID_TEST_A, + ESP_GATT_PERM_READ_ENCRYPTED | ESP_GATT_PERM_WRITE_ENCRYPTED, + GATTS_DEMO_CHAR_VAL_LEN_MAX, sizeof(char_value), (uint8_t *)char_value}}, + + [IDX_CHAR_CFG_A] = + {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&character_client_config_uuid, + ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE, + sizeof(uint16_t), sizeof(ccc), (uint8_t *)ccc}}, +}; + +static esp_gatt_if_t s_gatts_if = ESP_GATT_IF_NONE; + +/* Per-link notify subscription (CCC). Indexed by conn_id. */ +static bool s_notify_enabled[MAX_CONN]; + +/* -------------------------------------------------------------------------- */ + +static const char *auth_req_to_str(esp_ble_auth_req_t auth_req) +{ + switch (auth_req) { + case ESP_LE_AUTH_NO_BOND: return "NO_BOND"; + case ESP_LE_AUTH_BOND: return "BOND"; + case ESP_LE_AUTH_REQ_MITM: return "MITM"; + case ESP_LE_AUTH_REQ_BOND_MITM: return "BOND_MITM"; + case ESP_LE_AUTH_REQ_SC_ONLY: return "SC_ONLY"; + case ESP_LE_AUTH_REQ_SC_BOND: return "SC_BOND"; + case ESP_LE_AUTH_REQ_SC_MITM: return "SC_MITM"; + case ESP_LE_AUTH_REQ_SC_MITM_BOND: return "SC_MITM_BOND"; + default: return "INVALID"; + } +} + +static void show_bonded_devices(void) +{ + int dev_num = esp_ble_get_bond_device_num(); + if (dev_num <= 0) { + ESP_LOGI(TAG, "Bonded devices: 0"); + return; + } + esp_ble_bond_dev_t *list = malloc(sizeof(esp_ble_bond_dev_t) * dev_num); + if (!list) { + return; + } + esp_ble_get_bond_device_list(&dev_num, list); + ESP_LOGI(TAG, "Bonded devices: %d (each entry is a (local,peer) identity = one pseudo)", dev_num); + for (int i = 0; i < dev_num; i++) { + ESP_LOGI(TAG, " [%d] pseudo "ESP_BD_ADDR_STR"", i, ESP_BD_ADDR_HEX(list[i].bd_addr)); + } + free(list); +} + +#if CONFIG_BT_BLE_PERIPH_PSEUDO_ADDR_BOND +static void log_conn_identity(const char *tag, esp_bd_addr_t pseudo) +{ + esp_ble_conn_identity_t id; + if (esp_ble_gap_get_conn_identity(pseudo, &id) == ESP_OK) { + ESP_LOGI(TAG, "%s identity: real peer "ESP_BD_ADDR_STR" (type %u), local "ESP_BD_ADDR_STR" (type %u)", + tag, + ESP_BD_ADDR_HEX(id.peer_addr), id.peer_addr_type, + ESP_BD_ADDR_HEX(id.local_addr), id.local_addr_type); + } else { + ESP_LOGW(TAG, "%s identity: pseudo not finalized yet (link may still be on real peer)", tag); + } +} +#endif + +static void request_ext_adv_start(uint8_t inst) +{ + if (inst >= NUM_ADV_SET || s_adv_on_air[inst]) { + return; + } + esp_err_t ret = esp_ble_gap_ext_adv_start(1, &s_ext_adv[inst]); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "ext_adv_start enqueue failed for instance %u: %s", inst, esp_err_to_name(ret)); + } +} + +static void start_idle_adv_sets(void) +{ + for (int i = 0; i < NUM_ADV_SET; i++) { + request_ext_adv_start(i); + } +} + +static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) +{ + switch (event) { + case ESP_GAP_BLE_EXT_ADV_SET_RAND_ADDR_COMPLETE_EVT: + ESP_LOGI(TAG, "Set random addr (identity B) done, status %d", param->ext_adv_set_rand_addr.status); + if (param->ext_adv_set_rand_addr.status == ESP_BT_STATUS_SUCCESS) { + esp_ble_gap_config_ext_adv_data_raw(ADV_HANDLE_RANDOM, sizeof(adv_data_random), adv_data_random); + } else { + ESP_LOGE(TAG, "Identity B random addr failed, skip adv data/start"); + } + break; + case ESP_GAP_BLE_EXT_ADV_SET_PARAMS_COMPLETE_EVT: + ESP_LOGI(TAG, "Ext adv params set, instance %u, status %d", + param->ext_adv_set_params.instance, param->ext_adv_set_params.status); + if (param->ext_adv_set_params.status != ESP_BT_STATUS_SUCCESS) { + ESP_LOGE(TAG, "Ext adv params failed for instance %u, skip downstream setup", + param->ext_adv_set_params.instance); + break; + } + if (param->ext_adv_set_params.instance == ADV_HANDLE_PUBLIC) { + esp_ble_gap_config_ext_adv_data_raw(ADV_HANDLE_PUBLIC, sizeof(adv_data_public), adv_data_public); + esp_ble_gap_ext_adv_set_params(ADV_HANDLE_RANDOM, &s_adv_params_random); + } else if (param->ext_adv_set_params.instance == ADV_HANDLE_RANDOM) { + esp_ble_gap_ext_adv_set_rand_addr(ADV_HANDLE_RANDOM, s_identity_b_addr); + } + break; + case ESP_GAP_BLE_EXT_ADV_DATA_SET_COMPLETE_EVT: + ESP_LOGI(TAG, "Ext adv data set, status %d, instance %u", + param->ext_adv_data_set.status, param->ext_adv_data_set.instance); + if (param->ext_adv_data_set.status == ESP_BT_STATUS_SUCCESS) { + request_ext_adv_start(param->ext_adv_data_set.instance); + } + break; + case ESP_GAP_BLE_EXT_ADV_START_COMPLETE_EVT: + ESP_LOGI(TAG, "Ext adv start, status %d, instance_num %u", + param->ext_adv_start.status, param->ext_adv_start.instance_num); + for (uint8_t j = 0; j < param->ext_adv_start.instance_num; j++) { + uint8_t inst = param->ext_adv_start.instance[j]; + if (inst >= NUM_ADV_SET) { + continue; + } + if (param->ext_adv_start.status == ESP_BT_STATUS_SUCCESS) { + s_adv_on_air[inst] = true; + } else { + s_adv_on_air[inst] = false; + ESP_LOGE(TAG, "Ext adv start failed for instance %u", inst); + } + } + break; + case ESP_GAP_BLE_ADV_TERMINATED_EVT: + ESP_LOGI(TAG, "Adv terminated: instance %u status 0x%x conn_idx %u", + param->adv_terminate.adv_instance, param->adv_terminate.status, + param->adv_terminate.conn_idx); + if (param->adv_terminate.adv_instance < NUM_ADV_SET) { + /* This set produced a connection -> it stopped advertising. */ + s_adv_on_air[param->adv_terminate.adv_instance] = false; + } + break; + case ESP_GAP_BLE_SEC_REQ_EVT: + esp_ble_gap_security_rsp(param->ble_security.ble_req.bd_addr, true); + break; + case ESP_GAP_BLE_NC_REQ_EVT: + esp_ble_confirm_reply(param->ble_security.ble_req.bd_addr, true); + break; + case ESP_GAP_BLE_PASSKEY_NOTIF_EVT: + ESP_LOGI(TAG, "Passkey notify: %06" PRIu32, param->ble_security.key_notif.passkey); + break; + case ESP_GAP_BLE_KEY_EVT: + ESP_LOGI(TAG, "Key exchanged on link "ESP_BD_ADDR_STR" type %d", + ESP_BD_ADDR_HEX(param->ble_security.ble_key.bd_addr), + param->ble_security.ble_key.key_type); + break; + case ESP_GAP_BLE_AUTH_CMPL_EVT: { + esp_bd_addr_t pseudo; + memcpy(pseudo, param->ble_security.auth_cmpl.bd_addr, sizeof(esp_bd_addr_t)); + if (param->ble_security.auth_cmpl.success) { + ESP_LOGI(TAG, "Pairing OK on link "ESP_BD_ADDR_STR", auth %s", + ESP_BD_ADDR_HEX(pseudo), + auth_req_to_str(param->ble_security.auth_cmpl.auth_mode)); +#if CONFIG_BT_BLE_PERIPH_PSEUDO_ADDR_BOND + log_conn_identity("AUTH_CMPL", pseudo); +#endif + } else { + ESP_LOGE(TAG, "Pairing FAILED on link "ESP_BD_ADDR_STR", reason 0x%x", + ESP_BD_ADDR_HEX(pseudo), param->ble_security.auth_cmpl.fail_reason); + } + show_bonded_devices(); + break; + } + case ESP_GAP_BLE_REMOVE_BOND_DEV_COMPLETE_EVT: + ESP_LOGI(TAG, "Bond removed, status %d, pseudo "ESP_BD_ADDR_STR"", + param->remove_bond_dev_cmpl.status, + ESP_BD_ADDR_HEX(param->remove_bond_dev_cmpl.bd_addr)); + break; + case ESP_GAP_BLE_UPDATE_CONN_PARAMS_EVT: + ESP_LOGI(TAG, "Conn params update, link "ESP_BD_ADDR_STR", status %d, " + "conn_int %u, latency %u, timeout %u", + ESP_BD_ADDR_HEX(param->update_conn_params.bda), + param->update_conn_params.status, + param->update_conn_params.conn_int, + param->update_conn_params.latency, + param->update_conn_params.timeout); + break; + case ESP_GAP_BLE_SET_PKT_LENGTH_COMPLETE_EVT: + ESP_LOGI(TAG, "Data length update, status %d, rx %u, tx %u", + param->pkt_data_length_cmpl.status, + param->pkt_data_length_cmpl.params.rx_len, + param->pkt_data_length_cmpl.params.tx_len); + break; + default: + break; + } +} + +static void gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, + esp_ble_gatts_cb_param_t *param) +{ + switch (event) { + case ESP_GATTS_REG_EVT: + if (param->reg.status == ESP_GATT_OK) { + s_gatts_if = gatts_if; + esp_ble_gatts_create_attr_tab(gatt_db, gatts_if, HRS_IDX_NB, SVC_INST_ID); + } + break; + case ESP_GATTS_CREAT_ATTR_TAB_EVT: + if (param->add_attr_tab.status == ESP_GATT_OK && param->add_attr_tab.num_handle == HRS_IDX_NB) { + memcpy(s_handle_table, param->add_attr_tab.handles, sizeof(s_handle_table)); + esp_ble_gatts_start_service(s_handle_table[IDX_SVC]); + } else { + ESP_LOGE(TAG, "Create attr table failed"); + } + break; + case ESP_GATTS_CONNECT_EVT: + ESP_LOGI(TAG, "CONNECT conn_id %u, remote(pseudo) "ESP_BD_ADDR_STR"", + param->connect.conn_id, ESP_BD_ADDR_HEX(param->connect.remote_bda)); + ESP_LOGI(TAG, " -> use conn_id %u as the link key (remote_bda may be a Host pseudo)", + param->connect.conn_id); + /* Trigger pairing / encryption for this link. */ + esp_ble_set_encryption(param->connect.remote_bda, ESP_BLE_SEC_ENCRYPT_MITM); + break; + case ESP_GATTS_WRITE_EVT: + if (param->write.is_prep) { + break; + } + ESP_LOGI(TAG, "WRITE conn_id %u handle %u len %d", + param->write.conn_id, param->write.handle, param->write.len); + if (param->write.handle == s_handle_table[IDX_CHAR_CFG_A] && param->write.len == 2) { + uint16_t descr_value = (param->write.value[1] << 8) | param->write.value[0]; + if (param->write.conn_id < MAX_CONN) { + if (descr_value == NOTIFY_ENABLE) { + s_notify_enabled[param->write.conn_id] = true; + ESP_LOGI(TAG, "Notify enabled on conn_id %u", param->write.conn_id); + } else if (descr_value == 0x0000) { + s_notify_enabled[param->write.conn_id] = false; + ESP_LOGI(TAG, "Notify disabled on conn_id %u", param->write.conn_id); + } + } + } else if (param->write.handle == s_handle_table[IDX_CHAR_VAL_A]) { + ESP_LOG_BUFFER_HEX(TAG, param->write.value, param->write.len); + if (param->write.conn_id < MAX_CONN && s_notify_enabled[param->write.conn_id] && + param->write.len > 0) { + esp_err_t ret = esp_ble_gatts_send_indicate(gatts_if, param->write.conn_id, + s_handle_table[IDX_CHAR_VAL_A], + param->write.len, param->write.value, false); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Notify echo failed on conn_id %u: %s", + param->write.conn_id, esp_err_to_name(ret)); + } else { + ESP_LOGI(TAG, "Notify echo sent on conn_id %u, len %d", + param->write.conn_id, param->write.len); + } + } + } + break; + case ESP_GATTS_READ_EVT: + ESP_LOGI(TAG, "READ conn_id %u (link is encrypted)", param->read.conn_id); + break; + case ESP_GATTS_MTU_EVT: + ESP_LOGI(TAG, "MTU exchange, conn_id %u, mtu %u", + param->mtu.conn_id, param->mtu.mtu); + break; + case ESP_GATTS_CONF_EVT: + ESP_LOGI(TAG, "Notify/indicate confirm, conn_id %u, status %d, handle %u", + param->conf.conn_id, param->conf.status, param->conf.handle); + break; + case ESP_GATTS_DISCONNECT_EVT: + ESP_LOGI(TAG, "DISCONNECT conn_id %u, remote(pseudo) "ESP_BD_ADDR_STR", reason 0x%x", + param->disconnect.conn_id, ESP_BD_ADDR_HEX(param->disconnect.remote_bda), + param->disconnect.reason); + if (param->disconnect.conn_id < MAX_CONN) { + s_notify_enabled[param->disconnect.conn_id] = false; + } + /* Re-advertise only the set(s) that are not currently on-air. */ + start_idle_adv_sets(); + break; + default: + break; + } +} + +static void setup_advertising(void) +{ + /* Kick off the event-driven setup chain: + * PUBLIC params -> PUBLIC data -> RANDOM params -> RANDOM rand_addr -> RANDOM data. + * Each step is gated on the previous COMPLETE event status. */ + esp_err_t ret = esp_ble_gap_ext_adv_set_params(ADV_HANDLE_PUBLIC, &s_adv_params_public); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "ext_adv_set_params(PUBLIC) enqueue failed: %s", esp_err_to_name(ret)); + } +} + +void app_main(void) +{ + esp_err_t ret = nvs_flash_init(); + if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) { + ESP_ERROR_CHECK(nvs_flash_erase()); + ret = nvs_flash_init(); + } + ESP_ERROR_CHECK(ret); + + ESP_ERROR_CHECK(esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT)); + + esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT(); + ESP_ERROR_CHECK(esp_bt_controller_init(&bt_cfg)); + ESP_ERROR_CHECK(esp_bt_controller_enable(ESP_BT_MODE_BLE)); + + esp_bluedroid_config_t cfg = BT_BLUEDROID_INIT_CONFIG_DEFAULT(); + ESP_ERROR_CHECK(esp_bluedroid_init_with_cfg(&cfg)); + ESP_ERROR_CHECK(esp_bluedroid_enable()); + + ESP_ERROR_CHECK(esp_ble_gatts_register_callback(gatts_event_handler)); + ESP_ERROR_CHECK(esp_ble_gap_register_callback(gap_event_handler)); + ESP_ERROR_CHECK(esp_ble_gatts_app_register(ESP_APP_ID)); + + /* Security: bond + MITM + Secure Connections, static passkey. */ + esp_ble_auth_req_t auth_req = ESP_LE_AUTH_REQ_SC_MITM_BOND; + esp_ble_io_cap_t iocap = ESP_IO_CAP_NONE; + uint8_t key_size = 16; + uint8_t init_key = ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK; + uint8_t rsp_key = ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK; + uint32_t passkey = 123456; + uint8_t auth_option = ESP_BLE_ONLY_ACCEPT_SPECIFIED_AUTH_DISABLE; + uint8_t oob_support = ESP_BLE_OOB_DISABLE; + esp_ble_gap_set_security_param(ESP_BLE_SM_SET_STATIC_PASSKEY, &passkey, sizeof(uint32_t)); + esp_ble_gap_set_security_param(ESP_BLE_SM_AUTHEN_REQ_MODE, &auth_req, sizeof(uint8_t)); + esp_ble_gap_set_security_param(ESP_BLE_SM_IOCAP_MODE, &iocap, sizeof(uint8_t)); + esp_ble_gap_set_security_param(ESP_BLE_SM_MAX_KEY_SIZE, &key_size, sizeof(uint8_t)); + esp_ble_gap_set_security_param(ESP_BLE_SM_ONLY_ACCEPT_SPECIFIED_SEC_AUTH, &auth_option, sizeof(uint8_t)); + esp_ble_gap_set_security_param(ESP_BLE_SM_OOB_SUPPORT, &oob_support, sizeof(uint8_t)); + esp_ble_gap_set_security_param(ESP_BLE_SM_SET_INIT_KEY, &init_key, sizeof(uint8_t)); + esp_ble_gap_set_security_param(ESP_BLE_SM_SET_RSP_KEY, &rsp_key, sizeof(uint8_t)); + +#if !CONFIG_BT_BLE_PERIPH_PSEUDO_ADDR_BOND + ESP_LOGW(TAG, "CONFIG_BT_BLE_PERIPH_PSEUDO_ADDR_BOND is DISABLED:"); + ESP_LOGW(TAG, " two identities of the same phone will share one bond and overwrite each other."); + ESP_LOGW(TAG, " Enable it to get isolated bonds per local identity."); +#else + ESP_LOGI(TAG, "Pseudo-address dual-identity bond isolation ENABLED"); +#endif + + setup_advertising(); + ESP_LOGI(TAG, "Advertising as two identities: Public (A) + Static Random "ESP_BD_ADDR_STR" (B)", + ESP_BD_ADDR_HEX(s_identity_b_addr)); +} diff --git a/examples/bluetooth/bluedroid/ble_50/ble50_dual_identity_server/main/ble50_dual_identity_server.h b/examples/bluetooth/bluedroid/ble_50/ble50_dual_identity_server/main/ble50_dual_identity_server.h new file mode 100644 index 00000000000..7351aae3600 --- /dev/null +++ b/examples/bluetooth/bluedroid/ble_50/ble50_dual_identity_server/main/ble50_dual_identity_server.h @@ -0,0 +1,24 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ + +#ifndef BLE50_DUAL_IDENTITY_SERVER_H +#define BLE50_DUAL_IDENTITY_SERVER_H + +#include +#include +#include + +/* Attributes State Machine */ +enum { + IDX_SVC, + IDX_CHAR_A, + IDX_CHAR_VAL_A, + IDX_CHAR_CFG_A, + + HRS_IDX_NB, +}; + +#endif // BLE50_DUAL_IDENTITY_SERVER_H diff --git a/examples/bluetooth/bluedroid/ble_50/ble50_dual_identity_server/sdkconfig.defaults b/examples/bluetooth/bluedroid/ble_50/ble50_dual_identity_server/sdkconfig.defaults new file mode 100644 index 00000000000..85a3ad3d4ec --- /dev/null +++ b/examples/bluetooth/bluedroid/ble_50/ble50_dual_identity_server/sdkconfig.defaults @@ -0,0 +1,11 @@ +CONFIG_BT_ENABLED=y +CONFIG_BT_BLE_50_FEATURES_SUPPORTED=y +CONFIG_BT_BLE_42_FEATURES_SUPPORTED=n +CONFIG_BT_BLE_SMP_ENABLE=y +CONFIG_BT_BLE_PERIPH_PSEUDO_ADDR_BOND=y +CONFIG_BT_ACL_CONNECTIONS=2 +CONFIG_BT_GATTS_ENABLE=y +# CONFIG_BT_GATTC_ENABLE is not set +# CONFIG_BT_BLE_50_DTM_TEST_EN is not set +# CONFIG_BT_BLE_50_PERIODIC_ADV_EN is not set +# CONFIG_BT_BLE_50_EXTEND_SCAN_EN is not set diff --git a/examples/bluetooth/bluedroid/ble_50/ble50_dual_identity_server/sdkconfig.defaults.esp32c3 b/examples/bluetooth/bluedroid/ble_50/ble50_dual_identity_server/sdkconfig.defaults.esp32c3 new file mode 100644 index 00000000000..b4f291d7790 --- /dev/null +++ b/examples/bluetooth/bluedroid/ble_50/ble50_dual_identity_server/sdkconfig.defaults.esp32c3 @@ -0,0 +1,2 @@ +CONFIG_IDF_TARGET="esp32c3" +CONFIG_BT_ENABLED=y diff --git a/examples/bluetooth/bluedroid/ble_50/ble50_dual_identity_server/sdkconfig.defaults.esp32s3 b/examples/bluetooth/bluedroid/ble_50/ble50_dual_identity_server/sdkconfig.defaults.esp32s3 new file mode 100644 index 00000000000..f38423a524f --- /dev/null +++ b/examples/bluetooth/bluedroid/ble_50/ble50_dual_identity_server/sdkconfig.defaults.esp32s3 @@ -0,0 +1,2 @@ +CONFIG_IDF_TARGET="esp32s3" +CONFIG_BT_ENABLED=y