From 60fae183f5a3694c03458c636a53f0e12bf7c283 Mon Sep 17 00:00:00 2001 From: Szymon Czapracki Date: Tue, 31 Mar 2026 18:21:22 +0200 Subject: [PATCH 1/2] bttester: Remove app driven NRPA rotation mechanism NRPA rotation was handled in bttester application. We no longer want this, it's gonna by handled by host. --- apps/bttester/src/btp_gap.c | 60 +------------------------------------ 1 file changed, 1 insertion(+), 59 deletions(-) diff --git a/apps/bttester/src/btp_gap.c b/apps/bttester/src/btp_gap.c index 29fb50a103..16f19fd6ee 100644 --- a/apps/bttester/src/btp_gap.c +++ b/apps/bttester/src/btp_gap.c @@ -73,10 +73,6 @@ static struct os_callout connected_ev_co; static struct btp_gap_device_connected_ev connected_ev; #define CONNECTED_EV_DELAY_MS(itvl) 8 * BLE_HCI_CONN_ITVL * itvl / 1000 static int connection_attempts; -#if MYNEWT_VAL(BTTESTER_PRIVACY_MODE) && MYNEWT_VAL(BTTESTER_USE_NRPA) -static int64_t advertising_start; -static struct os_callout bttester_nrpa_rotate_timer; -#endif static const struct ble_gap_conn_params dflt_conn_params = { .scan_itvl = 0x0010, @@ -235,49 +231,6 @@ static struct ble_gap_adv_params adv_params = { static uint8_t ad_flags = BLE_HS_ADV_F_BREDR_UNSUP; -#if MYNEWT_VAL(BTTESTER_PRIVACY_MODE) && MYNEWT_VAL(BTTESTER_USE_NRPA) -static void rotate_nrpa_cb(struct os_event *ev) -{ - int rc; - ble_addr_t addr; - int32_t duration_ms = BLE_HS_FOREVER; - int32_t remaining_time; - os_time_t remaining_ticks; - - if (current_settings & BIT(BTP_GAP_SETTINGS_DISCOVERABLE)) { - if (ad_flags & BLE_HS_ADV_F_DISC_LTD) { - duration_ms = MYNEWT_VAL(BTTESTER_LTD_ADV_TIMEOUT); - } - } - -#if MYNEWT_VAL(BLE_EXT_ADV) - ble_gap_ext_adv_stop(0); -#else - ble_gap_adv_stop(); -#endif - - rc = ble_hs_id_gen_rnd(1, &addr); - assert(rc == 0); - rc = ble_hs_id_set_rnd(addr.val); - assert(rc == 0); - -#if MYNEWT_VAL(BLE_EXT_ADV) - ble_gap_ext_adv_start(0, duration_ms / 10, 0); -#else - ble_gap_adv_start(own_addr_type, NULL, duration_ms, - &adv_params, gap_event_cb, NULL); -#endif - - remaining_time = os_get_uptime_usec() - advertising_start; - if (remaining_time > 0) { - advertising_start = os_get_uptime_usec(); - os_time_ms_to_ticks(remaining_time, &remaining_ticks); - os_callout_reset(&bttester_nrpa_rotate_timer, - remaining_ticks); - } -} -#endif - static uint8_t set_connectable(const void *cmd, uint16_t cmd_len, void *rsp, uint16_t *rsp_len) @@ -556,14 +509,6 @@ start_advertising(const void *cmd, uint16_t cmd_len, return BTP_STATUS_FAILED; } -#if MYNEWT_VAL(BTTESTER_PRIVACY_MODE) && MYNEWT_VAL(BTTESTER_USE_NRPA) - if (MYNEWT_VAL(BTTESTER_NRPA_TIMEOUT) < duration_ms / 1000) { - advertising_start = os_get_uptime_usec(); - os_callout_reset(&bttester_nrpa_rotate_timer, - OS_TICKS_PER_SEC * MYNEWT_VAL(BTTESTER_NRPA_TIMEOUT)); - } -#endif - #if MYNEWT_VAL(BLE_EXT_ADV) err = ble_gap_ext_adv_start(0, duration_ms / 10, 0); #else @@ -2475,10 +2420,7 @@ tester_init_gap(void) return BTP_STATUS_FAILED; } #endif -#if MYNEWT_VAL(BTTESTER_PRIVACY_MODE) && MYNEWT_VAL(BTTESTER_USE_NRPA) - os_callout_init(&bttester_nrpa_rotate_timer, os_eventq_dflt_get(), - rotate_nrpa_cb, NULL); -#endif + adv_buf = os_msys_get(ADV_BUF_LEN, 0); assert(adv_buf); From 941b15037554a9e26709f14eb8ec4d426923e3d6 Mon Sep 17 00:00:00 2001 From: Szymon Czapracki Date: Tue, 31 Mar 2026 18:22:36 +0200 Subject: [PATCH 2/2] nimble/host: Implement NRPA rotation New functionality for NRPA rotation. NRPA will use RPA_TIMEOUT as refresh time. Only extended advertising set is affected by this change. --- nimble/host/src/ble_gap.c | 158 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 158 insertions(+) diff --git a/nimble/host/src/ble_gap.c b/nimble/host/src/ble_gap.c index 435c90b46f..d5de545275 100644 --- a/nimble/host/src/ble_gap.c +++ b/nimble/host/src/ble_gap.c @@ -186,11 +186,16 @@ struct ble_gap_slave_state { unsigned int high_duty_directed:1; unsigned int legacy_pdu:1; unsigned int rnd_addr_set:1; + unsigned int nrpa_exp_set : 1; #if MYNEWT_VAL(BLE_PERIODIC_ADV) unsigned int periodic_configured:1; uint8_t periodic_op; #endif uint8_t rnd_addr[6]; + ble_npl_time_t nrpa_exp_os_ticks; + ble_npl_time_t adv_start_time; + uint16_t adv_duration; + uint8_t adv_max_events; #else /* timer is used only with legacy advertising */ unsigned int exp_set:1; @@ -202,10 +207,141 @@ struct ble_gap_slave_state { }; static bssnz_t struct ble_gap_slave_state ble_gap_slave[BLE_ADV_INSTANCES]; +#if MYNEWT_VAL(BLE_EXT_ADV) +static int ble_gap_ext_adv_set_addr_no_lock(uint8_t instance, const uint8_t *addr); +static int ble_gap_ext_adv_stop_no_lock(uint8_t instance); +int ble_gap_ext_adv_start(uint8_t instance, int duration, int max_events); +#endif #if NIMBLE_BLE_ADVERTISE #if MYNEWT_VAL(BLE_EXT_ADV) static bool ext_adv_legacy_configured = false; + +static bool +ble_gap_ext_adv_rnd_addr_is_nrpa(const uint8_t *addr) +{ + return (addr[5] & 0xc0) == 0; +} + +static uint16_t +ble_gap_ext_adv_remaining_duration(uint8_t instance) +{ + ble_npl_time_t elapsed_ticks; + uint32_t elapsed_ms; + uint32_t elapsed_units; + + if (ble_gap_slave[instance].adv_duration == 0) { + return 0; + } + + elapsed_ticks = ble_npl_time_get() - ble_gap_slave[instance].adv_start_time; + elapsed_ms = ble_npl_time_ticks_to_ms32(elapsed_ticks); + elapsed_units = elapsed_ms / 10; + + if (elapsed_units >= ble_gap_slave[instance].adv_duration) { + return 1; + } + + return ble_gap_slave[instance].adv_duration - elapsed_units; +} + +static int +ble_gap_ext_adv_nrpa_rotate(uint8_t instance, const uint8_t *addr) +{ + uint16_t duration; + uint8_t max_events; + int rc; + + duration = ble_gap_ext_adv_remaining_duration(instance); + max_events = ble_gap_slave[instance].adv_max_events; + + rc = ble_gap_ext_adv_stop_no_lock(instance); + if (rc != 0) { + return rc; + } + + rc = ble_gap_ext_adv_set_addr_no_lock(instance, addr); + if (rc != 0) { + return rc; + } + + rc = ble_gap_ext_adv_start(instance, duration, max_events); + if (rc != 0) { + return rc; + } + + return 0; +} + +static void +ble_gap_ext_adv_nrpa_set_exp(uint8_t instance) +{ + ble_npl_time_t timeout_ticks; + uint32_t timeout_ms; + int rc; + + timeout_ms = MYNEWT_VAL(BLE_RPA_TIMEOUT) * 1000UL; + + rc = ble_npl_time_ms_to_ticks(timeout_ms, &timeout_ticks); + BLE_HS_DBG_ASSERT_EVAL(rc == 0); + + ble_gap_slave[instance].nrpa_exp_os_ticks = ble_npl_time_get() + timeout_ticks; + ble_gap_slave[instance].nrpa_exp_set = 1; + ble_hs_timer_resched(); +} + +static void +ble_gap_ext_adv_nrpa_clear_exp(uint8_t instance) +{ + ble_gap_slave[instance].nrpa_exp_set = 0; + ble_hs_timer_resched(); +} + +static uint32_t +ble_gap_ext_adv_nrpa_timer(void) +{ + ble_npl_stime_t ticks; + uint32_t min_ticks; + ble_addr_t addr; + int rc; + int i; + + min_ticks = BLE_HS_FOREVER; + + for (i = 0; i < BLE_ADV_INSTANCES; i++) { + if (ble_gap_slave[i].op != BLE_GAP_OP_S_ADV || + !ble_gap_slave[i].rnd_addr_set || !ble_gap_slave[i].nrpa_exp_set || + !ble_gap_ext_adv_rnd_addr_is_nrpa(ble_gap_slave[i].rnd_addr)) { + continue; + } + + ticks = ble_gap_slave[i].nrpa_exp_os_ticks - ble_npl_time_get(); + if (ticks > 0) { + min_ticks = min(min_ticks, ticks); + continue; + } + + rc = ble_hs_id_gen_rnd(1, &addr); + if (rc != 0) { + min_ticks = min(min_ticks, ble_npl_time_ms_to_ticks32( + BLE_GAP_CANCEL_RETRY_TIMEOUT_MS)); + continue; + } + + rc = ble_gap_ext_adv_nrpa_rotate(i, addr.val); + if (rc != 0) { + min_ticks = min(min_ticks, ble_npl_time_ms_to_ticks32( + BLE_GAP_CANCEL_RETRY_TIMEOUT_MS)); + continue; + } + + ble_gap_ext_adv_nrpa_set_exp(i); + ticks = ble_gap_slave[i].nrpa_exp_os_ticks - ble_npl_time_get(); + min_ticks = min(min_ticks, ticks); + } + + return min_ticks; +} #endif #endif @@ -2385,6 +2521,10 @@ ble_gap_timer(void) min_ticks = min(min_ticks, ble_gap_slave_timer()); #endif +#if NIMBLE_BLE_ADVERTISE && MYNEWT_VAL(BLE_EXT_ADV) + min_ticks = min(min_ticks, ble_gap_ext_adv_nrpa_timer()); +#endif + return min_ticks; } @@ -3427,6 +3567,13 @@ ble_gap_ext_adv_set_addr_no_lock(uint8_t instance, const uint8_t *addr) ble_gap_slave[instance].rnd_addr_set = 1; memcpy(ble_gap_slave[instance].rnd_addr, addr, 6); + if (ble_gap_ext_adv_rnd_addr_is_nrpa(addr) && + ble_gap_slave[instance].op == BLE_GAP_OP_S_ADV) { + ble_gap_ext_adv_nrpa_set_exp(instance); + } else if (!ble_gap_ext_adv_rnd_addr_is_nrpa(addr)) { + ble_gap_ext_adv_nrpa_clear_exp(instance); + } + return 0; } @@ -3548,8 +3695,18 @@ ble_gap_ext_adv_start(uint8_t instance, int duration, int max_events) } ble_gap_slave[instance].op = BLE_GAP_OP_S_ADV; + ble_gap_slave[instance].adv_start_time = ble_npl_time_get(); + ble_gap_slave[instance].adv_duration = duration; + ble_gap_slave[instance].adv_max_events = max_events; + if (ble_gap_slave[instance].rnd_addr_set && + ble_gap_ext_adv_rnd_addr_is_nrpa(ble_gap_slave[instance].rnd_addr)) { + ble_gap_ext_adv_nrpa_set_exp(instance); + } else { + ble_gap_ext_adv_nrpa_clear_exp(instance); + } ble_hs_unlock(); + return 0; } @@ -3584,6 +3741,7 @@ ble_gap_ext_adv_stop_no_lock(uint8_t instance) } ble_gap_slave[instance].op = BLE_GAP_OP_NULL; + ble_gap_ext_adv_nrpa_clear_exp(instance); if (!active) { return BLE_HS_EALREADY;