From 329c1c8f8e89cdbe33d0d4612fa2e44a37f92382 Mon Sep 17 00:00:00 2001 From: Zhi Wei Jian Date: Tue, 14 Jul 2026 12:14:16 +0800 Subject: [PATCH] feat(ble/bluedroid): Support bluedroid dual identity (cherry picked from commit 23587866478d39d96badf6edfed536b12247dbf5) Co-authored-by: zhiweijian --- components/bt/host/bluedroid/CMakeLists.txt | 6 + components/bt/host/bluedroid/Kconfig.in | 18 + .../bt/host/bluedroid/api/esp_gap_ble_api.c | 53 ++ .../api/include/api/esp_gap_ble_api.h | 88 +++ .../bt/host/bluedroid/bta/dm/bta_dm_act.c | 73 +- .../bt/host/bluedroid/bta/dm/bta_dm_api.c | 23 + .../bluedroid/bta/dm/include/bta_dm_int.h | 3 + .../host/bluedroid/bta/include/bta/bta_api.h | 7 + .../host/bluedroid/btc/core/btc_ble_storage.c | 112 ++- .../btc/include/btc/btc_ble_storage.h | 4 + .../include/common/bluedroid_user_config.h | 6 + .../common/include/common/bt_target.h | 8 + .../bt/host/bluedroid/stack/btm/btm_acl.c | 26 +- .../bt/host/bluedroid/stack/btm/btm_ble.c | 709 +++++++++++++++++- .../host/bluedroid/stack/btm/btm_ble_5_gap.c | 29 + .../bt/host/bluedroid/stack/btm/btm_ble_gap.c | 9 + .../host/bluedroid/stack/btm/btm_ble_pseudo.c | 289 +++++++ .../bt/host/bluedroid/stack/btm/btm_dev.c | 39 +- .../stack/btm/include/btm_ble_pseudo.h | 161 ++++ .../bluedroid/stack/btm/include/btm_int.h | 7 + .../stack/include/stack/btm_ble_api.h | 48 ++ .../bt/host/bluedroid/stack/smp/smp_act.c | 25 + .../bt/host/bluedroid/stack/smp/smp_utils.c | 11 + docs/conf_common.py | 5 + .../ble/bluedroid-dual-identity-host-dev.rst | 25 + docs/en/api-guides/ble/index.rst | 1 + .../ble/bluedroid-dual-identity-host-dev.rst | 25 + docs/zh_CN/api-guides/ble/index.rst | 1 + 28 files changed, 1774 insertions(+), 37 deletions(-) create mode 100644 components/bt/host/bluedroid/stack/btm/btm_ble_pseudo.c create mode 100644 components/bt/host/bluedroid/stack/btm/include/btm_ble_pseudo.h create mode 100644 docs/en/api-guides/ble/bluedroid-dual-identity-host-dev.rst create mode 100644 docs/zh_CN/api-guides/ble/bluedroid-dual-identity-host-dev.rst diff --git a/components/bt/host/bluedroid/CMakeLists.txt b/components/bt/host/bluedroid/CMakeLists.txt index 1c7432105ba..a578bddb26c 100644 --- a/components/bt/host/bluedroid/CMakeLists.txt +++ b/components/bt/host/bluedroid/CMakeLists.txt @@ -345,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 54e38d0fba3..062315c024d 100644 --- a/components/bt/host/bluedroid/Kconfig.in +++ b/components/bt/host/bluedroid/Kconfig.in @@ -404,6 +404,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 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 36bdf0f4c9c..762cf2cb02d 100644 --- a/components/bt/host/bluedroid/api/esp_gap_ble_api.c +++ b/components/bt/host/bluedroid/api/esp_gap_ble_api.c @@ -516,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) { 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 fa8165bc1ac..5035f5f441e 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 @@ -3610,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. * 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 bfe99214fcd..b4ed0ad887b 100644 --- a/components/bt/host/bluedroid/bta/dm/bta_dm_act.c +++ b/components/bt/host/bluedroid/bta/dm/bta_dm_act.c @@ -3815,17 +3815,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; } @@ -3880,10 +3918,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 } @@ -3892,6 +3938,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 { @@ -5127,7 +5178,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 57f816f913f..e7508a70075 100644 --- a/components/bt/host/bluedroid/bta/dm/bta_dm_api.c +++ b/components/bt/host/bluedroid/bta/dm/bta_dm_api.c @@ -1261,10 +1261,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; @@ -1281,6 +1303,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 e2e61e0f1af..e8e55a078b9 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 @@ -807,6 +807,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 92c8af0fd61..42672924a67 100644 --- a/components/bt/host/bluedroid/bta/include/bta/bta_api.h +++ b/components/bt/host/bluedroid/bta/include/bta/bta_api.h @@ -2531,12 +2531,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/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/common/include/common/bluedroid_user_config.h b/components/bt/host/bluedroid/common/include/common/bluedroid_user_config.h index 8877aa07196..31c25e087b7 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 @@ -161,6 +161,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 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 8e5aa0c3830..55c1056df42 100644 --- a/components/bt/host/bluedroid/common/include/common/bt_target.h +++ b/components/bt/host/bluedroid/common/include/common/bt_target.h @@ -208,6 +208,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 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 b4e9a32f4b4..51768b51fc6 100644 --- a/components/bt/host/bluedroid/stack/btm/btm_ble.c +++ b/components/bt/host/bluedroid/stack/btm/btm_ble.c @@ -36,6 +36,10 @@ #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" @@ -51,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 */ /*******************************************************************************/ @@ -281,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); @@ -1295,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: @@ -1876,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). @@ -1926,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", @@ -1935,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 ) /******************************************************************************* ** @@ -1954,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); @@ -1973,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; @@ -1988,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. */ @@ -2038,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++; } @@ -2074,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 ** @@ -2091,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); @@ -2103,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. @@ -2115,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); @@ -2148,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_gap.c b/components/bt/host/bluedroid/stack/btm/btm_ble_gap.c index ab99ea47d9b..e1157669dc3 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" @@ -3951,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 } /******************************************************************************* @@ -3976,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_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/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 0d0120051e3..fb04b219e20 100644 --- a/components/bt/host/bluedroid/stack/btm/include/btm_int.h +++ b/components/bt/host/bluedroid/stack/btm/include/btm_int.h @@ -624,6 +624,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; @@ -1282,6 +1288,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/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/smp/smp_act.c b/components/bt/host/bluedroid/stack/smp/smp_act.c index 42d7078a3b2..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) @@ -1215,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_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 bf6c03c0f4b..ebd92680735 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', @@ -250,6 +254,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 ********** 快速入门