blob: ec6b13eb086880859be060149f61d5e80c1f238f [file] [log] [blame]
/*
* Copyright (c) 2022 Nordic Semiconductor
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/kernel.h>
#include <zephyr/bluetooth/hci.h>
#include "mesh_test.h"
#include "mesh/adv.h"
#include "mesh/net.h"
#include "mesh/mesh.h"
#include "mesh/foundation.h"
#define LOG_MODULE_NAME test_adv
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(LOG_MODULE_NAME, LOG_LEVEL_INF);
#define WAIT_TIME 60 /*seconds*/
enum bt_mesh_gatt_service {
MESH_SERVICE_PROVISIONING,
MESH_SERVICE_PROXY,
};
struct bt_mesh_test_adv {
uint8_t retr; /* number of retransmits of adv frame */
int64_t interval; /* interval of transmitted frames */
};
struct bt_mesh_test_gatt {
uint8_t transmits; /* number of frame (pb gatt or proxy beacon) transmits */
int64_t interval; /* interval of transmitted frames */
enum bt_mesh_gatt_service service;
};
extern const struct bt_mesh_comp comp;
static uint8_t test_prov_uuid[16] = { 0x6c, 0x69, 0x6e, 0x67, 0x61, 0xaa };
static const struct bt_mesh_test_cfg adv_cfg = {
.addr = 0x0001,
.dev_key = { 0x01 },
};
static struct bt_mesh_send_cb send_cb;
static struct bt_mesh_test_adv xmit_param;
static const char txt_msg[] = "adv test";
static const char cb_msg[] = "cb test";
static int64_t tx_timestamp;
static int seq_checker;
static struct bt_mesh_test_gatt gatt_param;
static int num_adv_sent;
static uint8_t previous_checker = 0xff;
static K_SEM_DEFINE(observer_sem, 0, 1);
static void test_tx_init(void)
{
bt_mesh_test_cfg_set(NULL, WAIT_TIME);
}
static void test_rx_init(void)
{
bt_mesh_test_cfg_set(NULL, WAIT_TIME);
}
static void bt_init(void)
{
ASSERT_OK(bt_enable(NULL), "Bluetooth init failed");
LOG_INF("Bluetooth initialized");
}
static void adv_init(void)
{
bt_mesh_adv_init();
ASSERT_OK(bt_mesh_adv_enable(), "Mesh adv init failed");
}
static void allocate_all_array(struct net_buf **buf, size_t num_buf, uint8_t xmit)
{
for (int i = 0; i < num_buf; i++) {
*buf = bt_mesh_adv_create(BT_MESH_ADV_DATA, BT_MESH_LOCAL_ADV,
xmit, K_NO_WAIT);
ASSERT_FALSE(!*buf, "Out of buffers");
buf++;
}
}
static void verify_adv_queue_overflow(void)
{
struct net_buf *dummy_buf;
/* Verity Queue overflow */
dummy_buf = bt_mesh_adv_create(BT_MESH_ADV_DATA, BT_MESH_LOCAL_ADV,
BT_MESH_TRANSMIT(2, 20), K_NO_WAIT);
ASSERT_TRUE(!dummy_buf, "Unexpected extra buffer");
}
static bool check_delta_time(uint8_t transmit, uint64_t interval)
{
static int cnt;
static int64_t timestamp;
if (cnt) {
int64_t delta = k_uptime_delta(&timestamp);
LOG_INF("rx: cnt(%d) delta(%dms)", cnt, delta);
ASSERT_TRUE(delta >= interval &&
delta < (interval + 15));
} else {
timestamp = k_uptime_get();
LOG_INF("rx: cnt(%d) delta(0ms)", cnt);
}
cnt++;
if (cnt >= transmit) {
cnt = 0;
timestamp = 0;
return true;
}
return false;
}
static void single_start_cb(uint16_t duration, int err, void *cb_data)
{
int64_t delta;
delta = k_uptime_delta(&tx_timestamp);
LOG_INF("tx start: +%d ms", delta);
ASSERT_TRUE(duration >= 90 && duration <= 200);
ASSERT_EQUAL(0, err);
ASSERT_EQUAL(cb_msg, cb_data);
ASSERT_EQUAL(0, seq_checker & 1);
seq_checker++;
}
static void single_end_cb(int err, void *cb_data)
{
int64_t delta;
delta = k_uptime_delta(&tx_timestamp);
LOG_INF("tx end: +%d ms", delta);
ASSERT_EQUAL(0, err);
ASSERT_EQUAL(cb_msg, cb_data);
ASSERT_EQUAL(1, seq_checker & 1);
seq_checker++;
k_sem_give(&observer_sem);
}
static void realloc_end_cb(int err, void *cb_data)
{
struct net_buf *buf = (struct net_buf *)cb_data;
ASSERT_EQUAL(0, err);
buf = bt_mesh_adv_create(BT_MESH_ADV_DATA, BT_MESH_LOCAL_ADV,
BT_MESH_TRANSMIT(2, 20), K_NO_WAIT);
ASSERT_FALSE(!buf, "Out of buffers");
k_sem_give(&observer_sem);
}
static void seq_start_cb(uint16_t duration, int err, void *cb_data)
{
ASSERT_EQUAL(0, err);
ASSERT_EQUAL(seq_checker, (intptr_t)cb_data);
}
static void seq_end_cb(int err, void *cb_data)
{
ASSERT_EQUAL(0, err);
ASSERT_EQUAL(seq_checker, (intptr_t)cb_data);
seq_checker++;
if (seq_checker == CONFIG_BT_MESH_ADV_BUF_COUNT) {
k_sem_give(&observer_sem);
}
}
static void parse_mesh_gatt_preamble(struct net_buf_simple *buf)
{
ASSERT_EQUAL(0x0201, net_buf_simple_pull_be16(buf));
/* flags */
(void)net_buf_simple_pull_u8(buf);
ASSERT_EQUAL(0x0303, net_buf_simple_pull_be16(buf));
}
static void parse_mesh_pb_gatt_service(struct net_buf_simple *buf)
{
/* Figure 7.1: PB-GATT Advertising Data */
/* mesh provisioning service */
ASSERT_EQUAL(0x2718, net_buf_simple_pull_be16(buf));
ASSERT_EQUAL(0x1516, net_buf_simple_pull_be16(buf));
/* mesh provisioning service */
ASSERT_EQUAL(0x2718, net_buf_simple_pull_be16(buf));
}
static void parse_mesh_proxy_service(struct net_buf_simple *buf)
{
/* Figure 7.2: Advertising with Network ID (Identification Type 0x00) */
/* mesh proxy service */
ASSERT_EQUAL(0x2818, net_buf_simple_pull_be16(buf));
ASSERT_EQUAL(0x0c16, net_buf_simple_pull_be16(buf));
/* mesh proxy service */
ASSERT_EQUAL(0x2818, net_buf_simple_pull_be16(buf));
/* network ID */
ASSERT_EQUAL(0x00, net_buf_simple_pull_u8(buf));
}
static void gatt_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_IND) {
return;
}
parse_mesh_gatt_preamble(buf);
if (gatt_param.service == MESH_SERVICE_PROVISIONING) {
parse_mesh_pb_gatt_service(buf);
} else {
parse_mesh_proxy_service(buf);
}
LOG_INF("rx: %s", txt_msg);
if (check_delta_time(gatt_param.transmits, gatt_param.interval)) {
LOG_INF("rx completed. stop observer.");
k_sem_give(&observer_sem);
}
}
static void rx_gatt_beacons(void)
{
struct bt_le_scan_param scan_param = {
.type = BT_HCI_LE_SCAN_PASSIVE,
.options = BT_LE_SCAN_OPT_NONE,
.interval = BT_MESH_ADV_SCAN_UNIT(1000),
.window = BT_MESH_ADV_SCAN_UNIT(1000)
};
int err;
err = bt_le_scan_start(&scan_param, gatt_scan_cb);
ASSERT_FALSE(err && err != -EALREADY, "starting scan failed (err %d)", err);
err = k_sem_take(&observer_sem, K_SECONDS(20));
ASSERT_OK(err);
err = bt_le_scan_stop();
ASSERT_FALSE(err && err != -EALREADY, "stopping scan failed (err %d)", err);
}
static void xmit_scan_cb(const bt_addr_le_t *addr, int8_t rssi, uint8_t adv_type,
struct net_buf_simple *buf)
{
uint8_t length;
if (adv_type != BT_GAP_ADV_TYPE_ADV_NONCONN_IND) {
return;
}
length = net_buf_simple_pull_u8(buf);
ASSERT_EQUAL(buf->len, length);
ASSERT_EQUAL(length, sizeof(uint8_t) + sizeof(txt_msg));
ASSERT_EQUAL(BT_DATA_MESH_MESSAGE, net_buf_simple_pull_u8(buf));
char *data = net_buf_simple_pull_mem(buf, sizeof(txt_msg));
LOG_INF("rx: %s", txt_msg);
ASSERT_EQUAL(0, memcmp(txt_msg, data, sizeof(txt_msg)));
/* Add 1 initial transmit to the retransmit. */
if (check_delta_time(xmit_param.retr + 1, xmit_param.interval)) {
LOG_INF("rx completed. stop observer.");
k_sem_give(&observer_sem);
}
}
static void rx_xmit_adv(void)
{
struct bt_le_scan_param scan_param = {
.type = BT_HCI_LE_SCAN_PASSIVE,
.options = BT_LE_SCAN_OPT_NONE,
.interval = BT_MESH_ADV_SCAN_UNIT(1000),
.window = BT_MESH_ADV_SCAN_UNIT(1000)
};
int err;
err = bt_le_scan_start(&scan_param, xmit_scan_cb);
ASSERT_FALSE(err && err != -EALREADY, "starting scan failed (err %d)", err);
err = k_sem_take(&observer_sem, K_SECONDS(20));
ASSERT_OK(err);
err = bt_le_scan_stop();
ASSERT_FALSE(err && err != -EALREADY, "stopping scan failed (err %d)", err);
}
static void send_order_start_cb(uint16_t duration, int err, void *user_data)
{
struct net_buf *buf = (struct net_buf *)user_data;
ASSERT_OK(err, "Failed adv start cb err (%d)", err);
ASSERT_EQUAL(2, buf->len);
uint8_t current = buf->data[0];
uint8_t previous = buf->data[1];
LOG_INF("tx start: current(%d) previous(%d)", current, previous);
ASSERT_EQUAL(previous_checker, previous);
previous_checker = current;
}
static void send_order_end_cb(int err, void *user_data)
{
struct net_buf *buf = (struct net_buf *)user_data;
ASSERT_OK(err, "Failed adv start cb err (%d)", err);
ASSERT_TRUE(!buf->data, "Data not cleared!");
seq_checker++;
LOG_INF("tx end: seq(%d)", seq_checker);
if (seq_checker == num_adv_sent) {
seq_checker = 0;
previous_checker = 0xff;
k_sem_give(&observer_sem);
}
}
static void receive_order_scan_cb(const bt_addr_le_t *addr, int8_t rssi, uint8_t adv_type,
struct net_buf_simple *buf)
{
uint8_t length;
uint8_t current;
uint8_t previous;
length = net_buf_simple_pull_u8(buf);
ASSERT_EQUAL(buf->len, length);
ASSERT_EQUAL(BT_DATA_MESH_MESSAGE, net_buf_simple_pull_u8(buf));
current = net_buf_simple_pull_u8(buf);
previous = net_buf_simple_pull_u8(buf);
LOG_INF("rx: current(%d) previous(%d)", current, previous);
ASSERT_EQUAL(previous_checker, previous);
/* Add 1 initial transmit to the retransmit. */
if (check_delta_time(xmit_param.retr + 1, xmit_param.interval)) {
previous_checker = current;
k_sem_give(&observer_sem);
}
}
static void receive_order(int expect_adv)
{
struct bt_le_scan_param scan_param = {
.type = BT_HCI_LE_SCAN_PASSIVE,
.options = BT_LE_SCAN_OPT_NONE,
.interval = BT_MESH_ADV_SCAN_UNIT(1000),
.window = BT_MESH_ADV_SCAN_UNIT(1000)
};
int err;
err = bt_le_scan_start(&scan_param, receive_order_scan_cb);
ASSERT_FALSE(err && err != -EALREADY, "starting scan failed (err %d)", err);
previous_checker = 0xff;
for (int i = 0; i < expect_adv; i++) {
err = k_sem_take(&observer_sem, K_SECONDS(10));
ASSERT_OK(err, "Didn't receive adv in time");
}
err = bt_le_scan_stop();
ASSERT_FALSE(err && err != -EALREADY, "stopping scan failed (err %d)", err);
}
static void send_adv_buf(struct net_buf *buf, uint8_t curr, uint8_t prev)
{
send_cb.start = send_order_start_cb;
send_cb.end = send_order_end_cb;
(void)net_buf_add_u8(buf, curr);
(void)net_buf_add_u8(buf, prev);
bt_mesh_adv_send(buf, &send_cb, buf);
net_buf_unref(buf);
}
static void send_adv_array(struct net_buf **buf, size_t num_buf, bool reverse)
{
uint8_t previous;
int i;
num_adv_sent = num_buf;
previous = 0xff;
if (!reverse) {
i = 0;
} else {
i = num_buf - 1;
}
while ((!reverse && i < num_buf) || (reverse && i >= 0)) {
send_adv_buf(*buf, (uint8_t)i, previous);
previous = (uint8_t)i;
if (!reverse) {
buf++;
i++;
} else {
buf--;
i--;
}
}
}
static void test_tx_cb_single(void)
{
struct net_buf *buf;
int err;
bt_init();
adv_init();
buf = bt_mesh_adv_create(BT_MESH_ADV_DATA, BT_MESH_LOCAL_ADV,
BT_MESH_TRANSMIT(2, 20), K_NO_WAIT);
ASSERT_FALSE(!buf, "Out of buffers");
send_cb.start = single_start_cb;
send_cb.end = single_end_cb;
net_buf_add_mem(buf, txt_msg, sizeof(txt_msg));
seq_checker = 0;
tx_timestamp = k_uptime_get();
bt_mesh_adv_send(buf, &send_cb, (void *)cb_msg);
net_buf_unref(buf);
err = k_sem_take(&observer_sem, K_SECONDS(1));
ASSERT_OK(err, "Didn't call end tx cb.");
PASS();
}
static void test_rx_xmit(void)
{
xmit_param.retr = 2;
xmit_param.interval = 20;
bt_init();
rx_xmit_adv();
PASS();
}
static void test_tx_cb_multi(void)
{
struct net_buf *buf[CONFIG_BT_MESH_ADV_BUF_COUNT];
int err;
bt_init();
adv_init();
/* Allocate all network buffers. */
allocate_all_array(buf, ARRAY_SIZE(buf), BT_MESH_TRANSMIT(2, 20));
/* Start single adv to reallocate one network buffer in callback.
* Check that the buffer is freed before cb is triggered.
*/
send_cb.start = NULL;
send_cb.end = realloc_end_cb;
net_buf_add_mem(buf[0], txt_msg, sizeof(txt_msg));
bt_mesh_adv_send(buf[0], &send_cb, buf[0]);
net_buf_unref(buf[0]);
err = k_sem_take(&observer_sem, K_SECONDS(1));
ASSERT_OK(err, "Didn't call the end tx cb that reallocates buffer one more time.");
/* Start multi advs to check that all buffers are sent and cbs are triggered. */
send_cb.start = seq_start_cb;
send_cb.end = seq_end_cb;
seq_checker = 0;
for (int i = 0; i < CONFIG_BT_MESH_ADV_BUF_COUNT; i++) {
net_buf_add_le32(buf[i], i);
bt_mesh_adv_send(buf[i], &send_cb, (void *)(intptr_t)i);
net_buf_unref(buf[i]);
}
err = k_sem_take(&observer_sem, K_SECONDS(10));
ASSERT_OK(err, "Didn't call the last end tx cb.");
PASS();
}
static void test_tx_proxy_mixin(void)
{
static struct bt_mesh_prov prov = {
.uuid = test_prov_uuid,
};
uint8_t status;
int err;
/* Initialize mesh stack and enable pb gatt bearer to emit beacons. */
bt_mesh_device_setup(&prov, &comp);
err = bt_mesh_prov_enable(BT_MESH_PROV_GATT);
ASSERT_OK(err, "Failed to enable GATT provisioner");
/* Let the tester to measure an interval between advertisements.
* The node should advertise pb gatt service with 100 msec interval.
*/
k_sleep(K_MSEC(1800));
LOG_INF("Provision device under test");
/* Provision dut and start gatt proxy beacons. */
bt_mesh_provision(test_net_key, 0, 0, 0, adv_cfg.addr, adv_cfg.dev_key);
/* Disable secured network beacons to exclude influence of them on proxy beaconing. */
ASSERT_OK(bt_mesh_cfg_cli_beacon_set(0, adv_cfg.addr, BT_MESH_BEACON_DISABLED, &status));
ASSERT_EQUAL(BT_MESH_BEACON_DISABLED, status);
/* Let the tester to measure an interval between advertisements.
* The node should advertise proxy service with 1 second interval.
*/
k_sleep(K_MSEC(6000));
/* Send a mesh message while advertising proxy service.
* Advertising the proxy service should be resumed after
* finishing advertising the message.
*/
struct net_buf *buf = bt_mesh_adv_create(BT_MESH_ADV_DATA, BT_MESH_LOCAL_ADV,
BT_MESH_TRANSMIT(5, 20), K_NO_WAIT);
net_buf_add_mem(buf, txt_msg, sizeof(txt_msg));
bt_mesh_adv_send(buf, NULL, NULL);
k_sleep(K_MSEC(150));
/* Let the tester to measure an interval between advertisements again. */
k_sleep(K_MSEC(6000));
PASS();
}
static void test_rx_proxy_mixin(void)
{
/* (total transmit duration) / (transmit interval) */
gatt_param.transmits = 1500 / 100;
gatt_param.interval = 100;
gatt_param.service = MESH_SERVICE_PROVISIONING;
bt_init();
/* Scan pb gatt beacons. */
rx_gatt_beacons();
/* Delay to provision dut */
k_sleep(K_MSEC(1000));
/* Scan proxy beacons. */
/* (total transmit duration) / (transmit interval) */
gatt_param.transmits = 5000 / 1000;
gatt_param.interval = 1000;
gatt_param.service = MESH_SERVICE_PROXY;
rx_gatt_beacons();
/* Scan adv data. */
xmit_param.retr = 5;
xmit_param.interval = 20;
rx_xmit_adv();
/* Scan proxy beacons again. */
rx_gatt_beacons();
PASS();
}
static void test_tx_send_order(void)
{
struct net_buf *buf[CONFIG_BT_MESH_ADV_BUF_COUNT];
uint8_t xmit = BT_MESH_TRANSMIT(2, 20);
bt_init();
adv_init();
/* Verify sending order */
allocate_all_array(buf, ARRAY_SIZE(buf), xmit);
verify_adv_queue_overflow();
send_adv_array(&buf[0], ARRAY_SIZE(buf), false);
/* Wait for no message receive window to end. */
ASSERT_OK(k_sem_take(&observer_sem, K_SECONDS(10)),
"Didn't call the last end tx cb.");
/* Verify buffer allocation/deallocation after sending */
allocate_all_array(buf, ARRAY_SIZE(buf), xmit);
verify_adv_queue_overflow();
for (int i = 0; i < CONFIG_BT_MESH_ADV_BUF_COUNT; i++) {
net_buf_unref(buf[i]);
buf[i] = NULL;
}
/* Check that it possible to add just one net buf. */
allocate_all_array(buf, 1, xmit);
PASS();
}
static void test_tx_reverse_order(void)
{
struct net_buf *buf[CONFIG_BT_MESH_ADV_BUF_COUNT];
uint8_t xmit = BT_MESH_TRANSMIT(2, 20);
bt_init();
adv_init();
/* Verify reversed sending order */
allocate_all_array(buf, ARRAY_SIZE(buf), xmit);
send_adv_array(&buf[CONFIG_BT_MESH_ADV_BUF_COUNT - 1], ARRAY_SIZE(buf), true);
/* Wait for no message receive window to end. */
ASSERT_OK(k_sem_take(&observer_sem, K_SECONDS(10)),
"Didn't call the last end tx cb.");
PASS();
}
static void test_tx_random_order(void)
{
struct net_buf *buf[3];
uint8_t xmit = BT_MESH_TRANSMIT(0, 20);
bt_init();
adv_init();
/* Verify random order calls */
num_adv_sent = ARRAY_SIZE(buf);
previous_checker = 0xff;
buf[0] = bt_mesh_adv_create(BT_MESH_ADV_DATA, BT_MESH_LOCAL_ADV,
xmit, K_NO_WAIT);
ASSERT_FALSE(!buf[0], "Out of buffers");
buf[1] = bt_mesh_adv_create(BT_MESH_ADV_DATA, BT_MESH_LOCAL_ADV,
xmit, K_NO_WAIT);
ASSERT_FALSE(!buf[1], "Out of buffers");
send_adv_buf(buf[0], 0, 0xff);
buf[2] = bt_mesh_adv_create(BT_MESH_ADV_DATA, BT_MESH_LOCAL_ADV,
xmit, K_NO_WAIT);
ASSERT_FALSE(!buf[2], "Out of buffers");
send_adv_buf(buf[2], 2, 0);
send_adv_buf(buf[1], 1, 2);
/* Wait for no message receive window to end. */
ASSERT_OK(k_sem_take(&observer_sem, K_SECONDS(10)),
"Didn't call the last end tx cb.");
PASS();
}
static void test_rx_receive_order(void)
{
bt_init();
xmit_param.retr = 2;
xmit_param.interval = 20;
receive_order(CONFIG_BT_MESH_ADV_BUF_COUNT);
PASS();
}
static void test_rx_random_order(void)
{
bt_init();
xmit_param.retr = 0;
xmit_param.interval = 20;
receive_order(3);
PASS();
}
#define TEST_CASE(role, name, description) \
{ \
.test_id = "adv_" #role "_" #name, \
.test_descr = description, \
.test_pre_init_f = test_##role##_init, \
.test_tick_f = bt_mesh_test_timeout, \
.test_main_f = test_##role##_##name, \
}
static const struct bst_test_instance test_adv[] = {
TEST_CASE(tx, cb_single, "ADV: tx cb parameter checker"),
TEST_CASE(tx, cb_multi, "ADV: tx cb sequence checker"),
TEST_CASE(tx, proxy_mixin, "ADV: proxy mix-in gatt adv"),
TEST_CASE(tx, send_order, "ADV: tx send order"),
TEST_CASE(tx, reverse_order, "ADV: tx reversed order"),
TEST_CASE(tx, random_order, "ADV: tx random order"),
TEST_CASE(rx, xmit, "ADV: xmit checker"),
TEST_CASE(rx, proxy_mixin, "ADV: proxy mix-in scanner"),
TEST_CASE(rx, receive_order, "ADV: rx receive order"),
TEST_CASE(rx, random_order, "ADV: rx random order"),
BSTEST_END_MARKER
};
struct bst_test_list *test_adv_install(struct bst_test_list *tests)
{
tests = bst_add_tests(tests, test_adv);
return tests;
}