blob: 5ec6a8dc68525e7e523a27db08c7065e40ba7929 [file] [log] [blame]
/*
* Copyright (c) 2023 Nordic Semiconductor
*
* SPDX-License-Identifier: Apache-2.0
*
*/
#include "mesh_test.h"
#include "gatt_common.h"
#include <mesh/access.h>
#include <mesh/net.h>
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(test_suspend, LOG_LEVEL_INF);
#define WAIT_TIME 60 /* seconds */
#define SUSPEND_DURATION 15 /* seconds */
#define NUM_PUB 4 /* Times the DUT will publish per interval. */
#define TEST_MODEL_ID_1 0x2a2a
#define TEST_MODEL_ID_2 0x2b2b
#define TEST_MESSAGE_OP 0x1f
enum dut_mesh_status {
DUT_SUSPENDED,
DUT_RUNNING,
};
static int model_1_init(const struct bt_mesh_model *model);
static int model_2_init(const struct bt_mesh_model *model);
static uint8_t app_key[16] = {0xaa};
static uint8_t net_key[16] = {0xcc};
static const struct bt_mesh_test_cfg dut_cfg = {
.addr = 0x00a0,
.dev_key = {0x01},
};
static const struct bt_mesh_test_cfg tester_cfg = {
.addr = 0x00b0,
.dev_key = {0x02},
};
static uint8_t test_prov_uuid[16] = { 0x6c, 0x69, 0x6e, 0x67, 0x61, 0xaa };
static struct bt_mesh_prov prov = {
.uuid = test_prov_uuid,
};
static struct k_sem publish_sem;
static enum dut_mesh_status dut_status;
static int model_1_update(const struct bt_mesh_model *model)
{
model->pub->msg->data[1]++;
LOG_DBG("Model 1 publishing..., n: %d", model->pub->msg->data[1]);
k_sem_give(&publish_sem);
return 0;
}
static int msg_handler(const struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx,
struct net_buf_simple *buf)
{
static uint8_t prev_num;
static int64_t uptime;
uint8_t num = net_buf_simple_pull_u8(buf);
LOG_DBG("Received msg, n: %d", num);
/* Ensure that payload changes. */
ASSERT_TRUE(prev_num != num);
prev_num = num;
/* Ensure that no message is received while Mesh is suspended or disabled.
* A publication may be sent just before DUT is suspended, which is ignored.
*/
if ((dut_status == DUT_SUSPENDED) && !(num == (NUM_PUB + 1))) {
if (SUSPEND_DURATION * 1000ll <= k_uptime_delta(&uptime)) {
dut_status = DUT_RUNNING;
LOG_DBG("Suspend duration passed. Setting status to %d.", dut_status);
} else {
FAIL("Received publication while Mesh is suspended.");
}
}
if (num == NUM_PUB) {
dut_status = DUT_SUSPENDED;
LOG_DBG("Expected number of pubs received. Setting status to %d.", dut_status);
uptime = k_uptime_get();
}
k_sem_give(&publish_sem);
return 0;
}
BT_MESH_MODEL_PUB_DEFINE(model_1_pub, model_1_update, 4);
static const struct bt_mesh_model_cb model_1_cb = {
.init = model_1_init,
};
static const struct bt_mesh_model_cb model_2_cb = {
.init = model_2_init,
};
static const struct bt_mesh_model_op model_op_1[] = {BT_MESH_MODEL_OP_END};
static const struct bt_mesh_model_op model_op_2[] = {{TEST_MESSAGE_OP, 0, msg_handler},
BT_MESH_MODEL_OP_END};
static struct bt_mesh_cfg_cli cfg_cli_dut;
static struct bt_mesh_model dut_models[] = {
BT_MESH_MODEL_CFG_SRV,
BT_MESH_MODEL_CFG_CLI(&cfg_cli_dut),
BT_MESH_MODEL_CB(TEST_MODEL_ID_1, model_op_1, &model_1_pub, NULL, &model_1_cb),
};
static struct bt_mesh_elem dut_elems[] = {
BT_MESH_ELEM(0, dut_models, BT_MESH_MODEL_NONE),
};
static const struct bt_mesh_comp dut_comp = {
.cid = TEST_VND_COMPANY_ID,
.vid = 0xeeee,
.pid = 0xaaaa,
.elem = dut_elems,
.elem_count = ARRAY_SIZE(dut_elems),
};
static struct bt_mesh_cfg_cli cfg_cli_tester;
static struct bt_mesh_model tester_models[] = {
BT_MESH_MODEL_CFG_SRV,
BT_MESH_MODEL_CFG_CLI(&cfg_cli_tester),
BT_MESH_MODEL_CB(TEST_MODEL_ID_2, model_op_2, NULL, NULL, &model_2_cb),
};
static struct bt_mesh_elem tester_elems[] = {
BT_MESH_ELEM(0, tester_models, BT_MESH_MODEL_NONE),
};
static const struct bt_mesh_comp tester_comp = {
.cid = TEST_VND_COMPANY_ID,
.vid = 0xbaaa,
.pid = 0xb000,
.elem = tester_elems,
.elem_count = ARRAY_SIZE(tester_elems),
};
static int model_1_init(const struct bt_mesh_model *model)
{
bt_mesh_model_msg_init(model->pub->msg, TEST_MESSAGE_OP);
net_buf_simple_add_u8(model->pub->msg, 1);
return 0;
}
static int model_2_init(const struct bt_mesh_model *model)
{
return 0;
}
static void provision_and_configure(struct bt_mesh_test_cfg cfg, bool tester)
{
int err;
uint8_t status;
ASSERT_OK(bt_mesh_provision(net_key, 0, 0, 0, cfg.addr, cfg.dev_key));
err = bt_mesh_cfg_cli_app_key_add(0, cfg.addr, 0, 0, app_key, &status);
if (err || status) {
FAIL("AppKey add failed (err %d, status %u)", err, status);
}
const struct bt_mesh_test_cfg *pcfg = tester ? &tester_cfg : &dut_cfg;
uint16_t model_id = tester ? TEST_MODEL_ID_2 : TEST_MODEL_ID_1;
err = bt_mesh_cfg_cli_mod_app_bind(0, pcfg->addr, pcfg->addr, 0, model_id, &status);
if (err || status) {
FAIL("Model %#4x bind failed (err %d, status %u)", model_id, err, status);
}
}
struct bt_mesh_cfg_cli_mod_pub pub_params = {
.addr = tester_cfg.addr,
.uuid = NULL,
.cred_flag = false,
.app_idx = 0,
.ttl = 5,
.period = BT_MESH_PUB_PERIOD_SEC(1),
.transmit = 0,
};
extern const struct bt_mesh_comp comp;
/* For legacy adv, pb-gatt advs are sent with a 1000ms interval. For ext adv, they are sent
* with a 100ms interval.
*/
static struct bt_mesh_test_gatt gatt_param = {
#if defined(CONFIG_BT_EXT_ADV)
/* (total transmit duration) / (transmit interval) */
.transmits = 1500 / 100,
.interval = 100,
#else
.transmits = 2000 / 1000,
.interval = 1000,
#endif
.service = MESH_SERVICE_PROVISIONING,
};
static bool gatt_check_rx_count(uint8_t transmit)
{
static int cnt;
LOG_DBG("rx: cnt(%d)", cnt);
cnt++;
if (cnt >= transmit) {
cnt = 0;
return true;
}
return false;
}
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;
}
/* Ensure that no message is received while Mesh is suspended or disabled. */
ASSERT_FALSE_MSG(dut_status == DUT_SUSPENDED, "Received adv while Mesh is suspended.");
bt_mesh_test_parse_mesh_gatt_preamble(buf);
if (gatt_param.service == MESH_SERVICE_PROVISIONING) {
LOG_DBG("Parsing pb_gatt adv");
bt_mesh_test_parse_mesh_pb_gatt_service(buf);
} else {
LOG_DBG("Parsing proxy adv");
bt_mesh_test_parse_mesh_proxy_service(buf);
}
if (gatt_check_rx_count(gatt_param.transmits)) {
LOG_DBG("rx completed, stopping scan...");
k_sem_give(&publish_sem);
}
}
static void suspend_state_change_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(enum dut_mesh_status));
ASSERT_EQUAL(BT_DATA_MESH_MESSAGE, net_buf_simple_pull_u8(buf));
enum dut_mesh_status *msg_status =
net_buf_simple_pull_mem(buf, sizeof(enum dut_mesh_status));
if ((*msg_status == DUT_RUNNING) || (*msg_status == DUT_SUSPENDED)) {
dut_status = *msg_status;
} else {
FAIL("Received unexpected data");
}
LOG_DBG("Received %d from DUT, setting status to %s",
*msg_status, (dut_status == DUT_SUSPENDED) ? "true" : "false");
k_sem_give(&publish_sem);
}
static void dut_pub_common(bool disable_bt)
{
bt_mesh_test_cfg_set(NULL, WAIT_TIME);
bt_mesh_device_setup(&prov, &dut_comp);
provision_and_configure(dut_cfg, 0);
k_sem_init(&publish_sem, 0, 1);
uint8_t status;
int err;
err = bt_mesh_cfg_cli_mod_pub_set(0, dut_cfg.addr, dut_cfg.addr, TEST_MODEL_ID_1,
&pub_params, &status);
if (err || status) {
FAIL("Mod pub set failed (err %d, status %u)", err, status);
}
/* Wait until node has published before suspending. */
for (int i = 0; i < NUM_PUB; i++) {
ASSERT_OK_MSG(k_sem_take(&publish_sem, K_SECONDS(30)), "Pub timed out");
}
ASSERT_OK_MSG(bt_mesh_suspend(), "Failed to suspend Mesh.");
if (disable_bt) {
ASSERT_OK_MSG(bt_disable(), "Failed to disable Bluetooth.");
}
k_sleep(K_SECONDS(SUSPEND_DURATION));
if (disable_bt) {
ASSERT_OK_MSG(bt_enable(NULL), "Failed to enable Bluetooth.");
}
ASSERT_OK_MSG(bt_mesh_resume(), "Failed to resume Mesh.");
for (int i = 0; i < NUM_PUB; i++) {
ASSERT_OK_MSG(k_sem_take(&publish_sem, K_SECONDS(30)), "Pub timed out");
}
/* Allow publishing to finish before suspending. */
k_sleep(K_MSEC(100));
ASSERT_OK(bt_mesh_suspend());
}
static void dut_gatt_common(bool disable_bt)
{
bt_mesh_test_cfg_set(NULL, WAIT_TIME);
bt_mesh_device_setup(&prov, &comp);
ASSERT_OK_MSG(bt_mesh_prov_enable(BT_MESH_PROV_GATT), "Failed to enable GATT provisioner");
dut_status = DUT_RUNNING;
/* Let the Tester observe pb gatt advertisements before provisioning. The node should
* advertise pb gatt service with 100 msec (ext adv) or 1000msec (legacy adv) interval.
*/
k_sleep(K_MSEC(1800));
ASSERT_OK(bt_mesh_provision(test_net_key, 0, 0, 0, dut_cfg.addr, dut_cfg.dev_key));
/* Let the Tester observe proxy advertisements */
k_sleep(K_MSEC(6500));
/* Send a mesh message to notify Tester that DUT is about to be suspended. */
dut_status = DUT_SUSPENDED;
bt_mesh_test_send_over_adv(&dut_status, sizeof(enum dut_mesh_status));
k_sleep(K_MSEC(150));
ASSERT_OK_MSG(bt_mesh_suspend(), "Failed to suspend Mesh.");
if (disable_bt) {
ASSERT_OK_MSG(bt_disable(), "Failed to disable Bluetooth.");
}
k_sleep(K_SECONDS(SUSPEND_DURATION));
if (disable_bt) {
ASSERT_OK_MSG(bt_enable(NULL), "Failed to enable Bluetooth.");
}
ASSERT_OK_MSG(bt_mesh_resume(), "Failed to resume Mesh.");
/* Send a mesh message to notify Tester that device is resumed */
dut_status = DUT_RUNNING;
bt_mesh_test_send_over_adv(&dut_status, sizeof(enum dut_mesh_status));
/* Let the Tester observe that proxy advertisement resumes */
k_sleep(K_MSEC(6000));
}
static void test_dut_suspend_resume(void)
{
dut_pub_common(false);
PASS();
}
static void test_dut_suspend_disable_resume(void)
{
dut_pub_common(true);
PASS();
}
static void test_tester_pub(void)
{
bt_mesh_test_cfg_set(NULL, WAIT_TIME);
bt_mesh_device_setup(&prov, &tester_comp);
provision_and_configure(tester_cfg, 1);
k_sem_init(&publish_sem, 0, 1);
dut_status = DUT_RUNNING;
/* Receive messages before and after suspending. A publication may get lost
* when suspending immeditiately after publication, thus the "-1".
*/
for (int i = 0; i < NUM_PUB * 2 - 1; i++) {
ASSERT_OK_MSG(k_sem_take(&publish_sem, K_SECONDS(30)), "Receiver timed out");
}
PASS();
}
static void test_dut_gatt_suspend_resume(void)
{
dut_gatt_common(false);
PASS();
}
static void test_dut_gatt_suspend_disable_resume(void)
{
dut_gatt_common(true);
PASS();
}
static void test_tester_gatt(void)
{
k_sem_init(&publish_sem, 0, 1);
dut_status = DUT_RUNNING;
int err;
ASSERT_OK_MSG(bt_enable(NULL), "Bluetooth init failed");
/* Scan pb gatt beacons. */
ASSERT_OK(bt_mesh_test_wait_for_packet(gatt_scan_cb, &publish_sem, 10));
/* Delay to provision DUT */
k_sleep(K_MSEC(1000));
/* Scan gatt proxy beacons. */
/* (total transmit duration) / (transmit interval) */
gatt_param.transmits = 5000 / 1000;
gatt_param.interval = 1000;
gatt_param.service = MESH_SERVICE_PROXY;
ASSERT_OK(bt_mesh_test_wait_for_packet(gatt_scan_cb, &publish_sem, 10));
/* Allow DUT to suspend before scanning for gatt proxy beacons */
ASSERT_OK(bt_mesh_test_wait_for_packet(suspend_state_change_cb, &publish_sem, 20));
ASSERT_EQUAL(dut_status, DUT_SUSPENDED);
k_sleep(K_MSEC(500));
err = bt_mesh_test_wait_for_packet(gatt_scan_cb, &publish_sem, 10);
ASSERT_FALSE(err && err != -ETIMEDOUT);
/* Wait for DUT to resume Mesh and notify Tester, then scan for gatt proxy beacons */
ASSERT_OK(bt_mesh_test_wait_for_packet(suspend_state_change_cb, &publish_sem, 20));
ASSERT_EQUAL(dut_status, DUT_RUNNING);
ASSERT_OK(bt_mesh_test_wait_for_packet(gatt_scan_cb, &publish_sem, 10));
PASS();
}
#define TEST_CASE(role, name, description) \
{ \
.test_id = "suspend_" #role "_" #name, .test_descr = description, \
.test_tick_f = bt_mesh_test_timeout, .test_main_f = test_##role##_##name, \
}
static const struct bst_test_instance test_suspend[] = {
TEST_CASE(dut, suspend_resume,
"Suspend and resume Mesh with periodic pub"),
TEST_CASE(dut, suspend_disable_resume,
"Suspend and resume Mesh (and disable/enable BT) with periodic pub"),
TEST_CASE(dut, gatt_suspend_resume,
"Suspend and resume Mesh with GATT proxy advs"),
TEST_CASE(dut, gatt_suspend_disable_resume,
"Suspend and resume Mesh (and disable/enable BT) with GATT proxy advs"),
TEST_CASE(tester, pub, "Scan and verify behavior of periodic publishing adv"),
TEST_CASE(tester, gatt, "Scan and verify behavior of GATT proxy adv"),
BSTEST_END_MARKER};
struct bst_test_list *test_suspend_install(struct bst_test_list *tests)
{
tests = bst_add_tests(tests, test_suspend);
return tests;
}