blob: cc0dbfc1aac87c783b7864277f14c2c3f8609845 [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_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 */