feat(ble/bluedroid): Support bluedroid dual identity

(cherry picked from commit 2358786647)

Co-authored-by: zhiweijian <zhiweijian@espressif.com>
This commit is contained in:
Zhi Wei Jian
2026-07-21 10:32:11 +08:00
committed by zhiweijian
parent 11a1a33e26
commit 7ab919a76e
28 changed files with 1773 additions and 37 deletions
@@ -341,6 +341,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")
+18
View File
@@ -482,6 +482,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
@@ -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)
{
@@ -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.
*
@@ -3825,17 +3825,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;
}
@@ -3890,10 +3928,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
}
@@ -3902,6 +3948,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 {
@@ -5137,7 +5188,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
}
/*******************************************************************************
@@ -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
@@ -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;
@@ -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
/*******************************************************************************
@@ -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;
@@ -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"
@@ -219,6 +219,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
@@ -246,6 +246,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
@@ -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;
}
@@ -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);
@@ -1296,10 +1399,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:
@@ -1877,12 +2006,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).
@@ -1927,6 +2057,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",
@@ -1936,6 +2075,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 )
/*******************************************************************************
**
@@ -1955,6 +2157,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);
@@ -1974,6 +2180,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;
@@ -1989,10 +2203,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. */
@@ -2039,11 +2281,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++;
}
@@ -2075,6 +2350,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
**
@@ -2092,6 +2717,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);
@@ -2104,6 +2735,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.
@@ -2116,6 +2753,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);
@@ -2149,17 +2794,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
@@ -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;
@@ -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;
@@ -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 <string.h>
#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 */
@@ -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);
/*******************************************************************************
**
@@ -331,6 +331,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;
@@ -340,7 +356,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);
@@ -375,13 +391,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) {
@@ -655,16 +674,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;
@@ -674,6 +694,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 */
@@ -690,8 +713,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;
}
@@ -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 */
@@ -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);
@@ -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
@@ -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);
}
@@ -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
}
/*******************************************************************************