/* * Copyright (c) 2018 Nordic Semiconductor ASA * Copyright (c) 2017 Intel Corporation * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include #include #include #include #include #include #include "common/bt_str.h" #include "adv.h" #include "net.h" #include "foundation.h" #include "beacon.h" #include "host/ecc.h" #include "prov.h" #include "proxy.h" #include "pb_gatt_srv.h" #include "solicitation.h" #define LOG_LEVEL CONFIG_BT_MESH_ADV_LOG_LEVEL #include LOG_MODULE_REGISTER(bt_mesh_adv); /* Window and Interval are equal for continuous scanning */ #define MESH_SCAN_INTERVAL BT_MESH_ADV_SCAN_UNIT(BT_MESH_SCAN_INTERVAL_MS) #define MESH_SCAN_WINDOW BT_MESH_ADV_SCAN_UNIT(BT_MESH_SCAN_WINDOW_MS) const uint8_t bt_mesh_adv_type[BT_MESH_ADV_TYPES] = { [BT_MESH_ADV_PROV] = BT_DATA_MESH_PROV, [BT_MESH_ADV_DATA] = BT_DATA_MESH_MESSAGE, [BT_MESH_ADV_BEACON] = BT_DATA_MESH_BEACON, [BT_MESH_ADV_URI] = BT_DATA_URI, }; static bool active_scanning; static K_FIFO_DEFINE(bt_mesh_adv_queue); static K_FIFO_DEFINE(bt_mesh_relay_queue); static K_FIFO_DEFINE(bt_mesh_friend_queue); static void adv_buf_destroy(struct net_buf *buf) { struct bt_mesh_adv adv = *BT_MESH_ADV(buf); net_buf_destroy(buf); bt_mesh_adv_send_end(0, &adv); } NET_BUF_POOL_DEFINE(adv_buf_pool, CONFIG_BT_MESH_ADV_BUF_COUNT, BT_MESH_ADV_DATA_SIZE, BT_MESH_ADV_USER_DATA_SIZE, adv_buf_destroy); static struct bt_mesh_adv adv_local_pool[CONFIG_BT_MESH_ADV_BUF_COUNT]; #if defined(CONFIG_BT_MESH_RELAY) NET_BUF_POOL_DEFINE(relay_buf_pool, CONFIG_BT_MESH_RELAY_BUF_COUNT, BT_MESH_ADV_DATA_SIZE, BT_MESH_ADV_USER_DATA_SIZE, adv_buf_destroy); static struct bt_mesh_adv adv_relay_pool[CONFIG_BT_MESH_RELAY_BUF_COUNT]; #endif #if defined(CONFIG_BT_MESH_ADV_EXT_FRIEND_SEPARATE) NET_BUF_POOL_DEFINE(friend_buf_pool, CONFIG_BT_MESH_FRIEND_LPN_COUNT, BT_MESH_ADV_DATA_SIZE, BT_MESH_ADV_USER_DATA_SIZE, adv_buf_destroy); static struct bt_mesh_adv adv_friend_pool[CONFIG_BT_MESH_FRIEND_LPN_COUNT]; #endif static struct net_buf *bt_mesh_adv_create_from_pool(struct net_buf_pool *buf_pool, struct bt_mesh_adv *adv_pool, enum bt_mesh_adv_type type, enum bt_mesh_adv_tag tag, uint8_t xmit, k_timeout_t timeout) { struct bt_mesh_adv *adv; struct net_buf *buf; if (atomic_test_bit(bt_mesh.flags, BT_MESH_SUSPENDED)) { LOG_WRN("Refusing to allocate buffer while suspended"); return NULL; } buf = net_buf_alloc(buf_pool, timeout); if (!buf) { return NULL; } adv = &adv_pool[net_buf_id(buf)]; BT_MESH_ADV(buf) = adv; (void)memset(adv, 0, sizeof(*adv)); adv->type = type; adv->tag = tag; adv->xmit = xmit; return buf; } struct net_buf *bt_mesh_adv_create(enum bt_mesh_adv_type type, enum bt_mesh_adv_tag tag, uint8_t xmit, k_timeout_t timeout) { #if defined(CONFIG_BT_MESH_RELAY) if (tag & BT_MESH_RELAY_ADV) { return bt_mesh_adv_create_from_pool(&relay_buf_pool, adv_relay_pool, type, tag, xmit, timeout); } #endif #if defined(CONFIG_BT_MESH_ADV_EXT_FRIEND_SEPARATE) if (tag & BT_MESH_FRIEND_ADV) { return bt_mesh_adv_create_from_pool(&friend_buf_pool, adv_friend_pool, type, tag, xmit, timeout); } #endif return bt_mesh_adv_create_from_pool(&adv_buf_pool, adv_local_pool, type, tag, xmit, timeout); } #if CONFIG_BT_MESH_RELAY_ADV_SETS || CONFIG_BT_MESH_ADV_EXT_FRIEND_SEPARATE static struct net_buf *process_events(struct k_poll_event *ev, int count) { for (; count; ev++, count--) { LOG_DBG("ev->state %u", ev->state); switch (ev->state) { case K_POLL_STATE_FIFO_DATA_AVAILABLE: return net_buf_get(ev->fifo, K_NO_WAIT); case K_POLL_STATE_NOT_READY: case K_POLL_STATE_CANCELLED: break; default: LOG_WRN("Unexpected k_poll event state %u", ev->state); break; } } return NULL; } struct net_buf *bt_mesh_adv_buf_get(k_timeout_t timeout) { int err; struct k_poll_event events[] = { K_POLL_EVENT_STATIC_INITIALIZER(K_POLL_TYPE_FIFO_DATA_AVAILABLE, K_POLL_MODE_NOTIFY_ONLY, &bt_mesh_adv_queue, 0), #if defined(CONFIG_BT_MESH_ADV_EXT_RELAY_USING_MAIN_ADV_SET) K_POLL_EVENT_STATIC_INITIALIZER(K_POLL_TYPE_FIFO_DATA_AVAILABLE, K_POLL_MODE_NOTIFY_ONLY, &bt_mesh_relay_queue, 0), #endif /* CONFIG_BT_MESH_ADV_EXT_RELAY_USING_MAIN_ADV_SET */ }; err = k_poll(events, ARRAY_SIZE(events), timeout); if (err) { return NULL; } return process_events(events, ARRAY_SIZE(events)); } struct net_buf *bt_mesh_adv_buf_get_by_tag(uint8_t tag, k_timeout_t timeout) { if (IS_ENABLED(CONFIG_BT_MESH_ADV_EXT_FRIEND_SEPARATE) && tag & BT_MESH_FRIEND_ADV) { return net_buf_get(&bt_mesh_friend_queue, timeout); } #if CONFIG_BT_MESH_RELAY_ADV_SETS if (tag & BT_MESH_RELAY_ADV) { return net_buf_get(&bt_mesh_relay_queue, timeout); } #endif return bt_mesh_adv_buf_get(timeout); } #else /* !(CONFIG_BT_MESH_RELAY_ADV_SETS || CONFIG_BT_MESH_ADV_EXT_FRIEND_SEPARATE) */ struct net_buf *bt_mesh_adv_buf_get(k_timeout_t timeout) { return net_buf_get(&bt_mesh_adv_queue, timeout); } struct net_buf *bt_mesh_adv_buf_get_by_tag(uint8_t tag, k_timeout_t timeout) { ARG_UNUSED(tag); return bt_mesh_adv_buf_get(timeout); } #endif /* CONFIG_BT_MESH_RELAY_ADV_SETS || CONFIG_BT_MESH_ADV_EXT_FRIEND_SEPARATE */ void bt_mesh_adv_buf_get_cancel(void) { LOG_DBG(""); k_fifo_cancel_wait(&bt_mesh_adv_queue); #if CONFIG_BT_MESH_RELAY_ADV_SETS k_fifo_cancel_wait(&bt_mesh_relay_queue); #endif /* CONFIG_BT_MESH_RELAY_ADV_SETS */ if (IS_ENABLED(CONFIG_BT_MESH_ADV_EXT_FRIEND_SEPARATE)) { k_fifo_cancel_wait(&bt_mesh_friend_queue); } } void bt_mesh_adv_send(struct net_buf *buf, const struct bt_mesh_send_cb *cb, void *cb_data) { LOG_DBG("type 0x%02x len %u: %s", BT_MESH_ADV(buf)->type, buf->len, bt_hex(buf->data, buf->len)); BT_MESH_ADV(buf)->cb = cb; BT_MESH_ADV(buf)->cb_data = cb_data; BT_MESH_ADV(buf)->busy = 1U; if (IS_ENABLED(CONFIG_BT_MESH_ADV_EXT_FRIEND_SEPARATE) && BT_MESH_ADV(buf)->tag == BT_MESH_FRIEND_ADV) { net_buf_put(&bt_mesh_friend_queue, net_buf_ref(buf)); bt_mesh_adv_buf_friend_ready(); return; } #if CONFIG_BT_MESH_RELAY_ADV_SETS if (BT_MESH_ADV(buf)->tag == BT_MESH_RELAY_ADV) { net_buf_put(&bt_mesh_relay_queue, net_buf_ref(buf)); bt_mesh_adv_buf_relay_ready(); return; } #endif net_buf_put(&bt_mesh_adv_queue, net_buf_ref(buf)); bt_mesh_adv_buf_local_ready(); } int bt_mesh_adv_gatt_send(void) { if (bt_mesh_is_provisioned()) { if (IS_ENABLED(CONFIG_BT_MESH_GATT_PROXY)) { LOG_DBG("Proxy Advertising"); return bt_mesh_proxy_adv_start(); } } else if (IS_ENABLED(CONFIG_BT_MESH_PB_GATT)) { LOG_DBG("PB-GATT Advertising"); return bt_mesh_pb_gatt_srv_adv_start(); } return -ENOTSUP; } static void bt_mesh_scan_cb(const bt_addr_le_t *addr, int8_t rssi, uint8_t adv_type, struct net_buf_simple *buf) { if (adv_type != BT_GAP_ADV_TYPE_ADV_NONCONN_IND) { return; } LOG_DBG("len %u: %s", buf->len, bt_hex(buf->data, buf->len)); while (buf->len > 1) { struct net_buf_simple_state state; uint8_t len, type; len = net_buf_simple_pull_u8(buf); /* Check for early termination */ if (len == 0U) { return; } if (len > buf->len) { LOG_WRN("AD malformed"); return; } net_buf_simple_save(buf, &state); type = net_buf_simple_pull_u8(buf); buf->len = len - 1; switch (type) { case BT_DATA_MESH_MESSAGE: bt_mesh_net_recv(buf, rssi, BT_MESH_NET_IF_ADV); break; #if defined(CONFIG_BT_MESH_PB_ADV) case BT_DATA_MESH_PROV: bt_mesh_pb_adv_recv(buf); break; #endif case BT_DATA_MESH_BEACON: bt_mesh_beacon_recv(buf); break; case BT_DATA_UUID16_SOME: /* Fall through */ case BT_DATA_UUID16_ALL: if (IS_ENABLED(CONFIG_BT_MESH_OD_PRIV_PROXY_SRV)) { /* Restore buffer with Solicitation PDU */ net_buf_simple_restore(buf, &state); bt_mesh_sol_recv(buf, len - 1); } break; default: break; } net_buf_simple_restore(buf, &state); net_buf_simple_pull(buf, len); } } int bt_mesh_scan_active_set(bool active) { if (active_scanning == active) { return 0; } active_scanning = active; bt_mesh_scan_disable(); return bt_mesh_scan_enable(); } int bt_mesh_scan_enable(void) { struct bt_le_scan_param scan_param = { .type = active_scanning ? BT_HCI_LE_SCAN_ACTIVE : BT_HCI_LE_SCAN_PASSIVE, .interval = MESH_SCAN_INTERVAL, .window = MESH_SCAN_WINDOW }; int err; LOG_DBG(""); err = bt_le_scan_start(&scan_param, bt_mesh_scan_cb); if (err && err != -EALREADY) { LOG_ERR("starting scan failed (err %d)", err); return err; } return 0; } int bt_mesh_scan_disable(void) { int err; LOG_DBG(""); err = bt_le_scan_stop(); if (err && err != -EALREADY) { LOG_ERR("stopping scan failed (err %d)", err); return err; } return 0; }