Many #ifdefs can be removed, but the UNPROV_BEACON_INT Kconfig variable needs to be also made available also. This is done by making its prompt (user selectability) optional rather than the option itself. This approach is fine for "parameter style" options, but should probably not be used for feature enabling options. Signed-off-by: Johan Hedberg <johan.hedberg@intel.com>
449 lines
9.7 KiB
C
449 lines
9.7 KiB
C
/* Bluetooth Mesh */
|
|
|
|
/*
|
|
* Copyright (c) 2017 Intel Corporation
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#include <zephyr.h>
|
|
#include <errno.h>
|
|
#include <sys/util.h>
|
|
|
|
#include <net/buf.h>
|
|
#include <bluetooth/bluetooth.h>
|
|
#include <bluetooth/conn.h>
|
|
#include <bluetooth/mesh.h>
|
|
|
|
#define BT_DBG_ENABLED IS_ENABLED(CONFIG_BT_MESH_DEBUG_BEACON)
|
|
#define LOG_MODULE_NAME bt_mesh_beacon
|
|
#include "common/log.h"
|
|
|
|
#include "adv.h"
|
|
#include "mesh.h"
|
|
#include "net.h"
|
|
#include "prov.h"
|
|
#include "crypto.h"
|
|
#include "beacon.h"
|
|
#include "foundation.h"
|
|
|
|
#define PROVISIONED_INTERVAL K_SECONDS(10)
|
|
|
|
#define BEACON_TYPE_UNPROVISIONED 0x00
|
|
#define BEACON_TYPE_SECURE 0x01
|
|
|
|
/* 3 transmissions, 20ms interval */
|
|
#define UNPROV_XMIT BT_MESH_TRANSMIT(2, 20)
|
|
|
|
/* 1 transmission, 20ms interval */
|
|
#define PROV_XMIT BT_MESH_TRANSMIT(0, 20)
|
|
|
|
static struct k_delayed_work beacon_timer;
|
|
|
|
static struct bt_mesh_subnet *cache_check(uint8_t data[21])
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(bt_mesh.sub); i++) {
|
|
struct bt_mesh_subnet *sub = &bt_mesh.sub[i];
|
|
|
|
if (sub->net_idx == BT_MESH_KEY_UNUSED) {
|
|
continue;
|
|
}
|
|
|
|
if (!memcmp(sub->beacon_cache, data, 21)) {
|
|
return sub;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static void cache_add(uint8_t data[21], struct bt_mesh_subnet *sub)
|
|
{
|
|
memcpy(sub->beacon_cache, data, 21);
|
|
}
|
|
|
|
static void beacon_complete(int err, void *user_data)
|
|
{
|
|
struct bt_mesh_subnet *sub = user_data;
|
|
|
|
BT_DBG("err %d", err);
|
|
|
|
sub->beacon_sent = k_uptime_get_32();
|
|
}
|
|
|
|
void bt_mesh_beacon_create(struct bt_mesh_subnet *sub,
|
|
struct net_buf_simple *buf)
|
|
{
|
|
uint8_t flags = bt_mesh_net_flags(sub);
|
|
struct bt_mesh_subnet_keys *keys;
|
|
|
|
net_buf_simple_add_u8(buf, BEACON_TYPE_SECURE);
|
|
|
|
if (sub->kr_flag) {
|
|
keys = &sub->keys[1];
|
|
} else {
|
|
keys = &sub->keys[0];
|
|
}
|
|
|
|
net_buf_simple_add_u8(buf, flags);
|
|
|
|
/* Network ID */
|
|
net_buf_simple_add_mem(buf, keys->net_id, 8);
|
|
|
|
/* IV Index */
|
|
net_buf_simple_add_be32(buf, bt_mesh.iv_index);
|
|
|
|
net_buf_simple_add_mem(buf, sub->auth, 8);
|
|
|
|
BT_DBG("net_idx 0x%04x flags 0x%02x NetID %s", sub->net_idx,
|
|
flags, bt_hex(keys->net_id, 8));
|
|
BT_DBG("IV Index 0x%08x Auth %s", bt_mesh.iv_index,
|
|
bt_hex(sub->auth, 8));
|
|
}
|
|
|
|
/* If the interval has passed or is within 5 seconds from now send a beacon */
|
|
#define BEACON_THRESHOLD(sub) \
|
|
((10 * ((sub)->beacons_last + 1)) * MSEC_PER_SEC - (5 * MSEC_PER_SEC))
|
|
|
|
static int secure_beacon_send(void)
|
|
{
|
|
static const struct bt_mesh_send_cb send_cb = {
|
|
.end = beacon_complete,
|
|
};
|
|
uint32_t now = k_uptime_get_32();
|
|
int i;
|
|
|
|
BT_DBG("");
|
|
|
|
for (i = 0; i < ARRAY_SIZE(bt_mesh.sub); i++) {
|
|
struct bt_mesh_subnet *sub = &bt_mesh.sub[i];
|
|
struct net_buf *buf;
|
|
uint32_t time_diff;
|
|
|
|
if (sub->net_idx == BT_MESH_KEY_UNUSED) {
|
|
continue;
|
|
}
|
|
|
|
time_diff = now - sub->beacon_sent;
|
|
if (time_diff < (600 * MSEC_PER_SEC) &&
|
|
time_diff < BEACON_THRESHOLD(sub)) {
|
|
continue;
|
|
}
|
|
|
|
buf = bt_mesh_adv_create(BT_MESH_ADV_BEACON, PROV_XMIT,
|
|
K_NO_WAIT);
|
|
if (!buf) {
|
|
BT_ERR("Unable to allocate beacon buffer");
|
|
return -ENOBUFS;
|
|
}
|
|
|
|
bt_mesh_beacon_create(sub, &buf->b);
|
|
|
|
bt_mesh_adv_send(buf, &send_cb, sub);
|
|
net_buf_unref(buf);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int unprovisioned_beacon_send(void)
|
|
{
|
|
const struct bt_mesh_prov *prov;
|
|
uint8_t uri_hash[16] = { 0 };
|
|
struct net_buf *buf;
|
|
uint16_t oob_info;
|
|
|
|
BT_DBG("");
|
|
|
|
buf = bt_mesh_adv_create(BT_MESH_ADV_BEACON, UNPROV_XMIT, K_NO_WAIT);
|
|
if (!buf) {
|
|
BT_ERR("Unable to allocate beacon buffer");
|
|
return -ENOBUFS;
|
|
}
|
|
|
|
prov = bt_mesh_prov_get();
|
|
|
|
net_buf_add_u8(buf, BEACON_TYPE_UNPROVISIONED);
|
|
net_buf_add_mem(buf, prov->uuid, 16);
|
|
|
|
if (prov->uri && bt_mesh_s1(prov->uri, uri_hash) == 0) {
|
|
oob_info = prov->oob_info | BT_MESH_PROV_OOB_URI;
|
|
} else {
|
|
oob_info = prov->oob_info;
|
|
}
|
|
|
|
net_buf_add_be16(buf, oob_info);
|
|
net_buf_add_mem(buf, uri_hash, 4);
|
|
|
|
bt_mesh_adv_send(buf, NULL, NULL);
|
|
net_buf_unref(buf);
|
|
|
|
if (prov->uri) {
|
|
size_t len;
|
|
|
|
buf = bt_mesh_adv_create(BT_MESH_ADV_URI, UNPROV_XMIT,
|
|
K_NO_WAIT);
|
|
if (!buf) {
|
|
BT_ERR("Unable to allocate URI buffer");
|
|
return -ENOBUFS;
|
|
}
|
|
|
|
len = strlen(prov->uri);
|
|
if (net_buf_tailroom(buf) < len) {
|
|
BT_WARN("Too long URI to fit advertising data");
|
|
} else {
|
|
net_buf_add_mem(buf, prov->uri, len);
|
|
bt_mesh_adv_send(buf, NULL, NULL);
|
|
}
|
|
|
|
net_buf_unref(buf);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void unprovisioned_beacon_recv(struct net_buf_simple *buf)
|
|
{
|
|
const struct bt_mesh_prov *prov;
|
|
uint8_t *uuid;
|
|
uint16_t oob_info;
|
|
uint32_t uri_hash_val;
|
|
uint32_t *uri_hash = NULL;
|
|
|
|
if (buf->len != 18 && buf->len != 22) {
|
|
BT_ERR("Invalid unprovisioned beacon length (%u)", buf->len);
|
|
return;
|
|
}
|
|
|
|
uuid = net_buf_simple_pull_mem(buf, 16);
|
|
oob_info = net_buf_simple_pull_be16(buf);
|
|
|
|
if (buf->len == 4) {
|
|
uri_hash_val = net_buf_simple_pull_be32(buf);
|
|
uri_hash = &uri_hash_val;
|
|
}
|
|
|
|
BT_DBG("uuid %s", bt_hex(uuid, 16));
|
|
|
|
prov = bt_mesh_prov_get();
|
|
|
|
if (prov->unprovisioned_beacon) {
|
|
prov->unprovisioned_beacon(uuid,
|
|
(bt_mesh_prov_oob_info_t)oob_info,
|
|
uri_hash);
|
|
}
|
|
}
|
|
|
|
static void update_beacon_observation(void)
|
|
{
|
|
static bool first_half;
|
|
int i;
|
|
|
|
/* Observation period is 20 seconds, whereas the beacon timer
|
|
* runs every 10 seconds. We process what's happened during the
|
|
* window only after the seconnd half.
|
|
*/
|
|
first_half = !first_half;
|
|
if (first_half) {
|
|
return;
|
|
}
|
|
|
|
for (i = 0; i < ARRAY_SIZE(bt_mesh.sub); i++) {
|
|
struct bt_mesh_subnet *sub = &bt_mesh.sub[i];
|
|
|
|
if (sub->net_idx == BT_MESH_KEY_UNUSED) {
|
|
continue;
|
|
}
|
|
|
|
sub->beacons_last = sub->beacons_cur;
|
|
sub->beacons_cur = 0U;
|
|
}
|
|
}
|
|
|
|
static void beacon_send(struct k_work *work)
|
|
{
|
|
/* Don't send anything if we have an active provisioning link */
|
|
if (IS_ENABLED(CONFIG_BT_MESH_PB_ADV) && bt_prov_active()) {
|
|
k_delayed_work_submit(&beacon_timer, K_SECONDS(CONFIG_BT_MESH_UNPROV_BEACON_INT));
|
|
return;
|
|
}
|
|
|
|
BT_DBG("");
|
|
|
|
if (bt_mesh_is_provisioned()) {
|
|
update_beacon_observation();
|
|
secure_beacon_send();
|
|
|
|
/* Only resubmit if beaconing is still enabled */
|
|
if (bt_mesh_beacon_get() == BT_MESH_BEACON_ENABLED ||
|
|
atomic_test_bit(bt_mesh.flags, BT_MESH_IVU_INITIATOR)) {
|
|
k_delayed_work_submit(&beacon_timer,
|
|
PROVISIONED_INTERVAL);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
if (IS_ENABLED(CONFIG_BT_MESH_PB_ADV)) {
|
|
unprovisioned_beacon_send();
|
|
k_delayed_work_submit(&beacon_timer, K_SECONDS(CONFIG_BT_MESH_UNPROV_BEACON_INT));
|
|
}
|
|
}
|
|
|
|
static void secure_beacon_recv(struct net_buf_simple *buf)
|
|
{
|
|
uint8_t *data, *net_id, *auth;
|
|
struct bt_mesh_subnet *sub;
|
|
uint32_t iv_index;
|
|
bool new_key, kr_change, iv_change;
|
|
uint8_t flags;
|
|
|
|
if (buf->len < 21) {
|
|
BT_ERR("Too short secure beacon (len %u)", buf->len);
|
|
return;
|
|
}
|
|
|
|
sub = cache_check(buf->data);
|
|
if (sub) {
|
|
/* We've seen this beacon before - just update the stats */
|
|
goto update_stats;
|
|
}
|
|
|
|
/* So we can add to the cache if auth matches */
|
|
data = buf->data;
|
|
|
|
flags = net_buf_simple_pull_u8(buf);
|
|
net_id = net_buf_simple_pull_mem(buf, 8);
|
|
iv_index = net_buf_simple_pull_be32(buf);
|
|
auth = buf->data;
|
|
|
|
BT_DBG("flags 0x%02x id %s iv_index 0x%08x",
|
|
flags, bt_hex(net_id, 8), iv_index);
|
|
|
|
sub = bt_mesh_subnet_find(net_id, flags, iv_index, auth, &new_key);
|
|
if (!sub) {
|
|
BT_DBG("No subnet that matched beacon");
|
|
return;
|
|
}
|
|
|
|
if (sub->kr_phase == BT_MESH_KR_PHASE_2 && !new_key) {
|
|
BT_WARN("Ignoring Phase 2 KR Update secured using old key");
|
|
return;
|
|
}
|
|
|
|
cache_add(data, sub);
|
|
|
|
kr_change = bt_mesh_kr_update(sub, BT_MESH_KEY_REFRESH(flags), new_key);
|
|
if (kr_change) {
|
|
bt_mesh_net_beacon_update(sub);
|
|
/* Key Refresh without IV Update only impacts one subnet */
|
|
bt_mesh_net_sec_update(sub);
|
|
}
|
|
|
|
/* If we have NetKey0 accept initiation only from it */
|
|
if (bt_mesh_subnet_get(BT_MESH_KEY_PRIMARY) &&
|
|
sub->net_idx != BT_MESH_KEY_PRIMARY) {
|
|
BT_WARN("Ignoring secure beacon on non-primary subnet");
|
|
goto update_stats;
|
|
}
|
|
|
|
BT_DBG("net_idx 0x%04x iv_index 0x%08x, current iv_index 0x%08x",
|
|
sub->net_idx, iv_index, bt_mesh.iv_index);
|
|
|
|
if (atomic_test_bit(bt_mesh.flags, BT_MESH_IVU_INITIATOR) &&
|
|
(atomic_test_bit(bt_mesh.flags, BT_MESH_IVU_IN_PROGRESS) ==
|
|
BT_MESH_IV_UPDATE(flags))) {
|
|
bt_mesh_beacon_ivu_initiator(false);
|
|
}
|
|
|
|
iv_change = bt_mesh_net_iv_update(iv_index, BT_MESH_IV_UPDATE(flags));
|
|
|
|
if (iv_change) {
|
|
/* Update all subnets */
|
|
bt_mesh_net_sec_update(NULL);
|
|
}
|
|
|
|
update_stats:
|
|
if (bt_mesh_beacon_get() == BT_MESH_BEACON_ENABLED &&
|
|
sub->beacons_cur < 0xff) {
|
|
sub->beacons_cur++;
|
|
}
|
|
}
|
|
|
|
void bt_mesh_beacon_recv(struct net_buf_simple *buf)
|
|
{
|
|
uint8_t type;
|
|
|
|
BT_DBG("%u bytes: %s", buf->len, bt_hex(buf->data, buf->len));
|
|
|
|
if (buf->len < 1) {
|
|
BT_ERR("Too short beacon");
|
|
return;
|
|
}
|
|
|
|
type = net_buf_simple_pull_u8(buf);
|
|
switch (type) {
|
|
case BEACON_TYPE_UNPROVISIONED:
|
|
if (IS_ENABLED(CONFIG_BT_MESH_PB_ADV)) {
|
|
unprovisioned_beacon_recv(buf);
|
|
}
|
|
break;
|
|
case BEACON_TYPE_SECURE:
|
|
secure_beacon_recv(buf);
|
|
break;
|
|
default:
|
|
BT_WARN("Unknown beacon type 0x%02x", type);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void bt_mesh_beacon_init(void)
|
|
{
|
|
k_delayed_work_init(&beacon_timer, beacon_send);
|
|
}
|
|
|
|
void bt_mesh_beacon_ivu_initiator(bool enable)
|
|
{
|
|
atomic_set_bit_to(bt_mesh.flags, BT_MESH_IVU_INITIATOR, enable);
|
|
|
|
if (enable) {
|
|
k_work_submit(&beacon_timer.work);
|
|
} else if (bt_mesh_beacon_get() == BT_MESH_BEACON_DISABLED) {
|
|
k_delayed_work_cancel(&beacon_timer);
|
|
}
|
|
}
|
|
|
|
void bt_mesh_beacon_enable(void)
|
|
{
|
|
int i;
|
|
|
|
if (!bt_mesh_is_provisioned()) {
|
|
k_work_submit(&beacon_timer.work);
|
|
return;
|
|
}
|
|
|
|
for (i = 0; i < ARRAY_SIZE(bt_mesh.sub); i++) {
|
|
struct bt_mesh_subnet *sub = &bt_mesh.sub[i];
|
|
|
|
if (sub->net_idx == BT_MESH_KEY_UNUSED) {
|
|
continue;
|
|
}
|
|
|
|
sub->beacons_last = 0U;
|
|
sub->beacons_cur = 0U;
|
|
|
|
bt_mesh_net_beacon_update(sub);
|
|
}
|
|
|
|
k_work_submit(&beacon_timer.work);
|
|
}
|
|
|
|
void bt_mesh_beacon_disable(void)
|
|
{
|
|
if (!atomic_test_bit(bt_mesh.flags, BT_MESH_IVU_INITIATOR)) {
|
|
k_delayed_work_cancel(&beacon_timer);
|
|
}
|
|
}
|