| /* |
| * 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 "host/ecc.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_work_delayable beacon_timer; |
| |
| static bool beacon_cache_match(struct bt_mesh_subnet *sub, void *beacon_data) |
| { |
| return !memcmp(sub->beacon_cache, beacon_data, 21); |
| } |
| |
| static void cache_add(uint8_t data[21], struct bt_mesh_subnet *sub) |
| { |
| memcpy(sub->beacon_cache, data, 21); |
| } |
| |
| void bt_mesh_beacon_cache_clear(struct bt_mesh_subnet *sub) |
| { |
| (void)memset(sub->beacon_cache, 0, 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); |
| |
| keys = &sub->keys[SUBNET_KEY_TX_IDX(sub)]; |
| |
| 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 bool secure_beacon_send(struct bt_mesh_subnet *sub, void *cb_data) |
| { |
| static const struct bt_mesh_send_cb send_cb = { |
| .end = beacon_complete, |
| }; |
| uint32_t now = k_uptime_get_32(); |
| struct net_buf *buf; |
| uint32_t time_diff; |
| uint32_t time_since_last_recv; |
| |
| BT_DBG(""); |
| |
| time_diff = now - sub->beacon_sent; |
| time_since_last_recv = now - sub->beacon_recv; |
| if (time_diff < (600 * MSEC_PER_SEC) && |
| (time_diff < BEACON_THRESHOLD(sub) || |
| time_since_last_recv < (10 * MSEC_PER_SEC))) { |
| return false; |
| } |
| |
| buf = bt_mesh_adv_create(BT_MESH_ADV_BEACON, BT_MESH_LOCAL_ADV, |
| PROV_XMIT, K_NO_WAIT); |
| if (!buf) { |
| BT_ERR("Unable to allocate beacon buffer"); |
| return true; /* Bail out */ |
| } |
| |
| bt_mesh_beacon_create(sub, &buf->b); |
| |
| bt_mesh_adv_send(buf, &send_cb, sub); |
| net_buf_unref(buf); |
| |
| return false; |
| } |
| |
| 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, BT_MESH_LOCAL_ADV, |
| 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, BT_MESH_LOCAL_ADV, |
| 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 sub_update_beacon_observation(struct bt_mesh_subnet *sub) |
| { |
| sub->beacons_last = sub->beacons_cur; |
| sub->beacons_cur = 0U; |
| } |
| |
| static void update_beacon_observation(void) |
| { |
| static bool first_half; |
| |
| /* Observation period is 20 seconds, whereas the beacon timer |
| * runs every 10 seconds. We process what's happened during the |
| * window only after the second half. |
| */ |
| first_half = !first_half; |
| if (first_half) { |
| return; |
| } |
| |
| bt_mesh_subnet_foreach(sub_update_beacon_observation); |
| } |
| |
| static void beacon_send(struct k_work *work) |
| { |
| BT_DBG(""); |
| |
| if (bt_mesh_is_provisioned()) { |
| if (!bt_mesh_beacon_enabled() && |
| !atomic_test_bit(bt_mesh.flags, BT_MESH_IVU_INITIATOR)) { |
| return; |
| } |
| |
| update_beacon_observation(); |
| (void)bt_mesh_subnet_find(secure_beacon_send, NULL); |
| |
| k_work_schedule(&beacon_timer, PROVISIONED_INTERVAL); |
| return; |
| } |
| |
| if (IS_ENABLED(CONFIG_BT_MESH_PB_ADV)) { |
| /* Don't send anything if we have an active provisioning link */ |
| if (!bt_mesh_prov_active()) { |
| unprovisioned_beacon_send(); |
| } |
| |
| k_work_schedule(&beacon_timer, K_SECONDS(CONFIG_BT_MESH_UNPROV_BEACON_INT)); |
| } |
| |
| } |
| |
| struct beacon_params { |
| const uint8_t *net_id; |
| const uint8_t *auth; |
| uint32_t iv_index; |
| uint8_t flags; |
| |
| bool new_key; |
| }; |
| |
| static bool auth_match(struct bt_mesh_subnet_keys *keys, |
| const struct beacon_params *params) |
| { |
| uint8_t net_auth[8]; |
| |
| if (memcmp(params->net_id, keys->net_id, 8)) { |
| return false; |
| } |
| |
| bt_mesh_beacon_auth(keys->beacon, params->flags, keys->net_id, |
| params->iv_index, net_auth); |
| |
| if (memcmp(params->auth, net_auth, 8)) { |
| BT_WARN("Authentication Value %s != %s", |
| bt_hex(params->auth, 8), bt_hex(net_auth, 8)); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| static bool subnet_by_id(struct bt_mesh_subnet *sub, void *cb_data) |
| { |
| struct beacon_params *params = cb_data; |
| |
| for (int i = 0; i < ARRAY_SIZE(sub->keys); i++) { |
| if (sub->keys[i].valid && auth_match(&sub->keys[i], params)) { |
| params->new_key = (i > 0); |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| static void secure_beacon_recv(struct net_buf_simple *buf) |
| { |
| struct beacon_params params; |
| struct bt_mesh_subnet *sub; |
| uint8_t *data; |
| |
| if (buf->len < 21) { |
| BT_ERR("Too short secure beacon (len %u)", buf->len); |
| return; |
| } |
| |
| sub = bt_mesh_subnet_find(beacon_cache_match, 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; |
| |
| params.flags = net_buf_simple_pull_u8(buf); |
| params.net_id = net_buf_simple_pull_mem(buf, 8); |
| params.iv_index = net_buf_simple_pull_be32(buf); |
| params.auth = buf->data; |
| |
| BT_DBG("flags 0x%02x id %s iv_index 0x%08x", |
| params.flags, bt_hex(params.net_id, 8), params.iv_index); |
| |
| sub = bt_mesh_subnet_find(subnet_by_id, ¶ms); |
| if (!sub) { |
| BT_DBG("No subnet that matched beacon"); |
| return; |
| } |
| |
| if (sub->kr_phase == BT_MESH_KR_PHASE_2 && !params.new_key) { |
| BT_WARN("Ignoring Phase 2 KR Update secured using old key"); |
| return; |
| } |
| |
| cache_add(data, sub); |
| |
| bt_mesh_kr_update(sub, BT_MESH_KEY_REFRESH(params.flags), |
| params.new_key); |
| |
| /* 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, params.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(params.flags))) { |
| bt_mesh_beacon_ivu_initiator(false); |
| } |
| |
| bt_mesh_net_iv_update(params.iv_index, BT_MESH_IV_UPDATE(params.flags)); |
| |
| update_stats: |
| if (bt_mesh_beacon_enabled() && |
| sub->beacons_cur < 0xff) { |
| sub->beacons_cur++; |
| sub->beacon_recv = k_uptime_get_32(); |
| } |
| } |
| |
| 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_update(struct bt_mesh_subnet *sub) |
| { |
| uint8_t flags = bt_mesh_net_flags(sub); |
| struct bt_mesh_subnet_keys *keys; |
| int err; |
| |
| keys = &sub->keys[SUBNET_KEY_TX_IDX(sub)]; |
| |
| BT_DBG("NetIndex 0x%03x Using %s key", sub->net_idx, |
| SUBNET_KEY_TX_IDX(sub) ? "new" : "current"); |
| BT_DBG("flags 0x%02x, IVI 0x%08x", flags, bt_mesh.iv_index); |
| |
| err = bt_mesh_beacon_auth(keys->beacon, flags, keys->net_id, |
| bt_mesh.iv_index, sub->auth); |
| if (err) { |
| BT_ERR("Failed updating net beacon for 0x%03x", sub->net_idx); |
| } |
| } |
| |
| static void subnet_evt(struct bt_mesh_subnet *sub, enum bt_mesh_key_evt evt) |
| { |
| if (evt != BT_MESH_KEY_DELETED) { |
| bt_mesh_beacon_update(sub); |
| } |
| } |
| |
| BT_MESH_SUBNET_CB_DEFINE(beacon) = { |
| .evt_handler = subnet_evt, |
| }; |
| |
| void bt_mesh_beacon_init(void) |
| { |
| k_work_init_delayable(&beacon_timer, beacon_send); |
| } |
| |
| void bt_mesh_beacon_ivu_initiator(bool enable) |
| { |
| atomic_set_bit_to(bt_mesh.flags, BT_MESH_IVU_INITIATOR, enable); |
| |
| /* Fire the beacon handler straight away if it's not already pending - |
| * in which case we'll fire according to the ongoing periodic sending. |
| * If beacons are disabled, the handler will exit early. |
| * |
| * An alternative solution would be to check whether beacons are enabled |
| * here, and cancel if not. As the cancel operation may fail, we would |
| * still have to implement an early exit mechanism, so we might as well |
| * just use this every time. |
| */ |
| k_work_schedule(&beacon_timer, K_NO_WAIT); |
| } |
| |
| static void subnet_beacon_enable(struct bt_mesh_subnet *sub) |
| { |
| sub->beacons_last = 0U; |
| sub->beacons_cur = 0U; |
| |
| bt_mesh_beacon_update(sub); |
| } |
| |
| void bt_mesh_beacon_enable(void) |
| { |
| if (bt_mesh_is_provisioned()) { |
| bt_mesh_subnet_foreach(subnet_beacon_enable); |
| } |
| |
| k_work_reschedule(&beacon_timer, K_NO_WAIT); |
| } |
| |
| void bt_mesh_beacon_disable(void) |
| { |
| if (!atomic_test_bit(bt_mesh.flags, BT_MESH_IVU_INITIATOR)) { |
| /* If this fails, we'll do an early exit in the work handler. */ |
| (void)k_work_cancel_delayable(&beacon_timer); |
| } |
| } |