This is an automated email from the ASF dual-hosted git repository.

sjanc pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/mynewt-nimble.git


The following commit(s) were added to refs/heads/master by this push:
     new 79355f904 nimble/host: Evict stale resolving list entry before adding
79355f904 is described below

commit 79355f904055cb98228b58678a1e7f1bbce2b5aa
Author: Stefanos Anastasiou <[email protected]>
AuthorDate: Mon Aug 10 21:46:11 2026 +0200

    nimble/host: Evict stale resolving list entry before adding
    
    Some controllers reject LE Add Device To Resolving List with Invalid
    HCI Command Parameters (0x12) when an entry for that peer identity
    already exists. ble_hs_pvcy_add_entry() does not handle this: the error
    is returned to ble_store_write_peer_sec(), which discards it ("There is
    not much to do here if it fails"). The controller is then left
    resolving the OLD IRK.
    
    This is reachable whenever a peer re-pairs over an existing bond.
    Peers that rotate their IRK on unpair -- Android does -- become
    permanently unresolvable afterwards: the controller silently drops
    their CONNECT_IND because it still holds the previous IRK, and the host
    never learns why. The only recovery is a reset that rebuilds the
    resolving list from the bond store. Peers with a stable IRK are
    unaffected, which is why this tends to show up as "Android cannot
    reconnect after re-pairing, iOS is fine".
    
    Remove any existing entry for the identity before the add. Removing an
    absent entry is harmless, so it needs no prior lookup -- and the
    resolving list cannot be read back, so a lookup is not possible in the
    general case anyway.
    
    Note this calls ble_hs_pvcy_remove_entry_hci() rather than
    ble_hs_pvcy_remove_entry(). The latter wraps the command in its own
    ble_gap_preempt()/ble_gap_preempt_done() pair, and
    ble_gap_preempt_done() is not depth counted, so using it here would end
    the preemption established above before the add is issued.
    
    Observed on an ESP32-C3 (NimBLE-Arduino, host based privacy disabled)
    with rc=530 == BLE_HS_HCI_ERR(0x12) logged from the add path on every
    re-pair, and fixed by this change on the same hardware.
    
    Adjust the host unit tests for the extra LE Remove Device From
    Resolving List command that now precedes every add.
---
 nimble/host/src/ble_hs_pvcy.c               | 13 +++++++++++++
 nimble/host/test/src/ble_hs_pvcy_test.c     | 13 ++++++++++++-
 nimble/host/test/src/ble_hs_test_util.c     |  8 ++++++--
 nimble/host/test/src/ble_hs_test_util_hci.c |  4 ++++
 nimble/host/test/src/ble_sm_test_util.c     | 10 ++++++++++
 5 files changed, 45 insertions(+), 3 deletions(-)

diff --git a/nimble/host/src/ble_hs_pvcy.c b/nimble/host/src/ble_hs_pvcy.c
index c12263356..fb9ae4ca2 100644
--- a/nimble/host/src/ble_hs_pvcy.c
+++ b/nimble/host/src/ble_hs_pvcy.c
@@ -152,6 +152,19 @@ ble_hs_pvcy_add_entry(const uint8_t *addr, uint8_t 
addr_type,
      */
     ble_gap_preempt();
 
+    /* Evict any existing entry for this identity first.  Some controllers
+     * reject LE Add Device To Resolving List with Invalid HCI Command
+     * Parameters (0x12) when the identity is already present, which would
+     * leave the OLD IRK resolving after a re-pair.  Removing a non-existent
+     * entry is harmless, so this is safe unconditionally.
+     *
+     * Use the _hci variant, not ble_hs_pvcy_remove_entry(): the latter runs
+     * its own ble_gap_preempt()/ble_gap_preempt_done() pair, and
+     * ble_gap_preempt_done() is not depth-counted, so calling it here would
+     * end the preemption before the add below.
+     */
+    ble_hs_pvcy_remove_entry_hci(addr_type, addr);
+
     /* Try to add the entry now that GAP is halted. */
     rc = ble_hs_pvcy_add_entry_hci(addr, addr_type, irk);
 
diff --git a/nimble/host/test/src/ble_hs_pvcy_test.c 
b/nimble/host/test/src/ble_hs_pvcy_test.c
index 79b93b833..8a5ebd30f 100644
--- a/nimble/host/test/src/ble_hs_pvcy_test.c
+++ b/nimble/host/test/src/ble_hs_pvcy_test.c
@@ -110,6 +110,8 @@ ble_hs_pvcy_test_util_add_irk_set_acks(bool scanning, bool 
connecting)
             0);
     }
 
+    ble_hs_test_util_hci_ack_append(
+        BLE_HCI_OP(BLE_HCI_OGF_LE, BLE_HCI_OCF_LE_RMV_RESOLV_LIST), 0);
     ble_hs_test_util_hci_ack_append(
         BLE_HCI_OP(BLE_HCI_OGF_LE, BLE_HCI_OCF_LE_ADD_RESOLV_LIST), 0);
     ble_hs_test_util_hci_ack_append(
@@ -151,6 +153,9 @@ ble_hs_pvcy_test_util_add_irk_verify_tx(const ble_addr_t 
*peer_addr,
                                         bool scanning,
                                         bool connecting)
 {
+    uint8_t param_len;
+    uint8_t *param;
+
     ble_hs_test_util_hci_verify_tx(BLE_HCI_OGF_LE,
                                    BLE_HCI_OCF_LE_SET_ADV_ENABLE,
                                    NULL);
@@ -167,6 +172,12 @@ ble_hs_pvcy_test_util_add_irk_verify_tx(const ble_addr_t 
*peer_addr,
                                        NULL);
     }
 
+    param = ble_hs_test_util_hci_verify_tx(
+        BLE_HCI_OGF_LE, BLE_HCI_OCF_LE_RMV_RESOLV_LIST, &param_len);
+    TEST_ASSERT(param_len == 7);
+    TEST_ASSERT(param[0] == peer_addr->type);
+    TEST_ASSERT(memcmp(param + 1, peer_addr->val, 6) == 0);
+
     ble_hs_test_util_hci_verify_tx_add_irk(peer_addr->type,
                                            peer_addr->val,
                                            peer_irk,
@@ -192,7 +203,7 @@ ble_hs_pvcy_test_util_add_irk(const ble_addr_t *peer_addr,
     rc = ble_hs_pvcy_add_entry(peer_addr->val, peer_addr->type, peer_irk);
     TEST_ASSERT_FATAL(rc == 0);
 
-    num_acks = 3;
+    num_acks = 4; /* adv disable, remove, add, privacy mode */
     if (scanning) {
         num_acks++;
     }
diff --git a/nimble/host/test/src/ble_hs_test_util.c 
b/nimble/host/test/src/ble_hs_test_util.c
index 43f0b4c72..58722717f 100644
--- a/nimble/host/test/src/ble_hs_test_util.c
+++ b/nimble/host/test/src/ble_hs_test_util.c
@@ -619,17 +619,21 @@ ble_hs_test_util_set_our_irk(const uint8_t *irk, int 
fail_idx,
             ble_hs_test_util_hci_misc_exp_status(3, fail_idx, hci_status),
         },
         {
-            BLE_HS_TEST_UTIL_LE_OPCODE(BLE_HCI_OCF_LE_ADD_RESOLV_LIST),
+            BLE_HS_TEST_UTIL_LE_OPCODE(BLE_HCI_OCF_LE_RMV_RESOLV_LIST),
             ble_hs_test_util_hci_misc_exp_status(4, fail_idx, hci_status),
         },
         {
-            BLE_HS_TEST_UTIL_LE_OPCODE(BLE_HCI_OCF_LE_SET_PRIVACY_MODE),
+            BLE_HS_TEST_UTIL_LE_OPCODE(BLE_HCI_OCF_LE_ADD_RESOLV_LIST),
             ble_hs_test_util_hci_misc_exp_status(5, fail_idx, hci_status),
         },
         {
             BLE_HS_TEST_UTIL_LE_OPCODE(BLE_HCI_OCF_LE_SET_PRIVACY_MODE),
             ble_hs_test_util_hci_misc_exp_status(6, fail_idx, hci_status),
         },
+        {
+            BLE_HS_TEST_UTIL_LE_OPCODE(BLE_HCI_OCF_LE_SET_PRIVACY_MODE),
+            ble_hs_test_util_hci_misc_exp_status(7, fail_idx, hci_status),
+        },
         {
             0
         }
diff --git a/nimble/host/test/src/ble_hs_test_util_hci.c 
b/nimble/host/test/src/ble_hs_test_util_hci.c
index c27af143a..3e90aac2b 100644
--- a/nimble/host/test/src/ble_hs_test_util_hci.c
+++ b/nimble/host/test/src/ble_hs_test_util_hci.c
@@ -318,6 +318,10 @@ static const struct ble_hs_test_util_hci_ack 
hci_startup_seq[] = {
         .opcode = ble_hs_hci_util_opcode_join(
             BLE_HCI_OGF_LE, BLE_HCI_OCF_LE_SET_ADV_ENABLE),
     },
+    {
+        .opcode = ble_hs_hci_util_opcode_join(
+            BLE_HCI_OGF_LE, BLE_HCI_OCF_LE_RMV_RESOLV_LIST),
+    },
     {
         .opcode = ble_hs_hci_util_opcode_join(
             BLE_HCI_OGF_LE, BLE_HCI_OCF_LE_ADD_RESOLV_LIST),
diff --git a/nimble/host/test/src/ble_sm_test_util.c 
b/nimble/host/test/src/ble_sm_test_util.c
index 74d40e8b5..fbb8acc8e 100644
--- a/nimble/host/test/src/ble_sm_test_util.c
+++ b/nimble/host/test/src/ble_sm_test_util.c
@@ -1384,6 +1384,12 @@ ble_sm_test_util_verify_tx_add_resolve_list(uint8_t 
peer_id_addr_type,
                                    BLE_HCI_OCF_LE_SET_ADV_ENABLE,
                                    NULL);
 
+    param = ble_hs_test_util_hci_verify_tx(
+        BLE_HCI_OGF_LE, BLE_HCI_OCF_LE_RMV_RESOLV_LIST, &param_len);
+    TEST_ASSERT(param_len == 7);
+    TEST_ASSERT(param[0] == peer_id_addr_type);
+    TEST_ASSERT(memcmp(param + 1, peer_id_addr, 6) == 0);
+
     param = ble_hs_test_util_hci_verify_tx(BLE_HCI_OGF_LE,
                                            BLE_HCI_OCF_LE_ADD_RESOLV_LIST,
                                            &param_len);
@@ -2074,6 +2080,10 @@ ble_sm_test_util_rx_keys(struct ble_sm_test_params 
*params,
                 .opcode = ble_hs_hci_util_opcode_join(
                                 BLE_HCI_OGF_LE, BLE_HCI_OCF_LE_SET_ADV_ENABLE),
             },
+            {
+                .opcode = ble_hs_hci_util_opcode_join(
+                                BLE_HCI_OGF_LE, 
BLE_HCI_OCF_LE_RMV_RESOLV_LIST),
+            },
             {
                 .opcode = ble_hs_hci_util_opcode_join(
                                 BLE_HCI_OGF_LE, 
BLE_HCI_OCF_LE_ADD_RESOLV_LIST),

Reply via email to