blob: 3566876e0943db7be2a8117834055985d540b4cf [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 <stdint.h>
#include <assert.h>
#include <string.h>
#include "syscfg/syscfg.h"
#include "os/os.h"
#include "nimble/ble.h"
#include "nimble/nimble_opt.h"
#include "controller/ble_ll.h"
#include "controller/ble_ll_resolv.h"
#include "controller/ble_ll_hci.h"
#include "controller/ble_ll_scan.h"
#include "controller/ble_ll_adv.h"
#include "controller/ble_ll_sync.h"
#include "controller/ble_hw.h"
#include "ble_ll_conn_priv.h"
#include "ble_ll_priv.h"
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_PRIVACY)
struct ble_ll_resolv_data
{
uint8_t addr_res_enabled;
uint8_t rl_size;
uint8_t rl_cnt_hw;
uint8_t rl_cnt;
ble_npl_time_t rpa_tmo;
struct ble_npl_callout rpa_timer;
};
struct ble_ll_resolv_data g_ble_ll_resolv_data;
__attribute__((aligned(4)))
struct ble_ll_resolv_entry g_ble_ll_resolv_list[MYNEWT_VAL(BLE_LL_RESOLV_LIST_SIZE)];
#if MYNEWT_VAL(BLE_LL_HCI_VS_LOCAL_IRK)
struct local_irk_data {
uint8_t is_set;
uint8_t irk[16];
uint8_t rpa[6];
};
/* 0 is for public, 1 is for static address */
static struct local_irk_data g_local_irk[2];
#endif
/**
* Called to determine if a change is allowed to the resolving list at this
* time. We are not allowed to modify the resolving list if address translation
* is enabled and we are either scanning, advertising, or attempting to create
* a connection.
*
* @return int 0: not allowed. 1: allowed.
*/
static int
ble_ll_resolv_list_chg_allowed(void)
{
int rc;
if (g_ble_ll_resolv_data.addr_res_enabled &&
ble_ll_is_busy(BLE_LL_BUSY_EXCLUDE_CONNECTIONS)) {
rc = 0;
} else {
rc = 1;
}
return rc;
}
static void
generate_rpa(const uint8_t *irk, uint8_t *rpa)
{
uint8_t *prand;
struct ble_encryption_block ecb;
/* Get prand */
prand = rpa + 3;
ble_ll_rand_prand_get(prand);
/* Calculate hash, hash = ah(local IRK, prand) */
memcpy(ecb.key, irk, 16);
memset(ecb.plain_text, 0, 13);
ecb.plain_text[13] = prand[2];
ecb.plain_text[14] = prand[1];
ecb.plain_text[15] = prand[0];
/* Calculate hash */
ble_hw_encrypt_block(&ecb);
rpa[0] = ecb.cipher_text[15];
rpa[1] = ecb.cipher_text[14];
rpa[2] = ecb.cipher_text[13];
}
/**
* Called to generate a resolvable private address in rl structure
*
* @param rl
* @param local
*/
static void
ble_ll_resolv_gen_priv_addr(struct ble_ll_resolv_entry *rl, int local)
{
uint8_t *irk;
uint8_t *addr;
BLE_LL_ASSERT(rl != NULL);
if (local) {
addr = rl->rl_local_rpa;
irk = rl->rl_local_irk;
} else {
addr = rl->rl_peer_rpa;
irk = rl->rl_peer_irk;
}
generate_rpa(irk, addr);
}
/**
* Called when the Resolvable private address timer expires. This timer
* is used to regenerate local and peers RPA's in the resolving list.
*/
static void
ble_ll_resolv_rpa_timer_cb(struct ble_npl_event *ev)
{
int i;
os_sr_t sr;
struct ble_ll_resolv_entry *rl;
#if MYNEWT_VAL(BLE_LL_HCI_VS_LOCAL_IRK)
struct local_irk_data *irk_data;
uint8_t rpa[6];
#endif
rl = &g_ble_ll_resolv_list[0];
for (i = 0; i < g_ble_ll_resolv_data.rl_cnt; ++i) {
if (rl->rl_has_local) {
OS_ENTER_CRITICAL(sr);
ble_ll_resolv_gen_priv_addr(rl, 1);
OS_EXIT_CRITICAL(sr);
}
if (rl->rl_has_peer) {
OS_ENTER_CRITICAL(sr);
ble_ll_resolv_gen_priv_addr(rl, 0);
OS_EXIT_CRITICAL(sr);
}
++rl;
}
#if MYNEWT_VAL(BLE_LL_HCI_VS_LOCAL_IRK)
for (i = 0; i < ARRAY_SIZE(g_local_irk); i++) {
irk_data = &g_local_irk[i];
if (irk_data->is_set) {
generate_rpa(irk_data->irk, rpa);
OS_ENTER_CRITICAL(sr);
memcpy(irk_data->rpa, rpa, 6);
OS_EXIT_CRITICAL(sr);
}
}
#endif
ble_npl_callout_reset(&g_ble_ll_resolv_data.rpa_timer,
g_ble_ll_resolv_data.rpa_tmo);
#if MYNEWT_VAL(BLE_LL_ROLE_BROADCASTER)
ble_ll_adv_rpa_timeout();
#endif
}
/**
* Called to determine if the IRK is all zero.
*
* @param irk
*
* @return int 0: IRK is zero . 1: IRK has non-zero value.
*/
static int
ble_ll_resolv_irk_nonzero(const uint8_t *irk)
{
int i;
int rc;
rc = 0;
for (i = 0; i < 16; ++i) {
if (*irk != 0) {
rc = 1;
break;
}
++irk;
}
return rc;
}
/**
* Clear the resolving list
*
* @return int 0: success, BLE error code otherwise
*/
int
ble_ll_resolv_list_clr(void)
{
/* Check proper state */
if (!ble_ll_resolv_list_chg_allowed()) {
return BLE_ERR_CMD_DISALLOWED;
}
/* Sets total on list to 0. Clears HW resolve list */
g_ble_ll_resolv_data.rl_cnt_hw = 0;
g_ble_ll_resolv_data.rl_cnt = 0;
ble_hw_resolv_list_clear();
/* stop RPA timer when clearing RL */
ble_npl_callout_stop(&g_ble_ll_resolv_data.rpa_timer);
return BLE_ERR_SUCCESS;
}
/**
* Read the size of the resolving list. This is the total number of resolving
* list entries allowed by the controller.
*
* @param rspbuf Pointer to response buffer
*
* @return int 0: success.
*/
int
ble_ll_resolv_list_read_size(uint8_t *rspbuf, uint8_t *rsplen)
{
struct ble_hci_le_rd_resolv_list_size_rp *rsp = (void *) rspbuf;
rsp->size = g_ble_ll_resolv_data.rl_size;
*rsplen = sizeof(*rsp);
return BLE_ERR_SUCCESS;
}
/**
* Used to determine if the device is on the resolving list.
*
* @param addr
* @param addr_type Public address (0) or random address (1)
*
* @return int 0: device is not on resolving list; otherwise the return value
* is the 'position' of the device in the resolving list (the index of the
* element plus 1).
*/
static int
ble_ll_is_on_resolv_list(const uint8_t *addr, uint8_t addr_type)
{
int i;
struct ble_ll_resolv_entry *rl;
rl = &g_ble_ll_resolv_list[0];
for (i = 0; i < g_ble_ll_resolv_data.rl_cnt; ++i) {
if ((rl->rl_addr_type == addr_type) &&
(!memcmp(&rl->rl_identity_addr[0], addr, BLE_DEV_ADDR_LEN))) {
return i + 1;
}
++rl;
}
return 0;
}
/**
* Used to determine if the device is on the resolving list.
*
* @param addr
* @param addr_type Public address (0) or random address (1)
*
* @return Pointer to resolving list entry or NULL if no entry found.
*/
struct ble_ll_resolv_entry *
ble_ll_resolv_list_find(const uint8_t *addr, uint8_t addr_type)
{
int i;
struct ble_ll_resolv_entry *rl;
rl = &g_ble_ll_resolv_list[0];
for (i = 0; i < g_ble_ll_resolv_data.rl_cnt; ++i) {
if ((rl->rl_addr_type == addr_type) &&
(!memcmp(&rl->rl_identity_addr[0], addr, BLE_DEV_ADDR_LEN))) {
return rl;
}
++rl;
}
return NULL;
}
/**
* Add a device to the resolving list
*
* @return int
*/
int
ble_ll_resolv_list_add(const uint8_t *cmdbuf, uint8_t len)
{
const struct ble_hci_le_add_resolv_list_cp *cmd = (const void *) cmdbuf;
struct ble_ll_resolv_entry *rl;
int rc = BLE_ERR_SUCCESS;
if (len != sizeof(*cmd)) {
return BLE_ERR_INV_HCI_CMD_PARMS;
}
/* Must be in proper state */
if (!ble_ll_resolv_list_chg_allowed()) {
return BLE_ERR_CMD_DISALLOWED;
}
/* Check if we have any open entries */
if (g_ble_ll_resolv_data.rl_cnt >= g_ble_ll_resolv_data.rl_size) {
return BLE_ERR_MEM_CAPACITY;
}
/* spec is not clear on how to handle this but make sure host is aware
* that new keys are not used in that case
*/
if (ble_ll_is_on_resolv_list(cmd->peer_id_addr, cmd->peer_addr_type)) {
return BLE_ERR_INV_HCI_CMD_PARMS;
}
/* we keep this sorted in a way that entries with peer_irk are first */
if (ble_ll_resolv_irk_nonzero(cmd->peer_irk)) {
memmove(&g_ble_ll_resolv_list[g_ble_ll_resolv_data.rl_cnt_hw + 1],
&g_ble_ll_resolv_list[g_ble_ll_resolv_data.rl_cnt_hw],
(g_ble_ll_resolv_data.rl_cnt - g_ble_ll_resolv_data.rl_cnt_hw) *
sizeof(g_ble_ll_resolv_list[0]));
rl = &g_ble_ll_resolv_list[g_ble_ll_resolv_data.rl_cnt_hw];
} else {
rl = &g_ble_ll_resolv_list[g_ble_ll_resolv_data.rl_cnt];
}
memset (rl, 0, sizeof(*rl));
rl->rl_addr_type = cmd->peer_addr_type;
memcpy(rl->rl_identity_addr, cmd->peer_id_addr, BLE_DEV_ADDR_LEN);
if (ble_ll_resolv_irk_nonzero(cmd->peer_irk)) {
swap_buf(rl->rl_peer_irk, cmd->peer_irk, 16);
rl->rl_has_peer = 1;
/* generate peer RPA now, those will be updated by timer when
* resolution is enabled
*/
ble_ll_resolv_gen_priv_addr(rl, 0);
}
if (ble_ll_resolv_irk_nonzero(cmd->local_irk)) {
swap_buf(rl->rl_local_irk, cmd->local_irk, 16);
rl->rl_has_local = 1;
/* generate local RPA now, those will be updated by timer when
* resolution is enabled
*/
ble_ll_resolv_gen_priv_addr(rl, 1);
}
/* By default use privacy network mode */
rl->rl_priv_mode = BLE_HCI_PRIVACY_NETWORK;
/* Add peers IRKs to HW resolving list. Should always succeed since we
* already checked if there is room for it.
*/
if (rl->rl_has_peer) {
rc = ble_hw_resolv_list_add(rl->rl_peer_irk);
BLE_LL_ASSERT(rc == BLE_ERR_SUCCESS);
g_ble_ll_resolv_data.rl_cnt_hw++;
}
g_ble_ll_resolv_data.rl_cnt++;
/* start RPA timer if this was first element added to RL */
if (g_ble_ll_resolv_data.rl_cnt == 1) {
ble_npl_callout_reset(&g_ble_ll_resolv_data.rpa_timer,
g_ble_ll_resolv_data.rpa_tmo);
}
return rc;
}
/**
* Remove a device from the resolving list
*
* @param cmdbuf
*
* @return int 0: success, BLE error code otherwise
*/
int
ble_ll_resolv_list_rmv(const uint8_t *cmdbuf, uint8_t len)
{
const struct ble_hci_le_rmv_resolve_list_cp *cmd = (const void *) cmdbuf;
int position;
if (len != sizeof(*cmd)) {
return BLE_ERR_INV_HCI_CMD_PARMS;
}
/* Must be in proper state */
if (!ble_ll_resolv_list_chg_allowed()) {
return BLE_ERR_CMD_DISALLOWED;
}
/* Remove from IRK records */
position = ble_ll_is_on_resolv_list(cmd->peer_id_addr, cmd->peer_addr_type);
if (position) {
BLE_LL_ASSERT(position <= g_ble_ll_resolv_data.rl_cnt);
memmove(&g_ble_ll_resolv_list[position - 1],
&g_ble_ll_resolv_list[position],
(g_ble_ll_resolv_data.rl_cnt - position) *
sizeof(g_ble_ll_resolv_list[0]));
g_ble_ll_resolv_data.rl_cnt--;
/* Remove from HW list */
if (position <= g_ble_ll_resolv_data.rl_cnt_hw) {
ble_hw_resolv_list_rmv(position - 1);
g_ble_ll_resolv_data.rl_cnt_hw--;
}
/* stop RPA timer if list is empty */
if (g_ble_ll_resolv_data.rl_cnt == 0) {
ble_npl_callout_stop(&g_ble_ll_resolv_data.rpa_timer);
}
return BLE_ERR_SUCCESS;
}
return BLE_ERR_UNK_CONN_ID;
}
/**
* Called to enable or disable address resolution in the controller
*
* @param cmdbuf
*
* @return int
*/
int
ble_ll_resolv_enable_cmd(const uint8_t *cmdbuf, uint8_t len)
{
const struct ble_hci_le_set_addr_res_en_cp *cmd = (const void *) cmdbuf;
if (len != sizeof(*cmd)) {
return BLE_ERR_INV_HCI_CMD_PARMS;
}
if (ble_ll_is_busy(BLE_LL_BUSY_EXCLUDE_CONNECTIONS)) {
return BLE_ERR_CMD_DISALLOWED;
}
if (cmd->enable > 1) {
return BLE_ERR_INV_HCI_CMD_PARMS;
}
g_ble_ll_resolv_data.addr_res_enabled = cmd->enable;
return BLE_ERR_SUCCESS;
}
int
ble_ll_resolv_peer_addr_rd(const uint8_t *cmdbuf, uint8_t len,
uint8_t *rspbuf, uint8_t *rsplen)
{
const struct ble_hci_le_rd_peer_resolv_addr_cp *cmd = (const void *) cmdbuf;
struct ble_hci_le_rd_peer_resolv_addr_rp *rsp = (void *) rspbuf;
struct ble_ll_resolv_entry *rl;
int rc;
if (len != sizeof(*cmd)) {
return BLE_ERR_INV_HCI_CMD_PARMS;
}
rl = ble_ll_resolv_list_find(cmd->peer_id_addr, cmd->peer_addr_type);
if (rl) {
memcpy(rsp->rpa, rl->rl_peer_rpa, BLE_DEV_ADDR_LEN);
rc = BLE_ERR_SUCCESS;
} else {
memset(rsp->rpa, 0, BLE_DEV_ADDR_LEN);
rc = BLE_ERR_UNK_CONN_ID;
}
*rsplen = sizeof(*rsp);
return rc;
}
int
ble_ll_resolv_local_addr_rd(const uint8_t *cmdbuf, uint8_t len,
uint8_t *rspbuf, uint8_t *rsplen)
{
const struct ble_hci_le_rd_local_resolv_addr_cp *cmd = (const void *) cmdbuf;
struct ble_hci_le_rd_local_resolv_addr_rp *rsp = (void *) rspbuf;
struct ble_ll_resolv_entry *rl;
int rc;
if (len != sizeof(*cmd)) {
return BLE_ERR_INV_HCI_CMD_PARMS;
}
rl = ble_ll_resolv_list_find(cmd->peer_id_addr, cmd->peer_addr_type);
if (rl) {
memcpy(rsp->rpa, rl->rl_local_rpa, BLE_DEV_ADDR_LEN);
rc = BLE_ERR_SUCCESS;
} else {
memset(rsp->rpa, 0, BLE_DEV_ADDR_LEN);
rc = BLE_ERR_UNK_CONN_ID;
}
*rsplen = sizeof(*rsp);
return rc;
}
/**
* Set the resolvable private address timeout.
*
* @param cmdbuf
*
* @return int
*/
int
ble_ll_resolv_set_rpa_tmo(const uint8_t *cmdbuf, uint8_t len)
{
const struct ble_hci_le_set_rpa_tmo_cp *cmd = (const void *)cmdbuf;
uint16_t tmo_secs;
if (len != sizeof(*cmd)) {
return BLE_ERR_INV_HCI_CMD_PARMS;
}
tmo_secs = le16toh(cmd->rpa_timeout);
if (!((tmo_secs > 0) && (tmo_secs <= 0xA1B8))) {
return BLE_ERR_INV_HCI_CMD_PARMS;
}
g_ble_ll_resolv_data.rpa_tmo = ble_npl_time_ms_to_ticks32(tmo_secs * 1000);
/* restart timer if there is something on RL */
if (g_ble_ll_resolv_data.rl_cnt) {
ble_npl_callout_reset(&g_ble_ll_resolv_data.rpa_timer,
g_ble_ll_resolv_data.rpa_tmo);
}
return BLE_ERR_SUCCESS;
}
int
ble_ll_resolve_set_priv_mode(const uint8_t *cmdbuf, uint8_t len)
{
const struct ble_hci_le_set_privacy_mode_cp *cmd = (const void *) cmdbuf;
struct ble_ll_resolv_entry *rl;
if (ble_ll_is_busy(BLE_LL_BUSY_EXCLUDE_CONNECTIONS)) {
return BLE_ERR_CMD_DISALLOWED;
}
if (len != sizeof(*cmd)) {
return BLE_ERR_INV_HCI_CMD_PARMS;
}
rl = ble_ll_resolv_list_find(cmd->peer_id_addr, cmd->peer_id_addr_type);
if (!rl) {
return BLE_ERR_UNK_CONN_ID;
}
if (cmd->mode > BLE_HCI_PRIVACY_DEVICE) {
return BLE_ERR_INV_HCI_CMD_PARMS;
}
rl->rl_priv_mode = cmd->mode;
return BLE_ERR_SUCCESS;
}
/**
* Returns the Resolvable Private address timeout, in os ticks
*
*
* @return uint32_t
*/
uint32_t
ble_ll_resolv_get_rpa_tmo(void)
{
return g_ble_ll_resolv_data.rpa_tmo;
}
void
ble_ll_resolv_get_priv_addr(struct ble_ll_resolv_entry *rl, int local,
uint8_t *addr)
{
os_sr_t sr;
BLE_LL_ASSERT(rl != NULL);
BLE_LL_ASSERT(addr != NULL);
OS_ENTER_CRITICAL(sr);
if (local) {
BLE_LL_ASSERT(rl->rl_has_local);
memcpy(addr, rl->rl_local_rpa, BLE_DEV_ADDR_LEN);
} else {
BLE_LL_ASSERT(rl->rl_has_peer);
memcpy(addr, rl->rl_peer_rpa, BLE_DEV_ADDR_LEN);
}
OS_EXIT_CRITICAL(sr);
}
void
ble_ll_resolv_set_peer_rpa(int index, uint8_t *rpa)
{
os_sr_t sr;
struct ble_ll_resolv_entry *rl;
OS_ENTER_CRITICAL(sr);
rl = &g_ble_ll_resolv_list[index];
memcpy(rl->rl_peer_rpa, rpa, BLE_DEV_ADDR_LEN);
OS_EXIT_CRITICAL(sr);
}
void
ble_ll_resolv_set_local_rpa(int index, uint8_t *rpa)
{
os_sr_t sr;
struct ble_ll_resolv_entry *rl;
OS_ENTER_CRITICAL(sr);
rl = &g_ble_ll_resolv_list[index];
memcpy(rl->rl_local_rpa, rpa, BLE_DEV_ADDR_LEN);
OS_EXIT_CRITICAL(sr);
}
/**
* Generate a resolvable private address.
*
* @param addr
* @param addr_type
* @param rpa
*
* @return int
*/
int
ble_ll_resolv_gen_rpa(uint8_t *addr, uint8_t addr_type, uint8_t *rpa, int local)
{
struct ble_ll_resolv_entry *rl;
rl = ble_ll_resolv_list_find(addr, addr_type);
if (rl) {
if ((local && rl->rl_has_local) || (!local && rl->rl_has_peer)) {
ble_ll_resolv_get_priv_addr(rl, local, rpa);
return 1;
}
}
return 0;
}
#if MYNEWT_VAL(BLE_LL_HCI_VS_LOCAL_IRK)
int
ble_ll_resolv_local_irk_set(uint8_t own_addr_type, const uint8_t *irk)
{
struct local_irk_data *irk_data;
int i;
if (own_addr_type >= 2) {
return -1;
}
irk_data = &g_local_irk[own_addr_type];
memcpy(irk_data->irk, irk, 16);
irk_data->is_set = 0;
for (i = 0; i < 16; i++) {
if (irk[i]) {
irk_data->is_set = 1;
break;
}
}
if (irk_data->is_set) {
generate_rpa(irk_data->irk, irk_data->rpa);
}
return 0;
}
int
ble_ll_resolv_local_rpa_get(uint8_t own_addr_type, uint8_t *rpa)
{
struct local_irk_data *irk_data;
if (own_addr_type >= 2) {
return -1;
}
irk_data = &g_local_irk[own_addr_type];
if (!irk_data->is_set) {
return -1;
}
memcpy(rpa, irk_data->rpa, 6);
return 0;
}
#endif
/**
* Resolve a Resolvable Private Address
*
* @param rpa
* @param index
*
* @return int
*/
int
ble_ll_resolv_rpa(const uint8_t *rpa, const uint8_t *irk)
{
int rc;
const uint32_t *irk32;
uint32_t *key32;
uint32_t *pt32;
struct ble_encryption_block ecb;
irk32 = (const uint32_t *)irk;
key32 = (uint32_t *)&ecb.key[0];
key32[0] = irk32[0];
key32[1] = irk32[1];
key32[2] = irk32[2];
key32[3] = irk32[3];
pt32 = (uint32_t *)&ecb.plain_text[0];
pt32[0] = 0;
pt32[1] = 0;
pt32[2] = 0;
pt32[3] = 0;
ecb.plain_text[15] = rpa[3];
ecb.plain_text[14] = rpa[4];
ecb.plain_text[13] = rpa[5];
ble_hw_encrypt_block(&ecb);
if ((ecb.cipher_text[15] == rpa[0]) && (ecb.cipher_text[14] == rpa[1]) &&
(ecb.cipher_text[13] == rpa[2])) {
rc = 1;
} else {
rc = 0;
}
return rc;
}
int
ble_ll_resolv_peer_rpa_any(const uint8_t *rpa)
{
int i;
for (i = 0; i < g_ble_ll_resolv_data.rl_cnt_hw; i++) {
if (ble_ll_resolv_rpa(rpa, g_ble_ll_resolv_list[i].rl_peer_irk)) {
return i;
}
}
return -1;
}
/**
* Returns whether or not address resolution is enabled.
*
* @return uint8_t
*/
uint8_t
ble_ll_resolv_enabled(void)
{
return g_ble_ll_resolv_data.addr_res_enabled;
}
/**
* Called to reset private address resolution module.
*/
void
ble_ll_resolv_list_reset(void)
{
g_ble_ll_resolv_data.addr_res_enabled = 0;
ble_npl_callout_stop(&g_ble_ll_resolv_data.rpa_timer);
ble_ll_resolv_list_clr();
ble_ll_resolv_init();
}
void
ble_ll_resolv_init(void)
{
uint8_t hw_size;
/* Default is 15 minutes */
g_ble_ll_resolv_data.rpa_tmo = ble_npl_time_ms_to_ticks32(15 * 60 * 1000);
hw_size = ble_hw_resolv_list_size();
if (hw_size > MYNEWT_VAL(BLE_LL_RESOLV_LIST_SIZE)) {
hw_size = MYNEWT_VAL(BLE_LL_RESOLV_LIST_SIZE);
}
g_ble_ll_resolv_data.rl_size = hw_size;
ble_npl_callout_init(&g_ble_ll_resolv_data.rpa_timer,
&g_ble_ll_data.ll_evq,
ble_ll_resolv_rpa_timer_cb,
NULL);
#if MYNEWT_VAL(BLE_LL_HCI_VS_LOCAL_IRK)
memset(&g_local_irk, 0, sizeof(g_local_irk));
#endif
}
#endif /* if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_PRIVACY) */