/* * 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 #include #include #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_hw.h" #include "ble_ll_conn_priv.h" #if (MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_PRIVACY) == 1) struct ble_ll_resolv_data { uint8_t addr_res_enabled; uint8_t rl_size; uint8_t rl_cnt; uint32_t rpa_tmo; struct ble_npl_callout rpa_timer; }; struct ble_ll_resolv_data g_ble_ll_resolv_data; struct ble_ll_resolv_entry g_ble_ll_resolv_list[MYNEWT_VAL(BLE_LL_RESOLV_LIST_SIZE)]; static int ble_ll_is_controller_busy(void) { return ble_ll_adv_enabled() || ble_ll_scan_enabled() || g_ble_ll_conn_create_sm; } /** * 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_controller_busy()) { rc = 0; } else { rc = 1; } return rc; } /** * 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 *prand; struct ble_encryption_block ecb; 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; } /* Get prand */ prand = addr + 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); addr[0] = ecb.cipher_text[15]; addr[1] = ecb.cipher_text[14]; addr[2] = ecb.cipher_text[13]; } /** * 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; rl = &g_ble_ll_resolv_list[0]; for (i = 0; i < g_ble_ll_resolv_data.rl_cnt; ++i) { OS_ENTER_CRITICAL(sr); ble_ll_resolv_gen_priv_addr(rl, 1); OS_EXIT_CRITICAL(sr); OS_ENTER_CRITICAL(sr); ble_ll_resolv_gen_priv_addr(rl, 0); OS_EXIT_CRITICAL(sr); ++rl; } ble_npl_callout_reset(&g_ble_ll_resolv_data.rpa_timer, (int32_t)g_ble_ll_resolv_data.rpa_tmo); ble_ll_adv_rpa_timeout(); } /** * Called to determine if the IRK is all zero. * * @param irk * * @return int 0: IRK is zero . 1: IRK has non-zero value. */ int ble_ll_resolv_irk_nonzero(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 = 0; ble_hw_resolv_list_clear(); 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) { rspbuf[0] = g_ble_ll_resolv_data.rl_size; *rsplen = 1; 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(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(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(uint8_t *cmdbuf) { int rc; uint8_t addr_type; uint8_t *ident_addr; struct ble_ll_resolv_entry *rl; /* 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; } addr_type = cmdbuf[0]; ident_addr = cmdbuf + 1; /* 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(ident_addr, addr_type)) { return BLE_ERR_INV_HCI_CMD_PARMS; } rl = &g_ble_ll_resolv_list[g_ble_ll_resolv_data.rl_cnt]; memset (rl, 0, sizeof(*rl)); rl->rl_addr_type = addr_type; memcpy(&rl->rl_identity_addr[0], ident_addr, BLE_DEV_ADDR_LEN); swap_buf(rl->rl_peer_irk, cmdbuf + 7, 16); swap_buf(rl->rl_local_irk, cmdbuf + 23, 16); /* By default use privacy network mode */ rl->rl_priv_mode = BLE_HCI_PRIVACY_NETWORK; /* Add peer IRK to HW resolving list. Should always succeed since we * already checked if there is room for it. */ rc = ble_hw_resolv_list_add(rl->rl_peer_irk); BLE_LL_ASSERT (rc == BLE_ERR_SUCCESS); /* generate a local and peer RPAs now, those will be updated by timer * when resolution is enabled */ ble_ll_resolv_gen_priv_addr(rl, 1); ble_ll_resolv_gen_priv_addr(rl, 0); ++g_ble_ll_resolv_data.rl_cnt; 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(uint8_t *cmdbuf) { int position; uint8_t addr_type; uint8_t *ident_addr; /* Must be in proper state */ if (!ble_ll_resolv_list_chg_allowed()) { return BLE_ERR_CMD_DISALLOWED; } addr_type = cmdbuf[0]; ident_addr = cmdbuf + 1; /* Remove from IRK records */ position = ble_ll_is_on_resolv_list(ident_addr, 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); --g_ble_ll_resolv_data.rl_cnt; /* Remove from HW list */ ble_hw_resolv_list_rmv(position - 1); 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(uint8_t *cmdbuf) { int rc; int32_t tmo; uint8_t enabled; if (ble_ll_is_controller_busy()) { rc = BLE_ERR_CMD_DISALLOWED; } else { enabled = cmdbuf[0]; if (enabled <= 1) { /* If we change state, we need to disable/enable the RPA timer */ if ((enabled ^ g_ble_ll_resolv_data.addr_res_enabled) != 0) { if (enabled) { tmo = (int32_t)g_ble_ll_resolv_data.rpa_tmo; ble_npl_callout_reset(&g_ble_ll_resolv_data.rpa_timer, tmo); } else { ble_npl_callout_stop(&g_ble_ll_resolv_data.rpa_timer); } g_ble_ll_resolv_data.addr_res_enabled = enabled; } rc = BLE_ERR_SUCCESS; } else { rc = BLE_ERR_INV_HCI_CMD_PARMS; } } return rc; } int ble_ll_resolv_peer_addr_rd(uint8_t *cmdbuf, uint8_t *rspbuf, uint8_t *rsplen) { struct ble_ll_resolv_entry *rl; uint8_t addr_type; uint8_t *ident_addr; int rc; addr_type = cmdbuf[0]; ident_addr = cmdbuf + 1; rl = ble_ll_resolv_list_find(ident_addr, addr_type); if (rl) { memcpy(rspbuf, rl->rl_peer_rpa, BLE_DEV_ADDR_LEN); rc = BLE_ERR_SUCCESS; } else { memset(rspbuf, 0, BLE_DEV_ADDR_LEN); rc = BLE_ERR_UNK_CONN_ID; } *rsplen = BLE_DEV_ADDR_LEN; return rc; } int ble_ll_resolv_local_addr_rd(uint8_t *cmdbuf, uint8_t *rspbuf, uint8_t *rsplen) { struct ble_ll_resolv_entry *rl; uint8_t addr_type; uint8_t *ident_addr; int rc; addr_type = cmdbuf[0]; ident_addr = cmdbuf + 1; rl = ble_ll_resolv_list_find(ident_addr, addr_type); if (rl) { memcpy(rspbuf, rl->rl_local_rpa, BLE_DEV_ADDR_LEN); rc = BLE_ERR_SUCCESS; } else { memset(rspbuf, 0, BLE_DEV_ADDR_LEN); rc = BLE_ERR_UNK_CONN_ID; } *rsplen = BLE_DEV_ADDR_LEN; return rc; } /** * Set the resolvable private address timeout. * * @param cmdbuf * * @return int */ int ble_ll_resolv_set_rpa_tmo(uint8_t *cmdbuf) { uint16_t tmo_secs; tmo_secs = get_le16(cmdbuf); 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); /* If resolving is not enabled, we are done here. */ if (!ble_ll_resolv_enabled()) { return BLE_ERR_SUCCESS; } /* Reset timeout if resolving is enabled */ ble_npl_callout_reset(&g_ble_ll_resolv_data.rpa_timer, (int32_t)g_ble_ll_resolv_data.rpa_tmo); return BLE_ERR_SUCCESS; } int ble_ll_resolve_set_priv_mode(uint8_t *cmdbuf) { struct ble_ll_resolv_entry *rl; if (ble_ll_is_controller_busy()) { return BLE_ERR_CMD_DISALLOWED; } /* cmdbuf = addr_type(0) | addr(6) | priv_mode(1) */ rl = ble_ll_resolv_list_find(&cmdbuf[1], cmdbuf[0]); if (!rl) { return BLE_ERR_UNK_CONN_ID; } if (cmdbuf[7] > BLE_HCI_PRIVACY_DEVICE) { return BLE_ERR_INV_HCI_CMD_PARMS; } rl->rl_priv_mode = cmdbuf[7]; return 0; } /** * 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) { memcpy(addr, rl->rl_local_rpa, BLE_DEV_ADDR_LEN); } else { 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); } /** * 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) { int rc; uint8_t *irk; struct ble_ll_resolv_entry *rl; rc = 0; rl = ble_ll_resolv_list_find(addr, addr_type); if (rl) { if (local) { irk = rl->rl_local_irk; } else { irk = rl->rl_peer_irk; } if (ble_ll_resolv_irk_nonzero(irk)) { ble_ll_resolv_get_priv_addr(rl, local, rpa); rc = 1; } } return rc; } /** * Resolve a Resolvable Private Address * * @param rpa * @param index * * @return int */ int ble_ll_resolv_rpa(uint8_t *rpa, uint8_t *irk) { int rc; uint32_t *irk32; uint32_t *key32; uint32_t *pt32; struct ble_encryption_block ecb; irk32 = (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; } /** * 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); } #endif /* if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_PRIVACY) == 1 */